Skip to content

Sweep adjustment drag handles for remaining 24 shapes (P3-A.2)#226

Merged
hackerwins merged 12 commits into
mainfrom
slides-shapes-p3a2-sweep
May 12, 2026
Merged

Sweep adjustment drag handles for remaining 24 shapes (P3-A.2)#226
hackerwins merged 12 commits into
mainfrom
slides-shapes-p3a2-sweep

Conversation

@hackerwins

@hackerwins hackerwins commented May 12, 2026

Copy link
Copy Markdown
Collaborator

Summary

Completes the P3-A adjustment-UX workstream. PR #210 shipped yellow-diamond drag handles for the 9 pilot shapes (P3-A.1) and slides-shapes-p3a-adjustments.md deferred the remaining 24 shapes to a mechanical sweep — this is that sweep.

After merge, every P1/P2 shape with an ADJUSTMENT_SPECS entry (33 shapes total) renders with a draggable handle for each adjustment value, instead of being locked to defaults.

Sweep coverage (24 shapes)

  • Basic linear-x via linearTopEdgeHandle factory: triangle, parallelogram, trapezoid, hexagon, octagon, plus, pentagonArrow. roundRect retrofitted onto the same factory.
  • Basic linear-y / radial (inline): can, donut.
  • 5 directional arrows (inline 2-handle): rightArrow, leftArrow, upArrow, downArrow, leftRightArrow. Per-direction math differed enough that a factory wouldn't have saved code.
  • quadArrow (inline 3-handle): head length / head width / shaft thickness.
  • 3 callouts via pointTailHandle factory: wedgeRoundRectCallout (tail + corner radius), wedgeEllipseCallout, cloudCallout. Pilot's wedgeRectCallout retrofitted onto the same factory so corner-inset behaviour has a single source of truth.
  • 6 math equations (mostly inline): mathPlus, mathMinus, mathMultiply, mathEqual, mathDivide, mathNotEqual. mathPlus reuses the linear factory (geometrically identical to basic/plus).

P3-A.1 deferred follow-ups closed in T1

  • 8px corner inset guard on roundRect and wedgeRectCallout so boundary adjustments never paint under a corner resize handle (later generalised into insetAlongAxis, used by every new handle).
  • adjustmentLocalToWorld / adjustmentWorldToLocal extracted into interactions/adjustment.ts. The overlay's paint-side rotation and the editor's drag-side inverse rotation now share one source of truth, with a 30°-rotated star5 round-trip test as the regression guard.
  • radialStarHandle reads its star's AdjustmentSpec instead of hardcoding STAR_MIN/STAR_MAX.

New abstractions / plumbing

  • axisLabel?: string on AdjustmentSpec for multi-axis tooltips when the lastWord heuristic collides. After the sweep only mathNotEqual populates it ("Bar thickness" vs. "Slash thickness" collapse to "thickness" otherwise).
  • formatAdjustments moves from editor.ts to interactions/adjustment.ts so it's directly unit-testable.
  • shapes/index.test.ts grows two assertions: every ADJUSTMENT_SPECS entry has a matching ADJUSTMENT_HANDLES registration, and the two maps have the same size. P3-A.2 is the invariant boundary.

Visual baseline

  • New shapes-adjustments-sweep scenario (6×4 grid of the 24 sweep shapes at OOXML defaults). Pilot scenario untouched so its baseline approval from PR Add adjustment drag handles for 9 pilot slides shapes (P3-A.1) #210 carries forward.
  • Four new baseline PNGs (desktop/mobile × light/dark) generated via pnpm verify:browser:docker:update.

