[v0.1/IPC Review] Add an explicit terminator to pull-paced grid cycles #307
Labels
No labels
priority/P0
priority/P1
priority/P2
release/v0.1.0
status/blocked
status/planned
type/bug
type/design
type/test-gap
type/tracker
No milestone
No project
No assignees
1 participant
Due date
No due date set.
Dependencies
No dependencies set
Reference
natsukium/felis#307
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Parent: #12
Problem
One pull-paced daemon compose cycle has no explicit end on the wire.
compose_diffscan emit several orderedGridMsgs for one cycle: mode flags, prompt marks, viewport state, a scroll directive, referenced registry entries, one or moreRowDeltaframes, cursor state, and registry-tail entries. Large row batches are deliberately split across consecutiveRowDeltaframes.The daemon clears
pull_pendingbefore those events enter the subscriber outbox. The client clears its outstanding pull when it receives the first message matchingpull::answers_pull, which includes almost every compose-cycle variant. Every received message may request a redraw. Winit may therefore redraw and issue the next pull after the first message while the rest of the same cycle is still in transit.The ordered stream still converges on the final screen, but the current wire cannot guarantee either of the stronger properties its pacing design relies on:
Scrolledwithout the rows and cursor from that cycle.Proposed contract
Add an explicit end marker for every non-empty pull-composed diff cycle, for example
GridMsg::CycleEnd.RowDeltaframes.RehydrateEndboundary or is reconciled explicitly with the new cycle rule.A sequence number is not required for this proposal: the transport is ordered and the client permits only one outstanding pull. If the implementation needs a sequence for another invariant, that should be justified separately rather than added by default.
Acceptance criteria
GridMsgmakes a replacement pull due.RowDeltacycles are covered.Opssubscribers have explicit, tested behavior.CHANGELOG.mdagree.Principle check
Evidence
crates/felis-daemon/src/serve/streaming.rs:465(push_row_deltas)crates/felis-daemon/src/serve/streaming.rs:585(compose_diffs)crates/felis-daemon/src/serve/session_task.rs:1739(ship_sub_at)crates/felis-daemon/src/serve/session_task.rs:1773(pull_pending = false)crates/felis-client-core/src/pull.rs:14(answers_pull)crates/felis-client/src/event_handler.rs(pull.on_frameand redraw handling)docs/explanation/rendering/pipeline.md"Demand-driven emission"docs/reference/ipc.mdNextGridFrameandRowDeltaDirection agreed 2026-09-21 after three
soldesign-review rounds (final verdict PASS). Full plan and batch order: see the comment on #12.GridMsg::CycleEnd(modes=window,ops,phases=attached).Daemon emits it in
ship_sub_atafter theout.is_empty()check and afterthe registry drip tail, so an empty cycle keeps today's behavior: no message,
pull_pendingstays armed, the client's pull stays outstanding (pinned bypull_with_nothing_dirty_emits_no_redundant_frame). Client: the firstpull-answering message opens a cycle-in-progress state; every grid paint is
suppressed while it is open, whatever triggered the redraw (pending redraw,
cursor blink, animation, facet push);
CycleEndcloses it, clears theoutstanding pull, and requests one redraw. Facet pushes and
ImageMsgoutside the gate keep immediate behavior but cannot paint the grid mid-cycle.
Rehydrate keeps
RehydrateEnd(it is eager, never answers a pull, and thescheduler is rebuilt on attach/retarget); docs state the two boundaries.
Exhaustive matches in
shadow.rs::applyandreal_app_harness.rsgain ano-op arm. A cycle-open-then-adversarial-redraw test is required.