Skip to content

feat(marketing): add author registry and dynamic author metadata to blog#1150

Merged
saddlepaddle merged 3 commits into
mainfrom
add-author-registry-and-dynamic-author-metadata-to
Feb 3, 2026
Merged

feat(marketing): add author registry and dynamic author metadata to blog#1150
saddlepaddle merged 3 commits into
mainfrom
add-author-registry-and-dynamic-author-metadata-to

Conversation

@saddlepaddle
Copy link
Copy Markdown
Collaborator

@saddlepaddle saddlepaddle commented Feb 2, 2026

Summary

  • Create centralized author registry (apps/marketing/src/lib/authors.ts) mapping author IDs to full metadata (name, title, twitter, github)
  • Standardize all MDX frontmatter to use author IDs (avi, kiet) instead of inconsistent full names
  • Replace hardcoded author data in BlogCard, BlogPostLayout, and RSS feed with dynamic registry lookups
  • Enrich ArticleJsonLd with author url and sameAs social profiles for improved SEO/E-E-A-T signals
  • Graceful fallback: unknown author IDs display the raw string

Test plan

  • bun run typecheck --filter=@superset/marketing passes
  • bun run lint passes
  • bun test — 1193 tests pass, 0 failures
  • Verify blog post pages render correct author names and tooltips
  • Verify JSON-LD output includes author URL and sameAs array
  • Verify RSS feed shows resolved author display names

Summary by CodeRabbit

  • New Features

    • Added author profile pages and avatars; posts now display author name, role, avatar and optional social links.
    • Article metadata (JSON‑LD) and RSS include richer structured author data for improved sharing and discoverability.
  • Refactor

    • Unified author data shape across blog listings, post layout, feeds, and components to consume structured author profiles.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Feb 2, 2026

📝 Walkthrough

Walkthrough

Author handling was refactored: post frontmatter now uses author identifiers; new person MDX files and a people module provide structured Person objects; blog utilities, components, JSON‑LD, feed, and pages were updated to consume the Person shape instead of plain author strings.

Changes

Cohort / File(s) Summary
Blog post frontmatter
apps/marketing/content/blog/_archive/history-of-git-worktrees.mdx, apps/marketing/content/blog/_archive/introducing-superset.mdx, apps/marketing/content/blog/_archive/parallel-agents-guide.mdx, apps/marketing/content/blog/how-to-get-hit.mdx, apps/marketing/content/blog/terminal-daemon-deep-dive.mdx
Replaced full-name author frontmatter with identifier strings (e.g., Avi Peltzavi, Kiet Hokiet).
People content
apps/marketing/content/people/avi.mdx, apps/marketing/content/people/kiet.mdx, apps/marketing/content/people/satya.mdx
Added person profile MDX files containing metadata (name, role, avatar, social handles).
People management
apps/marketing/src/lib/people.ts
New module: Zod schema, Person types, getPersonById(id) and getAllPeople() to load/validate person MDX content; returns typed Person or null on errors.
Blog data model & utils
apps/marketing/src/lib/blog-utils.ts, apps/marketing/src/lib/blog.ts
BlogPost.author changed from string to Person; author resolution added via getPersonById; Person type exported; fallback author construction added.
Author UI / avatar
apps/marketing/src/app/blog/components/AuthorAvatar/AuthorAvatar.tsx, apps/marketing/src/app/blog/components/BlogCard/BlogCard.tsx, apps/marketing/src/app/blog/[slug]/components/BlogPostLayout/BlogPostLayout.tsx
AuthorAvatar API changed (accepts avatar?: string, expanded sizes, removed twitter/title/tooltip). Components updated to use nested author fields (name, avatar, role, socials) and render avatar, name, role, and conditional social links.
Pages, feed & SEO
apps/marketing/src/app/blog/[slug]/page.tsx, apps/marketing/src/app/changelog/[slug]/page.tsx, apps/marketing/src/app/feed.xml/route.ts, apps/marketing/src/components/JsonLd/JsonLd.tsx
ArticleJsonLd author prop changed from string{ name, url?, sameAs? }; metadata and RSS now use author.name; JsonLd types updated with ArticleAuthor interface; ArticleJsonLd builds author object conditionally including url/sameAs.

Sequence Diagram

sequenceDiagram
    participant Content as Content Files\n(blog .mdx)
    participant BlogUtil as Blog Utilities\n(blog.ts / blog-utils.ts)
    participant People as People Module\n(people.ts)
    participant Component as UI Components\n(BlogCard / Layout / AuthorAvatar)
    participant JsonLd as JSON‑LD Builder\n(JsonLd.tsx)

    Content->>BlogUtil: parse frontmatter (author id e.g., "avi")
    BlogUtil->>People: getPersonById("avi")
    People-->>BlogUtil: Person { id, name, role, avatar, socials, content }
    BlogUtil-->>Component: resolved BlogPost with author: Person
    Component->>Component: render author.name, avatar, role, social links
    Component->>JsonLd: provide author object (name, url?, sameAs?)
    JsonLd->>JsonLd: build Article JSON‑LD with author object
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • AviPeltz
  • Kitenite

Poem

🐰 I hopped from name to tidy id,