Test plan

  • pnpm verify:fast — green (full lint + every package's unit tests, ~764 tests)
  • pnpm --filter @wafflebase/slides test — green (130 test files / 528 tests; includes 24 new shape *.handles.test.ts smoke tests, factory round-trip tests, rotated-frame paint↔hit-test invariant, formatAdjustments axisLabel test, and the new registry consistency assertions)
  • pnpm verify:browser:docker:update — green with 4 new shapes-adjustments-sweep baselines + 4 modified composite full-harness baselines
  • Manual smoke in pnpm dev — author/edit each of the 24 sweep shapes, confirm the diamond drags, the tooltip shows readable values, and the live preview tracks the drag without lag

Notes

  • Lessons captured in docs/tasks/active/20260512-slides-shapes-p3a2-sweep-lessons.md. Highlights: factory consolidation matrix (when a shared factory earned its keep vs. when inline was clearer), mathMultiply rotation gotcha (SQRT1_2 falls out of the 45° rotation matrix), verify-visual-browser.mjs scenarioIds whitelist (NOT auto-discovered from slides-scenarios.tsx).
  • Pilot deferred limitations Precision issue with number type in coordinate system for over 720k rows #4 (thin per-star tests) and Add PR template #5 (module-level tooltip singleton) are unchanged and accepted; the sweep didn't sharpen either.

Out-of-scope follow-ups

  • P3-A.3 — optional popover number-input fallback for typed adjustment values.
  • P3-B — +50 shapes for Google Slides parity (banners, action buttons, more callouts). Will reuse linearTopEdgeHandle, pointTailHandle, insetAlongAxis.

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • New Features

    • Added interactive adjustment handles to 24 slide shape kinds, enabling drag-based property adjustments including arrow head/width, callout tail positions, and geometric modifications.
    • Expanded visual regression testing to verify shape adjustment behavior across browser profiles.
  • Tests

    • Added comprehensive unit test coverage for all shape adjustment handle implementations.
  • Documentation

    • Updated task tracking to reflect completion of the P3-A.2 shape adjustments phase.

Review Change Stack

hackerwins and others added 11 commits May 12, 2026 22:34
PR #210 (P3-A.1 pilot, adjustment drag handles for 9 pilot shapes)
landed but the task pair stayed in active/ because tasks-archive.mjs
gates archival on every todo checkbox being checked, and the pilot's
85 checkboxes were never flipped. Flipping them to match shipped
reality (PR #210 closed deferred items as accepted limitations in
the lessons doc) and running pnpm tasks:archive moves the pair into
archive/2026/05/ and rebuilds both task indexes.

P3-A.2 picks up where the pilot closed: register ADJUSTMENT_HANDLES
for the 24 remaining P1/P2 shapes (triangle, basic polygons, 8
arrows, 3 callouts, 6 equation shapes) that already declare
ADJUSTMENT_SPECS but currently render with default-only adjustments.
The plan's first task also closes the pilot's three deferred
limitations (8px corner inset guard, rotated paint test, STAR_MIN/
STAR_MAX duplication) so the sweep ships on a clean regression
baseline rather than dragging old debt forward.
Three carry-overs from the pilot lessons doc, landed first so each
later P3-A.2 shape commit ships on a clean regression baseline:

* 8px corner inset guard on roundRect and wedgeRectCallout
  `position` functions. A roundRect with adjustments=[0] previously
  painted the diamond at (0,0) — directly under the NW resize
  handle — and a callout whose tail landed at any frame corner did
  the same. The guards leave `apply` (data) unchanged while
  clamping `position` (paint) so the diamond stays at least 8px
  away from corners. Tiny degenerate frames (w<16) fall back to the
  raw position to avoid an inverted clamp. Wedge inset only fires
  when the tail is inside the frame; tails outside (the common
  callout case) keep their attached-to-tip position.

* Shared `adjustmentLocalToWorld` / `adjustmentWorldToLocal` in
  `interactions/adjustment.ts`. The overlay's paint-side rotation
  and the editor's drag-side inverse rotation were two structurally
  separate inline calculations that could silently drift. Both call
  sites now route through the same exported helpers, and a new
  round-trip test runs the star5 handle through paint→hit-test on a
  30°-rotated 200×100 frame so future divergence trips.

* `radialStarHandle` accepts its star's `AdjustmentSpec` rather
  than hardcoding STAR_MIN/STAR_MAX. The six stars are uniform
  today but the duplication would silently desync if a future star
  (or shape lifting the factory) declared a different max. Callers
  in star4..star10 pass their spec entry through.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Adds yellow-diamond drag handles for triangle, parallelogram,
trapezoid, hexagon, octagon, plus, and pentagonArrow. All seven
share a new linearTopEdgeHandle factory in shapes/handles.ts: each
shape supplies its own forward(adj, frame) → x and inverse(x, frame)
→ adj because the mappings genuinely differ — direct w-scaling for
triangle/parallelogram/trapezoid, min(w,h)-scaling for hexagon/
octagon, centered for plus, reversed for pentagonArrow. The factory
adds the spec-driven clamp on apply plus the 8px corner inset guard
on paint so boundary values never paint under a corner resize
handle.

roundRect is retrofitted onto the same factory, dropping the inline
inset logic from T1 so the corner-inset behavior has a single
source of truth.

Per the P3-A.1 lessons-doc recommendation, per-shape tests stay one
smoke case each ("registers a single handle on the top edge at the
expected position"); geometric coverage lives in handles.test.ts on
the factory itself (round-trip, inset, clamp, tiny-frame fallback,
spec-default fallback). plus and pentagonArrow get an extra round-
trip case each because their non-standard inverse math warrants
per-shape verification beyond the factory.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Two shapes that do not fit the linearTopEdgeHandle factory:

* `can`: the adjustment is the lid half-height (linear-y), so the
  diamond paints at (w/2, ry) on the lid line. As the user drags
  down, the lid grows taller. The y axis gets the same 8px inset as
  the x axis on top-edge handles — boundary adjustments would
  otherwise overlap the N/S resize handles.

* `donut`: the ring thickness `t = adj × min(w,h)` is *subtractive*
  (`innerRx = outerRx - t`) rather than the multiplicative ratio
  used by stars, so the `radialStarHandle` factory does not apply.
  The handle inlines its own forward/inverse and paints where the
  inner ring crosses the horizontal centre line on the right side
  (`w - t`, `h/2`). Dragging leftward thickens the ring.

The 8px inset logic that the linear factory built in for the seven
T2 shapes is now exported as `insetAlongAxis(coord, dim)` so these
two inline handles share the same guard. `linearTopEdgeHandle` is
refactored to call the same helper — single source of truth for the
corner inset behaviour.

Both shapes get smoke tests covering registration, the inset on
boundary values, and the inverse math (clamping past spec max).

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
right/left/up/down/leftRightArrow each get a head-length handle and
a head-width handle. The handles are inline per arrow rather than
shared through a factory because the geometry differs more than it
matches:

* axis direction varies (x for right/left/leftRight, y for up/down)
* head-length scaling differs (w for right/left, h for up/down,
  w/2 for leftRight whose two heads each take half the width)
* head-width scaling differs (h/2 vs w/2 depending on arrow axis)
* head start position differs (left side for right/leftRight, right
  side for left, top for up, bottom for down)

A `directionalArrowHandles(direction)` factory could collapse the
rotational symmetry between right and left, or up and down, but it
would need to take ~6 parameters to do so (axisStart, axisDir,
perpDir, lengthScale, widthScale, plus per-direction inverse math)
— the parameter list ends up the same size as the inline body.
Inline is clearer per-shape and easier to debug.

All handles share the existing insetAlongAxis helper for the 8px
corner inset on both axes, and pass through the non-controlled
adjustment index from startAdjustments per the AdjustmentHandle
contract. Head-width apply uses Math.abs around the centerline so
dragging either above or below grows the head symmetrically.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
quadArrow has three adjustments — head length, head width, shaft
thickness — all OOXML thousandths of min(w,h). The three diamonds
cluster around the TOP arrowhead, the closest 4-fold-symmetry axis
the user will visually associate with each adjustment:

  [0] head length   → (cx, head)            drag vertically
  [1] head width    → (cx + headHalf, head) drag horizontally
  [2] shaft         → (cx + shaft, cy)      drag horizontally

At defaults (all three = 22500) the head-width and shaft handles
share an x but live on different y rows so they remain visually
separable. Inline rather than factored: a 4-headed arrow has no
1-handle-controls-many-arrows reuse opportunity.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
wedgeEllipseCallout, cloudCallout, and wedgeRoundRectCallout all
share the same tail-tip encoding as wedgeRectCallout from the pilot
— adjustments [0]/[1] are signed thousandths of (w, h) around the
frame centre. The pilot's inline tail position math (including the
corner-inset guard from T1) is promoted into a new
pointTailHandle(specX, specY) factory in callouts/handles.ts, and
wedgeRectCallout is retrofitted to use it. The three new callouts
each register the same factory call.

wedgeRoundRectCallout adds a second handle for its corner-radius
adjustment (index 2). linearTopEdgeHandle from shapes/handles.ts is
extended with an `index?: number` parameter so it can drive a non-
zero index while passing the other adjustments through from
startAdjustments unchanged — the existing 7 single-adjustment
shapes keep working without changes (default index = 0).

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Closes the P3-A.2 sweep at 33/33 — every ADJUSTMENT_SPECS entry now
has a matching ADJUSTMENT_HANDLES registration.

* mathPlus reuses linearTopEdgeHandle with the same forward/inverse
  as basic/plus (the shape is geometrically identical; only the
  default thickness differs).

* mathMinus is a single horizontal bar — one linear-y handle at
  the top of the bar, dragging up grows thickness.

* mathMultiply is the rotated +; after 45° rotation the upper inner
  notch lands at (cx, cy - t * SQRT1_2), so the handle paints there
  and the inverse divides by SQRT1_2 to recover thickness.

* mathEqual takes two handles on the upper bar — top edge for bar
  thickness, bottom edge (= upper gap boundary) for gap.

* mathDivide adds three: bar top, top-dot right edge for radius,
  and the midpoint between bar top and top-dot bottom for gap.

* mathNotEqual repeats mathEqual's bar+gap pattern and adds a slash-
  thickness handle on the upper edge of the -45° slash. The slash
  thickness handle projects pointer movement onto the slash's
  perpendicular direction so dragging perpendicular to the slash
  changes slashT and parallel does not.

All handles use the start-adjustments pass-through pattern (spread
+ index assignment) so each handle drives exactly one index and
leaves the others unchanged. Per-shape smoke tests assert
registration count and the default-position math.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Two closures for the post-sweep state:

* `axisLabel?: string` on AdjustmentSpec — overrides the lastWord
  heuristic in formatAdjustments when the heuristic would collide.
  After P3-A.2 the only currently-colliding case is mathNotEqual,
  whose "Bar thickness" and "Slash thickness" would both collapse
  to "thickness"; explicit labels "bar" and "slash" disambiguate
  the tooltip. Single-axis specs and the other multi-axis shapes
  don't need it.

* formatAdjustments (with its lastWord helper) is moved out of
  editor.ts into interactions/adjustment.ts so it can be unit-
  tested directly. Three new test cases cover single-axis output,
  the lastWord fallback, and the axisLabel override.

* shapes/index.test.ts grows two assertions: every ADJUSTMENT_SPECS
  entry now has a matching ADJUSTMENT_HANDLES registration, and the
  two maps have the same size. The original "every HANDLES kind has
  a SPECS" assertion is the converse; this new direction is what
  P3-A.2 actually closes.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
The pilot 9 shapes have a visual baseline since PR #210; the 24
P3-A.2 sweep shapes (triangle, parallelogram, trapezoid, hexagon,
octagon, plus, pentagonArrow, can, donut, 5 directional arrows,
quadArrow, 3 wedge/cloud callouts, 6 math equation shapes) now
get their own `shapes-adjustments-sweep` scenario — a 6×4 grid at
OOXML defaults. Path-builder regressions on any of these shapes
will surface here.

The pilot scenario remains untouched so its baseline approval from
PR #210 carries forward. Adding a sibling scenario rather than
renaming avoids a baseline-rewrite on the pilot.

Two pieces of plumbing: the new scenario id is added to the
scenarioIds whitelist in verify-visual-browser.mjs (the harness
does NOT auto-discover scenarios from the .tsx); and the four
composite full-harness PNGs are updated because they include the
new section in their layout.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Captures scope-reconciliation surprises (donut isn't radial in the
star factory's sense; mathMultiply rotation needs SQRT1_2), the
factory consolidation matrix (linearTopEdgeHandle vs. inline arrows
vs. pointTailHandle), the visual-harness scenarioIds whitelist
gotcha, and the pre-commit hook output-buffer behaviour the
sweep tripped into. Also records what to lean on for P3-B.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
@coderabbitai

coderabbitai Bot commented May 12, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This PR implements interactive adjustment drag handles for 24 slide shape kinds (basic, arrows, callouts, and equations) by introducing shared handle factories, per-shape handle exports with tests, refactored editor coordinate utilities, registry registration, and visual verification infrastructure. The change spans 170+ modified files with ~1400 lines added.

Changes

Adjustment Handles Sweep Implementation

Layer / File(s) Summary
Shared handle factories, callout handles factory, and AdjustmentSpec update
packages/slides/src/view/canvas/shapes/handles.ts, packages/slides/src/view/canvas/shapes/callouts/handles.ts, packages/slides/src/view/canvas/shapes/builder.ts, packages/slides/src/view/canvas/shapes/handles.test.ts
Exports linearTopEdgeHandle and insetAlongAxis for top-edge diamond handles, pointTailHandle for callout tail positioning with corner-clip logic, and adds optional axisLabel: string to AdjustmentSpec for multi-axis tooltip disambiguation. Comprehensive unit tests verify position/apply round-trips across frame sizes and rotations.
Basic shape handles and tests (11 shapes)
packages/slides/src/view/canvas/shapes/basic/{triangle,parallelogram,trapezoid,hexagon,octagon,plus,can,donut,round-rect}.ts, basic/*.handles.test.ts
Each of 11 basic shapes now exports *_HANDLES using linearTopEdgeHandle factory (triangle, parallelogram, trapezoid, hexagon, octagon, plus) or inline position/apply logic (can, donut), with forward/inverse mappings tied to existing ADJUSTMENT_SPECS. Each includes dedicated unit test verifying handle count, position computation, and apply/round-trip behavior.
Arrow shape handles and tests (7 arrows)
packages/slides/src/view/canvas/shapes/arrows/{up,down,left,right,left-right,quad,pentagon}-arrow.ts, arrows/*.handles.test.ts
Six directional arrows (up/down/left/right/left-right) and quad-arrow export 2–3 handles using insetAlongAxis for handle placement and inline position/apply logic; pentagon-arrow uses linearTopEdgeHandle for notch position. All include unit tests validating handle geometry and clamping behavior per arrow's adjustment semantics.
Callout shape handles and tests (4 callouts)
packages/slides/src/view/canvas/shapes/callouts/{cloud,wedge-rect,wedge-ellipse,wedge-round-rect}-callout.ts, callouts/*.handles.test.ts
Cloud and wedge-variant callouts export handles via pointTailHandle (tail positioning with corner-clipping to avoid resize-handle overlap) plus linear handle for wedge-round-rect corner-radius. Tests expanded to cover tail placement at frame edges, corners, and inset-guard behavior.
Equation glyph handles and tests (6 glyphs)
packages/slides/src/view/canvas/shapes/equation/math-{plus,minus,divide,equal,multiply,not-equal}.ts, equation/*.handles.test.ts
Math operators export 1–3 handles mapping bar thickness, dot radius, gap, and slash adjustments to pointer movement. math-not-equal annotates bar/slash thickness specs with axisLabel: 'bar'/'slash' to resolve multi-axis collision. All include position/apply tests and inline clamping logic.
Star handle signature update and 7 star modules
packages/slides/src/view/canvas/shapes/stars/handles.ts, packages/slides/src/view/canvas/shapes/stars/{star4,star5,star6,star7,star8,star10}.ts
Updated radialStarHandle to accept AdjustmentSpec parameter for spec-driven min/max clamping instead of hard-coded constants. All 7 star modules now pass STAR_*_ADJUSTMENTS[0] into radialStarHandle for consistent dynamic behavior.
Editor coordinate utilities and refactored drag interaction
packages/slides/src/view/editor/interactions/adjustment.ts, packages/slides/src/view/editor/editor.ts, packages/slides/src/view/editor/overlay.ts, interactions/adjustment.test.ts
New RotatedFrame type and adjustmentLocalToWorld/adjustmentWorldToLocal coordinate conversion utilities centralize rotated-element transformation logic. New formatAdjustments helper produces drag-tooltip strings with multi-axis label support via axisLabel or spec name fallback. Refactored editor drag handler and overlay rendering to use shared utilities; removed inline rotation math. Comprehensive tests cover identity transforms, 30°/90° rotations, and label-selection behavior.
Shape registry registration and consistency tests
packages/slides/src/view/canvas/shapes/index.ts, packages/slides/src/view/canvas/shapes/index.test.ts
Updated ADJUSTMENT_HANDLES map to import and register *_HANDLES exports for all 24 covered shape kinds. Added registry tests asserting every ADJUSTMENT_SPECS key has a matching ADJUSTMENT_HANDLES entry and verifying handle/spec count parity.
Visual verification harness and shapes-adjustments-sweep scenario
packages/frontend/src/app/harness/visual/slides-scenarios.tsx, packages/frontend/scripts/verify-visual-browser.mjs
New makeAdjustmentsSweepDoc() builder renders a 6×4 grid of all 24 P3-A.2 sweep shapes with OOXML default adjustments. New SLIDES_SCENARIOS entry shapes-adjustments-sweep and verify-visual-browser.mjs scenario-list update enable visual regression baseline capture/comparison for the full sweep.
Task documentation and archive updates
docs/tasks/{README,active/20260512-*,archive/README,archive/2026/05/20260510-*}.md
New P3-A.2 sweep todo and lessons documents record implementation plan (T0–T11), scope reconciliation, factory consolidation choices, and operational insights. P3-A.1 pilot todo updated with step-by-step execution records (Tasks 0–14 marked complete). Archive index updated with pilot completion and task-count increments (171→172 archived, active→sweep).

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • wafflebase/wafflebase#210: Continuation of the pilot phase that introduced the foundational ADJUSTMENT_HANDLES registry, AdjustmentHandle type, and initial handle implementations for roundRect, chevron, wedgeRectCallout, and star shapes.
  • wafflebase/wafflebase#191: Related visual-harness changes affecting verify-visual-browser.mjs and slides-scenarios.tsx for scenario registration and baseline capture infrastructure.
  • wafflebase/wafflebase#205: Star-related changes where radialStarHandle signature updates and star module registrations overlap with prior handle infrastructure work.

Poem

🐰 A sweep of shapes now dance and play,
With handles dragged through night and day;
From arrows swift to curves so round,
Adjustment logic, strong and sound.
Twenty-four kinds now touch the air—
P3 complete with utmost care! 🎨

✨ 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-shapes-p3a2-sweep

@github-actions

github-actions Bot commented May 12, 2026

Copy link
Copy Markdown
Contributor

Verification: verify:self

Result: ✅ PASS in 181.8s

Lane Status Duration
sheets:build ✅ pass 13.1s
docs:build ✅ pass 12.5s
slides:build ✅ pass 10.1s
verify:fast ✅ pass 101.6s
frontend:build ✅ pass 18.6s
verify:frontend:chunks ✅ pass 0.3s
backend:build ✅ pass 4.8s
cli:build ✅ pass 2.1s
verify:entropy ✅ pass 18.7s

Verification: verify:integration

Result: ✅ PASS

@codecov

codecov Bot commented May 12, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@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

🧹 Nitpick comments (10)
packages/slides/src/view/canvas/shapes/equation/math-divide.handles.test.ts (1)

4-21: ⚡ Quick win

Consider adding apply() round-trip tests.

The test validates position() output but doesn't verify that apply() correctly updates adjustments when a handle is dragged. Round-trip tests (position → simulated drag → apply → verify adjustment values) would strengthen confidence in the handle behavior, especially for handle[2] which depends on the bar adjustment.

Example test structure
it('handle[0] apply updates bar thickness', () => {
  const initial = [23520, 5880, 11760];
  const size = { w: 200, h: 100 };
  // Simulate dragging bar handle upward (reduce bar thickness)
  const result = MATH_DIVIDE_HANDLES[0].apply(size, initial, { x: 100, y: 30 });
  expect(result[0]).toBeLessThan(initial[0]);
  expect(result[0]).toBeGreaterThanOrEqual(MATH_DIVIDE_ADJUSTMENTS[0].min);
});
🤖 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/canvas/shapes/equation/math-divide.handles.test.ts`
around lines 4 - 21, Add round-trip tests that exercise MATH_DIVIDE_HANDLES'
position() → simulated drag → apply() flow to ensure adjustments update
correctly; for each handle (especially MATH_DIVIDE_HANDLES[0], [1], and [2])
call position() to get the handle coords, simulate a drag target (e.g., move y
up/down or x left/right), invoke the handle's apply(size, initialAdjustments,
draggedPoint) and assert the returned adjustments changed in the expected
direction and remain within MATH_DIVIDE_ADJUSTMENTS[n].min/max bounds (and for
handle[2] include cases where bar adjustment from handle[0] affects its result).
Ensure tests reference the functions MATH_DIVIDE_HANDLES.position and
MATH_DIVIDE_HANDLES.apply and use the MATH_DIVIDE_ADJUSTMENTS constants for
assertions.
packages/slides/src/view/canvas/shapes/arrows/left-arrow.handles.test.ts (1)

4-11: ⚡ Quick win

Consider adding test coverage for apply logic and boundary conditions.

The test only verifies handle count and a single position call with default adjustments. Consider adding tests for:

  • The apply function's pointer-to-adjustment conversion and clamping
  • Boundary values (min/max from ARROW_ADJUSTMENTS)
  • Edge cases like zero dimensions or extreme pointer positions
🤖 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/canvas/shapes/arrows/left-arrow.handles.test.ts`
around lines 4 - 11, Add unit tests for LEFT_ARROW_HANDLES to cover the apply
logic and boundary conditions: write tests that call LEFT_ARROW_HANDLES[0].apply
(and other handles) with pointer vectors that map to min/max ARROW_ADJUSTMENTS
values and verify clamping, test pointer-to-adjustment conversion for typical
and extreme pointer positions (very large/small/negative), and include edge
cases like zero width or height to ensure position({w:0,h:0}, ...) and apply
behave correctly; reference LEFT_ARROW_HANDLES, its apply and position methods,
and ARROW_ADJUSTMENTS in your assertions.
packages/slides/src/view/canvas/shapes/equation/math-equal.handles.test.ts (1)

4-15: ⚡ Quick win

Add apply() and boundary clamp assertions for both handles.

This suite currently verifies only position(). The critical pointer→adjustment conversion path in apply() (including min/max clamping) is untested.

Proposed test additions
 describe('MATH_EQUAL_HANDLES', () => {
   it('registers thickness and gap handles', () => {
@@
     expect(p1.y).toBeCloseTo(44.12, 2);
   });
+
+  it('thickness apply updates index 0 and preserves index 1', () => {
+    const next = MATH_EQUAL_HANDLES[0].apply({ w: 200, h: 100 }, [23520, 11760], { x: 100, y: 0 });
+    expect(next).toEqual([44120, 11760]);
+  });
+
+  it('gap apply clamps to spec range', () => {
+    const hi = MATH_EQUAL_HANDLES[1].apply({ w: 200, h: 100 }, [23520, 11760], { x: 100, y: 0 });
+    const lo = MATH_EQUAL_HANDLES[1].apply({ w: 200, h: 100 }, [23520, 11760], { x: 100, y: 100 });
+    expect(hi).toEqual([23520, 50000]);
+    expect(lo).toEqual([23520, 0]);
+  });
 });

As per coding guidelines, **/*.{test,spec}.{js,ts,jsx,tsx}: Write tests for critical business logic and edge cases.

🤖 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/canvas/shapes/equation/math-equal.handles.test.ts`
around lines 4 - 15, Add tests for the handles' apply() behavior and boundary
clamping: for each handle in MATH_EQUAL_HANDLES, call its apply(pointer, size,
currentValues) with representative pointer positions (including
edge/out-of-bounds cases) and assert the returned adjustment matches expected
thickness/gap deltas and is clamped within allowed min/max; keep existing
position() assertions and add symmetrically for both handles (use
MATH_EQUAL_HANDLES[0].apply and MATH_EQUAL_HANDLES[1].apply, and verify
pointer→adjustment conversion and min/max enforcement).
packages/slides/src/view/canvas/shapes/basic/octagon.handles.test.ts (1)

4-12: ⚡ Quick win

Expand this suite to cover apply() inversion and clamp boundaries.

Right now it only checks position(). Add apply() assertions so drag behavior and min/max clamping are protected from regressions.

Proposed test additions
 describe('OCTAGON_HANDLES', () => {
   it('registers a single linear-x handle on the top edge', () => {
@@
     expect(p.y).toBe(0);
   });
+
+  it('apply inverts pointer.x back to the adjustment domain', () => {
+    const next = OCTAGON_HANDLES[0].apply({ w: 200, h: 100 }, [29289], { x: 29.289, y: 0 });
+    expect(next).toEqual([29289]);
+  });
+
+  it('apply clamps at spec min/max', () => {
+    const min = OCTAGON_HANDLES[0].apply({ w: 200, h: 100 }, [29289], { x: -50, y: 0 });
+    const max = OCTAGON_HANDLES[0].apply({ w: 200, h: 100 }, [29289], { x: 9999, y: 0 });
+    expect(min).toEqual([0]);
+    expect(max).toEqual([50000]);
+  });
 });

As per coding guidelines, **/*.{test,spec}.{js,ts,jsx,tsx}: Write tests for critical business logic and edge cases.

🤖 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/canvas/shapes/basic/octagon.handles.test.ts` around
lines 4 - 12, Extend the OCTAGON_HANDLES test to also verify the handle's
apply() is the inverse of position() and that applied values are clamped: for
OCTAGON_HANDLES[0] (use the same size {w:200,h:100} and initial cut array
[29289]) compute p = position(...), then call apply(..., { x: p.x + 10, y: p.y
}) and assert the returned cut array changed from 29289 in the expected
direction (i.e. increased) and that re-running position() with the new cut
yields an x close to p.x + 10; also call apply with extreme points (e.g. x < 0
and x > w) and assert the returned cut is clamped to the min/max allowed values
(0 and 100000 normalised as in this codebase) so out-of-bounds drags produce
boundary values.
packages/slides/src/view/canvas/shapes/basic/triangle.handles.test.ts (1)

5-9: ⚡ Quick win

Cover inverse behavior with a round-trip test.

This test only checks position(). Add apply(position(...)) round-trip to catch inverse-mapping regressions.

Proposed test addition
 describe('TRIANGLE_HANDLES', () => {
   it('registers a single linear-x handle on the top edge', () => {
     expect(TRIANGLE_HANDLES).toHaveLength(1);
     const p = TRIANGLE_HANDLES[0].position({ w: 200, h: 100 }, [50000]);
     expect(p).toEqual({ x: 100, y: 0 }); // apex centred at default
   });
+
+  it('round-trips position ↔ apply at default', () => {
+    const frame = { w: 200, h: 100 };
+    const start = [50000];
+    const p = TRIANGLE_HANDLES[0].position(frame, start);
+    const back = TRIANGLE_HANDLES[0].apply(frame, start, p);
+    expect(back[0]).toBeCloseTo(start[0], -1);
+  });
 });

As per coding guidelines **/*.{test,spec}.{js,ts,jsx,tsx}: Write tests for critical business logic and edge cases.

🤖 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/canvas/shapes/basic/triangle.handles.test.ts` around
lines 5 - 9, Add a round-trip assertion that verifies the handle's inverse
mapping: for the first handle in TRIANGLE_HANDLES call position with the same
shape ({ w: 200, h: 100 }) and seed ([50000]) to get p, then call
TRIANGLE_HANDLES[0].apply(p, { w: 200, h: 100 }) and assert the result equals
the original input used by position (e.g., [50000]); this will ensure
position(...) → apply(...) round-trip consistency and catch inverse-mapping
regressions.
packages/slides/src/view/canvas/shapes/basic/hexagon.handles.test.ts (1)

5-10: ⚡ Quick win

Add an inverse round-trip assertion for the handle.

This currently validates only position(); a break in apply()/inverse math would still pass. Add a position↔apply round-trip check in this suite.

Proposed test addition
 describe('HEXAGON_HANDLES', () => {
   it('registers a single linear-x handle on the top edge', () => {
     expect(HEXAGON_HANDLES).toHaveLength(1);
     // notch = 25% * min(200, 100) = 25
     const p = HEXAGON_HANDLES[0].position({ w: 200, h: 100 }, [25000]);
     expect(p).toEqual({ x: 25, y: 0 });
   });
+
+  it('round-trips position ↔ apply at default', () => {
+    const frame = { w: 200, h: 100 };
+    const start = [25000];
+    const p = HEXAGON_HANDLES[0].position(frame, start);
+    const back = HEXAGON_HANDLES[0].apply(frame, start, p);
+    expect(back[0]).toBeCloseTo(start[0], -1);
+  });
 });

As per coding guidelines **/*.{test,spec}.{js,ts,jsx,tsx}: Write tests for critical business logic and edge cases.

🤖 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/canvas/shapes/basic/hexagon.handles.test.ts` around
lines 5 - 10, The test only asserts HEXAGON_HANDLES[0].position(...) — add an
inverse round-trip assertion that verifies apply() reverses position(): capture
the handle param used (e.g. values = [25000]), compute p =
HEXAGON_HANDLES[0].position({ w:200,h:100 }, values), then call
HEXAGON_HANDLES[0].apply({ w:200,h:100 }, p) and expect the returned values to
equal the original values (or equal within a small tolerance if numeric scaling
occurs). Use the same handle symbol HEXAGON_HANDLES[0], the position and apply
methods, and include the round-trip expect in the same test case.
packages/slides/src/view/canvas/shapes/callouts/wedge-round-rect-callout.handles.test.ts (1)

9-28: ⚡ Quick win

Add direct coverage for the tail handle path.

Line 9 onward validates only the corner-radius handle (handles[1]). Please add one assertion for handles[0] position(...) and one apply(...) check so both registered handles have shape-level integration coverage.

Proposed test extension
 describe('WEDGE_ROUND_RECT_CALLOUT_HANDLES', () => {
@@
   it('corner-radius handle controls adjustments[2], leaving tail adjustments unchanged', () => {
@@
     expect(next[2]).toBe(30000);
   });
+
+  it('tail handle updates tail adjustments', () => {
+    const start = [-20833, 62500, 16667];
+    const p = WEDGE_ROUND_RECT_CALLOUT_HANDLES[0].position({ w: 200, h: 100 }, start);
+    expect(p).toBeDefined();
+
+    const next = WEDGE_ROUND_RECT_CALLOUT_HANDLES[0].apply(
+      { w: 200, h: 100 },
+      start,
+      { x: 150, y: 120 },
+    );
+    expect(next[0]).not.toBe(start[0]);
+    expect(next[1]).not.toBe(start[1]);
+    expect(next[2]).toBe(start[2]);
+  });
 });

As per coding guidelines, "Write tests for critical business logic and edge cases".

🤖 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/canvas/shapes/callouts/wedge-round-rect-callout.handles.test.ts`
around lines 9 - 28, Add explicit coverage for the tail handle (handles[0]) by
calling WEDGE_ROUND_RECT_CALLOUT_HANDLES[0].position(...) with the same input
used for handles[1] and asserting the returned x/y match the expected
tail-position values, then call WEDGE_ROUND_RECT_CALLOUT_HANDLES[0].apply(...)
with a dragged point and assert that the tail values (first two entries) change
as expected while the radius (third entry) remains unchanged; locate usages of
WEDGE_ROUND_RECT_CALLOUT_HANDLES[1].position/apply in the test and mirror them
for index 0, asserting the correct mutated array entries for the tail and
unchanged radius.
packages/slides/src/view/canvas/shapes/equation/math-multiply.handles.test.ts (1)

4-12: ⚡ Quick win

Add apply coverage for clamp and inverse behavior.

This suite only validates position; apply (the drag mutation path) should also be covered with at least one round-trip and min/max clamp case.

Proposed test additions
 describe('MATH_MULTIPLY_HANDLES', () => {
   it('registers a single arm-thickness handle on the rotated cross', () => {
@@
     expect(p.y).toBeCloseTo(50 - 23.52 * Math.SQRT1_2, 2);
   });
+
+  it('apply round-trips position for an in-range value', () => {
+    const handle = MATH_MULTIPLY_HANDLES[0];
+    const frame = { w: 200, h: 100 };
+    const start = [23520];
+    const p = handle.position(frame, start);
+    const next = handle.apply(frame, start, p);
+    expect(next[0]).toBeCloseTo(start[0], 0);
+  });
+
+  it('apply clamps when dragged far below center', () => {
+    const next = MATH_MULTIPLY_HANDLES[0].apply(
+      { w: 200, h: 100 },
+      [23520],
+      { x: 100, y: 1000 },
+    );
+    expect(next[0]).toBe(0);
+  });
 });

As per coding guidelines, **/*.{test,spec}.{js,ts,jsx,tsx}: Write tests for critical business logic and edge cases.

🤖 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/canvas/shapes/equation/math-multiply.handles.test.ts`
around lines 4 - 12, Add tests covering the handle's apply (drag) behavior for
MATH_MULTIPLY_HANDLES in addition to the existing position assertions: call the
handle's apply on MATH_MULTIPLY_HANDLES[0] with an initial args array (e.g.,
[23520]) and a delta that should round-trip (apply then re-calc position gives
same value), then add two separate tests that apply deltas pushing the parameter
below its minimum and above its maximum and assert the resulting value is
clamped to expected min/max; reference the handle via
MATH_MULTIPLY_HANDLES[0].apply(...) and verify by reading the mutated args
and/or recomputing position via MATH_MULTIPLY_HANDLES[0].position(...) to
confirm clamping and round-trip behavior.
packages/slides/src/view/canvas/shapes/equation/math-minus.handles.test.ts (1)

4-12: ⚡ Quick win

Cover the apply path in this suite.

Please add at least one inverse round-trip case and one clamp boundary case for MATH_MINUS_HANDLES[0].apply(...).

As per coding guidelines, **/*.{test,spec}.{js,ts,jsx,tsx}: Write tests for critical business logic and edge cases.

🤖 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/canvas/shapes/equation/math-minus.handles.test.ts`
around lines 4 - 12, Add tests that exercise the apply path of
MATH_MINUS_HANDLES[0]: (1) an inverse round-trip case — call
handler.position(...) to get a position, then pass that position through
MATH_MINUS_HANDLES[0].apply(...) and assert the returned values (e.g., the first
element of the values array) equal the original input within tolerance; and (2)
a clamp/boundary case — call .apply(...) with positions outside the allowed area
(e.g., beyond left/right/top/bottom or extreme y) and assert the returned value
is clamped to the min/max expected by the handler. Use the existing position
call to derive realistic inputs and reference MATH_MINUS_HANDLES[0].position and
MATH_MINUS_HANDLES[0].apply when locating code to change.
packages/slides/src/view/canvas/shapes/equation/math-not-equal.handles.test.ts (1)

4-16: ⚡ Quick win

Expand tests beyond slash-handle position.

Please add tests for apply (clamp + round-trip) and at least one check each for bar/gap handle positions to protect all three adjustment axes.

As per coding guidelines, **/*.{test,spec}.{js,ts,jsx,tsx}: Write tests for critical business logic and edge cases.

🤖 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/canvas/shapes/equation/math-not-equal.handles.test.ts`
around lines 4 - 16, Add tests to cover all three adjustment axes for
MATH_NOT_EQUAL_HANDLES: extend the existing test suite to (1) assert the bar
handle (index 0) position for a given size and values array, (2) assert the gap
handle (index 1) position similarly, and (3) test the slash handle's apply
method by calling its apply with out-of-range values to verify clamping and then
calling position to verify round-trip rounding behavior; reference the handles
via MATH_NOT_EQUAL_HANDLES[0], [1], [2], use the handle.apply function to check
clamping/round-trip, and use handle.position to assert expected x/y coordinates
(use toBeCloseTo for floating checks).
🤖 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/interactions/adjustment.ts`:
- Around line 98-101: The tooltip formatting currently calls
specs[...].format(v) using values that may be missing, causing "undefined" or
passing undefined into format; update both the single-spec branch (the if
(specs.length === 1) block) and the multi-spec branch (around the code at the
103-106 region) to first coalesce each value with its spec-level fallback (e.g.,
use v = values[i] ?? specs[i].default or specs[i].defaultValue) and then call
specs[i].format only with that concrete value (or fall back to String(fallback)
when no format exists); ensure you reference specs, values, and format so the
guard prevents calling format(undefined) and avoids emitting "undefined" in
tooltips.

---

Nitpick comments:
In `@packages/slides/src/view/canvas/shapes/arrows/left-arrow.handles.test.ts`:
- Around line 4-11: Add unit tests for LEFT_ARROW_HANDLES to cover the apply
logic and boundary conditions: write tests that call LEFT_ARROW_HANDLES[0].apply
(and other handles) with pointer vectors that map to min/max ARROW_ADJUSTMENTS
values and verify clamping, test pointer-to-adjustment conversion for typical
and extreme pointer positions (very large/small/negative), and include edge
cases like zero width or height to ensure position({w:0,h:0}, ...) and apply
behave correctly; reference LEFT_ARROW_HANDLES, its apply and position methods,
and ARROW_ADJUSTMENTS in your assertions.

In `@packages/slides/src/view/canvas/shapes/basic/hexagon.handles.test.ts`:
- Around line 5-10: The test only asserts HEXAGON_HANDLES[0].position(...) — add
an inverse round-trip assertion that verifies apply() reverses position():
capture the handle param used (e.g. values = [25000]), compute p =
HEXAGON_HANDLES[0].position({ w:200,h:100 }, values), then call
HEXAGON_HANDLES[0].apply({ w:200,h:100 }, p) and expect the returned values to
equal the original values (or equal within a small tolerance if numeric scaling
occurs). Use the same handle symbol HEXAGON_HANDLES[0], the position and apply
methods, and include the round-trip expect in the same test case.

In `@packages/slides/src/view/canvas/shapes/basic/octagon.handles.test.ts`:
- Around line 4-12: Extend the OCTAGON_HANDLES test to also verify the handle's
apply() is the inverse of position() and that applied values are clamped: for
OCTAGON_HANDLES[0] (use the same size {w:200,h:100} and initial cut array
[29289]) compute p = position(...), then call apply(..., { x: p.x + 10, y: p.y
}) and assert the returned cut array changed from 29289 in the expected
direction (i.e. increased) and that re-running position() with the new cut
yields an x close to p.x + 10; also call apply with extreme points (e.g. x < 0
and x > w) and assert the returned cut is clamped to the min/max allowed values
(0 and 100000 normalised as in this codebase) so out-of-bounds drags produce
boundary values.

In `@packages/slides/src/view/canvas/shapes/basic/triangle.handles.test.ts`:
- Around line 5-9: Add a round-trip assertion that verifies the handle's inverse
mapping: for the first handle in TRIANGLE_HANDLES call position with the same
shape ({ w: 200, h: 100 }) and seed ([50000]) to get p, then call
TRIANGLE_HANDLES[0].apply(p, { w: 200, h: 100 }) and assert the result equals
the original input used by position (e.g., [50000]); this will ensure
position(...) → apply(...) round-trip consistency and catch inverse-mapping
regressions.

In
`@packages/slides/src/view/canvas/shapes/callouts/wedge-round-rect-callout.handles.test.ts`:
- Around line 9-28: Add explicit coverage for the tail handle (handles[0]) by
calling WEDGE_ROUND_RECT_CALLOUT_HANDLES[0].position(...) with the same input
used for handles[1] and asserting the returned x/y match the expected
tail-position values, then call WEDGE_ROUND_RECT_CALLOUT_HANDLES[0].apply(...)
with a dragged point and assert that the tail values (first two entries) change
as expected while the radius (third entry) remains unchanged; locate usages of
WEDGE_ROUND_RECT_CALLOUT_HANDLES[1].position/apply in the test and mirror them
for index 0, asserting the correct mutated array entries for the tail and
unchanged radius.

In `@packages/slides/src/view/canvas/shapes/equation/math-divide.handles.test.ts`:
- Around line 4-21: Add round-trip tests that exercise MATH_DIVIDE_HANDLES'
position() → simulated drag → apply() flow to ensure adjustments update
correctly; for each handle (especially MATH_DIVIDE_HANDLES[0], [1], and [2])
call position() to get the handle coords, simulate a drag target (e.g., move y
up/down or x left/right), invoke the handle's apply(size, initialAdjustments,
draggedPoint) and assert the returned adjustments changed in the expected
direction and remain within MATH_DIVIDE_ADJUSTMENTS[n].min/max bounds (and for
handle[2] include cases where bar adjustment from handle[0] affects its result).
Ensure tests reference the functions MATH_DIVIDE_HANDLES.position and
MATH_DIVIDE_HANDLES.apply and use the MATH_DIVIDE_ADJUSTMENTS constants for
assertions.

In `@packages/slides/src/view/canvas/shapes/equation/math-equal.handles.test.ts`:
- Around line 4-15: Add tests for the handles' apply() behavior and boundary
clamping: for each handle in MATH_EQUAL_HANDLES, call its apply(pointer, size,
currentValues) with representative pointer positions (including
edge/out-of-bounds cases) and assert the returned adjustment matches expected
thickness/gap deltas and is clamped within allowed min/max; keep existing
position() assertions and add symmetrically for both handles (use
MATH_EQUAL_HANDLES[0].apply and MATH_EQUAL_HANDLES[1].apply, and verify
pointer→adjustment conversion and min/max enforcement).

In `@packages/slides/src/view/canvas/shapes/equation/math-minus.handles.test.ts`:
- Around line 4-12: Add tests that exercise the apply path of
MATH_MINUS_HANDLES[0]: (1) an inverse round-trip case — call
handler.position(...) to get a position, then pass that position through
MATH_MINUS_HANDLES[0].apply(...) and assert the returned values (e.g., the first
element of the values array) equal the original input within tolerance; and (2)
a clamp/boundary case — call .apply(...) with positions outside the allowed area
(e.g., beyond left/right/top/bottom or extreme y) and assert the returned value
is clamped to the min/max expected by the handler. Use the existing position
call to derive realistic inputs and reference MATH_MINUS_HANDLES[0].position and
MATH_MINUS_HANDLES[0].apply when locating code to change.

In
`@packages/slides/src/view/canvas/shapes/equation/math-multiply.handles.test.ts`:
- Around line 4-12: Add tests covering the handle's apply (drag) behavior for
MATH_MULTIPLY_HANDLES in addition to the existing position assertions: call the
handle's apply on MATH_MULTIPLY_HANDLES[0] with an initial args array (e.g.,
[23520]) and a delta that should round-trip (apply then re-calc position gives
same value), then add two separate tests that apply deltas pushing the parameter
below its minimum and above its maximum and assert the resulting value is
clamped to expected min/max; reference the handle via
MATH_MULTIPLY_HANDLES[0].apply(...) and verify by reading the mutated args
and/or recomputing position via MATH_MULTIPLY_HANDLES[0].position(...) to
confirm clamping and round-trip behavior.

In
`@packages/slides/src/view/canvas/shapes/equation/math-not-equal.handles.test.ts`:
- Around line 4-16: Add tests to cover all three adjustment axes for
MATH_NOT_EQUAL_HANDLES: extend the existing test suite to (1) assert the bar
handle (index 0) position for a given size and values array, (2) assert the gap
handle (index 1) position similarly, and (3) test the slash handle's apply
method by calling its apply with out-of-range values to verify clamping and then
calling position to verify round-trip rounding behavior; reference the handles
via MATH_NOT_EQUAL_HANDLES[0], [1], [2], use the handle.apply function to check
clamping/round-trip, and use handle.position to assert expected x/y coordinates
(use toBeCloseTo for floating checks).
🪄 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: ccd400d2-57e8-42af-a8de-28589e651498

📥 Commits

Reviewing files that changed from the base of the PR and between b430d59 and a481e3f.

⛔ Files ignored due to path filters (8)
  • packages/frontend/tests/visual/baselines/harness-visual.browser.desktop.dark.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.mobile.dark.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.mobile.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.shapes-adjustments-sweep.desktop.dark.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.shapes-adjustments-sweep.mobile.dark.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.shapes-adjustments-sweep.mobile.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.shapes-adjustments-sweep.png is excluded by !**/*.png
📒 Files selected for processing (77)
  • docs/tasks/README.md
  • docs/tasks/active/20260512-slides-shapes-p3a2-sweep-lessons.md
  • docs/tasks/active/20260512-slides-shapes-p3a2-sweep-todo.md
  • docs/tasks/archive/2026/05/20260510-slides-shapes-p3a-pilot-lessons.md
  • docs/tasks/archive/2026/05/20260510-slides-shapes-p3a-pilot-todo.md
  • docs/tasks/archive/README.md
  • packages/frontend/scripts/verify-visual-browser.mjs
  • packages/frontend/src/app/harness/visual/slides-scenarios.tsx
  • packages/slides/src/view/canvas/shapes/arrows/down-arrow.handles.test.ts
  • packages/slides/src/view/canvas/shapes/arrows/down-arrow.ts
  • packages/slides/src/view/canvas/shapes/arrows/left-arrow.handles.test.ts
  • packages/slides/src/view/canvas/shapes/arrows/left-arrow.ts
  • packages/slides/src/view/canvas/shapes/arrows/left-right-arrow.handles.test.ts
  • packages/slides/src/view/canvas/shapes/arrows/left-right-arrow.ts
  • packages/slides/src/view/canvas/shapes/arrows/pentagon-arrow.handles.test.ts
  • packages/slides/src/view/canvas/shapes/arrows/pentagon-arrow.ts
  • packages/slides/src/view/canvas/shapes/arrows/quad-arrow.handles.test.ts
  • packages/slides/src/view/canvas/shapes/arrows/quad-arrow.ts
  • packages/slides/src/view/canvas/shapes/arrows/right-arrow.handles.test.ts
  • packages/slides/src/view/canvas/shapes/arrows/right-arrow.ts
  • packages/slides/src/view/canvas/shapes/arrows/up-arrow.handles.test.ts
  • packages/slides/src/view/canvas/shapes/arrows/up-arrow.ts
  • packages/slides/src/view/canvas/shapes/basic/can.handles.test.ts
  • packages/slides/src/view/canvas/shapes/basic/can.ts
  • packages/slides/src/view/canvas/shapes/basic/donut.handles.test.ts
  • packages/slides/src/view/canvas/shapes/basic/donut.ts
  • packages/slides/src/view/canvas/shapes/basic/hexagon.handles.test.ts
  • packages/slides/src/view/canvas/shapes/basic/hexagon.ts
  • packages/slides/src/view/canvas/shapes/basic/octagon.handles.test.ts
  • packages/slides/src/view/canvas/shapes/basic/octagon.ts
  • packages/slides/src/view/canvas/shapes/basic/parallelogram.handles.test.ts
  • packages/slides/src/view/canvas/shapes/basic/parallelogram.ts
  • packages/slides/src/view/canvas/shapes/basic/plus.handles.test.ts
  • packages/slides/src/view/canvas/shapes/basic/plus.ts
  • packages/slides/src/view/canvas/shapes/basic/round-rect.handles.test.ts
  • packages/slides/src/view/canvas/shapes/basic/round-rect.ts
  • packages/slides/src/view/canvas/shapes/basic/trapezoid.handles.test.ts
  • packages/slides/src/view/canvas/shapes/basic/trapezoid.ts
  • packages/slides/src/view/canvas/shapes/basic/triangle.handles.test.ts
  • packages/slides/src/view/canvas/shapes/basic/triangle.ts
  • packages/slides/src/view/canvas/shapes/builder.ts
  • packages/slides/src/view/canvas/shapes/callouts/cloud-callout.handles.test.ts
  • packages/slides/src/view/canvas/shapes/callouts/cloud-callout.ts
  • packages/slides/src/view/canvas/shapes/callouts/handles.ts
  • packages/slides/src/view/canvas/shapes/callouts/wedge-ellipse-callout.handles.test.ts
  • packages/slides/src/view/canvas/shapes/callouts/wedge-ellipse-callout.ts
  • packages/slides/src/view/canvas/shapes/callouts/wedge-rect-callout.handles.test.ts
  • packages/slides/src/view/canvas/shapes/callouts/wedge-rect-callout.ts
  • packages/slides/src/view/canvas/shapes/callouts/wedge-round-rect-callout.handles.test.ts
  • packages/slides/src/view/canvas/shapes/callouts/wedge-round-rect-callout.ts
  • packages/slides/src/view/canvas/shapes/equation/math-divide.handles.test.ts
  • packages/slides/src/view/canvas/shapes/equation/math-divide.ts
  • packages/slides/src/view/canvas/shapes/equation/math-equal.handles.test.ts
  • packages/slides/src/view/canvas/shapes/equation/math-equal.ts
  • packages/slides/src/view/canvas/shapes/equation/math-minus.handles.test.ts
  • packages/slides/src/view/canvas/shapes/equation/math-minus.ts
  • packages/slides/src/view/canvas/shapes/equation/math-multiply.handles.test.ts
  • packages/slides/src/view/canvas/shapes/equation/math-multiply.ts
  • packages/slides/src/view/canvas/shapes/equation/math-not-equal.handles.test.ts
  • packages/slides/src/view/canvas/shapes/equation/math-not-equal.ts
  • packages/slides/src/view/canvas/shapes/equation/math-plus.handles.test.ts
  • packages/slides/src/view/canvas/shapes/equation/math-plus.ts
  • packages/slides/src/view/canvas/shapes/handles.test.ts
  • packages/slides/src/view/canvas/shapes/handles.ts
  • packages/slides/src/view/canvas/shapes/index.test.ts
  • packages/slides/src/view/canvas/shapes/index.ts
  • packages/slides/src/view/canvas/shapes/stars/handles.ts
  • packages/slides/src/view/canvas/shapes/stars/star10.ts
  • packages/slides/src/view/canvas/shapes/stars/star4.ts
  • packages/slides/src/view/canvas/shapes/stars/star5.ts
  • packages/slides/src/view/canvas/shapes/stars/star6.ts
  • packages/slides/src/view/canvas/shapes/stars/star7.ts
  • packages/slides/src/view/canvas/shapes/stars/star8.ts
  • packages/slides/src/view/editor/editor.ts
  • packages/slides/src/view/editor/interactions/adjustment.test.ts
  • packages/slides/src/view/editor/interactions/adjustment.ts
  • packages/slides/src/view/editor/overlay.ts

Comment thread packages/slides/src/view/editor/interactions/adjustment.ts
All P3-A.2 todo checkboxes flipped to done (the work landed across
the 10 task commits + lessons commit on this branch). pnpm
tasks:archive moves the todo/lessons pair into archive/2026/05/
and regenerates both task indexes.

slides-shapes-p3a-adjustments.md's "Out-of-scope follow-ups" table
strikes out the P3-A.2 row and notes PR #226 as the landing
reference, so future readers of the design doc see immediately
that the sweep is shipped rather than still pending.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
@hackerwins
hackerwins merged commit 0f6b56c into main May 12, 2026
1 check passed
@hackerwins
hackerwins deleted the slides-shapes-p3a2-sweep branch May 12, 2026 18:30
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