Skip to content

Draw the novice feedback as a straight line, above the menu - #238

Merged
QuentinRoy merged 5 commits into
mainfrom
claude/implement-issue-237-60a686
Sep 4, 2026
Merged

Draw the novice feedback as a straight line, above the menu#238
QuentinRoy merged 5 commits into
mainfrom
claude/implement-issue-237-60a686

Conversation

@QuentinRoy

@QuentinRoy QuentinRoy commented Sep 4, 2026

Copy link
Copy Markdown
Owner

Dwelling to open the novice menu and then dragging produced two wrong things at once. The line from the menu center followed every wobble of the pointer's path instead of pointing straight at it, so a slow or curved drag left a scribble across the menu. And the stroke was painted underneath the menu, hiding the round marker that shows where the current menu is anchored and cutting the line wherever it crossed an item.

Both are regressions in unreleased code, from separate causes, so they are fixed separately.

The novice move transition appended each new position to the upper stroke. Novice state now stores lastPosition and nothing else, and noviceUpperStroke builds the two-point segment from the menu center for the three places that read it: the layout projection, the submenu open that folds it into the lower stroke, and the completed-gesture stroke. The segment is a pair by type, so nothing has to assume it. Startup and expert are untouched and keep accumulating, because there the stroke is the mark being drawn. One consequence worth knowing: a menu nobody has moved on yet reports its segment as its center twice, where the old array held one point. The line drawn is the same and the completed-gesture stroke is byte-identical.

The renderer creates its canvases once and keeps them, and neither they nor the menu set a paint order, so the layers ended up wherever append order left them. render now re-asserts the order it needs, lower stroke behind the menu and upper stroke in front of it, moving an element only when it is out of place. Giving the three layers z-index values was the alternative, and was rejected because it turns an internal invariant into styling a consumer can override. The rule is written down in the renderer and in menu.css so the next rewrite inherits it.

To check by hand, run yarn demo:dev, hold the pointer still to open the menu, then drag slowly along a curve. The line stays straight and the origin dot stays visible.

The tests sit at the two seams that already existed. The machine ones drive it with pointer inputs and assert on the layout and feedback announcements rather than its internal state, so the stroke representation stays free to change. The renderer ones render a view into a fresh parent and assert the sibling order under it, including after a submenu replaces the menu element, which is the path the stacking fix could regress.

No changeset: the public event set and payloads are untouched, and both defects live in code that has not shipped.

Fixes #237

🤖 Generated with Claude Code

QuentinRoy and others added 3 commits September 4, 2026 14:01
The novice move transition appended each incoming position to the upper
stroke, so the feedback traced every wobble of the pointer's path from
the menu center instead of showing where the pointer is relative to the
menu. Replace the stroke with the two-point segment from the menu center
to the position instead. Startup and expert keep accumulating, since
their stroke is the mark being drawn.

The three consumers of the field read the same value, so they follow: the
layout projection, the submenu-open transition that folds the upper
stroke into the lower one, and the termination context that builds the
completed-gesture stroke. The post-gesture trace is a straight polyline
again, one segment per menu level.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The renderer creates its canvases once and keeps them, and nothing else
sets a paint order, so the layers ended up wherever append order left
them. The lower stroke, which records movement made before the menu
opened, covered the menu at root depth, and opening a submenu recreated
the menu element and pushed both strokes behind it, hiding the origin
marker and cutting the line where it crossed the menu's item lines.

Re-assert the order after every render: the lower stroke behind the menu,
the upper stroke and its marker in front of it. An element only moves
when it is out of place. `StrokeCanvas` and `Menu` now expose their
element so the renderer can place them.

The alternative, giving the three layers explicit z-index values, would
turn an internal invariant into styling that consumers can override.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…t canvas helper

The renderer's stacking note and the menu stylesheet covered the two
stroke canvases but left out the completed-gesture feedback, which also
belongs in front of the menu.

`canvasContext` moves into the shared canvas fixture so the renderer
suite's layer check and `queryCanvasContext` unwrap a stubbed context the
same way, and `queryCanvasContext` now throws a named error rather than
calling through a missing canvas.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@changeset-bot

changeset-bot Bot commented Sep 4, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 30a2369

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@QuentinRoy QuentinRoy left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Standards

No documented-standard violations or actionable code smells found.

Spec

One P2 finding: completed-gesture feedback can fall behind a new menu opened before the trace expires. This leaves the stacking contract in #237 partially implemented; details are attached to renderer.ts. No other actionable spec mismatches or scope creep found.

Validation: all 47 tests in machine.test.ts and renderer.test.ts pass. An additional isolated DOM test reproduces the ordering failure: show completed feedback, start another gesture, advance 334 ms, and open its novice menu. The feedback canvas appears before the menu in sibling order, so it paints underneath it.

Standards: 0 findings. Spec: 1 finding; worst severity P2, feedback stacking across consecutive gestures.

Comment thread src/engine/renderer.ts Outdated
Comment on lines +200 to +202
The completed-gesture feedback belongs in front of the menu too, which
it gets for free: `showFeedback` only ever runs once the menu is gone,
and appends its own canvas.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

[P2] Keep surviving feedback above the next menu

Completed feedback lasts 1,000 ms by default. If another gesture opens a menu before that trace expires, the new menu is appended after the feedback canvas. restack() only reorders the upper and lower strokes, so the completed trace paints underneath the new menu. Calling showFeedback() after the previous menu closes does not preserve the order across gestures.

Issue #237 requires "lower stroke behind the menu, upper stroke and completed-gesture feedback in front of it." Include surviving feedback canvases when maintaining that order, and add a DOM regression test that shows feedback before opening another menu. An isolated test with a 334 ms delay reproduces the failure with the default feedback duration.

QuentinRoy and others added 2 commits September 4, 2026 14:16
Novice state stored the upper stroke as an array that only ever held the
menu center and the pointer, which the machine had to rebuild on every
move and every consumer had to trust was exactly those two points. Store
`lastPosition` instead and let `noviceUpperStroke` build the segment for
the three places that read it: the layout projection, the submenu open
that folds it into the lower stroke, and the completed-gesture stroke.
The return type is a pair, so the shape is stated rather than assumed.

A menu that has not been moved on yet now reports its segment as its
center twice, where the old array held a single point. The line drawn is
the same, and the completed-gesture stroke is unchanged, since it already
repeated each center where one level's segment met the next.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A completed-gesture trace stays up for a second by default, so a menu
opened before it fades was appended after it and covered it. The render
that opens that menu now moves any trace still showing back in front of
it, after the upper stroke, so a live gesture draws over a fading one.

`GestureFeedback` exposes the canvases still showing, the same way the
stroke canvases and the menu expose their element.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@QuentinRoy
QuentinRoy enabled auto-merge (squash) September 4, 2026 12:21
@QuentinRoy
QuentinRoy merged commit 9c2f494 into main Sep 4, 2026
5 checks passed
@QuentinRoy
QuentinRoy deleted the claude/implement-issue-237-60a686 branch September 4, 2026 12:23
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.

Novice feedback draws the gesture path instead of the straight line, and stacks under the menu

1 participant