Skip to content

Cleanup - #53

Merged
tfukaza merged 21 commits into
mainfrom
Cleanup
Jul 27, 2026
Merged

Cleanup#53
tfukaza merged 21 commits into
mainfrom
Cleanup

Conversation

@tfukaza

@tfukaza tfukaza commented Jul 26, 2026

Copy link
Copy Markdown
Owner

No description provided.

tfukaza and others added 3 commits July 25, 2026 18:04
Carries the uncommitted working-tree changes (snapline core + adapters,
object.ts, connector-config unit tests, design docs incl. the new
planned-rearchitecture.md and geometry.ts) forward from pilot/terrain-lab
onto Cleanup, branched from main at e273a93 (PR #52 merge).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Applies the settled decisions from the design review:

- Push model: canonical state arrives via setCanonicalGraph(snapshot);
  the mirror caches and replays it. The on*Pull callback category and
  pullCanonical() are removed, along with the request→pull→request
  recursion rules they required.
- Request vocabulary: the outbound proposal is onLineChangeRequest with
  an `intent` field; "commit" refers only to completed canonical or
  framework updates.
- Mirror naming: core runtime types are *Mirror (NodeMirror, LineMirror,
  ...); Component stays reserved for framework types.
- IDs: strings only, graph-global connector IDs, minted by SnapLine when
  not supplied; supplied IDs are the persistence contract. Gesture lines
  carry a minted LineId that the app adopts (settle-in-place) or
  substitutes (discard/recreate).
- controlled/uncontrolled is the authority vocabulary for both the graph
  mode (one mode per engine, fail-fast) and geometry modes.
- Predicate callbacks may use is*/can* (isValidConnection); "unlimited"
  is the explicit no-limit value; GraphMirror/GraphQuery are the settled
  registry and facade names; runBatch() is the primary batch API;
  property propagation is removed; diagnostics stay minimal.
- Phase 0 scope shrunk to renames + dead code; prefix normalization and
  function splitting move to their owning phases.

Keeps the newer non-colliding edits: remeasureDomGeometry(),
#reconciling/#reconciliationQueued, and the strict reconcile-only
alignment-verb rule.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Behavior-preserving vocabulary cleanup per the re-architecture plan:

- NodeComponent → NodeMirror, ConnectorComponent → ConnectorMirror,
  LineComponent → LineMirror, GroupNodeComponent → GroupNodeMirror.
  Component is now reserved for framework types; the framework-facing
  Node/Connector/Line/Group component names are unchanged.
- RectSelectComponent → RectSelectController (interaction controller,
  not a canonical-record mirror; follows the PlacementController
  precedent so no core type carries the Component suffix).
- React NodeObjectContext → NodeMirrorContext.
- syncDomGeometry() → remeasureDomGeometry() (names the DOM measurement,
  not canonical reconciliation).
- EdgeSyncController privates #syncing/#syncQueued →
  #reconciling/#reconciliationQueued. The controller's public name and
  sync() entry point are unchanged until the Phase 3 protocol replaces
  them, per the no-double-rename rule.
- Remove dead startDragOutLine() (zero callers; pointer-down only arms).
- Update demos, unit/e2e tests, package READMEs, AGENTS.md, and the
  snapline docs to the new names.

Deferred by design: maxConnectors/allowDragOut removal (the whole
capability model is replaced by D13 in Phase 3) and the EdgeSync seam's
Edge* types (replaced wholesale by the controlled line protocol).

Verified: tsc, check:adapters (0 errors), all six snapline e2e suites
green (55 tests; one camera flake passed on isolated rerun).

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

vercel Bot commented Jul 26, 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 Ready Ready Preview, Comment Jul 27, 2026 5:22am

tfukaza and others added 4 commits July 25, 2026 18:48
- D1: explicit setGraphAuthority(engine, mode) declaration required for
  both modes; undeclared or cross-mode operations fail fast.
- D2/D3: domain IDs live in separate readonly nodeId/connectorId/lineId
  fields (never BaseObject.id); minted as `${kind}-${createId()}`;
  identity changes rejected with an "identity-changed" diagnostic.
- D4: diagnostics are derived state recomputed per reconciliation pass;
  resolved errors drop out automatically; onDiagnosticsChanged fires on
  content change.
- D8: onDragCommit/onResizeCommit consolidate into batched
  onGeometryChanged / onGeometryChangeRequest with a shared
  { nodes: NodeGeometry[] } payload; adapter prop geometry=
  "uncontrolled" | "controlled".
- D13: allowParallel stays a both-sides rule; onFull: "reject" |
  "replace-oldest" is the explicit replacement policy;
  ProposedLine.payload mirrors the staged line.
- D7: GraphQuery ships from the existing ./query subpath.
- Record the empty-string KEY_SEPARATOR defect as resolved by stable
  LineId identity.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Grows NodeManager into GraphMirror (graph-mirror.ts), the engine-scoped
registry for the whole graph:

- Stable domain identity on every mirror: readonly nodeId/connectorId/
  lineId, supplied via config.id or minted as `${kind}-${createId()}`.
  Never overloads the engine-internal BaseObject.id (whose writable
  setter is removed — it orphaned object-table keys).
- Identity indexes (nodesById/connectorsById/linesById) with a
  first-registration-wins duplicate policy: duplicates stay unindexed
  and surface as "duplicate-id" ReconciliationErrors via diagnostics();
  the index entry and diagnostic resolve when either mirror leaves.
- Lines join the registry: previews tracked from construction, settled
  lines indexed by lineId on connectTarget, unsettled on clearTarget,
  unregistered on destroy (new LineMirror.destroy override).
- unregisterConnector now notifies the reconciler slot (register-only
  notification was asymmetric).
- data.nodeManagers (a strong Map that leaked destroyed engines'
  registries) becomes data.graphMirrors, a WeakMap keyed by engine.
- Public NodeManager/getNodeManager/EdgeSyncLike exports removed (no
  external consumers); NodeId/ConnectorId/LineId/ReconciliationError
  types exported instead.
- Test harness extracted to tests/helpers/snapline-harness.ts (+ sibling
  -engine factory); new tests/ut/snapline-graph-mirror.spec.ts covers
  minted/supplied ids, index round-trips, duplicate-id resolution, line
  preview/settle/unsettle lifecycle, and per-engine registry isolation.

Verified: tsc, check:adapters (0 errors), ut suites 22/22, all six
snapline e2e suites green (55 tests).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Moves the cross-engine shared arrays off global.data into the per-engine
GraphMirror:

- GraphMirror.selection replaces global.data.select. setSelected removes
  by identity (the old shared list filtered by BaseObject.id, which
  could evict a same-id node on another engine); RectSelectController
  clears and reads only its engine's selection instead of clobbering the
  application-wide list; getSelectedNodes() is engine-scoped now.
- GraphMirror.groups replaces global.data.groups; group membership
  enumeration reads mirror.nodes (the group module's engine-object-table
  scan is gone) and getGroupNodes() reads the dedicated group index.
  parentGroups / membershipResolver / reconcilingMembership move from
  module-level WeakMaps keyed by engine to plain GraphMirror fields.
- GraphMirror.resizingNode replaces the shared global.data.resizingNode.
- snapline-globals drops select/groups/resizingNode, getSelectList,
  getGroups, and the now-unused GroupLike stand-in (the graph mirror's
  type-only group import keeps node.ts free of group value imports).
  resizeHandles/sourceSurfaces stay: engine input.ts duck-reads their
  shapes.
- New unit coverage: engine-scoped selection with identity-based
  removal, and engine-scoped group registries.

Node drag/resize settle now refreshes only its engine's groups (was
cross-engine). Verified: tsc, check:adapters (0 errors), ut 26/26, all
six snapline e2e suites green (55 tests).

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

