Skip to content

feat(marketing): add blog system with MDX support#929

Merged
AviPeltz merged 10 commits intomainfrom
mogging-and-blogging
Jan 26, 2026
Merged

feat(marketing): add blog system with MDX support#929
AviPeltz merged 10 commits intomainfrom
mogging-and-blogging

Conversation

@AviPeltz
Copy link
Copy Markdown
Collaborator

@AviPeltz AviPeltz commented Jan 24, 2026

Summary

  • Add blog listing page with grid background and category tags
  • Add blog post page with Better Auth-inspired layout
  • Implement MDX rendering with gray-matter and next-mdx-remote
  • Add CodeBlock component from ai-elements for syntax highlighting
  • Include sample blog posts
  • Style matches landing page aesthetic with mono fonts and hard borders

Features

  • Blog listing (/blog) - Grid background, category tags in mono font, hard-bordered cards
  • Blog post (/blog/[slug]) - Centered hero header, "← Blog" back link with line divider
  • MDX support - Syntax highlighted code blocks with copy button via Shiki
  • Static generation - Uses generateStaticParams for SSG

Test plan

  • Visit /blog and verify listing page renders with grid background
  • Click a blog post and verify post page renders correctly
  • Verify code blocks have syntax highlighting and copy button
  • Test on mobile viewport sizes

Summary by CodeRabbit

  • New Features

    • Full blog launch: listing and individual post pages with metadata, dark-theme support, and decorative layout.
    • Three new posts added: "Introducing Superset", "A Guide to Parallel Coding Agents", "History of Git Worktrees".
    • Enhanced rendering: MDX support, rich typography, table of contents, syntax-highlighted code blocks with copy, video embeds, author avatars, blog cards, and improved post navigation.
  • Chores

    • Added MDX/markdown tooling, typography plugin, and updated syntax-highlighting deps.

✏️ Tip: You can customize this high-level summary in your review settings.

- Add blog listing page with grid background and category tags
- Add blog post page with Better Auth-inspired layout
- Implement MDX rendering with gray-matter and next-mdx-remote
- Add CodeBlock component from ai-elements for syntax highlighting
- Include sample blog posts (Introducing Superset, Parallel Agents Guide)
- Style matches landing page aesthetic with mono fonts and hard borders
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jan 24, 2026

📝 Walkthrough

Walkthrough

Adds MDX-based blog content, blog data utilities, new UI components/layouts for posts, MDX rendering with syntax-highlighted code blocks and copy buttons, prose styling, and small package dependency updates for MDX and syntax highlighting.

Changes

