Skip to content

Pilot/terrain lab - #52

Merged
tfukaza merged 11 commits into
mainfrom
pilot/terrain-lab
Jul 26, 2026
Merged

Pilot/terrain lab#52
tfukaza merged 11 commits into
mainfrom
pilot/terrain-lab

Conversation

@tfukaza

@tfukaza tfukaza commented Jul 25, 2026

Copy link
Copy Markdown
Owner

No description provided.

tfukaza and others added 9 commits July 20, 2026 23:13
Composing SnapLine inside <Camera> had two pan leaks:
- connector line drags never touched global.data.allowCameraControl, so
  drawing a connection also panned the canvas; startDragOutLine now
  disables it and _endLineDragCleanup restores it (mirroring node drags)
- nodes only disabled it in onDragStart, which fires after the 3px drag
  threshold — the camera panned by one pointer-move event first; the flag
  now flips in onCursorDown and restores in onUp/onDragEnd

Adds the previously missing camera+snapline coverage: a /snapline-camera
demo route (nodes inside <Camera wheelZoomModifier="ctrlOrMeta">) and a
snapline-camera e2e suite (pan, ctrl-wheel zoom, node drag and line drag
without panning, connect after pan+zoom), run via test:snapline-camera.

Piloted by Terrain Lab (see terrain-lab pilot branch notes).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add a `panButton` option to CameraControlConfig (default 'left', so
existing consumers/tests/demos are unaffected) and forward it through the
Svelte Camera wrapper. onCursorDown now gates the pan-start on the
configured button instead of hard-coding the left button; the pan-end
path already keys off pointerId, so a middle-button release ends the pan
cleanly. This frees the left button for other gestures (e.g. rubber-band
select) while the middle button pans — the standard node-editor model.

The camera demo reads ?panButton= so the e2e suite exercises the modes
without a second route; adds a test asserting middle-drag pans and
left-drag does not when panButton='middle'.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ing, owned suspension

- writeTransformRecursive: write twin of readDomRecursive (synchronous
  polymorphic recursion over transformChildren; prunes transformMode:none
  and non-element children). Descendant world values are already correct
  via the epoch cache - the cascade only flushes DOM.
- Collider.containsWorldPoint: synchronous world-space point test on the
  cached bounds, independent of the frame-delayed collision sweep.
