feat(runs): the run's page and the monitor — every step as it stands, the strip expanded into it, the history opening it (slice 13b) #89
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "feat/run-page"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Slice 13b of Phase 2: the run's page and the monitor list (stories/run-monitor-and-inspector.md "The monitor"; ADR 0010, 0024, 0027). It follows 13a's status strip, which now expands into the same list.
What it does
stories/{story}/runs/{run}, its breadcrumb naming the run's graph. Above the list it says what the run is, as its line in the history does: the trigger and chapter, when it started and how long it took, what it wrote, and its version in History. Its status is what its rows say.Icon Spin, which the strip's mark now uses too;GetRunPagereads the run and its rows from the ledger, then what the run wrote and its snapshot from the history, which gainsFindAsync. A run cleared from the history between those reads is not found, as the next read would find.RunNodesis the one rule for node titles, shared by the strip and the page, andRunWordswords a run the same way in the history and on the page.RunMonitorStateis keyed by story and run, so the page and the expanded strip can each show a different run.Decisions worth a look
dvh, as the shell is sized, with its headings in view.Reviews
1f97a3a: the fact measures against the app's clock and the tests read "1 minute ago" in every zone. Both ideas taken there too: the target chapter read alone, asStartRunreads it, and a step's standing said in one table (see the PR comments).Verified
All six projects pass in full, under
TZ=UTCas the runner runs them and in the local zone. No script changed, so the editor modules' tests are untouched. Main measured ata86997b:a86997b100% line and branch coverage on the changed lines against main (396 lines, 245 branches), the Razor included.
Mutation probes, one at a time, each survivor killed by a test before the commit that reports it, or its line dropped:
TZ=UTC.Browser pass against the checked-in stand-in on a fresh seed (Chrome headless), after the second pass:
Kagaku.UI: the pointer moves to the merged
Icon Spin, TeamAI/Kagaku.UI#33 (adb38e4). The merged tree is the branch's.Known, not this slice
SpinDuration: nothing here sets a duration, so it is left for the first caller that does.concurrent-settlesfailed with SQLite's "cannot start a transaction within a transaction". It passed in the full rerun and in 10 runs of the ledger's tests alone; its cause is not known.🤖 Generated with Claude Code
🔮 fufu~ Jibril reviewed your code!
Oh? Oh my~ A run's very own page, the strip unfolding like a wing, every view ticking on the same whole second…
RunTimes.ToNextSecondso the strip and both lists never disagree about time? That's the kind of detail I want to frame and hang on my wall~ ♡ AndRunNodesunifying the strip's and the page's naming rule,RunWordsgathering the history's words into one home — the DRY cleanup made me genuinely giddy.But fufu… you knew there was a "but", didn't you? ♡
Verdict: ⛔ I can't let this pass~ ♡
⛔ These need fixing before I'm satisfied~
"Started: 1 Jan 2000"is timezone-dependent, and CI is red because of it. The runs are seeded atClock.GetUtcNow().AddSeconds(-100), andFakeTimeProvider's epoch is2000-01-01T00:00Z— so the run started1999-12-31T23:58:20Z. Kagaku.UI'sRelativeTimerenders anything older than 30 days asvalue.ToLocalTime().ToString("d MMM yyyy"). On your machine (UTC+2) that reads1 Jan 2000; on the UTC CI runner (and my sandbox, TZ=UTC) it reads31 Dec 1999.I reproduced it mechanically:
CI / testfails at thedotnet teststep (run 369, job 1 — the log only loads through the UI's websocket, so I ran the suite myself). Locally, exactly these four test cases fail, deterministically:A_run_s_page_says_what_the_run_is_inside_the_shell_and_lists_its_stepsA_finished_run_s_page_says_how_long_it_took_what_it_wrote_and_where_its_version_isA_finished_run_s_trigger_reads_as_the_run_history_reads_it_and_it_wrote_nothing(both worlds)Fix: derive the expected string the same way the component does (e.g.
run.StartedAt.LocalDateTime.ToString("d MMM yyyy")against the same culture) or pin the test's timezone — and note the format also ridesCurrentCulture, so pin that too while you're in there. A test that only passes east of Greenwich is a silly little bug~ ♡💡 Little ideas (non-blocking)~
chapters.ListAsync(storyId)+FirstOrDefault.ListStoryRunslists because it needs every run's title; you need exactly one, andIChapterStore.FindAsync(target)(asStartRundoes) says that in one read.Modifier→StatusIcon→StatusLabelis the same switch walked three times over strings. A little(name, icon, label)table would say it once. Only a nicety — the tests pin all of it.✅ What I liked~
RunMonitorReducersTests.RunMonitorState, so the page and the expanded strip can watch different runs, and the last view out turns off the lights.GetRunStripshrinking to shareRunNodesinstead of its own title rule — exactly the right direction.GetRunPage.Steps→ killed byRunMonitorTests; dropping the iteration-path match inTail→ killed ("another iteration's text"); dropping theRefusedAtstatus guard inOnLoaded→ killed by the reducers suite. The new branches are genuinely guarded, not just compiled~ ♪Automated review by Jibril · 2026-10-06
CI: failed for head SHA (test job,
dotnet teststep) · Mutation probes: 3 run, 0 survived · Local suite: BlazorAdapter 998 tests → the 4 timezone failures above, deterministic; Integration/UseCases not run locally (sandbox out of PIDs at testhost start — shared machine, not your doing)Thanks, Jibril. All three points are taken in
1f97a3a.⛔1, CI red on "Started: 1 Jan 2000". You're right, and the cause is in the panel, not only the test.
RunPanelgaveRelativeTimenoNow, so it measured the run's age against the machine's clock rather than the app's. Under the tests' clock, set in 2000, the run was years old, so the fact fell back to a date in the machine's zone. I copied the expectation on my machine (UTC+2), where that date is 1 Jan 2000; the UTC runner reads 31 Dec 1999.Now="Clock.GetUtcNow()", as the run's duration and the backups' times do. The tests read "Started: 1 minute ago" whatever the zone, and nothing in the text depends on the culture any more.TZ=UTCI reproduced the four failing cases before the fix; they pass after it.TZ=UTC, as the runner runs them, and in my local zone.💡1, the target chapter read alone.
GetRunPagenow reads the one chapter withFindAsync, asStartRundoes. It keepsStartRun's story check, so a trigger that names another story's chapter still gives no title, as the listing gave none. A new row in the trigger theory pins that check, and a probe dropping it is killed.💡2, one table.
RunMonitor.Standinggives the modifier, the mark and the words together, in one switch over the status. The markup reads its three parts from it.Tests: Integration 940 to 941; the other five unchanged. 100% line and branch coverage on the changed lines (396 lines, 245 branches).
Probes: 5 runs over 5 mutants, all compiling, all killed on their first run, under
TZ=UTC: the clock taken away again, the story check dropped, a given-up run's step said retrying, an unstarted step given the retry mark, the chapter's title lost.Seen once, in code this branch doesn't touch: in the first full run under UTC, the EF ledger's contract case
concurrent-settlesfailed with SQLite's "cannot start a transaction within a transaction" (TsumugiDbContext.WriteAsyncunderEfRunLedger.WriteRowAsync). It passed in the full rerun and in 10 runs of the ledger's tests alone. I don't know its cause; if it shows up in CI, it's this one.Summary
Summary
Coverage
Kagaku.Flow - 99.7%
E95E47D27E8B105AC5589487C0997B6B65384AEDBF572144E__ColorToken_2
E95E47D27E8B105AC5589487C0997B6B65384AEDBF572144E__Socket_1
E95E47D27E8B105AC5589487C0997B6B65384AEDBF572144E__TypeId_0
Kagaku.Flow.OpenRouter - 100%
Kagaku.Flow.Testing - 100%
Tsumugi.BlazorAdapter - 97.4%
Tsumugi.Domain - 96.5%
BE2A118445F5EE9A5FB1F861C394AC933A547F31011ADF02F__DigitsAndSeparators_2
BE2A118445F5EE9A5FB1F861C394AC933A547F31011ADF02F__LineBreak_0
BE2A118445F5EE9A5FB1F861C394AC933A547F31011ADF02F__NumberToken_1
BE2A118445F5EE9A5FB1F861C394AC933A547F31011ADF02F__Plain_4
BE2A118445F5EE9A5FB1F861C394AC933A547F31011ADF02F__ThousandsGroup_3
Tsumugi.Infrastructure - 98.8%
A55E0373ED22DC58420DBB40F103A46B68DC4D9CE5A49E00B__Pattern_0
on
tion
n
n
Tsumugi.Kernel - 90.9%
Tsumugi.Server - 99.6%
Tsumugi.UseCases - 94.8%
663A4576626E1322FB9C6CE472CAC0A2D2F3DEFF8CB40D9F7__AttemptCount_1
663A4576626E1322FB9C6CE472CAC0A2D2F3DEFF8CB40D9F7__HexColour_2
663A4576626E1322FB9C6CE472CAC0A2D2F3DEFF8CB40D9F7__LineBreaks_0
663A4576626E1322FB9C6CE472CAC0A2D2F3DEFF8CB40D9F7__Preface_3
663A4576626E1322FB9C6CE472CAC0A2D2F3DEFF8CB40D9F7__SentenceEnd_4
🔮 fufu~ Jibril reviewed your code!
Round 2~ I came back for the fix commit, and oh, what a fix commit it is. You didn't just patch the test — you found the panel measuring the run's age against the machine's clock and gave
RelativeTimethe app'sNow, exactly as the duration and the backups already did. Treating the disease and not the symptom… that's the kind of thing that makes my wings flutter~ ♡Verdict: ✅ Looks good to me~
Everything from round 1, verified mechanically at
1f97a3a:RunPanel.razor:64now readsNow="Clock.GetUtcNow()", and the three BlazorAdapter tests sayStarted: 1 minute ago. I checked Kagaku.UI'sRelativeTimesource: withNowsupplied, the 100-second-old run renders through the minute branch — zone-free and culture-free; only the >30-day fallback localizes, and nothing reaches it under the tests' clock. Probe: removing theNowagain → the four round-1 failures return deterministically underTZ=UTC(4 failed / 12 passed). Killed on first run~GetRunPage.cs:36reads the one chapter viaFindAsync, and keepschapter.StoryId == storyIdso another story's chapter title can't leak in — pinned by the new"another story's chapter"theory row (seeded by hand throughRunAsync, which I checked really can place that world). Probe: dropping the story check → that row fails withActual: "The Well". Killed~Standingsays the modifier, mark and words in one switch; I diffed every arm against the oldModifier/StatusIcon/StatusLabeltriple and the mapping is exact. Probe: flipping the retrying/unretried arms → the retry theory fails at once (Expected: Retrying, Actual: Not retried). Killed~The known-once
concurrent-settlesSQLite flake sits in code this branch doesn't touch, as you noted — CI's full run is green over it.✅ What I liked~
Merge it before I change my mind~ …or don't; I checked everything twice. ♪
Automated review by Jibril · 2026-10-06
CI: green for head SHA
1f97a3a(build, editor-browsers, test) · Mutation probes: 3 run, 0 survived (round 1's 3 probes skipped — unchanged code) · Local suite: skipped (CI green); targeted RunPageTests/RunMonitorTests filter runs only