Cohort / File(s) Summary
Blog Content
apps/marketing/content/blog/introducing-superset.mdx, apps/marketing/content/blog/parallel-agents-guide.mdx, apps/marketing/content/blog/history-of-git-worktrees.mdx
Three new MDX posts with frontmatter, examples (bash/TSX/TS/JSON), video embed, and guidance content.
Blog Data & Utilities
apps/marketing/src/lib/blog-constants.ts, apps/marketing/src/lib/blog.ts, apps/marketing/src/lib/blog-utils.ts
New BLOG_CATEGORIES/type; BlogPost and TocItem types; frontmatter parsing (gray-matter); getBlogPosts/getBlogPost/getAllSlugs; extractToc for TOC generation; date formatting and slugify helpers.
MDX & Rendering Components
apps/marketing/src/app/blog/components/mdx-components.tsx, .../BlogCodeBlock/*, .../BlogCodeBlock/index.ts
mdxComponents mapping for headings, pre/code, img, Video; BlogCodeBlock wrapper that composes CodeBlock + copy button.
UI Components
apps/marketing/src/app/blog/components/*
.../BlogCard/BlogCard.tsx, .../AuthorAvatar/AuthorAvatar.tsx, .../GridCross/GridCross.tsx
New BlogCard, AuthorAvatar (with optional Twitter tooltip), GridCross decorative element; each has index re-exports.
Post Layout & Page Rendering
apps/marketing/src/app/blog/[slug]/components/BlogPostLayout/BlogPostLayout.tsx, apps/marketing/src/app/blog/[slug]/page.tsx, apps/marketing/src/app/blog/page.tsx
New BlogPostLayout; dynamic blog post page with MDXRemote rendering, TOC extraction, code highlighting + copy, slugged headings, generateStaticParams and metadata; blog listing page using BlogCard.
Styling & Layout
apps/marketing/src/app/globals.css, apps/marketing/src/app/layout.tsx
Added comprehensive .prose typography and dark-mode code block styles; root HTML class now includes "dark".
Package Changes
apps/marketing/package.json, packages/ui/package.json
Added runtime deps: gray-matter, next-mdx-remote, shiki; dev dep: @tailwindcss/typography. Bumped lucide-react and shiki versions in UI package.

Sequence Diagram(s)

sequenceDiagram
    participant Browser as Browser
    participant NextPage as BlogPostPage
    participant FS as FileSystem
    participant Parser as gray-matter / blog parser
    participant MDX as MDXRemote
    participant Components as mdxComponents
    participant Layout as BlogPostLayout

    Browser->>NextPage: GET /blog/{slug}
    NextPage->>FS: read `content/blog/{slug}.mdx`
    FS-->>Parser: return frontmatter + raw content
    Parser-->>NextPage: BlogPost { frontmatter, content }
    NextPage->>MDX: render content with mdxComponents
    MDX->>Components: render headings, code blocks, images, video
    Components-->>MDX: highlighted code + copy button, slugged anchors
    MDX->>Layout: rendered nodes + TOC
    Layout-->>Browser: HTML response (hero, content, footer)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 I hopped through MDX and prose tonight,
Cards and avatars in soft moonlight,
Code blocks gleam and headings take flight,
Slugs and TOCs all stitched up tight,
A tiny rabbit cheers: publish delight!

🚥 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 blog system with MDX support' clearly and concisely summarizes the primary change: addition of a blog system with MDX functionality.
Description check ✅ Passed PR description follows most template sections with clear summary and features, but lacks explicit sections for Related Issues, Type of Change details, and complete Testing coverage.

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

✨ Finishing touches
  • 📝 Generate docstrings

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.

- Add GridCross component for intersection markers
- Add vertical guide lines on content edges
- Use border sections to separate header, content, footer
- Add shiki dependency for syntax highlighting types
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jan 24, 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: 3

🤖 Fix all issues with AI agents
In `@apps/marketing/src/app/blog/`[slug]/page.tsx:
- Around line 91-97: The BlogPostLayoutProps declares a toc prop that is passed
from the page (extractToc in page.tsx) but BlogPostLayout only destructures {
post, children }, so toc is unused; update the BlogPostLayout function signature
to also accept and use toc (e.g., destructure { post, children, toc } and render
or forward it to the table-of-contents UI), or if you prefer to remove the
feature, delete toc from BlogPostLayoutProps and stop passing toc from the page
where extractToc(post.content) is called. Target the BlogPostLayout function and
BlogPostLayoutProps types and the page component that calls <BlogPostLayout
post={post} toc={toc}> to keep the prop and usage consistent.
- Around line 30-40: The MDX language extraction currently force-casts arbitrary
strings to BundledLanguage (see codeProps/className -> match -> language) which
lets unsupported values reach shiki and cause unhandled promise rejections in
CodeBlock; fix by validating the extracted language against the supported
shiki/BundledLanguage set before casting (map unknown values to "text") or by
adding error handling around CodeBlock's highlightCode() call (in the CodeBlock
component's useEffect) to catch shiki/codeToHtml errors and fallback to
plaintext rendering, ensuring you reference the extracted variable named
language and the highlightCode()/codeToHtml call sites when implementing the
fix.

In `@apps/marketing/src/lib/blog.ts`:
- Around line 49-51: The catch block that currently swallows errors and returns
null should log the caught error with context before returning; modify the catch
to capture the error (e) and call your logger (e.g., processLogger.error or
console.error) with a message indicating which operation failed and include the
error object, then return null as before so failures are observable during
debugging.
🧹 Nitpick comments (6)
apps/marketing/src/app/globals.css (1)

25-105: Consider extracting repeated color values to CSS variables.

The rgb(255 255 255 / ...) pattern is repeated throughout. For easier maintenance and theme consistency, consider defining these at the top with the other prose variables.

♻️ Optional refactor example
 .prose {
+	--prose-border: rgb(255 255 255 / 0.1);
+	--prose-bg-subtle: rgb(255 255 255 / 0.05);
 	--tw-prose-body: rgb(255 255 255 / 0.7);
 	/* ... */
 }

 .prose pre {
-	background: rgb(255 255 255 / 0.05);
-	border: 1px solid rgb(255 255 255 / 0.1);
+	background: var(--prose-bg-subtle);
+	border: 1px solid var(--prose-border);
 	/* ... */
 }
apps/marketing/src/app/blog/page.tsx (2)

11-18: Extract GridCross to a shared component.

This component is duplicated in BlogPostLayout.tsx (lines 20-27) and violates the one-component-per-file guideline. Extract it to a shared location like @/components/GridCross to ensure consistency and reduce duplication.


26-33: Extract magic numbers and shared gradient style.

The 384px and 383px values appear as magic numbers. Per coding guidelines, extract these to named constants. This gradient is also duplicated in BlogPostLayout.tsx—consider extracting to a shared utility or CSS class.

Proposed approach
// At module top or in a shared constants file
const CONTENT_WIDTH = 384;
const GUIDE_LINE_GRADIENT = `
  linear-gradient(to right, 
    transparent 0%, 
    transparent calc(50% - ${CONTENT_WIDTH}px), 
    rgba(255,255,255,0.06) calc(50% - ${CONTENT_WIDTH}px), 
    rgba(255,255,255,0.06) calc(50% - ${CONTENT_WIDTH - 1}px), 
    transparent calc(50% - ${CONTENT_WIDTH - 1}px), 
    transparent calc(50% + ${CONTENT_WIDTH - 1}px), 
    rgba(255,255,255,0.06) calc(50% + ${CONTENT_WIDTH - 1}px), 
    rgba(255,255,255,0.06) calc(50% + ${CONTENT_WIDTH}px), 
    transparent calc(50% + ${CONTENT_WIDTH}px)
  )
`;
apps/marketing/src/app/blog/[slug]/components/BlogPostLayout/BlogPostLayout.tsx (2)

