Skip to content

Add endpoint-driven connectors with snap-on-draw to slides#241

Merged
hackerwins merged 27 commits into
mainfrom
feat/slides-connectors-base
May 15, 2026
Merged

Add endpoint-driven connectors with snap-on-draw to slides#241
hackerwins merged 27 commits into
mainfrom
feat/slides-connectors-base

Conversation

@hackerwins

@hackerwins hackerwins commented May 15, 2026

Copy link
Copy Markdown
Collaborator

Summary

Replace bbox-based kind: 'line'/'arrow' shapes in the slides package with
a new endpoint-driven Connector element type, matching Google Slides'
Line/Arrow tools.

  • New ConnectorElement (sibling of Shape/Text/Image): two Endpoints
    ({ kind: 'free', x, y } or { kind: 'attached', elementId, siteIndex }),
    routing field ('straight' only in PR1; elbow/curved arrive in PR2),
    per-endpoint arrowheads (filled triangle only in PR1; full set in PR3),
    derived frame bbox cache maintained by computeConnectorFrame.
  • Connection sites infrastructure: per-element ConnectionSite[] registry
    with default 4-cardinal (N/E/S/W mid-edge) for every kind (per-ShapeKind
    overrides arrive in PR2). siteWorldPos resolves a normalized local site
    through the source's frame transform.
  • Store layer: updateConnectorEndpoint, updateConnectorArrowheads,
    cascade sweep on source delete (attached → free at last-rendered world
    position), dependent-frame recompute on source move. Wired in both
    MemSlidesStore and YorkieSlidesStore. duplicateSlide rewrites
    attached connector ids to point at the copy's elements.
  • Editor UX: snap-on-draw insert (24px hover affordance, 12px snap),
    endpoint drag (free⇄attached transition), connection-points DOM overlay,
    ESC cancel, sub-threshold drag skips undo.
  • Toolbar: dedicated LinePicker dropdown right of ShapePicker so the
    endpoint-anchored insertion UX gets its own affordance.

Design doc: docs/design/slides/slides-connectors.md
Plan: docs/tasks/active/20260515-slides-connectors-pr1-todo.md
Lessons: docs/tasks/active/20260515-slides-connectors-pr1-lessons.md

PR2 (elbow + curved routing + per-shape sites) and PR3 (arrowhead variants

  • inspector) are explicit non-goals here.

Test plan

  • pnpm verify:fast green on every commit (804 slides tests, 239
    frontend tests, 764 docs tests, all packages green)
  • pnpm --filter @wafflebase/frontend exec tsc --noEmit green
  • Manual browser smoke (Line/Arrow draw, snap, attach, follow on move,
    cascade on delete, undo, no resize/rotate handles on connectors,
    LinePicker dropdown UX)
  • Reviewer: visual baseline regeneration via
    pnpm verify:browser:docker (shape catalog drops from 117→115; new
    Line dropdown adds a toolbar button — diffs expected)
  • Reviewer: peek at the lessons file's "Self code review" section for
    the 4 fixes that landed post-review and the 3 deferrals (hover-radius
    semantics, editor.ts size, read() migration cost) marked as
    follow-ups

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • New Features
    • Added ability to insert lines and arrows with configurable endpoints
    • Connector endpoints can attach to shapes or be positioned freely
    • Visual affordances show connection points on shapes during insertion
    • Support for dragging endpoints to adjust connector routing and attachment
    • Arrowhead styling options available for start and end points
    • Auto-snapping of endpoints to nearby connection sites on shapes

Review Change Stack

hackerwins and others added 25 commits May 15, 2026 18:52
The bbox-based line/arrow shapes can't represent attachment to other
elements, so adding connection points means redesigning the geometry.
Switch lines to an endpoint-driven Connector element type with a
per-shape connection-site registry, auto routing, and snap-on-draw
UX, matching Google Slides' four connector tools. Land in three PRs;
this commit only captures the design and the PR1 task plan.
Removing 'line' / 'arrow' from ShapeKind immediately breaks several
existing production sites (shape-renderer / shape-icon / insert) and
test files that reference them via string literals. The cleanup
belongs together with the call-site removals, so move it from Task 1
into Task 10 alongside the rest of the line/arrow shape teardown.
Task 1 is now purely additive — new Connector type, union extension,
and exhaustive-switch stubs.
Introduces the connector-insert mode for Slides — `'connector:line'`
and `'connector:arrow'` insert-mode keys, the pure-function snap
helpers (`findSnapTarget`, `snappedEndpoint`, `finalizeInsert`), and
the editor wiring that drags a connector ghost on the canvas until
mouseup.

The drag flow mirrors the existing shape-insert pattern: capture
mousedown, render a translucent live ghost via `forceRender`, commit
on mouseup, and revert to select mode. ESC cancels with capture-phase
pre-emption so the keyboard-rule's own Escape handler does not
double-fire. Sub-threshold drags are dropped by `finalizeInsert` to
avoid stray zero-length connectors.

Endpoints snap to the nearest connection site within 12 slide-logical
units; otherwise they land as `free` endpoints at the cursor. The
new `editor.isConnectorMode()` getter is exposed so Task 13's
connection-points overlay can subscribe through `onInsertModeChange`.

