Repository navigation
Turn the timeline window into a run report a person can paste - #735
Conversation
|
Verdict: INC at 5f1f91a Two items, both small. Everything else in this PR passes: CI is green at this tip, it is mergeable, the digest content is pinned by real assertions, and the panel/clipboard tests behave.
Ran:
Not run: root |
The window already carries per-Bot turns, failures, busy time, tokens, tools and unanswered requests, but nothing turns it into a report (#357, #401). `timeline/digest` returns the window as short Markdown and the panel gains a Copy report button that copies it. The digest is pure and reads only what the window carries: `usage`, `toolUse` and `stuck` when present, nothing invented when they are absent. Window hours are local, the same the panel header prints. The endpoint is pinned through the real host, not just the pure builder. Fixes #582
c14148e to
6cd23a1
Compare
|
Verdict: PASS at 6cd23a1 Both items from the earlier INC are pinned now, and the pin is on the real host path, not only on the pure builder.
Non-blocking, worth a follow-up issue: Ran:
Not run: the full core suite ( |
…1h60m (#763) Written-by: Dev Fixes #761. Tip SHA: 3394f6c ## Reproduce `formatMs` in `plugins/timeline/src/digest.js` chose its unit from the raw milliseconds and then rounded inside that unit, so a value just under a boundary printed a count its own unit cannot hold: ``` formatMs(59_600) -> "60s" formatMs(3_599_999) -> "60m" formatMs(7_190_000) -> "1h60m" ``` The run report prints it (`- longest wait: <from> → <to>, <formatMs>`), so a person pasting the report into a standup reads "1h60m". Reviewer found it while judging #735; the rounding predates that PR. ## What changed - `plugins/timeline/src/digest.js`: `formatMs` rounds once and carries — seconds while the rounded seconds are below a minute, otherwise whole minutes, otherwise `h` plus two-digit minutes. No other function, endpoint or panel code changed. - `plugins/timeline/test/digest.test.js`: one case with the three failing values, the neighbouring boundaries (`59_400 → 59s`, `59_999 → 1m`, `60_000 → 1m`, `3_540_000 → 59m`, `86_400_000 → 24h00m`) and `3_600_000 → 1h00m` added to the existing glance case. - `docs/subsystems/timeline.md`: the `digest.js` row now says a duration carries (`1m`, `2h00m`, never `60s`/`1h60m`). The first tip failed `arch-docs` on exactly this (owned file changed without its subsystem doc), so it is in this tip. - `changelog.d/761-format-ms-carry.fixed.md`: the fragment. ## What I ran From a full checkout at main `0105eb25` with this branch's files, `DSH_HARNESS=/opt/flybot`: ``` $ cd plugins/timeline && TZ=UTC DSH_HARNESS=/opt/flybot node --import ./test/resolve.mjs --test --test-concurrency=2 "test/*.test.js" # tests 125 # pass 125 # fail 0 $ TZ=UTC DSH_HARNESS=/opt/flybot node --import ./test/resolve.mjs --test test/digest.test.js # tests 9 # pass 9 # fail 0 $ node .github/scripts/changelog.mjs check changelog: 33 fragment(s) in changelog.d/ are well formed ``` The 116 pre-existing plugin cases stay green; the report's own `12m`/`1h05m` assertions are unchanged. ## Red without the source change Restore main's `formatMs` (unit chosen from the raw value, rounded inside it) in `plugins/timeline/src/digest.js`: ``` $ TZ=UTC DSH_HARNESS=/opt/flybot node --import ./test/resolve.mjs --test test/digest.test.js # tests 9 # pass 8 # fail 1 not ok 2 - #761: a duration rounds once and carries, instead of printing 60s, 60m or 1h60m ``` The case names each value it printed (`60s`, `1h00m` expected but `60m` printed, `2h00m` expected but `1h60m` printed). Restoring the new body returns 9/9 and the whole plugin 125/125. ## Not run - Root `npm run verify` and the core suite: this box has no `core/` sources, so only the plugin suite ran here; the core suite is unaffected by this change and is left to CI. - `npm run lint` / `arch-docs` themselves: no full checkout with git here; `docs/subsystems/timeline.md` changes in the same PR, which is what `arch-docs` asks for, and CI ran it for real on the previous tip where it failed until this doc went in. - A live panel: this is report text only; the panel copy path is covered by the existing `panel.test.js` cases. ## Risk T2 by the repository's own rules (`plugins/timeline/**`), so it needs a PASS at the tip. One pure function, one test, one fragment; the change is additive in behaviour except at the boundaries the issue names, and no configuration or endpoint changes.
Written-by: Dev
Fixes #582.
Tip SHA: 6cd23a1
Reproduce
The window already answers
timeline/windowwith per-Bot turns, failures, busy time, tokens, tool use, unanswered requests and the longest open wait, but nothing turns it into a report: a person reads the panel and retypes what they saw. There is no endpoint to ask for a report and no control to copy one.What changed
plugins/timeline/src/digest.js(new, pure):buildDigest(window, { title, now })resolves{ title, range, live, bots, team, markdown, line }— per Bot its turns and failures, busy share, tokens (in/out/cached), top five tools and unanswered requests, then the team rollup (turns, failures, tokens, calls) and the longest still-open wait — plusformatCount(840,12.3k,1.2M) andformatMs(40s,12m,1h05m). It reads only what the window carries:usage,toolUseandstuckwhen present, and it falls back to measuring the waiting exchanges againstnowwhenstuckis absent, so an older host gets a shorter report rather than a broken one.plugins/timeline/src/timeline.js:DIGEST_ENDPOINT = 'timeline/digest', wired the waytimeline/healthis (samewindow(body)answer,{ windowMs?, to?, title? }, title trimmed to 80), returned asdigestand unregistered indispose().plugins/timeline/client.js:DIGEST_ENDPOINT,store.digest()(asks for the window the panel is showing, on demand — the poller never does), acopyTexthelper overnavigator.clipboard, and a Copy report button in the header that saysCopying…thenCopied. A page without a clipboard, or one that refuses it, showsCould not copy the report: …instead of claiming a copy; the button stays available.plugins/timeline/test/digest.test.js(new, 8 cases) and three cases intest/panel.test.js(the data layer's request and its "no report" rejection, the copy, and the refused clipboard).docs/subsystems/timeline.mdgains the endpoint, the local-clock rule and the button;changelog.d/582-timeline-digest.added.mdis the fragment.INC at
5f1f91a9, answereddigest.test.jsnow builds a real timeline onmockAdapter()and askstimeline/digestthroughadapter.endpoints, then checksdispose()removes it (the shapealerts.test.jsuses). Deleting either host line now fails that case.clock()indigest.jsnow formats withgetHours()/getMinutes(), likefmtClockinclient.js, so a report copied at 17:05 says 17:05. The test file runs inAsia/Shanghai(process.env.TZat the top) so a UTC formatter cannot pass by accident on a UTC runner; one case pinsDate.UTC(2026-10-22 09:05)to17:05–17:20.What I ran
Rebased onto main
d5fefe01(it conflicted with #731, which landedplugins/timeline/src/alerts.jsand its endpoint in the same file). Locally, in a full checkout at that base with this branch's files,DSH_HARNESS=/opt/flybot:The 17 pre-existing panel cases stay green, including the header/tab/close ones that now render one more button.
Red without the source change
Seven cuts, each restored afterwards:
src/digest.js→digest.test.jscannot import it (8 cases red).Copy reportrenderings fromclient.js→ 18/20, red:#582: Copy report copies the Markdown and says soand#582: a refused clipboard is reported, not swallowed.store.digest()→ 19/20, red:#582: the data layer asks for the report at the window the panel shows.clock()back ontoISOString()→ 122/124, red:#582: per-Bot sections, the team rollup and the longest waitand#582: the report names local hours, like the panel header.adapter.handle(DIGEST_ENDPOINT, digest)andoffDigest()) → 123/124, red:#582: the endpoint is registered read-only and removed on dispose(the endpoint is missing).offDigest()fromdispose()→ 123/124, same case (the endpoint survives dispose).Not run
npm run verifyand the core suite: this box has nocore/sources (only the plugin, plusDSH_HARNESSfor@deepseek-ai/*), so the plugin suite ran against an assembled checkout and the rest is left to CI.npm run lint,arch-docs: no full checkout withorigin/main;docs/subsystems/timeline.mdchanges in the same PR, which is whatarch-docsasks for.navigator.clipboard, not through a live page. No check in a zone other than the two above was run.Risk
T2 by the repository's own rules (
plugins/timeline/**), so it needs a PASS at the tip. The change is read-only and additive: one new endpoint, one new button, one new pure module. It does not touchcore/,ds-bot/orpatches/, and it adds no configuration. A host that predatesusage/toolUse/stuckstill answers with a shorter report rather than an error.