8-18: Duplicate TocItem type and unused toc prop.

  1. TocItem is already exported from @/lib/blog—import it instead of redefining.
  2. The toc prop is declared but never used in the component. Either implement TOC rendering or remove the prop.
Proposed fix
 import type { BlogPost } from "@/lib/blog";
+import type { TocItem } from "@/lib/blog";

-interface TocItem {
-	id: string;
-	text: string;
-	level: number;
-}
-
 interface BlogPostLayoutProps {
 	post: BlogPost;
-	toc: TocItem[];
+	toc?: TocItem[]; // Make optional until implemented, or remove if not needed
 	children: ReactNode;
 }

20-27: Duplicate GridCross component.

This is identical to the GridCross in apps/marketing/src/app/blog/page.tsx. Extract to a shared component to maintain DRY principles and the one-component-per-file guideline.

apps/marketing/src/app/blog/[slug]/page.tsx (1)

13-18: Duplicate slug generation logic with extractToc in blog.ts.

This slugify function is identical to the ID generation logic in extractToc (lines 108-114 in blog.ts). If either implementation changes independently, TOC anchor links will break.

Consider extracting to a shared utility in blog.ts and reusing it in both places.

♻️ Suggested refactor

In apps/marketing/src/lib/blog.ts, export a slugify function:

export function slugify(text: string): string {
  return text
    .toLowerCase()
    .replace(/[^a-z0-9]+/g, "-")
    .replace(/(^-|-$)/g, "");
}

Then update extractToc to use it, and import it here:

-import { getAllSlugs, getBlogPost, extractToc } from "@/lib/blog";
+import { getAllSlugs, getBlogPost, extractToc, slugify } from "@/lib/blog";
-
-function slugify(text: string): string {
-	return text
-		.toLowerCase()
-		.replace(/[^a-z0-9]+/g, "-")
-		.replace(/(^-|-$)/g, "");
-}

Comment on lines +30 to +40
const codeProps = children.props as {
children?: string;
className?: string;
};
const code = codeProps.children?.trim() ?? "";
const className = codeProps.className ?? "";
const match = className.match(/language-(\w+)/);
const language = (match?.[1] ?? "text") as BundledLanguage;
return { code, language };
}
return { code: String(children ?? ""), language: "text" as BundledLanguage };
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 | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

shiki unknown language identifier fallback graceful handling

💡 Result:

To handle an unknown / unsupported Shiki language id gracefully, do one (or combine) of these patterns:

  1. Fallback to plain text (text, aliases: plain, txt)
import { codeToHtml } from 'shiki'

const safeLang = (lang?: string) => lang || 'text' // or your own validation
const html = await codeToHtml(code, { lang: safeLang(userLang), theme: 'vitesse-dark' })

Shiki explicitly supports lang: 'text' as a “no highlighting” fallback. [1]

  1. Try/catch and fallback
import { codeToHtml } from 'shiki'

let html: string
try {
  html = await codeToHtml(code, { lang: userLang, theme: 'vitesse-dark' })
} catch {
  html = await codeToHtml(code, { lang: 'text', theme: 'vitesse-dark' })
}

Catching highlighting errors is a supported/expected approach. [2]

  1. If you use a highlighter instance: load-or-fallback
    If you created a highlighter with only some languages, Shiki will throw when you request a language that isn’t loaded; you can loadLanguage() or fallback to text. [3]
import { createHighlighter } from 'shiki'

const highlighter = await createHighlighter({ themes: ['vitesse-dark'], langs: ['js'] })

async function highlight(code: string, lang: string) {
  try {
    await highlighter.loadLanguage(lang)   // load if available as a bundled lang
    return highlighter.codeToHtml(code, { lang, theme: 'vitesse-dark' })
  } catch {
    return highlighter.codeToHtml(code, { lang: 'text', theme: 'vitesse-dark' })
  }
}
  1. Map “unknown” ids via langAlias
    If your users pass non-standard ids (e.g. mylang, tsxreact, etc.), map them to a real Shiki id:
const highlighter = await createHighlighter({
  themes: ['nord'],
  langs: ['javascript'],
  langAlias: { mylang: 'javascript' },
})

Shiki supports langAlias for this. [4]

Sources: Shiki languages docs (plain-text fallback) [1], Shiki error-handling example [2], Shiki install/usage docs (throws when language not loaded; loadLanguage) [3], Shiki custom language docs (langAlias) [4].


🏁 Script executed:

cat -n apps/marketing/src/app/blog/[slug]/page.tsx | head -50

Repository: superset-sh/superset

Length of output: 1864


🏁 Script executed:

rg -A 10 "extractCodeFromChildren" apps/marketing/src/app/blog/[slug]/page.tsx

Repository: superset-sh/superset

Length of output: 706


🏁 Script executed:

fd CodeBlock --type f | head -10

Repository: superset-sh/superset

