Repository navigation
Conversation
Snapshot of in-flight SnapSort work, committed before the core geometry refactor so its diff reviews on its own: - keyboard-controller, drag/direct-controller, drag/direct-candidates and drag/pointer-controller for direct (non-pointer) drags - drag session, algorithm, and item updates to support them - core EventCallback exposes installed input/global callbacks - snapsort_keyboard demo route and e2e spec - docs and design plan updates; drop the docs-framework-code e2e spec Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DsiHfSvTGUzy6iNEkoWiG3
Introduce `@snap-engine/core/geometry`: one geometry vocabulary (Point, Size, Rect, Circle, Edges, Bounds, BoxModel, ElementBox, PointerPosition) plus pure rect and box-model helpers, with no DOM or Camera dependency. - Move the collision predicates and their scalar kernels into geometry; collision imports them and no longer re-exports CollisionPoint, CollisionRect, CollisionCircle, or the predicates. - SnapSort imports predicates and Rect/Circle from the geometry subpath. - Wire the subpath through package exports, the vite lib build, every tsconfig path map, the demo vite aliases, and the website aliases. - Declare `"sideEffects": false` on core. - Add `test:core-ut` and run core and SnapLine unit tests in `ci`. - Document the module in docs/snapengine/reference/geometry.mdx. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DsiHfSvTGUzy6iNEkoWiG3
`ElementObject.readDom()` now returns a new frozen `ElementBox` per read instead of mutating one shared `DomProperty`. A captured box can no longer change underneath its holder, so SnapSort snapshots keep the reference directly and `cloneDomProperty` is gone. Unchanged edges are interned, so a steady-state read allocates less than before. Measured values are unchanged in this commit. API (hard break): - `DomProperty` -> `ElementBox` (flat `screenX/screenY` -> `screen: Rect`, bogus always-1 `scaleX/scaleY` dropped) - `currentDomProperty` -> `box` - `getDomProperty(stage)` -> `getStageBox(stage)` - `copyDomProperty` -> `copyStageBox` - `saveDomProperety` -> `saveWorldPosition` - util `getDomProperty` / `cloneDomProperty` -> `measureElementBox`, a thin DOM read around the pure `elementBoxFromMeasurement` SnapSort, SnapLine, the website debug overlay, tests, and the objects and render-queue docs move to the new names. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DsiHfSvTGUzy6iNEkoWiG3
Pointer positions become `PointerPosition` (`x`/`y` world, `camera: Point`, `screen: Point`) instead of the flat `eventPosition` with six scalar fields. Drag deltas share the shape. Screen/world conversion was written five times; it now lives in three root helpers that accept `null` for "no camera": - `pointerPositionFromScreen` (input dispatch, asset-base CameraControl) - `pointerPositionFromWorld` (SnapLine `findCandidateAtPoint`, which used to fabricate camera/screen coordinates from world values) - `worldRectFromScreenRect` (SnapLine `measureDomCenter`) CameraControl also shares one container hit test and maps its pinch anchor through `getWorldFromCamera`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DsiHfSvTGUzy6iNEkoWiG3
- `ContainerBounds` -> `Bounds` (`Engine.containerBounds`, `EngineEventProps.bounds`, `Camera.updateCameraProperty`), built with `boundsOf(getBoundingClientRect())`. - The private `ColliderWorldBounds` becomes the exported `ColliderBounds`: a `Bounds` plus `center` and `radius`. `x`/`y` are now always the top-left corner and `width`/`height` span the bounds for every shape (a circle reports its enclosing square). Circle and point tests read `center`. The unused always-copied `scaleX`/`scaleY` are dropped. - SnapLine `ConnectorGeometrySnapshot` extends `Bounds`; `group.ts` drops its `ReturnType<>` Bounds alias and local area/containment helpers for `rectArea`, `rectContainsRect`, and `pointIntersectsRect`. - Input's engine hit test uses the shared bounds kernel. - `DebugMarker` is declared once; the two inline copies in object.ts and debug.ts were casts over the same record. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DsiHfSvTGUzy6iNEkoWiG3
SnapSort's layout simulation had no SnapSort behavior; it now lives in
`@snap-engine/core/layout`, a tree-shakeable subpath that imports only
geometry.
- Generic over `LayoutNode<N>` (box + children + layout config), which
SnapSort's `ItemSnapshot` extends, so any measured box tree works.
- `LayoutFilter { excludeSnapshots, excludeValues }` becomes an
`exclude(node)` predicate in `LayoutPlanOptions`.
- SnapSort vocabulary is renamed out: `SnapshotVisualLine` -> `VisualLine`
(`snapshots` -> `nodes`), `inferSnapshotVisualLines` ->
`inferVisualLines`, `VirtualLayoutEntry` -> `VirtualEntry`,
`FlowPositionResult` -> `LayoutPositions`, `VirtualDimensions` -> `Size`,
`layoutItems` -> `layoutChildren`, plan `snapshot` -> `container`,
`onSnapshotVisit` -> `onNodeVisit`.
- `contentBoxOrigin`/`contentBoxSize` are replaced by geometry's
`contentRect`/`contentOffset`; SnapSort's inline content-origin copies go
too. Tolerances are named: `LAYOUT_WRAP_TOLERANCE`,
`LAYOUT_LINE_BREAK_TOLERANCE` (now also used by the slot line break), and
`SLOT_ROW_RESIDUAL_TOLERANCE`.
- The slots backend moves intact as the basis for CSS grid simulation.
- `LayoutDirection/MainAxisAlign/Model/Wrap` move to core; SnapSort stops
re-exporting `LayoutMainAxisAlign`.
- Delete dead code in algorithm.ts: `VirtualGhost`, the duplicate
`VirtualDimensions`, `layoutInsertionFromGhost`, `virtualDimensions`.
- The test-only `flowLayoutPositions`/`virtualDimensions` wrappers move to
tests/helpers/layout-grid.ts; tests/ut/layout.spec.ts becomes
core-layout.spec.ts.
- Add docs/snapengine/reference/layout.mdx.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DsiHfSvTGUzy6iNEkoWiG3
`readDom()` mixed coordinate spaces: x/y were world units, but width and
height were screen pixels (the zoom conversion pair cancelled out) while
margin/padding/border were CSS pixels. At any zoom other than 1 this broke
SnapLine hitboxes and resize (a second resize jumped by the zoom factor),
SnapSort spacer sizes, content boxes, and drop prediction, and FLIP deltas.
- Measurement: the border box maps origin and size through the camera
(`worldRectFromScreenRect`); computed CSS edges pass through unchanged
because they are already world units inside the camera layer; `screen`
keeps the raw client rect.
- `parseTransformString` parsed only the first function, so scale was always
1. It now composes translate/translate3d/translateX/Y and scale/scaleX/Y
left to right. `readDom({ unapplyTransform })` removes both translate and
scale, honoring the computed `transform-origin`.
- SnapSort visual rects (FLIP, drop animations, pointer preview) are
world-space, so their deltas are the CSS pixels they are written as.
- Layout plans accept `wrapTolerance`; SnapSort scales the CSS-pixel default
by `1 / zoom` so browser measurement noise stays absorbed at zoom 0.2.
- CameraControl paints the camera transform in the same commit as the
camera change, and defers programmatic changes requested during a read
stage to the next write stage, so a read never divides by a zoom that is
not on screen yet. Its seven copies of the paint code go through
`paintCamera()`.
- The debug renderer scales world sizes into camera pixels (DOM boxes,
markers, and colliders were drawn unscaled).
Tests, each shown failing before this change:
- tests/ut/core-measure.spec.ts: measurement at zoom 0.5/1/2, transform
parsing, and transform-origin parsing.
- core-layout scale invariance, plus exact-fill wrapping under 1/64px noise
at zoom 0.2.
- tests/e2e/core-zoom.spec.ts: `readDom` under a real zoomed Camera in
Chromium, Firefox, and WebKit (`npm run test:core-zoom`).
- snapline-resize: consecutive resizes stay continuous after zooming in and
out (new `?camera=1` demo option).
- snapsort-camera: the spacer is 120px and exact-fill rows stay whole after
zooming to 0.2 and 2 (new /snapsort-camera fixture).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DsiHfSvTGUzy6iNEkoWiG3
SnapSort no longer declares rectangle types or rect helpers of its own.
Public API (hard break):
- `GhostRect`, `ContainerLocalRect`, and `DropPriorityRect` are removed;
ghost rects, drop-priority rects, and `toContainerLocalRect` /
`insertionMarkerRect` results are core `Rect`s, and event pointers are
core `Point`s.
- `ItemHitbox`'s circle variant carries a core `Circle`:
`{ shape: "circle", circle }` instead of `{ center, radius }`.
Duplicates removed:
- Four freeze-rect copies, three bounding-rect copies, two rect projections,
`interpolateRect`, `sameRect`, and `euclidean`, replaced by `freezeRect`,
`boundingRect`, `projectRects`, `lerpRect`, `rectsEqual`, `rectArea`, and
`Math.hypot`.
- Participant start rectangles were rebuilt in three files; they now come
from `DragSession.startMemberRects()`.
- The margin CSS string written three times (Vanilla adapter, React and
Svelte `Ghost`) goes through `edgesToCss`; session.ts's inline edge type
is `BoxModel`; the `ResolvedItemHitbox` mirror type is gone.
- Candidate-selection tolerances are named.
The type contract pins the removed names and the new circle shape. Docs
(ghost, container, and callbacks references, README, AGENTS) are updated.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DsiHfSvTGUzy6iNEkoWiG3
SnapLine no longer declares point, size, or rectangle types of its own. Public API (hard break): - `ConnectorPoint` and `ConnectorNormal` are removed; `ConnectorAnchor` extends core `Point` with an optional `normal: Point`. - `PlacementPoint`, `PlacementAnchor` -> `Point`; `PlacementSize` -> `Size`. - `ResolvedNodeDragPosition` is removed; `resolveDragPosition` returns a `Point`. - `NodeGeometry`, `NodeResizeEvent`, and `SelectRect` extend `Rect`; `node.geometrySnapshot()` returns a `Rect`; `LineGeometrySnapshot.delta` is a `Point`. - Connector local centers use `rectCenter` of the world-space box. The placement guide and the website demo map screen points through `pointerPositionFromScreen` instead of hand-chaining camera conversions. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DsiHfSvTGUzy6iNEkoWiG3
- src/AGENTS.md and AGENTS.md: core ships raw TypeScript through `exports` (dist/ is a build check, not the published artifact); list geometry and layout among the optional entry points. - Remove the stale tracked bundle demo/vanilla/snapengine/object-D-YQDlkD.js, a generated build of the pre-refactor API that nothing imports. - Rename leftover LAYOUT_EPSILON mentions in the layout tests. Package versions are intentionally left unchanged: the assets resolve @snap-engine/core from the registry, so the core/asset version bumps have to ship together with publishing core 0.5.0. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DsiHfSvTGUzy6iNEkoWiG3
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
tfukaza
added this pull request to stack #57
September 12, 2026 18:27
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Unifies element geometry across core, SnapSort, and SnapLine, and moves SnapSort's layout engine into core as a tree-shakeable module. Also fixes a coordinate-space bug that broke every measured size at camera zoom ≠ 1.
The first commit (
da25881) is pre-existing in-flight SnapSort work (direct/keyboard drag controllers), committed as-is so the refactor diff reviews on its own.Commits
refactor(core): add shared geometry module@snap-engine/core/geometry:Point/Size/Rect/Circle/Edges/Bounds/BoxModel/ElementBox/PointerPosition, plus pure rect and box-model helpers."sideEffects": false.refactor(core): replace DomProperty with frozen ElementBox snapshots:readDom()returns a new frozen box per read.refactor(core): replace eventPosition with PointerPosition helpers: the screen↔world conversion was written 5 times; it's now 3 shared helpers.refactor(core): unify container, collider, and connector boundsrefactor(core): move the layout engine into core@snap-engine/core/layout, generic over aLayoutNodetree.fix(core): measure element boxes in world space at any zoom: width/height used to be screen px while x/y were world units. Also fixed here:refactor(snapsort): use core geometry types and helpersrefactor(snapline): use core geometry typeschore: sweep docs and stale artifacts after the geometry refactorBreaking changes (no aliases, pre-1.0)
DomProperty→ElementBox,currentDomProperty→box,getDomProperty(stage)→getStageBox,saveDomProperety→saveWorldPosition.eventPosition→PointerPosition(camera/screensub-points).ContainerBounds→Bounds.@snap-engine/core/geometry.GhostRect,ContainerLocalRect,DropPriorityRect→ coreRect.ItemHitboxcircle is now{ shape: "circle", circle }.LayoutMainAxisAlignnow comes from core/layout.ConnectorPoint/ConnectorNormal/Placement*/ResolvedNodeDragPosition→ corePoint/Size.Rect.Not included: version bumps
The asset packages resolve
@snap-engine/corefrom the npm registry. Bumping core to 0.5.0 (and the asset ranges) has to ship together with publishing core.Test plan
typecheck,lint,check:adapters,check:snapsort-api,validate:packages,buildtest:core-ut,test:snapsort-ut,test:snapline-utmain:test:layout(3 browsers),test:snapsort,test:snapsort-examples, alltest:snapline*,test:camera-control*,test:asset-base-react.tests/ut/core-measure.spec.tstest:core-zoom(real Camera, 3 browsers)Pre-existing failures on
main(fixed in the stacked follow-up PR): websitecheck:website(8 ×session.pointerId),tests/ut/input.spec.ts, and several SnapSort/asset-base e2e tests.🤖 Generated with Claude Code
https://claude.ai/code/session_01DsiHfSvTGUzy6iNEkoWiG3