Conversation
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>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
- 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>
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>
- 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>
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.
No description provided.