Length of output: 152


🏁 Script executed:

rg -B 5 -A 15 "interface.*code.*language\|type.*code.*language" apps/marketing/src --type tsx --type ts | head -60

Repository: superset-sh/superset

Length of output: 91


🏁 Script executed:

cat -n apps/desktop/src/renderer/components/MarkdownRenderer/components/CodeBlock/CodeBlock.tsx | head -100

Repository: superset-sh/superset

Length of output: 1693


🏁 Script executed:

find apps -name "CodeBlock*" -type f | head -20

Repository: superset-sh/superset

Length of output: 152


🏁 Script executed:

rg "language.*BundledLanguage\|CodeBlock.*language" apps/marketing --type ts -A 3 -B 3

Repository: superset-sh/superset

Length of output: 46


🏁 Script executed:

find . -path "*/ai-elements/code-block*" -type f | head -20

Repository: superset-sh/superset

Length of output: 119


🏁 Script executed:

rg "export.*CodeBlock" --type ts --type tsx | grep -v node_modules | head -20

Repository: superset-sh/superset

Length of output: 91


🏁 Script executed:

fd "code-block" --type f | grep -v node_modules | head -20

Repository: superset-sh/superset

Length of output: 117


🏁 Script executed:

cat -n packages/ui/src/components/ai-elements/code-block.tsx

Repository: superset-sh/superset

Length of output: 5402


Unsafe type assertion for BundledLanguage causes silent rendering failures with unsupported languages.

The as BundledLanguage cast on lines 37 and 40 bypasses type checking, allowing invalid language identifiers to reach runtime. When an unsupported language is extracted from MDX (e.g., language-foobar), shiki's codeToHtml() will throw an error. However, the CodeBlock component (packages/ui/src/components/ai-elements/code-block.tsx) has no error handling in its useEffect promise chain (lines 87-94), causing unhandled promise rejections and silent failures where code blocks simply don't render.

Either validate the extracted language against a set of supported languages before passing it to CodeBlock, or add error handling in CodeBlock's highlightCode() call to catch shiki errors and fallback to plaintext rendering.

🤖 Prompt for AI Agents
In `@apps/marketing/src/app/blog/`[slug]/page.tsx around lines 30 - 40, The MDX
language extraction currently force-casts arbitrary strings to BundledLanguage
(see codeProps/className -> match -> language) which lets unsupported values
reach shiki and cause unhandled promise rejections in CodeBlock; fix by
validating the extracted language against the supported shiki/BundledLanguage
set before casting (map unknown values to "text") or by adding error handling
around CodeBlock's highlightCode() call (in the CodeBlock component's useEffect)
to catch shiki/codeToHtml errors and fallback to plaintext rendering, ensuring
you reference the extracted variable named language and the
highlightCode()/codeToHtml call sites when implementing the fix.

Comment on lines +91 to +97
const toc = extractToc(post.content);

return (
<main>
<BlogPostLayout post={post} toc={toc}>
<MDXRemote source={post.content} components={components} />
</BlogPostLayout>
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 | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# Find the BlogPostLayout component file
fd -t f "BlogPostLayout" apps/marketing/src/

Repository: superset-sh/superset

Length of output: 143


🏁 Script executed:

# Search for BlogPostLayoutProps interface and toc references
rg "BlogPostLayoutProps|interface.*BlogPostLayout" apps/marketing/src/ -A 10

Repository: superset-sh/superset

Length of output: 2518


🏁 Script executed:

# Search for toc usage in the BlogPostLayout directory
rg "toc" apps/marketing/src/app/blog/ -n

Repository: superset-sh/superset

Length of output: 338


Fix unused toc prop in BlogPostLayout component.

The toc prop is defined in BlogPostLayoutProps and passed from the page component, but the BlogPostLayout function signature only destructures { post, children }, leaving toc unused. Either destructure and use toc in the component implementation or remove it from the interface and stop passing it.

🤖 Prompt for AI Agents
In `@apps/marketing/src/app/blog/`[slug]/page.tsx around lines 91 - 97, The
BlogPostLayoutProps declares a toc prop that is passed from the page (extractToc
in page.tsx) but BlogPostLayout only destructures { post, children }, so toc is
unused; update the BlogPostLayout function signature to also accept and use toc
(e.g., destructure { post, children, toc } and render or forward it to the
table-of-contents UI), or if you prefer to remove the feature, delete toc from
BlogPostLayoutProps and stop passing toc from the page where
extractToc(post.content) is called. Target the BlogPostLayout function and
BlogPostLayoutProps types and the page component that calls <BlogPostLayout
post={post} toc={toc}> to keep the prop and usage consistent.

Comment on lines +49 to +51
} catch {
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

Silent error swallowing in catch block.

The empty catch block discards error information, making debugging difficult. Per coding guidelines, errors should at minimum be logged with context.

Proposed fix
-	} catch {
+	} catch (error) {
+		console.error(`[blog/parseFrontmatter] Failed to parse ${filePath}:`, error);
 		return null;
 	}