- Connector candidate discovery reads GraphMirror.connectors instead of
  scanning the engine object table (the scan ran once per pointer move
  during connection drags).
- outgoingLines/incomingLines getters return readonly snapshots, never
  the internal arrays; topology mutation goes through the connector
  commands only.
- New read-only query facade: query(engine): GraphQuery — snapshots plus
  node/connector/line identity lookups and diagnostics(). Returns live
  mirrors (their mutation surface is constrained separately); exposes no
  registry sets, topology arrays, or mutation methods.
- Remove dead NodeMirror._filterDeletedLines and the write-only
  _components bag.

Phase 2 complete. Deferred by design: LineMirror field hardening
(start/target/phase/candidate getter-backing) moves to Phase 3b, which
rewrites the line lifecycle anyway — one churn pass instead of two.

Verified: tsc, check:adapters (0 errors), ut 26/26, all six snapline
e2e suites green (55 tests).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Replaces the asymmetric source/target-plus-maxIncoming capability model
(D13):

- ConnectorRules { maxOutgoing, maxIncoming, reconnect, allowParallel,
  onFull, isValidConnection } with ConnectionLimit = number | "unlimited"
  (normalized to Infinity internally so capacity checks are branch-free).
  source/target become derived isSource/isTarget (limit !== 0). Defaults:
  maxOutgoing "unlimited", maxIncoming 1, reconnect true, allowParallel
  false, onFull "reject".
