Context
Operator brief #3 of 2026-08-22 (screenshot of a LIVE streaming cockpit): "view does not scroll. '36 more' at the bottom => this should be a scrollable list with buffered rendering. it could show a total count (e.g. for the last 24h and total). we probably already have a ticket to show times and dates (local format) — right now missing."
Sweep answer to the suspected time ticket (corrected per the intake challenge, IC_kwDODSospM8AAAABQMCBMg): nothing OPEN owns row times — but the merged-PR sweep the author's first pass skipped shows the contract already SHIPPED: PR #17323 (resolves #17302) renders the viewer-local time cell + exact ISO title at ActivityStream.mjs:367,463-466, and both live producer adapters stamp occurredAt end-to-end (fleetA2AActivityAdapter.mjs:166; fleetPrLaneActivityAdapter.mjs:110,150). The operator-observed absence in the live cockpit is therefore a headed/deployment regression to reproduce, not a wire feature to build.
The Problem
- The bounded window + fold is now the wrong shape for a live cockpit.
boundActivity (apps/agentos/view/fleet/ActivityStream.mjs:9-24) renders the newest maxVisible rows plus an "N earlier events" fold, with a ring bound behind it — deliberately, against measured boot-burst density ("an unbounded feed freezes the frame … a silent drop falsifies the feed"). The rationale stands; the CONCLUSION changes: the honest answer to overflow is a scrollable list with buffered rendering (bounded DOM, not bounded content), not a dead "36 earlier events" label. The stream root even sets overflow: hidden (ActivityStream.scss).
- No counts. The head shows the streaming state but no volume truth: last-24h and total counts (as the adapter reports them — honest, never derived client-side from a partial ring).
- Row times are shipped at head but absent in the operator's live build. PR #17323 landed the render + the adapters stamp
occurredAt (receipts above) — so this ticket owes a PRESERVATION witness (the contract survives the rebuild, proven headed at the current head) plus a reproduction of the live-deployment absence, never a re-implementation.
- Row text is thin and self-duplicating ("neo-opus-grace · pr-activity" beside a
neo-opus-grace chip): the actor twice, the category as the message, no object (PR/issue number or title). Ties into the #17549 content-first thread.
The Architectural Reality
apps/agentos/view/fleet/ActivityStream.mjs — boundActivity, the run-coalescing grammar, the ring bound (_events = held.slice(-value)), the fold line; ActivityStream.scss (--fm-rail root, hairline rows, .fm-ev-time exists).
- The events arrive through the cockpit-owned authenticated bridge (
fleetActivity read-observe verb; fleetA2AActivityAdapter / fleetPrLaneActivityAdapter compose them) — count truths and occurredAt belong to the adapter/wire, never invented in the view.
- Buffered rendering precedent lives in the engine (grid/list windowing); the pane owns the scroll seat per the drawer-shell contract (
overflow: auto, min-height: 0 — the sibling panes' idiom).
- T5 viewer-time pattern (
viewerTime.mjs) shipped and in use by the sibling panes.
The Fix (narrowed per the intake fork — AC-1 design carried by the intake's Activity sketch)
- The rows region becomes the scroll seat (
overflow: auto, min-height: 0) with a bounded mounted-row pool — no app-local virtualizer: Neo.list.Component has no windowing substrate (zero buffer/virtual hits under src/list), so the DOM bound rides the buffered component-list engine leaf Emmy claimed 2026-08-22 16:13Z (linked as blocked-by once filed; Neo.grid.Body's bufferRowRange row pool is the engine precedent). The fold line retires; ring drops stay honestly surfaced as retention facts.
- Counts become a producer-owned contract before any rendering:
fleetActivity grows a counts envelope whose rows carry value, scope (last24h / total), complete: true and a capture time — the UI renders a count ONLY when its producer proves completeness; absence renders as absence. Counts and retention are different truths: N retained / M dropped describes the local ring and never masquerades as a source count.
- Row times: preservation + regression witness only (Problem 3) — viewer-local text + ISO title survive the rebuild, proven headed; the live-deploy absence gets a reproduction.
- Row grammar rewrite (the intake sketch, adopted): one fixed-height row — local time · kind · actor (once) · named object (PR/issue number/title when carried); narrow widths break the SAME fixed-height row into two lines (time/kind/actor · object) — no mid-word clipping, no variable-height buffer math. Newest-first survives; while the operator reads history, the anchored record + pixel offset are preserved and new arrivals surface as
N new events ↑ — the viewport is never stolen. The role=log polite-announcement contract survives row recycling (recycled history rows are never announced as new).
- §04 tokens/rhythm; zero CSS-in-JS; both themes.
Decision Record impact: none — aligned-with the bounded-honesty rationale it evolves.
Contract Ledger delta — accepted from the implementation intake (IC_kwDODSospM8AAAABQMN9SQ)
The buffered-list shape needs one contract the current DTO never carried: createFleetCockpitEvent() emits no stable identity, and a Store reconciliation keyed on presentation-derived signatures would mint a second identity truth and break prepend anchoring when two events share time/type/actor.
| Target surface |
Authority |
Proposed behavior |
Fallback |
Evidence |
activity event eventId (new) |
the source adapter that owns the event |
durable within its source: A2A = messageId; PR = PR number; issue = issue number; lane = comment id; stall = stable subject + finding class; degraded = source slot |
an adapter unable to name identity omits/refuses the event — the view never invents one |
adapter uniqueness/stability specs + Store reconcile witness |
count row source (new required field) |
producer count owner |
every {value, scope, complete, capturedAt} row names its source; an unqualified aggregate renders only when the composer proves every contributing slot complete |
source-specific label or absence; a partial source is never presented as a fleet total |
truncated-source composer specs |
A2A total / last24h completeness |
MailboxService result |
total may be complete from totalCount; last24h is complete only when the returned page covers the whole matching population |
incomplete 24h row omitted from the UI |
page smaller/larger than totalCount controls |
| PR / lane counts |
synced-source read |
absent until the reader proves completeness without re-parsing the corpus each poll |
no count rendered |
explicit no-count control |
The source-independent slice (named Model/Store + pooled row keyed by eventId) proceeds on this fold; the adapter/composer slice follows the same ledger. Under #17559 the surface is born as view/fleet/activity/ with its primitive chosen per law 0 (the buffered component list #17554, given component-item rows).
Acceptance Criteria
Out of Scope
The header/status cluster and agent detail (#17549) · south-tab placement (#17269) · new event sources · the Tasks pane (#17329 / PR #17542).
Related
Epic #14560 (parent) · #17549 (same design pass family) · #17268 · #16380 / #16699 (closed prior art) · #17302 / PR #17323 (the shipped row-time contract this ticket preserves) · the buffered component-list engine leaf (Emmy's 16:13Z claim; blocked-by link lands when filed) · #17552/#17553 (disjoint: the roster's animate-plugin reflow is full-set positioning for ~20 cards; this pane windows 500+ rows) · PR #17542
Live latest-open sweep: latest 20 open issues checked 2026-08-22T14:56Z (re-checked against #17547–#17549 additions at 15:0xZ) — no equivalent. A2A window (10 newest, all read-states, 14:57Z + this claim): no competing claim. Local exact sweep: six hits, five CLOSED, one unrelated open (#16853).
Origin Session ID: 14acab5a-4b6c-4987-91c7-f683e39baa55
Retrieval Hint: query_raw_memories("activity stream scroll buffered rendering fold earlier events occurredAt viewer local time counts")
Context
Operator brief #3 of 2026-08-22 (screenshot of a LIVE streaming cockpit): "view does not scroll. '36 more' at the bottom => this should be a scrollable list with buffered rendering. it could show a total count (e.g. for the last 24h and total). we probably already have a ticket to show times and dates (local format) — right now missing."
Sweep answer to the suspected time ticket (corrected per the intake challenge, IC_kwDODSospM8AAAABQMCBMg): nothing OPEN owns row times — but the merged-PR sweep the author's first pass skipped shows the contract already SHIPPED: PR #17323 (resolves #17302) renders the viewer-local time cell + exact ISO title at
ActivityStream.mjs:367,463-466, and both live producer adapters stampoccurredAtend-to-end (fleetA2AActivityAdapter.mjs:166;fleetPrLaneActivityAdapter.mjs:110,150). The operator-observed absence in the live cockpit is therefore a headed/deployment regression to reproduce, not a wire feature to build.The Problem
boundActivity(apps/agentos/view/fleet/ActivityStream.mjs:9-24) renders the newestmaxVisiblerows plus an "N earlier events" fold, with a ring bound behind it — deliberately, against measured boot-burst density ("an unbounded feed freezes the frame … a silent drop falsifies the feed"). The rationale stands; the CONCLUSION changes: the honest answer to overflow is a scrollable list with buffered rendering (bounded DOM, not bounded content), not a dead "36 earlier events" label. The stream root even setsoverflow: hidden(ActivityStream.scss).occurredAt(receipts above) — so this ticket owes a PRESERVATION witness (the contract survives the rebuild, proven headed at the current head) plus a reproduction of the live-deployment absence, never a re-implementation.neo-opus-gracechip): the actor twice, the category as the message, no object (PR/issue number or title). Ties into the #17549 content-first thread.The Architectural Reality
apps/agentos/view/fleet/ActivityStream.mjs—boundActivity, the run-coalescing grammar, the ring bound (_events = held.slice(-value)), the fold line;ActivityStream.scss(--fm-railroot, hairline rows,.fm-ev-timeexists).fleetActivityread-observe verb;fleetA2AActivityAdapter/fleetPrLaneActivityAdaptercompose them) — count truths andoccurredAtbelong to the adapter/wire, never invented in the view.overflow: auto,min-height: 0— the sibling panes' idiom).viewerTime.mjs) shipped and in use by the sibling panes.The Fix (narrowed per the intake fork — AC-1 design carried by the intake's Activity sketch)
overflow: auto,min-height: 0) with a bounded mounted-row pool — no app-local virtualizer:Neo.list.Componenthas no windowing substrate (zero buffer/virtual hits undersrc/list), so the DOM bound rides the buffered component-list engine leaf Emmy claimed 2026-08-22 16:13Z (linked as blocked-by once filed;Neo.grid.Body'sbufferRowRangerow pool is the engine precedent). The fold line retires; ring drops stay honestly surfaced as retention facts.fleetActivitygrows acountsenvelope whose rows carryvalue,scope(last24h/total),complete: trueand a capture time — the UI renders a count ONLY when its producer proves completeness; absence renders as absence. Counts and retention are different truths:N retained / M droppeddescribes the local ring and never masquerades as a source count.N new events ↑— the viewport is never stolen. Therole=logpolite-announcement contract survives row recycling (recycled history rows are never announced as new).Decision Record impact: none — aligned-with the bounded-honesty rationale it evolves.
Contract Ledger delta — accepted from the implementation intake (IC_kwDODSospM8AAAABQMN9SQ)
The buffered-list shape needs one contract the current DTO never carried:
createFleetCockpitEvent()emits no stable identity, and a Store reconciliation keyed on presentation-derived signatures would mint a second identity truth and break prepend anchoring when two events share time/type/actor.eventId(new)source(new required field){value, scope, complete, capturedAt}row names its source; an unqualified aggregate renders only when the composer proves every contributing slot completetotal/last24hcompletenessMailboxServiceresulttotalmay be complete fromtotalCount;last24his complete only when the returned page covers the whole matching populationtotalCountcontrolsThe source-independent slice (named Model/Store + pooled row keyed by
eventId) proceeds on this fold; the adapter/composer slice follows the same ledger. Under #17559 the surface is born asview/fleet/activity/with its primitive chosen per law 0 (the buffered component list #17554, given component-item rows).Acceptance Criteria
countsrows withcomplete: true, scope-labelled 24h/total, capture-time carried); incomplete/absent stays absent; retention facts never masquerade as source counts (adapter/composer specs against truncated source reads).N new events ↑; therole=logannouncement contract survives recycling.npm run check-theme-surfacesgreen.Out of Scope
The header/status cluster and agent detail (#17549) · south-tab placement (#17269) · new event sources · the Tasks pane (#17329 / PR #17542).
Related
Epic #14560 (parent) · #17549 (same design pass family) · #17268 · #16380 / #16699 (closed prior art) · #17302 / PR #17323 (the shipped row-time contract this ticket preserves) · the buffered component-list engine leaf (Emmy's 16:13Z claim; blocked-by link lands when filed) · #17552/#17553 (disjoint: the roster's animate-plugin reflow is full-set positioning for ~20 cards; this pane windows 500+ rows) · PR #17542
Live latest-open sweep: latest 20 open issues checked 2026-08-22T14:56Z (re-checked against #17547–#17549 additions at 15:0xZ) — no equivalent. A2A window (10 newest, all read-states, 14:57Z + this claim): no competing claim. Local exact sweep: six hits, five CLOSED, one unrelated open (#16853).
Origin Session ID: 14acab5a-4b6c-4987-91c7-f683e39baa55
Retrieval Hint:
query_raw_memories("activity stream scroll buffered rendering fold earlier events occurredAt viewer local time counts")