📝 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
} catch {
return null;
}
} catch (error) {
console.error(`[blog/parseFrontmatter] Failed to parse ${filePath}:`, error);
return null;
}
🤖 Prompt for AI Agents
In `@apps/marketing/src/lib/blog.ts` around lines 49 - 51, The catch block that
currently swallows errors and returns null should log the caught error with
context before returning; modify the catch to capture the error (e) and call
your logger (e.g., processLogger.error or console.error) with a message
indicating which operation failed and include the error object, then return null
as before so failures are observable during debugging.

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 8-18: The TocItem interface and the toc prop on
BlogPostLayoutProps are unused; either implement the TOC or remove the dead
code—if you’re not adding a table of contents, delete the TocItem interface and
remove toc from BlogPostLayoutProps and any references to toc in the
BlogPostLayout component (look for BlogPostLayoutProps, TocItem, and the
BlogPostLayout component declaration) so the prop and type are no longer
declared but the component still accepts post and children.
🧹 Nitpick comments (3)
apps/marketing/src/app/blog/[slug]/components/BlogPostLayout/BlogPostLayout.tsx (3)

1-1: Consider removing "use client" directive.

This component doesn't use any client-side hooks (useState, useEffect, etc.) or event handlers with state. It only performs static rendering and date formatting, both of which work server-side. Making this a Server Component would improve performance by reducing the client bundle.

Suggested change
-"use client";
-
 import { ArrowLeft } from "lucide-react";

20-42: Multiple components in single file.

Per coding guidelines, .tsx files should have one component per file. GridCross and AuthorAvatar are helper components defined alongside BlogPostLayout. Consider extracting them to separate files (e.g., GridCross.tsx, AuthorAvatar.tsx) or a shared components/decorative directory if reused elsewhere.


54-61: Extract magic numbers to named constants.

The values 384 and 383 are repeated and not self-documenting. Per coding guidelines, extract hardcoded values to named constants at module top for clarity and maintainability.

