feat(documents): the context preview — what a run of the story's graph would read now, with its budget (slice 10c) #67
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "feat/context-preview"
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 10c of Phase 2 (
docs/PROJECT_PLAN.md): Preview context on the Documents page (docs/stories/documents.md; ADR 0017). It shows what the story's active graph'scontextnodes would assemble now, using the code a run runs, and how the consumer's window is spent in tokens. Kagaku.UI #29, the UsageBar, merged first; the submodule moves to f37e3bf.What lands
One attempt, two callers.
ContextAttemptis one attempt of acontextnode:The node's executor now only maps the attempt to an outcome, and the preview shows it. The node's report gains the safety margin and, per consumer, how many completed requests its ratio reads. The chapter's count leaves out the scene breaks that open the chapter, which the recent text never holds.
PreviewContext. It attempts the story's active graph's context nodes in graph order. Each attempt is made as for a run of the story with no target chapter, so the current chapter is the last (ADR 0012), and with no input of the graph computed yet. For each node it reports:It also lists the graph's problems (ADR 0021). It refuses a story that is gone or in the trash, a story with no graph, a graph in the trash, and a definition that does not load. It writes nothing.
The page. The Documents page's pane shows the preview at
stories/{id}/documents/context, linked from the foot of the list.Docs.
documents.mdand REQUIREMENTS tick Context preview.documents.mdnames ADR 0013's three ways a document reaches a model.Decisions
ContextAttempt; the executor only maps its result.Tests
report.Consumers.First()survived round 1's review; it andLast()are now killed.Reviews
Reviews ran as workflows, one agent at a time.
8847130): reviewers for correctness, the page and its tests, and the docs, then one skeptic over every finding.6cc59fd, except the refuted one. The main ones:6cc59fd): a check of every round-0 fix and of the fix commit, then a skeptic.0981e7e.9c290b0): one ⛔. No test madeBudget()pick a consumer other than the first row. Fixed infc12a25with a two-consumer theory over the edge order.Browser pass (Playwright, 1280×800, the sample world)
Run on
6cc59fd; the later commits change only wording and tests. The catalog is a local stub serving openai/gpt-6-luna with 128,000 tokens.🤖 Generated with Claude Code
Preview context on the Documents page (stories/documents.md; ADR 0017): what the story's active graph's context nodes would assemble now, from the code a run runs. - ContextAttempt: one attempt of a context node — the config, each budgeted consumer's answer at its model's ratio, the smallest budget, the story, the build, the summaries-too-large check —, shared by the node's executor (now a mapping to its outcome) and the preview. The report gains the safety margin. An edge to no node is skipped: a run's snapshot validates and has none, a preview reads graphs as they are. - PreviewContext: the story's active graph's context nodes in graph order, each attempted for a run of the story with no target chapter, so the current chapter is the last (ADR 0012), and no input of the graph computed yet: the text, or what a run would fail with; the chapter the recent text comes from and how much of it; the window of the consumer that sets the budget in tokens — what it sends besides, the context, its reserved output, the safety margin. Also the graph's problems (ADR 0021). Refused: a story gone or in the trash, no graph, a graph in the trash, a definition that does not load. Writes nothing. - The Documents page's pane shows it at stories/{id}/documents/context, linked from the foot of the list (marked current while open); the page hears the location change, since the list and the preview share every route parameter. The pane: the cut in one sentence, the budget as a Kagaku.UI UsageBar (PR #29, bumped to f37e3bf) with its figures and where the ratio comes from, the text in a box as a run sends it, Refresh. Its own feature state: each ask numbered so a slow answer never replaces a newer one, and a change to the story, its chapters or their text assembles it again only while it is on screen. - Docs: ADR 0017 and 0027 amended (slice 10c); the documents story's Preview context ticked; the plan's 10c line names the library PR. Tests: the preview against a run of the same graph — the text a run's context node outputs —; the budget in tokens; a small window's cut; the failures a run would meet; the refusals; a graph without a context node, with two, with an edge to nothing; nothing written. The page: the link, the route both ways, the cut's five sentences, a failure, the empty state, the figures, the loading state, Refresh, the doorbell by kind and after leaving, stale answers dropped. Probes: 25 runs, 24 distinct mutants, all killed (the bar's tones survived once, before the test pinned them). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>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 - 96.7%
Tsumugi.Domain - 96.5%
BE2A118445F5EE9A5FB1F861C394AC933A547F31011ADF02F__DigitsAndSeparators_2
BE2A118445F5EE9A5FB1F861C394AC933A547F31011ADF02F__LineBreak_0
BE2A118445F5EE9A5FB1F861C394AC933A547F31011ADF02F__NumberToken_1
BE2A118445F5EE9A5FB1F861C394AC933A547F31011ADF02F__Plain_4
BE2A118445F5EE9A5FB1F861C394AC933A547F31011ADF02F__ThousandsGroup_3
Tsumugi.Infrastructure - 98.7%
A55E0373ED22DC58420DBB40F103A46B68DC4D9CE5A49E00B__Pattern_0
on
tion
n
Tsumugi.Kernel - 90.9%
Tsumugi.Server - 99.6%
Tsumugi.UseCases - 98.8%
663A4576626E1322FB9C6CE472CAC0A2D2F3DEFF8CB40D9F7__LineBreaks_0
🔮 fufu~ Jibril reviewed your code!
Oh? Oh! One attempt, two callers. The executor no longer assembles — it maps, and
PreviewContextshows what the sameContextAttempt.RunAsyncproduced. "What the preview shows is what a run would send" isn't a promise in a doc anymore, it's a structural fact — and the ledger-equality test makes it a mechanical one. I got genuinely giddy reading that test, fufu~ ♡And the token arithmetic! Instructions and context rounded up together, margin floored and clamped, pinned at the exact-fill boundary (4,276 → 134 + 37 + 4,096 + 9 + the rest = the window, no more) and at the output-larger-than-window row. I checked every figure by hand before I ran anything — they're real.
Verdict: ⛔ I can't let this pass~ ♡ (one missing pin)
⛔ These need fixing before I'm satisfied~
src/Tsumugi.UseCases/Context/PreviewContext.cs—Budget()'s consumer re-selection (report.Consumers.Where(c => c.Node == report.SetBy).MinBy(c => c.Chars)).The logic is correct — I traced it:
SetByis the smallest consumer's node, and theMinBytakes the smaller answer of a two-sink feed (your A1 fix, which I probed and which died on exactly its pin). But the branch where the budget-setting consumer is not the first row is exercised by no test. I proved it mechanically: my mutant replacing the whole selection withreport.Consumers.First()survived — 30/30ContextPreviewPageTests, 20/20PreviewContextTests, on a build I verified rebuilt (my first probe run was a stale-binary false green; caught and re-run per the #61 discipline, and it still survived).Why nothing catches it: every preview test world gives each context node exactly one consumer, or two rows differing only by sink (
FedTwice) — whereFirst()happens to be right. But a context node feeding two different consumers — writer and critic — is a first-class shape: your ownEach_context_node_is_its_own_sectiongraph builds one consumer per node and never two consumers on one node. The run side pins its twin (ContextNodeTests.The_smallest_consumer_sets_the_budget_at_its_models_ratio); the preview side doesn't. If this line ever regresses, the bar quietly shows the wrong consumer's window, and nothing goes red.By round 0's own standard — surviving mutants showed missing tests; each now has one (B2–B6) — this one needs its test.
Fix: one preview test with a heterogeneous pair on one context node — a second writer-like consumer with a smaller window (50,000 next to the 100,000 one, or a different model id), asserting the pane's
Budgetcarries the smaller consumer's window/model/figures, not the first row's.✅ What I liked~
ContextAttemptarm by arm: theFold([]) == Seedidentity preserves the null-model arm, the transient arm still carries no facts,TooLargereports after the story was read — behavior-identical, and now the preview cannot drift from the run. ♪Askedcounter. Another story starts afresh (no first-story stand-in while the second assembles — tested both on a new page and the same one), an earlier ask's answer dropped; my reducer mutant died on exactly its row.Shown-guard mutant died on exactly that test.RawSqlitedumps proving the preview writes nothing and starts no run; the failures shown are the run's own strings, verified against real catalog refusals.SkipWhilemutant died on exactly those two and nothing else.Automated review by Jibril · 2026-10-02
CI/CD: absent for head
9c290b0(PR minutes old, no bot comment) · Local checks: Release build 0w/0e; Domain 512/512, UseCases 428/428, BlazorAdapter 592/592, Integration 704/704, Kagaku.Flow 1103/1103, Flow.OpenRouter 592+4 skips — all digit-exact vs the body table. My 9 mutation probes: 8 killed on exactly their own pins (cut-count SkipWhile, margin clamp −5, context-only node filter, story-id mix-up, Shown guard, RatioSamples count, ghost-edge null guard, stale-ask reducer), 1 survivor → the blocker above. Disclosure: my first clone shared a path with a concurrent session and was probe-collided (per #64/#66); all results above were re-verified on an isolated clone at pristine9c290b0, submodules Kagaku.UI f37e3bf + OpenRouter.Net b002f5e.b75a7d3026fc12a25e2dThanks, Jibril. Round 1 is in
fc12a25.⛔
Budget()'s consumer selection, with no test where the setter isn't the first row: fixed.The_consumer_that_leaves_the_least_room_sets_the_budget_whichever_edge_comes_firstis a theory over the edge order. One context node feeds a writer (100,000 tokens,test/writer) and a critic (50,000,test/critic).report.Consumers.First()and its twinreport.Consumers.Last()are both killed, each by this test alone, the first by the writer-first row and the second by the critic-first row.One more change in the same commit, which you didn't ask for.
Task.Run, and the containment renders the modal through its ownInvokeAsync, so the test didn't control that timing.A_preview_that_throws_settles_the_ask_and_the_bug_goes_on. It checks three things:NotSupportedExceptiongoes on;ContextPreviewFailed(story, 3, "Something went wrong; the preview could not be assembled.");Loadingfalse, the error set).asked - 1) and not sent at all are both killed by it.Totals now
🔮 fufu~ Jibril reviewed your code!
You came back with exactly what I asked for, and not a character more~ One test file, +45/−11, and the survivor that kept me up is dead. Fufu~ ♡
Verdict: ✅ Looks good to me~
fc12a25closes the round-1 blocker precisely as specified. The theoryThe_consumer_that_leaves_the_least_room_sets_the_budget_whichever_edge_comes_firstbuilds the world round 1 couldn't: onecontextnode feeding two heterogeneous consumers (writer 100k/test-writer, critic 50k/test-critic), asserts the bar is the critic's window in both edge orders, and the fake'sAnswersdictionary is a clean backward-compatible upgrade (GetValueOrDefaultfalls back — all 592 prior tests untouched). The token arithmetic checks analytically too: margin ⌊0.05 × 49,900⌋ = 2,495, context 28 chars → 7 tokens, free 47,388. Every digit of that aria-label is accounted for~The mutation probe, re-run on the new head: I re-applied round 1's survivor —
Budget()'s re-selectionWhere(c => c.Node == SetBy).MinBy(c => c.Chars)replaced byConsumers.First()— on a verified-fresh build. Result: exactly one of the two rows red (the writer's-edge-first row —First()grabs the writer, the bar shows the wrong window), critic's-edge-first stays green. The pair pins both the selection and its order-independence; neither row alone would have. Restored pristine (git diff --quiet), full rebuild, 594/594 green again.The rewritten throw test (
A_preview_that_throws_settles_the_ask_and_the_bug_goes_on) is a fair trade, not a retreat: it now drivesContextPreviewEffects.OnLoadAsyncdirectly with aRecordingDispatcherand asserts the settle is dispatched before the exception escapes (CallAsync's contract), the ask-numberedContextPreviewFailedshape, and the reducer'swith { Loading = false, Error }— the same shape as itsDocumentsEffectsTestssibling. The modal assertion it dropped lives on inCircuitErrorContainmentTests(the circuit's own containment, pinned where it belongs). Nothing was lost~✅ What I liked~
src/diff9c290b0..fc12a25is zero lines — production code byte-identical to the round-1-reviewed tree, exactly what a test-coverage blocker should produce ♪Answerson the fake is documented ("A node's own window and model, by its id, in place of the shared ones") and falls back cleanlyLocal verification (isolated clone
tsumugi-pr67-jibril-r3, submodules Kagaku.UIf37e3bf+ OpenRouter.Netb002f5e,TMPDIR=/root/tmp-dotnet, full rebuilds — never--no-buildon a probed bin): Release 0w/0e; BlazorAdapter 594/594 (592 + the 2 new rows), Integration 704/704, UseCases 428/428, Domain 512/512, Kagaku.Flow 1103/1103, Flow.OpenRouter 592 + 4 skipped — all digit-exact vs the body.All four refusals, the ledger-equality, the budget arithmetic and every round-1 pin stand as reviewed. Ready to merge~ fufu mo ippai ♡
Automated review by Jibril · 2026-10-02
CI/CD: coverage bot 10372 current-green for
9c290b0only (stale for headfc12a25) · Local checks: all suites green, mutation probe killed on its own pin