Skip to content

Add adjustment drag handles for 9 pilot slides shapes (P3-A.1)#210

Merged
hackerwins merged 18 commits into
mainfrom
slides-shapes-p3a-pilot
May 10, 2026
Merged

Add adjustment drag handles for 9 pilot slides shapes (P3-A.1)#210
hackerwins merged 18 commits into
mainfrom
slides-shapes-p3a-pilot

Conversation

@hackerwins

@hackerwins hackerwins commented May 10, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Adds yellow-diamond drag handles for 9 pilot slides shapes (6 stars + roundRect + chevron + wedgeRectCallout) covering all 4 adjustment axis types (radial, linear, point).
  • Introduces ADJUSTMENT_HANDLES registry parallel to PATH_BUILDERS / ADJUSTMENT_SPECS; P3-A.2 will register the remaining 24 shapes mechanically.
  • Reuses the existing DOM overlay (renderOverlay) and resize-drag pattern; one store.updateElementData per drag → one undo entry. No Yorkie schema migration; existing P1/P2 documents render bit-identical until the user drags a handle.

Spec: docs/design/slides/slides-shapes-p3a-adjustments.md
Plan + lessons: docs/tasks/active/20260510-slides-shapes-p3a-pilot-{todo,lessons}.md

Known limitations (deferred to P3-A.2 follow-ups)

  • No 8px inset guard for handles near corners — when roundRect.adjustments = [0] or callout tail lands at a corner, the yellow diamond visually overlaps the green NW resize handle. Hit priority is correct (adjust handle wins via DOM last-sibling), but the diamond is hidden behind the resize handle. Cosmetic; spec called for the inset.
  • No unit test for adjustment-handle paint position on a rotated frame. The forward (renderAdjustmentHandles) and inverse (worldToLocal in startAdjustmentDrag) transforms are mirror-symmetric and correct, but a regression in either would manifest as "handle drifts off the rotated shape." Visual harness scenario includes a rotated star (canvas-only — handles are DOM, not in the screenshot).

Test plan

  • Unit: 9 shape × handle test files PASS — pnpm --filter @wafflebase/slides test
  • Integration: editor drag end-to-end test PASS — single store update on >2px drag, zero updates below threshold, selection preserved
  • Visual: shapes-adjustments-pilot baseline regenerated and matches — pnpm verify:browser:docker
  • Verify: pnpm verify:fast and pnpm verify:self PASS
  • Smoke (please do before merge): pnpm dev → open a slides doc, insert a star5; verify yellow diamond on selection, drag → live shape change + tooltip with %, release → one undo entry; repeat for roundRect, chevron, wedgeRectCallout; rotate a shape and verify handle co-rotates and drag still works; multi-select two shapes and verify diamonds disappear.

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • New Features
    • Added draggable adjustment handles for shape editing. Users can now drag interactive handles on rounded rectangles, chevrons, stars (4, 5, 6, 7, 8, and 10-pointed variants), and rectangle callout shapes to customize their appearance. Hold Shift while dragging to snap adjustments to defaults. Adjustment tooltips appear during interaction.

Review Change Stack