- input: #resolveResizeOwner - a pointerdown inside a registered resize
  hitbox resolves the gesture owner to the hitbox's parent object, with
  precedence over DOM routing (covers grabs beyond the element's edge).
- GlobalManager.suspend/resume/isSuspended: owned suspension channels
  (token per suspender) replacing last-writer-wins shared booleans.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…nsion reads

- wheelPan + wheelPanSensitivity: unmodified wheel (trackpad two-finger
  scroll) pans; ctrl/cmd wheel and pinch still zoom. Pan and zoom speeds
  tune independently.
- zoomSensitivity + pinch gain; zoom direction fixed so pinch-out /
  scroll-up zooms in.
- panButton: left|middle|both for pointer pans.
- Grouped config (wheel: {action, zoomModifier, zoomSensitivity,
  panSensitivity, pinchZoomGain}, pointer: {panButton, panLock}) resolved
  at read time via resolveCameraOptions; flat keys remain as deprecated
  aliases. React Camera gains the previously svelte-only options.
- Camera readers honor global.isSuspended(cameraControl) alongside the
  legacy allowCameraControl boolean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ship refactor

Features:
- GroupNodeComponent: a resizable box that carries any node whose center
  is inside it. Live geometric membership maintained at settle events
  (move-end, resize, node drop); transient transform-parenting rides the
  engine's write cascade; onMemberEnter/Leave/onDragCommit callbacks.
- JS node resize (CSS resize is unsupported on iOS Safari): a virtual
  CircleCollider at a configurable corner anchor (tl|tr|bl|br, default
  br) with signed drag deltas and a fixed opposite corner; opt-in per
  node (resizable, minWidth/minHeight, resizeHandleRadius).

Framework-DOM-ownership refactor (per the SnapSort-derived contract:
structural DOM is framework-owned; transforms/data-*/property writes may
stay engine-direct):
- Node width/height delegated: core fires nodeCallback.onSizeChange and
  adapters render the size as state; core setSizeState keeps hitboxes in
  sync and never writes the DOM box.
- Selection rectangle delegated: selectCallback.onRectChange carries
  {x,y,width,height,visible}; Select adapters render (and can restyle)
  the box. No flush handshake - not paint-atomic.
- Base positioning styles moved into adapter markup.
- nodeCallback EventProxyFactory dict (onSizeChange/onResizeCommit/
  onLinesChanged) with deprecated setLineListCallback/_onResizeCommit
  shims; DEFAULT_NODE_CONFIG/DEFAULT_GROUP_CONFIG single-site merges;
  #-private internals with hitBox/config getters; typed global.data
  accessors in snapline-globals.ts; line-method renames with deprecated
  aliases (scheduleLineWrites/writeLinesNow/resyncNodeGeometry).
- React parity: Group.tsx, Node resize props/handle, state-rendered
  Select box; demos and e2e for both frameworks (group, resize incl.
  TL-anchor fixed-corner and min-clamp cases).
- AGENTS.md documents the ownership contract and v1 design limits.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A gesture owner now blocks global input listeners at the dispatch layer:
engine.input.claimPointer(pointerId), called from the owner's pointerDown
handler. While claimed, that pointer's global pointerDown/Move and
dragStart/drag are not delivered (owner dispatch unaffected), pinches
involving the pointer are suppressed, and wheel is suppressed while any
claim is held. End events (pointerUp/dragEnd/pinchEnd) always deliver so
a global listener engaged before a late claim terminates cleanly.

Claims anchor to the input layer's per-pointer records, which are cleaned
up on pointer up/cancel regardless of owner liveness - so claims
auto-release and cannot be stranded. This deletes every manual resume
(dual release paths, destroy hooks) from node/connector/demo/website
writers, removes the GlobalManager suspend/resume/isSuspended channel
API (no consumers remain), and leaves the camera with zero knowledge of
the mechanism (only the legacy third-party allowCameraControl boolean
read remains). Per-pointer semantics: a second pointer can still pan or
pinch the camera while the first is claimed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…e prep

Broad working-tree snapshot committed ahead of rebasing onto
origin/website-update:

- snapsort: coalesced onVisualGeometryInvalidated container event
  (invalidation plumbing through drag/session, animations, and settle),
  plus a ghost-safe base Item.invalidateVisualGeometry (Item.rootContainer
  falls back to the item itself for detached ghost anchors; the missing
  method aborted the FLIP pass mid-loop, killing displaced-item animations
  for every entry after the ghost — upward-moving rows jumped) and an
  upward-reorder animation regression test
- snapline: placement/query modules, nested group demo, framework adapters
  and package metadata churn
- asset-base: camera config and adapter updates
- docs/website: snapline docs, demo component, catalog and layout updates
- publish workflow and package validation updates

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…d-edges EdgeSync

Two workstreams landing together (interleaved in the connector layer):

Surface connectors and capabilities (parallel stream): ConnectorCapabilities
(source/target/maxIncoming/reconnect/allowParallel), surface strategies with
priority hit-testing, virtual connectors, onConnectionRequest creation seam,
ConnectorLinePhase state machine, live updateConfig, reconnect gestures,
arming semantics (click no longer creates a preview line), docs guide and
connector-config unit tests.

NodeManager + controlled edges (this stream):
- NodeManager: per-engine registry of live nodes/connectors, lazy-created via
  getNodeManager(engine) on first component registration (GlobalManager is
  app-wide, so managers key by engine); query.ts enumeration delegates to it;
  home for engine-scoped facilities (edge sync now, layout helpers later)
- EdgeSyncController + EdgeSync adapters (Svelte/React): the consumer's edge
  document is the only edge authority; sync() reconciles lines to it
  (hydration origin, in-flight drag lines and foreign lines untouched,
  #syncing guard suppresses intent echo) and gestures forward as semantic
  onEdgeConnect/onEdgeDisconnect intents ({node, port} endpoints; gesture +
  replacement forward, programmatic/hydration/teardown never). Intents fire
  synchronously in the drop dispatch; adapters reconcile in a same-task
  microtask so accept and reject both resolve before paint. Connector
  registration nudges a coalesced re-sync so remounted nodes re-hydrate
  their document edges.
- node unmount now tears down lines with reason 'teardown' (was the
  'programmatic' default), keeping the reason contract honest
- snapline-edges e2e suite (7 tests: intents, programmatic silence,
  doc-driven replacement ordering, reject reconcile + paint-flicker
  observer, unmount silence + re-hydration, mid-drag safety) + React parity
  suite (3), node_ui_edges demos in both frameworks, AGENTS.md contract docs

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@vercel

vercel Bot commented Jul 25, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
snap-engine-js Error Error Jul 25, 2026 11:59pm

The website's copies under website/static/ (the ones its components
reference) are unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Nothing in the website references it; its license.txt goes with it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@tfukaza
tfukaza merged commit e273a93 into main Jul 26, 2026
2 of 3 checks passed
@tfukaza
tfukaza deleted the pilot/terrain-lab branch July 26, 2026 00:11
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