EditorThreadBar.tsx renders a static 10-color picker with random lengths disconnected from the Workspace's actual threadStops #94

Closed
opened 2026-09-14 17:38:26 +00:00 by Mark · 1 comment
Owner

Where: src/components/editor/EditorThreadBar.tsx (79 lines).

Bug: The thread bar along the bottom of the Editor presents itself as a "thread stops" picker but is fully static and disconnected from the actual design.

const names = ['Red', 'Teal', 'Navy', 'Gold', 'Purple', 'Coral', 'Sky', 'Sage', 'Magenta', 'Olive'];
return names.map((name, i) => ({
  color:,
  index: i,
  name,
  length: Math.round(15 + Math.random() * 85),  // ← random per render-init
}));

Issues:

  1. Hard-coded 10 colors/names that don't reflect THREAD_COLORS length (src/components/editor/constants.ts). If THREAD_COLORS has fewer than 10 entries, accessing THREAD_COLORS[i] for i >= length is undefined and renders nothing for that stop. If it has more, only 10 are shown. The bar pretends to be a picker over the full catalog but actually shows a 10-color subset.

  2. Length is randomized (Math.random() * 85). Wrapped in useState(getThreadStops) so it's stable across re-renders, but the value is meaningless — there is no per-stop length in the underlying state.threadStops. The field is computed but never displayed.

  3. No connection to state.threadStops. The Workspace produces real thread stops via computeThreadStops(stitchObjects) in useWorkspace.ts:568, passed through the tc_workspace_snapshot sessionStorage bridge into the Editor. But the Editor thread bar ignores the snapshot's threadStops and instead generates fake data on mount.

  4. ThreadStop interface (L5-10) is locally redefined with a different shape than the Workspace's ThreadStop (in src/components/workspace/types.ts, fields: id, colorId, paletteIndex, hex, length). The local interface omits colorId, paletteIndex, hex, id and only has color, index, name, length. So even if the bar were ever wired to read real stops, the types would need to be unified first.

  5. useState with no setter ([stops]) — the function returns [stops] and never calls setStops. Minor stylistic issue but signals "this is read-only seed data, not interactive state".