- Admission is line-aware: #admitsConnection(target, line, phase) runs
  the structural rules plus both endpoints' isValidConnection predicates
  with the actual proposed LineMirror, during candidate discovery and
  again at drop. The in-flight line never counts against capacity or
  parallel checks. connectToConnector mints its line before validating
  (and discards it on refusal) so programmatic admission sees a real
  line too. The pair-only canConnect callback is removed; the imperative
  predicate is now canConnect(target, line?).
- Implicit oldest-line eviction becomes the explicit onFull:
  "replace-oldest" policy; a full "reject" target refuses admission
  outright. maxOutgoing is enforced: starting a preview reserves an
  outgoing slot (a full source cannot arm a new drag; reconnect reuses
  its slot), and programmatic connects respect the cap.
- maxConnectors/allowDragOut are deleted everywhere: config, adapters
  (whose allowDragOut=true default silently flipped core's source:false),
  demos (ported to explicit rules preserving their old derived behavior,
  with replace-oldest where the e2e suites exercise eviction), website
  demo, and unit tests. Adapters gain the id prop and forward rules.
- Delete the dead legacy React demo subtree (SnapLineReact.jsx,
  components/nodes, components/lib) — it imported a nonexistent
  snapline.mjs with a long-gone constructor signature.

Verified: tsc, check:adapters (0 errors), ut 26/26, all six snapline
e2e suites green (55 tests).

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

- The phase union moves to line.ts as LineMirrorPhase (ConnectorLinePhase
  is gone) and gains "staged": a gesture that completed locally and
  awaits the canonical owner's decision (wired up by the controlled
  protocol; inert until then).
- LineMirror's public fields are getter-backed: start, target, payload,
  phase, candidate, and the anchors are read-only outside the mirror's
  own lifecycle operations; endWorldX/endWorldY derive from endAnchor.
  Reconnect pickup uses the @internal detachTarget() instead of writing
  line.target directly (completes the field hardening deferred from 2c).
- deleteLine(index) becomes deleteLine(line) — identity-based, no more
  indexOf at every call site (edge-sync prune, eviction, discard,
  deleteAllLines, disconnectFromConnector).
- connectToConnector's settle tail is the named #settlePreviewLine
  operation (replacement policy, previous-target detach, glue, emits);
  the public command is validation + delegation. createLine() accepts
  { id } so the reconciler can mint canonical-id lines directly.

Verified: tsc, check:adapters (0 errors), ut 26/26, all six snapline
e2e suites green (55 tests).

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

