Skip to content

fix: v1.5.1 — UX bug-fix pass (20 verified defects from a six-dimension frontend review) - #29

Merged
vppillai merged 5 commits into
mainfrom
fix/v1.5.1-ux-review-pass
Jul 17, 2026
Merged

fix: v1.5.1 — UX bug-fix pass (20 verified defects from a six-dimension frontend review)#29
vppillai merged 5 commits into
mainfrom
fix/v1.5.1-ux-review-pass

Conversation

@vppillai

Copy link
Copy Markdown
Owner

Summary

A six-dimension parallel frontend review (input pipeline, select tool, text tool, camera/render, UI chrome/keyboard, undo/persistence) surfaced twenty verified UX defects; every finding was traced end-to-end in code before fixing. Full details in the CHANGELOG entry this PR closes as v1.5.1.

Headliners

  • Window resize blanked the board — canvas bitmap reassignment erases content and nothing marked the frame dirty; setupCanvas now owns an onResize repaint contract, and DPR changes (monitor drags that fire no resize) are tracked via a re-armed matchMedia listener.
  • Pointer router single-owner gating — a stray second touch mid-stroke restarted the stroke; a stray pointerup from an unrelated pointer prematurely committed the gesture.
  • Firefox wheel deltas normalized — line-mode deltas made pan crawl at 3 px/notch and Ctrl+zoom imperceptible.
  • Line/arrow selection handles — 8 rect handles were drawn/grabbable but only 2 lie on the line; dragging the others teleported an endpoint. Per-kind handle sets now come from one shared enabledHandles() used by renderer + hit-test.
  • Stale text-measurement cache — re-editing a committed text rendered its pre-edit glyphs until undo/reload; invalidation now lives inside resizeToFit, the shared refit chokepoint.
  • Destructive-confirm safety — toasts focused the destructive button (stray Enter = factory reset), and Esc-closing the settings panel left reset toasts invisibly armed so the next click confirmed with no toast.
  • Editable-target guards centralized (editable.ts, two tiers) — typing e/Space into inputs hijacked eraser/pan; focused sliders killed every shortcut including Esc.
  • Plus: pagehide flush for in-progress text edits, persistence-failure toast, laser tap dot, ghost-cursor cleanup via new Tool.onPointerLeave hook, Shift+tap keeps selections, rotated-text editor overlay, Cmd+B refit, ? repeat guard, IME-safe Escape.

Architectural chokepoints introduced

editable.ts guards · setupCanvas(parent, onResize?) · Tool.onPointerLeave?(ctx) · enabledHandles(view) · invalidate-inside-resizeToFit — each fix that came from "two hand-maintained copies drifted" got a single source of truth so the class can't recur.

Test plan

  • bun run typecheck — clean across all workspaces
  • bun test — 283/283 (+9 new: eraser-hold guard, pointer single-owner gating, laser tap fade)
  • bun run test:e2e — 6/6 Playwright smoke tests against the production build
  • bun run lint / production build — clean
  • On-device Wacom feel-test (wheel zoom step, pen-leave cursor, rotated-text edit) — post-merge, per milestone-close convention

Closes CHANGELOG [Unreleased]v1.5.1; tag to follow after merge.

🤖 Generated with Claude Code

vppillai and others added 5 commits July 17, 2026 10:48
Six parallel review dimensions (input pipeline, select, text,
camera/render, UI chrome/keyboard, undo/persistence); every finding
traced end-to-end before fixing. Headliners:

- window resize / DPR change blanked or blurred the board (canvas
  bitmap wipe with no dirty-mark; DPR now tracked via re-armed
  matchMedia) — setupCanvas gains an onResize contract
- pointer router gains single-owner pointerId gating (stray second
  touch corrupted strokes; stray pointerup committed gestures early)
- wheel deltaMode normalized (Firefox line-mode pan/zoom crawled)
- line/arrow selection handles reduced to the two true endpoints via
  shared enabledHandles() (six bogus handles teleported endpoints)
- text measurement cache invalidation folded into resizeToFit (edited
  texts rendered pre-edit glyphs until undo/reload); rotated texts get
  a rotated editor overlay; Cmd+B/I on selected text refits its box
- editable-target guards centralized in editable.ts with two tiers
  (typing 'e'/Space in inputs hijacked eraser/pan; focused sliders
  killed every shortcut)
- destructive toasts focus Cancel and disarm on Esc/panel dismiss
  (stray Enter could factory-reset; orphaned armed toast confirmed
  silently)
- in-progress text edits flush on pagehide/beforeunload; persistence
  failures surface a once-per-session toast; laser taps draw a fading
  dot; pen/eraser ghost cursor cleared via new Tool.onPointerLeave;
  Shift+tap keeps selections; '?' gains a repeat guard; Escape
  respects IME composition

+9 unit tests (283 total); e2e smoke suite passes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
CI's high-severity audit gate (bun audit) failed on the vite
server.fs.deny Windows bypass advisory. Lockfile-only bump within the
existing ^8.0.13 range; the bun.lock change also rotates the e2e job's
Playwright browser-cache key, replacing the stale June cache that was
missing chromium_headless_shell-1223.

Verified locally: audit clean, typecheck, 283 unit tests, 6/6 e2e
against the vite 8.1.5 production build.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…nstalls

`bun update vite` injected a spurious root vite dependency entry into
bun.lock that validated against a warm local cache but failed
`bun install --frozen-lockfile` on every cold CI install (lint/test,
e2e, Docker). Regenerated via a cold `bun install` in a fresh clone;
vite stays at 8.1.5, audit stays clean, frozen install verified cold.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
`bunx playwright install` at the repo root doesn't resolve the e2e
workspace's bin (it lives in apps/e2e/node_modules/.bin, not hoisted),
so bunx silently downloaded the LATEST playwright and installed that
version's browser builds — while the pinned 1.60.0 test runner looks
for chromium_headless_shell-1223. The drift was masked while latest ≈
1.60 and the browser cache was warm; the June cache expired (7-day
eviction) and latest moved on, so every fresh install produced
mismatched browsers. New `install-browsers` package script runs the
workspace-pinned playwright deterministically.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@vppillai
vppillai merged commit 36158a0 into main Jul 17, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant