6.5 KiB
Headless Canvas: portable ESM preview
Build from the Endoleap checkout with npm run build:headless. The generated
.headless-dist directory is a private package; nothing is published to a registry.
Copy it elsewhere, or use npm pack --offline --ignore-scripts inside that directory.
Public exports:
| Import | Responsibility |
|---|---|
@endoleap/headless-canvas or /core |
Document, registry, capabilities, transactions, history, sessions, geometry and queries |
/runtime |
Project/Workspace/Host, storage and service port types, recovery, scoped loader and compute/display pools |
/service |
Foundry protocol codecs and transport adapters; credentials remain in the host |
/browser |
Explicit IndexedDB/Web Locks and module Worker adapters |
The portable entries have no React, Next, React Flow or other runtime package
dependency. They use standard platform APIs such as AbortController, Blob, URL,
TextEncoder and Web Crypto. The sample runs on Node 22+. Browser adapters require
their browser APIs; importing /runtime does not import the browser adapter.
Run node consumer.mjs inside the generated package. It uses a custom counter
component, rejects an atomic invalid transaction, commits edits, undoes/redoes,
cancels a gesture preview, deep-copies through the runtime, and reopens the engine.
The storage and protocol-2 transport are in-memory examples. Reopening demonstrates
the codec and host contract, not cross-process crash durability or a deployed server.
npm run test:headless:package builds, packs/unpacks into an isolated directory,
blocks framework imports and browser-only globals, runs the sample, checks Node
TypeScript consumers without the DOM library, and exercises compiled browser adapters
and module Worker URLs in isolated Chromium. The browser checks load the emitted JS
directly, without TypeScript stripping or a Next bundler.
This preview does not yet package the React renderer/bindings. Their legacy projection DTO types still require an explicit adapter boundary. No registry publication, release support promise, server deployment or full-goal completion is implied by this local package.
Experimental partial-model runtime: pass a validated prefix plus loading in
EngineOptions. Version 1 uses recovery format 7; version 2 carries snapshot-bound direct canvas
membership counts and uses format 8. Both keep the scheduler held. Version 3
uses format 9 and retains runtime.loadingRead separately from the moving ACK base.
Loaded non-reference data fields and node geometry can be edited and undone; creation,
deletions, ownership/reference changes, uploads, imports and AI admission wait.
engine.loading.accept(document, versions, state) extends the captured prefix without
replacing local edits. engine.loading.complete(document, versions, {sourceToken})
validates the full capture and releases scheduling only after its cut is durable.
Use {fresh:true} only for an explicitly fresh complete read; conflicts require the
existing reviewed recovery flow. engine.loading.resume() coalesces a fresh complete
read through the project's persistence adapter and persists completion before resolving.
workspace.resumeLoading(projectId) delegates to that owner and reports current-identity
failures. Closing the project aborts the read and saves the recoverable prefix; detaching
a view does not close its engine.
Endoleap's page now opens the same project engine for a validated prefix, allows the restricted field/geometry edits above, coalesces incoming prefixes, and preserves its textarea, history and viewport when the full model arrives. App navigation cancels the view's stream and asks the retained project owner to complete through a fresh read. Reload recovers the durable prefix when that project is opened again. This is not a persistent HTTP cursor or a workspace-wide startup scan. Version-3 scoped ACK/chunk coordination is described below; broader structural permissions remain unfinished.
Scope-aware queries are obtained from engine.editor.getQueries() and share the
same publication boundary as the root. lookupElement(id) returns present,
absent (complete project), or unloaded; a missing ID in a partial model is not
proof of absence. membersResult(canvasId) adds a completeness flag for direct
members; childCanvasesResult covers published topology, while incidentResult
requires the complete project because another owner may contain an incoming edge.
Legacy array-returning methods enumerate loaded records only. Hit-query completeness
also includes scope coverage. Derived indexes and coverage views are not persisted.
An optional canvasElementCounts field in the version-1 stream header allows new
clients to certify individual scopes. Missing metadata retains legacy conservative
coverage. Counts include nodes and connections; they must cover exactly the declared
active canvases, sum to the total, and never be exceeded by received records. A scope
is ready only after its full count has entered the dependency-closed published prefix.
Version-2 loading permits connection.path edits and history in a certified owner
scope; endpoint, ownership, creation and deletion edits still wait. Scope completion never replaces terminal/EOF validation for the whole project.
Versions 1/2 continue to hold network scheduling. In version 3, mutable operations
wait in the scheduler's derived admission set until every participant scope is
complete and its root is durable; unrelated certified scopes can send meanwhile.
The normal frozen-body/key, writer claims and pre-dispatch durability barrier apply.
runtime.loadingRead retains the original source document and version vector. Incoming
prefixes and same-source completion validate against that capture; scopes with advanced
ACK versions retain their newer acknowledged records, then pending intents replay.
Workspace.open(document, versions, {sourceToken}) completes that original stream,
even when its captured version is now older than a scoped ACK. An unrelated fresh
read waits for outstanding receipts and uses a scheduling hold while capturing and
persisting its base; edits during the read remain local and are replayed.
Recovery format 9 encodes both baselines and original frozen requests. Unknown results still query/retry the same body/key after reopening. Partial-source completion clears the retained read baseline and forces a full checkpoint without older partial journal dependencies. Creation, deletion, owner/endpoint/reference changes, uploads and AI remain gated during partial loading; broader structural scope admission and real Foundry deployment/integration remain unfinished.