- setGraphAuthority(engine, "controlled" | "uncontrolled") declares the
  engine's one authority mode. Imperative topology commands
  (connectToConnector with origin "programmatic", deleteLine/
  deleteAllLines/disconnectFromConnector with reason "programmatic")
  throw SnapLineAuthorityError on an undeclared engine or against the
  other mode — nothing silently defaults into uncontrolled authority.
  EdgeSyncController's constructor IS the "controlled" declaration; its
  reconciliation passes set GraphMirror.reconcilerActive so canonical
  hydration/pruning bypasses the gate. Gesture routing by authority
  arrives with the request path (3e).
- One coalescing, batch-aware scheduler on GraphMirror:
  scheduleReconciliation() (microtask, replaces EdgeSyncController's
  private connectorRegistered coalescing), runBatch(fn) /
  beginBatch()+end() (nestable, idempotent, exception-safe; only the
  outermost close schedules the final pass; clean batches schedule
  nothing), and synchronous flush() that cancels the queued pass.
  Connector register/unregister route through it; the reconciler slot's
  contract is now reconcile().
- New ut coverage: undeclared-command rejection, same-mode
  re-declaration no-op, cross-mode fail-fast, burst coalescing, nested
  batch deferral, empty-batch no-op, runBatch exception safety, and
  flush semantics.

Verified: tsc, check:adapters (0 errors), ut 30/30, all six snapline
e2e suites green (55 tests).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The controlled read path (replacing EdgeSyncController's model at the
core level; the legacy controller still serves the current adapters
until the 3f cutover):

- attachControlledGraph(engine, { onLineChangeRequest,
  onDiagnosticsChanged? }) declares "controlled", installs the new
  LineReconciler in the reconciler slot, and returns the handle:
  setCanonicalGraph(snapshot) / flush() / dispose(). Canonical state is
  PUSHED and cached — there is no pull callback; internally triggered
  passes (registration, batch close) replay the cache.
- CanonicalGraphSnapshot { lines: LineRecord[] } with stable-id
  LineRecords; LineChangeRequest/ProposedLine/LineEndpointUpdate types
  land for the 3e write path.
- reconcile() = prune (record gone or source moved) → preserve by
  lineId (a toConnectorId change re-targets the same mirror instance;
  a fromConnectorId change recreates under the same id since a line's
  start is fixed) → create settled mirrors for fully-mounted records →
  report. Unmounted endpoints are latent, silent, and retried on
  registration; rules violations leave the record unrepresented with
  derived "capacity-exceeded"/"connection-rejected"/"duplicate-id"
  diagnostics that clear when their cause resolves
  (GraphMirror.setReconciliationErrors + onDiagnosticsChanged on
  change). Reconciliation never emits a change request and never evicts
  canonical lines to satisfy gesture limits.
- Connector gains reconciler-only entry points with strict, never-
  evicting admission: createSettledLineFromRecord (mints the line with
  the canonical id, runs both isValidConnection predicates) and
  retargetSettledLineFromRecord.

New snapline-line-reconciler.spec.ts: hydrate/latent-retry/prune,
preserve-by-id across retargets, capacity diagnostics without eviction,
duplicate ids, predicate veto + resolution, and the no-requests
invariant. Verified: tsc, check:adapters (0 errors), ut 38/38, all six
snapline e2e suites green (55 tests).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
In controlled mode a gesture drop no longer mutates settled topology.
endDragOutLine routes by declared authority and bridge capability:

- Connect: validate at drop (rules + both isValidConnection predicates),
  stage the outcome on the same LineMirror (phase "staged": visually
  attached, in no incoming list, not in the settled index), and dispatch
  one atomic LineChangeRequest { intent: "connect", add: [ProposedLine
  with the SnapLine-minted lineId] }. The canonical owner adopting the
  id settles the staged mirror in place on the next pass; a snapshot
  without it (rejection, or rejection-by-inaction via the adapter's
  guaranteed post-request push) discards it — no timing inference.
