Room timeline backfills from the spec-scoped event history - #44
Merged
Conversation
The room fetched the last 100 global events and filtered client-side, so lifecycle rows silently vanished on a busy control plane even though they sit durably in sail's audit store. The timeline now loads through GET /v1/events?spec= (sail >= 0.26) and gap-fills scoped from the last seen event id when the SSE stream reconnects, converging without duplicates on the existing monotonic-id dedupe. A server that predates the route falls back to the filtered global window, and the board's EventStream keeps /recent — the firehose is its actual question.
Two review mediums on the spec-scoped gap-fill: - It made a single specEvents request, so a disconnect longer than one page (PAGE_SIZE) silently dropped every event past the first 100. Now it pages with an advancing since cursor until the server returns a short page. - It only merged events into the timeline, bypassing the durable-state refreshes the live handler runs — so a gap-filled message/review/run event left the message list, review status, and run status stale until the next live event. Gap-fill now splits message events (drive the message list, like the live handler) from lifecycle events (merge + reconcile optimistic decisions), and after draining runs refreshMessages / refreshReviewsAndRuns exactly when the batch warranted them. Extracts the decision-reconciliation both paths share into one pure helper so a confirmation landing during a disconnect clears the optimistic overlay like a live one. Proven red-first: paging stopped at 100, and durable state never refreshed.
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.
Spec: sail-spec-events (sail PR: standardapplied/sail#196).
Why
The room timeline backfilled by fetching the last 100 global events and filtering client-side to the spec, so on an active control plane a spec's lifecycle rows silently vanished from its room even though they sit durably in sail's audit store.
What
Gateway.specEvents(id, {since?, limit?})→GET /v1/events?spec=(sail ≥ 0.26): one spec's durable history, oldest first, RECORD-class only;sinceis the exclusive event-id cursor.SpecRoombackfills throughspecEventsinstead of filtering the global window. Version skew: a server that predates the route (405) falls back to the old filtered/recentread, so rooms never regress against an older sail.onConnectionStatusand, on a reconnect, requests?spec=&since=<max seen event id>; the existing monotonic-id dedupe makes convergence duplicate-free. The board'sEventStreamkeeps/recent— the firehose is its actual question.Note: history served this way is node-local by contract (no event replica in sail); the fleet-consistent room content remains messages, reviews, and runs.
Verification
bun run typecheck+bun testgreen (494 pass). New tests: lifecycle rows survive a busy global stream (and/recentis not called), the version-skew fallback filters the global window, reconnect gap-fill issues{since}and renders no duplicate rows, and the Tauri gateway builds the scoped URLs.