hackerwins and others added 18 commits May 11, 2026 04:04
P2 deferred the adjustments UX to P3 around Google Slides' canonical
yellow-diamond drag-handle pattern. P3-A.1 (pilot) covers 9 shapes
spanning all 4 axis types — radial (6 stars), linear (roundRect,
chevron), point (wedgeRectCallout) — to validate the
ADJUSTMENT_HANDLES registry abstraction before P3-A.2 sweeps the
remaining 24 shapes mechanically.
Foundation for P3-A.1: per-shape drag-handle metadata. Lives next
to AdjustmentSpec so the registry built in the next commit can
co-locate position/apply with the existing spec entries.
Empty Map<ShapeKind, AdjustmentHandle[]> with consistency tests
(every registered kind must also have a builder + spec). Subsequent
commits register the 9 pilot shapes one at a time so each entry
gets reviewed alongside its per-shape position/apply math.
Linear axis along the top edge — diamond at (r, 0) where r is the
rendered corner radius. Drag horizontally to change ratio.
Establishes the per-shape file pattern for the 8 remaining pilot
shapes: handle constant + matching .handles.test.ts + one .set()
call in shapes/index.ts.
Linear axis along the back V notch. Diamond sits at the inner V
tip (inset, h/2). Vertical pointer motion is ignored — drag is
purely horizontal because notch depth is one-dimensional.
Point axis — one handle controls both tail-x and tail-y adjustments
in a single drag. Demonstrates the multi-index handle case that
the AdjustmentHandle type was shaped to support; subsequent
2D-axis shapes follow the same pattern.
Shared factory parametrized by point count handles all 6 stars in
P3-A.1; star5 is the canonical first wired up. Math runs in
unit-ellipse space (pointer pre-divided by rx,ry) so non-square
frames behave consistently with the path builder's ellipse
inscription. Pointer projects onto the handle's ray, so
perpendicular motion does not change the ratio.
Mechanical follow-up to star5 + radialStarHandle helper. Each star
is a one-line export; tests verify position-in-bounds at default
and round-trip identity in the clamp range. Registry now covers
all 9 P3-A.1 pilot shapes.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Shared by the overlay renderer (default expansion when an element
has no data.adjustments yet) and the drag loop (Shift modifier
snap-to-default). All-or-nothing snap on multi-index handles
matches the user's expectation that Shift produces the canonical
shape, not a partially-snapped intermediate.
Yellow-diamond DOM elements with data-handle="adjust-N", appended
after resize handles so they sit on top in both DOM stacking and
hit-test order. Shows only on single-select; uses default
adjustments when data.adjustments is missing (P1-authored shapes).
Both rotated and axis-aligned branches paint via the existing
overlay container.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Existing handleHitTest already reads data-handle from DOM, so the
runtime change is just the type union. Editor pointerdown now
routes adjust-N to startAdjustmentDrag (stubbed; real impl lands
next commit). Adjustment handles take hit priority over resize
handles because they paint later in the DOM.
Mirrors startResize: mousemove paints a live preview of the shape
with the adjusted geometry, mouseup commits one store.updateElementData
call → one undo entry. 2px threshold prevents accidental commits on
click. Shift snaps every adjustment to its default when all components
are within 5% of range. Tooltip uses AdjustmentSpec.format for the
live readout.

Live preview strategy: Option A — new paintLiveAdjustments method that
builds a synthetic slide with data overrides (same forceRender+renderOverlay
pattern as paintLive), keeping handles in sync during drag. Chose Option A
because the renderer already has forceRender for exactly this purpose; it
required adding only one private method to editor.ts with zero changes to
slide-renderer.ts or any other renderer file.
Drag's paintLive path calls renderOverlay which does
overlay.innerHTML = '', detaching the tooltip singleton from the
DOM. The singleton ref then short-circuits subsequent show calls,
making the tooltip invisible from the second move event onward.
Guard with isConnected and re-append when detached. Test wipes
the overlay between two show calls and asserts re-attachment.
Verifies single store update on a real drag (>2px) and zero updates
when the threshold is not crossed. Catches regressions in the
hit-test wiring, threshold, and store.batch invocation in one
JSDOM-driven scenario.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Two-row 9-shape grid + one rotated star verifies handle painting
across all 4 axis types and through the rotation transform. Top
row shows default geometry; bottom row shows authored values that
visibly differ so a regression in apply/position math jumps out
in the diff. Baselines regenerated and all 152 profile targets pass.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Multi-axis tooltip labels were derived via .charAt(0).toLowerCase()
which collided for shapes whose adjustment names share a leading
word — wedgeRectCallout's "Tail x"/"Tail y" both resolved to "t",
producing the unhelpful "t: 75% / t: 100%". Switching to the last
whitespace-delimited word gives "x"/"y" while still working for
single-word names.

Also corrects the wedgeRectCallout position formula in the spec
doc to match the implementation (and the path builder it adjusts):
adjustments are signed thousandths relative to the frame center,
not absolute fractions.
Records what shipped (15 commits across 4 axis types' worth of
handles) and the deviations from the plan that came out at
implementation time — most notably the DOM-vs-Canvas overlay
(spec was wrong about handles being Canvas-painted), the
forceRender approach to live preview, and the tooltip-detach
bug + fix flagged by the spec-compliance reviewer.

Lessons file calls out two non-blocking follow-ups deferred to
P3-A.2: the 8px inset guard near corners (cosmetic at boundary
values) and a rotated-frame paint position unit test (math is
likely correct; missing the regression guard).
Origin/main extended OverlayOptions with required slideWidth +
slideHeight (for snap-guide drawing). The Task 9 overlay tests
and the Task 11 paintLiveAdjustments call passed only { scale },
which compiled before the rebase but not after. Pass SLIDE_WIDTH /
SLIDE_HEIGHT in both call sites.

