Developer Portal (Docusaurus IA refocus) Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Refocus the app/test-gallery Docusaurus site into an intent-organized developer portal — three sections (Developer / Design System / Test Gallery) + a landing page — served at developer.eldr-labs.duckdns.org.
Architecture: Authored developer pages live in a committed app/test-gallery/authored/ source dir (outside the wiped docs/ tree). build_test_gallery.mjs keeps its wholesale docs/ wipe, then (a) copies authored/developer/** → docs/developer/, and (b) generates docs/developer/decisions/** + docs/developer/specs-and-plans/** from the repo docs/. The navbar gets 3 items and sidebars.js gets 3 autogenerated sidebars.
Tech Stack: Node (ESM), Docusaurus v3, Markdown/MDX. NO Dart/Flutter in this plan.
Global Constraints
- This is a Node/Docusaurus + Markdown task — NO
fvm flutter/Dart. Run fromapp/test-gallery/. - Capture command exit codes honestly: run
<cmd> > /tmp/t.txt 2>&1; echo "EXIT=$?"then inspect; never decide pass/fail from a pipedtail. The build passes only whenEXIT=0and the output shows[SUCCESS] Generated static files in "build". build_test_gallery.mjswipesdocs/wholesale (fs.rmSync(DOCS,{recursive,force})at ~line 226) — so authored pages MUST live OUTSIDEdocs/(inapp/test-gallery/authored/) and be COPIED intodocs/by the build. Never put hand-written content directly underdocs/.onBrokenLinks: 'warn'(already set) — the build must not be made to hard-fail on links; keep cross-links valid where practical.- The flow-gallery + Widgetbook generation in
build_test_gallery.mjs(thegenerateFlowGallery/generateDesignSystemparts) stays untouched. - Content is ingested as-is / lightly normalized; only the authored pages are curated prose (distilled from
rewhaven/CLAUDE.md). - Commit per task on branch
feat/developer-portal; do NOT push (the human pushes). Redeploy viascripts/deploy-developer-gallery.sh.
Today's shapes (verified)
app/test-gallery/scripts/build_test_gallery.mjs(261 lines): constsDOCS = <site>/docs,STATIC = <site>/static,REPO_DIR = app/../(= the rewhaven root). HelperswriteFile(p,text)(mkdirp + write) and a_category_.jsonwriter (~line 69). Itfs.rmSync(DOCS,{recursive,force})+mkdirSync(DOCS)(~226), writesdocs/index.md(landing, slug/, ~230), thengenerateFlowGallery()→docs/gallery/**andgenerateDesignSystem()→docs/design-system/**.app/test-gallery/sidebars.js: a singlegallerySidebar: [{type:'autogenerated', dirName:'.'}].app/test-gallery/docusaurus.config.js: navbar has ONE item — adocSidebar→gallerySidebarlabelled "Gallery" (~line 96-99).- Repo docs to ingest (
REPO_DIR/docs/):decisions/2026-*.md(4),superpowers/specs/2026-*.md(3),superpowers/plans/2026-*.md(3),rebuild-spec.md. The dev guide source:REPO_DIR/CLAUDE.md(single file; no package-level CLAUDE.md).
Task 1: generateDeveloperDocs() — copy authored + ingest repo docs
Files:
- Modify:
app/test-gallery/scripts/build_test_gallery.mjs - Create:
app/test-gallery/authored/developer/.gitkeep(placeholder so the copy source exists; real pages land in Task 2)
Interfaces:
-
Consumes: the existing
DOCS,REPO_DIR,writeFile,_category_.jsonhelpers. -
Produces: a
generateDeveloperDocs()function, called inmain()AFTER thedocs/wipe and the flow/design-system generators. It:- Copies
app/test-gallery/authored/developer/**→docs/developer/**verbatim (recursive;fs.cpSync(AUTHORED_DEV, path.join(DOCS,'developer'), {recursive:true})).const AUTHORED_DEV = path.join(SITE_DIR, 'authored', 'developer'). - Generates
docs/developer/decisions/<file>.mdfrom eachREPO_DIR/docs/decisions/*.mdanddocs/developer/specs-and-plans/<file>.mdfrom eachREPO_DIR/docs/superpowers/specs/*.md+plans/*.md+REPO_DIR/docs/rebuild-spec.md— copying the body and prepending a front-matter block (---\ntitle: <derived>\n---) if the file lacks one. Derive the title from the first H1 (# …) or the filename. - Writes
_category_.jsonfordocs/developer(label "Developer", position 1),docs/developer/decisions(label "Decisions", position 40),docs/developer/specs-and-plans(label "Specs & Plans", position 50). (Authored pages set their own order via front-mattersidebar_positionin Task 2.)
- Copies
-
Step 1: Write a failing structural check
app/test-gallery/scripts/check_developer_docs.mjs (a tiny assert script — this repo's "test" for the build):
import fs from 'node:fs';
import path from 'node:path';
const DOCS = path.resolve(import.meta.dirname, '..', 'docs');
const must = [
'developer/decisions', // generated from repo docs/decisions
'developer/specs-and-plans', // generated from repo docs/superpowers + rebuild-spec
];
let ok = true;
for (const rel of must) {
const p = path.join(DOCS, rel);
const exists = fs.existsSync(p) && fs.readdirSync(p).some((f) => f.endsWith('.md'));
console.log(`${exists ? 'OK ' : 'MISSING'} docs/${rel}`);
ok = ok && exists;
}
// the generated decisions dir must contain the 4 known decision files
const dec = path.join(DOCS, 'developer', 'decisions');
const decCount = fs.existsSync(dec) ? fs.readdirSync(dec).filter((f) => f.endsWith('.md')).length : 0;
console.log(`decisions .md count = ${decCount} (expect >= 4)`);
process.exit(ok && decCount >= 4 ? 0 : 1);
- Step 2: Run it against the current build → FAIL
Run: cd app/test-gallery && node scripts/build_test_gallery.mjs > /tmp/t.txt 2>&1; echo "GEN=$?"; node scripts/check_developer_docs.mjs; echo "CHECK=$?"
Expected: CHECK=1 (no docs/developer/ yet). (GEN=0 — the existing generator runs.)
- Step 3: Implement
generateDeveloperDocs()
Add const AUTHORED_DEV = path.join(SITE_DIR, 'authored', 'developer'); near the other consts. Add the function (mirror the existing generators' style + the writeFile/_category_.json helpers):
function copyRepoDocsInto(subdir, files, categoryLabel, categoryPosition) {
const dest = path.join(DOCS, 'developer', subdir);
fs.mkdirSync(dest, {recursive: true});
writeFile(path.join(dest, '_category_.json'),
JSON.stringify({label: categoryLabel, position: categoryPosition}, null, 2));
for (const src of files) {
if (!fs.existsSync(src)) continue;
let body = fs.readFileSync(src, 'utf8');
if (!body.startsWith('---')) {
const h1 = body.match(/^#\s+(.+)$/m);
const title = (h1 ? h1[1] : path.basename(src, '.md')).replace(/"/g, '\\"');
body = `---\ntitle: "${title}"\n---\n\n${body}`;
}
writeFile(path.join(dest, path.basename(src)), body);
}
}
function generateDeveloperDocs() {
// 1) authored pages (committed source, outside the wiped docs/) → docs/developer
if (fs.existsSync(AUTHORED_DEV)) {
fs.cpSync(AUTHORED_DEV, path.join(DOCS, 'developer'), {recursive: true});
} else {
fs.mkdirSync(path.join(DOCS, 'developer'), {recursive: true});
}
// developer section category
writeFile(path.join(DOCS, 'developer', '_category_.json'),
JSON.stringify({label: 'Developer', position: 1}, null, 2));
// 2) generated: decisions + specs-and-plans from the repo docs/
const decDir = path.join(REPO_DIR, 'docs', 'decisions');
const decisions = fs.existsSync(decDir)
? fs.readdirSync(decDir).filter((f) => f.endsWith('.md')).map((f) => path.join(decDir, f))
: [];
copyRepoDocsInto('decisions', decisions, 'Decisions', 40);
const spDir = path.join(REPO_DIR, 'docs', 'superpowers');
const specs = fs.existsSync(path.join(spDir, 'specs'))
? fs.readdirSync(path.join(spDir, 'specs')).filter((f) => f.endsWith('.md')).map((f) => path.join(spDir, 'specs', f)) : [];
const plans = fs.existsSync(path.join(spDir, 'plans'))
? fs.readdirSync(path.join(spDir, 'plans')).filter((f) => f.endsWith('.md')).map((f) => path.join(spDir, 'plans', f)) : [];
const rebuild = path.join(REPO_DIR, 'docs', 'rebuild-spec.md');
copyRepoDocsInto('specs-and-plans', [...specs, ...plans, rebuild], 'Specs & Plans', 50);
}
Call generateDeveloperDocs(); in main() right after the existing design-system generation.
- Step 4: Run the build + the check → PASS
Run: cd app/test-gallery && node scripts/build_test_gallery.mjs > /tmp/t.txt 2>&1; echo "GEN=$?"; node scripts/check_developer_docs.mjs; echo "CHECK=$?"
Expected: GEN=0, CHECK=0 (docs/developer/decisions has ≥4 .md, docs/developer/specs-and-plans populated).
- Step 5: Commit
cd C:/Users/ryted/Development/repo/rytedesigns/rewhaven
git add app/test-gallery/scripts/build_test_gallery.mjs app/test-gallery/scripts/check_developer_docs.mjs app/test-gallery/authored/developer/.gitkeep
git commit -m "feat(portal): generateDeveloperDocs() — ingest repo docs + copy authored pages into docs/developer"
Task 2: Author the curated developer pages
Files:
- Create:
app/test-gallery/authored/developer/getting-started.md - Create:
app/test-gallery/authored/developer/architecture.md - Create:
app/test-gallery/authored/developer/guides/add-a-repository.md,guides/add-a-bloc-and-page.md,guides/write-a-flow-test.md,guides/codegen-and-graphify.md - Create:
app/test-gallery/authored/developer/guides/_category_.json
Interfaces:
-
Consumes:
rewhaven/CLAUDE.md(the source to distill — read it first). Task 1's copy step puts these underdocs/developer/. -
Produces: the authored Developer pages (intent-ordered via
sidebar_positionfront-matter). -
Step 1: Read the source —
cat C:/Users/ryted/Development/repo/rytedesigns/rewhaven/CLAUDE.md. The pages distill its sections; do NOT copy verbatim (drop the Claude-agent framing; keep the architecture/convention substance). -
Step 2: Write
getting-started.md— front-matter--- \n sidebar_position: 1 \n title: Getting Started \n ---. Content: clone, FVM (fvm flutter pub get), run (fvm flutter run -t lib/main_dev.dart), test (cd app && fvm flutter test), codegen (fvm dart run build_runner build), graphify (graphify query "<q>"). Pull exact commands fromCLAUDE.md+ the repo scripts. -
Step 3: Write
architecture.md—sidebar_position: 2, title "Architecture & Concepts". Content: the inside/outside split, the one data path (Bloc → Repository → Client facade → Service → Adapter), the SDK construction rules (createClient({config})), the load-bearing invariants (append-only ledger, zero-floor, expectation-pays-zero, RLS). Include a Mermaid diagram of the data path:
```mermaid
flowchart TD
W[Widget] --> B[Bloc] --> R[Repository] --> C[Client facade] --> S[Service] --> A[Adapter] --> St[(local / cloud)]
(Docusaurus v3 renders Mermaid if `@docusaurus/theme-mermaid` is enabled — Step 5 wires that.)
- [ ] **Step 4: Write the four `guides/*.md`** — each `sidebar_position` 1..4, a focused task recipe distilled from `CLAUDE.md`: add a repository (the 3-step Repositories_All + runner wiring + mock), add a bloc/page (the `@JsonSerializable` state contract + `AutoRouteWrapper`), write a flow test (the gadfly harness, `MocksContainer`, light/dark/focus + `expectedEvents`), codegen + graphify (`--build-filter`, restore clobbered siblings, `graphify update .`). Add `guides/_category_.json` = `{"label":"Guides","position":3}`.
- [ ] **Step 5: Enable Mermaid (if used)** — in `docusaurus.config.js` add `markdown: { mermaid: true }` and `themes: ['@docusaurus/theme-mermaid']`; `npm install @docusaurus/theme-mermaid@<same major as docusaurus>`. (Skip if you avoid the diagram — but the diagram is in scope.)
- [ ] **Step 6: Rebuild + verify the pages land**
Run: `cd app/test-gallery && node scripts/build_test_gallery.mjs > /tmp/t.txt 2>&1; echo "GEN=$?"; ls docs/developer docs/developer/guides 2>&1 | head`
Expected: `GEN=0`; `docs/developer/` contains `getting-started.md`, `architecture.md`, `guides/`, `decisions/`, `specs-and-plans/`.
- [ ] **Step 7: Commit**
```bash
git add app/test-gallery/authored app/test-gallery/docusaurus.config.js app/test-gallery/package.json app/test-gallery/package-lock.json
git commit -m "feat(portal): authored developer pages (getting-started, architecture + diagram, guides)"
Task 3: IA wiring — landing, 3-item navbar, 3 sidebars
Files:
- Modify:
app/test-gallery/sidebars.js - Modify:
app/test-gallery/docusaurus.config.js - Modify:
app/test-gallery/scripts/build_test_gallery.mjs(the generateddocs/index.mdlanding)
Interfaces:
-
Consumes: the
docs/developer/,docs/design-system/,docs/gallery/trees (Tasks 1–2 + existing). -
Produces:
developerSidebar,designSystemSidebar,gallerySidebar; a 3-item navbar; a 3-card landing. -
Step 1: Three sidebars —
sidebars.js:
const sidebars = {
developerSidebar: [{type: 'autogenerated', dirName: 'developer'}],
designSystemSidebar: [{type: 'autogenerated', dirName: 'design-system'}],
gallerySidebar: [{type: 'autogenerated', dirName: 'gallery'}],
};
export default sidebars;
- Step 2: Three navbar items — in
docusaurus.config.jsnavbaritems, replace the single Gallery item with:
{type: 'docSidebar', sidebarId: 'developerSidebar', position: 'left', label: 'Developer'},
{type: 'docSidebar', sidebarId: 'designSystemSidebar', position: 'left', label: 'Design System'},
{type: 'docSidebar', sidebarId: 'gallerySidebar', position: 'left', label: 'Test Gallery'},
-
Step 3: Three-card landing — in
build_test_gallery.mjswhere it writesdocs/index.md(~line 230), replace the body with aslug: /landing: a short intro ("Rewhaven developer portal") + three links — Developer, Design System + "Open the live Widgetbook" →/design_system/, Test Gallery. Keepslug: /so it's the home. -
Step 4: Build the production site → green
Run: cd app/test-gallery && node scripts/build_test_gallery.mjs > /tmp/g.txt 2>&1; echo "GEN=$?"; npm run build > /tmp/b.txt 2>&1; echo "BUILD=$?"; tail -3 /tmp/b.txt
Expected: GEN=0, BUILD=0, [SUCCESS] Generated static files in "build". (Broken-link WARNINGS are acceptable — onBrokenLinks: 'warn'; a hard ERROR is not.)
- Step 5: Commit
git add app/test-gallery/sidebars.js app/test-gallery/docusaurus.config.js app/test-gallery/scripts/build_test_gallery.mjs
git commit -m "feat(portal): 3-section navbar + 3 sidebars + a 3-card landing"
Task 4: Deploy + verify live
Files: none (uses scripts/deploy-developer-gallery.sh).
-
Step 1: Redeploy —
cd C:/Users/ryted/Development/repo/rytedesigns/rewhaven && bash scripts/deploy-developer-gallery.sh > /tmp/d.txt 2>&1; echo "DEPLOY=$?"; tail -5 /tmp/d.txtExpected:DEPLOY=0, "OK — test gallery live". -
Step 2: Verify the live portal + that nothing regressed
Run:
for u in \
"https://developer.eldr-labs.duckdns.org/" \
"https://developer.eldr-labs.duckdns.org/developer/getting-started" \
"https://developer.eldr-labs.duckdns.org/design-system/" \
"https://developer.eldr-labs.duckdns.org/gallery/" \
"https://developer.eldr-labs.duckdns.org/design_system/" \
"https://home.eldr-labs.duckdns.org/app/" ; do
echo "$u -> $(curl -s -o /dev/null -w '%{http_code}' --max-time 12 "$u")"
done
Expected: all 200 (the three sections + the embedded live Widgetbook at /design_system/ + the untouched /app/).
- Step 3: Final commit (if any deploy-side files changed) — usually none; if the deploy script or config needed a tweak, commit it:
git add -A ':!graphify-out'
git commit -m "chore(portal): deploy the developer portal" || echo "(nothing to commit)"
Self-review
- Spec coverage: the 3 sections + landing (T3), Developer intent-order (Getting Started/Architecture/Guides authored T2; Decisions/Specs&Plans generated T1), Design System as component reference (existing + landing link, T3), Test Gallery (existing), the authored-vs-generated seam (authored in
authored/, copied in — T1, the resolution of the wholesale-wipe finding), build + deploy + verify (T4). ✓ - Placeholder scan: none —
generateDeveloperDocs()+copyRepoDocsIntoare full code; the authored pages have concrete content briefs + exact commands; the sidebars/navbar/landing are exact. The four guide bodies are "distilled from CLAUDE.md" with the specific sections named (not a vague TODO). - Type/name consistency:
AUTHORED_DEV,generateDeveloperDocs,copyRepoDocsInto,docs/developer/{decisions,specs-and-plans,guides},developerSidebar/designSystemSidebar/gallerySidebarused consistently across tasks. - Wipe finding resolved: the wholesale
docs/wipe stays; authored pages live inapp/test-gallery/authored/(never wiped) and are copied in each build → no clobber, no stale files.