Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: The History of Git Worktrees
description: How git worktrees evolved from a niche feature to the foundation of modern parallel development workflows.
author: Avi Peltz
author: avi
date: 2025-01-18
category: Research
---
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: Introducing Superset
description: Run 10+ parallel coding agents on your machine. A new way to work with AI coding assistants.
author: Avi Peltz
author: avi
date: 2025-01-15
category: Product
---
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: A Guide to Parallel Coding Agents
description: Learn how to maximize your productivity by running multiple AI coding agents simultaneously.
author: Avi Peltz
author: avi
date: 2025-01-20
category: Research
---
Expand Down
2 changes: 1 addition & 1 deletion apps/marketing/content/blog/how-to-get-hit.mdx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: How to get hit (probably in the face but anywhere else works too)
description: I ran out of technical content to write for the blog so here is something I am interested in instead.
author: Kiet Ho
author: kiet
date: 2026-01-28
category: Company
relatedSlugs:
Expand Down
2 changes: 1 addition & 1 deletion apps/marketing/content/blog/terminal-daemon-deep-dive.mdx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: "The Terminal That (Almost) Never Dies: Building a Persistent Terminal Daemon for Electron"
description: "How we built a process-isolated terminal host that survives app restarts, handles backpressure gracefully, and enables cold restore from disk."
author: Avi Peltz
author: avi
date: 2026-01-26
category: Engineering
relatedSlugs:
Expand Down
8 changes: 8 additions & 0 deletions apps/marketing/content/people/avi.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
name: Avi Peltz
role: Cofounder
avatar: /team/avi.jpg
twitter: avimakesrobots
github: avipeltz
linkedin: avipeltz
---
8 changes: 8 additions & 0 deletions apps/marketing/content/people/kiet.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
name: Kiet Ho
role: Cofounder
avatar: /team/kiet.jpg
twitter: kietho_
github: kietho
linkedin: kiet-ho
---
8 changes: 8 additions & 0 deletions apps/marketing/content/people/satya.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
name: Satya Patel
role: Cofounder
avatar: /team/satya.webp
twitter: saddle_paddle
github: saddlepaddle
linkedin: saddlepaddle
---
Binary file added apps/marketing/public/team/avi.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/marketing/public/team/kiet.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/marketing/public/team/satya.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
"use client";