Visual baselines regenerated against the rebased state — main's
slides-toolbar changes plus the new shapes-adjustments-pilot card
shifted layout for the harness root and toolbar PNGs.
@coderabbitai

coderabbitai Bot commented May 10, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This PR completes the Phase 3-A.1 adjustment-handles pilot for slides shapes, implementing interactive drag handles for shape geometry customization across nine pilot shapes (roundRect, chevron, wedgeRectCallout, and six star variants) with live preview, Shift snapping, overlay rendering, editor integration, and comprehensive tests.

Changes

Adjustment Handle Pilot Implementation

Layer / File(s) Summary
Type Definitions & Contracts
packages/slides/src/view/canvas/shapes/builder.ts
New AdjustmentHandle type defines per-shape handle contract with position (compute handle location from adjustments) and apply (convert pointer to adjustments). Re-export Point type.
Registry Definition & Consistency Tests
packages/slides/src/view/canvas/shapes/index.ts, index.test.ts
Create ADJUSTMENT_HANDLES: Map<ShapeKind, AdjustmentHandle[]> registry with imports for all shape handles; verify all registered kinds exist in both PATH_BUILDERS and ADJUSTMENT_SPECS.
Public Node-Layer Exports
packages/slides/src/node.ts
Re-export AdjustmentHandle type and ADJUSTMENT_HANDLES registry to public API alongside ADJUSTMENT_SPECS and PATH_BUILDERS.
RoundRect Linear-Axis Handle
packages/slides/src/view/canvas/shapes/basic/round-rect.ts, round-rect.handles.test.ts
Implement ROUND_RECT_HANDLES with corner-radius position/apply logic; tests verify geometry and round-trip identity across default/zero/max radius cases.
Chevron Linear-Axis Handle
packages/slides/src/view/canvas/shapes/arrows/chevron.ts, chevron.handles.test.ts
Implement CHEVRON_HANDLES with horizontal-only notch-depth adjustment; position computes inset, apply converts pointer x to clamped ratio.
WedgeRectCallout 2D-Point Handle
packages/slides/src/view/canvas/shapes/callouts/wedge-rect-callout.ts, wedge-rect-callout.handles.test.ts
Implement WEDGE_RECT_CALLOUT_HANDLES controlling two indices from single drag; tests verify center/bounds behavior and clamping.
Shared Radial Star Handle Factory
packages/slides/src/view/canvas/shapes/stars/handles.ts
Implement radialStarHandle(points) factory using unit-ellipse projection for N-pointed stars; computes ray-based position and applies pointer projection with ratio clamping.
Star5 Handle Implementation
packages/slides/src/view/canvas/shapes/stars/star5.ts, star5.handles.test.ts
Export STAR_5_HANDLES with 5-point radial logic; comprehensive tests cover geometry, center behavior, perpendicular projection, and round-trip identity.
Star4/6/7/8/10 Handle Implementations
packages/slides/src/view/canvas/shapes/stars/star{4,6,7,8,10}.ts, .handles.test.ts
Mechanically add radial handle exports and per-star tests for remaining pilot shapes, verifying position bounds and round-trip consistency.
Adjustment Interaction Utilities
packages/slides/src/view/editor/interactions/adjustment.ts, adjustment.test.ts
Add defaultAdjustmentsFor (expand ADJUSTMENT_SPECS to adjustment array) and snapToDefaults (all-or-nothing 5%-threshold snapping).
Adjustment Tooltip System
packages/slides/src/view/editor/adjustment-tooltip.ts, adjustment-tooltip.test.ts
Module-scoped DOM tooltip singleton with lazy attachment, isConnected guard to prevent detachment, and positioned via world coordinates.
Overlay Handle Rendering
packages/slides/src/view/editor/overlay.ts, overlay.test.ts
Render adjustment handles for single-selected shapes via renderAdjustmentHandles, computing local positions, transforming to world coords with rotation, and appending diamond DOM elements after resize handles; test presence/absence and z-order.
Hit-Testing for Adjustment Handles
packages/slides/src/view/editor/hit-test.ts
Add AdjustmentHandleKind = \adjust-${number}`type and updateHandleKindunion; extendisHandleKindvalidation to recognizeadjust-\d+` pattern.
Adjustment Drag Loop & State Updates
packages/slides/src/view/editor/editor.ts
Implement startAdjustmentDrag with world→local coordinate conversion, live adjustment application, Shift snapping, 2px threshold, live canvas repainting, tooltip display, and store commit via updateElementData on pointerup.
Editor Drag Integration Tests
packages/slides/src/view/editor/editor.test.ts
Verify >2px drags commit adjustments, sub-threshold drags remain unset, and selection is preserved after commit.
Visual Harness & Baseline
packages/frontend/scripts/verify-visual-browser.mjs, packages/frontend/src/app/harness/visual/slides-scenarios.tsx
Register shapes-adjustments-pilot scenario with 9 shapes arranged in two rows (defaults vs authored adjustments) plus rotated star5 for transform validation.
Design & Task Documentation
docs/design/slides/slides-shapes-p3a-adjustments.md, docs/tasks/active/20260510-slides-shapes-p3a-pilot-{lessons,todo}.md
Comprehensive design spec defining goals, architecture, registry concept, editor wiring, test strategy, data semantics, risks, and forward guidance; lessons document spec vs code discrepancies, tooltip singleton fix, star geometry approach, and live-preview strategy; detailed task breakdown with shipped-tasks table.

