$npx -y skills add strongeron/storybook-workbench --skill sb-flowsMap the whole app — routes, navigation edges, and persistent nav chrome, not just a screen list. Use for 'map the app', 'show the flow', 'app map', 'audit navigation', or 'how do screens connect'.
| 1 | # sb-flows — the connection half of an audit |
| 2 | |
| 3 | A route list can't prove coverage; a flow graph needs **edges** and **persistent nav sources** |
| 4 | (sidebar/header/footer link from every screen and are invisible to a page-body sweep — the #1 |
| 5 | audit miss). Load `references/flow-capture.md` before mapping. |
| 6 | |
| 7 | ## Run it |
| 8 | |
| 9 | ```bash |
| 10 | SKILL=${CLAUDE_PLUGIN_ROOT:-${CLAUDE_PLUGIN_ROOT}} |
| 11 | CORE=${CLAUDE_PLUGIN_ROOT:-${CLAUDE_PLUGIN_ROOT}} |
| 12 | "$SKILL/scripts/extract-flows.sh" # 1. generic first pass → .storybook/flows.json (routes + edges + navSources) |
| 13 | "$CORE/scripts/scaffold-wrapper.sh" --flow # 2. AppFlowGraph + JourneyGraph (+ icons.tsx) |
| 14 | |
| 15 | # 3. The app-graph the wrappers actually render (edges with ACTION labels, deep store/service |
| 16 | # tracing, role lanes, component lists). Copy the reference template, ADAPT its 3 marked blocks |
| 17 | # to this repo's router/nav/role-gate, then run it. It writes app-graph.json + component-pages.json. |
| 18 | cp "$SKILL/templates/extract-app-graph.mjs" .storybook/scripts/extract-app-graph.mjs # then edit ADAPT-1/2/3 |
| 19 | node .storybook/scripts/extract-app-graph.mjs |
| 20 | ``` |
| 21 | |
| 22 | Act on the **sweep reminder** the extractor prints when nav chrome exists. And if it prints |
| 23 | **`⚠ LIKELY UNDER-EXTRACTION`**, treat `flows.json` as a draft — the app navigates via an idiom |
| 24 | the script doesn't parse yet (the nanostores-class miss). Don't trust `edgeCount`: read the |
| 25 | unmatched call sites it lists, recover the real edges (a repo-local source-aware resolver beats more |
| 26 | regex), and fold general idioms back into `extract-flows.sh`. Full loop: `references/flow-capture.md` |
| 27 | → **"When extraction under-reports."** |
| 28 | |
| 29 | **Deep edges (don't stop at the shallow pass).** A nav fired from a store/service action (or a |
| 30 | deeply-nested feature component) has no screen of its own — its real origin is *whoever calls it*. |
| 31 | The template's `moduleOrigins` traces that **re-export-aware** (it follows barrels: a service |
| 32 | re-exported through a store is found via the store's import token), up the import graph to the routed |
| 33 | pages. This recovered real `switchToHotel → scheduler` edges that the shallow pass dropped. Verify in |
| 34 | `references/flow-capture.md` → **"Deep-edge tracing"**. |
| 35 | |
| 36 | **VALIDATE — never hallucinate an empty edge.** The contract: every edge has file:line provenance; |
| 37 | anything unattributable is recorded in `unresolvedEdges`/`dynamicCallSites`, not invented. After |
| 38 | running, check `node -e` the JSON: `unresolvedEdges` should be small and each reason honest (a 404 |
| 39 | with no route, a genuinely dynamic dispatch), and spot-check a couple of `module-trace` edges against |
| 40 | the source. See `references/flow-capture.md` → **"Validate the graph."** |
| 41 | |
| 42 | ## Model it |
| 43 | |
| 44 | - **`<AppFlowGraph graph={...}>`** — whole-app route map: role swimlanes, typed/colored edges, |
| 45 | coverage colouring, click-to-story. Feed an `AppGraph` derived from `flows.json`. |
| 46 | - **`<JourneyGraph journey={...}>`** — one flow's journey map (the `Flows/*` Docs index); each step |
| 47 | links to its per-state story by `storyId`. Ships a collapsed "how to add a flow" authoring hint by |
| 48 | default (suppress with `hideAuthoringHint`); the "what is this?" provenance banner is **off by |
| 49 | default** — reachable on demand via `setProvenance()` / `__SB_WB_PROVENANCE__`, and `hideIntro` |
| 50 | hard-suppresses it. The |
| 51 | `journey` is a CURATED narrative — one persona's path across the captured graph — not a field in |
| 52 | `flows.json` (which holds the route nodes + edges `AppFlowGraph` draws). |
| 53 | - Both take an injectable `icons` prop (pass lucide/project icons) — **no emoji** (CONTEXT.md |
| 54 | §wrapper-view-design). |
| 55 | - **Theme via the app's tokens, not a fixed palette.** Chrome (panels, text, borders, lane stripes) |
| 56 | reads `var(--color-foreground|surface|border-subtle|muted-foreground, <fallback>)`, so one `.dark` |
| 57 | flip re-skins the whole map light↔dark and the fallback keeps it rendering in a token-less app. |
| 58 | Only the *data* encodings stay literal — coverage dots and the per-`EdgeKind` hues are categorical, |
| 59 | not chrome. Don't replace the `var()` chrome with hardcoded colors. |
| 60 | |
| 61 | ## The rules that matter |
| 62 | |
| 63 | - Sweep **every** navigation source — `flows.json.navSources[]`, not just page bodies. |
| 64 | - Capture **edges**, not just routes — `flows.json.edges[]`. |
| 65 | - Explore **roles** — the same app is a different graph per persona. `flows.json` gives each route an |
| 66 | `access` (public/user/admin, a path heuristic) + `roleSignals[]` (the real g |