Workspace 'Save to Gallery' silently no-ops after Run Auto-Digitize — closure captures stale appState='digitizing' #102
Labels
No labels
blocked:commit-ready-failed
blocked:gate-fetch
blocked:gate-lint
blocked:gate-plan
blocked:gate-research
blocked:needs-attention
blocked:needs-decision
blocked:needs-human
blocked:no-changes
blocked:verify-failed
bug
deferred
feature-request
in-progress
needs-decompose
needs-triage
quickfix
security/medium
triage-approved
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Mark/Kimi_Agent_Brother_10_Digitizing_App#102
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Bug 1: Save to Gallery silent no-op after digitize
Severity
High (data-loss-impacting). The user-facing "Save to Gallery" button does nothing
after the recent runAutoDigitize rewrite.
Reproduction
/workspace.tests/fixtures/flower.png.digitized.data-testid="save-to-gallery"button in AppHeader).tc_gallery_designs. It's stillnull.Expected: a
Designentry appears undertc_gallery_designsin localStorage.Actual:
addDesign(design)is never called.localStorage.setItemis never invoked.Evidence
Reproduced by shim-instrumenting
saveCurrentDesigninsrc/components/workspace/useWorkspace.tsto log the closure state on entry:The closure still sees
appState='digitizing'at click time, even though thecanvas is mounted (meaning the actual appState in Redux is
'digitized'). Thesecond guard at
useWorkspace.ts:765-768:fires before
addDesign(design)is reached, returningnullsilently.This was confirmed two ways:
Storage.prototype.setItemwas shimmed to log every write — zero writesoccurred between Save click and the end of the test.
localStorage.getItem('tc_gallery_designs')isnullbefore AND after Savein a clean session.
Root cause
saveCurrentDesignis auseCallbackwhose dep array is[state.uploadedImage, state.appState, state.designSettings.fileName, state.designSettings.hoopSize, state.stitchObjects, state.threadStops, state.colorRegions, addToast](
useWorkspace.ts:815). AftercommitDigitizedResultdispatchesSET_STATE: 'digitizing', a new closure is created.pages/Workspace.tsx:254passesonSave={saveCurrentDesign}down tocomponents/workspace/AppHeader.tsx. The<button onClick={onSave}>atAppHeader.tsx:170capturesonSaveat render time. Because the AppHeader'sprops are deep-equal on the prior render (the closure identity changed but the
prop value didn't), React's reconciler may not re-render the button — the DOM
keeps the previous
onclickreference.When the user clicks Save, the previous closure fires, and it sees
appState='digitizing'because it was captured before the digitize started.Suggested fix
Wrap the callback in a ref pattern so AppHeader always reads the latest closure:
Alternative:
useEventfromreact-use, or context.Related
wiring landed broken from day one.
designs never reach
tc_gallery_designs.button end-to-end until Save works.
Fixed by
11752b2— AppHeader dispatches Save/Export through latest-callback refs so the button always reads the freshestonSaveclosure rather than the one captured at first render. SHIM[SHIM-SAVE]debug log also removed (was added during E2E reproduction, safe to drop). Regression tests added toAppHeader.test.tsx((#102) clicking Save to Gallery calls the LATEST onSave closure, not a stale oneand the matching Export assertion) — these fail against the old code path and pass against the fix.Reproduction was: upload → Run Auto-Digitize → wait for state=
digitized→ click Save to Gallery →addDesign(design)never called,localStorage.setItemnever invoked, closure still sawappState=digitizing. Fix verified at unit-test level (7/7 AppHeader tests pass, 330/330 total across 38 test files). CI run #87 (workflow run id 7108) was queued on push — localnpm test,tsc -b, andeslint .all clean on the changed files.