🐇 With diamond handles now in place, shapes dance and bend to mouse commands,
Each adjustment snaps and tweens through frames so grand,
A pilot light for future shape geometry lands! ✨

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • wafflebase/wafflebase#190: Introduced @wafflebase/slides foundation and adjustment specs that this PR builds upon with interactive handles.
  • wafflebase/wafflebase#205: Added star shape variants and adjustment specs that the radial handle factory operates on.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 39.13% 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.
Title check ✅ Passed The title concisely and accurately summarizes the main change: adding adjustment drag handles for 9 pilot shapes in the P3-A.1 phase. It clearly identifies the feature and the scope of shapes covered.
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.

✏️ 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-shapes-p3a-pilot

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.

@github-actions

github-actions Bot commented May 10, 2026

Copy link
Copy Markdown
Contributor

Verification: verify:self

Result: ✅ PASS in 179.7s

Lane Status Duration
sheets:build ✅ pass 13.3s
docs:build ✅ pass 12.3s
slides:build ✅ pass 9.8s
verify:fast ✅ pass 100.1s
frontend:build ✅ pass 17.9s
verify:frontend:chunks ✅ pass 0.3s
backend:build ✅ pass 4.9s
cli:build ✅ pass 2.1s
verify:entropy ✅ pass 19.0s

Verification: verify:integration

Result: ✅ PASS

@codecov

codecov Bot commented May 10, 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.

🧹 Nitpick comments (4)
packages/slides/src/view/canvas/shapes/stars/handles.ts (1)

4-5: 💤 Low value

Consider parameterizing STAR_MIN/STAR_MAX in future.

The hardcoded constants duplicate values from each star's ADJUSTMENT_SPEC. As noted in the lessons document, this is acceptable for the pilot since all 6 stars share the same range, but P3-A.2 should consider passing (min, max) to the factory or reading from ADJUSTMENT_SPECS.get(kind) to avoid silent desync if a future star shape has different bounds.