- Disconnect (reconnect pickup dropped in the void): the line stays
  visibly detached in "staged" while { intent: "disconnect", remove:
  [lineId] } is proposed; a rejected removal re-glues the SAME mirror
  from the unchanged document, an accepted one discards it.
- Reconnect: { intent: "reconnect", update: [{ id, toConnectorId }] }.
- A full replace-oldest target produces ONE atomic { intent: "replace",
  remove: [evicted ids], add/update } — evictions ride the proposal and
  nothing is deleted locally until the document says so.
- Dispatch order guarantees the decisive pass sees fresh state: the
  adapter queues its post-request push inside the dispatch callback,
  ahead of the core's scheduled reconcile microtask.
  GraphMirror.pendingGestureRequest enforces the one-in-flight
  invariant; the reconciler clears it each pass.
- The reconciler now settles staged lines whose record appears
  (recreating on normalization), discards staged lines whose id is
  absent, and leaves records latent while their line is mid-drag.
- onConnectionRequest and its event/result types are deleted (veto →
  isValidConnection; payload channel → canonical LineRecord.payload /
  line.setPayload). Undeclared engines get a console.warn bridge and
  uncontrolled gesture behavior until the Phase 4 adapters make the
  declaration ergonomic. Legacy EdgeSync engines (controlled, but no
  dispatch capability on the bridge) keep their current gesture path
  until the 3f cutover.

New ut coverage: stage/adopt settle-in-place, rejection-by-inaction,
atomic replace with no local eviction, disconnect propose + revert/
accept. Verified: tsc, check:adapters (0 errors), ut 46/46, all six
snapline e2e suites green (55 tests).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
EdgeSync is gone; the controlled protocol is the one controlled path:

- Delete edge-sync.ts, EdgeSync.tsx, EdgeSync.svelte and every Edge*
  export. The reconciler slot sheds its legacy name: EdgeSyncLike →
  GraphReconcilerLike, mirror.edgeSync → mirror.reconciler, with the
  local-topology notify hooks now optional (the controlled reconciler
  ignores them).
- New ControlledGraph.svelte / ControlledGraph.tsx adapters: props
  { lines, onLineChangeRequest, onDiagnosticsChanged? }. They attach the
  bridge (which declares "controlled"), push setCanonicalGraph on prop
  change, and guarantee the post-request microtask push from a live
  snapshot source — acceptance, normalization, rejection, and
  rejection-by-inaction all resolve from the next snapshot.
- Property propagation removed: the node prop bag, setProp graph walk,
  addSetPropCallback (core + Svelte Node export), the connector prop
  aliasing and gesture copy are gone. Dataflow is application-owned.
  Demos keep local state; the prop-walk unit test is deleted.
- The edges demos (Svelte + React) are rewritten on the controlled
  contract: stable line ids, `${node}:${port}` connector ids, one
  handler applying atomic add/remove/update proposals, reject toggle by
  simply not applying. Both e2e suites rewritten accordingly — notably
  a full replace-oldest input now asserts ONE atomic request
  ("connect:a->b|replace:-a->b+c->b") instead of the old ordered
  intents, and the no-flicker accept test passes because settling
  reuses the staged mirror.

Phases 1-3 complete. Verified: tsc, check:adapters (0 errors), ut
44/44, all six snapline e2e suites green (55 tests) — including the
rewritten controlled-edges suites.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Eliminates the uncontrolled graph mode (user decision): the framework
document is the single source of truth for what exists — nodes,
connectors, and lines. "Controlled" no longer needs a qualifier.

Core:
- endDragOutLine has one path: gesture drops route through the request
  protocol; with no graph owner attached there is no document to
  propose to, so the drop warns once and discards the preview (a
  reconnect pickup likewise — there is nothing to revert from).
- connectToConnector is deleted outright (the settle/record paths cover
  every remaining use); deleteLine / deleteAllLines /
  disconnectFromConnector / createLine are @internal
  reconciler/teardown operations, no longer part of the public surface.
  canConnect(target, line?) stays as a read-only query.