import { ArrowLeft } from "lucide-react";
import Link from "next/link";
import type { ReactNode } from "react";
import {
RiGithubFill,
RiLinkedinBoxFill,
RiTwitterXFill,
} from "react-icons/ri";
import { AuthorAvatar } from "@/app/blog/components/AuthorAvatar";
import { BlogCard } from "@/app/blog/components/BlogCard";
import { GridCross } from "@/app/blog/components/GridCross";
Expand All @@ -21,6 +24,7 @@ export function BlogPostLayout({
children,
}: BlogPostLayoutProps) {
const formattedDate = formatBlogDate(post.date);
const { author } = post;

return (
<article className="relative min-h-screen">
Expand Down Expand Up @@ -56,15 +60,48 @@ export function BlogPostLayout({
</p>
)}

<div className="flex items-center justify-center gap-3 text-sm text-muted-foreground">
<AuthorAvatar
name={post.author}
title="Cofounder, Superset"
twitterHandle="avimakesrobots"
/>
<span className="text-foreground/70">{post.author}</span>
<span className="text-muted-foreground/50">·</span>
<time dateTime={post.date}>{formattedDate}</time>
<div className="inline-flex items-center gap-3 text-sm text-muted-foreground mx-auto">
<AuthorAvatar name={author.name} avatar={author.avatar} />
<div className="flex flex-col items-start">
<span className="text-foreground/70">{author.name}</span>
<span className="text-xs text-muted-foreground">
{author.role}
<span className="text-muted-foreground/50"> · </span>
<time dateTime={post.date}>{formattedDate}</time>
</span>
</div>
</div>
<div className="inline-flex items-center gap-3 mt-3 mx-auto pl-11">
{author.twitter && (
<a
href={`https://x.com/${author.twitter}`}
target="_blank"
rel="noopener noreferrer"
className="text-muted-foreground hover:text-foreground transition-colors"
>
<RiTwitterXFill className="size-4" />
</a>
)}
{author.github && (
<a
href={`https://github.com/${author.github}`}
target="_blank"
rel="noopener noreferrer"
className="text-muted-foreground hover:text-foreground transition-colors"
>
<RiGithubFill className="size-4" />
</a>
)}
{author.linkedin && (
<a
href={`https://linkedin.com/in/${author.linkedin}`}
target="_blank"
rel="noopener noreferrer"
className="text-muted-foreground hover:text-foreground transition-colors"
>
<RiLinkedinBoxFill className="size-4" />
</a>
Comment on lines +75 to +103
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add accessible labels to icon-only social links.

Screen readers won’t have descriptive text for these links. Add aria-label (or visually hidden text) per link.

🔧 Suggested fix
 							{author.twitter && (
 								<a
 									href={`https://x.com/${author.twitter}`}
+									aria-label={`${author.name} on X`}
 									target="_blank"
 									rel="noopener noreferrer"
 									className="text-muted-foreground hover:text-foreground transition-colors"
 								>
 									<RiTwitterXFill className="size-4" />
 								</a>
 							)}
 							{author.github && (
 								<a
 									href={`https://github.com/${author.github}`}
+									aria-label={`${author.name} on GitHub`}
 									target="_blank"
 									rel="noopener noreferrer"
 									className="text-muted-foreground hover:text-foreground transition-colors"
 								>
 									<RiGithubFill className="size-4" />
 								</a>
 							)}
 							{author.linkedin && (
 								<a
 									href={`https://linkedin.com/in/${author.linkedin}`}
+									aria-label={`${author.name} on LinkedIn`}
 									target="_blank"
 									rel="noopener noreferrer"
 									className="text-muted-foreground hover:text-foreground transition-colors"
 								>
 									<RiLinkedinBoxFill className="size-4" />
 								</a>
 							)}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{author.twitter && (
<a
href={`https://x.com/${author.twitter}`}
target="_blank"
rel="noopener noreferrer"
className="text-muted-foreground hover:text-foreground transition-colors"
>
<RiTwitterXFill className="size-4" />
</a>
)}
{author.github && (
<a
href={`https://github.com/${author.github}`}
target="_blank"
rel="noopener noreferrer"
className="text-muted-foreground hover:text-foreground transition-colors"
>
<RiGithubFill className="size-4" />
</a>
)}
{author.linkedin && (
<a
href={`https://linkedin.com/in/${author.linkedin}`}
target="_blank"
rel="noopener noreferrer"
className="text-muted-foreground hover:text-foreground transition-colors"
>
<RiLinkedinBoxFill className="size-4" />
</a>
{author.twitter && (
<a
href={`https://x.com/${author.twitter}`}
aria-label={`${author.name} on X`}
target="_blank"
rel="noopener noreferrer"
className="text-muted-foreground hover:text-foreground transition-colors"
>
<RiTwitterXFill className="size-4" />
</a>
)}
{author.github && (
<a
href={`https://github.com/${author.github}`}
aria-label={`${author.name} on GitHub`}
target="_blank"
rel="noopener noreferrer"
className="text-muted-foreground hover:text-foreground transition-colors"
>
<RiGithubFill className="size-4" />
</a>
)}
{author.linkedin && (
<a
href={`https://linkedin.com/in/${author.linkedin}`}
aria-label={`${author.name} on LinkedIn`}
target="_blank"
rel="noopener noreferrer"
className="text-muted-foreground hover:text-foreground transition-colors"
>
<RiLinkedinBoxFill className="size-4" />
</a>
)}
🤖 Prompt for AI Agents
In
`@apps/marketing/src/app/blog/`[slug]/components/BlogPostLayout/BlogPostLayout.tsx
around lines 75 - 103, In BlogPostLayout, the social icon-only links (anchors
rendered when author.twitter, author.github, author.linkedin are present) lack
accessible labels; update each <a> that wraps RiTwitterXFill, RiGithubFill, and
RiLinkedinBoxFill to include an appropriate aria-label (for example "Author on
X", "Author on GitHub", "Author on LinkedIn") or add visually-hidden text inside
the link, so screen readers get descriptive text while preserving existing
classes, target, and rel attributes.

)}
</div>
</div>
</div>
Expand Down
20 changes: 18 additions & 2 deletions apps/marketing/src/app/blog/[slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,31 @@ export default async function BlogPostPage({ params }: PageProps) {
slug,
relatedSlugs: post.relatedSlugs,
});
const { author } = post;

const url = `${COMPANY.MARKETING_URL}/blog/${slug}`;

const sameAs: string[] = [];
if (author.twitter) {
sameAs.push(`https://x.com/${author.twitter}`);
}
if (author.github) {
sameAs.push(`https://github.com/${author.github}`);
}
if (author.linkedin) {
sameAs.push(`https://linkedin.com/in/${author.linkedin}`);
}

return (
<main>
<ArticleJsonLd
title={post.title}
description={post.description}
author={post.author}
author={{
name: author.name,
url: author.twitter ? `https://x.com/${author.twitter}` : undefined,
sameAs: sameAs.length > 0 ? sameAs : undefined,
}}
publishedTime={new Date(post.date).toISOString()}
url={url}
image={post.image}
Expand Down Expand Up @@ -85,7 +101,7 @@ export async function generateMetadata({
url,
siteName: COMPANY.NAME,
publishedTime: post.date,
authors: [post.author],
authors: [post.author.name],
...(post.image && { images: [post.image] }),
},
twitter: {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,79 +1,48 @@
"use client";
import Image from "next/image";

import { Tooltip, TooltipContent, TooltipTrigger } from "@superset/ui/tooltip";

function XIcon({ className }: { className?: string }) {
return (
<svg
viewBox="0 0 24 24"
aria-hidden="true"
className={className}
fill="currentColor"
>
<path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z" />
</svg>
);
}
const SIZE_CONFIG = {
sm: { container: "size-6", text: "size-6 text-[10px]", imgSize: "24px" },
md: { container: "size-8", text: "size-8 text-xs", imgSize: "32px" },
lg: { container: "size-12", text: "size-12 text-sm", imgSize: "48px" },
};

interface AuthorAvatarProps {
name: string;
twitterHandle?: string;
title?: string;
size?: "sm" | "md";
avatar?: string;
size?: "sm" | "md" | "lg";
}

export function AuthorAvatar({
name,
twitterHandle,
title,
size = "md",
}: AuthorAvatarProps) {
export function AuthorAvatar({ name, avatar, size = "md" }: AuthorAvatarProps) {
const initials = name
.split(" ")
.map((n) => n[0])
.join("")
.toUpperCase()
.slice(0, 2);

const sizeClasses = size === "sm" ? "size-6 text-[10px]" : "size-8 text-xs";
const config = SIZE_CONFIG[size];

if (avatar) {
return (
<div
className={`${config.container} relative rounded-full overflow-hidden flex-shrink-0`}
>
<Image
src={avatar}
alt={name}
fill
className="object-cover"
sizes={config.imgSize}
/>
</div>
);
}

const avatar = (
return (
<div
className={`${sizeClasses} rounded-full bg-muted flex items-center justify-center font-medium text-foreground/70 cursor-pointer`}
className={`${config.text} rounded-full bg-muted flex items-center justify-center font-medium text-foreground/70 flex-shrink-0`}
>
{initials}
</div>
);

if (!twitterHandle) {
return avatar;
}

return (
<Tooltip delayDuration={100}>
<TooltipTrigger asChild>{avatar}</TooltipTrigger>
<TooltipContent
side="bottom"
sideOffset={8}
className="bg-white text-black px-4 py-3 rounded-xl shadow-lg border border-gray-200"
showArrow={false}
>
<div className="flex flex-col gap-1.5">
<div className="text-sm">
<span className="font-semibold">{name}</span>
{title && <span className="text-gray-500"> {title}</span>}
</div>
<a
href={`https://x.com/${twitterHandle}`}
target="_blank"
rel="noopener noreferrer"
className="flex items-center gap-1.5 text-sm text-gray-600 hover:text-black transition-colors"
>
<XIcon className="size-3.5" />
<span>@{twitterHandle}</span>
</a>
</div>
</TooltipContent>
</Tooltip>
);
}
9 changes: 5 additions & 4 deletions apps/marketing/src/app/blog/components/BlogCard/BlogCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,13 @@ export function BlogCard({ post }: BlogCardProps) {
)}
<div className="flex items-center gap-2 mt-auto">
<AuthorAvatar
name={post.author}
title="Cofounder, Superset"
twitterHandle="avimakesrobots"
name={post.author.name}
avatar={post.author.avatar}
size="sm"
/>
<span className="text-xs text-muted-foreground">{post.author}</span>
<span className="text-xs text-muted-foreground">
{post.author.name}
</span>
</div>
</article>
</Link>
Expand Down
2 changes: 1 addition & 1 deletion apps/marketing/src/app/changelog/[slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export default async function ChangelogEntryPage({ params }: PageProps) {
<ArticleJsonLd
title={entry.title}
description={entry.description}
author="Superset Team"
author={{ name: "Superset Team" }}
publishedTime={new Date(entry.date).toISOString()}
url={url}
image={entry.image}
Expand Down
2 changes: 1 addition & 1 deletion apps/marketing/src/app/feed.xml/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export async function GET() {
<description>${escapeXml(post.description || "")}</description>
<pubDate>${new Date(post.date).toUTCString()}</pubDate>
<guid isPermaLink="true">${baseUrl}/blog/${post.slug}</guid>
<author>${escapeXml(post.author)}</author>
<author>${escapeXml(post.author.name)}</author>
</item>`,
)
.join("")}
Expand Down
13 changes: 11 additions & 2 deletions apps/marketing/src/components/JsonLd/JsonLd.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,16 @@ export function SoftwareApplicationJsonLd() {
);
}

interface ArticleAuthor {
name: string;
url?: string;
sameAs?: string[];
}

interface ArticleJsonLdProps {
title: string;
description?: string;
author: string;
author: ArticleAuthor;
publishedTime: string;
url: string;
image?: string;
Expand All @@ -69,7 +75,10 @@ export function ArticleJsonLd({
description: description || title,
author: {
"@type": "Person",
name: author,
name: author.name,
...(author.url && { url: author.url }),
...(author.sameAs &&
author.sameAs.length > 0 && { sameAs: author.sameAs }),
},
publisher: {
"@type": "Organization",
Expand Down
3 changes: 2 additions & 1 deletion apps/marketing/src/lib/blog-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
*/

import type { BlogCategory } from "./blog-constants";
import type { Person } from "./people";

export interface TocItem {
id: string;
Expand All @@ -16,7 +17,7 @@ export interface BlogPost {
url: string;
title: string;
description?: string;
author: string;
author: Person;
date: string;
category: BlogCategory;
image?: string;
Expand Down
Loading