🤖 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/stars/handles.ts` around lines 4 - 5,
STAR_MIN and STAR_MAX are hardcoded duplicates of ranges defined on each star's
ADJUSTMENT_SPEC; update the star handle creation so the min/max are taken from
the star's ADJUSTMENT_SPEC (or from ADJUSTMENT_SPECS.get(kind)) or passed into
the factory instead of using the constants STAR_MIN/STAR_MAX to avoid silent
desync—locate usages in the handle factory and replace references to
STAR_MIN/STAR_MAX with the adjustmentSpec.min/adjustmentSpec.max (or injected
min/max parameters) for the functions that build star handles.
packages/slides/src/view/editor/overlay.ts (1)

216-246: ⚡ Quick win

Extract localToWorld to avoid duplicating the rotation math.

The closure at L231-235 is byte-for-byte identical to the one in renderRotatedHandles at L119-123. They both transform (lx, ly) from frame-local coords (origin = top-left, scale = frame.w × frame.h) into world coords via a rotation around the frame center. Future tweaks (e.g. a different anchor, or scaling for the rotated path) now have to be made in two places in lockstep.

Pulling it into a file-local helper keeps both call sites in sync and shrinks the new function meaningfully. This is per coding guidelines ("Keep functions small and focused on a single responsibility").

♻️ Proposed extraction
+function frameLocalToWorld(frame: Frame) {
+  const cx = frame.x + frame.w / 2;
+  const cy = frame.y + frame.h / 2;
+  const cos = Math.cos(frame.rotation);
+  const sin = Math.sin(frame.rotation);
+  return (lx: number, ly: number) => {
+    const dx = lx - frame.w / 2;
+    const dy = ly - frame.h / 2;
+    return { x: cx + dx * cos - dy * sin, y: cy + dx * sin + dy * cos };
+  };
+}

 function renderAdjustmentHandles(
   overlay: HTMLDivElement,
   el: Element,
   options: OverlayOptions,
 ): void {
   if (el.type !== 'shape') return;
   const handles = ADJUSTMENT_HANDLES.get(el.data.kind);
   if (!handles || handles.length === 0) return;

   const { scale } = options;
   const { frame } = el;
-  const cx = frame.x + frame.w / 2;
-  const cy = frame.y + frame.h / 2;
-  const cos = Math.cos(frame.rotation);
-  const sin = Math.sin(frame.rotation);
-  const localToWorld = (lx: number, ly: number) => {
-    const dx = lx - frame.w / 2;
-    const dy = ly - frame.h / 2;
-    return { x: cx + dx * cos - dy * sin, y: cy + dx * sin + dy * cos };
-  };
+  const localToWorld = frameLocalToWorld(frame);

And the same swap inside renderRotatedHandles at L119-123.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/slides/src/view/editor/overlay.ts` around lines 216 - 246, Extract
the duplicated rotation/translation closure into a file-local helper (e.g.,
transformLocalToWorld or localToWorldForFrame) and replace the inline
localToWorld closures in renderAdjustmentHandles and renderRotatedHandles with
calls to that helper; the helper should accept the Element frame (or frame.x,
frame.y, frame.w, frame.h, frame.rotation) and return either a function (lx, ly)
=> {x,y} or accept (frame, lx, ly) and compute cx, cy, cos, sin and the same
dx/dy math so behavior remains identical to the existing localToWorld used when
computing world coordinates for handles (used alongside
ADJUSTMENT_HANDLES/defaultAdjustmentsFor and makeAdjustmentHandle).
packages/slides/src/view/editor/editor.ts (2)

957-1012: ⚡ Quick win

Local↔world transform is computed three different ways inside one function.

Within startAdjustmentDrag you build cx/cy/cos/sin (L957-960), the worldToLocal closure (L962-968), and then again the inverse (local→world) inline for the tooltip anchor at L996-1005 — all from the same captured startEl.frame. The forward transform also matches the closures in overlay.ts (renderRotatedHandles / renderAdjustmentHandles). Three copies of the rotation math means a bugfix has to land in three sites to stay correct.

A small frameTransform(frame) helper returning { localToWorld, worldToLocal } makes the inline tooltip math at L996-1005 a one-liner and aligns this file with the overlay refactor suggested in overlay.ts. Per coding guidelines, this keeps startAdjustmentDrag focused on the interaction loop rather than the geometry kernel.

♻️ Sketch
-    const handleLocal = handle.position(
-      { w: startEl.frame.w, h: startEl.frame.h },
-      live,
-    );
-    const handleWorld = {
-      x:
-        cx +
-        (handleLocal.x - startEl.frame.w / 2) * cos -
-        (handleLocal.y - startEl.frame.h / 2) * sin,
-      y:
-        cy +
-        (handleLocal.x - startEl.frame.w / 2) * sin +
-        (handleLocal.y - startEl.frame.h / 2) * cos,
-    };
+    const handleLocal = handle.position(
+      { w: startEl.frame.w, h: startEl.frame.h },
+      live,
+    );
+    const handleWorld = localToWorld(handleLocal.x, handleLocal.y);

