Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
114 commits
Select commit Hold shift + click to select a range
9f6adc0
feat: add blog infrastructure (pages, components, sitemap, nav link)
adibarra Mar 19, 2026
a158758
feat: add blog SEO (JSON-LD, RSS feed, dynamic OG images, enriched me…
adibarra Mar 19, 2026
a11bb56
Merge branch 'master' into feat/blog-infra
adibarra Mar 21, 2026
0ac758c
remove blog infrastructure in preparation for MDX rewrite
adibarra Mar 21, 2026
c514c26
add next-mdx-remote, shiki, gray-matter dependencies
adibarra Mar 21, 2026
0abd489
add blog helpers library with MDX compilation and Shiki highlighting
adibarra Mar 21, 2026
3a937b0
add MDX components map for blog
adibarra Mar 21, 2026
e36b7e0
add blog listing page
adibarra Mar 21, 2026
6fc8da0
add blog post page with MDX rendering and Shiki highlighting
adibarra Mar 21, 2026
b72b667
add dynamic OG image generation for blog posts
adibarra Mar 21, 2026
d40fb4f
add RSS feed for blog
adibarra Mar 21, 2026
369199e
wire blog into sitemap, layout RSS link, and header nav
adibarra Mar 21, 2026
0fc8571
remove unused react-markdown dependency
adibarra Mar 21, 2026
7236152
add blog smoke tests (vitest RSS feed, Cypress E2E)
adibarra Mar 21, 2026
41da042
add llms.txt and llms-full.txt for LLM discoverability
adibarra Mar 21, 2026
80ab1a4
add 10 OG image variants with preview page at /blog/og-preview
adibarra Mar 21, 2026
238eb10
add 5 more OG variants (v11-v15) with exact SemiAnalysis brand palette
adibarra Mar 21, 2026
0ab91f9
add 5 fully branded OG variants (v16-v20) matching GSA presentation s…
adibarra Mar 21, 2026
1f632c6
add 5 thumbnail-optimized OG variants (v21-v25) with huge text and mi…
adibarra Mar 22, 2026
284e4e2
render OG preview at actual platform sizes (Twitter, Slack, iMessage)
adibarra Mar 22, 2026
f8e44a2
fix OG preview images rendering at correct pixel sizes
adibarra Mar 22, 2026
25f2319
fix OG preview layout: full size on own row, platform sizes below
adibarra Mar 22, 2026
c80f0a0
fix OG preview overflow breaking site layout
adibarra Mar 22, 2026
e6d926d
fix OG preview: use lazy loading, inline width, overflow-x-auto for t…
adibarra Mar 22, 2026
58bf72f
OG variants: bump text sizes for thumbnail legibility, replace SemiAn…
adibarra Mar 22, 2026
6252482
Merge branch 'master' into feat/blog-infra
adibarra Mar 22, 2026
8f1ac98
triple logo height in all OG variants for legibility
adibarra Mar 22, 2026
0529dc3
OG variants: move logo to top-right, increase title sizes
adibarra Mar 22, 2026
0da413d
OG variants: bump secondary text to 36-42px, fix contrast for WCAG AA…
adibarra Mar 22, 2026
918fa8f
OG overhaul: keep v11/v12/v13/v15, consolidate logos to brand/, renam…
adibarra Mar 23, 2026
d12376d
Merge branch 'master' into feat/blog-infra
adibarra Mar 23, 2026
f3a8f05
blog OG: consolidate to single circuit-sidebar design with extracted …
adibarra Mar 23, 2026
3b924d2
remove OG preview page and API route
adibarra Mar 23, 2026
8cfd4d7
move pattern SVGs to brand/
adibarra Mar 23, 2026
f861c36
blog list: match site layout with Card wrappers and full-width container
adibarra Mar 23, 2026
b8de0a0
blog: match site-wide Card/container layout, use 265 WPM reading time
adibarra Mar 23, 2026
eefcd40
blog post: add semantic HTML, subtitle, and inline tags
adibarra Mar 23, 2026
3e8acc2
rename "Blog" display text to "Articles" across site
adibarra Mar 23, 2026
4277df9
fix AUTHOR_HANDLE to correct Twitter handle @semianalysis_
adibarra Mar 23, 2026
7be07c8
mock blog posts in tests instead of using real content
adibarra Mar 23, 2026
6d0dba5
add slugify to sanitize blog slugs
adibarra Mar 23, 2026
e506a5c
Merge remote-tracking branch 'origin/master' into feat/blog-infra
adibarra Mar 23, 2026
a913ab1
fix E2E test heading and replace hardcoded author strings with AUTHOR…
adibarra Mar 23, 2026
8c4ad51
fix heading hierarchy: use h2 for blog page titles to match media/quotes
adibarra Mar 23, 2026
2f081a4
add active nav link highlighting based on current pathname
adibarra Mar 23, 2026
3398ceb
add blog analytics tracking for post clicks and back navigation
adibarra Mar 23, 2026
762eb99
add share buttons to blog posts and content links to footer
adibarra Mar 23, 2026
0099add
remove unused coverImage frontmatter field
adibarra Mar 24, 2026
d307305
add next/previous post navigation to blog posts
adibarra Mar 24, 2026
20b4172
add reading progress bar to blog posts
adibarra Mar 24, 2026
1210793
add table of contents with heading anchors to blog posts
adibarra Mar 24, 2026
7d0a617
add tag filtering to blog list and clickable tags on posts
adibarra Mar 24, 2026
1d174db
use article title as X share text on blog posts
adibarra Mar 24, 2026
7fab7d1
use brand color for active nav links (blue/gold per theme)
adibarra Mar 24, 2026
1eb4fde
fix tags: plain spans on post page, back inside card on list page
adibarra Mar 24, 2026
b7816ee
include h1 headings in table of contents
adibarra Mar 24, 2026
87c88d1
strip fenced code blocks before extracting TOC headings
adibarra Mar 24, 2026
12e38f8
use replaceAll for code block stripping
adibarra Mar 24, 2026
42cfd02
remove Content section from footer
adibarra Mar 24, 2026
f025c00
reorder llms.txt: site info, links, then articles
adibarra Mar 24, 2026
23ebc84
ToC: sticky sidebar at title level on ultrawides, active highlight, g…
adibarra Mar 24, 2026
f348591
ToC: JS-based sidebar positioning, inline fallback when not enough space
adibarra Mar 24, 2026
07a4dc7
fix ToC sidebar: defer position calculation until after hydration
adibarra Mar 24, 2026
aaf8ba3
fix data-blog-section attribute rendering
adibarra Mar 24, 2026
ee16c3e
always show TOC expanded, remove collapsible details
adibarra Mar 24, 2026
ea2f226
align sidebar TOC with title height, follow scroll via ref
adibarra Mar 24, 2026
559beed
add lorem ipsum sections to test post for scroll testing
adibarra Mar 24, 2026
a27e77c
replace test post with full lorem ipsum content
adibarra Mar 24, 2026
95638a2
double test post length with deeper h3 nesting throughout
adibarra Mar 24, 2026
edf888f
add two more lorem ipsum test posts, update existing title and subtitle
adibarra Mar 24, 2026
fc1cbdb
activate last TOC heading when scrolled to bottom of page
adibarra Mar 24, 2026
9b54ca3
add twitter:site, article:modified_time, and content:encoded to RSS feed
adibarra Mar 24, 2026
6d19b97
use brand color for progress bar, fix AUTHOR_HANDLE casing
adibarra Mar 24, 2026
f8619f3
remove content:encoded from RSS feed to avoid raw MDX/HTML in XML
adibarra Mar 24, 2026
2c4452f
add 32px scroll offset when clicking TOC headings
adibarra Mar 24, 2026
6b735ba
add Shiki dual-theme CSS for syntax highlighting in light/dark mode
adibarra Mar 24, 2026
5ffe3c0
re-add collapsible TOC on mobile, expanded on desktop
adibarra Mar 24, 2026
6eaea3d
add lorem ipsum comments to all code blocks
adibarra Mar 24, 2026
e3ad0a1
collapsible TOC on non-ultrawide, default closed with hint text
adibarra Mar 24, 2026
5c26b24
fix progress bar: use rect.top for accurate scroll tracking, handle s…
adibarra Mar 24, 2026
2eab768
use brand color on blog post title hover
adibarra Mar 24, 2026
12a08b6
add Figure component and placeholder images with lorem ipsum captions
adibarra Mar 24, 2026
7312470
Merge remote-tracking branch 'origin/master' into feat/blog-infra
adibarra Mar 24, 2026
0846d01
fix TOC sidebar initial position on page load
adibarra Mar 24, 2026
2ebad7e
increase footer top padding for breathing room
adibarra Mar 24, 2026
2615b3f
add InferenceX v2 article crosspost from Substack with 90 images
adibarra Mar 24, 2026
e5ce7dd
limit figure images to 66% width on desktop, full width on mobile
adibarra Mar 24, 2026
179060f
add paywall notice and Substack subscribe CTA at end of v2 article
adibarra Mar 24, 2026
6894217
add blurred paywall teaser and subscribe CTA at end of v2 article
adibarra Mar 24, 2026
c50cef9
add Blur MDX component for paywall teaser section
adibarra Mar 24, 2026
8914111
rename v2 article slug to match title
adibarra Mar 24, 2026
f636b03
add InferenceMAX v1 open source inference benchmarking article
adibarra Mar 24, 2026
13a5318
remove auto-inserted curly quotes from blog blockquotes
adibarra Mar 24, 2026
f1c635c
remove placeholder posts, tweak blog article styling and add copyrigh…
adibarra Mar 24, 2026
f56f052
move copyright footer inside article prose
adibarra Mar 24, 2026
cb35d5e
deduplicate heading IDs using parent section prefix
adibarra Mar 24, 2026
bbe91ee
add hash navigation for blog article headings
adibarra Mar 24, 2026
237358c
add copy-link icon on heading hover for blog articles
adibarra Mar 24, 2026
d91f3f8
polish heading link: show "Link copied" with fade-out, no underline
adibarra Mar 24, 2026
389e788
add remark-gfm for strikethrough and GFM support in blog
adibarra Mar 24, 2026
26f2efb
fix escaped asterisks in multiplication expression
adibarra Mar 24, 2026
50f4380
add width/height, lazy loading and async decoding to blog figure images
adibarra Mar 24, 2026
59af025
add lazy loading to blog images, fix inline code wrapping on mobile
adibarra Mar 24, 2026
1b79908
eagerly load first blog image, preconnect to Substack CDN
adibarra Mar 24, 2026
945dad9
Merge branch 'master' into feat/blog-infra
adibarra Mar 24, 2026
3d239a3
guard against null pathname in header isActive check
adibarra Mar 24, 2026
49bc990
fix gpu-specs e2e tests to use updated logo watermark path
adibarra Mar 24, 2026
204a3dd
update blog e2e test to use real article slug after placeholder removal
adibarra Mar 24, 2026
e0c4752
Merge remote-tracking branch 'origin/master' into feat/blog-infra
adibarra Mar 24, 2026
86a82ed
auto-scroll sidebar TOC to keep active heading visible
adibarra Mar 24, 2026
a139dda
add track() and error handling to heading link, add extractHeadings t…
adibarra Mar 24, 2026
5d825ab
add getAdjacentPosts tests, use node: protocol for fs/path imports
adibarra Mar 24, 2026
957f287
docs: add blog infrastructure documentation
adibarra Mar 24, 2026
daf05c8
add frontmatter and llms to vscode spellcheck dictionary
adibarra Mar 24, 2026
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
2 changes: 2 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"dotenv",
"esbuild",
"evals",
"frontmatter",
"gptoss",
"gsutil",
"hyperscaler",
Expand All @@ -38,6 +39,7 @@
"kimi",
"kimik",
"lefthook",
"llms",
"maxage",
"minimaxm",
"mooncake",
Expand Down
39 changes: 35 additions & 4 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,15 @@ pnpm test:e2e # Cypress E2E tests
```
packages/
├── app/ # Next.js frontend (@semianalysisai/inferencex-app)
│ ├── content/blog/ # MDX blog posts (frontmatter + content)
│ └── src/
│ ├── app/ # Pages, layouts, API routes (/api/v1/*)
│ │ └── blog/ # Blog list + [slug] post pages, OG image generation
│ ├── components/ # Tab sections: inference/, evaluation/, historical-trends/,
│ │ # throughput-calculator/, reliability/, gpu-specs/, ui/
│ │ # throughput-calculator/, reliability/, gpu-specs/, blog/, ui/
│ ├── hooks/api/ # React Query hooks (use-benchmarks, use-availability, etc.)
│ └── lib/ # Utilities, constants, d3-chart/, chart-utils, data-mappings
├── constants/ # Shared constants (GPU keys, model mappings)
│ └── lib/ # Utilities, constants, d3-chart/, chart-utils, blog, data-mappings
├── constants/ # Shared constants (GPU keys, model mappings, SEO)
└── db/ # DB layer, ETL, migrations, queries, ingest scripts
```

Expand All @@ -66,6 +68,15 @@ API routes (`packages/app/src/app/api/v1/`):

**API routes return raw DB data** — no presentation logic. Frontend handles all transformations.

Static content routes (no DB):

- `/blog` — blog listing (statically generated from MDX files in `content/blog/`)
- `/blog/[slug]` — blog post page with MDX rendering and OG image generation
- `/feed.xml` — RSS 2.0 feed
- `/llms.txt` — LLM-readable site index
- `/llms-full.txt` — full article content for LLM ingestion
- `/sitemap.xml` — dynamic sitemap (includes blog posts)

## Code Style & Tooling

- **Linter**: oxlint — `pnpm lint` / `pnpm lint:fix`
Expand All @@ -87,7 +98,7 @@ All interactive elements should have `track()` from `@/lib/analytics` (autocaptu

**Convention**: `[section]_[action]` — e.g., `latency_zoom_reset`, `calculator_bar_selected`, `tab_changed`

**Prefixes**: `latency_`, `interactivity_`, `gpu_timeseries_`, `inference_`, `calculator_`, `evaluation_`, `reliability_`, `tab_`, `selector_`
**Prefixes**: `latency_`, `interactivity_`, `gpu_timeseries_`, `inference_`, `calculator_`, `evaluation_`, `reliability_`, `tab_`, `selector_`, `blog_`, `social_`

## Tab Structure

Expand Down Expand Up @@ -118,6 +129,25 @@ Order: `inference` → `evaluation` → `historical` → `calculator` → `relia
6. Add disagg caveat banner in `ChartDisplay.tsx` for per-GPU or per-MW metrics (animated amber `border-l-2` banner pattern)
7. Expose in UI state: `InferenceContext.tsx`

### Add a new blog post

1. Create `packages/app/content/blog/<slug>.mdx` with frontmatter: `title`, `subtitle`, `date` (required), `tags`, `modifiedDate` (optional)
2. Write content using Markdown + custom MDX components (`Figure`, `Blur`)
3. No code changes needed — the post automatically appears in the blog list, sitemap, RSS feed, llms.txt, and gets a generated OG image

See [Blog](./docs/blog.md) for content format, available MDX components, and design details.

### Modify blog components

- Blog library (posts, headings, reading time): `src/lib/blog.ts`
- Blog list page: `src/app/blog/page.tsx`
- Blog post page: `src/app/blog/[slug]/page.tsx`
- MDX components: `src/components/blog/mdx-components.tsx`
- TOC sidebar: `src/components/blog/blog-toc.tsx`
- OG image generation: `src/app/blog/[slug]/og-image-render.tsx`
- RSS feed: `src/app/feed.xml/route.ts`
- SEO constants: `packages/constants/src/seo.ts`

### Add a new model or GPU

**First ask for the PR / GitHub Actions run URL** — see [Adding Entities](./docs/adding-entities.md) for the full workflow. Never ask other questions before getting the URL.
Expand All @@ -144,6 +174,7 @@ Detailed design rationale (the "why" and "how", not the "what") lives in [docs/]
- **[Testing](./docs/testing.md)** — Requirements, quality standards, pre-commit checklist
- **[Data Transforms](./docs/data-transforms.md)** — BenchmarkRow → AggDataEntry → InferenceData pipeline, hardware key construction, derived metrics
- **[State Ownership](./docs/state-ownership.md)** — Context provider state map, availability filtering cascade, comparison dates, URL params
- **[Blog](./docs/blog.md)** — MDX content system, SEO features, TOC sidebar, reading progress, analytics events

## Claude AI Agents

Expand Down
117 changes: 117 additions & 0 deletions docs/blog.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
# Blog Infrastructure

## Why MDX + Static Generation

Blog posts are MDX files in `packages/app/content/blog/`, compiled at build time via `next-mdx-remote`. This was chosen over a CMS or database because:

- **No runtime dependency**: Posts are part of the repo, versioned in git, reviewed in PRs. No CMS outage can break the blog.
- **MDX flexibility**: Authors can embed custom React components (`Figure`, `Blur`) alongside Markdown. This matters for image-heavy technical articles with captions, paywall teasers, and code blocks.
- **Static generation**: `generateStaticParams()` pre-renders all post pages. No server-side rendering at request time.

Syntax highlighting uses Shiki with dual light/dark themes (CSS class-based switching, not runtime theme detection).

## Content Format

```yaml
# Frontmatter (required: title, subtitle, date)
title: string
subtitle: string
date: YYYY-MM-DD
modifiedDate?: YYYY-MM-DD # Used in sitemap and JSON-LD
tags?: string[] # Used for filtering on /blog and in RSS categories
```

Slug is derived from the filename (e.g., `my-post.mdx` -> `my-post`), not from frontmatter. Reading time is calculated at 265 WPM.

## MDX Components Available to Authors

| Component | Usage | Notes |
| ---------------------------------------------- | -------------------------------- | --------------------------------------------------------------------------------- |
| `# / ## / ###` | Headings with auto-generated IDs | IDs are deduped: second `## Details` under `## Results` becomes `results-details` |
| `[text](url)` | Links | Internal links use `<Link>`, external get `target="_blank"` |
| `![alt](src)` | Images | Rendered via `next/image` with lazy loading (first image is eager) |
| `<Figure src="..." alt="..." caption="..." />` | Captioned figures | Uses `<img>` (not `next/image`) for external URLs |
| `<Blur>...</Blur>` | Paywall teaser blur overlay | Content is blurred, unselectable, and not clickable |

Heading ID deduplication: if two headings share a slug, the second gets prefixed with its parent heading's slug (e.g., `overview-details`). If no parent exists, a level suffix is appended (`intro-2`).

## Blog Library (`src/lib/blog.ts`)

| Function | Purpose |
| ------------------------- | --------------------------------------------------------- |
| `getAllPosts()` | All posts sorted newest-first |
| `getPostBySlug(slug)` | Single post meta + raw MDX content |
| `getAdjacentPosts(slug)` | `{ prev, next }` — prev is older, next is newer |
| `extractHeadings(rawMdx)` | h1-h3 headings with unique IDs (strips code blocks first) |
| `slugify(raw)` | URL-safe slug generation |
| `getReadingTime(content)` | Word count / 265, minimum 1 minute |

## SEO Features

### Dynamic OG Images (`/blog/[slug]/opengraph-image.tsx`)

1200x630px images generated at build time with `next/og` (Satori). Design: decorative tile sidebar + dark content panel with title, subtitle, date, and logo. Title font size scales (56-72px) based on length for readability at thumbnail sizes.

### RSS Feed (`/feed.xml`)

RSS 2.0 with Dublin Core and Atom extensions. Includes all posts with title, link, description, creator, pubDate, categories. Cached 1 hour.

### LLM Discovery (`/llms.txt`, `/llms-full.txt`)

- `/llms.txt`: Site description + article index with titles, URLs, and subtitles
- `/llms-full.txt`: Full raw MDX content of every post, for LLM context ingestion

### Sitemap Integration

Blog index at priority 0.8 (weekly), individual posts at priority 0.7 (monthly, uses `modifiedDate` if present).

### JSON-LD

- `/blog` page: `Blog` schema
- `/blog/[slug]` page: `BlogPosting` schema (headline, author, publisher, dates, wordCount, timeRequired)

## UI Components

### Table of Contents (`blog-toc.tsx`)

Two modes based on available viewport space:

- **Sidebar** (>= 240px right of content): Fixed position, follows scroll via imperative DOM updates in a scroll handler. Active heading tracked via `IntersectionObserver` with `rootMargin: '0px 0px -80% 0px'`. Falls back to last heading when scrolled to page bottom.
- **Inline** (narrow screens): Collapsible `<details>` card.

The sidebar position is calculated relative to the `[data-blog-section]` element and updated on scroll/resize.

### Reading Progress Bar (`reading-progress-bar.tsx`)

Fixed-top 0.5px bar tracking scroll position within the `<article>` element. Fires milestone events at 25/50/75/100% thresholds (each fires only once per page load).

### Heading Links (`heading-link.tsx`)

Copy-to-clipboard button shown on heading hover. State cycle: idle -> copied ("Link copied" text) -> fade out -> idle.

### Post Navigation (`blog-post-nav.tsx`)

Previous (older) / Next (newer) post links with title display. Uses `getAdjacentPosts()`.

## Analytics Events

All blog analytics use the `blog_` prefix per the `[section]_[action]` convention:

| Event | Trigger |
| ------------------------------------------------ | ---------------------------- |
| `blog_post_clicked` | Click post card on list page |
| `blog_toc_clicked` | Click TOC heading |
| `blog_read_milestone` | Scroll past 25/50/75/100% |
| `blog_heading_link_copied` | Copy heading link |
| `blog_nav_prev` / `blog_nav_next` | Click prev/next post |
| `blog_back_clicked` | Click back to articles |
| `blog_tag_filtered` | Click tag filter |
| `social_share_twitter` / `social_share_linkedin` | Click share buttons |

## Adding a New Blog Post

1. Create `packages/app/content/blog/<slug>.mdx` with required frontmatter (`title`, `subtitle`, `date`)
2. Add optional `tags` and `modifiedDate` frontmatter
3. Write content using standard Markdown + available MDX components
4. The post automatically appears in: blog list, sitemap, RSS feed, llms.txt, OG image generation
5. No code changes needed — just the MDX file
1 change: 1 addition & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@ Design rationale and non-obvious conventions. See [CLAUDE.md](../CLAUDE.md) for
- [Testing](./testing.md) — Requirements, quality standards, pre-commit checklist
- [Data Transforms](./data-transforms.md) — Full pipeline from BenchmarkRow to RenderableGraph: type hierarchy, hardware key construction, derived metrics, memoization strategy
- [State Ownership](./state-ownership.md) — Which context owns which state, availability filtering cascade, comparison date mechanics, URL param sync
- [Blog](./blog.md) — MDX content system, SEO features (OG images, RSS, llms.txt, JSON-LD), TOC sidebar, reading progress, heading links, analytics events
Loading
Loading