`buildInsertElement` is now typed `ShapeOrTextInsertKind` (no
connector branches reach it), and the new `ConnectorInsertKind` is
re-exported from `@wafflebase/slides` for toolbar wiring in Task 14.
`finalizeInsert` already short-circuits on drag distance below
`MIN_DRAG_DISTANCE` and returns null without calling
`store.addElement`. But the editor's `onUp` handler wrapped the call
in `store.batch(...)`, and `MemSlidesStore.batch` unconditionally
pushes an undo snapshot when entered at depth 0. A stray click in
connector-arm mode therefore recorded a no-op undo entry, so the
user's next Cmd+Z did nothing visible.

Move the `store.batch(...)` wrap inside `finalizeInsert`, around the
`store.addElement` call only. Now the threshold check and the
transaction boundary live together: sub-threshold drags skip both
the mutation and the batch, leaving the undo stack untouched. The
editor `onUp` becomes a straight call to `finalizeConnectorInsert`.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
`SHAPE_HOVER_RADIUS` (24) and `SITE_SNAP_RADIUS` (12) are documented
as screen-pixel constants (DPR-corrected), but `findSnapTarget`
compared `dx² + dy²` (slide-logical units squared) directly against
`SITE_SNAP_RADIUS²`, treating 12 as slide-logical. The overlay's
`renderConnectionPointsOverlay` already divides by zoom — so at
zoom=2 the highlight ring covered 6 logical units while the snap
window remained 12 logical units. The dot stopped highlighting but
the connector still snapped (and vice versa at zoom < 1).

Thread the editor's `this.scale()` through `findSnapTarget`,
`snappedEndpoint`, `buildConnectorInit`, `finalizeInsert`, and
`dragEndpoint` so the snap rule divides by zoom the same way the
highlight rule does. Update JSDoc on both constants to make the
screen-pixel intent explicit, and add regression tests at zoom=2
and zoom=0.5 that lock in the new semantics from both call paths.
The Lines category in the slides shape picker now arms the new
connector insert pipeline (connector:line / connector:arrow) instead
of the removed line/arrow ShapeKinds. The picker icon for connectors
is drawn inline (a diagonal stroke, plus a small head for arrows)
since they live outside PATH_BUILDERS.

Cleans up the harness visual scenarios that still listed line/arrow
in SHAPE_CATALOG / LINE_KINDS — these no longer typecheck against
the slides .d.ts. Connector-rendering visual coverage is deferred to
a follow-up PR.
PR1 shipped connectors against MemSlidesStore but pnpm dev uses the
Yorkie-backed store, which silently dropped every connector-specific
field on read (start, end, routing, arrowheads, stroke, elbowBend) and
omitted the two new store methods entirely — so the overlay crashed
with "Cannot read properties of undefined (reading 'kind')" as soon as
a user selected a connector.

This mirrors the MemSlidesStore connector implementation against the
Yorkie proxy model: read() now branches on `type === 'connector'` and
unwraps each field via `yorkieToPlain`; rebuildSlide/duplicateSlide/
reorderElement unwrap the whole element so no fields are lost; the
two new SlidesStore methods `updateConnectorEndpoint` and
`updateConnectorArrowheads` are added with the cached-frame recompute
on endpoint changes; removeElement/removeElements cascade-sweep
attached endpoints to free at their last world position (Q4 c1
policy); updateElementFrame refreshes dependent connector frames so
selection bbox / hit-testing tracks the moved source.

Also exports `ConnectorElement`, `Endpoint`, `ArrowheadStyle`,
`ConnectorRouting`, `ArrowheadKind`, `computeConnectorFrame`, and
`resolveEndpoint` from `@wafflebase/slides` so the frontend can
reuse the frame helpers instead of duplicating them.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Line insertion is endpoint-anchored (snap-to-shape) — fundamentally
different UX from shape drag-to-size — so burying the two connector
entries inside the Shapes picker confused the affordance during manual
smoke. Match the Google Slides toolbar where Line is a separate tool.

- Shape picker: drop the "Lines" category, tighten `CategoryEntry.kind`
  back to `ShapeKind`, remove the connector branch + `drawConnectorIcon`
  from `shape-picker.tsx`. Catalogue shrinks 117 → 115.
- New `<LinePicker />` dropdown: two entries (Line, Arrow) rendered as
  labelled rows with inline canvas previews; trigger uses the Tabler
  `IconLine` glyph and mirrors the Shape button's pressed visual.
- Toolbar wires both pickers side-by-side; `isLinePickerKind` splits
  the editor's `InsertKind` between the two `activeKind` props.
- Tests: shape-picker invariants updated (8 cats / 115 entries / no
  lines); new `line-picker.test.ts` asserts the two-entry catalogue
  and the `isLinePickerKind` guard. `.tsx` is still stubbed at test
  load so the testable surface lives in `line-picker-helpers.ts`.
Self code review found:
1. duplicateSlide silently corrupted attached connector references —
   regenerated element ids weren't rewritten on connector endpoints.
2. MIN_DRAG_DISTANCE was in slide-logical units while sibling radii
   were screen-pixel — inconsistent click-vs-drag at non-1 zoom.
3. addElement trusted caller frame, so future paste/import paths
   could persist a degenerate bbox on connectors.