…where localToWorld is built alongside worldToLocal at the top of the function (or imported from a shared frameTransform helper used by overlay.ts too).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/slides/src/view/editor/editor.ts` around lines 957 - 1012, The
interaction computes rotation transforms in three places; extract a helper
frameTransform(frame) that returns { worldToLocal(wx,wy), localToWorld(lx,ly) }
based on startEl.frame (using cx/cy/cos/sin) and replace the inline worldToLocal
closure and the tooltip local→world math in startAdjustmentDrag with calls to
frameTransform.worldToLocal and frameTransform.localToWorld respectively (update
the tooltip call that feeds showAdjustmentTooltip to use
localToWorld(handleLocal)). Keep symbol references: startAdjustmentDrag,
startEl.frame, worldToLocal, localToWorld, handle.position,
paintLiveAdjustments, showAdjustmentTooltip; optionally reuse/import the same
helper in overlay.ts to keep geometry consistent.

1129-1149: ⚡ Quick win

lastWord will collapse labels for future multi-word specs that share a trailing token.

For the 9 pilot shapes the spec names produce unambiguous suffixes (e.g. x / y for wedgeRectCallout), so this is fine in P3-A.1. But the function is the canonical label formatter for every future adjustment registered in P3-A.2 (24 more shapes per PR objectives). The moment a shape ships specs like "Inner radius" + "Outer radius" or "First inset" + "Last inset", both legs render as radius: … / inset: … in the tooltip, and the user can't tell which value is which.

Two options that scale cleanly:

  • Disambiguate by taking the last two words when the trailing token isn't unique within specs, or
  • Let each spec declare a short label field and use that directly instead of slicing name.

The lighter-touch fix:

♻️ Disambiguate at format time
 function formatAdjustments(
   specs: readonly { name: string; format?: (v: number) => string }[],
   values: number[],
 ): string {
   if (specs.length === 1) {
     const v = values[0];
     return specs[0].format ? specs[0].format(v) : String(v);
   }
+  const labels = specs.map((s) => lastWord(s.name));
+  const hasCollision = new Set(labels).size !== labels.length;
   return specs
     .map((s, i) => {
-      const label = lastWord(s.name);
+      const label = hasCollision ? s.name.toLowerCase() : lastWord(s.name);
       const value = s.format ? s.format(values[i]) : String(values[i]);
       return `${label}: ${value}`;
     })
     .join(' / ');
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/slides/src/view/editor/editor.ts` around lines 1129 - 1149,
formatAdjustments currently calls lastWord(name) which collapses different spec
names that share a trailing token; change formatting so labels are
disambiguated: in formatAdjustments, for each spec use an explicit short label
if provided (e.g. spec.label) otherwise compute the label by inspecting all
specs' name values — if the last word is unique use that, but when duplicates
exist use the last two words (or more as needed) for that group; update or
replace lastWord to return either one- or two-word suffixes based on uniqueness
and ensure you still apply the existing s.format when present (refer to
formatAdjustments, lastWord, specs[i].name, spec.label and s.format to locate
the logic).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@packages/slides/src/view/canvas/shapes/stars/handles.ts`:
- Around line 4-5: STAR_MIN and STAR_MAX are hardcoded duplicates of ranges
defined on each star's ADJUSTMENT_SPEC; update the star handle creation so the
min/max are taken from the star's ADJUSTMENT_SPEC (or from
ADJUSTMENT_SPECS.get(kind)) or passed into the factory instead of using the
constants STAR_MIN/STAR_MAX to avoid silent desync—locate usages in the handle
factory and replace references to STAR_MIN/STAR_MAX with the
adjustmentSpec.min/adjustmentSpec.max (or injected min/max parameters) for the
functions that build star handles.

In `@packages/slides/src/view/editor/editor.ts`:
- Around line 957-1012: The interaction computes rotation transforms in three
places; extract a helper frameTransform(frame) that returns {
worldToLocal(wx,wy), localToWorld(lx,ly) } based on startEl.frame (using
cx/cy/cos/sin) and replace the inline worldToLocal closure and the tooltip
local→world math in startAdjustmentDrag with calls to
frameTransform.worldToLocal and frameTransform.localToWorld respectively (update
the tooltip call that feeds showAdjustmentTooltip to use
localToWorld(handleLocal)). Keep symbol references: startAdjustmentDrag,
startEl.frame, worldToLocal, localToWorld, handle.position,
paintLiveAdjustments, showAdjustmentTooltip; optionally reuse/import the same
helper in overlay.ts to keep geometry consistent.
- Around line 1129-1149: formatAdjustments currently calls lastWord(name) which
collapses different spec names that share a trailing token; change formatting so
labels are disambiguated: in formatAdjustments, for each spec use an explicit
short label if provided (e.g. spec.label) otherwise compute the label by
inspecting all specs' name values — if the last word is unique use that, but
when duplicates exist use the last two words (or more as needed) for that group;
update or replace lastWord to return either one- or two-word suffixes based on
uniqueness and ensure you still apply the existing s.format when present (refer
to formatAdjustments, lastWord, specs[i].name, spec.label and s.format to locate
the logic).

In `@packages/slides/src/view/editor/overlay.ts`:
- Around line 216-246: Extract the duplicated rotation/translation closure into
a file-local helper (e.g., transformLocalToWorld or localToWorldForFrame) and
replace the inline localToWorld closures in renderAdjustmentHandles and
renderRotatedHandles with calls to that helper; the helper should accept the
Element frame (or frame.x, frame.y, frame.w, frame.h, frame.rotation) and return
either a function (lx, ly) => {x,y} or accept (frame, lx, ly) and compute cx,
cy, cos, sin and the same dx/dy math so behavior remains identical to the
existing localToWorld used when computing world coordinates for handles (used
alongside ADJUSTMENT_HANDLES/defaultAdjustmentsFor and makeAdjustmentHandle).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f80b2290-9d7f-49f7-9b72-37c100c6de03

📥 Commits

Reviewing files that changed from the base of the PR and between d657c52 and 6cf5106.

⛔ 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-pilot.desktop.dark.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.shapes-adjustments-pilot.mobile.dark.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.shapes-adjustments-pilot.mobile.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.shapes-adjustments-pilot.png is excluded by !**/*.png
📒 Files selected for processing (38)
  • docs/design/README.md
  • docs/design/slides/slides-shapes-p3a-adjustments.md
  • docs/tasks/active/20260510-slides-shapes-p3a-pilot-lessons.md
  • docs/tasks/active/20260510-slides-shapes-p3a-pilot-todo.md
  • packages/frontend/scripts/verify-visual-browser.mjs
  • packages/frontend/src/app/harness/visual/slides-scenarios.tsx
  • packages/slides/src/node.ts
  • packages/slides/src/view/canvas/shapes/arrows/chevron.handles.test.ts
  • packages/slides/src/view/canvas/shapes/arrows/chevron.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/builder.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/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.handles.test.ts
  • packages/slides/src/view/canvas/shapes/stars/star10.ts
  • packages/slides/src/view/canvas/shapes/stars/star4.handles.test.ts
  • packages/slides/src/view/canvas/shapes/stars/star4.ts
  • packages/slides/src/view/canvas/shapes/stars/star5.handles.test.ts
  • packages/slides/src/view/canvas/shapes/stars/star5.ts
  • packages/slides/src/view/canvas/shapes/stars/star6.handles.test.ts
  • packages/slides/src/view/canvas/shapes/stars/star6.ts
  • packages/slides/src/view/canvas/shapes/stars/star7.handles.test.ts
  • packages/slides/src/view/canvas/shapes/stars/star7.ts
  • packages/slides/src/view/canvas/shapes/stars/star8.handles.test.ts
  • packages/slides/src/view/canvas/shapes/stars/star8.ts
  • packages/slides/src/view/editor/adjustment-tooltip.test.ts
  • packages/slides/src/view/editor/adjustment-tooltip.ts
  • packages/slides/src/view/editor/editor.test.ts
  • packages/slides/src/view/editor/editor.ts
  • packages/slides/src/view/editor/hit-test.ts
  • packages/slides/src/view/editor/interactions/adjustment.test.ts
  • packages/slides/src/view/editor/interactions/adjustment.ts
  • packages/slides/src/view/editor/overlay.test.ts
  • packages/slides/src/view/editor/overlay.ts

@hackerwins
hackerwins merged commit 43f875c into main May 10, 2026
4 checks passed
@hackerwins
hackerwins deleted the slides-shapes-p3a-pilot branch May 10, 2026 23:37
@hackerwins hackerwins mentioned this pull request May 11, 2026
6 tasks
hackerwins added a commit that referenced this pull request May 11, 2026
Adds @wafflebase/slides as a third surface alongside Sheets and Docs,
plus 53-shape library (Phase 1+2), adjustment handles for 9 pilot
shapes (P3-A.1), live snap guides, align/distribute toolbar, themed
authoring, and layout-change UI. Also ships Sheets cell comments
(Phase B), docs peer-avatar caret jump, yorkie-js-sdk 0.7.8 upgrade,
and CLI/REST API improvements (docs export imageFetcher, expired
session refresh, REST docs split). Minor bump because slides is a
new top-level package.

Highlights: #184 #185 #186 #187 #188 #189 #190 #191 #192 #197 #198
#201 #202 #203 #204 #205 #206 #207 #209 #210
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