|
| 1 | +# VisualBuilderPlan-v9 — UX Polish & Visual System Closure |
| 2 | + |
| 3 | +**Status**: planned 2026-05-24 (epic `cppmega-mlx-v9` — to be created) |
| 4 | + |
| 5 | +**Driver**: post-V8 operator-feedback cycle. V8 closed the |
| 6 | +gallery→train round trip (R01-R12). V9 closes the *visual surface* |
| 7 | +the operator interacts with every minute: canvas chrome, sidebar |
| 8 | +ergonomics, theme consistency, and discoverability. |
| 9 | + |
| 10 | +> "Цвета, фонты, ux — куча компонентов и непонятно зачем. mtp это не |
| 11 | +> компонент? зачем оно тут банером? memory bar длиннюща, как ты это |
| 12 | +> покажешь на h100:8? ckpt path виден когда idle. parallel/insert/ |
| 13 | +> transplant — ugly full-width strips. draft 1 + new draft — для |
| 14 | +> чего? TopBar 18 элементов, цвета непоследовательны." |
| 15 | +
|
| 16 | +## bd ticket ID mapping |
| 17 | + |
| 18 | +| U# | scope | priority | depends on | |
| 19 | +|------|------------------------------------------------------------|----------|------------| |
| 20 | +| U01 | Draft tabs strip (browser-tab style) in TopBar left group | P2 | - | |
| 21 | +| U02 | Floating canvas toolbar (vertical 32px icon-strip) | P2 | - | |
| 22 | +| U03 | Mass hex-literal migration — 592 → ≤ 150 across `vbgui/src/components` | P3 | UX#8 ratchet | |
| 23 | +| U04 | Theme tokens v2 — layered surfaces + elevation scale | P3 | U03 | |
| 24 | +| U05 | Sidebar tab strip — collapse 10 tabs into grouped accordion | P2 | - | |
| 25 | +| U06 | Empty-state contracts for Loss/Optim/Memory/Gotchas tabs | P3 | - | |
| 26 | +| U07 | BrickNode unified styling — Palette item == canvas chip | P3 | U04 | |
| 27 | +| U08 | TopBar responsive collapse — groups stack < 1024px | P3 | UX#7 | |
| 28 | +| U09 | Help icon catalogue audit — every topic has content or 404 | P2 | - | |
| 29 | +| U10 | Keyboard shortcuts overlay (? key) | P3 | - | |
| 30 | + |
| 31 | +## 1. Already done (do NOT re-open) |
| 32 | + |
| 33 | +The operator-feedback redesign sprint closed these between |
| 34 | +2026-05-23 and 2026-05-24 (commits `23bb694`, `7908932`, `6fe5518`, |
| 35 | +`7bc0b43`, `5a4e314`, `7e1a031`): |
| 36 | + |
| 37 | +| # | Title | Commit | |
| 38 | +|---|---|---| |
| 39 | +| #0 | HelpModal Portal — escape transformed ancestor (`position:fixed` containing-block trap) | `S3615` | |
| 40 | +| #1 | FeatureInjectionBar dedupe → chip with `×N` count + per-chip remove | `6fe5518` | |
| 41 | +| #2 | DimEnvEditor relocated to right Sidebar `Dimensions` tab | `7bc0b43` | |
| 42 | +| #3 | TrainOptionsPanel + RunHistoryPicker → new `TrainOps` Sidebar tab | `7bc0b43` | |
| 43 | +| #4 | TrainLiveControls conditional `{trainInFlight && ...}` | `7bc0b43` | |
| 44 | +| #5 (interim) | Parallel / Insert / Transplant bars removed from canvas, hosted in TrainOps tab | `7bc0b43` | |
| 45 | +| #6 | MemoryBar compact (100×18 pill) + topology-aware N-rank mini-bars | `23bb694` | |
| 46 | +| #7 | TopBar 3 flex groups (left/center/right) + single Precision dropdown | `5a4e314` | |
| 47 | +| #8 (ratchet) | `HexColorRatchet.test.ts` + per-file baseline + DimensionsTab migration | `7e1a031` | |
| 48 | + |
| 49 | +## 2. Why v9 exists |
| 50 | + |
| 51 | +V8 delivered the *journey*. V9 delivers the *finish*: |
| 52 | +- discoverability (Draft tabs, keyboard shortcuts, help catalogue) |
| 53 | +- visual consistency (theme tokens, BrickNode unity, mass hex |
| 54 | + migration ratcheted down from 592 to ≤ 150) |
| 55 | +- canvas chrome (floating toolbar instead of buried sidebar splicing |
| 56 | + section; responsive TopBar) |
| 57 | +- empty-state polish (so first-time operators see prompts instead of |
| 58 | + white space) |
| 59 | + |
| 60 | +## 3. Goal |
| 61 | + |
| 62 | +Operator opens the GUI cold. Without reading docs they can: |
| 63 | +1. Recognise every clickable region from visual weight alone. |
| 64 | +2. Find every action through ≤ 2 clicks (TopBar + Sidebar grouped). |
| 65 | +3. Use `?` overlay to discover all keyboard shortcuts. |
| 66 | +4. Switch between concurrent specs via Draft tabs. |
| 67 | +5. Splice graphs (parallel/insert/transplant) from a fixed-position |
| 68 | + floating toolbar instead of scrolling a tab. |
| 69 | + |
| 70 | +## 4. Stages — 10 U-epics × 1-4 sub-tasks ≈ 26 sub-tickets |
| 71 | + |
| 72 | +### U01 — Draft tabs strip in TopBar (P2, 3 subs) |
| 73 | + |
| 74 | +**Why**: operator currently sees `[Draft 1] [+ New Draft]` with no |
| 75 | +visual taxonomy. Should be browser-tab-style strip wedged into |
| 76 | +TopBar left group, right of `project-name`. |
| 77 | + |
| 78 | +**Subs**: |
| 79 | +- U01.1 `vbgui/src/components/DraftTabsStrip.tsx`: render N tabs |
| 80 | + `[Untitled] [Draft 1] [Draft 2 ×] [+]`. Active tab highlighted. |
| 81 | + Close button (×) only on inactive tabs. |
| 82 | +- U01.2 `vbgui/src/state/drafts.ts`: `useDrafts()` hook persists |
| 83 | + drafts to `localStorage` under `vbgui_drafts_v1` (array of full |
| 84 | + spec snapshots + name + timestamp). |
| 85 | +- U01.3 Vitest: switching draft swaps `nodes` + `edges` + `spec` |
| 86 | + on canvas; closing draft does NOT lose data of OTHER drafts. |
| 87 | + |
| 88 | +### U02 — Floating canvas toolbar (P2, 4 subs) |
| 89 | + |
| 90 | +**Why**: Parallel / Insert / Transplant currently live in |
| 91 | +`TrainOpsTab` (a hidden tab). They should be a fixed-position |
| 92 | +vertical strip 32px wide, left edge of canvas, between Palette |
| 93 | +and FlowCanvas. |
| 94 | + |
| 95 | +**Subs**: |
| 96 | +- U02.1 `vbgui/src/components/CanvasToolbar.tsx`: vertical icon |
| 97 | + strip with three icons. Each icon opens a popover (positioned to |
| 98 | + the right of the icon) that contains the existing |
| 99 | + `ParallelComposeBar` / `InsertIntoEdgeBar` / `TransplantBar`. |
| 100 | +- U02.2 Remove the splicing section from `TrainOpsTab` (revert |
| 101 | + linter's interim placement). Update test |
| 102 | + `tests/TrainOpsTab.test.tsx` to drop the splicing assertions. |
| 103 | +- U02.3 Popovers close on click-outside and on Esc; icons show |
| 104 | + active-state ring while popover is open. |
| 105 | +- U02.4 Vitest: 3 icons render; opening one closes any other |
| 106 | + already-open popover; ParallelComposeBar still fires `onCompose`. |
| 107 | + |
| 108 | +### U03 — Mass hex-literal migration (P3, 4 subs) |
| 109 | + |
| 110 | +**Why**: `tests/HexColorRatchet.test.ts` locks in baseline 592. The |
| 111 | +target is ≤ 150 by end of V9 (75% reduction). High-frequency |
| 112 | +literals: `#6b7280` (55), `#374151` (36), `#94a3b8` (30), |
| 113 | +`#e5e7eb` (29). |
| 114 | + |
| 115 | +**Subs**: |
| 116 | +- U03.1 Map every hex literal to the closest semantic token in |
| 117 | + `src/theme.ts` — produce `vbgui/MIGRATION_TABLE.md` with rows |
| 118 | + `hex → T.token category notes`. |
| 119 | +- U03.2 Migrate `Sidebar.tsx` + every file under `src/components/sidebar/` |
| 120 | + (target: sidebar drops to ≤ 10 hex total). |
| 121 | +- U03.3 Migrate `BrickContextPanel.tsx` (currently 32 hex — |
| 122 | + highest single offender after TrainLiveControls). |
| 123 | +- U03.4 Migrate `TrainLiveControls.tsx` (46 hex — relocate its |
| 124 | + ad-hoc colour table into `T.live*` extension if any unique values |
| 125 | + remain after migration). |
| 126 | + |
| 127 | +### U04 — Theme tokens v2 — layered surfaces + elevation (P3, 2 subs) |
| 128 | + |
| 129 | +**Why**: existing tokens (`T.surface`, `T.surface2`, `T.surface3`) |
| 130 | +are flat — there's no notion of "card on card" or "modal vs |
| 131 | +popover" depth. Adding `T.surfaceLayered{1,2,3}` and `T.elevation*` |
| 132 | +gives the migration target enough semantic vocabulary. |
| 133 | + |
| 134 | +**Subs**: |
| 135 | +- U04.1 Extend `vbgui/src/theme.css` + `theme.ts` with: |
| 136 | + `surfaceLayered1/2/3` (each a darker variant of `surface` with a |
| 137 | + hairline border); `elevationLow/Mid/High` (box-shadow tokens); |
| 138 | + `liveAccent`, `liveAccentSoft` (semantic for live-train UI). |
| 139 | +- U04.2 Pin the additions in `vbgui/STYLE.md` (see Spec §2). |
| 140 | + |
| 141 | +### U05 — Sidebar tab strip overflow (P2, 3 subs) |
| 142 | + |
| 143 | +**Why**: Sidebar now has 10 tabs (Loss, Optim, Rewriters, Side |
| 144 | +Ch., Sharding, Gotchas, Dimensions, Train Ops, Ablations, Memory). |
| 145 | +At width 320px with `flex: 1 0 33%` they wrap to 4 rows of 3 — eats |
| 146 | +sidebar headroom. |
| 147 | + |
| 148 | +**Subs**: |
| 149 | +- U05.1 Group tabs into 3 sections (Spec / Diagnostics / Train) — |
| 150 | + visible header per section, tabs underneath as smaller chips. |
| 151 | +- U05.2 OR: dropdown picker for less-used tabs (Ablations, |
| 152 | + Memory). A/B both, pick one with screenshots. |
| 153 | +- U05.3 Vitest: every existing `sidebar-tab-{name}` testid still |
| 154 | + resolves (back-compat). |
| 155 | + |
| 156 | +### U06 — Empty-state contracts (P3, 2 subs) |
| 157 | + |
| 158 | +**Why**: opening LossTab / MemoryMatrixTab / GotchasTab cold gives |
| 159 | +blank panels. Operator can't tell whether the tab is broken or |
| 160 | +empty. |
| 161 | + |
| 162 | +**Subs**: |
| 163 | +- U06.1 Each of {LossTab, MemoryMatrixTab, GotchasTab, |
| 164 | + AblationsTab} renders an empty-state block when its data array |
| 165 | + is `[]`. Block = icon + headline + 1-sentence prompt + CTA. |
| 166 | + Example for GotchasTab: "✓ No issues yet — run Smoke to populate". |
| 167 | +- U06.2 Vitest: each tab in empty state has |
| 168 | + `data-testid="<tab>-empty-state"` and renders the CTA text. |
| 169 | + |
| 170 | +### U07 — BrickNode unified styling (P3, 2 subs) |
| 171 | + |
| 172 | +**Why**: Palette items (left dock) and canvas nodes use different |
| 173 | +chrome — Palette is white-bg + grey border, canvas is dark + accent |
| 174 | +glow. Single brick should *look the same* in both places (different |
| 175 | +backgrounds, identical typography + accent treatment). |
| 176 | + |
| 177 | +**Subs**: |
| 178 | +- U07.1 Extract shared `BrickChip` primitive used by both |
| 179 | + `Palette` items and `BrickNode` canvas nodes. |
| 180 | +- U07.2 Vitest: Palette item and BrickNode for the same kind have |
| 181 | + same `data-brick-kind` + same `--vb-node-accent` CSS variable. |
| 182 | + |
| 183 | +### U08 — TopBar responsive collapse (P3, 2 subs) |
| 184 | + |
| 185 | +**Why**: UX#7 split into 3 flex groups. Below 1024px width they |
| 186 | +overflow horizontally — need to stack groups onto 2 rows when |
| 187 | +viewport is narrow. |
| 188 | + |
| 189 | +**Subs**: |
| 190 | +- U08.1 `@container` or `min-width` media query — when |
| 191 | + `< 1024px` stack the center group below left+right. |
| 192 | +- U08.2 Vitest with jsdom-resize: at 800px width |
| 193 | + `top-bar-group-center.style.flexDirection === "column"` OR a |
| 194 | + wrap container. |
| 195 | + |
| 196 | +### U09 — Help icon catalogue audit (P2, 3 subs) |
| 197 | + |
| 198 | +**Why**: `HelpIcon topic="..."` references ~80 topics. Many may |
| 199 | +have drifted (renames, broken slugs) — operator clicks `?` and |
| 200 | +sees "missing" modal. Need a programmatic audit + autofix path. |
| 201 | + |
| 202 | +**Subs**: |
| 203 | +- U09.1 vitest scans every `<HelpIcon topic="X">` use, every |
| 204 | + `Tooltip name="X">`, and every `ExplainModal name="X">` in |
| 205 | + `vbgui/src/components/**/*.tsx`. For each, asserts the topic |
| 206 | + exists in `HELP_TOPICS` map OR a `catalog.explain` RPC response. |
| 207 | + Failing topics listed. |
| 208 | +- U09.2 Add missing topics to `HelpIcon.tsx` HELP_TOPICS map (one |
| 209 | + paragraph each) OR remove the reference if the surface is dead. |
| 210 | +- U09.3 Add `topic` autocomplete TS type so future references type-check. |
| 211 | + |
| 212 | +### U10 — Keyboard shortcuts overlay (P3, 3 subs) |
| 213 | + |
| 214 | +**Why**: power users want `?` to bring up a list of all shortcuts. |
| 215 | +Currently only Cmd/Ctrl+Z (undo) + Shift+Cmd/Ctrl+Z (redo) are |
| 216 | +hinted in button titles. |
| 217 | + |
| 218 | +**Subs**: |
| 219 | +- U10.1 `vbgui/src/components/KeyboardShortcutsOverlay.tsx`: |
| 220 | + modal triggered by `?` (and dismissed by `?` or Esc) listing |
| 221 | + every registered shortcut grouped by section (Canvas / Sidebar |
| 222 | + / Train / Help). |
| 223 | +- U10.2 `vbgui/src/state/shortcuts.ts`: central registry — every |
| 224 | + bound shortcut registers `{key, label, section, handler}` so the |
| 225 | + overlay is generated, not hand-edited. |
| 226 | +- U10.3 Vitest: pressing `?` opens overlay; overlay lists ≥ 1 |
| 227 | + shortcut per section. |
| 228 | + |
| 229 | +## 5. Workflow per epic (same as v5/v6/v7/v8) |
| 230 | + |
| 231 | +1. Read `VisualBuilderSpec-v9.md` for the relevant U-section. |
| 232 | +2. Create files per spec, with the listed data-testids. |
| 233 | +3. Write vitest tests first (TDD); they should fail. |
| 234 | +4. Wire the component; tests pass. |
| 235 | +5. Run full `npx vitest run` from `vbgui/` to confirm zero regression. |
| 236 | +6. Commit with `UX-U0#: <title>` message; close the bd ticket. |
| 237 | +7. Push to main. |
| 238 | + |
| 239 | +## 6. Acceptance counters (mirrors §4) |
| 240 | + |
| 241 | +- 10 U-epics → 26 sub-tickets created in bd as |
| 242 | + `cppmega-mlx-v9/U01..U10` (one parent + 10 child epics). |
| 243 | +- Each sub-ticket has acceptance criteria copied from §4. |
| 244 | +- New vitest count: ≥ 25 (across all U-stages). |
| 245 | +- `HexColorRatchet.test.ts` baseline drops from 592 to ≤ 150 by end |
| 246 | + of U03 (≥ 75% reduction). |
| 247 | + |
| 248 | +## 7. Out of scope (V10+) |
| 249 | + |
| 250 | +- True theming (user-switchable light/dark/contrast). |
| 251 | +- Drag-to-reorder canvas nodes (current ELK auto-align only). |
| 252 | +- Multi-user collaboration (CRDT spec sync). |
| 253 | +- Mobile/tablet viewports (focused on ≥ 1024px desktop). |
| 254 | +- Internationalisation (English-only UI text). |
| 255 | + |
| 256 | +## 8. Done definition |
| 257 | + |
| 258 | +V9 is done when: |
| 259 | +- All 10 U-epics closed in bd (sub-tickets either closed or |
| 260 | + deferred to V10 with explicit reason). |
| 261 | +- `npx vitest run` from `vbgui/`: 0 failures (counting only |
| 262 | + V9-relevant test files; pre-existing HelpModal/Palette/Backward |
| 263 | + failures are tracked separately). |
| 264 | +- `HexColorRatchet` baseline ≤ 150. |
| 265 | +- `VisualBuilderPlan-v9.md` + `VisualBuilderSpec-v9.md` + `vbgui/STYLE.md` |
| 266 | + shipped on `main`. |
| 267 | +- A 5-min screen-recording captures Draft tabs + Canvas toolbar + |
| 268 | + empty states + `?` overlay (UAT footage). |
0 commit comments