- The whole authority apparatus goes: setGraphAuthority, GraphAuthority,
  SnapLineAuthorityError, GraphMirror.authority, the imperative-command
  gates, and the undeclared-engine warn bridge. attachControlledGraph
  simply installs the bridge. ConnectionOrigin loses "programmatic".
- Future vanilla-JS support is a pure-JS graph-owner module driving the
  same attachControlledGraph/setCanonicalGraph contract (documented in
  the Phase 5 migration notes).

Demos: every gesture demo becomes a small controlled app — a DemoGraph
owner (svelte component / react component) holds the line document and
accepts each atomic proposal; connectors carry stable `${node}:${port}`
ids (SimpleNode/ResizableNode in both frameworks, website connections
demo). Replace-oldest inputs now produce "replace" requests the demo
applies — the e2e eviction/reconnect assertions pass unchanged.

Tests: shared harness gains createControlledHarness / gesture drivers /
mountConnectedPair; connector-config and graph-mirror specs move onto
the record/request paths; the authority test becomes a
no-graph-owner-discard test.

Verified: tsc, check:adapters (0 errors), ut 44/44, all six snapline
e2e suites green (55 tests).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
tfukaza and others added 2 commits July 25, 2026 20:12
Geometry is SnapLine-owned ("just a visual cue" — user decision): no
geometry authority modes, no request variant. The drag/resize split
disappears into one observation:

- NodeGeometry { node, x, y, width, height } and GeometryChangeEvent
  { nodes } replace NodePosition/NodeDragCommitEvent.
- onGeometryChanged replaces onDragCommit + onResizeCommit in
  NodeCallbacks: a group/multi-select drag stays one event (every moved
  node, now with width/height); a resize reports a single-entry batch.
  emitDragCommit becomes emitGeometryChange. onSizeChange (live) and
  onResizeHandleChange keep NodeResizeEvent.
- Node and Group adapters (svelte + react) expose the single
  onGeometryChanged prop through the existing callback-merge wiring.

Verified: tsc, check:adapters (0 errors), ut 44/44, all six snapline
e2e suites green (55 tests).

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

- Node and Group adapters (svelte + react) gain the `id` prop →
  NodeConfig.id (minted when omitted; supply for persistence), closing
  the identity gap with Connector.
- Adapter line lists key by the stable LineMirror.lineId instead of the
  engine-internal BaseObject.id; SVG marker ids follow
  (`arrow-${lineId}`) and the Line components render
  data-line-id={lineId} for e2e/consumer targeting.
- Package export maps catch up with the public API: core gains
  ./graph-mirror, ./line-reconciler, ./geometry subpaths; react gains
  ./ControlledGraph; svelte gains ./ControlledGraph.svelte.
  validate:packages green (10 packages).
- React demo's stale EdgeSyncNode name → GraphNode.

Verified: tsc, check:adapters (0 errors), ut 44/44, all six snapline
e2e suites green (55 tests).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
tfukaza and others added 3 commits July 25, 2026 20:25
- Sibling controlled engines are fully isolated: same connector/line ids
  settle independently and a gesture cannot discover the other engine's
  targets.
- A gesture reconnect preserves the line's stable id AND mirror
  instance: pickup from one input, drop on another, apply the endpoint
  update — same LineMirror settles onto the new target.
- A bulk load (open batch + setCanonicalGraph + connectors mounting
  across multiple tasks) runs exactly one reconciliation pass at the
  outermost end(), with rule violations surfacing as diagnostics in
  that same single pass.

ut 50/50 green.

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

One reference for consumers crossing the Cleanup re-architecture: the
Mirror renames, the capabilities/maxConnectors/allowDragOut → rules
mapping, EdgeSync → ControlledGraph (stable-id LineRecords, atomic
requests, id adoption, rejection-by-inaction), the imperative-API
removal with the vanilla graph-owner-module pattern, stable identity
and its persistence contract, property-propagation removal, and the
consolidated geometry observation.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The new API was 100% undocumented; every doc now describes the shipped
system:

