Skip to content

Select precise shape and add connector hit-test#266

Merged
hackerwins merged 4 commits into
mainfrom
slides-precise-shape-hit-test
May 18, 2026
Merged

Select precise shape and add connector hit-test#266
hackerwins merged 4 commits into
mainfrom
slides-precise-shape-hit-test

Conversation

@hackerwins

@hackerwins hackerwins commented May 18, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Replace axis-aligned bbox hit-test for slides elements with a path-based precise hit-test.
  • Filled shapes route through ctx.isPointInPath against the Path2D from PATH_BUILDERS, with rotation + flipH/flipV inverted into element-local coords; then an isPointInStroke fallback (lineWidth = stroke.width + 2 * tolerance) catches AA-fringe + round-join clicks and is the primary test for stroke-only / OPEN_PATH_KINDS shapes.
  • Connectors use point-to-segment distance with stroke.width / 2 + tolerance.
  • Tolerance defaults to 6 logical pixels and the editor divides by zoom so the click feel stays at ~6 viewport pixels.
  • test-canvas-env.ts got an isPointInStroke shim (distance-to-segment / 32-sample ellipse / 4-edge rect) on both the jsdom prototype patch and createTestCanvas.

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: round extends 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:fast
  • Manual smoke in pnpm dev:
    • Ellipse / diamond / star — bbox corners no longer select; interior + edge clicks do.
    • Line / arrow connector — only clicks within ~6 viewport px of the line select.
    • Heart, smileyFace — clicks on the visible outline select; clicks well inside the bbox dip (above the heart's lobes) do not.
    • Brackets / braces — bbox interior no longer selects; clicks on the visible C-shape do.

Known limitations (non-blocking)

  • The bbox fast-reject in hitShape is padded by tolerance, 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.md mentioned ctx.isPointInPath-based hit-test as a follow-up — that note is now stale. A design doc refresh (and a short hit-test section in slides.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

    • Precise geometric hit-testing for element selection: shape- and connector-aware detection with zoom-aware tolerance, improving accuracy for ellipses, diamonds, rotated/flipped shapes, stroke-only outlines, and connector endpoints (applies to click, right-click, and double-click flows).
  • Tests

    • Expanded test coverage for connector hits and precise shape geometry (ellipse, diamond, stroked shapes, brackets, and selection interactions).
  • Documentation

    • Added a new active task/design entry describing the precise hit-testing plan and verification steps.

Review Change Stack

@coderabbitai

coderabbitai Bot commented May 18, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This 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.

Changes

Precise Shape Hit-Testing

Layer / File(s) Summary
Task specification and shape rendering exports
docs/tasks/README.md, docs/tasks/active/20260519-slides-precise-shape-hit-test-todo.md, packages/slides/src/view/canvas/shape-renderer.ts
Task index updated; design document specifies geometry-based detection for shapes (fill/stroke via path APIs) and connectors (distance-to-segment); EVENODD_KINDS and OPEN_PATH_KINDS constants exported for fill-rule logic.
Test canvas hit-testing infrastructure
packages/slides/src/view/canvas/test-canvas-env.ts
Test canvas extended with isPointInPath and new isPointInStroke method; stroke-distance helpers (distanceToSegment, opOutlineDistance, isPointInStrokeImpl) approximate browser canvas hit APIs using recorded path operations.
Core hit-testing API and element routing
packages/slides/src/view/editor/element-hit.ts
New module exports hitTestElement function and hit-test types (HitTestCtx, HitTestOptions); routes detection by element kind: connectors via distance-to-segment, shapes via padded-bbox fast-reject then path/stroke geometry, others via frame-based containment.
Hit-test slide integration
packages/slides/src/view/editor/hit-test-elements.ts
hitTestSlide now accepts HitTestSlideOptions, builds a global element lookup for connector endpoint resolution, and threads ctx/tolerance through recursive hit testing.
Selection layer hit-test integration
packages/slides/src/view/editor/interactions/select.ts
selectAt and topmostUnderPoint updated to accept canvas context and tolerance via SelectAtOptions; replaces frame-based point checks with hitTestElement calls; preserves shift-click toggle and multi-selection behavior.
Editor integration and zoom-scaled hit-testing
packages/slides/src/view/editor/editor.ts
Editor maintains reusable 2D hit context and zoom-adjusted tolerance; threads hit-test options through context menu, pointer-down selection, and double-click-to-edit flows.
Connector and selection test coverage
packages/slides/test/view/editor/connector-hit.test.ts, packages/slides/test/view/editor/interactions/select.test.ts, packages/slides/test/view/editor/hit-test-elements.test.ts, packages/slides/test/view/editor/editor.test.ts
New connector test suite validates distance-to-segment detection across tolerance and stroke-width scenarios; select and hit-test suites use canvas-backed fixtures (ellipse, diamond) and include new geometry-precision assertions.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • wafflebase/wafflebase#241: Related connector geometry and endpoint handling changes used as a foundation for connector hit-distance logic.
  • wafflebase/wafflebase#253: Prior work touching open-path shape handling (OPEN_PATH_KINDS) that this PR exports and relies on.

Poem

🐰 I nibble at pixels, ears up for a test,
Paths and strokes now find my quest.
Ellipses, hearts, connectors in line,
Tolerance scaled, every click feels fine.
A hop, a tap — selection so precise.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title accurately captures the main change: implementation of precise shape hit-testing and connector hit-test support, replacing bbox-based testing.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch slides-precise-shape-hit-test

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 4e9c310 and 9d15bb5.

📒 Files selected for processing (9)
  • docs/tasks/README.md
  • docs/tasks/active/20260519-slides-precise-shape-hit-test-todo.md
  • packages/slides/src/view/canvas/shape-renderer.ts
  • packages/slides/src/view/canvas/test-canvas-env.ts
  • packages/slides/src/view/editor/editor.ts
  • packages/slides/src/view/editor/element-hit.ts
  • packages/slides/src/view/editor/interactions/select.ts
  • packages/slides/test/view/editor/connector-hit.test.ts
  • packages/slides/test/view/editor/interactions/select.test.ts

Comment thread packages/slides/src/view/editor/element-hit.ts Outdated
hackerwins and others added 3 commits May 19, 2026 08:18
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]>
@hackerwins
hackerwins force-pushed the slides-precise-shape-hit-test branch from 9d15bb5 to 8a7f8af Compare May 18, 2026 23:27
@github-actions

github-actions Bot commented May 18, 2026

Copy link
Copy Markdown
Contributor

Verification: verify:self

Result: ✅ PASS in 218.0s

Lane Status Duration
sheets:build ✅ pass 14.3s
docs:build ✅ pass 12.4s
slides:build ✅ pass 13.9s
verify:fast ✅ pass 129.8s
frontend:build ✅ pass 18.6s
verify:frontend:chunks ✅ pass 0.3s
backend:build ✅ pass 4.9s
cli:build ✅ pass 2.2s
verify:entropy ✅ pass 21.4s

Verification: verify:integration

Result: ✅ PASS

@codecov

codecov Bot commented May 18, 2026

Copy link
Copy Markdown

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]>
@hackerwins hackerwins changed the title Slides: precise shape and connector hit-test Select precise shape and add connector hit-test May 18, 2026
@hackerwins
hackerwins merged commit 5b6197e into main May 18, 2026
1 check passed
@hackerwins
hackerwins deleted the slides-precise-shape-hit-test branch May 18, 2026 23:39
hackerwins added a commit that referenced this pull request May 20, 2026
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.
@hackerwins hackerwins mentioned this pull request May 24, 2026
2 tasks
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