Ship streaming append once, split, per tick (protocol v5)#178
Merged
Conversation
Streaming append previously rebuilt the whole packed payload (join copy)
and transmitted it twice per tick on the widget host: once as the
spec/buffers trait re-sync and again as a custom append message, as two
separate comm messages plus the send.
Now append_data emits the split layout — the same per-column borrowed
views as first paint, no join copy — and stamps the spec with
append: {seq, affected}. The widget delivers the refresh as one
hold_sync trait update that doubles as notebook-reopen state; the client
applies an append when spec.append.seq advances and detaches the
listener on destroy. The /_xy socket push keeps its message envelope,
now with split buffers. _applyAppend reads the layout from
spec.buffer_layout (a legacy packed reopen blob still applies).
Protocol bumps to v5 so a cached pre-v5 bundle fails loudly at first
paint instead of waiting for a custom append message that no longer
exists.
Halves streaming wire bytes and removes two full-payload copies per
append tick. Closes #162.
Merging this PR will not alter performance
Comparing Footnotes
|
Greptile SummaryThis PR updates streaming append delivery to protocol v5. The main changes are:
Confidence Score: 5/5This looks safe to merge. The updated listener handles both spec-first and buffer-first trait events. Coherence failures defer application without consuming the append sequence. No blocking issues were found in the changed code. No additional files need attention.
What T-Rex did
Important Files Changed
Reviews (2): Last reviewed commit: "Tolerate non-atomic spec/buffers trait w..." | Re-trigger Greptile |
Greptile P1: the change:spec handler paired a new spec with whatever buffers the model held and consumed the seq, so a host that surfaces the two trait writes non-atomically could apply stale data with no repair path. The client now listens to both change events, defers a torn pair — detected by payloadCoherent (every column must fit its buffer) — without consuming the seq, and keys applied state on (seq, buffers identity) so the write that completes the pair re-fires the apply and repairs even a same-shape tear. Atomic hosts see one apply per tick as before, guarded by the seq+identity check. Browser probe extended with torn-order scenarios (spec-first defer, buffers-first converge, identity re-apply, both listeners detached on destroy); wire-protocol §4 documents the contract.
This was referenced Jul 22, 2026
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.
Closes #162.
What
Streaming
appendpreviously rebuilt the whole packed payload (paying theb"".joincopy that split mode was invented to avoid) and transmitted it twice per tick on the widget host: once as thespec/bufferstrait re-sync (two separate comm messages, nohold_sync) and again as a customappendmessage — three comm messages per tick carrying the payload twice.Now:
append_dataemits the split layout — the same per-column borrowed memoryviews as first paint, no join copy — and stamps the spec withappend: {seq, affected}(monotonic, kernel-side counter onFigure).FigureWidget.appenddelivers the refresh as onehold_synctrait update that doubles as notebook-reopen state. No custom send. The client applies an append whenspec.append.seqadvances, andwantsViewChangereads the live spec so the transport flag survives appends.payloadCoherent— every column must fit its buffer) without consuming the seq, and keys applied state on (seq, buffers identity) so the completing write repairs even a same-shape tear./_xysocket push keeps itsmsgenvelope, now carrying split buffers;_applyAppendreads the layout fromspec.buffer_layout(payloadBuffersmoved to00_header.tsand shared), and a legacy packed reopen blob still applies by declaration.PROTOCOL_VERSION/PROTOCOLbump to 5: a cached pre-v5 bundle would otherwise wait forever for a custom append message that no longer exists — the handshake now fails loudly at first paint instead.Measured
Per append tick, old (main @ b0f8780) vs this branch, same venv/native core, median of 100 ticks (50 for the dashboard), data-level wire accounting that mirrors each host's message sequence exactly:
Wire bytes exactly halve on every tier (the payload crosses once instead of twice); the kernel-side win on the direct case is the eliminated packed join copy. The dashboard row also shows what this PR deliberately does not fix: unaffected traces still re-encode and re-ship (~3.9 MB where ~0.4 MB is live) — that is #163's per-column content hashing, kept out of scope.
Spec
wire-protocol.md§4 (append contract, per-host delivery, torn-update handling), §5 (packed/split usage), §7 (v5 note);rust-engine.md§5 andreflex-integration.mdstreaming bullet updated to match. CHANGELOG entry under Unreleased → Changed.Verification
ruff check/ruff format --check/ pre-commit /ty check(25 pre-existing diagnostics, none new) /abi_smoke(121 checks).n_points10→12, GPU vertex count 12); same-seq echoes are no-ops; torn spec-first updates defer without consuming the seq and apply when buffers land; buffers-first updates converge on the spec write; a new buffers identity at the same seq re-applies (same-shape tear repair); both listeners detach on destroy. All green.node js/build.mjsregenerated both committed bundles.🤖 Generated with Claude Code