4. detachConnectorsTargeting rebuilt the lookup Map per mutated
   connector when the outer Map was already valid (free endpoints
   don't consult it).
Record the four landed fixes (connector hygiene) and the three
deferred polish items (hover-radius semantics, editor.ts size,
read() migration cost) flagged during the senior self code review.
@coderabbitai

coderabbitai Bot commented May 15, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@hackerwins has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 28 minutes and 1 second before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3dfcaa86-6919-44e6-bffc-51484175a567

📥 Commits

Reviewing files that changed from the base of the PR and between a683c8d and 8c3f9b3.

⛔ Files ignored due to path filters (54)
  • 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-action-buttons.mobile.dark.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.shapes-action-buttons.mobile.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.shapes-adjustments-p3b-arrows.mobile.dark.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.shapes-adjustments-p3b-arrows.mobile.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.shapes-adjustments-p3b-basics.mobile.dark.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.shapes-adjustments-p3b-basics.mobile.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-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.slides-canvas-callout-tail.mobile.dark.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.slides-canvas-callout-tail.mobile.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.slides-canvas-default-dark.mobile.dark.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.slides-canvas-default-dark.mobile.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.slides-canvas-default-light.mobile.dark.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.slides-canvas-default-light.mobile.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.slides-canvas-donut-evenodd.mobile.dark.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.slides-canvas-donut-evenodd.mobile.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.slides-canvas-focus.mobile.dark.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.slides-canvas-focus.mobile.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.slides-canvas-layout-big-number.mobile.dark.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.slides-canvas-layout-big-number.mobile.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.slides-canvas-layout-section-header.mobile.dark.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.slides-canvas-layout-section-header.mobile.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.slides-canvas-layout-title-body.mobile.dark.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.slides-canvas-layout-title-body.mobile.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.slides-canvas-material.mobile.dark.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.slides-canvas-material.mobile.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.slides-canvas-shapes-catalog-dark.desktop.dark.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.slides-canvas-shapes-catalog-dark.mobile.dark.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.slides-canvas-shapes-catalog-dark.mobile.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.slides-canvas-shapes-catalog-dark.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.slides-canvas-shapes-catalog-light.desktop.dark.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.slides-canvas-shapes-catalog-light.mobile.dark.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.slides-canvas-shapes-catalog-light.mobile.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.slides-canvas-shapes-catalog-light.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.slides-canvas-shapes-catalog-material.desktop.dark.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.slides-canvas-shapes-catalog-material.mobile.dark.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.slides-canvas-shapes-catalog-material.mobile.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.slides-canvas-shapes-catalog-material.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.slides-canvas-streamline.mobile.dark.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.slides-canvas-streamline.mobile.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.slides-pickers.mobile.dark.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.slides-pickers.mobile.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.slides-theme-panel.mobile.dark.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.slides-theme-panel.mobile.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.slides-toolbar.desktop.dark.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.slides-toolbar.mobile.dark.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.slides-toolbar.mobile.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.slides-toolbar.png is excluded by !**/*.png
📒 Files selected for processing (10)
  • docs/design/slides/slides-connectors.md
  • docs/tasks/active/20260515-slides-connectors-pr1-todo.md
  • packages/frontend/src/app/slides/line-picker.tsx
  • packages/frontend/src/app/slides/yorkie-slides-store.ts
  • packages/frontend/src/types/slides-document.ts
  • packages/slides/src/store/memory.test.ts
  • packages/slides/src/store/memory.ts
  • packages/slides/src/view/editor/interactions/insert-connector.ts
  • packages/slides/src/view/editor/overlay.test.ts
  • packages/slides/src/view/editor/overlay.ts
📝 Walkthrough

Walkthrough

This PR implements PR1 of the phased connector architecture: adds endpoint-driven connector elements with free/attached endpoints, connection-site anchoring, straight routing, triangle arrowheads, store-layer CRUD with cascade detachment semantics, drag-to-place insertion with snapping and deadband, endpoint dragging with live snap feedback, overlay affordances, and toolbar UI. Removes legacy line/arrow shape kinds.

Changes

Connector Architecture & Implementation

Layer / File(s) Summary
Connector data model and public API types
packages/slides/src/model/connector.ts, packages/slides/src/model/connection-site.ts, packages/slides/src/model/element.ts, packages/slides/src/types/slides-document.ts, packages/slides/src/index.ts
New Endpoint, ConnectorElement, ArrowheadStyle, ConnectorRouting types define connector geometry, attachment, and styling. ConnectionSite anchors element attachment points. ShapeKind union removes 'line' / 'arrow'. Element union extended to include ConnectorElement.
Connection site anchor point system
packages/slides/src/view/canvas/connection-sites/defaults.ts, packages/slides/src/view/canvas/connection-sites/index.ts, packages/slides/src/view/canvas/connection-sites/connection-sites.test.ts
Exports 4-cardinal default connection sites and siteWorldPos transform that converts element-relative normalized coordinates into world space accounting for frame rotation and scale.
Connector frame computation and endpoint resolution
packages/slides/src/view/canvas/connector-frame.ts, packages/slides/src/view/canvas/connector-frame.test.ts
resolveEndpoint converts free/attached endpoints into world coordinates using connection-site lookup with fallback. computeConnectorFrame derives bounding box with stroke-width padding.
Connector path routing and geometry
packages/slides/src/view/canvas/routing.ts, packages/slides/src/view/canvas/routing.test.ts
Exports Point and SegmentPath types and routeStraight function for computing straight connector paths.
Canvas arrowhead rendering
packages/slides/src/view/canvas/arrowhead-renderer.ts, packages/slides/src/view/canvas/arrowhead-renderer.test.ts
Implements triangle arrowhead rendering with size variants positioned at endpoint with computed base width and backward offset. Non-triangle kinds accepted by type but produce no drawing.
Connector drawing and integration
packages/slides/src/view/canvas/connector-renderer.ts, packages/slides/src/view/canvas/connector-renderer.test.ts, packages/slides/src/view/canvas/element-renderer.ts, packages/slides/src/view/canvas/slide-renderer.ts
drawConnector resolves endpoints, strokes paths, draws aligned arrowheads. drawElement updated with elementsLookup parameter for attached-endpoint resolution. drawSlide builds per-render lookup.
Shape integration: remove line/arrow special cases
packages/slides/src/view/canvas/shape-icon.ts, packages/slides/src/view/canvas/shape-renderer.ts, packages/slides/src/view/canvas/shape-special.ts, packages/frontend/src/app/harness/visual/slides-scenarios.tsx, packages/slides/src/view/canvas/shape-icon.test.ts, packages/slides/src/view/canvas/shape-renderer.test.ts
Removes drawLine/drawArrow helpers and special-case rendering branches. Line/arrow kinds now unavailable in ShapeKind. Visual harness catalog updated (117 → 115 shapes).
Store interface for connector mutations
packages/slides/src/store/store.ts
Extends SlidesStore with updateConnectorEndpoint and updateConnectorArrowheads methods.
Memory store connector implementation
packages/slides/src/store/memory.ts
Implements connector mutations: endpoint/arrowhead updates with frame recomputation, defensive frame caching on insert, cascade endpoint detachment on element removal with world-position pinning, frame refresh when attached targets move, endpoint id rewriting on slide duplication.
Memory store connector test coverage
packages/slides/src/store/memory.test.ts
Comprehensive tests for connector persistence, frame updates, endpoint conversions, cascade detachment, arrowhead mutations, duplication id rewriting, and undo hygiene.
Yorkie store connector support
packages/frontend/src/app/slides/yorkie-slides-store.ts
Bridges Yorkie proxies via unwrapElement helper. read() reconstructs connectors with routing/endpoints/arrowheads. duplicateSlide remaps connector endpoint ids. Cascade detachment converts attached endpoints to free on element removal. Frame updates trigger connector dependents.
Connector insertion with snapping and deadband
packages/slides/src/view/editor/interactions/insert-connector.ts, packages/slides/src/view/editor/interactions/insert-connector.test.ts
findSnapTarget scans connection sites within zoom-adjusted radius. snappedEndpoint returns free or attached endpoint. buildConnectorInit precomputes frame for preview/commit consistency. finalizeInsert enforces minimum drag distance and commits via store.batch. Comprehensive tests cover snapping, deadband scaling, undo hygiene.
Shape insertion cleanup for connectors
packages/slides/src/view/editor/interactions/insert.ts, packages/slides/src/view/editor/interactions/insert.test.ts
New ShapeOrTextInsertKind type excludes connectors. Removes line/arrow from DEFAULT_INSERT_SIZE and style defaults. Removes line/arrow insertion tests.
Connector endpoint drag interactions
packages/slides/src/view/editor/interactions/connector-endpoint-drag.ts, packages/slides/src/view/editor/interactions/connector-endpoint-drag.test.ts, packages/slides/src/view/editor/hit-test.ts
dragEndpoint excludes connector self from snap candidates and commits via updateConnectorEndpoint. ConnectorEndpointHandle type added to HandleKind union. Tests verify snap/free behavior and zoom-consistent snapping radius.
Editor connector mode and interaction wiring
packages/slides/src/view/editor/editor.ts
Introduces ConnectorInsertKind mode, isConnectorMode() query, connectorCursor state for affordances. startConnectorInsert implements drag-to-place flow with live preview. startConnectorEndpointDrag snaps endpoint during drag and commits on mouseup. Overlay repaints include allElements and connectorAffordance.
Editor connector interaction tests
packages/slides/src/view/editor/editor.test.ts
Tests endpoint drag deadband: sub-threshold clicks don't create undo, above-threshold drags add exactly one undo entry.
Overlay connection-point affordances and endpoint handles
packages/slides/src/view/editor/overlay.ts, packages/slides/src/view/editor/overlay.test.ts
renderOverlay renders connection-site dots when affordance cursor is within zoom-adjusted hover radius, highlights sites within snap radius. Selected single connectors render only start/end endpoint handles at resolved world positions instead of resize/rotate.
LinePicker toolbar UI component
packages/frontend/src/app/slides/line-picker-helpers.ts, packages/frontend/src/app/slides/line-picker.tsx, packages/frontend/tests/app/slides/line-picker.test.ts
New LinePicker dropdown renders canvas-based line/arrow icons with strokes. LINE_PICKER_ENTRIES catalog, isLinePickerKind type guard. Tests validate catalogue and guard behavior.
Shape/line picker integration and UI wiring
packages/frontend/src/app/slides/shape-picker-helpers.ts, packages/frontend/src/app/slides/shape-picker.tsx, packages/frontend/src/app/slides/slides-formatting-toolbar.tsx
SlidesFormattingToolbar renders LinePicker alongside ShapePicker using isLinePickerKind guard. Doc comments updated to clarify connector exclusion from shape picker.
Shape picker catalogue test updates
packages/frontend/tests/app/slides/shape-picker.test.ts
Removes lines category expectation, adjusts total from 117 → 115, adds test asserting connector kind exclusion.
Design, task, and lessons documentation
docs/design/README.md, docs/design/slides/slides-connectors.md, docs/tasks/active/20260515-slides-connectors-pr1-todo.md, docs/tasks/active/20260515-slides-connectors-pr1-lessons.md
Complete design document, phased rollout plan, PR1 task checklist, and post-implementation lessons including common stumbles, self code review findings, and deferred work for PR2/PR3.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • wafflebase/wafflebase#239: Extends the insert-mode/ghost/overlay interaction machinery in editor.ts to add connector insert modes and endpoint dragging, building on prior shape/text insertion UX.
  • wafflebase/wafflebase#190: Removes legacy line/arrow shape kind support that was previously added in #190, replacing it with dedicated connector element architecture.

🐰 Endpoints dancing free,
Snapping smooth as morning dew,
Lines find their true form.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/slides-connectors-base

@github-actions

github-actions Bot commented May 15, 2026

Copy link
Copy Markdown
Contributor

Verification: verify:self

Result: ✅ PASS in 184.5s

Lane Status Duration
sheets:build ✅ pass 13.0s
docs:build ✅ pass 11.9s
slides:build ✅ pass 10.6s
verify:fast ✅ pass 105.9s
frontend:build ✅ pass 17.7s
verify:frontend:chunks ✅ pass 0.3s
backend:build ✅ pass 4.7s
cli:build ✅ pass 2.0s
verify:entropy ✅ pass 18.4s

Verification: verify:integration

Result: ✅ PASS

@codecov

codecov Bot commented May 15, 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: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
packages/slides/src/store/memory.ts (1)

258-280: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Reject direct updateElementFrame patches for connectors.

frame is derived cache for connectors, but this path still lets callers persist arbitrary frame patches onto a connector. That can leave selection/hit-testing out of sync with the actual endpoints until some later connector-specific mutation recomputes the cache. Either throw for connector targets here, or translate endpoints and then recompute the frame instead of merging the patch.

Suggested fix
   updateElementFrame(
     slideId: string, elementId: string, frame: Partial<Frame>,
   ): void {
     this.requireBatch();
     const slide = this.requireSlide(slideId);
     const e = slide.elements[this.requireElementIndex(slide, elementId)];
+    if (e.type === 'connector') {
+      throw new Error(
+        `Element ${elementId} is a connector; update its endpoints instead of its frame`,
+      );
+    }
     e.frame = { ...e.frame, ...frame };
     const lookup = this.elementsLookup(slideId);
     for (const el of slide.elements) {
🤖 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/store/memory.ts` around lines 258 - 280, The
updateElementFrame method currently allows callers to patch the frame of any
element, which lets connectors receive direct frame writes; change
updateElementFrame so it rejects (throw) when the target element is a connector
(detect via slide.elements[this.requireElementIndex(slide, elementId)].type ===
'connector') OR, alternatively, when the element is a connector translate the
supplied frame patch into endpoint updates and then recompute the connector
cache instead of merging: i.e., do not assign e.frame = {...} for connectors;
call this.elementsLookup(slideId) and computeConnectorFrame(connector, lookup)
to refresh the cached frame after applying endpoint changes (use requireSlide,
requireElementIndex to locate the element), and ensure callers get an error if
they attempt to directly mutate connector.frame.
packages/frontend/src/app/slides/yorkie-slides-store.ts (1)

752-768: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Block direct frame updates for connectors here too.

This has the same invariant hole as the memory store: updateElementFrame can still write arbitrary cached bbox values onto a connector even though connector geometry is endpoint-driven. Once that happens, Yorkie can persist stale hit-test/selection bounds until another connector-specific update repairs them.

Suggested fix
   updateElementFrame(
     slideId: string,
     elementId: string,
     frame: Partial<Frame>,
   ): void {
     this.requireBatch();
     this.doc.update((r) => {
       const s = r.slides.find((s) => s.id === slideId);
       if (!s) throw new Error(`Slide not found: ${slideId}`);
       const e = s.elements.find((e) => e.id === elementId);
       if (!e) throw new Error(`Element not found: ${elementId}`);
+      if (e.type === 'connector') {
+        throw new Error(
+          `Element ${elementId} is a connector; update its endpoints instead of its frame`,
+        );
+      }
       e.frame = { ...e.frame, ...frame };
       this.recomputeDependentConnectorFrames(s, elementId);
     });
   }
🤖 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/frontend/src/app/slides/yorkie-slides-store.ts` around lines 752 -
768, updateElementFrame must not write cached bbox values onto connector
elements; add a guard in updateElementFrame that detects connector elements
(e.g., check e.type === 'connector' or use the project’s isConnector predicate)
and skip merging/assigning into e.frame for those elements (do not perform
e.frame = { ...e.frame, ...frame } for connectors); keep the existing
recomputeDependentConnectorFrames call for non-connector moves, and for
connectors either return early or delegate to the connector-specific recompute
logic instead so connector geometry stays endpoint-driven.
🧹 Nitpick comments (3)
packages/frontend/src/app/slides/line-picker.tsx (1)

52-57: ⚡ Quick win

Consider using fill() for the arrowhead instead of stroke().

The comment at line 18 describes "a filled arrowhead," but line 57 uses ctx.stroke() instead of ctx.fill(). This renders the arrowhead as an outline rather than a solid filled triangle.

🎨 Proposed fix to fill the arrowhead
     ctx.lineTo(baseX + px * headHalf, baseY + py * headHalf);
     ctx.lineTo(baseX - px * headHalf, baseY - py * headHalf);
     ctx.closePath();
-    ctx.stroke();
+    ctx.fill();
🤖 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/frontend/src/app/slides/line-picker.tsx` around lines 52 - 57, The
arrowhead path is being stroked (ctx.stroke()) which produces an outline, but
the comment and intent call for a filled arrowhead: change the final call in the
arrowhead drawing block (the sequence starting with ctx.beginPath();
ctx.moveTo(...); ctx.lineTo(...); ctx.lineTo(...); ctx.closePath();) to use
ctx.fill() (or ctx.fill() followed by ctx.stroke() if you want an outline) and
ensure the desired color is set via ctx.fillStyle before calling ctx.fill().
packages/slides/src/view/editor/overlay.test.ts (1)

217-247: ⚡ Quick win

Type these connector fixtures directly instead of double-casting them.

as unknown as Element disables compile-time drift detection on the new connector shape, so these tests can keep compiling after a connector contract change with stale fixtures. Returning ConnectorElement from the helpers keeps the suite honest.

Suggested cleanup
-import type { Element, ShapeElement } from '../../model/element';
+import type { ConnectorElement } from '../../model/connector';
+import type { Element, ShapeElement } from '../../model/element';

-  function freeConnector(): Element {
+  function freeConnector(): ConnectorElement {
     return {
       id: 'c1',
       type: 'connector',
@@
-      frame: { x: 100, y: 100, w: 200, h: 0, rotation: 0 },
-    } as unknown as Element;
+      frame: { x: 100, y: 100, w: 200, h: 0, rotation: 0 },
+    };
   }

-  function attachedToRect(
-    site: number,
-  ): { connector: Element; host: Element } {
+  function attachedToRect(
+    site: number,
+  ): { connector: ConnectorElement; host: Element } {
@@
-    const connector: Element = {
+    const connector: ConnectorElement = {
@@
-      frame: { x: 0, y: 0, w: 0, h: 0, rotation: 0 },
-    } as unknown as Element;
+      frame: { x: 0, y: 0, w: 0, h: 0, rotation: 0 },
+    };
     return { connector, host };
   }

-  function connectorAt(): Element {
+  function connectorAt(): ConnectorElement {
     return {
@@
-      frame: { x: 1000, y: 1000, w: 100, h: 100, rotation: 0 },
-    } as unknown as Element;
+      frame: { x: 1000, y: 1000, w: 100, h: 100, rotation: 0 },
+    };
   }
As per coding guidelines, "Use type annotations in TypeScript to improve code clarity and catch errors early".

Also applies to: 359-368

🤖 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.test.ts` around lines 217 - 247, The
fixtures freeConnector() and attachedToRect() are being double-cast with "as
unknown as Element", which hides type mismatches; change their return types and
local connector variables to the concrete ConnectorElement type (and the host to
the appropriate ShapeElement/Element subtype) instead of using "as unknown as
Element", update the function signatures (e.g. make freeConnector():
ConnectorElement and attachedToRect(site: number): { connector:
ConnectorElement; host: ShapeElement } or the correct host type) and remove the
unsafe casts so the compiler will enforce the connector contract; adjust any
property shapes to satisfy ConnectorElement/ShapeElement definitions.
packages/frontend/src/types/slides-document.ts (1)

114-118: ⚡ Quick win

Consider removing placeholderRef from YorkieConnectorElement.

The comment on lines 111-112 explicitly states that connectors "do not appear as layout placeholders." If connectors can never be placeholders, the placeholderRef?: PlaceholderRef field on line 118 appears unnecessary and could cause confusion.

If this field is included for structural consistency with other element types or for future-proofing, consider adding a clarifying comment.

🤖 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/frontend/src/types/slides-document.ts` around lines 114 - 118, The
YorkieConnectorElement interface currently includes an optional placeholderRef
which contradicts the comment that connectors "do not appear as layout
placeholders"; remove the placeholderRef?: PlaceholderRef property from the
YorkieConnectorElement definition (or, if you intend to keep it for structural
reasons, add a clarifying comment above YorkieConnectorElement explaining why it
remains) so the interface and its intent are consistent; locate the
YorkieConnectorElement declaration and either delete placeholderRef or add the
explanatory comment to avoid confusion.
🤖 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 `@docs/design/slides/slides-connectors.md`:
- Around line 127-131: The fenced code blocks showing the
index.ts/getConnectionSites, defaults.ts/fourCardinal, and
overrides.ts/CONNECTION_SITES examples lack language identifiers and cause
MD040; update each triple-backtick fence around those snippets to include an
appropriate language tag (e.g., "ts" for TypeScript examples or "text" where not
code) so the blocks are lint-clean; apply the same fix to the other unlabeled
fences referenced in the review (the additional ranges noted) to eliminate the
MD040 warnings.
- Around line 51-53: The doc currently states Yorkie support is deferred; update
the "Yorkie schema migration" paragraph and any other occurrences (e.g., the
sections referencing deferred support around lines 344-346) to reflect that the
Yorkie connector implementation (yorkie-slides-store.ts) is present in the
current PR stack and not postponed—remove the deferment language, state that
Yorkie-backed store is implemented and shipping with the Phase 4 collaboration
workstream, and ensure the architecture/status wording across the file is
consistent.

In `@docs/tasks/active/20260515-slides-connectors-pr1-todo.md`:
- Around line 1651-1656: The task text is out of date: it instructs adding
Line/Arrow into the ShapePicker dropdown and references ShapeKind, but the app
uses a dedicated LinePicker component next to ShapePicker; update the task
wording to match current UI architecture by replacing references to adding
entries into ShapePicker/ShapeKind with instructions to add two options to the
existing LinePicker (or confirm they exist), and update the click behavior to
call editor.setInsertMode('connector:line') and
editor.setInsertMode('connector:arrow') as described (reference LinePicker,
ShapePicker, ShapeKind, and editor.setInsertMode in the updated text so
reviewers can locate code).

In `@packages/slides/src/view/editor/interactions/insert-connector.ts`:
- Around line 65-74: Validate zoom before dividing in findSnapTarget (and the
other block using MIN_DRAG_DISTANCE around symbols at lines ~167-173): ensure
Number.isFinite(zoom) && zoom > 0; if not, throw a clear RangeError or return
null with a descriptive message that includes the invalid zoom value and
function name (e.g. "findSnapTarget: invalid zoom = <value>"), so the code never
computes SITE_SNAP_RADIUS/zoom or MIN_DRAG_DISTANCE/zoom when zoom is non-finite
or <= 0.

In `@packages/slides/src/view/editor/overlay.ts`:
- Around line 410-428: The current hover selection uses centre distance (cx/cy)
to pick nearest elements; replace that with point-to-element distance: for each
non-connector element in elements compute either the minimum distance from
cursor to the element's connection sites (if the element exposes a
sites/connections array) or the distance to the closest point on el.frame
(clamping cursor to rect edges) to get dx/dy and d2, then compare against bestD2
(derived from hoverR2) and set nearest accordingly (update the loop that sets
nearest and bestD2). Keep the zoom/hoverR2 logic and skip connectors as before,
and add a regression test asserting the hover chooses the element when cursor is
exactly on a connection site (covering the blue-dot/endpoint case).

---

Outside diff comments:
In `@packages/frontend/src/app/slides/yorkie-slides-store.ts`:
- Around line 752-768: updateElementFrame must not write cached bbox values onto
connector elements; add a guard in updateElementFrame that detects connector
elements (e.g., check e.type === 'connector' or use the project’s isConnector
predicate) and skip merging/assigning into e.frame for those elements (do not
perform e.frame = { ...e.frame, ...frame } for connectors); keep the existing
recomputeDependentConnectorFrames call for non-connector moves, and for
connectors either return early or delegate to the connector-specific recompute
logic instead so connector geometry stays endpoint-driven.

In `@packages/slides/src/store/memory.ts`:
- Around line 258-280: The updateElementFrame method currently allows callers to
patch the frame of any element, which lets connectors receive direct frame
writes; change updateElementFrame so it rejects (throw) when the target element
is a connector (detect via slide.elements[this.requireElementIndex(slide,
elementId)].type === 'connector') OR, alternatively, when the element is a
connector translate the supplied frame patch into endpoint updates and then
recompute the connector cache instead of merging: i.e., do not assign e.frame =
{...} for connectors; call this.elementsLookup(slideId) and
computeConnectorFrame(connector, lookup) to refresh the cached frame after
applying endpoint changes (use requireSlide, requireElementIndex to locate the
element), and ensure callers get an error if they attempt to directly mutate
connector.frame.

---

Nitpick comments:
In `@packages/frontend/src/app/slides/line-picker.tsx`:
- Around line 52-57: The arrowhead path is being stroked (ctx.stroke()) which
produces an outline, but the comment and intent call for a filled arrowhead:
change the final call in the arrowhead drawing block (the sequence starting with
ctx.beginPath(); ctx.moveTo(...); ctx.lineTo(...); ctx.lineTo(...);
ctx.closePath();) to use ctx.fill() (or ctx.fill() followed by ctx.stroke() if
you want an outline) and ensure the desired color is set via ctx.fillStyle
before calling ctx.fill().

In `@packages/frontend/src/types/slides-document.ts`:
- Around line 114-118: The YorkieConnectorElement interface currently includes
an optional placeholderRef which contradicts the comment that connectors "do not
appear as layout placeholders"; remove the placeholderRef?: PlaceholderRef
property from the YorkieConnectorElement definition (or, if you intend to keep
it for structural reasons, add a clarifying comment above YorkieConnectorElement
explaining why it remains) so the interface and its intent are consistent;
locate the YorkieConnectorElement declaration and either delete placeholderRef
or add the explanatory comment to avoid confusion.

In `@packages/slides/src/view/editor/overlay.test.ts`:
- Around line 217-247: The fixtures freeConnector() and attachedToRect() are
being double-cast with "as unknown as Element", which hides type mismatches;
change their return types and local connector variables to the concrete
ConnectorElement type (and the host to the appropriate ShapeElement/Element
subtype) instead of using "as unknown as Element", update the function
signatures (e.g. make freeConnector(): ConnectorElement and attachedToRect(site:
number): { connector: ConnectorElement; host: ShapeElement } or the correct host
type) and remove the unsafe casts so the compiler will enforce the connector
contract; adjust any property shapes to satisfy ConnectorElement/ShapeElement
definitions.
🪄 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: 202aa373-cd2f-4bd5-8537-339947fb5ebc

📥 Commits

Reviewing files that changed from the base of the PR and between 10e6165 and a683c8d.

📒 Files selected for processing (50)
  • docs/design/README.md
  • docs/design/slides/slides-connectors.md
  • docs/tasks/active/20260515-slides-connectors-pr1-lessons.md
  • docs/tasks/active/20260515-slides-connectors-pr1-todo.md
  • packages/frontend/src/app/harness/visual/slides-scenarios.tsx
  • packages/frontend/src/app/slides/line-picker-helpers.ts
  • packages/frontend/src/app/slides/line-picker.tsx
  • packages/frontend/src/app/slides/shape-picker-helpers.ts
  • packages/frontend/src/app/slides/shape-picker.tsx
  • packages/frontend/src/app/slides/slides-formatting-toolbar.tsx
  • packages/frontend/src/app/slides/yorkie-slides-store.ts
  • packages/frontend/src/types/slides-document.ts
  • packages/frontend/tests/app/slides/line-picker.test.ts
  • packages/frontend/tests/app/slides/shape-picker.test.ts
  • packages/slides/src/index.ts
  • packages/slides/src/model/connection-site.ts
  • packages/slides/src/model/connector.ts
  • packages/slides/src/model/element.ts
  • packages/slides/src/store/memory.test.ts
  • packages/slides/src/store/memory.ts
  • packages/slides/src/store/store.ts
  • packages/slides/src/view/canvas/arrowhead-renderer.test.ts
  • packages/slides/src/view/canvas/arrowhead-renderer.ts
  • packages/slides/src/view/canvas/connection-sites/connection-sites.test.ts
  • packages/slides/src/view/canvas/connection-sites/defaults.ts
  • packages/slides/src/view/canvas/connection-sites/index.ts
  • packages/slides/src/view/canvas/connector-frame.test.ts
  • packages/slides/src/view/canvas/connector-frame.ts
  • packages/slides/src/view/canvas/connector-renderer.test.ts
  • packages/slides/src/view/canvas/connector-renderer.ts
  • packages/slides/src/view/canvas/element-renderer.ts
  • packages/slides/src/view/canvas/routing.test.ts
  • packages/slides/src/view/canvas/routing.ts
  • packages/slides/src/view/canvas/shape-icon.test.ts
  • packages/slides/src/view/canvas/shape-icon.ts
  • packages/slides/src/view/canvas/shape-renderer.test.ts
  • packages/slides/src/view/canvas/shape-renderer.ts
  • packages/slides/src/view/canvas/shape-special.ts
  • packages/slides/src/view/canvas/slide-renderer.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/connector-endpoint-drag.test.ts
  • packages/slides/src/view/editor/interactions/connector-endpoint-drag.ts
  • packages/slides/src/view/editor/interactions/insert-connector.test.ts
  • packages/slides/src/view/editor/interactions/insert-connector.ts
  • packages/slides/src/view/editor/interactions/insert.test.ts
  • packages/slides/src/view/editor/interactions/insert.ts
  • packages/slides/src/view/editor/overlay.test.ts
  • packages/slides/src/view/editor/overlay.ts
💤 Files with no reviewable changes (4)
  • packages/slides/src/view/canvas/shape-icon.test.ts
  • packages/slides/src/view/editor/interactions/insert.test.ts
  • packages/slides/src/view/canvas/shape-special.ts
  • packages/slides/src/view/canvas/shape-renderer.test.ts

Comment thread docs/design/slides/slides-connectors.md Outdated
Comment thread docs/design/slides/slides-connectors.md Outdated
Comment thread docs/tasks/active/20260515-slides-connectors-pr1-todo.md Outdated
Comment thread packages/slides/src/view/editor/interactions/insert-connector.ts
Comment thread packages/slides/src/view/editor/overlay.ts
CodeRabbit + senior self-review surfaced two real invariant holes
plus minor polish:

- updateElementFrame on a connector left its derived bbox cache
  out of sync with the endpoints. Reject the call instead so
  callers get a clear error and use updateConnectorEndpoint.
- Hover affordance was gated on element-center distance, so the
  connection-points overlay disappeared the moment the cursor
  sat ON a connection site of a larger shape (the very position
  where snapping was about to commit). Switch to nearest-site
  distance so the overlay agrees with the snap.

Plus guards for zoom <= 0, doc drift fixes for the design and
plan files, an arrowhead fill/stroke nit on the picker icon,
type narrowing in test fixtures, and trimming a never-used
placeholderRef field from the Yorkie connector type.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
@hackerwins
hackerwins merged commit b735297 into main May 15, 2026
4 checks passed
@hackerwins
hackerwins deleted the feat/slides-connectors-base branch May 15, 2026 10:42
hackerwins added a commit that referenced this pull request May 18, 2026
These seven slides + PPTX tasks shipped (PRs #238, #240, #241, #255,
#259, #260, #261) but their checklist boxes were never ticked during
execution, so the archive script left them in active/. Sweep the
checkboxes to reflect what actually landed, add lessons files for the
two that were missing (keyboard-shortcuts, presentation-mode), and
convert the keyboard-shortcuts onLinkRequest popover bullet from a
checkbox to a "Deferred (follow-up)" note so the gate stops blocking on
work that is intentionally out of scope.

Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>
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