Suggested refactor
+const GRID_LINE_OFFSET_PX = 384;
+const GRID_LINE_INNER_OFFSET_PX = 383;
+
 export function BlogPostLayout({ post, children }: BlogPostLayoutProps) {

Then use template literals or a helper to construct the background image, or add a comment explaining these values represent half the container width (768px / 2).

Comment thread apps/marketing/src/app/blog/[slug]/components/BlogPostLayout/BlogPostLayout.tsx Outdated
@AviPeltz AviPeltz marked this pull request as draft January 24, 2026 01:53
@AviPeltz AviPeltz marked this pull request as ready for review January 26, 2026 02:43
@AviPeltz AviPeltz marked this pull request as draft January 26, 2026 02:44
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: 2

🤖 Fix all issues with AI agents
In `@apps/marketing/src/app/blog/components/AuthorAvatar/AuthorAvatar.tsx`:
- Around line 31-36: The initials computation can produce undefined when name
has extra spaces; update the initials logic (the initials constant in
AuthorAvatar) to normalize and filter segments before taking first chars — e.g.,
split on whitespace with name.trim().split(/\s+/) or split(" ").filter(Boolean),
map to n[0] safely (fallback to ""), join, toUpperCase(), and slice(0,2) so
empty segments or leading/trailing spaces don't yield undefined initials.

In `@apps/marketing/src/app/blog/components/mdx-components.tsx`:
- Around line 11-32: The extractCodeFromChildren helper currently casts any
regex match to BundledLanguage which can lead to runtime errors in codeToHtml;
update extractCodeFromChildren to validate the extracted language against
Shiki's supported languages (or return "text") instead of unguarded casting, and
modify highlightCode (the function calling codeToHtml) to wrap codeToHtml calls
in try/catch and return a plaintext fallback on error; also ensure CodeBlock's
useEffect that calls highlightCode attaches a .catch handler or awaits inside a
try/catch so rejected promises are handled and the component falls back to plain
rendering.
♻️ Duplicate comments (3)
apps/marketing/src/lib/blog.ts (1)

49-51: Silent error swallowing in catch block.

This was previously flagged. The empty catch block discards error information, making debugging difficult. Per coding guidelines, errors should at minimum be logged with context.

apps/marketing/src/app/blog/[slug]/components/BlogPostLayout/BlogPostLayout.tsx (1)

10-20: Unused toc prop and duplicate TocItem interface.

This was previously flagged. The TocItem interface duplicates the one in @/lib/blog, and the toc prop is defined but never used in the component. Either implement the TOC feature or remove the dead code.

apps/marketing/src/app/blog/[slug]/page.tsx (1)

12-28: toc is computed but unused downstream.

The toc is extracted and passed to BlogPostLayout, but as noted in the layout component review, it's not being rendered. This is dead code until the TOC feature is implemented.

🧹 Nitpick comments (8)
apps/marketing/content/blog/parallel-agents-guide.mdx (1)

65-73: Example code lacks error handling.

The fetchUser function doesn't handle potential failures from fetch() or response.json(). While this is documentation, readers may copy this pattern. Consider adding basic error handling to demonstrate best practices:

useEffect(() => {
  async function fetchUser() {
    try {
      const response = await fetch(`/api/users/${userId}`);
      if (!response.ok) throw new Error('Failed to fetch');
      const data = await response.json();
      setUser(data);
    } catch (error) {
      console.error('[UserProfile/fetchUser]', error);
    } finally {
      setLoading(false);
    }
  }
  fetchUser();
}, [userId]);
apps/marketing/src/app/blog/components/AuthorAvatar/AuthorAvatar.tsx (1)

55-59: Hardcoded light-mode colors in tooltip.

The tooltip uses explicit light-mode colors (bg-white, text-black, border-gray-200). If the blog supports dark mode, this may look inconsistent. If intentional for contrast, consider adding a comment explaining the choice.

apps/marketing/src/lib/blog.ts (1)

99-118: Slugify logic is duplicated across files.

The ID generation logic here (lines 110-113) duplicates the slugify function in mdx-components.tsx. Consider extracting this to a shared utility to ensure consistent slug generation between TOC extraction and heading rendering.

♻️ Suggested refactor
+export function slugify(text: string): string {
+	return text
+		.toLowerCase()
+		.replace(/[^a-z0-9]+/g, "-")
+		.replace(/(^-|-$)/g, "");
+}
+
 export function extractToc(content: string): TocItem[] {
 	const headingRegex = /^(#{2,3})\s+(.+)$/gm;
 	const toc: TocItem[] = [];

 	for (const match of content.matchAll(headingRegex)) {
 		const hashes = match[1];
 		const heading = match[2];
 		if (!hashes || !heading) continue;

 		const level = hashes.length;
 		const text = heading.trim();
-		const id = text
-			.toLowerCase()
-			.replace(/[^a-z0-9]+/g, "-")
-			.replace(/(^-|-$)/g, "");
+		const id = slugify(text);

 		toc.push({ id, text, level });
 	}

 	return toc;
 }

Then in mdx-components.tsx, import and use the shared slugify function.

apps/marketing/src/app/blog/components/mdx-components.tsx (1)

4-9: Duplicated slugify function.

This slugify implementation is identical to the ID generation logic in blog.ts extractToc. As noted in the other file, consider extracting to a shared utility.

apps/marketing/src/app/blog/components/BlogCard/BlogCard.tsx (1)

36-44: Hardcoded author metadata limits extensibility.

The title and twitterHandle props are hardcoded for all posts. If different authors contribute, this will display incorrect information.

Consider extending BlogPost to include optional author metadata or creating an author lookup configuration.

♻️ Suggested approach

Option 1: Extend BlogPost interface:

// In blog.ts
export interface BlogPost {
  // ... existing fields
  authorTitle?: string;
  authorTwitter?: string;
}

Option 2: Create author config lookup:

const AUTHORS: Record<string, { title: string; twitter: string }> = {
  "Avi Peltz": { title: "Cofounder, Superset", twitter: "avimakesrobots" },
  // ... other authors
};
apps/marketing/src/app/blog/[slug]/components/BlogPostLayout/BlogPostLayout.tsx (2)

32-39: Extract magic number to named constant.

The value 384 (and 383) appears in the background gradient calculation. Per coding guidelines, extract magic numbers to named constants for clarity.

♻️ Suggested refactor
+"use client";
+
+const CONTENT_HALF_WIDTH_PX = 384;
+
 // In the style block:
 style={{
   backgroundImage: `
-    linear-gradient(to right, transparent 0%, transparent calc(50% - 384px), rgba(255,255,255,0.06) calc(50% - 384px), rgba(255,255,255,0.06) calc(50% - 383px), transparent calc(50% - 383px), transparent calc(50% + 383px), rgba(255,255,255,0.06) calc(50% + 383px), rgba(255,255,255,0.06) calc(50% + 384px), transparent calc(50% + 384px))
+    linear-gradient(to right, transparent 0%, transparent calc(50% - ${CONTENT_HALF_WIDTH_PX}px), rgba(255,255,255,0.06) calc(50% - ${CONTENT_HALF_WIDTH_PX}px), rgba(255,255,255,0.06) calc(50% - ${CONTENT_HALF_WIDTH_PX - 1}px), transparent calc(50% - ${CONTENT_HALF_WIDTH_PX - 1}px), transparent calc(50% + ${CONTENT_HALF_WIDTH_PX - 1}px), rgba(255,255,255,0.06) calc(50% + ${CONTENT_HALF_WIDTH_PX - 1}px), rgba(255,255,255,0.06) calc(50% + ${CONTENT_HALF_WIDTH_PX}px), transparent calc(50% + ${CONTENT_HALF_WIDTH_PX}px))
   `,
 }}

64-68: Hardcoded author metadata (same issue as BlogCard).

The title and twitterHandle are hardcoded here as well. This should be addressed together with the BlogCard component.

apps/marketing/src/app/blog/page.tsx (1)

18-25: Extract shared background gradient constant.

The same 384px magic number and gradient pattern appears here and in BlogPostLayout. Consider extracting to a shared constant or utility.

Comment on lines +31 to +36
const initials = name
.split(" ")
.map((n) => n[0])
.join("")
.toUpperCase()
.slice(0, 2);
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

Edge case: empty string segments in name splitting.

If name contains leading/trailing spaces or consecutive spaces (e.g., "John Doe" or " John"), split(" ") produces empty strings, and n[0] returns undefined, resulting in initials like "JU" (from undefined coerced).

Suggested fix
 const initials = name
   .split(" ")
+  .filter(Boolean)
   .map((n) => n[0])
   .join("")
   .toUpperCase()
   .slice(0, 2);
🤖 Prompt for AI Agents
In `@apps/marketing/src/app/blog/components/AuthorAvatar/AuthorAvatar.tsx` around
lines 31 - 36, The initials computation can produce undefined when name has
extra spaces; update the initials logic (the initials constant in AuthorAvatar)
to normalize and filter segments before taking first chars — e.g., split on
whitespace with name.trim().split(/\s+/) or split(" ").filter(Boolean), map to
n[0] safely (fallback to ""), join, toUpperCase(), and slice(0,2) so empty
segments or leading/trailing spaces don't yield undefined initials.

Comment on lines +11 to +32
function extractCodeFromChildren(children: React.ReactNode): {
code: string;
language: BundledLanguage;
} {
if (
children &&
typeof children === "object" &&
"props" in children &&
children.props
) {
const codeProps = children.props as {
children?: string;
className?: string;
};
const code = codeProps.children?.trim() ?? "";
const className = codeProps.className ?? "";
const match = className.match(/language-(\w+)/);
const language = (match?.[1] ?? "text") as BundledLanguage;
return { code, language };
}
return { code: String(children ?? ""), language: "text" as BundledLanguage };
}
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 | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, locate the mdx-components.tsx file
find . -name "mdx-components.tsx" -type f

Repository: superset-sh/superset

Length of output: 158


🏁 Script executed:

# Search for BundledLanguage type definition
rg "BundledLanguage" --type ts --type tsx -A 2 -B 2

Repository: superset-sh/superset

Length of output: 91


🏁 Script executed:

# Find BlogCodeBlock component
rg "BlogCodeBlock" --type ts --type tsx -A 5 -B 2

Repository: superset-sh/superset

Length of output: 91


🏁 Script executed:

# Check imports in mdx-components.tsx to understand dependencies
rg "import.*Shiki|from.*shiki" --type ts --type tsx -A 2 -B 2

Repository: superset-sh/superset

Length of output: 91


🏁 Script executed:

# Read the mdx-components.tsx file directly
cat -n ./apps/marketing/src/app/blog/components/mdx-components.tsx

Repository: superset-sh/superset

Length of output: 3748


🏁 Script executed:

# Search for BundledLanguage without type restriction
rg "BundledLanguage" -A 2 -B 2

Repository: superset-sh/superset

Length of output: 3589


🏁 Script executed:

# Search for BlogCodeBlock without type restriction
rg "BlogCodeBlock" -A 5 -B 2

Repository: superset-sh/superset

Length of output: 3127


🏁 Script executed:

# Search for Shiki imports
rg "from.*shiki" -A 2 -B 2

Repository: superset-sh/superset

Length of output: 1295


🏁 Script executed:

# Read BlogCodeBlock.tsx fully
cat -n ./apps/marketing/src/app/blog/components/BlogCodeBlock/BlogCodeBlock.tsx

Repository: superset-sh/superset

Length of output: 846


🏁 Script executed:

# Read CodeBlock.tsx to see how codeToHtml is called
cat -n ./packages/ui/src/components/ai-elements/code-block.tsx | head -80

Repository: superset-sh/superset

Length of output: 2144


🏁 Script executed:

# Check for Shiki error handling or validation patterns in codebase
rg "codeToHtml" -A 10 -B 2

Repository: superset-sh/superset

Length of output: 2346


🏁 Script executed:

# Read the rest of code-block.tsx
cat -n ./packages/ui/src/components/ai-elements/code-block.tsx | tail -50

Repository: superset-sh/superset

Length of output: 1445


🏁 Script executed:

# Search for try-catch blocks around code highlighting
rg "try|catch|error|Error" ./packages/ui/src/components/ai-elements/code-block.tsx -A 2 -B 2

Repository: superset-sh/superset

Length of output: 691


🏁 Script executed:

# Check how highlightCode is called/used
rg "highlightCode" -A 5 -B 2

Repository: superset-sh/superset

Length of output: 1300


🌐 Web query:

Shiki BundledLanguage type definition and how it handles unknown language identifiers

💡 Result:

BundledLanguage type (Shiki)

In Shiki’s prebuilt bundles (e.g. shiki/bundle/web), BundledLanguage is a TypeScript string-literal union of the language IDs included in that bundle, and the bundle also exports bundledLanguages: Record<BundledLanguage, DynamicImportLanguageRegistration>. (app.unpkg.com)

So, for a given bundle, only those IDs are valid at the type level (e.g. 'js', 'ts', 'astro', etc. in the web bundle). (app.unpkg.com)

How Shiki handles unknown language identifiers (runtime)

  • Shiki treats plain-text languages specially and bypasses highlighting when lang is missing or is one of: plaintext, txt, text, plain. (app.unpkg.com)
  • For a truly unknown / not-bundled language id (i.e. not loaded/registered), Shiki’s APIs are expected to throw an error (so callers commonly wrap highlighting in try/catch and/or map unknown inputs to lang: 'text'). (jsr.io)

Citations:


🏁 Script executed:

# Get the full useEffect hook in CodeBlock
cat -n ./packages/ui/src/components/ai-elements/code-block.tsx | sed -n '84,120p'

Repository: superset-sh/superset

Length of output: 1599


Add error handling for invalid language identifiers in code highlighting.

The as BundledLanguage cast on lines 28 and 31 bypasses TypeScript's type safety. The regex /language-(\w+)/ extracts any word sequence (e.g., language-foobarfoobar), which may not exist in Shiki's supported languages bundle. While codeToHtml accepts a BundledLanguage type at compile time, Shiki throws a runtime error for unsupported language identifiers.

The fallback to "text" only applies when the regex doesn't match—not when it matches an invalid language. The highlightCode function (code-block.tsx lines 61–72) has no error handling around codeToHtml calls, and CodeBlock's useEffect (line 88) calls highlightCode().then(...) without a .catch() handler, risking unhandled promise rejections.

Add validation against supported languages, or wrap codeToHtml calls in try-catch to default to plaintext rendering on failure.

🤖 Prompt for AI Agents
In `@apps/marketing/src/app/blog/components/mdx-components.tsx` around lines 11 -
32, The extractCodeFromChildren helper currently casts any regex match to
BundledLanguage which can lead to runtime errors in codeToHtml; update
extractCodeFromChildren to validate the extracted language against Shiki's
supported languages (or return "text") instead of unguarded casting, and modify
highlightCode (the function calling codeToHtml) to wrap codeToHtml calls in
try/catch and return a plaintext fallback on error; also ensure CodeBlock's
useEffect that calls highlightCode attaches a .catch handler or awaits inside a
try/catch so rejected promises are handled and the component falls back to plain
rendering.

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 54-58: The AuthorAvatar is receiving hardcoded title and
twitterHandle while name uses post.author, causing mismatched metadata; update
the data model and JSX to pass real author metadata: add authorTitle and
authorTwitter fields to the BlogPost frontmatter/interface (and to whatever
parser builds post), populate them in existing posts or provide sensible
fallbacks, then replace the hardcoded strings in the BlogPostLayout component by
passing post.authorTitle and post.authorTwitter into AuthorAvatar (or, if you
prefer a fixed set, implement an author lookup map keyed by post.author and use
that to supply title and twitterHandle; alternatively remove those props if no
reliable source exists).
♻️ Duplicate comments (2)
apps/marketing/src/lib/blog.ts (1)

43-45: Silent error swallowing in catch block.

The empty catch block discards error information, making debugging difficult. Per coding guidelines, errors should at minimum be logged with context.

Proposed fix
-	} catch {
+	} catch (error) {
+		console.error(`[blog/parseFrontmatter] Failed to parse ${filePath}:`, error);
 		return null;
 	}
apps/marketing/src/app/blog/[slug]/components/BlogPostLayout/BlogPostLayout.tsx (1)

10-16: Unused toc prop still present in interface.

The toc prop is defined in BlogPostLayoutProps (line 12) but is never destructured or used in the component. Either implement the table of contents feature or remove this dead code.

If TOC is not needed, remove the unused prop
-import { type BlogPost, formatBlogDate, type TocItem } from "@/lib/blog-utils";
+import { type BlogPost, formatBlogDate } from "@/lib/blog-utils";

 interface BlogPostLayoutProps {
 	post: BlogPost;
-	toc: TocItem[];
 	children: ReactNode;
 }

Comment on lines +54 to +58
<AuthorAvatar
name={post.author}
title="Cofounder, Superset"
twitterHandle="avimakesrobots"
/>
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

Hardcoded author metadata will be incorrect for different authors.

The title and twitterHandle props are hardcoded to "Cofounder, Superset" and "avimakesrobots" respectively, but name uses post.author. If blog posts have different authors, this will display incorrect metadata.

Consider either:

  1. Adding authorTitle and authorTwitter fields to the BlogPost interface and frontmatter
  2. Creating an author lookup map if there's a fixed set of authors
  3. Removing the author details if they can't be reliably sourced
🤖 Prompt for AI Agents
In
`@apps/marketing/src/app/blog/`[slug]/components/BlogPostLayout/BlogPostLayout.tsx
around lines 54 - 58, The AuthorAvatar is receiving hardcoded title and
twitterHandle while name uses post.author, causing mismatched metadata; update
the data model and JSX to pass real author metadata: add authorTitle and
authorTwitter fields to the BlogPost frontmatter/interface (and to whatever
parser builds post), populate them in existing posts or provide sensible
fallbacks, then replace the hardcoded strings in the BlogPostLayout component by
passing post.authorTitle and post.authorTwitter into AuthorAvatar (or, if you
prefer a fixed set, implement an author lookup map keyed by post.author and use
that to supply title and twitterHandle; alternatively remove those props if no
reliable source exists).

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