perf(chat): tail-only SSE refetch with content-hash identity to stop render churn #178
No reviewers
Labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Cloonar/coding-lab!178
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "afk/175"
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?
Resolves the SSE-driven refetch/render churn that made the PWA sluggish as the fleet grew: while any agent streamed, the open chat fetched an after-cursor tail PLUS an unconditional latest window per ~1s event, the shell refetched the entire instances list to flip one state dot, and the merge replaced every rendered identity so Solid's reference-keyed
<For>rebuilt up to 60 subtrees (markdown parse included) every second.Wire contract (ADR-0047)
content_hash— FNV-64a over the rendered message, stamped in core afterscanAndRedacton both serve paths (never in adapters), so the hash always covers the served, masked content and every provider gets it for free.run.messages.changedcarries the tick's conversationalstateandbackpatchSeq— the lowest seq whose hash changed vs. the tailer's previous read (per-goroutine baseline, reset on rotation, absent on pure appends/first tick).run.changedcarriesrunIDat every single-run emit site (stop, launch, deep-link capture, AFK stop, sweep, startup, pull, exposure redact, title rename); genuinely repo-scoped emits (stop-all, reaper, CR merge, parked) stay bare and keep the conservative refetch.Client behavior
backpatchSeq) into a LIGHT tail-only refetch fromafter=min(cursor, backpatchSeq-1)— one small fetch covers appends and back-patches, no latest-window request. Route change / rotation / resync /run.changed/ send / answer / interrupt keep the FULL protocol. Sibling-runrun.changedis filtered byrunID.mergeMessageskeeps the previous message object on an equal hash (and the previous array when nothing changed);renderItemsis memoized withreconcileRenderItemsso unchanged messages, wrappers, and groups keep DOM identity — no teardown of settled subtrees.run.messages.changedpatches the one affected row's state in place from the event payload — zeroGET /instancesrequests for that event type;run.changed/resync still refetch (the self-heal path).Acceptance criteria → proof
RunChat.test.tsx"coalesces a burst into ONE tail request…" (URL-level request counting).TestTailer_backpatchSeq.TestTailer_derivesStateAndPublishes(envelope carries state) +AppShell.test.tsx(fetch count pinned, dot/badge flip asserted).run.changedignored; bare events stay conservative: RunChat + instance/httpapi payload tests.TestReadRedact_hashCoversMaskedContent.Verification
CGO_ENABLED=0 go test ./...: green except the untouchedinternal/tmuxxreal-tmux integration tests, which cannot run in this sandbox (pre-existing environmental).go vet/gofmtclean.web: vitest 861/861 green,tsc --noEmitclean, eslint clean, productionnpm run buildsucceeds.Out of scope, per the brief: stream windowing/virtualization (deferred in ADR-0047), server-side query work (#176, #177), push/presence behavior (#160 — untouched, the notify gate still rides the tailer read path).
Closes #175
🤖 Generated with Claude Code