Console: Queue and Activity in the shared frame, Activity scrolls back through history
维护者通常 1 天内回复
评估
- 难度
- 4/5
- 预计耗时
- 3-5 天
- 新手友好度
- 48/100
- Issue 类型
- 功能
- 描述清晰度
- 描述清楚
- 活跃度
- 活跃
- 技术栈
- react, typescript
调研方向
Start with ui/src/data/event-feed.ts, ui/src/model/events.ts, ui/src/state/events.ts, and ui/src/ds/feed.tsx to trace event loading, merging, and virtual-list behavior. Then read the Activity and Queue screen entry points and the named model and feed tests. Done means older history loads and ends as specified, the list stays stable while reading, Queue and Activity use the shared frame and drawer, and the browser cases cover the listed states.
由索引模型根据 Issue 内容生成。
描述
Part of #756.
Scope
Queue and Activity move to the shared frame, with the drawer. Activity scrolls back through the daemon's whole event history as far as it is kept, instead of stopping at one hour. After this PR an operator reads why each request waits in the same table and drawer as everything else. They can scroll Activity back days to see what happened overnight.
In short
Queue (/queue) is a table: place, requester and the device asked for, stage, why it waits, and how long it has waited. Above it, the Wait p95 number is shown as today. A row opens the drawer at /queue/<requestId>. The drawer shows today's inspector facts: Waiting, Place, Stage, Device, Queue, Why, and the one row per worker on a gateway.
Activity (/activity) is the event list, newest first, in the shared toolbar: the type chips (All, Leases, Devices, Workers, Components, Other) on the left and a search box on the right. A row opens the drawer at /activity/<eventId> with the payload and the link to what it is about. New events arrive on top as today. While the reader has scrolled down, nothing moves, and the button "<n> new events" appears at the top.
- Infinite scroll: the page first loads the last hour, as today (
GET /v1/events?since=1h). When the reader comes within 20 rows of the end, it asks for the 100 events before the oldest one it holds (GET /v1/events?before=<oldest id>&limit=100, from #764). With no event held, it asks for the newest 100 (?limit=100). It adds them at the bottom and repeats until the history ends, or until the list holds 10,000 events, today's cap. - It loads by itself only while the chips are on All and the search is empty. With a filter on, the last row is the button Load older events: each press loads one page of 100, which may add no visible row.
- Search matches, ignoring case, any part of a row's event name or its summary line (who, which device, which worker), over the events held. The text is in the address as
?q=. - The last row says why the list stops. Times are the local date and time,
YYYY-MM-DD HH:MM.- At the cap: "Showing the newest 10,000 events."
- When the history ends (a page shorter than 100 in the raw reply, or one that adds no event the list does not already hold): "Start of the history ()".
- On a 422: "Older events are no longer kept."
- On a 503, any other error, or no answer within the client's timeout: "Older events are not available right now." with Try again. A 401 signs out, as for any read.
- When the first load returns no events, the page asks for the newest 100 (
?limit=100) at once, so a quiet last hour still shows older history. Only when that also returns none does it say "No events yet." - When a chip or search matches none of the held events, the page says "No events among those loaded." (for search, "No events match among those loaded."), with Load older events under it while the history has not ended.
- The last row sits below the scrolling list, outside it, so it never counts as an event row.
- An address
/activity/<id>for an event not held opens the drawer with "This event is not among those loaded. Older events load as you scroll the list, or with Load older events." - The Events per minute chart and its table stay above the list, as today.
flowchart LR
A["/activity (changed)"] -->|scroll near the end| B["GET /v1/events?before=…&limit=100 (new)"]
B -->|100 rows| A
B -->|fewer| C[Start of the history]
A -->|row| D["/activity/<id> drawer"]
If this goes wrong, Activity shows an event twice or skips some while scrolling back, or the list jumps while someone reads it.
Technical spec
Facts
| # | Fact | Source |
|---|---|---|
| F1 | The console loads GET /v1/events?since=1h and, after a stream reopens, the missed window |
ui/src/data/event-feed.ts:78, ui/src/model/events.ts:10-13 |
| F2 | mergeEvents dedups by id, sorts newest first by timestamp then seq, and keeps at most MAX_EVENTS = 10_000 |
ui/src/model/events.ts:10-13,46-64 |
| F3 | One EventFeed per screen; the stream feeds streamEvent |
ui/src/state/events.ts:30-37 |
| F4 | The feed is a virtual list (useVirtualizer) anchored to the start at the top and to the end when scrolled; it has no near-end callback |
ui/src/ds/feed.tsx:32-40 |
| F5 | The Events page shows "No events in the last hour." and gives Feed count, newText ("N new events") and startAt |
ui/src/screens/events/index.tsx:93-106 |
| F6 | Events page today: chart, filter all/leases/devices/workers/components/other in the address, payload inspector with a link | docs/CONSOLE.md:451-486 |
| F7 | Waiting page today: Wait p95 tile, rows place, requester, wait, device, stage; inspector facts and per-worker rows | docs/CONSOLE.md:388-415, ui/src/state/waiting.ts:22 |
| F8 | GET /v1/events?before=&limit= returns up to limit events older than the named one, oldest first; 422 HISTORY_NOT_KEPT, 503 HISTORY_UNAVAILABLE |
#764 In short |
| F9 | long-lists.spec.ts measures the events feed's frame time; #730 tracks its WebKit flake |
e2e/console/long-lists.spec.ts, #730 |
| F10 | Drawer, Toolbar, the search field and the addresses /queue[/<id>] and /activity[/<id>] come from #765 |
#756 Shared values |
| F11 | The long-lists stubs answer every /v1/events request the same, whatever the query; the frame-time case seeds 5,000 events and waits for them |
e2e/console/long-lists.spec.ts:27,119-127,703-751 |
| F12 | With no rows after filtering the screen draws Empty and no Feed |
ui/src/screens/events/index.tsx:95-99 |
| F13 | readEvent drops malformed events from a reply |
ui/src/model/events.ts:34-38 |
Modules touched
Searches: EventFeed, mergeEvents, MAX_EVENTS, Feed, useVirtualizer, WaitingScreen, EventsScreen, "Events per minute".
ui/src/data/event-feed.ts:loadOlder()callsbefore(orlimitalone when nothing is held) and reports the end, 422 or an error. The first load and the gap load after a reconnect staysince.ui/src/model/events.ts:mergeEventskeeps older pages, under today's 10,000 cap; the end rule (a raw page under 100, or a page adding nothing new); the search match.ui/src/state/events.ts: exposesloadOlderand the list's end state.ui/src/ds/feed.tsx: anonNearEndcallback when the last rendered row is within 20 of the end, and an end row.ui/src/screens/events/(renamedactivity/),ui/src/screens/waiting/(renamedqueue/): table or feed, drawer, toolbar, the end rows, Load older events.e2e/console/events.spec.ts(becomesactivity.spec.ts),waiting.spec.ts(becomesqueue.spec.ts),views.spec.ts, andlong-lists.spec.ts, whose/v1/eventsstubs answerbefore=with no events, so the stubbed list ends where it does today.docs/CONSOLE.md(Waiting → Queue, Events → Activity; "Event not in the last hour." goes),ui/AGENTS.md.
flowchart LR
s["screens/activity (changed)"] --> st["state/events (changed)"]
s --> feed["ds/feed (changed)"]
st --> ef["data/event-feed (changed)"]
st --> m["model/events (changed)"]
ef --> api["GET /v1/events?before (#764)"]
Contract and event changes
None in this task (#764 adds the query).
Other code on the same state
The state is the console's list of held events. Searches: mergeEvents(, streamEvent, EventFeed. The writers are the initial load, the gap load after a reconnect, the stream, and now loadOlder.
- Stream and
loadOlderat once: both go throughmergeEvents, which dedups byidand sorts, so an event appears once in its place. - The gap load after a reconnect adds newer events only, and
loadOlderadds older ones. Neither moves what the reader is looking at when scrolled down (F4's end anchor). - Overview (#768) reads recent events through its own feed and is unaffected.
Failure modes
| Call | Outcome | What happens | Limit, counted from |
|---|---|---|---|
loadOlder |
100 rows | appended at the bottom; may load again when near the end | the client's request timeout |
loadOlder |
fewer than 100 in the raw reply (before malformed events are dropped, F13) | appended; end row "Start of the history ()"; no more loads | — |
loadOlder |
422 | end row "Older events are no longer kept."; no more loads | — |
loadOlder |
503, 400, 403, 500, network error | end row "Older events are not available right now." with Try again | — |
loadOlder |
no answer | the same end row | the client's 10 s request timeout, from send |
loadOlder |
a full page that adds no new event | treated as the end: "Start of the history (…)", no more loads | — |
loadOlder |
401 | signed out, as for any read | — |
loadOlder |
the list holds 10,000 | no request; end row "Showing the newest 10,000 events." | — |
| two near-end signals before the first answer | — | one request at a time; the second is dropped | — |
Rules in play
- ADR 0013 §3: the stream and the refetch work as today;
loadOlderis a read of a route. - ADR 0037 §2: the drawer at
/activity/<id>and/queue/<id>. - DESIGN.md "Charts." bullet (
docs/internal/DESIGN.md:208-215): the chart keeps its table twin, as today. - ADR 0037 is Proposed until this feature's approval; the task follows it as accepted.
Tests
Seam: the browser lane (e2e/console/events.spec.ts becomes activity.spec.ts, waiting.spec.ts becomes queue.spec.ts), with a real daemon, and with /v1/events stubbed through page.route for the history's end states, as long-lists.spec.ts stubs it. The merge, end and search rules in Node in ui/src/model/events.test.ts and ui/src/data/event-feed.test.ts.
- Queue lists each waiting request with place, requester, device, stage, why and wait; a row opens
/queue/<id>with today's inspector facts and, on a gateway, one row per worker (movewaiting.spec.tscases) - a request that is granted while its drawer is open shows "Request not waiting." (as today)
- Activity lists events newest first; the chips and the search filter the list and are kept in the address (
?q=); search matches part of the event name or summary, ignoring case, and not other payload values (model test) - a row opens
/activity/<id>with every payload key and the link to its lease, worker or request - scrolling within 20 rows of the end loads the 100 events before the oldest held and appends them without moving the rows in view (stub: a last hour of 30 events, then pages of 100)
- with a chip or search on, nothing loads by itself, and Load older events loads one page per press
- an address
/activity/<id>for an event not held shows "This event is not among those loaded. Older events load as you scroll the list, or with Load older events." - each end row shows its wording in the browser, with stubbed answers: a page of 40 gives "Start of the history ()" and no further request; 10,000 held gives "Showing the newest 10,000 events."; a 422 gives "Older events are no longer kept."; a 503 and a 500 give "Older events are not available right now.", and Try again asks again; a full page of events already held ends the list
- a daemon with no events in the last hour but older history shows them at once (
?limit=100sent with no scrolling); an empty history shows "No events yet." - a chip that matches no held event shows "No events among those loaded." and Load older events, which loads one page
- an event that arrives by the stream while older pages load appears once, at the top
- while scrolled down, new events show " new events" and the rows in view do not move (extend the existing case)
mergeEventskeeps older pages in order and dedups by id across a page and the stream (extendevents.test.ts)- the long-lists frame-time check still passes with its 5,000 seeded events, its stubs answering
before=with none (long-lists.spec.ts) - axe passes on Queue and Activity with a drawer open, in light and dark; no
<style>element or CSP violation (extendviews.spec.ts)
Done when
pnpm test:console --project=chromiumpasses, and CI's Console check passes in every browser.
Out of scope
- Cancel and every other action (#769).
Depends on
The console tasks run one after another: #766 and this task both edit the route table, views.spec.ts's page list and the manual's Pages table.
- #765
- #766
- #764
Written by an agent.
- 主要语言
- TypeScript
- 星标
- 20
- 派生
- 2
- 平均合并
- 8 小时 13 分钟
- 30 天内合并 PR
- 297
环境准备
这个项目没有提供开发容器、Dockerfile 或贡献指南,环境需要你自己搭建:先看它的 README,通用步骤见我们的新手贡献指南。
从这里开始
- 先读完整个 Issue,再读项目的贡献指南。
- 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
- Fork 仓库,在一个分支上完成修改。
- 提交 Pull Request,并在描述里引用这个 Issue 编号。
callstackincubator/simlock 的其他 Issue
-
难度 2/5 1-3 小时 新手友好度 70/100
callstackincubator/simlock#639 · 1 条评论 ·
维护者通常 1 天内回复
-
feature:spec
难度 5/5 一周以上 新手友好度 25/100
callstackincubator/simlock#771 ·
维护者通常 1 天内回复
-
task:ready
难度 5/5 一周以上 新手友好度 25/100
callstackincubator/simlock#770 ·
维护者通常 1 天内回复
-
task:ready
难度 5/5 一周以上 新手友好度 25/100
callstackincubator/simlock#769 ·
维护者通常 1 天内回复
-
Console: Overview home and Inbox可能已有人在做 关联的 PR 仍在进行中或已合并。 未关闭task:ready
难度 5/5 一周以上 新手友好度 38/100
callstackincubator/simlock#768 ·
维护者通常 1 天内回复
查看 callstackincubator/simlock 的全部 Issue
相似的 Issue
-
check:passed streams:edit
难度 2/5 1-3 小时 新手友好度 68/100
维护者通常 1 天内回复
-
area:mcp bug has repro platform:vscode regression
难度 2/5 1-3 小时 新手友好度 68/100
anthropics/claude-code#103970 ·
维护者通常 1 天内回复
-
难度 2/5 1-3 小时 新手友好度 78/100
维护者通常 1 天内回复
-
bug
难度 2/5 1-3 小时 新手友好度 84/100
维护者通常 1 天内回复
-
sdk-typescript: unquoted COPY globs in shell form are silently dropped from the image build context未关闭bug javascript
难度 2/5 1-3 小时 新手友好度 78/100
维护者通常 1 天内回复