diff --git a/docs/internal/specs/2026-04-29-pr39-handoff.md b/docs/internal/specs/2026-04-29-pr39-handoff.md new file mode 100644 index 0000000..56da996 --- /dev/null +++ b/docs/internal/specs/2026-04-29-pr39-handoff.md @@ -0,0 +1,293 @@ +# PR #39 Handoff — Docs Site Revamp (Rounds 1-6) + +**Date:** 2026-04-29 +**Author:** previous Claude Code session +**Audience:** next agent picking up the docs work + +--- + +## TL;DR + +PR #39 is open, mergeable, **all 22 review threads resolved (0 unresolved)**, CI green, CodeRabbit green. All 6 docs rounds + the Codex P1/P2 triage + the gen-commands preservation fix + the complete light-theme tokens fix are landed. Two clear next steps: + +1. **Round 6.5** — Claude Design (CD) is shipping `mdx-strict-mode-rules.md` as a small standalone deliverable. Just integrate it under `docs/internal/specs/` when it arrives. +2. **Round 7** — visitor walkthrough on the open PR branch. Output is a prioritized issue list per page, NOT a generated zip. CD is waiting for the green light. + +The dev server has been freshly restarted with `.astro` cache cleared on `c9c6f5c` (force-dark + defensive gen-commands). Your job is mostly: finish round 7, absorb whatever CD ships for 6.5, push final fixes to PR #39, ping the human when ready to merge. + +--- + +## Where we are right now + +### Git state +- **Branch:** `docs/site-revamp` (you're on it) +- **HEAD:** `c9c6f5c feat(theme): force dark + harden gen-commands against custom-page wipes` +- **Origin:** in sync, pushed +- **Master tip:** `c745101` (PR #39 forks from there, ten commits ahead) + +### Branch contents (commits on `docs/site-revamp` not on `master`): + +| SHA | Round | What it added | +|-----|-------|--------------| +| `20c7159` | R1 | CAVE theme + per-page Copy/Open-in-LLM dropdown (`PageActions.astro`, `PageTitle.astro`, `[...slug].md.ts`) | +| `05a3d51` | R2 | Keystone-emphasis chain diagram, native-vs-fsuite block, drone sensor hero | +| `aa17ce4` | R3 | Monokai terminal blocks, full agent-scrapeable `cheatsheet.mdx`, MCP-sequential note | +| `9e492d6` | R4 | 14 per-command drone profile preambles | +| `c4d7128` | R5 | `architecture/index.mdx` (NEW), MCP/Hooks/Telemetry rebuilt, delivery flow CSS | +| `36053d5` | R6 | Story-page HUD rebuild (Episodes 1/2/3 + Lightbulb) | +| `a5a25ca` | triage | Codex P1/P2 review wave 1+2+3 (16 fixes total this commit) | +| `bc2afd3` | docs | First handoff doc for next agent | +| `e566679` | fix | gen-commands.mjs preserves preambles above `## Help output` (Codex P1 lH6b) | +| `42ad05f` | docs | Handoff updated with newly-reported visual bugs | +| `f244d02` | fix | Complete light-theme Starlight token overrides (Codex P2 lXf8) | +| `dca597a` | docs | Handoff updated to strike light-theme bug — fixed | +| `7070184` | docs | Handoff refresh after dev-server rebuild | +| `c9c6f5c` | feat | Force dark theme (hide toggle, pin data-theme) + defensive gen-commands | + +### PR #39 +- URL: https://github.com/lliWcWill/fsuite/pull/39 +- State: OPEN, MERGEABLE +- Checks: `test-suite` SUCCESS, `CodeRabbit` SUCCESS +- Threads: **22/22 resolved** (16 fixed-with-commit, 3 false positives thumbs-down, 3 deferred-with-rationale thumbs-down) + +### Sister branch — `codex/fread-media-pdf-image` (PR #38) +- This is the media-reading feature branch, **not yours** +- Last cleaned to drop the docs commits (was polluting feature scope) +- Tip is `dceb006 fix: align media MCP caps with full mode` +- If the human asks about media reading, that's where it lives + +--- + +## Dev environment + +### Server is already running (freshly restarted at 17:28 on `c9c6f5c`) +- Astro dev server on `http://localhost:4321/fsuite/` +- PIDs: **15076 + 15077** (find live with `pgrep -af "astro dev"`) +- Log: `/tmp/fsuite-site-dev.log` (previous session at `/tmp/fsuite-site-dev.log.prev`) +- Started detached via `setsid nohup npm run dev > /tmp/fsuite-site-dev.log 2>&1 < /dev/null &` from `site/` +- `.astro` cache was cleared on restart so all 6 docs rounds + the force-dark overrides + defensive gen-commands fix are loaded fresh +- **Theme is now force-dark** — the toggle UI is gone (ThemeSelect override) and `data-theme="dark"` is pinned on every load (ThemeProvider override). The `:root[data-theme='light']` block in `custom.css` stays as dormant code. + +### If the server breaks +1. **Stale content cache** (most common): `rm -rf site/.astro && touch ` to force re-scan +2. **Hung after rapid branch switches:** `kill ` then re-launch with the same setsid command (don't run npm in foreground — long-running command will time out) +3. **404 on a real file:** Starlight's `starlight-docs-loader` caches per-file YAML/MDX parse failures. Edit + save the file again to force a reload, OR `touch` it. + +### Verify pages render +```bash +for url in / architecture/ architecture/chains/ reference/cheatsheet/ getting-started/first-contact/ commands/fmap/ story/episode-1/ story/lightbulb/ ; do + code=$(curl -s -o /dev/null -w "%{http_code}" "http://localhost:4321/fsuite$url") + echo " $code $url" +done +``` + +--- + +## What's next (in priority order) + +### 1. Round 6.5 — CD's `mdx-strict-mode-rules.md` + +**Status:** CD greenlit, agent is waiting for delivery. + +**What it is:** Single standalone file for `docs/internal/specs/mdx-strict-mode-rules.md`. Contains the 4 MDX `
` rules + YAML frontmatter colon rule + dev-server caching gotchas. Audience is FUTURE BUILDERS (any agent extending the site), not visitors.
+
+**When it arrives** (probably as a zip in `~/Downloads/round6.5.zip` or similar):
+1. Unzip
+2. Read README
+3. `cp` into target path (or `fwrite` if rewriting)
+4. Verify dev server doesn't break (it's docs-internal, not in the site collection, so it shouldn't)
+5. Commit + push to `docs/site-revamp`
+6. Update PR #39 description if needed
+
+**Why standalone, not folded into round 7:** different audiences. R7 = visitor review. R6.5 = engineering rule reference. CD argued this convincingly; honor the split.
+
+### 2. Round 7 — visitor walkthrough
+
+**Status:** ready to start. CD is waiting for ping. Walk on the OPEN PR BRANCH (current state).
+
+**The brief from CD (verbatim):**
+> Read the site front-to-back as a brand-new visitor with no prior context, flag broken handoffs, redundant content, missing bridges, dead links, voice/tone drift between pages, and pacing problems. Output should be a prioritized issue list per page + a top-line "what to fix first" recommendation, not a generated zip — that comes after we know what needs fixing.
+
+**Suggested route to walk** (matches first-time-visitor flow):
+1. `/` (homepage) — does the hero land? Does the chain diagram make sense?
+2. `/getting-started/installation/` — can someone install in <2 min?
+3. `/getting-started/mental-model/` — does the keystone framing click?
+4. `/getting-started/first-contact/` — does the ftree sample feel like a "first 30 seconds" moment?
+5. `/reference/cheatsheet/` — is it scannable as a reference?
+6. `/commands/fs/` → walk through a few command pages (fmap is the keystone, fbash is the MCP escape hatch — those are the two most important)
+7. `/architecture/` → `/architecture/chains/` → `/architecture/mcp/` → `/architecture/hooks/` → `/architecture/telemetry/`
+8. `/story/episode-0/` → `/episode-1/` → `/episode-2/` → `/episode-3/` → `/lightbulb/`
+
+**Output format** (prioritized, per page):
+- 🔴 Blocker (broken link, factual error, page that 404s)
+- 🟠 Major (confusing handoff, redundant with another page, missing bridge)
+- 🟡 Minor (voice/tone drift, pacing, polish)
+- ⚪ Nit (typo, formatting)
+
+**Top-line recommendation:** "If we only fix one thing, fix X."
+
+### 3. After R7 — absorb feedback into PR #39
+
+Reviewer (the human) decides whether to:
+- Amend on `docs/site-revamp` (keep PR clean, one squash) — preferred for polish
+- Open follow-up issue (if walk surfaces big rework) — only for fundamental rethinks
+
+---
+
+## CD partnership context
+
+**Claude Design (CD)** is an external design/content partner — a separate Claude conversation operated by the human, with its own session context. CD ships visual + content deliverables as zip files (delivered via `~/Downloads/round.zip`). Each zip contains:
+- `README.md` with explicit integration prompts
+- `custom-components-r.css` (sometimes — CSS appendix)
+- `site-pages/*.mdx` and `*.md` (page content)
+
+**Your job vs CD's job:**
+- CD = generates the deliverable (visual design, content writing, copy)
+- You = integrate it (apply files, fix MDX strict-mode landmines, verify renders, commit, push)
+
+CD has a generation discipline. As of now, CD knows about and pre-applies these rules:
+- YAML frontmatter description with `: `, `#`, `[`, `>`, `|`, `&`, `*` → quote it
+- Bare `{` `}` inside `
` → escape as `\{` `\}` or `{` `}`
+- Blank lines inside `
` → use `·` separator
+- `
` and `
` → on their own lines (don't put content on same line as opener) +- Indentation inside `
` → use ` ` × N or lead-glyphs (`·`/`└`), never literal 4+ spaces
+- `*` literals inside `
` → quote (`"\*.ts"`) or escape (`*`)
+
+**Communication pattern:** the human pastes CD's reply into the conversation. You draft replies for the human to paste back. Treat CD as a peer collaborator with their own context — don't assume they know what's on your machine.
+
+---
+
+## Lessons learned (durable, may not be in CD's spec)
+
+### MDX strict-mode breakage modes (compendium)
+
+These caused 500s during rounds 2-6:
+
+| Trigger | Symptom | Fix |
+|---|---|---|
+| Bare `{` `}` in `
` | "Could not parse expression with acorn" / "Unexpected content after expression" | `\{` `\}` or `{` `}` |
+| Blank line inside `
` | "Expected a closing tag for `
` before end of paragraph" | `·` separator |
+| Paired `*` chars in `
` (e.g. `*.ts` × 2) | "Expected closing tag `` after end of `emphasis`" | `*` or quote it |
+| 4+ leading spaces in `
` | Same closing-tag-for-pre error | ` ` × N |
+| `
` followed by `` on same line | Same closing-tag error | Split `
\n /tmp/fsuite-site-dev.log 2>&1 < /dev/null &`.
+
+### Codex bot behavior on PR #39
+
+- Bot leaves both inline (line-anchored) and outside-diff comments.
+- Inline comments on PR #39: 21 findings, ~15 real, 2 false positives, 4 deferred.
+- False positives observed: claims about MCP tool registration count (verify with `grep -nE "server\.registerTool" mcp/index.js`).
+- When in doubt: read the actual code, then trust your eyes over Codex's wording.
+
+---
+
+## Open questions / pending decisions
+
+
+## NEW BUGS (reported at end of session, not yet addressed)
+
+These surfaced after the Codex triage push and are filed in a top-level comment on PR #39 ([link](https://github.com/lliWcWill/fsuite/pull/39#issuecomment-4347915103)). Tackle in a follow-up PR after R6.5/R7 land.
+
+### A. Page-navigation flicker
+Every link click between pages briefly flashes white/empty before the next page paints. Wasn't there in earlier rounds. Likely candidates:
+- Astro view transitions not configured (need `` in ``)
+- Dev-server-only artifact from Vite HMR + content collection rehydration
+- One of the new CSS animations (page-actions menu, HUD frame, status strip) re-running on every route change
+
+**Suggested first move:** reproduce on production build (`astro build && astro preview`) to confirm dev-only vs ships-to-deploy. If it ships, add `` to `PageTitle.astro` or the layout.
+
+### B. Light theme — DISABLED entirely in [`c9c6f5c`](https://github.com/lliWcWill/fsuite/commit/c9c6f5c)
+Originally fixed the light-theme tokens in `f244d02` (full Starlight palette overrides per the firecrawl-verified canonical pattern). After verifying the actual rendered light theme on the user's screen with Dark Reader extension OFF, the conclusion was: **even with all tokens correct, light mode loses ~70% of the CAVE design language** (bloom halos, scanlines, void-glow effects only read on dark surfaces). A correct-but-flat light theme felt worse than no light theme.
+
+Force-dark via two Starlight component overrides (registered in `astro.config.mjs`):
+- `HiddenThemeSelect.astro` — empty component, hides the toggle UI from the nav
+- `ForceDarkThemeProvider.astro` — `is:inline` script that pins `data-theme="dark"` and overwrites `localStorage.starlight-theme` on every page load (no theme flash, no escape hatch)
+- ExpressiveCode `themes` reduced to `['monokai']` only (was `['monokai', 'github-light']`)
+
+The `:root[data-theme='light']` token block in `custom.css` is left as dormant code — re-enabling the toggle later just means deleting the two component overrides + adding `github-light` back to expressiveCode.
+
+### gen-commands.mjs preamble preservation (already fixed)
+Codex thread `lH6b` flagged that `npm run build` (which runs `gen:commands && astro build`) was wiping the round-4 preambles. Verified + fixed in [`e566679`](https://github.com/lliWcWill/fsuite/commit/e566679): `buildPage()` now reads the existing file and splices the regenerated `## Help output` section onto the bottom, preserving everything above. Verified locally: `node site/scripts/gen-commands.mjs` re-ran without stripping the drone profile + canonical chains + monokai sample blocks.
+
+---
+
+1. **Round 7 scope:** walk-now on `docs/site-revamp` branch (CD's preference) — but if you want to wait until merge, that's also fine. Same content, different process risk.
+2. **Stylelint @import order + lowercase keywords:** deferred from triage. If round 7 touches `custom.css` for any other reason, fold these in. Otherwise leave for a separate cleanup pass.
+3. **Self-host Geist + JetBrains Mono fonts:** deferred. Currently using Google Fonts CDN via `@import url(...)`. Privacy + offline-build improvement, but bigger scope. Don't tackle in PR #39.
+4. **`pages/[...slug].md.ts` homepage slug:** Codex suggested mapping `index` → `''`. Current `index` slug works fine after the F1 base-path fix; not blocking. Defer unless URL pattern matters for SEO/sharing.
+
+---
+
+## Files most relevant to your work
+
+```
+site/src/components/PageActions.astro          # the dropdown widget; URL-build logic just got fixed
+site/src/components/PageTitle.astro            # Starlight override that hosts PageActions
+site/src/pages/[...slug].md.ts                 # raw markdown endpoint (`/.md`)
+site/src/styles/custom.css                     # 1745-line CAVE theme + all components
+site/src/content/docs/                         # all the actual content
+site/astro.config.mjs                          # base: '/fsuite', sidebar config
+docs/internal/specs/                           # this handoff lives here; CD's R6.5 will too
+```
+
+---
+
+## How to verify everything works after your changes
+
+```bash
+# Set up
+cd /home/player3vsgpt/Desktop/Projects/fsuite
+git branch --show-current  # should be docs/site-revamp
+
+# Test pages
+sleep 2
+for url in / architecture/ architecture/chains/ reference/cheatsheet/ getting-started/first-contact/ commands/fmap/ commands/fbash/ story/episode-1/ story/lightbulb/ ; do
+  code=$(curl -s -o /dev/null -w "%{http_code}" "http://localhost:4321/fsuite$url")
+  echo "  $code  $url"
+done
+
+# Check log for errors since some timestamp
+awk '/14:5[5-9]:|15:|16:|17:/' /tmp/fsuite-site-dev.log | grep -ciE "MDXError|fail" || echo 0
+
+# When done — commit & push to update PR #39
+git add site/
+git commit -m ""
+git push origin docs/site-revamp
+```
+
+---
+
+## fcase trail
+
+For continuity, search the fcase ledger for prior decisions:
+
+```
+fcase find "site round" --deep --statuses all
+fcase find "pr39" --deep --statuses all
+fcase find "mdx" --deep --statuses all
+```
+
+Resolved cases worth knowing about:
+- `#523 site-round3-monokai`
+- `#524 site-round4-preambles`
+- `#525 site-round5-architecture`
+- `#526 site-round6-story-pages`
+- `#527 pr39-codex-triage`
+
+---
+
+## What I'd do first if I were you
+
+1. Verify dev server is up (`curl http://localhost:4321/fsuite/`).
+2. Glance at `git log --oneline -10` and `gh pr view 39` to confirm state.
+3. Check if CD's R6.5 zip has dropped in `~/Downloads/`. If yes, integrate first.
+4. If R6.5 is integrated (or not yet shipped), tell the human you're ready for round 7 walkthrough.
+5. Walk it. Output a prioritized issue list. Send it back.
+
+Good luck. The site is in really good shape — just polish from here.
diff --git a/site/astro.config.mjs b/site/astro.config.mjs
index 13757b1..671f4a9 100644
--- a/site/astro.config.mjs
+++ b/site/astro.config.mjs
@@ -18,6 +18,11 @@ export default defineConfig({
         // Override the page H1 so we can render the page-actions dropdown
         // (Copy / View / Open in LLM) inline with the title.
         PageTitle: './src/components/PageTitle.astro',
+        // Force-dark theme: hide the toggle UI + pin data-theme="dark" on load.
+        // CAVE theme is dark-only by design — bloom/scanline effects don't
+        // translate to a light canvas.
+        ThemeSelect: './src/components/HiddenThemeSelect.astro',
+        ThemeProvider: './src/components/ForceDarkThemeProvider.astro',
       },
       social: [
         { icon: 'github', label: 'GitHub', href: 'https://github.com/lliWcWill/fsuite' },
@@ -26,7 +31,7 @@ export default defineConfig({
         './src/styles/custom.css',
       ],
       expressiveCode: {
-        themes: ['monokai', 'github-light'],
+        themes: ['monokai'],
         styleOverrides: {
           codeFontFamily: '"JetBrainsMono Nerd Font", "Fira Code", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace',
         },
diff --git a/site/scripts/gen-commands.mjs b/site/scripts/gen-commands.mjs
index f5db57d..6d5dfae 100644
--- a/site/scripts/gen-commands.mjs
+++ b/site/scripts/gen-commands.mjs
@@ -10,7 +10,7 @@
  */
 
 import { execSync } from 'node:child_process';