- current-architecture.md rewritten end to end: always-controlled
  topology, the ControlledGraph protocol (gesture→request→adopt
  sequence, reconcile-pass flowchart), ConnectorRules and the two
  admission paths, SnapLine-owned geometry with the batched observation,
  GraphMirror registries and what stays on global.data, scheduler/
  batching, diagnostics, the real public API surface and subpaths,
  ownership matrix, source map.
- planned-rearchitecture.md pruned per its own contract: records the two
  implementation amendments (uncontrolled mode eliminated; geometry
  modes dropped) and keeps only the final simplification review with its
  known consolidation candidates.
- ownership-specification.md: conformance snapshot now reflects full
  conformance; every "open decision" answered as decided-and-shipped;
  dead-API wording fixed without touching the normative requirement
  sections.
- AGENTS.md: GraphMirror/query() and ControlledGraph/LineReconciler
  sections replace NodeManager/EdgeSyncController; property-system
  section deleted; connection rules rewritten around ConnectorRules;
  globals and file-structure sections updated.
- Guides + references + READMEs: setup examples rewritten on the
  controlled protocol in all three frameworks (keeping exactly two
  framework-tagged blocks per the docs e2e), core-concepts and
  surface-connectors teach rules/ControlledGraph instead of
  maxConnectors/onConnectionRequest, selection-resize uses the batched
  onGeometryChanged, reference tables gain ControlledGraph/id/rules and
  the query() facade, subpath lists updated.
- Website shiki highlighter falls back to plaintext for unknown fence
  languages — the design docs' mermaid blocks were breaking the ENTIRE
  docs build with a 500 (pre-existing on main); the docs e2e suite now
  passes except one pre-existing llms.txt heading drift
  ("SnapEngine Core Documentation" vs the spec's expectation) that
  predates this work.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The re-architecture reviewed as a new codebase:

- One structural admission check: #admitsEndpoints (roles, capacity with
  the in-flight line excluded, parallel rule) with an allowReplacement
  flag replaces the parallel gesture/record implementations; predicates
  live in #predicatesAdmit. Gesture admission composes both; the record
  paths call them strictly.
- #settlePreviewLine's replacement-eviction block removed: evictions
  ride the atomic request and the reconciler prunes accepted removals
  before settling, so a settling line's target has room by construction.
- The reconciler slot's local-topology notify hooks are gone — the
  controlled reconciler never consumed them, so the emit sites stop
  forwarding and GraphReconcilerLike shrinks to reconcile()/dispatch.
- Dead code out: _endLineDragCleanup (no callers), the write-only
  #selected field (selection truth is GraphMirror.selection).
- Remaining single-class _-prefixed fields converted to # privates in
  node.ts and select.ts; _connectors documented as the one deliberate
  cross-class field. pendingGestureRequest evaluated and kept (catches
  stalled adapter pushes); the four adapters' callback-merge blocks
  deliberately NOT extracted into a shared helper — a parameterized
  cross-framework helper would trade real clarity for deduplication.
- New informational benchmark tests/ut/snapline-perf.spec.ts (no timing
  assertions): 200 nodes / 150 lines — cold reconcile ~4ms, warm ~0.1ms,
  candidate discovery ~0.34ms per pointer move, bulk load ~3.5ms.
- planned-rearchitecture.md closed out as the decision record.

Final matrix: tsc, check:adapters (0 errors), ut 50/50, all six
snapline e2e suites green (55 tests), validate:packages (10), docs e2e
green except the pre-existing llms.txt heading drift.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@tfukaza
tfukaza merged commit df8cd62 into main Jul 27, 2026
3 checks passed
@tfukaza
tfukaza deleted the Cleanup branch July 28, 2026 01:35
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