Workspace has no progress indicator for auto-digitize — user uploading a large image sees the canvas blank with no feedback while the service runs (could be 5-30s), no step number, no progress bar, no message #264

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

Finding

src/components/workspace/useWorkspace.ts:218 defines processingStep: number (0-4) and processingMessage: string. The reducer dispatches SET_PROCESSING to set them. The values are read in tests but not in the UI.

grep -rn 'processingStep\|processingMessage' src/components/workspace/ returns matches in the types and tests only. The workspace's actual UI components don't render these.

A user uploading a 4K image:

  • Clicks "Auto-Digitize"
  • Canvas goes blank (or stays on the image preview)
  • The user waits
  • The "Analyzing image…" message is in processingMessage but never displayed
  • 5-30 seconds pass with no visible feedback
  • The user wonders: is it stuck? is the service down? should I click again?

The polish move is a small overlay at the bottom of the canvas:

{state.processingStep > 0 && (
  <div className="absolute bottom-4 left-1/2 -translate-x-1/2 ...">
    <Loader2 size={16} className="animate-spin" />
    {state.processingMessage} (Step {state.processingStep}/4)
  </div>
)}

Where

  • processingStep: src/components/workspace/types.ts:218 (state, type says 0-4)
  • processingMessage: same file
  • No UI: grep -rn 'processingStep\|processingMessage' src/components/workspace/CanvasArea.tsx returns 0
  • Same as #202 (filed earlier): processingMessage is dispatched but never rendered

Suggested fix shape

Add the progress overlay to the workspace's canvas (or as a floating bottom-of-screen pill):

{state.processingStep > 0 && (
  <motion.div
    initial={{ y: 20, opacity: 0 }}
    animate={{ y: 0, opacity: 1 }}
    className="fixed bottom-6 left-1/2 -translate-x-1/2 px-4 py-2.5 rounded-full"
    style={{ backgroundColor: '#FFFFFF', border: '1px solid #D9D2C8', boxShadow: '...' }}
  >
    <Loader2 size={14} className="animate-spin inline mr-2" style={{ color: '#E6A948' }} />
    <span style={{ color: '#1A1A1A' }}>{state.processingMessage}</span>
    <span className="ml-2 text-[11px]" style={{ color: '#6B6560' }}>
      Step {state.processingStep} of 4
    </span>
  </motion.div>
)}

The progress indicator is dismissed when processingStep === 0 (which is set after digitize completes or fails).

The 4 steps (per the type): 0=idle, 1=analyzing, 2=color reduction, 3=stitch generation, 4=done. Each transition is a SET_PROCESSING dispatch; the UI surfaces them as they happen.

Severity

Medium. The user has no feedback during a 5-30s operation. Power users notice immediately.

Discovered by

Code review (2026-10-02) of workspace processing state.

  • #202 (open) — Same root cause: state is dispatched but never rendered. The progress indicator is the comprehensive fix.
  • The toasts are shown on success/error; the progress indicator is the during-state surface.
  • The editor's session restore (per #161) also takes a moment; a similar indicator could appear during that.
## Finding `src/components/workspace/useWorkspace.ts:218` defines `processingStep: number` (0-4) and `processingMessage: string`. The reducer dispatches `SET_PROCESSING` to set them. The values are read in tests but not in the UI. `grep -rn 'processingStep\|processingMessage' src/components/workspace/` returns matches in the types and tests only. The workspace's actual UI components don't render these. A user uploading a 4K image: - Clicks "Auto-Digitize" - Canvas goes blank (or stays on the image preview) - The user waits - The "Analyzing image…" message is in `processingMessage` but never displayed - 5-30 seconds pass with no visible feedback - The user wonders: is it stuck? is the service down? should I click again? The polish move is a small overlay at the bottom of the canvas: ```tsx {state.processingStep > 0 && ( <div className="absolute bottom-4 left-1/2 -translate-x-1/2 ..."> <Loader2 size={16} className="animate-spin" /> {state.processingMessage} (Step {state.processingStep}/4) </div> )} ``` ## Where - processingStep: `src/components/workspace/types.ts:218` (state, type says 0-4) - processingMessage: same file - No UI: `grep -rn 'processingStep\|processingMessage' src/components/workspace/CanvasArea.tsx` returns 0 - Same as #202 (filed earlier): `processingMessage` is dispatched but never rendered ## Suggested fix shape Add the progress overlay to the workspace's canvas (or as a floating bottom-of-screen pill): ```tsx {state.processingStep > 0 && ( <motion.div initial={{ y: 20, opacity: 0 }} animate={{ y: 0, opacity: 1 }} className="fixed bottom-6 left-1/2 -translate-x-1/2 px-4 py-2.5 rounded-full" style={{ backgroundColor: '#FFFFFF', border: '1px solid #D9D2C8', boxShadow: '...' }} > <Loader2 size={14} className="animate-spin inline mr-2" style={{ color: '#E6A948' }} /> <span style={{ color: '#1A1A1A' }}>{state.processingMessage}</span> <span className="ml-2 text-[11px]" style={{ color: '#6B6560' }}> Step {state.processingStep} of 4 </span> </motion.div> )} ``` The progress indicator is dismissed when `processingStep === 0` (which is set after digitize completes or fails). The 4 steps (per the type): 0=idle, 1=analyzing, 2=color reduction, 3=stitch generation, 4=done. Each transition is a `SET_PROCESSING` dispatch; the UI surfaces them as they happen. ## Severity Medium. The user has no feedback during a 5-30s operation. Power users notice immediately. ## Discovered by Code review (2026-10-02) of workspace processing state. ## Related - #202 (open) — Same root cause: state is dispatched but never rendered. The progress indicator is the comprehensive fix. - The toasts are shown on success/error; the progress indicator is the during-state surface. - The editor's session restore (per #161) also takes a moment; a similar indicator could appear during that.
Author
Owner