profiles sprouted where they hid,
avatars snug and socials sing,
JSON‑LD now knows the thing—
author.name leads springtime cheer! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat(marketing): add author registry and dynamic author metadata to blog' clearly and specifically describes the main changes: creating an author registry and adding dynamic author metadata to the blog.
Description check ✅ Passed The PR description includes a comprehensive summary, detailed test plan with execution status, and relevant context. While the formal description template sections (Related Issues, Type of Change, Screenshots) are not explicitly filled, the actual description content is well-structured and provides all necessary information for understanding and reviewing the PR.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch add-author-registry-and-dynamic-author-metadata-to

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@apps/marketing/src/lib/people.ts`:
- Around line 25-45: The console error in getPersonById currently uses prefix
"[people]" which omits the operation; update the logging call inside the catch
block to use the required "[domain/operation]" format (e.g.,
"[people/getPersonById]") and keep the existing message and error variable so it
logs like "[people/getPersonById] Failed to parse ${id}.mdx:" followed by the
error to conform to the pattern.
🧹 Nitpick comments (1)
apps/marketing/src/lib/blog.ts (1)

34-47: Defensively normalize non-string frontmatter author values.

Frontmatter is untyped; if author isn’t a string, the fallback can render [object Object] and attempt invalid file lookups. A small guard keeps this predictable.

🛡️ Suggested guard
-		const authorId: string = data.author ?? "unknown";
+		const authorId =
+			typeof data.author === "string" && data.author.trim().length > 0
+				? data.author
+				: "unknown";

Comment on lines +25 to +45
export function getPersonById(id: string): Person | null {
const filePath = path.join(peopleDirectory, `${id}.mdx`);

if (!fs.existsSync(filePath)) {
return null;
}

try {
const fileContent = fs.readFileSync(filePath, "utf-8");
const { data, content } = matter(fileContent);
const validatedData = personSchema.parse(data);

return {
...validatedData,
id,
content,
};
} catch (error) {
console.error(`[people] Failed to parse ${id}.mdx:`, error);
return null;
}
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

Adjust the console prefix to the required [domain/operation] format.

Current prefix omits the operation segment.

🔧 Suggested fix
-		console.error(`[people] Failed to parse ${id}.mdx:`, error);
+		console.error(`[people/getPersonById] Failed to parse ${id}.mdx:`, error);

As per coding guidelines: Use prefixed console logging with pattern [domain/operation] message for all logging.

🤖 Prompt for AI Agents
In `@apps/marketing/src/lib/people.ts` around lines 25 - 45, The console error in
getPersonById currently uses prefix "[people]" which omits the operation; update
the logging call inside the catch block to use the required "[domain/operation]"
format (e.g., "[people/getPersonById]") and keep the existing message and error
variable so it logs like "[people/getPersonById] Failed to parse ${id}.mdx:"
followed by the error to conform to the pattern.

Replace hardcoded author data with a centralized author registry that
resolves author IDs from MDX frontmatter to full metadata (name, title,
twitter, github). Enriches JSON-LD with author URL and sameAs profiles
for better SEO/E-E-A-T signals.
Replace hardcoded AUTHORS record with MDX content files in
content/people/ (one file per author, parsed with gray-matter + Zod).
Resolve author to full Person object during blog parsing so components
access post.author.name/role/twitter directly instead of doing lookups.
- Add satya.mdx person file, update avi/kiet with avatars, LinkedIn, and correct socials
- Add team avatar photos (avi.jpg, kiet.jpg, satya.webp) to public/team/
- Replace AuthorAvatar tooltip hover card with real avatar images using next/image
- Show author name, role, date, and social icon links (X, GitHub, LinkedIn) in blog post header
- Use react-icons (RiTwitterXFill, RiGithubFill, RiLinkedinBoxFill) for social icons
- BlogCard now shows real author avatars in listing
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Feb 3, 2026

🧹 Preview Cleanup Complete

The following preview resources have been cleaned up:

  • ✅ Neon database branch
  • ✅ Electric Fly.io app

Thank you for your contribution! 🎉

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In
`@apps/marketing/src/app/blog/`[slug]/components/BlogPostLayout/BlogPostLayout.tsx:
- Around line 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.
🧹 Nitpick comments (2)
apps/marketing/src/app/blog/[slug]/components/BlogPostLayout/BlogPostLayout.tsx (1)

66-71: Hide the role separator when role is empty.

Fallback authors with an empty role render a leading separator. Consider conditional rendering.

💡 Suggested tweak
 								<span className="text-xs text-muted-foreground">
-									{author.role}
-									<span className="text-muted-foreground/50"> · </span>
+									{author.role && (
+										<>
+											{author.role}
+											<span className="text-muted-foreground/50"> · </span>
+										</>
+									)}
 									<time dateTime={post.date}>{formattedDate}</time>
 								</span>
apps/marketing/src/app/blog/[slug]/page.tsx (1)

36-56: Populate JSON‑LD author.url from any available profile.

Right now url only uses Twitter, so authors with GitHub/LinkedIn only won’t get a primary URL. Consider choosing the first available profile.

💡 Suggested adjustment
 	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}`);
 	}
+
+	const authorUrl =
+		author.twitter
+			? `https://x.com/${author.twitter}`
+			: author.github
+				? `https://github.com/${author.github}`
+				: author.linkedin
+					? `https://linkedin.com/in/${author.linkedin}`
+					: undefined;
 
 			<ArticleJsonLd
 				title={post.title}
 				description={post.description}
 				author={{
 					name: author.name,
-					url: author.twitter ? `https://x.com/${author.twitter}` : undefined,
+					url: authorUrl,
 					sameAs: sameAs.length > 0 ? sameAs : undefined,
 				}}

Comment on lines +75 to +103
{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>
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.

@saddlepaddle saddlepaddle merged commit d1cfb47 into main Feb 3, 2026
13 checks passed
@Kitenite Kitenite deleted the add-author-registry-and-dynamic-author-metadata-to branch February 7, 2026 02:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant