Make slides shape insertion feel like a real tool#239
Conversation
Click-anchored default-size insert per kind, crosshair cursor while insert is armed, hover + drag ghost previews, and Escape to disarm (or cancel an in-flight drag). Together these give the user feedback at every step — they can see what they are about to insert, where it will land, and that the editor is in a special mode they can back out of — instead of relying on the toolbar button alone. Drag preview now flows through the same forceRender(slide, doc, ghost) channel as the hover ghost, so the in-flight shape stays semi- transparent until commit on mouseup. ESC during a drag aborts without writing anything to the store. Drive-by fixes surfaced during browser smoke: Toggle pressed visual broke when wrapped in TooltipTrigger asChild (Tooltip's data-state clobbered Toggle's data-state=on); now also matches on aria-pressed. Shape picker dropdown didn't auto-close on shape select since its entries are plain buttons, not DropdownMenuItem; the picker now controls its own open state and closes on select.
Review surfaced two real cleanup gaps in the hover-ghost path:
- detach() left a pending rAF in flight, painting into a torn-down
canvas. Now cancels and clears hoverPreview.
- setInsertMode swapping shape kind A → B kept the old kind in
hoverPreview until the next mousemove, so a queued rAF could
briefly paint a kind-A ghost after the user picked kind B. Now
clears on every insert-mode change; only repaints when leaving
a ghost-eligible mode to avoid flashing between shape kinds.
Also adds the two missing test paths flagged in review: ESC mid-drag
abort (no element committed, mode disarmed) and disarm-while-hovered
(cleanup path is safe, detach() doesn't throw).
Minor: Size interface in insert.ts was exported but never imported
anywhere — narrowed to module-local.
|
Important Review skippedReview was skipped due to path filters ⛔ Files ignored due to path filters (8)
CodeRabbit blocks several paths by default. You can override this behavior by explicitly including those paths in the path filters. For example, including ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
📝 WalkthroughWalkthroughThis PR implements shape-insert UX improvements for the slides editor: click-triggered inserts with per-kind default sizing, hover and drag ghost previews, cursor feedback (crosshair during insert mode), and Escape-key disarm/abort behavior. It also fixes related UI regressions in shape picker and toggle styling. ChangesSlides Editor Insert Mode UX
Sequence DiagramsequenceDiagram
participant User
participant Editor
participant Canvas as Canvas/Ghost
participant Renderer as SlideRenderer
participant Insert as insert.ts
User->>Editor: Click shape button (arm insert mode)
Editor->>Renderer: setInsertMode('rect')<br/>cursor=crosshair
User->>Canvas: Hover over canvas
Canvas->>Editor: mousemove
Editor->>Renderer: schedule hover preview via rAF
Renderer->>Canvas: forceRender(slide, doc, ghost)<br/>with ghost alpha=0.4
User->>Canvas: Drag to size
Canvas->>Editor: mousemove (drag)
Editor->>Insert: buildInsertElement with start/end
Insert->>Insert: compute dx, dy
Insert->>Renderer: renders ghost preview per frame
User->>Canvas: Release (mouseup)
Editor->>Insert: buildInsertElement (finalized)
Insert->>Editor: returns committed Element
Editor->>Editor: setInsertMode(null)<br/>cursor=default
User->>Canvas: Press Escape
Canvas->>Editor: keydown (capture)
Editor->>Editor: abort drag, clear ghost
Editor->>Editor: setInsertMode(null)
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Warning Review ran into problems🔥 ProblemsStopped waiting for pipeline failures after 30000ms. One of your pipelines takes longer than our 30000ms fetch window to run, so review may not consider pipeline-failure results for inline comments if any failures occurred after the fetch window. Increase the timeout if you want to wait longer or run a Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Verification: verify:selfResult: ✅ PASS in 191.0s
Verification: verify:integrationResult: ✅ PASS |
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
packages/slides/src/view/editor/editor.ts (1)
894-952: 💤 Low valueNote: Closure-local
cancelledflag relies on explicit cleanup calls.The
cancelledflag (line 904) and thecleanup()helper (lines 911-916) correctly handle the current exit paths (mouseup, ESC). However, if future code adds new exit paths (e.g., visibility change, window blur), those paths must also callcleanup()to avoid leaking listeners and correctly resetinsertDragging. This is noted as a known limitation in the PR objectives.Current implementation is correct; this is a maintainability note for future changes.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/slides/src/view/editor/editor.ts` around lines 894 - 952, The drag-to-size flow relies on the closure-local cancelled flag and the cleanup() helper to remove listeners and reset this.insertDragging; ensure any future new exit paths (e.g., visibilitychange, blur, beforeunload, or programmatic aborts) call cleanup() and, if they can cancel the insert, set cancelled = true before calling cleanup(); reference the drag handlers and helpers: cancelled, cleanup(), onMove, onUp, onKey, this.insertDragging, and any new abort handler so listeners aren’t leaked and insert mode is properly reset.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@packages/slides/src/view/editor/editor.ts`:
- Around line 894-952: The drag-to-size flow relies on the closure-local
cancelled flag and the cleanup() helper to remove listeners and reset
this.insertDragging; ensure any future new exit paths (e.g., visibilitychange,
blur, beforeunload, or programmatic aborts) call cleanup() and, if they can
cancel the insert, set cancelled = true before calling cleanup(); reference the
drag handlers and helpers: cancelled, cleanup(), onMove, onUp, onKey,
this.insertDragging, and any new abort handler so listeners aren’t leaked and
insert mode is properly reset.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 5605d8aa-b03f-43e9-9f10-7c12b87d3930
📒 Files selected for processing (9)
docs/tasks/active/20260514-slides-click-insert-cursor-todo.mdpackages/frontend/src/app/slides/shape-picker.tsxpackages/frontend/src/components/ui/toggle.tsxpackages/slides/src/view/canvas/slide-renderer.tspackages/slides/src/view/editor/editor.test.tspackages/slides/src/view/editor/editor.tspackages/slides/src/view/editor/interactions/insert.test.tspackages/slides/src/view/editor/interactions/insert.tspackages/slides/src/view/editor/interactions/keyboard.ts
The Toggle aria-pressed CSS fix restores the pressed visual on the Select / Text toggle buttons, which the previous baselines captured in their broken (unhighlighted) state. CI's verify-browser job flagged the 8 affected snapshots (light/dark × desktop/mobile × full-harness/slides-toolbar). Regenerated through the canonical docker path so the host environment can't drift them.
Followup to #239: move the task doc into archive/2026/05 and refresh the active/archive index files.
Summary
The slides editor's "insert a shape" flow gets four coupled UX improvements so insert mode feels like a coherent tool instead of a toolbar checkbox:
setInsertMode(kind)togglescursor: 'crosshair'on the slide canvas + overlay; disarm restores to''. Matches Google Slides and PowerPoint — without it the user has no signal that the next click will place a shape.Drive-by fixes surfaced during browser smoke
<TooltipTrigger asChild>— Tooltip'sdata-state="closed"/"open"cloned onto the trigger element clobbered Toggle'sdata-state="on", sodata-[state=on]:bg-accentnever matched. Now also matchesaria-pressed=true(set reliably by Radix Toggle regardless of Tooltip wrapping). Affects Select / Text toggle buttons in the slides toolbar.<button>s, notDropdownMenuItems. The picker now controls its ownopenstate.Known limitations / non-blocking
[data-state="open"][role="menu"].cancelledflag is a closure-local boolean shared between two listeners. Pattern works but is fragile if a future exit path is added without going throughcleanup().Test plan
pnpm verify:fast(lint + 764 frontend / 710 slides tests) greenverify:self(all builds + entropy) greencrosshairon every shape arm; resets to''on Select / ESC.[74, 92, 121](accent1 × 0.4) over[32, 33, 36]slide bg; clears onmouseleave, ESC, Select, and on shape-kind switch.🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Bug Fixes