From ebb70168ad50c406f511a859e6212d0f9d2a98f8 Mon Sep 17 00:00:00 2001 From: Brian Love Date: Sat, 16 May 2026 21:28:01 -0700 Subject: [PATCH 01/20] docs(gtm): spec for content-pillar-pages as blog (Spec 5) Blog infrastructure at cacheplane.ai/blog plus one seed pillar post ("Build a streaming chat UI in Angular with LangGraph") written in Brian Love's voice. Flat /blog/[slug] routes, date-prefixed MDX, RSS, OG, sitemap, new blog:* analytics namespace. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../2026-05-17-content-pillar-pages-design.md | 591 ++++++++++++++++++ 1 file changed, 591 insertions(+) create mode 100644 docs/superpowers/specs/gtm/2026-05-17-content-pillar-pages-design.md diff --git a/docs/superpowers/specs/gtm/2026-05-17-content-pillar-pages-design.md b/docs/superpowers/specs/gtm/2026-05-17-content-pillar-pages-design.md new file mode 100644 index 000000000..770a4e0b7 --- /dev/null +++ b/docs/superpowers/specs/gtm/2026-05-17-content-pillar-pages-design.md @@ -0,0 +1,591 @@ +--- +workstream: content-pillar-pages +status: approved +owner: brian +phase: 2 +spec: docs/superpowers/specs/gtm/2026-05-17-content-pillar-pages-design.md +plan: docs/superpowers/plans/gtm/2026-05-17-content-pillar-pages.md +parent: docs/superpowers/specs/gtm/2026-05-13-gtm-meta-design.md +--- + +# Spec 5 — Content Pillar Pages (Design) + +> Phase 2 ecosystem deliverable. Stand up `/blog` on cacheplane.ai as a long-form SEO surface — flat URLs, MDX-driven, RSS-fed, OG-imaged, analytics-instrumented. Ship one seed pillar post written in Brian Love's existing blog voice. Remaining pillar posts land as follow-up commits against the same infra. + +## 1. Goal + +Two outcomes: + +1. **A production-grade blog infrastructure** on `cacheplane.ai/blog` that reuses the existing MDX renderer + design system + analytics pipeline. Frontmatter-driven, flat URLs, RSS feed, per-post OG image, sitemap inclusion, dedicated `blog:*` analytics namespace. +2. **The first pillar post — "Build a streaming chat UI in Angular with LangGraph"** — drafted in Brian Love's existing voice (sourced from `~/repos/brianflove/src/content/posts/2026-*.md`). The seed post is the highest-leverage SEO target for the developer-track funnel. + +Phase 2 exit criterion (per `gtm.md`) is "6 pillar pages indexed, organic traffic baseline captured." Spec 5 satisfies the infrastructure half plus 1 of the 6 posts. The remaining 5 land as follow-up content commits against the same infra — each a focused review. + +## 2. Context + +- Parent: `docs/superpowers/specs/gtm/2026-05-13-gtm-meta-design.md` §6 (Phase 2: Ecosystem path). +- Reference layout: `~/repos/dawn/apps/web/{app/blog,content/blog}` — date-prefixed MDX filenames, flat URLs, RSS, tags. Adopted wholesale. +- Voice reference: `~/repos/brianflove/src/content/posts/2026-*.md` — Brian's existing blog. The seed post must match this voice. +- Reused infra already in the cacheplane website: + - `next-mdx-remote/rsc` MDX pipeline with custom components (`Callout`, `Steps`, `Tabs`, `Card`, `CodeGroup`, `Pre`). + - `rehype-pretty-code` (tokyo-night), `rehype-slug`, `remark-gfm`. + - `createPageMetadata()` for OG + canonical. + - `getSitemapRoutes()` for sitemap. + - Default `opengraph-image.tsx` at the route level via `next/og` `ImageResponse`. +- `docs:*` analytics namespace is the established pattern; `blog:*` is the parallel namespace this spec introduces. + +## 3. Scope + +**In scope:** + +- **Routes:** + - `/blog` — index page (date-sorted, featured post on top, post grid below, tag chips). + - `/blog/[slug]` — dynamic post route reusing ``. Renders title, date, author byline, body, tags, "more posts" links at bottom. + - `/blog/[slug]/opengraph-image.tsx` — per-post 1200×630 OG card with post title + author. Falls back to the default site OG card if the post can't be resolved. + - `/blog/rss.xml` — RSS 2.0 feed, `Content-Type: application/rss+xml`. +- **Content:** `apps/website/content/blog/YYYY-MM-DD-.mdx`. Date-prefixed filenames so the directory listing matches publish order. Slug derived from filename minus the date prefix. +- **Frontmatter shape** (required keys bolded): + - **`title`** — string, 60-80 chars + - **`description`** — string, 130-180 chars (used in ``, RSS ``, social previews) + - **`date`** — ISO `YYYY-MM-DD` + - `tags` — `string[]`, lowercase kebab-case + - **`author`** — registry key (`brian` in v1) + - `featured` — boolean, optional. The first `featured: true` post in date-desc order becomes the featured slot on `/blog`. If none, the most recent post is featured. + - `draft` — boolean, optional. When true, the post is skipped by `getAllPosts()` in production builds and from RSS + sitemap. (Internal preview path TBD; out of scope here. Drafts simply don't appear in listings; a direct URL still works for review.) +- **Author registry** at `apps/website/src/lib/blog-authors.ts` with one entry (`brian`) — name, role, optional `bio`, optional `twitter`, optional `github`, optional `avatar` path. The byline renders gracefully without avatar/twitter/github fields. +- **Analytics:** add two new event names to `events.ts` and `taxonomy.md`: + - `blog:cta_click` — tracked CTAs inside post bodies (e.g., links to `/pricing`, `/contact`). Properties: `surface: 'blog'`, `cta_id?: CtaId`, `destination_url?: string`. + - `blog:copy_code_click` — copy-button click on a code block. Properties: `surface: 'blog'`, `code_lang?: string`. + - Add `'blog'` to the `AnalyticsSurface` union. +- **Nav:** add a `Blog` link to the site nav. Position between `Pricing` and the `GitHub` icon (or wherever the navbar's existing convention puts secondary links). +- **Sitemap:** `getSitemapRoutes()` includes `/blog` plus every published `/blog/[slug]`. Sorted desc by date. `changeFrequency: 'weekly'` for the index, `'monthly'` for individual posts. Priority 0.7 for both. +- **Seed pillar post** at `apps/website/content/blog/2026-05-17-build-a-streaming-chat-ui-in-angular-with-langgraph.mdx`. ~2,500 words. Written in Brian Love's voice (see §6 for voice rules and outline). +- **Unit tests:** + - `apps/website/src/lib/blog.spec.ts` — `getAllPosts()` returns posts sorted desc; `getPostBySlug()` returns a known seed post; frontmatter parsing populates required fields; missing-required-field frontmatter throws a helpful error. + - `apps/website/src/components/blog/PostCard.spec.tsx` — renders title, date, and tag chips for a given post. +- **Verification:** `nx run website:build` green; manual smoke at `localhost:3000/blog` and `/blog/[seed-slug]`; RSS feed validates as XML. + +**Out of scope (deferred to follow-ups):** + +- Posts 2-6 of the 6-post pillar set. Each lands as a separate commit/PR against this infra. +- `/blog/tags/[tag]` per-tag pages. v1 has tag chips on the index that filter client-side (or just link nowhere). Per-tag pages are a follow-up. +- A draft preview workflow (e.g., `?preview=token` to render `draft: true` posts on production). +- Blog-specific MDX components (e.g., ``, `