-import { mkdirSync, writeFileSync, existsSync } from 'node:fs';
+import { mkdirSync, writeFileSync, existsSync, readFileSync } from 'node:fs';
 import { dirname, join, resolve } from 'node:path';
 import { fileURLToPath } from 'node:url';
 
@@ -65,24 +65,17 @@ function captureHelp(toolName) {
 
 /**
  * Build a markdown page for one tool.
+ *
+ * Preserves any handcrafted preamble above the `## Help output` heading
+ * (e.g. the round-4 drone-profile cards + canonical chains + monokai
+ * terminal samples). The auto-generated `## Help output` section + `## See
+ * also` are regenerated on every build so the help text stays in sync with
+ * the binary; everything above is treated as durable hand-edited content.
  */
-function buildPage(tool) {
+function buildPage(tool, outPath) {
   const helpText = captureHelp(tool.name);
-  const frontmatter = [
-    '---',
-    `title: ${tool.emoji} ${tool.title}`,
-    `description: ${tool.tagline}`,
-    `sidebar:`,
-    `  order: ${tool.order}`,
-    '---',
-  ].join('\n');
-
-  const body = `
-## ${tool.tagline}
 
-\`${tool.name}\` is part of the fsuite toolkit — a set of fourteen CLI tools built for AI coding agents.
-
-## Help output
+  const helpSection = `## Help output
 
 The content below is the **live** \`--help\` output of \`${tool.name}\`, captured at build time from the tool binary itself. It cannot drift from the source — regenerating the docs regenerates this section.
 
@@ -97,7 +90,41 @@ ${helpText}
 - [View source on GitHub](https://github.com/lliWcWill/fsuite/blob/master/${tool.name})
 `;
 
-  return frontmatter + '\n' + body;
+  // If a page already exists, preserve everything above `## Help output`
+  // (frontmatter + tagline H2 + intro paragraph + any round-4 preamble) and
+  // splice the regenerated help + see-also onto the bottom.
+  //
+  // Defensive case: if the file exists but the marker is missing, the page
+  // has been hand-customized (e.g. `fwrite.md` uses `## Usage notes` since
+  // its surface is MCP-only). Leave it untouched rather than nuking the
+  // custom content with the default first-time-generation template.
+  if (existsSync(outPath)) {
+    const existing = readFileSync(outPath, 'utf8');
+    const helpIdx = existing.indexOf('## Help output');
+    if (helpIdx > 0) {
+      return existing.slice(0, helpIdx) + helpSection;
+    }
+    return existing;
+  }
+
+  // First-time generation: build the default frontmatter + tagline H2 + intro
+  const frontmatter = [
+    '---',
+    `title: ${tool.emoji} ${tool.title}`,
+    `description: ${tool.tagline}`,
+    `sidebar:`,
+    `  order: ${tool.order}`,
+    '---',
+  ].join('\n');
+
+  const intro = `
+## ${tool.tagline}
+
+\`${tool.name}\` is part of the fsuite toolkit — a set of fourteen CLI tools built for AI coding agents.
+
+`;
+
+  return frontmatter + intro + helpSection;
 }
 
 /**
@@ -110,8 +137,8 @@ function main() {
 
   let written = 0;
   for (const tool of TOOLS) {
-    const page = buildPage(tool);
     const outPath = join(OUT_DIR, `${tool.name}.md`);
+    const page = buildPage(tool, outPath);
     writeFileSync(outPath, page, 'utf8');
     written++;
     console.log(`  ✓ ${tool.name.padEnd(10)} → src/content/docs/commands/${tool.name}.md`);
diff --git a/site/src/components/ForceDarkThemeProvider.astro b/site/src/components/ForceDarkThemeProvider.astro
new file mode 100644
index 0000000..451b1af
--- /dev/null
+++ b/site/src/components/ForceDarkThemeProvider.astro
@@ -0,0 +1,23 @@
+---
+/**
+ * ForceDarkThemeProvider.astro — Starlight `ThemeProvider` override that pins
+ * the site to dark mode permanently.
+ *
+ * Replaces Starlight's default theme-detection script (which respects
+ * `localStorage.starlight-theme` + `prefers-color-scheme`). Since the toggle
+ * UI is also hidden via `HiddenThemeSelect.astro`, no code path can flip the
+ * site to light. The `[data-theme='light']` block in custom.css remains as
+ * dormant code in case we ever re-enable the toggle.
+ *
+ * Runs `is:inline` so it executes before any visible paint — no theme flash.
+ */
+---
+
diff --git a/site/src/components/HiddenThemeSelect.astro b/site/src/components/HiddenThemeSelect.astro
new file mode 100644
index 0000000..6b6b1a4
--- /dev/null
+++ b/site/src/components/HiddenThemeSelect.astro
@@ -0,0 +1,11 @@
+---
+/**
+ * HiddenThemeSelect.astro — Starlight `ThemeSelect` override that renders nothing.
+ *
+ * The fsuite docs site is dark-only by design (CAVE theme: bioluminescent green
+ * accents on void-black surfaces; the bloom + scanline effects only read on a
+ * dark canvas). The toggle is therefore disabled — paired with
+ * `ForceDarkThemeProvider.astro` which pins `data-theme="dark"` regardless of
+ * any prior user preference.
+ */
+---
diff --git a/site/src/components/PageActions.astro b/site/src/components/PageActions.astro
index 0a23736..495f94e 100644
--- a/site/src/components/PageActions.astro
+++ b/site/src/components/PageActions.astro
@@ -18,26 +18,33 @@
  *       · View source on GitHub
  *
  * Accessibility:
- *   - Real