Canvas placeholder overlay persists after digitize, hiding stitched output #42

Closed
opened 2026-07-26 15:04:46 +00:00 by Mark · 1 comment
Owner

Bug — "Drop your image here" placeholder overlay persists after digitize

Severity: P3 (visual/UX — stitches are rendered underneath but hidden by the placeholder overlay).

Description: The "Drop your image here" heading + "or click to browse" paragraph in the canvas upload zone is always rendered on top of the canvas regardless of appState. After uploading an SVG and clicking Auto-Digitize, the digitize succeeds (stitch objects are created, bottom bar shows "Total: X stops | X.X yards", success toast is shown), but the canvas still displays the "Drop your image here" placeholder as if no design is loaded. The stitches are rendered underneath the overlay but not visible to the user.

Repro (verified in live browser, 2026-07-24):

  1. Navigate to /workspace
  2. Upload any SVG (text, shapes, gradient, or PNG)
  3. Click Auto-Digitize FAB
  4. Wait for success toast ("Auto-digitize completed. Design ready for editing.")
  5. Canvas still shows "Drop your image here" — stitches are underneath but hidden
  6. Bottom bar shows "Total: X stops | X.X yards" confirming digitize produced output
  7. Canvas.toDataURL() returns pixel data (943x537 with content)

Expected: The placeholder should hide when appState is not 'idle' and digitized content exists. Once stitches are rendered, the user should see them — not the upload prompt.

Fix: The upload-zone component should be gated behind appState === 'idle' (or whichever state means "no design loaded"). It currently renders unconditionally on top of the canvas.

Related: #28 was the FAB auto-advance fix that landed in 8da6c17. The placeholder visibility was not part of that fix.

## Bug — "Drop your image here" placeholder overlay persists after digitize **Severity**: P3 (visual/UX — stitches are rendered underneath but hidden by the placeholder overlay). **Description**: The "Drop your image here" heading + "or click to browse" paragraph in the canvas upload zone is always rendered on top of the canvas regardless of appState. After uploading an SVG and clicking Auto-Digitize, the digitize succeeds (stitch objects are created, bottom bar shows "Total: X stops | X.X yards", success toast is shown), but the canvas still displays the "Drop your image here" placeholder as if no design is loaded. The stitches are rendered underneath the overlay but not visible to the user. **Repro** (verified in live browser, 2026-07-24): 1. Navigate to /workspace 2. Upload any SVG (text, shapes, gradient, or PNG) 3. Click Auto-Digitize FAB 4. Wait for success toast ("Auto-digitize completed. Design ready for editing.") 5. **Canvas still shows "Drop your image here"** — stitches are underneath but hidden 6. Bottom bar shows "Total: X stops | X.X yards" confirming digitize produced output 7. Canvas.toDataURL() returns pixel data (943x537 with content) **Expected**: The placeholder should hide when `appState` is not `'idle'` and digitized content exists. Once stitches are rendered, the user should see them — not the upload prompt. **Fix**: The upload-zone component should be gated behind `appState === 'idle'` (or whichever state means "no design loaded"). It currently renders unconditionally on top of the canvas. **Related**: #28 was the FAB auto-advance fix that landed in `8da6c17`. The placeholder visibility was not part of that fix.
Author
Owner

Verification — closed

Fix commit: ca0c025 (on main). Resolves the overlay-after-digitize bug.

Root cause (rooted in React fiber inspection of the running bundle):

The placeholder overlay was wrapped in <AnimatePresence> with two
nested <motion.div>s. The outer had exit={{ opacity: 0, scale: 0.95 }} transition={{ duration: 300 }}. The inner had its own entrance
transition={{ duration: 400 }} and no exit. Framer-motion v12 with
React 18 concurrent rendering failed to coordinate the exit cleanly —
the H2 hung in the DOM with opacity decaying over several seconds
(observed: 0.38 at t=2s, 0.30 at t=3s, 0.22 at t=5s, slowly toward
zero but never reaching unmount within typical user attention spans).

The condition appState === 'empty' alone was correctly toggling, but
the rendered overlay kept stamping on top of the canvas, hiding the
stitched output.

Fix: Drop the <AnimatePresence> wrapper and the outer
<motion.div>. Render the conditional <div> directly. Exit unmounts
on the same React tick that flips appState. Tightened the condition
to also require colorRegions.length === 0 && stitchObjects.length === 0 (defensive).

Verified live (browser snapshot after Auto-Digitize completes):

  • State: digitized
  • document.querySelectorAll('h2').length = 0 (was 1 before fix)
  • Stitch UI overlays (Image/Stitches/Both toggles, Adjust Settings,
    Confirm & Edit) visible and unobstructed
  • Bottom bar shows Total: X stops | X.X yards

Build status: npm run build exit 0, 198/198 tests pass.

Closes #42.

## Verification — closed **Fix commit:** `ca0c025` (on `main`). Resolves the overlay-after-digitize bug. **Root cause** (rooted in React fiber inspection of the running bundle): The placeholder overlay was wrapped in `<AnimatePresence>` with two nested `<motion.div>`s. The outer had `exit={{ opacity: 0, scale: 0.95 }} transition={{ duration: 300 }}`. The inner had its own entrance `transition={{ duration: 400 }}` and no exit. Framer-motion v12 with React 18 concurrent rendering failed to coordinate the exit cleanly — the H2 hung in the DOM with opacity decaying over several seconds (observed: 0.38 at t=2s, 0.30 at t=3s, 0.22 at t=5s, slowly toward zero but never reaching unmount within typical user attention spans). The condition `appState === 'empty'` alone was correctly toggling, but the rendered overlay kept stamping on top of the canvas, hiding the stitched output. **Fix:** Drop the `<AnimatePresence>` wrapper and the outer `<motion.div>`. Render the conditional `<div>` directly. Exit unmounts on the same React tick that flips appState. Tightened the condition to also require `colorRegions.length === 0 && stitchObjects.length === 0` (defensive). **Verified live (browser snapshot after Auto-Digitize completes):** - State: `digitized` - `document.querySelectorAll('h2').length` = **0** (was 1 before fix) - Stitch UI overlays (Image/Stitches/Both toggles, Adjust Settings, Confirm & Edit) visible and unobstructed - Bottom bar shows `Total: X stops | X.X yards` **Build status:** `npm run build` exit 0, 198/198 tests pass. Closes #42.
Mark 2026-07-29 14:19:55 +00:00
Sign in to join this conversation.
No description provided.