Select precise shape and add connector hit-test#266
Conversation
📝 WalkthroughWalkthroughThis PR implements geometry-accurate hit-testing for slide editor elements: shapes use Path2D fill/stroke checks and connectors use distance-to-segment tests, with zoom-scaled tolerance, editor integration, test-canvas shim support, and updated unit tests. ChangesPrecise Shape Hit-Testing
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 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 docstrings
🧪 Generate unit tests (beta)
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 |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 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.
Inline comments:
In `@packages/slides/src/view/editor/element-hit.ts`:
- Around line 91-97: The action-button hit test is using
containsPointPadded(frame, px, py, tol) so clicks in the padded area select
buttons; move the isActionButton(el.data.kind) check before the padded test and
perform a strict bbox test for action buttons (call the non-padded containsPoint
variant or directly test px/py against frame coordinates) instead of using tol.
Update the branch around containsPointPadded, isActionButton, and the
frame/px/py check so action buttons use the strict bbox-only path.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 3e56695f-5686-4156-bafc-d1629458fc01
📒 Files selected for processing (9)
docs/tasks/README.mddocs/tasks/active/20260519-slides-precise-shape-hit-test-todo.mdpackages/slides/src/view/canvas/shape-renderer.tspackages/slides/src/view/canvas/test-canvas-env.tspackages/slides/src/view/editor/editor.tspackages/slides/src/view/editor/element-hit.tspackages/slides/src/view/editor/interactions/select.tspackages/slides/test/view/editor/connector-hit.test.tspackages/slides/test/view/editor/interactions/select.test.ts
Clicking the empty corner of an ellipse, diamond, or other non- rectangular shape used to select it because `selectAt` only tested the axis-aligned bounding rect. The same bbox made long diagonal connectors painful to dismiss — a thin line claimed a huge area behind it. Route filled shapes through `isPointInPath` against the registered `PATH_BUILDERS` Path2D (in element-local coords, with rotation + flipH/flipV inverted), and route connectors through distance-to- segment with a `stroke.width / 2 + tolerance` threshold. Tolerance defaults to 6 logical pixels and the editor divides it by the current zoom so the click feel stays at ~6 viewport pixels. Stroke-only shapes (no fill, plus `OPEN_PATH_KINDS` brackets/braces) keep bbox semantics for now — `isPointInPath` on their auto-closed outline would treat the C-shape's interior as a hit. Stroke-distance for those is deferred (see task doc). Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Heart and smileyFace clicks just outside the fill polygon — but on the visibly-rendered outline — missed under v1. The renderer fills the polyline interior and then strokes with `lineJoin: 'round'`, so the visible boundary sits ~1-2 px outside `isPointInPath`'s answer. Brackets / braces (OPEN_PATH_KINDS) and unfilled outlines had it worse — v1 fell back to the full bbox, so empty regions inside the C-shape's bounding rect still selected. After `isPointInPath`, also run `isPointInStroke` with `lineWidth = stroke.width + 2 * tolerance` (default 12 px band) against the same Path2D. Stroke-only shapes use this as their primary test instead of bbox. A shape with neither fill nor stroke renders nothing and is now explicitly not clickable. `test-canvas-env.ts` got an `isPointInStroke` shim — distance-to- segment for subpath polylines, 32-sample polyline distance for ellipse ops, 4-edge distance for rect ops — exposed via both the jsdom prototype patch and the explicit `createTestCanvas` factory. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Rebase folded in main's depth-aware `hitTestSlide` (added with the group / ungroup work in #263 and reused by the connector endpoint drag fix in #265). The integration commit reroutes that function through the new precise `hitTestElement`, which two existing tests now need: - `hit-test-elements.test.ts` — pass a `ctx` option to every `hitTestSlide` call, and give the shape factory a `fill` so the rect has a visible body for `isPointInPath` to land on. (The previous fixture relied on bbox-only hit-test which the precise test rejects for empty shapes.) - `editor.test.ts` body-drag-of-half-attached-connector — the click used to land in empty bbox space; move it onto the line's midpoint with the same drag delta so the connector still gets selected. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
9d15bb5 to
8a7f8af
Compare
Verification: verify:selfResult: ✅ PASS in 218.0s
Verification: verify:integrationResult: ✅ PASS |
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
CodeRabbit on PR #266 flagged that action-button selection went through `containsPointPadded` with the tolerance halo other shapes use for AA-fringe forgiveness. Action buttons have no path builder — the comment right next to the check already promised "Selection stays bbox-based" — so the halo doesn't widen anything visible and just makes the click region inconsistent with text / image (both strict bbox). Move the action-button branch above the padded test and route it through `containsPoint`. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Captures lessons from PR #269 (diagnosis-by-instrumentation, rotation-preserving refit math, ghost-pattern reuse, DOM ownership across renderOverlay rebuilds) and archives the three May 19 slides tasks (mobile-shell, mobile-toolbar-morph, precise-shape-hit-test) that shipped in PRs #266 / #268 but stayed in `active/` until now.
Summary
ctx.isPointInPathagainst the Path2D fromPATH_BUILDERS, with rotation +flipH/flipVinverted into element-local coords; then anisPointInStrokefallback (lineWidth = stroke.width + 2 * tolerance) catches AA-fringe + round-join clicks and is the primary test for stroke-only /OPEN_PATH_KINDSshapes.stroke.width / 2 + tolerance.test-canvas-env.tsgot anisPointInStrokeshim (distance-to-segment / 32-sample ellipse / 4-edge rect) on both the jsdom prototype patch andcreateTestCanvas.Why
Clicking the empty corners of an ellipse, diamond, star, etc. used to select them, and a long diagonal connector claimed a huge bbox area behind the line. After v1 (Path2D-only), heart and smileyFace still felt imprecise because the renderer's AA fringe +
lineJoin: roundextends 1–2 px outside the fill polygon — the v2 stroke band closes that gap.Test plan
pnpm --filter @wafflebase/slides test --run(1044/1044)pnpm verify:fastpnpm dev:Known limitations (non-blocking)
hitShapeis padded bytolerance, so at very low zoom (e.g. 10%) the reject window grows to ~60 logical px. Precise tests still narrow it down; in practice this matches the "feels-natural 6 viewport-px halo" the design targets.docs/design/slides/slides-shapes.mdmentionedctx.isPointInPath-based hit-test as a follow-up — that note is now stale. A design doc refresh (and a short hit-test section inslides.md) is deferred to a follow-up so this PR stays focused on the behavioral change. The new module is well-commented and the task doc (docs/tasks/active/20260519-slides-precise-shape-hit-test-todo.md) captures the design.🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Tests
Documentation