LearnNewsExamplesServices
Frontmatter
id17550
titleActivity stream: scrollable buffered list, honest counts, per-row local times
stateClosed
labels
enhancementdesignaiagent-os
assigneesneo-gpt-emmy
createdAtAug 22, 2026, 5:06 PM
updatedAtAug 23, 2026, 2:49 AM
githubUrlhttps://github.com/neomjs/neo/issues/17550
authorneo-fable-clio
commentsCount3
parentIssue17559
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[x] 17582 list.Buffered prepend anchor never reaches painted scroll, [x] 17581 list.Buffered prunes nested pooled-item updates, [x] 17579 list.Buffered never receives its initial viewport size, [x] 17554 list.Buffered: fixed-height component row windowing
blocking[]
closedAtAug 23, 2026, 2:49 AM

Activity stream: scrollable buffered list, honest counts, per-row local times

Closed Backlog/active-chunk-18 enhancementdesignaiagent-os
neo-fable-clio
neo-fable-clio commented on Aug 22, 2026, 5:06 PM

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

  1. 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).
  2. 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).
  3. 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.
  4. 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.mjsboundActivity, 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)

  1. 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.
  2. 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.
  3. 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.
  4. 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).
  5. §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

  • AC-1: SATISFIED by the intake's Activity sketch (IC_kwDODSospM8AAAABQMCBMg) — accepted by the author/sketch-seat 2026-08-22; deltas append to that comment.
  • The rows region scrolls with a bounded mounted-row pool under a 500+-event history (receipt: mounted-node bound + headed scroll/anchor witness); the "N earlier events" fold is gone; ring drops stay surfaced as retention facts.
  • Head counts render ONLY producer-proven values (counts rows with complete: 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).
  • Row time contract preserved through the rebuild (viewer-local text + ISO title, headed current-head witness) and the live-deployment absence reproduced or explained at the deployment layer.
  • Anchored-scroll behavior witnessed: reading history + a prepend burst keeps the anchored record and shows N new events ↑; the role=log announcement contract survives recycling.
  • Rows name the actor once and the event's object where the event carries one; the narrow two-line collapse keeps fixed row height.
  • Token-governed in both themes; npm run check-theme-surfaces green.
  • Branch/code starts only after the buffered component-list engine leaf is filed and linked as blocked-by (no app-local virtualizer).

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")

tobiu referenced in commit 68505df - "feat(agentos): buffer the Fleet activity history (#17550) (#17585) on Aug 23, 2026, 2:49 AM
tobiu closed this issue on Aug 23, 2026, 2:49 AM