Repository navigation
One shared recent-events list per console tab #917
Description
Part of #905.
Scope
The last hour of events is read once per tab and shared by the Inbox drawer and the lease drawer, instead of once by each. The shared list is kept for 60 s after the last place using it closes, then dropped. Activity keeps its own list and scroll-back as today, which is already dropped the moment the operator leaves Activity.
In short
Opening the Inbox, then a lease's drawer, makes one GET /v1/events?since=1h instead of two, and coming back within a minute makes none. Nothing the operator sees changes.
flowchart LR
S[stream] --> R["shared recent feed (new, one per tab)"]
R --> I[Inbox drawer] & L[lease drawer]
S --> A["Activity's own feed (unchanged)"]
If it goes wrong: a drawer misses events that arrived while no page used the list.
Technical spec
Facts
| # | Fact | Source |
|---|---|---|
| F1 | Each screen that reads events builds its own EventFeed (useEventFeed), which reads since=1h on start, merges the stream by event id, reads the gap after a reconnect, and ignores stream events while inactive. |
ui/src/state/events.ts:36-56; ui/src/data/event-feed.ts:56-140 |
| F2 | Users of the recent list: Inbox (state/inbox.ts:91), lease drawer (state/leases.ts:154), worker Events tab (state/workers.ts:186); Activity uses its own feed with history (state/events.ts:82). |
as cited |
| F3 | A refused recent read is shown, not retried; a network failure is retried 1, 2, 4, 8 s, then every 10 s. | ui/src/data/event-feed.ts:121-128 |
| F4 | LiveProvider holds the per-tab connection and ticker. |
ui/src/state/live.tsx:39-60 |
Modules touched
ui/src/state/live.tsx (the provider holds one shared EventFeed, started at its first user and stopped and dropped 60 s after its last), ui/src/state/events.ts (useRecentEvents uses the shared feed; useEvents keeps its own), ui/src/data/event-feed.ts (only if a stop hook is missing), ui/src/state/inbox.ts, ui/src/state/leases.ts (unchanged calls; listed because their reads change), tests. The shared feed is not a query key: it is filled by the stream and its own reads, as today. Searches: useRecentEvents(, new EventFeed(, useEventFeed(.
Contract and event changes
None.
Other code on the same state
- While the shared feed is kept (a user, or within 60 s of the last), it stays active: it holds every streamed event and reads the gap after a reconnect, as F1. After it is dropped, the next user starts a new one with a
since=1hread. - The worker page's Events tab (
state/workers.ts:186) is Console: the worker's Events tab scrolls back through its history #911's: this task does not change it. - Activity's feed (
state/events.ts:82) is separate and unchanged.
Searches:streamEvent,streamOpened,onStreamOpened((4 hits).
Failure modes
| Call | Outcome | What happens | Limit, counted from |
|---|---|---|---|
since=1h read |
refused | shown with the refusal, as F3 | — |
since=1h read |
network failure | retried as F3 | 1, 2, 4, 8, then 10 s from the failure |
| reconnect | gap | one since=<gap> read merged into the shared list, as F1 |
— |
Rules in play
- ADR 0013 §2 "Open the stream and hold what it sends": the shared feed holds the stream for as long as it is kept.
- ADR 0044 §1: the shared list is dropped 60 s after its last user.
Tests
Seam: the state hooks through LiveContext with connect, fakeClock and settle from ui/src/data/live/test-support.ts, as ui/src/state/live.test.tsx uses them.
- Event lists are shared and bounded:
useInboxthenuseLeaseEvents(each mounted and unmounted) → onesince=1hread; a streamed event appears in both once; an event streamed 30 s after the last user left appears when a user comes back at 50 s, with no read; a user at 61 s → a newsince=1hread.
Done when
pnpm vitest run ui/srcpasses.e2e/console/inbox.spec.tsandleases.spec.tspass.
Out of scope
Activity's history; the worker Events tab (#911); the per-second clock (follow-up, see #905 Non-goals).
Depends on
- The daemon says what needs the operator: GET /v1/inbox #915 (both change
state/inbox.ts)
Written by an agent.
Activity
[-]The console sends ETags back and patches lease renewals in place[/-][+]One shared recent-events list, bounded history, and a cheaper clock[/+][-]One shared recent-events list, bounded history, and a cheaper clock[/-][+]One shared recent-events list per console tab[/+]test: the shared event list stays while any user remains and a return…
test(ui): the shared recent list reads a reconnect gap and stops a dr…
feat(ui): one shared recent-events list per console tab (#939)