789 lines
52 KiB
Markdown
789 lines
52 KiB
Markdown
# 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 |
|
||
| `/bindings` | Portable projection, renderer descriptors, caches, connectors and hit queries |
|
||
| `/react` | React Provider and selector, element, action, mode, gesture, operation, asset and sync hooks; no React Flow runtime dependency |
|
||
| `/react-flow` | Explicit client adapter, renderer command context and framework node/edge types |
|
||
|
||
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.
|
||
|
||
The React adapter has an explicit entry and optional peer dependencies; portable
|
||
consumers do not load it. 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 and later scope-based structure/import permissions are described below.
|
||
|
||
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.
|
||
|
||
|
||
Loading version 4 adds an optional stream-header `elementOwners` manifest and uses
|
||
recovery format 10. It covers every active source ID, including unloaded records,
|
||
and must match both per-owner counts and each received record. New IDs must also
|
||
avoid the captured tombstones; new canvas IDs avoid all captured version keys.
|
||
Without this manifest the page retains version-3 field-only admission.
|
||
|
||
In version 4, complete owners can create elements and a frame/child-canvas pair,
|
||
undo/redo them, redirect loaded connections, and move/remove objects born after the
|
||
capture when the affected owners are ready. Existing-element removal/reparenting
|
||
requires the entire captured source closure; a missing incoming edge is not proof
|
||
that no edge exists. Declared references are checked against the transaction's final
|
||
model. Shared loaded asset references are permitted; new uploads, generation,
|
||
complete export remain gated in version 4. Version 5 adds import/copy admission as described below.
|
||
|
||
ACK validation reconstructs the exact protocol-2 effects of applied logical intents
|
||
on the original source; late source blocks cannot erase births, restorations or tree
|
||
changes. Both the source capture and exact request identities recover through format
|
||
10. Pure intent proofs and applied prefixes are cached by immutable source/registry/
|
||
intent identities; changing the source invalidates those proofs.
|
||
|
||
Query coverage also exposes an immutable captured ID set. A missing ID outside that
|
||
complete namespace can be reported absent before all payloads arrive; an ID inside it
|
||
remains unloaded until received. An old tombstone alone cannot classify a captured
|
||
restored ID as absent. Absence does not make an old ID available for fresh creation.
|
||
The identity manifest costs O(N) header bytes and memory and still obeys the existing
|
||
8 MiB line / 64 MiB response limits; this does not make database acquisition lazy.
|
||
|
||
Loading version 5 (recovery format 11) also retains the complete captured
|
||
`sourceCanvases` topology from the existing stream header, even for partitions
|
||
whose entry payloads are not published yet. With counts and `elementOwners`, the
|
||
page now selects this version. Existing v1–v4 recovery images retain their original
|
||
contracts; future format 12 is rejected.
|
||
|
||
`engine.imports.canStart(canvasId)` and `canDuplicate(selection, {viewCanvasId})`
|
||
return availability for UI controls, including policy and lifecycle checks. The
|
||
actual methods recheck admission. Native NDJSON import requires a complete target
|
||
owner. Deep copy requires complete selected subtrees and captured ancestor owners,
|
||
so upper-canvas incoming edges are copied from the same original endpoint into
|
||
the new partition. A declared but unpublished child is not an empty partition.
|
||
Unrelated incomplete partitions do not prevent these jobs from starting.
|
||
|
||
The existing ImportJob/source Blob/runner, history and scheduler are reused.
|
||
`source.loadingAdmission` records the source token, latest captured logical
|
||
operation sequence, and sorted required canvas IDs. On partial recovery it must
|
||
match the retained read and proved scopes. Before copy planning/resume, the source
|
||
topology is checked against that historical logical cut, independently of newer
|
||
source chunks and local edits. Source SHA, stable mappings and generation checks
|
||
continue to protect payloads and late callbacks. This is provenance metadata,
|
||
not another queue or a lock held during parsing.
|
||
|
||
Pause/reopen/resume retains source bytes and identities. Cancellation keeps already
|
||
published content and does not restart; undo remains independent of the source
|
||
parser. All content sends through the normal durability and frozen-request barriers.
|
||
Full-source completion removes the loading marker and read baseline without
|
||
replacing job/history state. In version 5, new image uploads and generation remain gated while
|
||
loading; loaded shared resource references obey the existing structural rules.
|
||
|
||
Loading version 6 (recovery format 12) admits local images in complete owners.
|
||
`engine.canStageFiles(canvasId)` supplies UI availability; `stageFiles` rechecks
|
||
actual transaction changes and policy. Existing field/geometry and import/copy
|
||
contracts continue to apply, including copying a pending image by the same
|
||
ResourceId rather than starting another upload.
|
||
|
||
`ResourceRecord.loadingAdmission` records `{version:1, sourceToken, sourceURL,
|
||
canvasIds}`. The resource's original upload OperationId precedes its first document
|
||
use. Validation checks that first use's exact owner set, complete captured/born
|
||
scopes, and the adapter's URL interpretation on reopening. New resource references
|
||
are admitted from these proved identities and the current staging transaction;
|
||
arbitrary resource IDs/URLs remain rejected. The proof and file bytes use the
|
||
existing project recovery store, not a second queue or workspace-wide AssetDepot.
|
||
|
||
Working content, history, upload intent and Blob bytes retain their existing atomic
|
||
durable cut. An upload has no canvas participants, so its own admission does not
|
||
claim a fake canvas writer; it still waits for the root/file/request barriers.
|
||
Document requests depend on stored upload results and keep normal owner/version
|
||
claims. Upload receipt validation does not advance document versions. A lost ACK
|
||
reopens the same frozen key/body and queries it before another document send.
|
||
|
||
Whole-source completion preserves pending/stored resources and history. Default
|
||
image sizing and display handles reuse the existing decoder/cache lifecycle.
|
||
Undo/redo, source failure, quota retry and late chunks do not re-upload bytes or
|
||
restore deleted incarnations. Earlier loading/recovery versions retain their old
|
||
restrictions; current recovery compatibility is described below. Generation, cross-project durable depot
|
||
sharing and broader lifecycle/acceptance work remain separate unfinished goals.
|
||
|
||
The package now exposes `/bindings` for pure projection, renderer descriptors,
|
||
geometry/projection caches, hit queries, connectors and resource-view handles.
|
||
`projectCanvas` consumes the captured Document/Registry/queries and optional gesture
|
||
geometry. It returns view DTOs; it does not own another document, history or queue.
|
||
Projection DTOs and compatibility card/edge hints no longer import React Flow types
|
||
or the legacy element classes. Legacy views re-export the same shared data types.
|
||
|
||
`/react-flow` is the explicit client/framework entry. It exports renderer factories,
|
||
the command context/hook, and `ReactFlowProjectionNode`/`ReactFlowProjectionEdge`
|
||
structural adapter types. React, React DOM and React Flow are optional peers at the
|
||
package level, required when a host opts into this entry. Core/runtime/service and
|
||
pure bindings remain usable without them. The declared floors are React/React DOM
|
||
19.3.0 and React Flow 12.12.1; tests use the locally installed compatible versions,
|
||
not a claim to have tested every allowed version.
|
||
|
||
A host retains the immutable renderer catalog and creates view-local projection
|
||
caches; installed components submit commands through its scoped Provider. Attaching
|
||
or removing UI does not replace the ProjectEngine or terminate durable jobs.
|
||
Compatibility DTOs are rendering hints, not the model schema or a migration format.
|
||
The existing application now uses the same explicit framework entry.
|
||
|
||
The packed Node consumer projects and hits its custom counter using only public
|
||
exports, with DOM globals and external runtime dependency resolution blocked. Its
|
||
NodeNext type check excludes DOM libraries. A separate peer consumer renders a
|
||
registered component, verifies its command context, and checks typed NodeProps and
|
||
EdgeProps with the declared peers. Browser tests still execute the compiled ESM
|
||
storage and Worker artifacts independently of Next.js/TypeScript stripping.
|
||
|
||
|
||
Workspace asset depot and file storage:
|
||
|
||
`createWorkspaceRuntime` owns one depot when `StoragePort.assetDepot` is provided.
|
||
`await engine.adoptResource(id, (resource, tx) => { ... }, {signal})` obtains fresh
|
||
server permission, verifies the original bytes, and runs a synchronous editor
|
||
transaction. The callback uses the existing `resource.id` / canonical `resource.src`
|
||
in a newly allocated element; it also receives server dimensions in `resource.asset`.
|
||
The project stores a `SharedAssetBinding`, not a second upload. Binding, document,
|
||
history, ledger, shared bytes and pins cross the same durable barrier. The returned
|
||
local `Result` does not imply durability or backend completion. Async or failed
|
||
callbacks leave no partial binding. The Foundry authority requires the matching
|
||
permission-checked asset metadata endpoint; cached metadata is not an authorization.
|
||
|
||
Bindings use recovery envelope 13 (including partial loading v6); readers retain
|
||
formats 1–12 and reject unknown future formats. A reopened binding can display
|
||
shared local bytes, and undo/redo reuse the asset without uploading again. The
|
||
workspace closes projects before its depot; retiring the identity fences late work.
|
||
|
||
The browser adapter uses IndexedDB version 4. After a proven stored-upload cut, a
|
||
project's `files` row holds an opaque pointer to digest-addressed `asset-bytes`.
|
||
Unconfirmed uploads and import source files keep their original Blob rows. Use
|
||
`readFile` / `scanFiles`; direct assumptions that every raw files-store row is a
|
||
Blob are invalid. Logical reads preserve the original local MIME, even if the
|
||
server detected another MIME. Existing v3 databases upgrade without dropping
|
||
checkpoints or original files and promote on a subsequent matching Root commit
|
||
(or an idempotent reindex). Older DB readers reject v4. Blob URLs and already
|
||
returned Blob objects live independently of a later persistent eviction; actual
|
||
disk reclamation remains the browser's responsibility.
|
||
|
||
Resource identities retain immutable metadata after byte eviction. Complete,
|
||
revision-matched durable manifests from every project in the workspace protect
|
||
closed editors; missing/stale/opaque coverage prevents shared deletion. Distinct
|
||
resource IDs may own the same digest; the shared byte row disappears only when
|
||
its last live identity is retired. An older Root cannot release temporary bytes
|
||
that belong to a newer adoption revision. Collection is explicit and local; this
|
||
preview does not enable background deletion on Foundry or claim the full retention,
|
||
permission-race and product acceptance matrix is finished.
|
||
|
||
|
||
React bindings:
|
||
|
||
Wrap descendants in `CanvasProvider` with an existing `engine` and, for session
|
||
hooks, an already attached `viewId`. The Provider borrows both; unmounting it does
|
||
not close the project or detach a view owned by the host. Explicitly unmount the
|
||
surface, detach its view, and close the engine at their respective lifetimes.
|
||
Import from `/react` for React only, or the re-export in `/react-flow`.
|
||
|
||
- `useCanvas(selector, isEqual?)` observes the one immutable project Root. Default
|
||
equality is `Object.is`; choose an appropriate comparator for allocated results.
|
||
Snapshots are cached per selection and unchanged selections do not notify React.
|
||
- `useElement(idOrTarget, selector?, isEqual?)` returns `present`, `absent`,
|
||
`unloaded`, or `stale`, or the supplied selector result. A Target includes
|
||
incarnation; a restored element does not silently retarget it. Custom selectors
|
||
use `Object.is` by default; provide a comparator for allocated field groups.
|
||
- `useActions(target)` exposes stable `rename`, `setData`, `resize`, `remove`, and
|
||
`moveTo` facades over normal editor commands. Optional command settings retain
|
||
label/grouping/history semantics. Call actions from events, not during render.
|
||
- `useMode(target)` returns the current view's mode and `setMode`; its session
|
||
subscription is keyed by view and target. It never writes document/history
|
||
state. Missing view context is an explicit error.
|
||
- `useGestureBindings(target)` returns effective geometry, active ownership and
|
||
`begin/update/commit/cancel`. A token protects the lease; cleanup cancels only
|
||
its own lease. Controller replacements keep independent token slots. Preview
|
||
geometry remains shared across views; active ownership and controls belong to
|
||
the owning view. Host bindings still handle pointer capture and events.
|
||
- `useOperation(id)` observes both the logical record and its frozen request;
|
||
request-only changes are included without observing unrelated Root fields.
|
||
- `useAsset(id)` resolves a releasable display handle, returns metadata plus
|
||
loading/ready/error/url/retry, and releases on ref/owner change or unmount.
|
||
Ready means the display handle resolved; it does not prove upload ACK, image
|
||
decoding or a successful subsequent fetch of a remote fallback URL.
|
||
- `useSyncStatus(canvasId?)` reports pending/blocked/unknown work plus loading,
|
||
dirty and persistence errors. Durability is project-wide even when filtering
|
||
operations to one canvas. It does not call an undurable Root synced. Pending
|
||
counts logical ledger work; running generation/import job progress is observed
|
||
separately from document synchronization.
|
||
|
||
`engine.getDurability()` now returns a stable readonly snapshot including closing,
|
||
closed and an optional error. `subscribeDurability` observes successful cuts,
|
||
failures and closure without creating another business Root. Hooks and the Provider
|
||
own no second document, ledger, network queue or storage lifecycle. Source selectors
|
||
still run on Root notifications; this is not a claim of complete per-field indexing.
|
||
|
||
A React renderer may use these hooks once its real engine exists. For the early
|
||
read-only preview before an engine opens, declare a separate `preview` renderer in
|
||
its `ReactRendererDefinition`. The assembly exposes `previewNodeTypes` and
|
||
`previewEdgeTypes`. An omitted preview gets a passive preparing placeholder; the
|
||
app never constructs a fake editor merely to satisfy the hooks. Preview functions
|
||
must render the supplied records without requiring CanvasProvider. Built-in preview
|
||
renderers are registered explicitly. Framework definitions never enter core data.
|
||
|
||
Presentation scheduling:
|
||
|
||
`createFrameQueue(clock, publish)` accumulates dirty keys in a Set and requests
|
||
one frame. `flush()` publishes now; reentrant invalidations wait for the next
|
||
frame. `dispose()` cancels pending work, including late canceled callbacks.
|
||
This queue never holds commands, undo units or requests. `createSessionFrameSource`
|
||
wraps a session source with a per-subscription queue; reads still return the
|
||
current session immediately, while preview notifications coalesce per frame.
|
||
The application uses it for CanvasView sessions. Pointerup reads its final event
|
||
and commits through the engine synchronously; persistence and Root notifications
|
||
are not frame-throttled. This does not implement paint extent indexing or selective
|
||
per-element drawing: dirty keys currently schedule a view notification.
|
||
|
||
Node paint overflow:
|
||
|
||
`createPaintIndex` owns a rebuildable view-space grid of rectangles, with bounded
|
||
cell expansion and oversized fallbacks. `projectedPaintOverflow` asks node geometry
|
||
for the `paint` role separately from logical/hit geometry, transforms its bounds
|
||
into scene space, and reports failed handlers conservatively. CanvasView queries
|
||
the overflow index against the live viewport; visible overflow or unknown bounds
|
||
suspend native culling so extended paint is retained. This currently disables
|
||
culling for the view rather than selectively retaining one node. Geometry
|
||
extensions must declare their paint extent; undeclared DOM overflow cannot be
|
||
inferred. Edge/overlay paint indexing and region-based drawing remain separate work.
|
||
|
||
`createProjectedPaintIndex` retains the grid per engine/view and caches each node's
|
||
paint result by projected node identity, immutable record identity and canvas
|
||
transform. Unchanged nodes reuse their geometry result; removed nodes release
|
||
cache entries. Viewport queries reuse the grid. `clear()` discards it. The cache
|
||
still scans projected nodes and synchronizes rectangle membership; it is not a
|
||
claim of constant-time full-frame updates or complete region-based drawing.
|
||
|
||
`createJsonArrayImportSource` is a seekable parser building block for a UTF-8 JSON
|
||
array, with byte cursors, bounded record payloads, structural depth limits and
|
||
abort/yield checks. It uses parserVersion 3 and can feed a host-managed generic
|
||
ImportRunner. It does not yet interpret the exported document envelope, and
|
||
ProjectEngine now accepts native record arrays through `imports.startJsonArray(bytes, canvasId)`; the file picker routes `.json` to it. Exported document envelopes use the separate `imports.startDocument` path described below.
|
||
A malformed structural tail ends with an explicit failure; previously emitted
|
||
records remain available. Empty source bytes reject, while `[]` is a valid empty
|
||
array. Array record errors are distinct from document schema/reference failures.
|
||
|
||
JSON array jobs preserve the original Blob and integrity digest, parserVersion 3,
|
||
byte cursor, stable IDs, pending dependencies and failures using the same ImportJob
|
||
and commit boundary as NDJSON. Such roots use recovery envelope 14; readers accept
|
||
1–16 and reject 17. Downgrading an array-bearing image to 13 rejects. This changes
|
||
neither IndexedDB schema v4 nor the server wire protocol. Native array items have
|
||
the same `{kind,key,record}` schema as NDJSON; `$target` denotes the chosen canvas.
|
||
|
||
`imports.startDocument(bytes, canvasId)` accepts the toolbar's exported document
|
||
format v1. The JSON picker distinguishes object envelopes from record arrays.
|
||
Source canvas ownership and references map to fresh IDs; its root maps to the
|
||
explicit target, and tombstones are not copied into the target. The original
|
||
file/digest and parserVersion persist. New jobs use parser 5: after the version,
|
||
project and canvas headers, records are scanned and decoded on demand rather than
|
||
indexing the full elements array first. The ordinal cursor resumes against the
|
||
same bytes. Legal prefixes survive malformed array/footer tails with an explicit
|
||
partial result. Headers following the elements array require indexed preparation
|
||
to establish ownership first. Legacy parser 4 jobs keep their indexed interpreter.
|
||
New document jobs use recovery v16; legacy document jobs keep v15 (readers accept
|
||
1–16, reject 17), without changing DB v4 or the wire protocol. Resource-bearing
|
||
records use fresh authorization when the host provides a depot and authority;
|
||
missing capabilities or denied resources produce explicit partial results.
|
||
Complete service acceptance remains pending. Source SHA verification still reads
|
||
the original file before the runner starts; this removes the second full range
|
||
scan, not the remaining integrity startup barrier.
|
||
|
||
ImportRunner hosts may provide async `beforeCommit(units, job, signal)` for read-only
|
||
preparation such as resource permission checks. It returns Availability; a denial
|
||
uses normal independent-group failure isolation. A successful result still must
|
||
pass cancellation/hold/generation/status checks before the synchronous commit.
|
||
No document, history or committed source progress advances while it waits. The document import host now uses this hook to authorize/adopt resource references as described below.
|
||
|
||
Document resource adoption now resolves fresh permission through the workspace
|
||
AssetDepot before each candidate commit, verifies original bytes/digest, and
|
||
canonicalizes `assetId` image URLs through the current authority. It retains at
|
||
most 32 MiB of prepared bytes per group (a single in-progress authority read may
|
||
add its separately limited 20 MiB); over-capacity groups fail explicitly. Authority
|
||
or depot absence remains an explicit partial-record failure. Cancellation and
|
||
generation fences run before the synchronous transaction. New bindings, adopted
|
||
byte ownership, elements, history, ledger and job progress publish together;
|
||
rejected transactions restore prior temporary bytes and leave no binding. Existing
|
||
owned resources must match immutable stored metadata; no fake upload is created.
|
||
Cached bytes never replace fresh permission resolution. Recovery remains v15.
|
||
|
||
`projectedConnectionPaintOverflow` adds conservative scene-space bounds for cubic
|
||
control hulls plus half stroke. It uses the same geometry protocol and connector
|
||
adapters as hit testing, with native handles for custom record renderers. Default
|
||
work limits are 4096 edges and 4096 total cubic segments. Missing geometry,
|
||
invalid values and exhausted budgets remain unknown and suspend native culling;
|
||
they are not treated as empty paint. CanvasView queries a separate overflow grid
|
||
against its viewport. This currently retains the whole view when needed; it is
|
||
not selective edge mounting, exact curve bounds, overlay indexing or dirty-region
|
||
rendering. Custom renderers must supply a compatible geometry protocol.
|
||
|
||
`createOverlayIndex(viewId)` owns ephemeral screen-space placements keyed by
|
||
instance ID, each with a target incarnation, placement name, measured bounds,
|
||
focus and composition state. Registration returns an update/release lease; a
|
||
late old lease cannot remove its replacement. `visible(rect, currentTarget)` also
|
||
retains focused/composing editors outside the rect and rejects stale targets.
|
||
No overlay state enters the document, history, ledger or recovery image. The
|
||
application registers frame settings and node style toolbars through an optional
|
||
DOM context, including portalled content; legacy and pre-engine renderers use no
|
||
registry. Other overlays need explicit registration. Current retention still
|
||
suspends culling for the view; it does not selectively mount each overlay target.
|
||
|
||
Pointer transport helpers (`ownsPointerCapture`, `acquirePointerCapture`,
|
||
`releasePointerCapture`) keep DOM capture separate from editor gesture ownership.
|
||
Move/up/cancel must match the active pointer ID. Lost capture also matches its
|
||
capture target and cannot cancel a target that already owns a newer platform
|
||
capture. Capture acquisition failure cancels the acquired edit lease; detached
|
||
release is idempotent. Node transforms and edge controls now share these checks
|
||
and cancel/release on blur or unmount. Native React Flow drag handling remains an
|
||
adapter path; these helpers do not replace the full tool/input controller system.
|
||
|
||
While a control owns an active capture, foreign pointer events reaching that
|
||
control are consumed without changing the lease; they do not fall through into
|
||
pane navigation/connection handlers. Outside an active control, unclaimed input
|
||
continues normally. Node drag cancellation only addresses its own pointer/token.
|
||
|
||
The view projection cache now retains node/edge array identities when all retained
|
||
items and their order are unchanged, so metadata-only roots do not hand React Flow
|
||
new collections. A changed node replaces its node array while an unchanged edge
|
||
array remains stable. Document and transform provenance still come from the fresh
|
||
projection; lifecycle/scope changes and `clear()` invalidate the retained arrays.
|
||
Projection calculation still scans the scene. This is not selective mounting or
|
||
complete dirty-region rendering.
|
||
|
||
Additional node/overlay retention now checks whether the target is already kept
|
||
by native rectangle visibility (including initial measurement/dragging). A toolbar
|
||
on an already visible node no longer disables culling for every other node. Only
|
||
an otherwise offscreen target that needs extra paint/overlay retention uses the
|
||
view fallback. Focus/IME and gesture safeguards remain. This fixes a measured
|
||
regression where selecting a frame mounted all 2003 nodes after a large copy.
|
||
|
||
Node paint signatures now compare effective geometry, lifecycle, owner, component,
|
||
transform and declared geometry `dataDependencies`, rather than immutable record
|
||
object identity. An ACK that reconstructs equal records no longer recomputes all
|
||
node paint bounds. Unrelated titles do not invalidate a protocol that declares
|
||
only another data field. Omitting dependencies conservatively compares all data;
|
||
components must declare every data field their geometry reads. The scene still
|
||
gets scanned, and connection paint currently has its separate computation path.
|
||
|
||
`createConnectionPaintIndex` retains curve hulls and grid membership across view
|
||
updates. Its signature includes the connection record/projection, numeric endpoint
|
||
anchors, transform and endpoint rectangles. Equal ACKs and unrelated node titles
|
||
do not call curve/stroke geometry again. Cached segment counts still consume the
|
||
query budget. Invalid geometry stays unknown and is retried; removed edges release
|
||
entries. Registry/project/canvas changes or `clear()` reset the cache. Hosts whose
|
||
geometry services capture changing external configuration must pass a changed
|
||
`geometryRevision` to `sync`. CanvasView now uses this index with native handles.
|
||
The edge list/signature comparison still gets scanned; this is not full dirty-region
|
||
drawing or selective offscreen mounting.
|
||
|
||
React renderer assembly now memoizes installed node/edge/preview components using
|
||
stable wrappers shared by original component identity. Explicit React.memo inputs
|
||
are preserved. `definitions[].render/preview` retain the original implementations;
|
||
`nodeTypes`/`edgeTypes` tables contain the compiled wrappers. Context and hook updates
|
||
still propagate normally. The projection cache separately shares equal data/style/
|
||
position fields across measurement-only updates without mutating input objects.
|
||
A real three-node fixture verifies that editing one node through ACK leaves an
|
||
unrelated node and nonincident edge's render function counts unchanged. Connected
|
||
edges may legitimately remeasure when a custom renderer's data changes; this does
|
||
not assume arbitrary custom Handle layouts stay fixed.
|
||
|
||
Node projections now carry readonly `__headlessTarget` display metadata with
|
||
id/incarnation/ownerCanvasId/display canvasId, written from model identity after
|
||
user data is projected. It is not a persisted element field. The application binds
|
||
legacy editing actions to that captured target through `useScopedNodeEditing`;
|
||
old incarnation/owner/placement and detached/closed-view callbacks do not write to
|
||
current records. Frame titles, node titles, text/style/branch/transform controls
|
||
use the scoped bridge. Legacy non-headless contexts retain their old interface.
|
||
Text grouping now records incarnation and drops obsolete groups. This is separate
|
||
from the public explicit-Target action API and gesture lease tokens.
|
||
|
||
|
||
`canvasKeyContext(nativeEvent)` classifies text and overlay ownership using the
|
||
composed path, including retargeted shadow inputs, native selects and registered
|
||
`data-canvas-overlay-view` placements. Pass it to `routeCanvasKey` with the native
|
||
IME state and the event's current `defaultPrevented` value. Window Escape fallback
|
||
runs in the bubble phase so target handlers can claim it first; it cancels only
|
||
this view's gesture. Style toolbars dismiss their own modes on unclaimed Escape,
|
||
while input controls retain priority. The helper needs no browser globals at
|
||
module load or when called with non-DOM ports.
|
||
|
||
|
||
Native import scanners share `ImportScheduling` (`now`, `yieldTask`,
|
||
`frameBudgetMs`, default 6 ms). Array/envelope scanners check elapsed time every
|
||
128 scan steps; NDJSON checks after each bounded chunk. A 16 KiB/step work
|
||
threshold also yields when the clock does not advance. Cancellation is checked
|
||
again after each real task yield. Byte/ordinal cursors, parser versions and source
|
||
fingerprints are unchanged. This is a soft budget: a bounded JSON decode is
|
||
indivisible. Legacy and out-of-order document headers retain indexed preparation;
|
||
canonical parser 5 headers allow progressive payload scanning.
|
||
|
||
|
||
The dedicated digest Worker uses native `crypto.subtle.digest('SHA-256', ...)`
|
||
for inputs up to 64 MiB. The browser pool admits two active Workers, each reading at most
|
||
64 MiB on this path. This bounds admitted input per Worker, not total browser
|
||
memory or native cleanup timing. Larger inputs or a missing native capability keep the 1 MiB
|
||
streaming SHA implementation. Main-thread fallback stays chunked/cooperative.
|
||
Native read/hash errors remain visible; they do not silently select another path.
|
||
Worker termination still owns cancellation. The full source check remains before
|
||
imported elements are published; no journal revisions or integrity gates are skipped.
|
||
|
||
|
||
Element hooks share one upstream editor listener per engine while subscribed.
|
||
An ID→bucket Map and listener Sets route explicit changes to their targets;
|
||
runtime-only cuts do no element lookup work. Document replacements check active
|
||
IDs, and loading coverage changes recheck missing lookups. Reads still consult the
|
||
current engine, including while unsubscribed. Buckets retain lookup values, not
|
||
per-element Root snapshots; the last listener releases its bucket and the final
|
||
bucket releases the upstream listener. No document, history or durable state is
|
||
copied into the bindings. Arbitrary `useCanvas` selectors remain Root observers.
|
||
|
||
```tsx
|
||
const title = useElement(target, view =>
|
||
view.status === 'present' && typeof view.element.data.title === 'string'
|
||
? view.element.data.title : ''
|
||
);
|
||
```
|
||
|
||
A change to another field on this target evaluates the title selector but does
|
||
not rerender its consumer. Unrelated target edits skip the selector entirely.
|
||
This is ID routing and value selection, not a static field dependency index.
|
||
|
||
|
||
Mode and gesture hooks pair keyed element lookups with small session selections,
|
||
instead of retaining a full Root/session pair per hook. All session channels in
|
||
an engine share one upstream listener. Mode buckets use view→target Maps;
|
||
gesture buckets use target IDs. Preview updates compare the active preview map,
|
||
then notify only changed targets. A WeakMap indexes each immutable target array
|
||
once for incarnation checks. Viewport/selection-only updates and connection
|
||
previews do not reevaluate these node hook selectors.
|
||
|
||
The pairing caches lookup/selection tuples; values stay fresh on direct reads,
|
||
and equal effective geometry/mode values suppress intermediate renders. The last
|
||
subscription removes its bucket and releases the upstream listener. A controller
|
||
identity change (engine/view/target/incarnation) creates a private lease slot, so
|
||
old cleanup cannot consume the replacement controller's token. This does not
|
||
change the one shared gesture owner, its history unit or durable task lifecycle.
|
||
|
||
|
||
Operation, asset metadata and sync-count channels share one upstream editor
|
||
listener per engine. Their dependency tuples contain only the tables they read
|
||
(and the loading flag for counts). Table identities use weak object IDs, so a
|
||
channel caches a primitive version signature and selected values rather than an
|
||
old Root or full table. Changed tables recheck subscribed IDs; unchanged tables
|
||
skip those reads. Read caches and last-notified values stay separate so an early
|
||
`getSnapshot()` cannot consume another observer's notification.
|
||
|
||
Sync counts build one scalar index per operations/requests/loading version:
|
||
project totals plus canvas→counts. Requests spanning multiple pending operations
|
||
count once per matching canvas. Operations with no canvas remain in project
|
||
counts. Unknown canvas queries return the shared zero count without growing the
|
||
index. This preserves the existing blocked/unknown definitions; table changes
|
||
still require a traversal, not a per-record ledger write-set update.
|
||
|
||
Asset display effects keep their previous ownership, abort/release and retry
|
||
rules. Durable-only completion/error/close is still read from the independent
|
||
durability source, so saving and synchronization remain distinct.
|
||
|
||
The host's view-owned scene cache retains one current projection and its document,
|
||
canvas, registry/catalog, selection, geometry preview, and connection target/path
|
||
inputs. Viewport, tool, modes, task receipts and other views' selections do not
|
||
rebuild that scene. Geometries are read from the captured session preview rather
|
||
than a newer live session; shared previews still appear in every view. An empty
|
||
node gesture and phase-only transitions do not require geometry projection.
|
||
Field publications, selection changes and ordinary node/edge previews patch the
|
||
changed projections through view-owned ID-to-array-position Maps. Continuous
|
||
publication changes are coalesced in a dirty Set (up to 16384 IDs); missing change
|
||
chains and canonical replacements compare document records and order first.
|
||
Equivalent replacement records retain projected objects and transforms. Entry
|
||
geometry/presentation, topology, membership/lifecycle, redirected endpoints and
|
||
unsupported scenes retain full traversal. Canvas/renderer replacement also rebuilds.
|
||
Array copying and existing paint/measurement/retention passes can still be linear;
|
||
this is not a claim that the entire view is O(changed IDs). Numeric geometry caches
|
||
remain derived Maps, with no history or durable task ownership. Engine/view
|
||
replacement discards this cache; `clear()` also drops the retained scene and
|
||
numeric caches. Query provenance is validated even on a cache hit.
|
||
|
||
Session transitions publish the final view and any owned preview cancellation in
|
||
one snapshot. Changing tools cancels only that view's active node/path gesture;
|
||
`begin`/`beginConnection` require the selection tool (`tool_not_select` otherwise).
|
||
Repeated selection of the current tool is a no-op. The React Flow adapter also
|
||
disables native connection handles in pan mode and rejects a late connection
|
||
callback against the current tool. Multitouch interruption is view scoped.
|
||
|
||
Modes remain `Map<ElementId, string>` in each view. On document publication, the
|
||
session compares the previous and current target incarnation, owner, structure,
|
||
component and data version: replacing or moving the target clears its mode,
|
||
while content edits and equivalent canonical clones preserve it. Missing targets
|
||
cannot acquire a mode. Identical mode updates and redundant clears do not notify.
|
||
This uses a borrowed current document reference, not a second mode identity store.
|
||
|
||
Each attach disposer runs once, so a late second cleanup cannot detach a newly
|
||
attached view with the same ID. Explicit canvas navigation and detach publish the
|
||
new view set together with cancellation. Session disposal fences admission before
|
||
notifying, clears all views/previews, and is idempotent. These transient actions
|
||
create no document/history/ledger entries and change no recovery format.
|
||
|
||
Gesture admission also checks the target owner's visible ancestry relative to the
|
||
owning view's canvas. A collapsed or opaque ancestor is `target_hidden`; an owner
|
||
outside that view's subtree is `target_outside_view`. Node and path gestures use
|
||
the same rule. The temporary owner-to-chain Map shares work for multi-selection;
|
||
it lives only during admission or a document publication, with no project scan.
|
||
|
||
An active gesture cancels with `coordinate_context_changed` when an entry in that
|
||
relative chain changes geometry, normalized content/unit scale, identity, or
|
||
parent relation, or becomes hidden. Entry titles and unrelated style fields do
|
||
not affect the chain. Ancestors above an independently opened child view are
|
||
excluded. Target component/version changes cancel with `target_changed`; removal
|
||
of the view canvas publishes fallback navigation and `canvas_changed` together.
|
||
Late updates/commit cannot use the canceled preview. Failed admission does not
|
||
replace the accepted baseline, and no chain, preview or gesture enters recovery.
|
||
|
||
Internal parser byte ports and read evidence (`import-byte-reader.ts`,
|
||
`import-read-proof.ts`) support progressive native imports before
|
||
whole-file hashing completes. NDJSON, JSON array, streaming document and its indexed
|
||
fallback all accept the optional `readBytes` port; the default Blob path keeps
|
||
its original slice request bounds and verifies the returned byte count.
|
||
|
||
The proof reader binds a file ID and byte length to SHA-256 hashes of aligned
|
||
16 KiB blocks, recording evidence only after an entire range is delivered.
|
||
A restored manifest can verify its recorded blocks before parsing resumes.
|
||
Unrecorded blocks are explicitly outside this proof: the manifest is neither a
|
||
whole-file digest nor authority to dispatch mutations. New native jobs use source
|
||
integrity version 2 and recovery format 17. Parser cursor, IDs, pending locators,
|
||
read evidence and accepted document changes publish through the same StateRoot.
|
||
The complete-source digest runs concurrently; content can display and be edited
|
||
before that digest finishes. Legacy source version 1 keeps its SHA-first path,
|
||
including array recovery 14 and streaming document recovery 16. Copy sources
|
||
retain their existing interpreters. IndexedDB schema remains 4; wire remains 2.
|
||
|
||
The scheduler admits no newly compiled project mutation while any accepted source
|
||
is unsealed or its seal has not reached storage. `durableImportSeals` is a derived
|
||
file-ID → SHA Map rebuilt from each successfully stored cut, not a permission
|
||
boolean or a second job store. Existing immutable requests keep their original
|
||
verified bodies. The gate is conservatively project-wide, including unrelated
|
||
new edits; dependency-scoped dispatch is not claimed.
|
||
|
||
Pause, partial success and cancel after an accepted prefix retain verification
|
||
and source pins. Cancel before any accepted content abandons verification without
|
||
blocking unrelated sends. Read/digest failure retains local content and the gate;
|
||
`retry()` rebuilds the reader and verifies again without restarting a cancelled
|
||
parser. Reopening verifies saved block evidence before continuing the same cursor
|
||
and IDs. Known complete hashes are checked before resumed parsing. A late SHA
|
||
seal changes source metadata without changing parser generation; every parser
|
||
checkpoint merges that current engine-owned metadata instead of reverting it.
|
||
Public runtime updates cannot forge or remove existing proof/hash evidence.
|
||
|
||
Readers and verification tasks belong to ProjectEngine, with per-job Maps and
|
||
AbortControllers. Recovery holds abort and later restart unfinished verification;
|
||
close fences late results and releases readers. Job source metadata is durable;
|
||
read caches, pending Promises and controllers are transient. No extra document,
|
||
business queue or history stack is introduced.
|
||
|
||
The reader owns a hash Map (at most 4096 blocks for the 64 MiB source limit), a
|
||
bounded LRU byte Map (four blocks by default), and up to four shared in-flight
|
||
block Promises/four range reads. Each range is capped at 4 MiB. Aborted waiters
|
||
release their own read but do not free occupied native reads early or cancel
|
||
another waiter. Close fences late results and clears byte buffers; checkpoints
|
||
contain no byte buffers. These bounds do not assert native Blob memory release.
|
||
|
||
Byte I/O/capacity/integrity failures throw instead of becoming a terminal malformed
|
||
record. In particular a document reader preserves its ordinal for retry; the
|
||
engine pauses the import, retains its editable prefix, and resumes after the I/O
|
||
recovers. Ordinary malformed JSON/UTF-8 still uses the existing record diagnostics.
|
||
|
||
|
||
Ordinary node insertion now keeps existing node/edge projections and transforms.
|
||
A continuous change hint records element IDs rather than treating every element
|
||
creation as a canvas topology change. A new visible known node uses the shared
|
||
single-node builder; ordered member IDs restore the original depth-first canvas
|
||
order. Hidden additions update the document without rebuilding visible objects.
|
||
The layout index is rebuilt when visible node membership changes, so subsequent
|
||
field edits address the new indices. Canvas changes, removal, reparenting,
|
||
connection insertion/endpoints and unsupported placeholders retain full traversal.
|
||
|
||
This reduces projection construction, not the whole path to O(k). Reordering
|
||
node references and rebuilding layout indices still takes linear work; missing
|
||
membership services require a record scan. The 1000-node insertion test resolves
|
||
only the new renderer, keeps all 1000 prior node objects and forbids full document
|
||
record enumeration when a membership index is available. Actual toolbar insertion
|
||
and ACK are checked separately in the page integration harness.
|
||
|
||
A local Chrome157/Next-development 1000-child copy probe completed with a first
|
||
presented frame around 348 ms and frame-gap P95 around 33 ms. These observations
|
||
are not an SLA or proof of overall speedup. The 10000-child baseline probe at
|
||
0ce3abb remained unresponsive with a CPU-active renderer and approximately
|
||
4.4 GiB sampled physical footprint, and was interrupted. Its completion, first
|
||
frame and root cause remain unverified; 10000-node support is not claimed.
|
||
|
||
|
||
Node measurement requests now share a microtask batch per React Flow store.
|
||
`createNodeInternalsBatch` owns a Map from mount tokens to node IDs and a dirty
|
||
Set of tokens. A flush deduplicates live IDs and invokes the public bulk update
|
||
once. Releasing a mount removes its pending request without touching a replacement
|
||
mount for the same ID. Reentrant invalidation schedules another flush; separate
|
||
stores never share work. The React adapter keys its WeakMap by `getState`, because
|
||
`useStoreApi()` creates a separate wrapper per hook. Acquisition/release happens
|
||
in effects, and the invalidate callback stays stable across renders. The SDK
|
||
still schedules its own animation frame; the adapter adds no extra frame timer.
|
||
|
||
Inactive node/frame toolbars no longer mount `NodeToolbar`. AnimatePresence owns
|
||
the conditional toolbar so selection, portal editing and exit animation remain
|
||
available. In React Flow 12.12.1 each mounted toolbar subscribes to a selector
|
||
that scans the whole node array; an empty child was not an inactive subscription.
|
||
Likewise each individual forced measurement updates all absolute positions, so
|
||
per-node mount calls multiplied work. Both paths are bounded by actual active
|
||
UI needs now. These are renderer resources; no model, history or durable queue
|
||
state changes.
|
||
|
||
A 10000-node page reached its initial scene after these changes, but its copy
|
||
probe still exceeded the 240-second completion window. During the copy it accepted
|
||
read-only diagnostics and displayed incremental content; commit/persistence costs
|
||
remain unresolved at that scale. Do not interpret the initial-scene improvement
|
||
or the batching checks as a passed 10000-node copy budget.
|
||
|
||
|
||
Browser storage writes `journalVersion: 2` deltas whose SHA-256 covers the ordered
|
||
JSON object `{journalVersion, baseRevision, baseChecksum, header, patches}`. The
|
||
prior checksum may belong to a full checkpoint, a v1 delta's reconstructed image,
|
||
or another v2 delta. Checkpoints and full-record fallbacks still hash the entire
|
||
image. Readers retain v1's full-image checksum rule and select the interpreter
|
||
explicitly; unknown journal versions fail. DB schema remains 4 and RecoveryImage
|
||
remains 1–17. This changes the internal journal interpreter, not wire protocol 2.
|
||
|
||
The recovery codec records exact UTF-8 byte lengths while producing immutable JSON,
|
||
including omission/null normalization and escaped Unicode. Only factory-owned
|
||
images expose the cached size, so choosing a smaller delta no longer first
|
||
serializes the complete owned image. Mutable callers retain defensive copying
|
||
and full size calculation. Noncanonical image header order/fields use a full
|
||
record to preserve exact serialization.
|
||
|
||
Each leader retains its last verified image/checksum and checkpoint revision in
|
||
WeakMaps. Both advance only after transaction completion. Every next cut still
|
||
checks stored metadata, prior checksum, continuous revision and local revision;
|
||
all file/depot writes remain atomic with the cut. Same-cut retries reread and
|
||
validate the chain, compare complete serialized images (including property order),
|
||
and keep the existing restrictions on new files. No Root cuts are skipped.
|
||
Recovery hashes/applies outside the read transaction, validates every reconstructed
|
||
image, and retains original frozen request bytes. A v1-only reader must reject a
|
||
v2 row rather than ignore its checksum semantics.
|
||
|
||
The 10000-child copy probe still exceeded its 240-second completion window after
|
||
this change. The exact hashed-input browser test proves removal of whole-image
|
||
hashing for ordinary adjacent delta writes; this is not a passed end-to-end copy
|
||
budget or a claim that encoding, rendering and dispatch costs are all resolved.
|
||
|
||
|
||
Import identity capture now reuses a frozen, own-data string dictionary when no
|
||
new ID is reserved. One active runner retains its last ID-table reference and a
|
||
reverse Set; only the first actual allocation copies both. Mutable dictionaries
|
||
and frozen getters are captured defensively, and a late reserve callback cannot
|
||
mutate an already published result. The index is cleared when the pump ends,
|
||
including pause/cancel/close/restart; the durable `stableIds` remains on ImportJob.
|
||
Allocation keeps the caller port method receiver. No task or recovery version
|
||
changes, and first-item publication/durability barriers remain in place.
|
||
|
||
Recovery also reuses frozen plain document views across metadata-only Roots.
|
||
`isFactoryDocument` identifies an envelope with fixed table membership produced
|
||
by core constructors; it is not a content-validation or deep-immutability test.
|
||
A WeakMap owns only a derived plain view. Its array/tuple containers are frozen
|
||
without freezing caller-owned records. The codec still independently checks deep
|
||
immutability, so mutable values inherited through `applyChanges` are reevaluated.
|
||
Unowned envelopes reread mutable map membership. A test prohibits document-record
|
||
enumeration during a metadata-only encode after the initial view is built.
|
||
|
||
The 1000-child copy probe completes with these paths, but the 10000-child copy
|
||
still exceeds 240 seconds. Removing ID-table copies and metadata-only document
|
||
scans is directly verified; a general throughput improvement at 10000 nodes is
|
||
not established. Per-record commit, ACK and remaining table traversal costs remain
|
||
part of the larger implementation goal.
|
||
|
||
Import commit sizing uses runner-local feedback rather than dividing total
|
||
transaction time by unit count and treating it as a purely marginal cost.
|
||
The first ready record remains immediate. Successful batches within the soft
|
||
frame budget grow by a bounded step; an over-budget batch reduces the limit.
|
||
A short tail or dependency group cannot increase it. `maxBatch`, byte/change
|
||
limits, atomic reference cycles, parser slices and durability backpressure keep
|
||
their existing roles. Rejected attempts do not train the success-cost estimate.
|
||
The limit is a scheduling hint, not another persistent queue or an assurance
|
||
that an indivisible transaction or subsequent renderer work fits one frame.
|