Effect on user: when the user navigates from Workspace (post-digitize, with 8 real thread stops derived from the design's colors) → /editor, the thread bar at the bottom shows 10 generic named colors with random lengths, not the design's actual thread palette. The user can't see which threads their design uses, can't reorder real stops, can't pick "the actual blue I see in my design". The selectedThreadIndex (controlled by clicking these) is used as THREAD_COLORS[currentThreadIndex] for currentThreadColor passed to the canvas (Editor.tsx:532) and as the color for newly drawn paths (Editor.tsx:381, threadColor: THREAD_COLORS[currentThreadIndex]). So clicking changes the next-path-draw color, but the user has no idea which color is which in the design.

Fix consistent with existing patterns: Two paths, pick one.

  1. Wire to the snapshot. Read tc_workspace_snapshot from sessionStorage (already done by Editor.loadInitialPaths at Editor.tsx:35) and parse out threadStops. Pass them down as a prop:

    • In Editor.tsx, on mount, parse the snapshot once: const [threadStops] = useState(() => { const raw = sessionStorage.getItem('tc_workspace_snapshot'); return raw ? (JSON.parse(raw).threadStops ?? []) : []; });
    • Pass threadStops={threadStops} to <EditorThreadBar /> at Editor.tsx:561.
    • Replace EditorThreadBarProps (L12-15) with threadStops: ThreadStop[] matching the Workspace type. Render one stop per real entry: swatch stop.hex, label stop.paletteIndex + 1 or stop name if you add one. Drop the Math.random() length field entirely.
  2. Honest defer. If the bar is purely cosmetic, mark it disabled or hide it entirely when no thread stops are available:

    {threadStops.length === 0 ? null : <EditorThreadBar ... />}
    

    Or render a single "No threads yet — digitize a design first" message. Match what #71/#76 did for orphan UI: be honest about scope.

Recommended: Path 1. The snapshot already has the data, the bridge already exists, the only missing piece is reading threadStops alongside stitchObjects and passing them down. ~30 lines of changes.

Severity: low — the user can still pick a color and draw new paths, the click handler does work. But the bar misrepresents the design's actual threads, which is confusing. Same orphan-UI family as #71 / #76 / #88 / #91.

**Where:** `src/components/editor/EditorThreadBar.tsx` (79 lines). **Bug:** The thread bar along the bottom of the Editor presents itself as a "thread stops" picker but is fully static and disconnected from the actual design. ```ts const names = ['Red', 'Teal', 'Navy', 'Gold', 'Purple', 'Coral', 'Sky', 'Sage', 'Magenta', 'Olive']; return names.map((name, i) => ({ color:, index: i, name, length: Math.round(15 + Math.random() * 85), // ← random per render-init })); ``` Issues: 1. **Hard-coded 10 colors/names** that don't reflect `THREAD_COLORS` length (`src/components/editor/constants.ts`). If THREAD_COLORS has fewer than 10 entries, accessing `THREAD_COLORS[i]` for `i >= length` is `undefined` and renders nothing for that stop. If it has more, only 10 are shown. The bar pretends to be a picker over the full catalog but actually shows a 10-color subset. 2. **Length is randomized** (`Math.random() * 85`). Wrapped in `useState(getThreadStops)` so it's stable across re-renders, but the *value* is meaningless — there is no per-stop length in the underlying `state.threadStops`. The field is computed but never displayed. 3. **No connection to `state.threadStops`.** The Workspace produces real thread stops via `computeThreadStops(stitchObjects)` in `useWorkspace.ts:568`, passed through the `tc_workspace_snapshot` sessionStorage bridge into the Editor. But the Editor thread bar ignores the snapshot's `threadStops` and instead generates fake data on mount. 4. **`ThreadStop` interface (L5-10) is locally redefined** with a different shape than the Workspace's `ThreadStop` (in `src/components/workspace/types.ts`, fields: `id, colorId, paletteIndex, hex, length`). The local interface omits `colorId`, `paletteIndex`, `hex`, `id` and only has `color, index, name, length`. So even if the bar were ever wired to read real stops, the types would need to be unified first. 5. **`useState` with no setter (`[stops]`)** — the function returns `[stops]` and never calls setStops. Minor stylistic issue but signals "this is read-only seed data, not interactive state". **Effect on user:** when the user navigates from Workspace (post-digitize, with 8 real thread stops derived from the design's colors) → `/editor`, the thread bar at the bottom shows 10 generic named colors with random lengths, not the design's actual thread palette. The user can't see which threads their design uses, can't reorder real stops, can't pick "the actual blue I see in my design". The `selectedThreadIndex` (controlled by clicking these) is used as `THREAD_COLORS[currentThreadIndex]` for `currentThreadColor` passed to the canvas (Editor.tsx:532) and as the color for newly drawn paths (Editor.tsx:381, `threadColor: THREAD_COLORS[currentThreadIndex]`). So clicking changes the next-path-draw color, but the user has no idea which color is which in the design. **Fix consistent with existing patterns:** Two paths, pick one. 1. **Wire to the snapshot.** Read `tc_workspace_snapshot` from sessionStorage (already done by `Editor.loadInitialPaths` at Editor.tsx:35) and parse out `threadStops`. Pass them down as a prop: - In `Editor.tsx`, on mount, parse the snapshot once: `const [threadStops] = useState(() => { const raw = sessionStorage.getItem('tc_workspace_snapshot'); return raw ? (JSON.parse(raw).threadStops ?? []) : []; });` - Pass `threadStops={threadStops}` to `<EditorThreadBar />` at Editor.tsx:561. - Replace `EditorThreadBarProps` (L12-15) with `threadStops: ThreadStop[]` matching the Workspace type. Render one stop per real entry: swatch `stop.hex`, label `stop.paletteIndex + 1` or stop name if you add one. Drop the `Math.random()` length field entirely. 2. **Honest defer.** If the bar is purely cosmetic, mark it `disabled` or hide it entirely when no thread stops are available: ```tsx {threadStops.length === 0 ? null : <EditorThreadBar ... />} ``` Or render a single "No threads yet — digitize a design first" message. Match what #71/#76 did for orphan UI: be honest about scope. **Recommended:** Path 1. The snapshot already has the data, the bridge already exists, the only missing piece is reading threadStops alongside stitchObjects and passing them down. ~30 lines of changes. **Severity:** low — the user can still pick a color and draw new paths, the click handler does work. But the bar misrepresents the design's actual threads, which is confusing. Same orphan-UI family as #71 / #76 / #88 / #91.
Mark closed this issue 2026-09-14 17:44:05 +00:00
Author
Owner

Fixed in commit 91acc5f on origin/main (kilo-gsd.py Phase 8/9 Commit landed). Followed up by 5539429 (REORDER_STITCH_OBJECTS + threadStops prop). EditorThreadBar now reads the workspace snapshot and renders one stop per real thread entry. Closing this card as pre-completed.

Fixed in commit 91acc5f on `origin/main` (kilo-gsd.py Phase 8/9 Commit landed). Followed up by 5539429 (REORDER_STITCH_OBJECTS + threadStops prop). EditorThreadBar now reads the workspace snapshot and renders one stop per real thread entry. Closing this card as pre-completed.
Sign in to join this conversation.
No description provided.