Gallery→Workspace→Editor→Save creates a duplicate gallery row instead of updating the original — sessionStorage bridge carries the snapshot but not the design id #152

Closed
opened 2026-10-03 16:04:39 +00:00 by Mark · 1 comment
Owner

Finding

The Gallery → Workspace → Editor → Save flow creates a duplicate gallery row instead of updating the original. The root cause is that the bridge between Gallery and Workspace (sessionStorage key tc_load_design_snapshot) carries the design's snapshot (stitch geometry) but not the design's id (gallery row identifier).

The flow:

  1. User clicks "Open in Workspace" on Gallery design A (id design-1737000000)
  2. Gallery writes the snapshot to sessionStorage (tc_load_design_snapshot), navigates to /workspace (src/pages/Gallery.tsx:158-168)
  3. Workspace reads the snapshot, dispatches LOAD_DESIGN (src/components/workspace/useWorkspace.ts:551-590); the design ID is lost
  4. User clicks "Open in Editor"; navigates to /editor
  5. User edits paths; Editor writes the new paths to a different sessionStorage key (tc_workspace_snapshot, per #110)
  6. User clicks "Back to Workspace"; the snapshot is re-hydrated
  7. User clicks "Save to Gallery" (saveCurrentDesign at src/components/workspace/useWorkspace.ts:1045-1140); the save constructs a new design with id: 'design-${Date.now()}' (line 1096)
  8. The gallery now has the original design A plus a new design B (the edited version). The user's edits are in B, but A is unchanged.

If the user opens design A, edits, saves, opens B from gallery, edits, saves — they have A, B, C, D. The gallery fills with one-off rows that aren't connected to their lineage.

Where

  • Gallery bridge (missing the id): src/pages/Gallery.tsx:158-168
  • Workspace mount effect (consumes snapshot, not id): src/components/workspace/useWorkspace.ts:551-590
  • Editor write (carries paths, not id): src/pages/Editor.tsx:188-210
  • Save handler (always assigns new id): src/components/workspace/useWorkspace.ts:1096

Suggested fix shape

Three changes:

  1. Extend the Gallery→Workspace bridge to carry the design id. In Gallery.tsx:158-168, write { id, snapshot, designSettings } instead of just the snapshot. Update the Workspace's mount effect at useWorkspace.ts:551-590 to dispatch the id into state.

  2. Add a loadedDesignId field to WorkspaceState (or a transient lastLoadedDesignId that the save handler reads). When saveCurrentDesign runs, if the workspace has a loadedDesignId:

    • If a row with that id exists in the gallery's localStorage, update it in place (preserve folderId, isFavorite, createdAt).
    • If not (corrupt or external upload), fall through to creating a new row.
    • Clear loadedDesignId after the save so subsequent saves create new rows.
  3. Same change for the Editor→Workspace bridge at #110: have the Editor carry the id through its sessionStorage write so a round-trip from Gallery → Editor → Save updates the original row, not a new one.

Severity

High. Every save from the editor creates a gallery row that's a near-duplicate of its parent. The gallery fills with stale copies.

Discovered by

Gap analysis (2026-10-02) of editor/gallery/save integration.

  • #74 (closed) — added "Open in Workspace" bridge. Set up the sessionStorage but didn't carry the id.
  • #110 (closed) — Editor writes back to sessionStorage. Same gap.
  • #150 (in progress per this filing batch) — Editor needs a Save button. Saving from the Editor should update the original row, not create a new one.
## Finding The Gallery → Workspace → Editor → Save flow creates a duplicate gallery row instead of updating the original. The root cause is that the bridge between Gallery and Workspace (`sessionStorage` key `tc_load_design_snapshot`) carries the design's `snapshot` (stitch geometry) but not the design's `id` (gallery row identifier). The flow: 1. User clicks "Open in Workspace" on Gallery design A (id `design-1737000000`) 2. Gallery writes the snapshot to `sessionStorage` (`tc_load_design_snapshot`), navigates to `/workspace` (`src/pages/Gallery.tsx:158-168`) 3. Workspace reads the snapshot, dispatches `LOAD_DESIGN` (`src/components/workspace/useWorkspace.ts:551-590`); the design ID is **lost** 4. User clicks "Open in Editor"; navigates to `/editor` 5. User edits paths; Editor writes the new paths to a different sessionStorage key (`tc_workspace_snapshot`, per #110) 6. User clicks "Back to Workspace"; the snapshot is re-hydrated 7. User clicks "Save to Gallery" (`saveCurrentDesign` at `src/components/workspace/useWorkspace.ts:1045-1140`); the save constructs a new design with `id: 'design-${Date.now()}'` (line 1096) 8. The gallery now has the original design A plus a new design B (the edited version). The user's edits are in B, but A is unchanged. If the user opens design A, edits, saves, opens B from gallery, edits, saves — they have A, B, C, D. The gallery fills with one-off rows that aren't connected to their lineage. ## Where - Gallery bridge (missing the id): `src/pages/Gallery.tsx:158-168` - Workspace mount effect (consumes snapshot, not id): `src/components/workspace/useWorkspace.ts:551-590` - Editor write (carries paths, not id): `src/pages/Editor.tsx:188-210` - Save handler (always assigns new id): `src/components/workspace/useWorkspace.ts:1096` ## Suggested fix shape Three changes: 1. **Extend the Gallery→Workspace bridge to carry the design id.** In `Gallery.tsx:158-168`, write `{ id, snapshot, designSettings }` instead of just the snapshot. Update the Workspace's mount effect at `useWorkspace.ts:551-590` to dispatch the id into state. 2. **Add a `loadedDesignId` field to `WorkspaceState`** (or a transient `lastLoadedDesignId` that the save handler reads). When `saveCurrentDesign` runs, if the workspace has a `loadedDesignId`: - If a row with that id exists in the gallery's localStorage, update it in place (preserve `folderId`, `isFavorite`, `createdAt`). - If not (corrupt or external upload), fall through to creating a new row. - Clear `loadedDesignId` after the save so subsequent saves create new rows. 3. **Same change for the Editor→Workspace bridge at #110**: have the Editor carry the id through its sessionStorage write so a round-trip from Gallery → Editor → Save updates the original row, not a new one. ## Severity High. Every save from the editor creates a gallery row that's a near-duplicate of its parent. The gallery fills with stale copies. ## Discovered by Gap analysis (2026-10-02) of editor/gallery/save integration. ## Related - #74 (closed) — added "Open in Workspace" bridge. Set up the sessionStorage but didn't carry the id. - #110 (closed) — Editor writes back to sessionStorage. Same gap. - #150 (in progress per this filing batch) — Editor needs a Save button. Saving from the Editor should update the original row, not create a new one.
Author
Owner

Fixed in commit 91a00aa on origin/main.

Three coordinated changes:

  1. Bridge payloads now carry the gallery row id. Gallery→Workspace writes { id, snapshot } instead of bare snapshot; same envelope for Gallery→Editor and the Editor→Workspace round-trip. The Workspace mount effect accepts both shapes (id optional, snapshot may be nested or top-level) for back-compat with the existing #74 / #110 tests.

  2. WorkspaceState carries loadedDesignId: string | null set by LOAD_DESIGN. A new SET_LOADED_DESIGN_ID action clears the binding after success so a follow-up save creates a fresh row (save-as).

  3. saveDesignToGallery (the helper #150 extracted for the Editor Save button) now threads loadedDesignId and routes the save through a new updateDesign helper when the id matches an existing row. The result carries updated: boolean so callers can surface an "Updated" vs "Saved" toast. Both Workspace.saveCurrentDesign and Editor.handleSave now share this primitive.

Backwards compat: bare-snapshot payloads (no id, no envelope) still accepted by both Workspace mount bridges — #74 and #110 tests still pass unchanged. The 3 existing Gallery bridge tests were updated to assert the new envelope shape.

Tests: 12 new #152 cases pin the bridge envelopes, the loadedDesignId field, the in-place update path, the fall-through-to-create path, the save-as semantics, and the folder/favorite preservation. Updated fixtures in 4 mock-state helpers. Total suite: 634/634 pass, lint clean, tsc -b clean.

Fixed in commit 91a00aa on origin/main. Three coordinated changes: 1. **Bridge payloads now carry the gallery row id.** Gallery→Workspace writes `{ id, snapshot }` instead of bare `snapshot`; same envelope for Gallery→Editor and the Editor→Workspace round-trip. The Workspace mount effect accepts both shapes (id optional, snapshot may be nested or top-level) for back-compat with the existing #74 / #110 tests. 2. **`WorkspaceState` carries `loadedDesignId: string | null`** set by `LOAD_DESIGN`. A new `SET_LOADED_DESIGN_ID` action clears the binding after success so a follow-up save creates a fresh row (save-as). 3. **`saveDesignToGallery` (the helper #150 extracted for the Editor Save button) now threads `loadedDesignId`** and routes the save through a new `updateDesign` helper when the id matches an existing row. The result carries `updated: boolean` so callers can surface an "Updated" vs "Saved" toast. Both `Workspace.saveCurrentDesign` and `Editor.handleSave` now share this primitive. Backwards compat: bare-snapshot payloads (no id, no envelope) still accepted by both Workspace mount bridges — #74 and #110 tests still pass unchanged. The 3 existing Gallery bridge tests were updated to assert the new envelope shape. Tests: 12 new #152 cases pin the bridge envelopes, the loadedDesignId field, the in-place update path, the fall-through-to-create path, the save-as semantics, and the folder/favorite preservation. Updated fixtures in 4 mock-state helpers. Total suite: 634/634 pass, lint clean, tsc -b clean.
Mark closed this issue 2026-10-03 16:32:12 +00:00
Sign in to join this conversation.
No description provided.