Skip to main content

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 from app/test-gallery/.
  • Capture command exit codes honestly: run <cmd> > /tmp/t.txt 2>&1; echo "EXIT=$?" then inspect; never decide pass/fail from a piped tail. The build passes only when EXIT=0 and the output shows [SUCCESS] Generated static files in "build".
  • build_test_gallery.mjs wipes docs/ wholesale (fs.rmSync(DOCS,{recursive,force}) at ~line 226) — so authored pages MUST live OUTSIDE docs/ (in app/test-gallery/authored/) and be COPIED into docs/ by the build. Never put hand-written content directly under docs/.
  • 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 (the generateFlowGallery/generateDesignSystem parts) 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 via scripts/deploy-developer-gallery.sh.

Today's shapes (verified)

  • app/test-gallery/scripts/build_test_gallery.mjs (261 lines): consts DOCS = <site>/docs, STATIC = <site>/static, REPO_DIR = app/../ (= the rewhaven root). Helpers writeFile(p,text) (mkdirp + write) and a _category_.json writer (~line 69). It fs.rmSync(DOCS,{recursive,force}) + mkdirSync(DOCS) (~226), writes docs/index.md (landing, slug /, ~230), then generateFlowGallery()docs/gallery/** and generateDesignSystem()docs/design-system/**.
  • app/test-gallery/sidebars.js: a single gallerySidebar: [{type:'autogenerated', dirName:'.'}].
  • app/test-gallery/docusaurus.config.js: navbar has ONE item — a docSidebargallerySidebar labelled "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_.json helpers.

  • Produces: a generateDeveloperDocs() function, called in main() AFTER the docs/ wipe and the flow/design-system generators. It:

    1. 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').
    2. Generates docs/developer/decisions/<file>.md from each REPO_DIR/docs/decisions/*.md and docs/developer/specs-and-plans/<file>.md from each REPO_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.
    3. Writes _category_.json for docs/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-matter sidebar_position in Task 2.)
  • 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 under docs/developer/.

  • Produces: the authored Developer pages (intent-ordered via sidebar_position front-matter).

  • Step 1: Read the sourcecat 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 from CLAUDE.md + the repo scripts.

  • Step 3: Write architecture.mdsidebar_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 generated docs/index.md landing)

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 sidebarssidebars.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.js navbar items, 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.mjs where it writes docs/index.md (~line 230), replace the body with a slug: / landing: a short intro ("Rewhaven developer portal") + three links — Developer, Design System + "Open the live Widgetbook" → /design_system/, Test Gallery. Keep slug: / 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: Redeploycd C:/Users/ryted/Development/repo/rytedesigns/rewhaven && bash scripts/deploy-developer-gallery.sh > /tmp/d.txt 2>&1; echo "DEPLOY=$?"; tail -5 /tmp/d.txt Expected: 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() + copyRepoDocsInto are 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/gallerySidebar used consistently across tasks.
  • Wipe finding resolved: the wholesale docs/ wipe stays; authored pages live in app/test-gallery/authored/ (never wiped) and are copied in each build → no clobber, no stale files.