Fixed in commit f932453 (main).

Change: src/components/workspace/CanvasArea.tsx replaces the full-screen digitize overlay (absolute inset-0 with translucent panel + 4-step checklist) with a small floating bottom-of-canvas pill (absolute bottom-6 left-1/2 -translate-x-1/2 rounded-full). The image preview stays visible underneath while the service runs.

Pill contents:

  • spinner (<Loader> rotating 360°)
  • processingMessage || "Processing…" (#202 fallback contract preserved)
  • Step {processingStep} of 4 indicator
  • Cancel button (same cancelAutoDigitize prop, same data-testid="cancel-auto-digitize" so existing call sites still find it)

Pill visibility: 0 < processingStep < 4 — present during processing, gone at step 4 (digitize done) so the user sees the result without a stale indicator.

Scope notes:

  • The previous PROCESSING_STEPS 4-label checklist is gone — SET_PROCESSING only fires at step 1 (start) and step 4 (done), so the per-step labels were aspirational and never matched the dispatched processingMessage. The pill surfaces the dispatched message as the headline, which is the actual source of truth.
  • The full-screen overlay was the read-as-hang failure mode on 4K images. The pill keeps the image visible so the user knows the service is doing something.

Verification:

  • npx vitest run → 1409 pass, 1 skipped (pre-existing)
  • npx tsc --noEmit -p tsconfig.app.json → no new errors in CanvasArea
  • npx eslint → clean on changed files
  • New src/components/workspace/issue-264.test.tsx (6 tests) pins: pill renders during processing with the dispatched message + Step indicator; pill hidden at step 0 (idle) and step 4 (done); pill renders the Processing… fallback when processingMessage is empty (#202 contract); Cancel button wires cancelAutoDigitize.
Fixed in commit f932453 (main). **Change:** `src/components/workspace/CanvasArea.tsx` replaces the full-screen digitize overlay (`absolute inset-0` with translucent panel + 4-step checklist) with a small floating bottom-of-canvas pill (`absolute bottom-6 left-1/2 -translate-x-1/2 rounded-full`). The image preview stays visible underneath while the service runs. **Pill contents:** - spinner (`<Loader>` rotating 360°) - `processingMessage || "Processing…"` (#202 fallback contract preserved) - `Step {processingStep} of 4` indicator - Cancel button (same `cancelAutoDigitize` prop, same `data-testid="cancel-auto-digitize"` so existing call sites still find it) **Pill visibility:** `0 < processingStep < 4` — present during processing, gone at step 4 (digitize done) so the user sees the result without a stale indicator. **Scope notes:** - The previous `PROCESSING_STEPS` 4-label checklist is gone — `SET_PROCESSING` only fires at step 1 (start) and step 4 (done), so the per-step labels were aspirational and never matched the dispatched `processingMessage`. The pill surfaces the dispatched message as the headline, which is the actual source of truth. - The full-screen overlay was the read-as-hang failure mode on 4K images. The pill keeps the image visible so the user knows the service is doing something. **Verification:** - `npx vitest run` → 1409 pass, 1 skipped (pre-existing) - `npx tsc --noEmit -p tsconfig.app.json` → no new errors in CanvasArea - `npx eslint` → clean on changed files - New `src/components/workspace/issue-264.test.tsx` (6 tests) pins: pill renders during processing with the dispatched message + Step indicator; pill hidden at step 0 (idle) and step 4 (done); pill renders the `Processing…` fallback when `processingMessage` is empty (#202 contract); Cancel button wires `cancelAutoDigitize`.
Mark closed this issue 2026-10-04 12:11:00 +00:00
Sign in to join this conversation.
No description provided.