feat(editor): M2b — the chapter page switches to the manuscript editor; segment text becomes inline markup #38

Merged
bjoern merged 4 commits from feat/manuscript-switch into main 2026-09-26 11:01:38 +02:00
Member

M2b of the manuscript editor track (ADR 0030, PROJECT_PLAN.md): the chapter page switches from the textarea column to the ProseMirror surface M2a built, and segment text becomes inline markup.

What changes

The chapter page. ManuscriptSurface (Razor) mounts the module on the chapter as stored and never renders inside its element. It answers the module's events strictly in order, one at a time, each through its use case in a service scope of its own, so its writes never share a DbContext with the page's effects. After any change to the workspace it re-reads the chapter inside the same queue, starting only once the answer before it has been returned, and sends the module only what differs from what it last told it:

  • an answer counts only for what its write stored, so the page's own echoes send nothing, while a removal or a move made elsewhere meanwhile still goes out;
  • a delta counts as told only once its call has returned, so one the circuit lost goes again with the next re-read.

A page left while the module is still loading mounts nothing, and a surface that arrives late is still flushed and destroyed. The first re-read after an undo or redo carries the caret. Events over 24 KB arrive in parts and are applied as one. ManuscriptColumn, SegmentRow, their script and the per-paragraph request actions are gone.

Undo. The shell stays the one undo path: SaveScope flushes the module first, and the module's Edit-menu undo asks the same path. The shell gains Cmd+Z, Cmd+Y, Cmd+Shift+Z and Ctrl+Shift+Z; hotkeys match their modifiers exactly.

The server's checks (ManuscriptWrites, applied under the write lock in EfSegmentStore.WriteChapterAsync, one journaled operation per event):

  • stamps are compared for equality;
  • a minted id is refused if any segment or any journal row already uses it;
  • a reshape's base must be the stored order of the segments it names, and segments added elsewhere are woven in after the one they followed;
  • the new order is the base minus the removed plus the created, each exactly once, and no paragraph may be named twice;
  • a refusal answers with the chapter as stored and writes nothing.

Segment.Edit and Segment.Reshape do nothing when the text stores as it already is. A move within a chapter (Reposition, used by the editor's writes, reorders and inserts alike) leaves a segment's stamp, and every chapter read breaks an Order tie by id, as the checks do. Split and merge store what the editor sends, and the server no longer cuts or joins text.

Provenance:

  • a split keeps it when the halves are the old words, whitespace aside;
  • a merge with an empty paragraph keeps the full side's provenance, whichever id the editor kept;
  • a merge of two sides from one run, node and model keeps it, unless the writer had edited the removed side;
  • any other merge, and a reshape's rewrite, marks run text edited.

The migration SegmentTextMarkup escapes every stored segment text, and every segment journal bag's Text, into the canonical markup that shows exactly what it showed: a stored *sigh* stays literal. It calls the C# codec through SQLite functions the app registers on every connection it opens, the design-time factory's included. It rebuilds no table, so it commits whole with its history row; Down writes the visible text back.

Build. dotnet build builds both script modules and registers their output as static web assets in that same build; -p:SkipModuleBuild=true skips them. Each module's npm ci re-runs when its lockfile changes. The Dockerfile's module stage builds both. CI keeps the module's typecheck, vitest and browser jobs.

Docs. ADR 0030 is amended where the implementation is more precise:

  • the host's scope, queue and mirror;
  • the server's checks, stamps and no-op writes;
  • split and merge provenance;
  • the Cmd+Y binding and the caret after an undo;
  • the migration's mechanics.

ADR 0027 is amended too (its flush paragraph, the undo count in place of the caret request, Cmd+Y). AGENTS.md, the workspace story, MANUAL-MATRIX.md and README are updated.

Verification

  • dotnet test: every project green — Domain 466, UseCases 155, Integration 373, BlazorAdapter 354, Kagaku.Flow 1103, Kagaku.Flow.OpenRouter 427.

  • Mutation probes (Jibril-style), each confirmed red by the tests:

    • 21 on the host;
    • 27 on the write planner and the merge rule;
    • 5 on the store (used-id reads, tie-breaks, stamps on a move);
    • 9 on the migration and its function registration;
    • 1 on the build wiring: the old registration order, checked on a clean build both ways.

    Every survivor got a test.

  • A five-lens review with a skeptic per lens (host, writes, migration, build, tests and docs): 20 of 23 findings held up and are fixed here.

    • A page left while the module was still loading crashed the circuit.
    • A write's answer hid a removal or a move made in another tab.
    • A lost applyDelta was never sent again.
    • A re-read could run inline before the write's answer returned.
    • A clean dotnet build / publish left manuscript-editor.* out of the static-asset manifest (the Docker image was unaffected).
    • A merge dropped the edited flag of a run paragraph the writer had typed in.
    • Reads broke Order ties differently from the write path.
    • In-chapter moves re-stamped paragraphs.
    • A reshape could name one id twice in place of another.

    The last came out of the mutation probes before the review.

  • Browser pass against the seeded app, each engine on a fresh seed — Chromium 27/27, Firefox 27/27:

    • the mount and provenance marks (the icon on hover);
    • a typing pause saved, with *sigh* kept literal;
    • split and merge stored;
    • Ctrl+Z / Ctrl+Y in place, with the caret in the change;
    • the toolbar's scene break;
    • a second tab picking up the change and converging while both type;
    • typing that survives leaving the page inside the pause;
    • History's labels;
    • no console errors.

Not in this PR

  • The Ctrl+B / Ctrl+I toggles and a formatted seed segment (M3).
  • A narrow race the host cannot close by itself: in the milliseconds between a write's answer being returned and it reaching the browser, another tab's write to the same paragraph can arrive in a delta first. The module then keeps the older stamp, and its next split or merge of that paragraph is refused and adopts the stored chapter. Closing it means the module keeping a stamp a delta recorded while the event was in flight (M2a's session).
  • Unchanged from before:
    • every save still comes back to its own circuit as a manuscript reload through the bridge; the surface then finds nothing to send;
    • the chapter header's flush does not await its journal writes;
    • undo's replay runs outside the write lock.

🤖 Generated with Claude Code

M2b of the manuscript editor track (ADR 0030, `PROJECT_PLAN.md`): the chapter page switches from the textarea column to the ProseMirror surface M2a built, and segment text becomes inline markup. ## What changes **The chapter page.** `ManuscriptSurface` (Razor) mounts the module on the chapter as stored and never renders inside its element. It answers the module's events strictly in order, one at a time, each through its use case in a service scope of its own, so its writes never share a `DbContext` with the page's effects. After any change to the workspace it re-reads the chapter inside the same queue, starting only once the answer before it has been returned, and sends the module only what differs from what it last told it: - an answer counts only for what its write stored, so the page's own echoes send nothing, while a removal or a move made elsewhere meanwhile still goes out; - a delta counts as told only once its call has returned, so one the circuit lost goes again with the next re-read. A page left while the module is still loading mounts nothing, and a surface that arrives late is still flushed and destroyed. The first re-read after an undo or redo carries the caret. Events over 24 KB arrive in parts and are applied as one. `ManuscriptColumn`, `SegmentRow`, their script and the per-paragraph request actions are gone. **Undo.** The shell stays the one undo path: `SaveScope` flushes the module first, and the module's Edit-menu undo asks the same path. The shell gains Cmd+Z, Cmd+Y, Cmd+Shift+Z and Ctrl+Shift+Z; hotkeys match their modifiers exactly. **The server's checks** (`ManuscriptWrites`, applied under the write lock in `EfSegmentStore.WriteChapterAsync`, one journaled operation per event): - stamps are compared for equality; - a minted id is refused if any segment or any journal row already uses it; - a reshape's base must be the stored order of the segments it names, and segments added elsewhere are woven in after the one they followed; - the new order is the base minus the removed plus the created, each exactly once, and no paragraph may be named twice; - a refusal answers with the chapter as stored and writes nothing. `Segment.Edit` and `Segment.Reshape` do nothing when the text stores as it already is. A move within a chapter (`Reposition`, used by the editor's writes, reorders and inserts alike) leaves a segment's stamp, and every chapter read breaks an `Order` tie by id, as the checks do. Split and merge store what the editor sends, and the server no longer cuts or joins text. Provenance: - a split keeps it when the halves are the old words, whitespace aside; - a merge with an empty paragraph keeps the full side's provenance, whichever id the editor kept; - a merge of two sides from one run, node and model keeps it, unless the writer had edited the removed side; - any other merge, and a reshape's rewrite, marks run text `edited`. **The migration** `SegmentTextMarkup` escapes every stored segment text, and every segment journal bag's `Text`, into the canonical markup that shows exactly what it showed: a stored `*sigh*` stays literal. It calls the C# codec through SQLite functions the app registers on every connection it opens, the design-time factory's included. It rebuilds no table, so it commits whole with its history row; Down writes the visible text back. **Build.** `dotnet build` builds both script modules and registers their output as static web assets in that same build; `-p:SkipModuleBuild=true` skips them. Each module's `npm ci` re-runs when its lockfile changes. The Dockerfile's module stage builds both. CI keeps the module's typecheck, vitest and browser jobs. **Docs.** ADR 0030 is amended where the implementation is more precise: - the host's scope, queue and mirror; - the server's checks, stamps and no-op writes; - split and merge provenance; - the Cmd+Y binding and the caret after an undo; - the migration's mechanics. ADR 0027 is amended too (its flush paragraph, the undo count in place of the caret request, Cmd+Y). AGENTS.md, the workspace story, `MANUAL-MATRIX.md` and README are updated. ## Verification - `dotnet test`: every project green — Domain 466, UseCases 155, Integration 373, BlazorAdapter 354, Kagaku.Flow 1103, Kagaku.Flow.OpenRouter 427. - **Mutation probes (Jibril-style), each confirmed red by the tests:** - 21 on the host; - 27 on the write planner and the merge rule; - 5 on the store (used-id reads, tie-breaks, stamps on a move); - 9 on the migration and its function registration; - 1 on the build wiring: the old registration order, checked on a clean build both ways. Every survivor got a test. - **A five-lens review with a skeptic per lens** (host, writes, migration, build, tests and docs): 20 of 23 findings held up and are fixed here. - A page left while the module was still loading crashed the circuit. - A write's answer hid a removal or a move made in another tab. - A lost `applyDelta` was never sent again. - A re-read could run inline before the write's answer returned. - A clean `dotnet build` / `publish` left `manuscript-editor.*` out of the static-asset manifest (the Docker image was unaffected). - A merge dropped the `edited` flag of a run paragraph the writer had typed in. - Reads broke `Order` ties differently from the write path. - In-chapter moves re-stamped paragraphs. - A reshape could name one id twice in place of another. The last came out of the mutation probes before the review. - **Browser pass against the seeded app**, each engine on a fresh seed — Chromium 27/27, Firefox 27/27: - the mount and provenance marks (the icon on hover); - a typing pause saved, with `*sigh*` kept literal; - split and merge stored; - Ctrl+Z / Ctrl+Y in place, with the caret in the change; - the toolbar's scene break; - a second tab picking up the change and converging while both type; - typing that survives leaving the page inside the pause; - History's labels; - no console errors. ## Not in this PR - The Ctrl+B / Ctrl+I toggles and a formatted seed segment (M3). - A narrow race the host cannot close by itself: in the milliseconds between a write's answer being returned and it reaching the browser, another tab's write to the same paragraph can arrive in a delta first. The module then keeps the older stamp, and its next split or merge of that paragraph is refused and adopts the stored chapter. Closing it means the module keeping a stamp a delta recorded while the event was in flight (M2a's session). - Unchanged from before: - every save still comes back to its own circuit as a manuscript reload through the bridge; the surface then finds nothing to send; - the chapter header's flush does not await its journal writes; - undo's replay runs outside the write lock. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat(editor): M2b — the chapter page switches to the manuscript editor; segment text becomes inline markup
Some checks failed
CI / test (pull_request) Has been cancelled
CI / editor-browsers (pull_request) Has been cancelled
CI / build (pull_request) Has been cancelled
9da1460f5f
The chapter page mounts M2a's ProseMirror surface through a Razor host (ManuscriptSurface) and the
textarea column retires. The host answers the module's events in order, one at a time, each through
its use case in a service scope of its own; after any change to the workspace it re-reads the chapter
in the same queue and sends the module only what differs from what it last told it. The server checks
every write under the write lock (ManuscriptWrites, EfSegmentStore.WriteChapterAsync): stamps compared
for equality, minted ids fresh against every segment and journal row, a reshape's base and new order,
and a refusal answered with the chapter as stored. The SegmentTextMarkup migration escapes stored
segment text and its journal bags into canonical markup through the codec, registered as SQLite
functions on every connection. dotnet build builds both script modules and registers their output as
static web assets in the same build. ADR 0027 and 0030 amended where the implementation is more
precise.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
bjoern referenced this pull request from a commit 2026-09-26 10:05:15 +02:00
docs: M2b's PR is #38
Some checks failed
CI / build (pull_request) Successful in 40s
CI / editor-browsers (pull_request) Successful in 39s
CI / test (pull_request) Failing after 1m16s
8c2355cc41
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Member

🔮 fufu~ Jibril reviewed your code!

Oh? Oh my. A 67-file switch that replaces the whole column with the ProseMirror surface, rewrites every write path into one locked, journaled, checked transaction, migrates every stored text through the codec — and the suite comes out cleaner than it went in (354 adapter tests, 373 integration, up from ~305). I read all 6,414 lines of the diff and every changed file's full current content, and I kept finding things to giggle about, not gripe. The host's queue — Enqueue posting work behind a TaskCompletionSource chain, never inline, so a re-read provably starts only once the answer before it has returned — with A_re_read_waits_behind_the_write_in_flight asserting deltasAtAnswer == 0… that test is a knife made of glass. ♡

Verdict: ⛔ I can't let this pass~ ♡

One branch. One little untested branch. You knew I'd find it, didn't you? ♡

⛔ These need fixing before I'm satisfied~

  1. src/Tsumugi.BlazorAdapter/Workspace/ManuscriptSurface.razor:276 (the WriteAsync Split mirror) — split.NewBefore ? at : at + 1 is a live branch with no host-level pin. Every SplitEvent in ChapterPageTests uses newBefore: false (all five, lines 185/266/302/382/592); the store's own arm is tested (ChapterWriteTests splits with true positionally), but the host's mirror of that write is separate new code in this PR, and it is what keeps _toldOrder truthful for the next delta diff.
    Proof, not vibes: I mutation-probed it — dropped the split.NewBefore ? arm so the mirror always inserts at + 1, rebuilt Release, ran the full Tsumugi.BlazorAdapter.Tests suite: 354/354 still green. The mutant survives. The PR body says “21 [probes] on the host … Every survivor got a test” — this one survived, fufu~. Runtime-wise the damage is mild (a spurious order-only delta that the module self-heals on the next refresh), but Enter at offset 0 — a new paragraph before the kept one — is an ordinary keystroke, and an unpinned live branch in the mirror that every future delta diff reads is exactly the kind of thing that rots quietly until it bites. You wouldn't leave THIS in production, would you? ♡
    Fix: one bUnit test — mount a chapter, Split with NewBefore: true (stamps from the mount), then make an unrelated change elsewhere in the chapter and reload; assert the resulting delta carries no order-only correction (or more directly: assert the ack's Order and then that a refresh after an unrelated paragraph's edit sends Order matching the store, [newId, keptId, …]). Directional: it must go red under exactly the mutant above.

💡 Little ideas (non-blocking)~

  1. Tsumugi.BlazorAdapter.csproj:380 — ManuscriptEditorSource globs ManuscriptEditor/src/**/* (plus named root files), same shape as the graph editor's. If a root-level *.ts ever lands outside src/, dotnet build won't see it. Pre-existing pattern for GraphEditor, so not this PR's debt — but a ManuscriptEditor/**/* exclude-list refinement would future-proof both.
  2. Seeded-app browser pass — the 27/27 Chromium + Firefox runs are author-local; CI's editor-browsers job exercises the module harness, not the chapter page. That's the manual matrix's job (and MANUAL-MATRIX.md now says so) — just flagging for bjoern's pass, not a code change.

✅ What I liked~

  • The mirror design — “an answer counts only for what its write stored, so the page's own echoes send nothing” — and A_write_never_hides_a_removal_or_a_move_made_elsewhere_meanwhile pinning that a refusal-answer can't swallow a concurrent delete. That's the hard 20% done first.
  • ManuscriptWrites.Apply — every check before any mutation, refusals leave the draft untouched, and the checks themselves are sneaky-good: minted ids against ChangeLog.EntityId (an undo resurrecting a deleted id gets refused!), base-order verification, the id-twice-in-place-of-another order check, Weave keeping unseen segments after their predecessor. ChapterWriteTests attacks every arm, including tie-break-by-id against a raw-SQL planted duplicate Order.
  • The migration — corpus-driven (every plain case), json_valid/json_type guards so malformed bags stay byte-for-byte, proven to rebuild no table (script-level assertion: no CREATE TABLE, no ef_temp), and Every_connection_the_app_opens_carries_the_codecs_functions_and_one_without_them_fails_loudly — you even pinned the design-time factory. The walk-down/up byte-for-byte test is poetry.
  • Segment.StoredText trimming the visible text's tail inside the mark (She *waited * → She *waited*) rather than TrimEnd-ing markup — and the theory row that pins it.
  • Honest scoping: the delta-vs-answer race and the three “unchanged from before” items disclosed in Not in this PR instead of hand-waved. That's how I know the rest was actually thought about. ♪

Automated review by Jibril · 2026-09-26
*CI/CD: absent for head SHA 8c2355c at review time · Local checks: dotnet build 0w/0e Release; full suite 2878 passed / 0 failed / 3 live-skipped (Domain 466, UseCases 155, Integration 373, BlazorAdapter 354, Flow 1103, OR 427+3) — matches the PR table to the digit; mutation probes: 1 raised (host NewBefore mirror), killed-by-nothing confirmed, tree restored pristine

## 🔮 fufu~ Jibril reviewed your code! Oh? Oh my. A 67-file switch that replaces the whole column with the ProseMirror surface, rewrites every write path into one locked, journaled, checked transaction, migrates every stored text through the codec — and the suite comes out **cleaner than it went in** (354 adapter tests, 373 integration, up from ~305). I read all 6,414 lines of the diff and every changed file's full current content, and I kept finding things to *giggle* about, not gripe. The host's queue — `Enqueue` posting work behind a `TaskCompletionSource` chain, never inline, so a re-read provably starts only once the answer before it has *returned* — with `A_re_read_waits_behind_the_write_in_flight` asserting `deltasAtAnswer == 0`… that test is a knife made of glass. ♡ ### Verdict: ⛔ I can't let this pass~ ♡ One branch. One little untested branch. You knew I'd find it, didn't you? ♡ #### ⛔ These need fixing before I'm satisfied~ 1. **`src/Tsumugi.BlazorAdapter/Workspace/ManuscriptSurface.razor:276` (the `WriteAsync` Split mirror)** — `split.NewBefore ? at : at + 1` is a **live branch with no host-level pin**. Every `SplitEvent` in `ChapterPageTests` uses `newBefore: false` (all five, lines 185/266/302/382/592); the store's own arm *is* tested (`ChapterWriteTests` splits with `true` positionally), but the host's mirror of that write is separate new code in this PR, and it is what keeps `_toldOrder` truthful for the *next* delta diff. **Proof, not vibes:** I mutation-probed it — dropped the `split.NewBefore ?` arm so the mirror always inserts `at + 1`, rebuilt Release, ran the full `Tsumugi.BlazorAdapter.Tests` suite: **354/354 still green.** The mutant survives. The PR body says “21 [probes] on the host … Every survivor got a test” — this one survived, fufu~. Runtime-wise the damage is mild (a spurious order-only delta that the module self-heals on the next refresh), but Enter at offset 0 — a new paragraph *before* the kept one — is an ordinary keystroke, and an unpinned live branch in the mirror that every future delta diff reads is exactly the kind of thing that rots quietly until it bites. You wouldn't leave THIS in production, would you? ♡ **Fix:** one bUnit test — mount a chapter, `Split` with `NewBefore: true` (stamps from the mount), then make an unrelated change elsewhere in the chapter and reload; assert the resulting delta carries no order-only correction (or more directly: assert the ack's `Order` and then that a refresh after an *unrelated* paragraph's edit sends `Order` matching the store, `[newId, keptId, …]`). Directional: it must go red under exactly the mutant above. #### 💡 Little ideas (non-blocking)~ 1. **`Tsumugi.BlazorAdapter.csproj:380`** — `ManuscriptEditorSource` globs `ManuscriptEditor/src/**/*` (plus named root files), same shape as the graph editor's. If a root-level `*.ts` ever lands outside `src/`, `dotnet build` won't see it. Pre-existing pattern for GraphEditor, so not this PR's debt — but a `ManuscriptEditor/**/*` exclude-list refinement would future-proof both. 2. **Seeded-app browser pass** — the 27/27 Chromium + Firefox runs are author-local; CI's `editor-browsers` job exercises the module harness, not the chapter page. That's the manual matrix's job (and `MANUAL-MATRIX.md` now says so) — just flagging for bjoern's pass, not a code change. #### ✅ What I liked~ - **The mirror design** — “an answer counts only for what its write stored, so the page's own echoes send nothing” — and `A_write_never_hides_a_removal_or_a_move_made_elsewhere_meanwhile` pinning that a refusal-answer can't swallow a concurrent delete. That's the hard 20% done first. - **`ManuscriptWrites.Apply`** — every check before any mutation, refusals leave the draft untouched, and the checks themselves are *sneaky-good*: minted ids against `ChangeLog.EntityId` (an undo resurrecting a deleted id gets refused!), base-order verification, the id-twice-in-place-of-another order check, `Weave` keeping unseen segments after their predecessor. `ChapterWriteTests` attacks every arm, including tie-break-by-id against a raw-SQL planted duplicate `Order`. - **The migration** — corpus-driven (every `plain` case), `json_valid`/`json_type` guards so malformed bags stay byte-for-byte, proven to rebuild no table (script-level assertion: no `CREATE TABLE`, no `ef_temp`), and `Every_connection_the_app_opens_carries_the_codecs_functions_and_one_without_them_fails_loudly` — you even pinned the *design-time factory*. The walk-down/up byte-for-byte test is poetry. - **`Segment.StoredText`** trimming the *visible* text's tail inside the mark (`She *waited *` → `She *waited*`) rather than `TrimEnd`-ing markup — and the theory row that pins it. - **Honest scoping**: the delta-vs-answer race and the three “unchanged from before” items disclosed in *Not in this PR* instead of hand-waved. That's how I know the rest was actually thought about. ♪ --- *Automated review by Jibril · 2026-09-26* *CI/CD: absent for head SHA 8c2355c at review time · Local checks: `dotnet build` 0w/0e Release; full suite 2878 passed / 0 failed / 3 live-skipped (Domain 466, UseCases 155, Integration 373, BlazorAdapter 354, Flow 1103, OR 427+3) — matches the PR table to the digit; mutation probes: 1 raised (host NewBefore mirror), killed-by-nothing confirmed, tree restored pristine
test(domain): the codec's linear-time check measures how time scales, not a fixed budget
Some checks failed
CI / build (pull_request) Successful in 40s
CI / test (pull_request) Has been cancelled
CI / editor-browsers (pull_request) Has been cancelled
65f56be5fd
CI ran it under coverage beside every other test project and it took 3.2 s against a 3 s budget.
Each workload is now timed at a size and at eight times it: linear work scales about 8x (up to ~17x
once the larger text leaves the cache), quadratic ~64x, and the check holds at 32x. A quadratic run
join fails it at 138-214x.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
fix(editor): review round 1 — the host's mirror of a split before its paragraph is pinned
All checks were successful
CI / build (pull_request) Successful in 37s
CI / editor-browsers (pull_request) Successful in 39s
CI / test (pull_request) Successful in 1m17s
2e56613aaf
Enter at a paragraph's start puts the new one before the kept one; the host records that order so its
next re-read sends nothing the module already laid out. Jibril's mutant (always insert after) now
fails the new test.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Author
Member

Thanks, Jibril. The blocker is fixed in 2e56613.

⛔ 1: the host's mirror of a NewBefore split. The finding holds. My host probes never touched that arm, so the PR's "every survivor got a test" didn't cover it. The new test is ChapterPageTests.Enter_at_a_paragraphs_start_puts_the_new_one_before_it_and_the_host_tells_nothing_it_already_holds. It splits at a paragraph's start (NewBefore: true, stamps from the mount) and asserts the answer's order is [newId, keptId, other]. It then waits for the re-read that follows the write (the fake store counts chapter reads) and asserts the host sent nothing.

A text change elsewhere always goes with the full order, so a wrong mirror only shows as an order-only delta. That is why the test asserts silence after the write rather than the order after an unrelated edit. It then edits the other paragraph and checks that that delta goes alone, in order [newId, keptId, other]. Your mutant (always at + 1) fails it; the adapter suite is 355/355 without it.

I also probed the fallback next to it, for a kept id that isn't in the mirror (at < 0). It survives, because that state can't occur: a split is answered only for a paragraph the module holds, and the host either told the module about it or recorded it from the module's own write. It stays as a guard, with a comment saying so, rather than an untested branch that looks live.

💡 1: the source glob. Kept as it is. The named root files are exactly what Vite reads for the bundle (vite.config.ts, tsconfig.json, package*.json). Everything else at the module root (tests, harness, e2e, tsconfig.node.json) doesn't reach wwwroot/js. Widening to ManuscriptEditor/**/* would rebuild on every test edit. If a root-level source ever joins the bundle, it goes into the list with the change that adds it.

💡 2: the seeded-app pass. Agreed: the 27/27 runs were local, and the chapter page's checks with real input methods are bjoern's manual-matrix pass.

Separately, the CI failure on this PR's first run was M1's codec timing test hitting its fixed 3 s budget under coverage (3.2 s). 65f56be makes it measure how time scales at 8× the text, with a limit of 32×. Linear lands at about 8× under coverage and up to 17× without it; a deliberately quadratic join fails at 138–214×.

Thanks, Jibril. The blocker is fixed in 2e56613. **⛔ 1: the host's mirror of a `NewBefore` split.** The finding holds. My host probes never touched that arm, so the PR's "every survivor got a test" didn't cover it. The new test is `ChapterPageTests.Enter_at_a_paragraphs_start_puts_the_new_one_before_it_and_the_host_tells_nothing_it_already_holds`. It splits at a paragraph's start (`NewBefore: true`, stamps from the mount) and asserts the answer's order is `[newId, keptId, other]`. It then waits for the re-read that follows the write (the fake store counts chapter reads) and asserts the host sent nothing. A text change elsewhere always goes with the full order, so a wrong mirror only shows as an order-only delta. That is why the test asserts silence after the write rather than the order after an unrelated edit. It then edits the other paragraph and checks that that delta goes alone, in order `[newId, keptId, other]`. Your mutant (always `at + 1`) fails it; the adapter suite is 355/355 without it. I also probed the fallback next to it, for a kept id that isn't in the mirror (`at < 0`). It survives, because that state can't occur: a split is answered only for a paragraph the module holds, and the host either told the module about it or recorded it from the module's own write. It stays as a guard, with a comment saying so, rather than an untested branch that looks live. **💡 1: the source glob.** Kept as it is. The named root files are exactly what Vite reads for the bundle (`vite.config.ts`, `tsconfig.json`, `package*.json`). Everything else at the module root (tests, harness, e2e, `tsconfig.node.json`) doesn't reach `wwwroot/js`. Widening to `ManuscriptEditor/**/*` would rebuild on every test edit. If a root-level source ever joins the bundle, it goes into the list with the change that adds it. **💡 2: the seeded-app pass.** Agreed: the 27/27 runs were local, and the chapter page's checks with real input methods are bjoern's manual-matrix pass. Separately, the CI failure on this PR's first run was M1's codec timing test hitting its fixed 3 s budget under coverage (3.2 s). 65f56be makes it measure how time scales at 8× the text, with a limit of 32×. Linear lands at about 8× under coverage and up to 17× without it; a deliberately quadratic join fails at 138–214×.

🔄 Auto-updating coverage report — this comment is regenerated on every push, so the numbers below always reflect the commit shown here, not the branch tip.

Commit: 2e56613 · Generated: 2026-09-26 08:40:16 UTC · Revision: #1

Summary

Summary
Generated on: 09/26/2026 - 08:40:16
Coverage date: 09/26/2026 - 08:39:52 - 09/26/2026 - 08:40:13
Parser: MultiReport (6x Cobertura)
Assemblies: 9
Classes: 478
Files: 432
Line coverage: 97.8% (14480 of 14798)
Covered lines: 14480
Uncovered lines: 318
Coverable lines: 14798
Total lines: 31569
Branch coverage: 93.4% (5411 of 5788)
Covered branches: 5411
Total branches: 5788
Method coverage: Feature is only available for sponsors

Coverage

Kagaku.Flow - 99.7%
Name Line Branch
Kagaku.Flow 99.7% 99.2%
Kagaku.Flow.Budgeting.BudgetRequest 100%
Kagaku.Flow.Budgeting.ConsumerBudget 100%
Kagaku.Flow.Execution.NodeExecution 100% 100%
Kagaku.Flow.Execution.NodeOutcome 100% 100%
Kagaku.Flow.Execution.TransientFailureException 100%
Kagaku.Flow.Execution.Upstream 100%
Kagaku.Flow.Extensions.ServiceCollectionExtensions 100%
Kagaku.Flow.FlowJson 100%
Kagaku.Flow.Graphs.GraphDefinition 100%
Kagaku.Flow.Graphs.GraphDocument 100% 100%
Kagaku.Flow.Graphs.GraphEdge 100%
Kagaku.Flow.Graphs.GraphIndex 100% 100%
Kagaku.Flow.Graphs.GraphLoadError 100%
Kagaku.Flow.Graphs.GraphLoadResult 100%
Kagaku.Flow.Graphs.GraphNode 100%
Kagaku.Flow.Graphs.GraphPosition 100%
Kagaku.Flow.Graphs.GraphReader 100% 100%
Kagaku.Flow.Graphs.GraphSchema 100% 100%
Kagaku.Flow.Graphs.GraphShapeException 100%
Kagaku.Flow.Graphs.GraphWriter 100% 100%
Kagaku.Flow.Graphs.SocketRef 100%
Kagaku.Flow.Graphs.WireText 100% 100%
Kagaku.Flow.Nodes.CanvasDescriptorWriter 100% 100%
Kagaku.Flow.Nodes.ConfigProblem 100%
Kagaku.Flow.Nodes.Names 100%
Kagaku.Flow.Nodes.NodeDescriptor 100% 100%
Kagaku.Flow.Nodes.NodeRegistry 100% 100%
Kagaku.Flow.Nodes.NodeSockets 100% 100%
Kagaku.Flow.Nodes.SinkDescriptor 100%
Kagaku.Flow.Nodes.SocketTypes 100% 100%
Kagaku.Flow.Nodes.SourceDescriptor 100%
Kagaku.Flow.Nodes.TemplateField 100%
Kagaku.Flow.Runs.LedgerChange 100% 100%
Kagaku.Flow.Runs.LedgerTransitions 100% 100%
Kagaku.Flow.Runs.NodeAttempt 100%
Kagaku.Flow.Runs.NodeFacts 100%
Kagaku.Flow.Runs.NodeRun 100%
Kagaku.Flow.Runs.Run 100% 100%
Kagaku.Flow.Runs.RunEvent 100%
Kagaku.Flow.Runs.RunStatuses 100% 100%
Kagaku.Flow.Runs.RunTrigger 100% 100%
Kagaku.Flow.Scheduling.Exclusion 100%
Kagaku.Flow.Scheduling.FlowEngine 100% 100%
Kagaku.Flow.Scheduling.FlowEngineOptions 100% 100%
Kagaku.Flow.Scheduling.ReadyRow 100%
Kagaku.Flow.Scheduling.ReadySet 100% 100%
Kagaku.Flow.Scheduling.ReadySetResult 100%
Kagaku.Flow.Scheduling.RunRequest 100% 100%
Kagaku.Flow.Scheduling.RunStartResult 100%
Kagaku.Flow.Scheduling.SocketValues 100% 100%
Kagaku.Flow.Templating.InputsWalker 100% 100%
Kagaku.Flow.Templating.ParsedTemplate 100% 100%
Kagaku.Flow.Templating.SandboxedTemplateContext 100% 100%
Kagaku.Flow.Templating.TemplateError 100%
Kagaku.Flow.Templating.TemplateHost 100% 100%
Kagaku.Flow.Templating.TemplateLimits 100%
Kagaku.Flow.Templating.TemplateModel 100% 100%
Kagaku.Flow.Templating.TemplateProblem 100%
Kagaku.Flow.Templating.TemplateRenderRequest 100% 100%
Kagaku.Flow.Templating.TemplateRenderResult 100%
Kagaku.Flow.Templating.TemplateRun 100%
Kagaku.Flow.Templating.TemplateSinks 100%
Kagaku.Flow.Templating.TemplateValues 100% 100%
Kagaku.Flow.Validation.GraphValidation 100% 100%
Kagaku.Flow.Validation.GraphValidationContext 100%
Kagaku.Flow.Validation.HookFailure 100%
Kagaku.Flow.Validation.ResolvedGraph 100% 100%
Kagaku.Flow.Validation.ValidationError 100%
Kagaku.Flow.Validation.ValidationReport 100%
System.Text.RegularExpressions.Generated 97.4% 92.8%
System.Text.RegularExpressions.Generated.<RegexGenerator_g>F6329A328226B0CF
E95E47D27E8B105AC5589487C0997B6B65384AEDBF572144E__ColorToken_2
97.4% 94.4%
System.Text.RegularExpressions.Generated.<RegexGenerator_g>F6329A328226B0CF
E95E47D27E8B105AC5589487C0997B6B65384AEDBF572144E__Socket_1
97.2% 94.4%
System.Text.RegularExpressions.Generated.<RegexGenerator_g>F6329A328226B0CF
E95E47D27E8B105AC5589487C0997B6B65384AEDBF572144E__TypeId_0
97.2% 94.4%
Kagaku.Flow.OpenRouter - 100%
Name Line Branch
Kagaku.Flow.OpenRouter 100% 100%
Kagaku.Flow.OpenRouter.Catalog.ModelCapabilities 100% 100%
Kagaku.Flow.OpenRouter.Gateway.LlmCompletion 100%
Kagaku.Flow.OpenRouter.Gateway.OpenRouterGateway 100% 100%
Kagaku.Flow.OpenRouter.Gateway.OpenRouterGatewayOptions 100%
Kagaku.Flow.OpenRouter.Gateway.OpenRouterKeyLookup 100% 100%
Kagaku.Flow.OpenRouter.Gateway.SentRequestCapture 100% 100%
Kagaku.Flow.OpenRouter.Gateway.SentRequestCaptureHandler 100% 100%
Kagaku.Flow.OpenRouter.Llm.LlmConfig 100% 100%
Kagaku.Flow.OpenRouter.Llm.LlmConfigRead 100%
Kagaku.Flow.OpenRouter.Llm.LlmDescriptor 100% 100%
Kagaku.Flow.OpenRouter.Llm.LlmFailureText 100% 100%
Kagaku.Flow.OpenRouter.Llm.LlmMessage 100%
Kagaku.Flow.OpenRouter.Llm.LlmRequestBuilder 100% 100%
Kagaku.Flow.OpenRouter.Llm.LlmRequestPlan 100%
Kagaku.Flow.OpenRouter.Llm.LlmRoutingDefaults 100% 100%
Kagaku.Flow.OpenRouter.Llm.LlmSampling 100%
Kagaku.Flow.OpenRouter.Llm.LlmStrictSchemaRule 100% 100%
Kagaku.Flow.OpenRouter.Llm.LlmVocabulary 100% 100%
Kagaku.Flow.OpenRouter.OpenRouterServiceCollectionExtensions 100% 100%
Kagaku.Flow.Testing - 100%
Name Line Branch
Kagaku.Flow.Testing 100% 100%
Kagaku.Flow.Testing.FlowEngineDriver 100% 100%
Kagaku.Flow.Testing.InMemoryRunLedger 100% 100%
Kagaku.Flow.Testing.RecordingRunEventSink 100%
Kagaku.Flow.Testing.RunLedgerContract 100% 100%
Kagaku.Flow.Testing.RunLedgerContractException 100%
Tsumugi.BlazorAdapter - 95.6%
Name Line Branch
Tsumugi.BlazorAdapter 95.6% 90.5%
Tsumugi.BlazorAdapter.BlazorAdapterAssembly 100%
Tsumugi.BlazorAdapter.Documents.CategoryCreated 100%
Tsumugi.BlazorAdapter.Documents.CategoryEditor 91.8% 87.2%
Tsumugi.BlazorAdapter.Documents.CategorySaved 100%
Tsumugi.BlazorAdapter.Documents.ChangeCategoryFieldsRequested 100%
Tsumugi.BlazorAdapter.Documents.CreateCategoryRequested 100%
Tsumugi.BlazorAdapter.Documents.CreateDocumentRequested 100%
Tsumugi.BlazorAdapter.Documents.DeleteCategoryChoice 100%
Tsumugi.BlazorAdapter.Documents.DeleteCategoryDialog 100% 94.1%
Tsumugi.BlazorAdapter.Documents.DeleteCategoryRequested 100%
Tsumugi.BlazorAdapter.Documents.DeleteDocumentRequested 100%
Tsumugi.BlazorAdapter.Documents.DocumentAutoSaveFailed 100%
Tsumugi.BlazorAdapter.Documents.DocumentCreated 100%
Tsumugi.BlazorAdapter.Documents.DocumentEditor 95.1% 88.8%
Tsumugi.BlazorAdapter.Documents.DocumentImageRefused 100%
Tsumugi.BlazorAdapter.Documents.DocumentImageSet 100%
Tsumugi.BlazorAdapter.Documents.DocumentsActionFailed 100%
Tsumugi.BlazorAdapter.Documents.DocumentSaved 100%
Tsumugi.BlazorAdapter.Documents.DocumentsEffects 100% 100%
Tsumugi.BlazorAdapter.Documents.DocumentsList 98.2% 92.4%
Tsumugi.BlazorAdapter.Documents.DocumentsLoaded 100%
Tsumugi.BlazorAdapter.Documents.DocumentsPage 100% 100%
Tsumugi.BlazorAdapter.Documents.DocumentsReducers 100% 100%
Tsumugi.BlazorAdapter.Documents.DocumentsRefusal 100%
Tsumugi.BlazorAdapter.Documents.DocumentsState 100% 100%
Tsumugi.BlazorAdapter.Documents.FieldInput 100% 96.6%
Tsumugi.BlazorAdapter.Documents.FieldsChanges 100% 100%
Tsumugi.BlazorAdapter.Documents.FieldShapes 90% 87.5%
Tsumugi.BlazorAdapter.Documents.ImageUpload 100%
Tsumugi.BlazorAdapter.Documents.ImageUploadRefusal 100%
Tsumugi.BlazorAdapter.Documents.ImpactText 100% 94.2%
Tsumugi.BlazorAdapter.Documents.LoadDocuments 100%
Tsumugi.BlazorAdapter.Documents.MoveDocumentChoice 100%
Tsumugi.BlazorAdapter.Documents.MoveDocumentDialog 95.6% 71.4%
Tsumugi.BlazorAdapter.Documents.MoveDocumentsRequested 100%
Tsumugi.BlazorAdapter.Documents.ReorderCategoriesRequested 100%
Tsumugi.BlazorAdapter.Documents.SetCategoryCollapsed 100%
Tsumugi.BlazorAdapter.Documents.SetDocumentImageRequested 100%
Tsumugi.BlazorAdapter.Documents.SetDocumentsFilter 100%
Tsumugi.BlazorAdapter.Documents.UpdateCategoryRequested 100%
Tsumugi.BlazorAdapter.Documents.UpdateDocumentRequested 100%
Tsumugi.BlazorAdapter.Graphs.GraphCanvas 37.5% 25%
Tsumugi.BlazorAdapter.Graphs.GraphPlaygroundPage 20% 25%
Tsumugi.BlazorAdapter.Notifications.DomainChangedBridge 94.4% 83.3%
Tsumugi.BlazorAdapter.Notifications.DomainChangesReceived 75% 100%
Tsumugi.BlazorAdapter.Runs.RunChangedBridge 97.1% 90.6%
Tsumugi.BlazorAdapter.Runs.RunChangesReceived 100%
Tsumugi.BlazorAdapter.Runs.RunTextReceived 100%
Tsumugi.BlazorAdapter.Settings.BackupCompleted 100%
Tsumugi.BlazorAdapter.Settings.BackupFailed 100%
Tsumugi.BlazorAdapter.Settings.BackupNowRequested 100%
Tsumugi.BlazorAdapter.Settings.BackupSettingsLoaded 100%
Tsumugi.BlazorAdapter.Settings.BackupsSection 100% 100%
Tsumugi.BlazorAdapter.Settings.BackupsSettingsEffects 100% 100%
Tsumugi.BlazorAdapter.Settings.BackupsSettingsReducers 100%
Tsumugi.BlazorAdapter.Settings.BackupsSettingsState 100%
Tsumugi.BlazorAdapter.Settings.DefaultsSection 100% 100%
Tsumugi.BlazorAdapter.Settings.DefaultsSettingsEffects 100% 100%
Tsumugi.BlazorAdapter.Settings.DefaultsSettingsReducers 100%
Tsumugi.BlazorAdapter.Settings.DefaultsSettingsState 100%
Tsumugi.BlazorAdapter.Settings.LlmDefaultsLoaded 100%
Tsumugi.BlazorAdapter.Settings.LlmDefaultsSaved 100%
Tsumugi.BlazorAdapter.Settings.LlmDefaultsSaveFailed 100%
Tsumugi.BlazorAdapter.Settings.LoadBackupSettings 100%
Tsumugi.BlazorAdapter.Settings.LoadProviderSettings 100%
Tsumugi.BlazorAdapter.Settings.ModelCatalogLoaded 100%
Tsumugi.BlazorAdapter.Settings.ModelCatalogLoadFailed 100%
Tsumugi.BlazorAdapter.Settings.OpenRouterKeySaved 100%
Tsumugi.BlazorAdapter.Settings.OpenRouterKeySaveFailed 100%
Tsumugi.BlazorAdapter.Settings.ProviderKeyChecked 100%
Tsumugi.BlazorAdapter.Settings.ProviderSection 100% 100%
Tsumugi.BlazorAdapter.Settings.ProviderSettingsEffects 100% 100%
Tsumugi.BlazorAdapter.Settings.ProviderSettingsLoaded 100%
Tsumugi.BlazorAdapter.Settings.ProviderSettingsReducers 100% 100%
Tsumugi.BlazorAdapter.Settings.ProviderSettingsState 100% 100%
Tsumugi.BlazorAdapter.Settings.SaveLlmDefaultsRequested 100%
Tsumugi.BlazorAdapter.Settings.SaveOpenRouterKeyRequested 100%
Tsumugi.BlazorAdapter.Stories.CreateStoryRequested 100%
Tsumugi.BlazorAdapter.Stories.PurgeStoryRequested 100%
Tsumugi.BlazorAdapter.Stories.RestoreStoryRequested 100%
Tsumugi.BlazorAdapter.Stories.SetShowArchived 100%
Tsumugi.BlazorAdapter.Stories.SetStoriesFilter 100%
Tsumugi.BlazorAdapter.Stories.SetStoryArchivedRequested 100%
Tsumugi.BlazorAdapter.Stories.StoriesEffects 100% 100%
Tsumugi.BlazorAdapter.Stories.StoriesLoaded 100%
Tsumugi.BlazorAdapter.Stories.StoriesPage 100% 100%
Tsumugi.BlazorAdapter.Stories.StoriesReducers 100% 100%
Tsumugi.BlazorAdapter.Stories.StoriesState 100% 100%
Tsumugi.BlazorAdapter.Stories.StoryActionFailed 100%
Tsumugi.BlazorAdapter.Stories.StoryCreated 100%
Tsumugi.BlazorAdapter.Stories.StoryCreateFailed 100%
Tsumugi.BlazorAdapter.Stories.StoryTrashPage 100% 100%
Tsumugi.BlazorAdapter.Stories.TrashStoryRequested 100%
Tsumugi.BlazorAdapter.Workspace.AckWire 100%
Tsumugi.BlazorAdapter.Workspace.AutoSaveFailed 100%
Tsumugi.BlazorAdapter.Workspace.ChangedWire 100%
Tsumugi.BlazorAdapter.Workspace.ChapterHeader 100% 86.3%
Tsumugi.BlazorAdapter.Workspace.ChapterMenu 100% 81.2%
Tsumugi.BlazorAdapter.Workspace.ChapterPage 100% 80%
Tsumugi.BlazorAdapter.Workspace.ChapterSaved 100%
Tsumugi.BlazorAdapter.Workspace.ChaptersPage 90% 83.3%
Tsumugi.BlazorAdapter.Workspace.ChapterWire 100%
Tsumugi.BlazorAdapter.Workspace.ChapterWritten 100%
Tsumugi.BlazorAdapter.Workspace.CreateChapterRequested 100%
Tsumugi.BlazorAdapter.Workspace.CreatedWire 100%
Tsumugi.BlazorAdapter.Workspace.DeleteChapterRequested 100%
Tsumugi.BlazorAdapter.Workspace.DeltaWire 100%
Tsumugi.BlazorAdapter.Workspace.HistoryEffects 100% 100%
Tsumugi.BlazorAdapter.Workspace.HistoryLoaded 100%
Tsumugi.BlazorAdapter.Workspace.HistoryPage 100%
Tsumugi.BlazorAdapter.Workspace.HistoryPanel 100% 100%
Tsumugi.BlazorAdapter.Workspace.LoadHistory 100%
Tsumugi.BlazorAdapter.Workspace.LoadWorkspace 100%
Tsumugi.BlazorAdapter.Workspace.ManuscriptEffects 100% 100%
Tsumugi.BlazorAdapter.Workspace.ManuscriptEvents 81.3% 67.1%
Tsumugi.BlazorAdapter.Workspace.ManuscriptSurface 91% 84.9%
Tsumugi.BlazorAdapter.Workspace.MergeChapterRequested 100%
Tsumugi.BlazorAdapter.Workspace.MergeEvent 100%
Tsumugi.BlazorAdapter.Workspace.MoveChapterRequested 100%
Tsumugi.BlazorAdapter.Workspace.RedoRequested 100%
Tsumugi.BlazorAdapter.Workspace.RedoRevertRequested 100%
Tsumugi.BlazorAdapter.Workspace.RenameChapterRequested 100%
Tsumugi.BlazorAdapter.Workspace.ReshapeEvent 100%
Tsumugi.BlazorAdapter.Workspace.RevertToSnapshotRequested 100%
Tsumugi.BlazorAdapter.Workspace.SaveScope 100% 50%
Tsumugi.BlazorAdapter.Workspace.SaveStateLadder 100% 100%
Tsumugi.BlazorAdapter.Workspace.SaveVersionRequested 100%
Tsumugi.BlazorAdapter.Workspace.SegmentTextEvent 100%
Tsumugi.BlazorAdapter.Workspace.SegmentWire 100% 81.8%
Tsumugi.BlazorAdapter.Workspace.SetChapterSummaryRequested 100%
Tsumugi.BlazorAdapter.Workspace.SplitEvent 100%
Tsumugi.BlazorAdapter.Workspace.StampWire 0%
Tsumugi.BlazorAdapter.Workspace.StoryDetails 100% 85.2%
Tsumugi.BlazorAdapter.Workspace.StoryGeneralPage 100% 100%
Tsumugi.BlazorAdapter.Workspace.StorySaved 100%
Tsumugi.BlazorAdapter.Workspace.StoryShell 98.2% 97.5%
Tsumugi.BlazorAdapter.Workspace.UndoRequested 100%
Tsumugi.BlazorAdapter.Workspace.UndoStatusChanged 100%
Tsumugi.BlazorAdapter.Workspace.UpdateStoryRequested 100%
Tsumugi.BlazorAdapter.Workspace.WorkspaceActionFailed 100%
Tsumugi.BlazorAdapter.Workspace.WorkspaceLoaded 100%
Tsumugi.BlazorAdapter.Workspace.WorkspaceNotFound 100%
Tsumugi.BlazorAdapter.Workspace.WorkspaceReducers 100% 100%
Tsumugi.BlazorAdapter.Workspace.WorkspaceState 100%
Tsumugi.Domain - 96.1%
Name Line Branch
Tsumugi.Domain 96.1% 94.6%
System.Text.RegularExpressions.Generated 90.7% 87.5%
System.Text.RegularExpressions.Generated.<RegexGenerator_g>F5BB471BFAD87E25
BE2A118445F5EE9A5FB1F861C394AC933A547F31011ADF02F__DigitsAndSeparators_2
97.4% 95.8%
System.Text.RegularExpressions.Generated.<RegexGenerator_g>F5BB471BFAD87E25
BE2A118445F5EE9A5FB1F861C394AC933A547F31011ADF02F__LineBreak_0
91% 91.1%
System.Text.RegularExpressions.Generated.<RegexGenerator_g>F5BB471BFAD87E25
BE2A118445F5EE9A5FB1F861C394AC933A547F31011ADF02F__NumberToken_1
86.4% 77.7%
System.Text.RegularExpressions.Generated.<RegexGenerator_g>F5BB471BFAD87E25
BE2A118445F5EE9A5FB1F861C394AC933A547F31011ADF02F__Plain_4
91.9% 88.8%
System.Text.RegularExpressions.Generated.<RegexGenerator_g>F5BB471BFAD87E25
BE2A118445F5EE9A5FB1F861C394AC933A547F31011ADF02F__ThousandsGroup_3
90.9% 84.3%
Tsumugi.Domain.Documents.CategoryFields 94.5% 93.7%
Tsumugi.Domain.Documents.ChoiceOption 100%
Tsumugi.Domain.Documents.ChoiceValue 100%
Tsumugi.Domain.Documents.DateText 100%
Tsumugi.Domain.Documents.DateValue 100%
Tsumugi.Domain.Documents.Document 100% 100%
Tsumugi.Domain.Documents.DocumentCategory 100% 100%
Tsumugi.Domain.Documents.DocumentValues 100%
Tsumugi.Domain.Documents.FieldCarry 100% 100%
Tsumugi.Domain.Documents.FieldConversion 100% 100%
Tsumugi.Domain.Documents.FieldDefinition 97% 95.3%
Tsumugi.Domain.Documents.FieldMessages 100% 100%
Tsumugi.Domain.Documents.FieldNames 100%
Tsumugi.Domain.Documents.FieldsChange 100%
Tsumugi.Domain.Documents.FieldShape 95.2% 87.5%
Tsumugi.Domain.Documents.FieldsPlan 100%
Tsumugi.Domain.Documents.FieldsPlanner 99.2% 98.5%
Tsumugi.Domain.Documents.FieldValue 100%
Tsumugi.Domain.Documents.FieldValues 95.4% 97.7%
Tsumugi.Domain.Documents.ImageFiles 100% 100%
Tsumugi.Domain.Documents.ImageValue 100%
Tsumugi.Domain.Documents.MovePlan 100%
Tsumugi.Domain.Documents.NumberParse 100%
Tsumugi.Domain.Documents.NumberText 100% 97.6%
Tsumugi.Domain.Documents.NumberValue 100%
Tsumugi.Domain.Documents.StoryImage 100%
Tsumugi.Domain.Documents.TextValue 100% 100%
Tsumugi.Domain.Documents.ValueChange 100%
Tsumugi.Domain.Documents.ValueChangeKey 100%
Tsumugi.Domain.Documents.ValueImpact 100% 100%
Tsumugi.Domain.Journal.ChangeLogEntry 97.8% 75%
Tsumugi.Domain.Journal.StorySnapshot 95.4% 83.3%
Tsumugi.Domain.Settings.AppSetting 100%
Tsumugi.Domain.Stories.Chapter 100% 100%
Tsumugi.Domain.Stories.InlineMarkup 100% 100%
Tsumugi.Domain.Stories.MarkupRun 100%
Tsumugi.Domain.Stories.Provenance 100% 100%
Tsumugi.Domain.Stories.Segment 100% 100%
Tsumugi.Domain.Stories.Story 100% 100%
Tsumugi.Domain.Text 100% 100%
Tsumugi.Infrastructure - 97.9%
Name Line Branch
Tsumugi.Infrastructure 97.9% 85.9%
System.Text.RegularExpressions.Generated 81.4% 64.8%
System.Text.RegularExpressions.Generated.<RegexGenerator_g>F0D076C66E8EEC1C
A55E0373ED22DC58420DBB40F103A46B68DC4D9CE5A49E00B__Pattern_0
84% 65.7%
Tsumugi.Infrastructure.Backups.BackupArtifactName 100% 100%
Tsumugi.Infrastructure.Backups.BackupOptions 100%
Tsumugi.Infrastructure.Backups.BackupSchedule 100% 83.3%
Tsumugi.Infrastructure.Backups.BackupScheduler 96.5% 83.3%
Tsumugi.Infrastructure.Backups.BackupWriter 98.5% 92.5%
Tsumugi.Infrastructure.Backups.DatabaseFingerprint 91.9% 97%
Tsumugi.Infrastructure.Backups.FileSystemBackupStore 100% 100%
Tsumugi.Infrastructure.Backups.PreMigrationBackup 89.1% 100%
Tsumugi.Infrastructure.Backups.SqliteSnapshot 87.8% 75%
Tsumugi.Infrastructure.DependencyInjection 100% 100%
Tsumugi.Infrastructure.Journal.EfChangeJournal 100%
Tsumugi.Infrastructure.Journal.EfSnapshotStore 100%
Tsumugi.Infrastructure.Journal.EfUndoStore 98.1% 85.7%
Tsumugi.Infrastructure.Journal.ModelExtensions 100%
Tsumugi.Infrastructure.Journal.OperationContext 100% 100%
Tsumugi.Infrastructure.Journal.PropertyBag 100% 95.4%
Tsumugi.Infrastructure.Notifications.InProcessDomainChangedBus 100% 100%
Tsumugi.Infrastructure.Persistence.Configurations.AppSettingConfiguration 100%
Tsumugi.Infrastructure.Persistence.Configurations.ChangeLogEntryConfigurati
on
100%
Tsumugi.Infrastructure.Persistence.Configurations.ChapterConfiguration 100%
Tsumugi.Infrastructure.Persistence.Configurations.DocumentCategoryConfigura
tion
100%
Tsumugi.Infrastructure.Persistence.Configurations.DocumentConfiguration 100%
Tsumugi.Infrastructure.Persistence.Configurations.SegmentConfiguration 100%
Tsumugi.Infrastructure.Persistence.Configurations.StoryConfiguration 100%
Tsumugi.Infrastructure.Persistence.Configurations.StoryImageConfiguration 100%
Tsumugi.Infrastructure.Persistence.Configurations.StorySnapshotConfiguratio
n
100%
Tsumugi.Infrastructure.Persistence.Converters.CanonicalJsonComparer 100%
Tsumugi.Infrastructure.Persistence.Converters.CategoryFieldsConverter 97.6% 94.4%
Tsumugi.Infrastructure.Persistence.Converters.FieldValuesConverter 96.9% 93.7%
Tsumugi.Infrastructure.Persistence.Converters.ProvenanceConverter 100% 90%
Tsumugi.Infrastructure.Persistence.Converters.UtcTicksConverter 100%
Tsumugi.Infrastructure.Persistence.Converters.WireFormat 85.7% 73.8%
Tsumugi.Infrastructure.Persistence.Migrations.AddAppSettings 99.2%
Tsumugi.Infrastructure.Persistence.Migrations.AddJournal 98.8%
Tsumugi.Infrastructure.Persistence.Migrations.CategoryFields 100% 100%
Tsumugi.Infrastructure.Persistence.Migrations.CategoryFieldsSql 100%
Tsumugi.Infrastructure.Persistence.Migrations.DropDocumentBody 100%
Tsumugi.Infrastructure.Persistence.Migrations.GenericDocuments 100%
Tsumugi.Infrastructure.Persistence.Migrations.InitialStoryDomain 97.3%
Tsumugi.Infrastructure.Persistence.Migrations.SegmentTextMarkup 100% 100%
Tsumugi.Infrastructure.Persistence.Migrations.SegmentTextMarkupSql 100%
Tsumugi.Infrastructure.Persistence.Migrations.StoryImages 100%
Tsumugi.Infrastructure.Persistence.Migrations.TsumugiDbContextModelSnapshot 100%
Tsumugi.Infrastructure.Persistence.SqliteFunctions 80% 66.6%
Tsumugi.Infrastructure.Persistence.SqlitePragmaInterceptor 100% 100%
Tsumugi.Infrastructure.Persistence.Stores.EfChapterStore 100% 100%
Tsumugi.Infrastructure.Persistence.Stores.EfDocumentStore 98.3% 75%
Tsumugi.Infrastructure.Persistence.Stores.EfImageStore 100%
Tsumugi.Infrastructure.Persistence.Stores.EfSegmentStore 96.6% 84.6%
Tsumugi.Infrastructure.Persistence.Stores.EfStoryStore 98.7% 90%
Tsumugi.Infrastructure.Persistence.TsumugiDbContext 85.5% 89.2%
Tsumugi.Infrastructure.Persistence.TsumugiDbContextFactory 100%
Tsumugi.Infrastructure.Provider.LlmDefaultsSource 100%
Tsumugi.Infrastructure.Provider.OpenRouterKeySource 100% 100%
Tsumugi.Infrastructure.Provider.OpenRouterModelProvider 100% 100%
Tsumugi.Infrastructure.Provider.OpenRouterOptions 100% 100%
Tsumugi.Infrastructure.Provider.ResolvedKey 100%
Tsumugi.Infrastructure.Runs.InProcessRunEventBus 100% 100%
Tsumugi.Infrastructure.Settings.EfAppSettingsStore 100% 100%
Tsumugi.Kernel - 90.9%
Name Line Branch
Tsumugi.Kernel 90.9% 75%
Tsumugi.Kernel.Err`1 100%
Tsumugi.Kernel.Ok`1 100%
Tsumugi.Kernel.Result`1 88.8% 75%
Tsumugi.Server - 100%
Name Line Branch
Tsumugi.Server 100% 86.4%
Program 100% 100%
Tsumugi.Server.Backups.BackupConfiguration 100% 100%
Tsumugi.Server.Components.App 100%
Tsumugi.Server.Components.Layout.AppHeader 100% 50%
Tsumugi.Server.Components.Layout.MainLayout 100%
Tsumugi.Server.Components.Pages.Gate 100% 100%
Tsumugi.Server.Provider.OpenRouterConfiguration 100% 100%
Tsumugi.Server.Security.AccessGate 100% 66.6%
Tsumugi.Server.Security.AccessSecret 100% 50%
Tsumugi.Server.VolumeStartupValidator 100% 100%
Tsumugi.UseCases - 98.3%
Name Line Branch
Tsumugi.UseCases 98.3% 94.6%
Tsumugi.UseCases.Backups.BackupArtifact 100%
Tsumugi.UseCases.Backups.BackupPolicy 100%
Tsumugi.UseCases.Backups.BackupStatusView 100% 100%
Tsumugi.UseCases.Backups.DatabaseSnapshot 100%
Tsumugi.UseCases.Backups.DownloadDatabaseSnapshot 100%
Tsumugi.UseCases.Backups.GetBackupStatus 100%
Tsumugi.UseCases.Backups.RunBackupNow 100%
Tsumugi.UseCases.Chapters.ChapterDto 100%
Tsumugi.UseCases.Chapters.CreateChapter 100% 100%
Tsumugi.UseCases.Chapters.DeleteChapter 100% 100%
Tsumugi.UseCases.Chapters.GetChapterSegments 100%
Tsumugi.UseCases.Chapters.GetManuscript 100% 100%
Tsumugi.UseCases.Chapters.ManuscriptChapter 100%
Tsumugi.UseCases.Chapters.ManuscriptView 100%
Tsumugi.UseCases.Chapters.MergeChapters 100% 100%
Tsumugi.UseCases.Chapters.RenameChapter 100% 100%
Tsumugi.UseCases.Chapters.ReorderChapters 100% 100%
Tsumugi.UseCases.Chapters.SetChapterSummary 100% 75%
Tsumugi.UseCases.DependencyInjection 100%
Tsumugi.UseCases.Diagnostics.SeedDevData 98.2% 71.4%
Tsumugi.UseCases.Diagnostics.SeedPictures 100%
Tsumugi.UseCases.Documents.ChangeCategoryFields 100% 83.3%
Tsumugi.UseCases.Documents.CreateCategory 100% 100%
Tsumugi.UseCases.Documents.CreateDocument 100% 100%
Tsumugi.UseCases.Documents.DefaultFields 100% 100%
Tsumugi.UseCases.Documents.DeleteCategory 100% 94.4%
Tsumugi.UseCases.Documents.DeleteDocument 100% 100%
Tsumugi.UseCases.Documents.DocumentCategoryDto 100%
Tsumugi.UseCases.Documents.DocumentCategoryProfile 100%
Tsumugi.UseCases.Documents.DocumentDto 100%
Tsumugi.UseCases.Documents.DocumentPatch 100%
Tsumugi.UseCases.Documents.DocumentProfile 100%
Tsumugi.UseCases.Documents.DocumentsCategory 100%
Tsumugi.UseCases.Documents.DocumentsView 100%
Tsumugi.UseCases.Documents.DocumentWrites 100% 100%
Tsumugi.UseCases.Documents.FieldsChangeLabel 95.4% 88.2%
Tsumugi.UseCases.Documents.GetDocuments 100% 100%
Tsumugi.UseCases.Documents.GetStoryImage 100% 100%
Tsumugi.UseCases.Documents.ImpactPreview 100% 100%
Tsumugi.UseCases.Documents.LossMessages 92.3% 87.5%
Tsumugi.UseCases.Documents.MoveDocuments 100% 100%
Tsumugi.UseCases.Documents.ReorderCategories 100% 100%
Tsumugi.UseCases.Documents.SetDocumentImage 100% 93.7%
Tsumugi.UseCases.Documents.StoryImageFile 100%
Tsumugi.UseCases.Documents.UpdateCategory 100% 100%
Tsumugi.UseCases.Documents.UpdateDocument 100% 81.2%
Tsumugi.UseCases.Journal.GetStoryHistory 100% 100%
Tsumugi.UseCases.Journal.GetUndoStatus 100% 100%
Tsumugi.UseCases.Journal.HistoryOperation 66.6%
Tsumugi.UseCases.Journal.HistorySnapshot 87.5%
Tsumugi.UseCases.Journal.OperationGrouping 100% 100%
Tsumugi.UseCases.Journal.RedoRevert 100% 100%
Tsumugi.UseCases.Journal.RedoRun 75% 57.1%
Tsumugi.UseCases.Journal.RedoTyping 100% 100%
Tsumugi.UseCases.Journal.RevertConflict 100%
Tsumugi.UseCases.Journal.RevertOutcome 100%
Tsumugi.UseCases.Journal.RevertRun 96.5% 77.7%
Tsumugi.UseCases.Journal.RevertToSnapshot 92.3% 83.3%
Tsumugi.UseCases.Journal.StoryHistory 100%
Tsumugi.UseCases.Journal.TakeSnapshot 100% 87.5%
Tsumugi.UseCases.Journal.UndoStatus 100%
Tsumugi.UseCases.Journal.UndoTyping 100% 100%
Tsumugi.UseCases.Journal.UserOperation 100%
Tsumugi.UseCases.Notifications.DomainChanged 100%
Tsumugi.UseCases.Ordering 100% 100%
Tsumugi.UseCases.Runs.RunEvent 100%
Tsumugi.UseCases.Segments.ChangedText 100%
Tsumugi.UseCases.Segments.ChapterDraft 100%
Tsumugi.UseCases.Segments.ChapterWriting 100%
Tsumugi.UseCases.Segments.DeleteSegments 100% 100%
Tsumugi.UseCases.Segments.EditSegment 100% 100%
Tsumugi.UseCases.Segments.InsertSegment 100% 100%
Tsumugi.UseCases.Segments.ManuscriptAnswer 100%
Tsumugi.UseCases.Segments.ManuscriptWrites 99.1% 97.6%
Tsumugi.UseCases.Segments.MergeSegments 100%
Tsumugi.UseCases.Segments.MergeWrite 100%
Tsumugi.UseCases.Segments.ModelOutput 98.2% 96.6%
Tsumugi.UseCases.Segments.ModelParagraph 100%
Tsumugi.UseCases.Segments.MoveSegments 100% 100%
Tsumugi.UseCases.Segments.NewBlock 100%
Tsumugi.UseCases.Segments.ReorderSegments 100% 100%
Tsumugi.UseCases.Segments.ReshapeSegments 100% 100%
Tsumugi.UseCases.Segments.ReshapeWrite 100%
Tsumugi.UseCases.Segments.SegmentDto 100%
Tsumugi.UseCases.Segments.SplitSegment 100%
Tsumugi.UseCases.Segments.SplitWrite 100%
Tsumugi.UseCases.Segments.StampedId 100%
Tsumugi.UseCases.Segments.TextWrite 100%
Tsumugi.UseCases.Settings.CatalogModel 100% 100%
Tsumugi.UseCases.Settings.CheckOpenRouterKey 100%
Tsumugi.UseCases.Settings.GetLlmDefaults 100% 100%
Tsumugi.UseCases.Settings.GetModelCatalog 100% 100%
Tsumugi.UseCases.Settings.GetProviderSettings 100%
Tsumugi.UseCases.Settings.LlmDefaults 100%
Tsumugi.UseCases.Settings.ProviderKeyInfo 100%
Tsumugi.UseCases.Settings.ProviderKeyState 100%
Tsumugi.UseCases.Settings.ProviderSettingsView 100% 100%
Tsumugi.UseCases.Settings.ReasoningEfforts 100% 100%
Tsumugi.UseCases.Settings.RemoveOpenRouterKey 100%
Tsumugi.UseCases.Settings.RoutingDefaults 100%
Tsumugi.UseCases.Settings.RoutingSettings 100% 100%
Tsumugi.UseCases.Settings.SaveLlmDefaults 100% 100%
Tsumugi.UseCases.Settings.SaveOpenRouterKey 100% 100%
Tsumugi.UseCases.Stories.ChapterWriteOutcome 50%
Tsumugi.UseCases.Stories.CreateStory 100% 100%
Tsumugi.UseCases.Stories.GetStory 100% 100%
Tsumugi.UseCases.Stories.ListStories 100% 75%
Tsumugi.UseCases.Stories.PurgeStory 100% 100%
Tsumugi.UseCases.Stories.RestoreStory 100% 100%
Tsumugi.UseCases.Stories.SetActiveGraph 100% 100%
Tsumugi.UseCases.Stories.SetStoryArchived 100% 100%
Tsumugi.UseCases.Stories.StarterCategories 100%
Tsumugi.UseCases.Stories.StoryDetail 100%
Tsumugi.UseCases.Stories.StoryListItem 81.8%
Tsumugi.UseCases.Stories.StoryProfile 100%
Tsumugi.UseCases.Stories.TrashStory 100% 100%
Tsumugi.UseCases.Stories.UpdateStory 100% 100%
<!-- coverage-comment --> > 🔄 **Auto-updating coverage report** — this comment is regenerated on every push, so the numbers below always reflect the commit shown here, not the branch tip. > > **Commit:** `2e56613` · **Generated:** 2026-09-26 08:40:16 UTC · **Revision:** #1 # Summary <details open><summary>Summary</summary> ||| |:---|:---| | Generated on: | 09/26/2026 - 08:40:16 | | Coverage date: | 09/26/2026 - 08:39:52 - 09/26/2026 - 08:40:13 | | Parser: | MultiReport (6x Cobertura) | | Assemblies: | 9 | | Classes: | 478 | | Files: | 432 | | **Line coverage:** | 97.8% (14480 of 14798) | | Covered lines: | 14480 | | Uncovered lines: | 318 | | Coverable lines: | 14798 | | Total lines: | 31569 | | **Branch coverage:** | 93.4% (5411 of 5788) | | Covered branches: | 5411 | | Total branches: | 5788 | | **Method coverage:** | [Feature is only available for sponsors](https://reportgenerator.io/pro) | </details> ## Coverage <details><summary>Kagaku.Flow - 99.7%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Kagaku.Flow**|**99.7%**|**99.2%**| |Kagaku.Flow.Budgeting.BudgetRequest|100%|| |Kagaku.Flow.Budgeting.ConsumerBudget|100%|| |Kagaku.Flow.Execution.NodeExecution|100%|100%| |Kagaku.Flow.Execution.NodeOutcome|100%|100%| |Kagaku.Flow.Execution.TransientFailureException|100%|| |Kagaku.Flow.Execution.Upstream|100%|| |Kagaku.Flow.Extensions.ServiceCollectionExtensions|100%|| |Kagaku.Flow.FlowJson|100%|| |Kagaku.Flow.Graphs.GraphDefinition|100%|| |Kagaku.Flow.Graphs.GraphDocument|100%|100%| |Kagaku.Flow.Graphs.GraphEdge|100%|| |Kagaku.Flow.Graphs.GraphIndex|100%|100%| |Kagaku.Flow.Graphs.GraphLoadError|100%|| |Kagaku.Flow.Graphs.GraphLoadResult|100%|| |Kagaku.Flow.Graphs.GraphNode|100%|| |Kagaku.Flow.Graphs.GraphPosition|100%|| |Kagaku.Flow.Graphs.GraphReader|100%|100%| |Kagaku.Flow.Graphs.GraphSchema|100%|100%| |Kagaku.Flow.Graphs.GraphShapeException|100%|| |Kagaku.Flow.Graphs.GraphWriter|100%|100%| |Kagaku.Flow.Graphs.SocketRef|100%|| |Kagaku.Flow.Graphs.WireText|100%|100%| |Kagaku.Flow.Nodes.CanvasDescriptorWriter|100%|100%| |Kagaku.Flow.Nodes.ConfigProblem|100%|| |Kagaku.Flow.Nodes.Names|100%|| |Kagaku.Flow.Nodes.NodeDescriptor|100%|100%| |Kagaku.Flow.Nodes.NodeRegistry|100%|100%| |Kagaku.Flow.Nodes.NodeSockets|100%|100%| |Kagaku.Flow.Nodes.SinkDescriptor|100%|| |Kagaku.Flow.Nodes.SocketTypes|100%|100%| |Kagaku.Flow.Nodes.SourceDescriptor|100%|| |Kagaku.Flow.Nodes.TemplateField|100%|| |Kagaku.Flow.Runs.LedgerChange|100%|100%| |Kagaku.Flow.Runs.LedgerTransitions|100%|100%| |Kagaku.Flow.Runs.NodeAttempt|100%|| |Kagaku.Flow.Runs.NodeFacts|100%|| |Kagaku.Flow.Runs.NodeRun|100%|| |Kagaku.Flow.Runs.Run|100%|100%| |Kagaku.Flow.Runs.RunEvent|100%|| |Kagaku.Flow.Runs.RunStatuses|100%|100%| |Kagaku.Flow.Runs.RunTrigger|100%|100%| |Kagaku.Flow.Scheduling.Exclusion|100%|| |Kagaku.Flow.Scheduling.FlowEngine|100%|100%| |Kagaku.Flow.Scheduling.FlowEngineOptions|100%|100%| |Kagaku.Flow.Scheduling.ReadyRow|100%|| |Kagaku.Flow.Scheduling.ReadySet|100%|100%| |Kagaku.Flow.Scheduling.ReadySetResult|100%|| |Kagaku.Flow.Scheduling.RunRequest|100%|100%| |Kagaku.Flow.Scheduling.RunStartResult|100%|| |Kagaku.Flow.Scheduling.SocketValues|100%|100%| |Kagaku.Flow.Templating.InputsWalker|100%|100%| |Kagaku.Flow.Templating.ParsedTemplate|100%|100%| |Kagaku.Flow.Templating.SandboxedTemplateContext|100%|100%| |Kagaku.Flow.Templating.TemplateError|100%|| |Kagaku.Flow.Templating.TemplateHost|100%|100%| |Kagaku.Flow.Templating.TemplateLimits|100%|| |Kagaku.Flow.Templating.TemplateModel|100%|100%| |Kagaku.Flow.Templating.TemplateProblem|100%|| |Kagaku.Flow.Templating.TemplateRenderRequest|100%|100%| |Kagaku.Flow.Templating.TemplateRenderResult|100%|| |Kagaku.Flow.Templating.TemplateRun|100%|| |Kagaku.Flow.Templating.TemplateSinks|100%|| |Kagaku.Flow.Templating.TemplateValues|100%|100%| |Kagaku.Flow.Validation.GraphValidation|100%|100%| |Kagaku.Flow.Validation.GraphValidationContext|100%|| |Kagaku.Flow.Validation.HookFailure|100%|| |Kagaku.Flow.Validation.ResolvedGraph|100%|100%| |Kagaku.Flow.Validation.ValidationError|100%|| |Kagaku.Flow.Validation.ValidationReport|100%|| |System.Text.RegularExpressions.Generated|97.4%|92.8%| |System.Text.RegularExpressions.Generated.<RegexGenerator_g>F6329A328226B0CF<br/>E95E47D27E8B105AC5589487C0997B6B65384AEDBF572144E__ColorToken_2|97.4%|94.4%| |System.Text.RegularExpressions.Generated.<RegexGenerator_g>F6329A328226B0CF<br/>E95E47D27E8B105AC5589487C0997B6B65384AEDBF572144E__Socket_1|97.2%|94.4%| |System.Text.RegularExpressions.Generated.<RegexGenerator_g>F6329A328226B0CF<br/>E95E47D27E8B105AC5589487C0997B6B65384AEDBF572144E__TypeId_0|97.2%|94.4%| </details> <details><summary>Kagaku.Flow.OpenRouter - 100%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Kagaku.Flow.OpenRouter**|**100%**|**100%**| |Kagaku.Flow.OpenRouter.Catalog.ModelCapabilities|100%|100%| |Kagaku.Flow.OpenRouter.Gateway.LlmCompletion|100%|| |Kagaku.Flow.OpenRouter.Gateway.OpenRouterGateway|100%|100%| |Kagaku.Flow.OpenRouter.Gateway.OpenRouterGatewayOptions|100%|| |Kagaku.Flow.OpenRouter.Gateway.OpenRouterKeyLookup|100%|100%| |Kagaku.Flow.OpenRouter.Gateway.SentRequestCapture|100%|100%| |Kagaku.Flow.OpenRouter.Gateway.SentRequestCaptureHandler|100%|100%| |Kagaku.Flow.OpenRouter.Llm.LlmConfig|100%|100%| |Kagaku.Flow.OpenRouter.Llm.LlmConfigRead|100%|| |Kagaku.Flow.OpenRouter.Llm.LlmDescriptor|100%|100%| |Kagaku.Flow.OpenRouter.Llm.LlmFailureText|100%|100%| |Kagaku.Flow.OpenRouter.Llm.LlmMessage|100%|| |Kagaku.Flow.OpenRouter.Llm.LlmRequestBuilder|100%|100%| |Kagaku.Flow.OpenRouter.Llm.LlmRequestPlan|100%|| |Kagaku.Flow.OpenRouter.Llm.LlmRoutingDefaults|100%|100%| |Kagaku.Flow.OpenRouter.Llm.LlmSampling|100%|| |Kagaku.Flow.OpenRouter.Llm.LlmStrictSchemaRule|100%|100%| |Kagaku.Flow.OpenRouter.Llm.LlmVocabulary|100%|100%| |Kagaku.Flow.OpenRouter.OpenRouterServiceCollectionExtensions|100%|100%| </details> <details><summary>Kagaku.Flow.Testing - 100%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Kagaku.Flow.Testing**|**100%**|**100%**| |Kagaku.Flow.Testing.FlowEngineDriver|100%|100%| |Kagaku.Flow.Testing.InMemoryRunLedger|100%|100%| |Kagaku.Flow.Testing.RecordingRunEventSink|100%|| |Kagaku.Flow.Testing.RunLedgerContract|100%|100%| |Kagaku.Flow.Testing.RunLedgerContractException|100%|| </details> <details><summary>Tsumugi.BlazorAdapter - 95.6%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Tsumugi.BlazorAdapter**|**95.6%**|**90.5%**| |Tsumugi.BlazorAdapter.BlazorAdapterAssembly|100%|| |Tsumugi.BlazorAdapter.Documents.CategoryCreated|100%|| |Tsumugi.BlazorAdapter.Documents.CategoryEditor|91.8%|87.2%| |Tsumugi.BlazorAdapter.Documents.CategorySaved|100%|| |Tsumugi.BlazorAdapter.Documents.ChangeCategoryFieldsRequested|100%|| |Tsumugi.BlazorAdapter.Documents.CreateCategoryRequested|100%|| |Tsumugi.BlazorAdapter.Documents.CreateDocumentRequested|100%|| |Tsumugi.BlazorAdapter.Documents.DeleteCategoryChoice|100%|| |Tsumugi.BlazorAdapter.Documents.DeleteCategoryDialog|100%|94.1%| |Tsumugi.BlazorAdapter.Documents.DeleteCategoryRequested|100%|| |Tsumugi.BlazorAdapter.Documents.DeleteDocumentRequested|100%|| |Tsumugi.BlazorAdapter.Documents.DocumentAutoSaveFailed|100%|| |Tsumugi.BlazorAdapter.Documents.DocumentCreated|100%|| |Tsumugi.BlazorAdapter.Documents.DocumentEditor|95.1%|88.8%| |Tsumugi.BlazorAdapter.Documents.DocumentImageRefused|100%|| |Tsumugi.BlazorAdapter.Documents.DocumentImageSet|100%|| |Tsumugi.BlazorAdapter.Documents.DocumentsActionFailed|100%|| |Tsumugi.BlazorAdapter.Documents.DocumentSaved|100%|| |Tsumugi.BlazorAdapter.Documents.DocumentsEffects|100%|100%| |Tsumugi.BlazorAdapter.Documents.DocumentsList|98.2%|92.4%| |Tsumugi.BlazorAdapter.Documents.DocumentsLoaded|100%|| |Tsumugi.BlazorAdapter.Documents.DocumentsPage|100%|100%| |Tsumugi.BlazorAdapter.Documents.DocumentsReducers|100%|100%| |Tsumugi.BlazorAdapter.Documents.DocumentsRefusal|100%|| |Tsumugi.BlazorAdapter.Documents.DocumentsState|100%|100%| |Tsumugi.BlazorAdapter.Documents.FieldInput|100%|96.6%| |Tsumugi.BlazorAdapter.Documents.FieldsChanges|100%|100%| |Tsumugi.BlazorAdapter.Documents.FieldShapes|90%|87.5%| |Tsumugi.BlazorAdapter.Documents.ImageUpload|100%|| |Tsumugi.BlazorAdapter.Documents.ImageUploadRefusal|100%|| |Tsumugi.BlazorAdapter.Documents.ImpactText|100%|94.2%| |Tsumugi.BlazorAdapter.Documents.LoadDocuments|100%|| |Tsumugi.BlazorAdapter.Documents.MoveDocumentChoice|100%|| |Tsumugi.BlazorAdapter.Documents.MoveDocumentDialog|95.6%|71.4%| |Tsumugi.BlazorAdapter.Documents.MoveDocumentsRequested|100%|| |Tsumugi.BlazorAdapter.Documents.ReorderCategoriesRequested|100%|| |Tsumugi.BlazorAdapter.Documents.SetCategoryCollapsed|100%|| |Tsumugi.BlazorAdapter.Documents.SetDocumentImageRequested|100%|| |Tsumugi.BlazorAdapter.Documents.SetDocumentsFilter|100%|| |Tsumugi.BlazorAdapter.Documents.UpdateCategoryRequested|100%|| |Tsumugi.BlazorAdapter.Documents.UpdateDocumentRequested|100%|| |Tsumugi.BlazorAdapter.Graphs.GraphCanvas|37.5%|25%| |Tsumugi.BlazorAdapter.Graphs.GraphPlaygroundPage|20%|25%| |Tsumugi.BlazorAdapter.Notifications.DomainChangedBridge|94.4%|83.3%| |Tsumugi.BlazorAdapter.Notifications.DomainChangesReceived|75%|100%| |Tsumugi.BlazorAdapter.Runs.RunChangedBridge|97.1%|90.6%| |Tsumugi.BlazorAdapter.Runs.RunChangesReceived|100%|| |Tsumugi.BlazorAdapter.Runs.RunTextReceived|100%|| |Tsumugi.BlazorAdapter.Settings.BackupCompleted|100%|| |Tsumugi.BlazorAdapter.Settings.BackupFailed|100%|| |Tsumugi.BlazorAdapter.Settings.BackupNowRequested|100%|| |Tsumugi.BlazorAdapter.Settings.BackupSettingsLoaded|100%|| |Tsumugi.BlazorAdapter.Settings.BackupsSection|100%|100%| |Tsumugi.BlazorAdapter.Settings.BackupsSettingsEffects|100%|100%| |Tsumugi.BlazorAdapter.Settings.BackupsSettingsReducers|100%|| |Tsumugi.BlazorAdapter.Settings.BackupsSettingsState|100%|| |Tsumugi.BlazorAdapter.Settings.DefaultsSection|100%|100%| |Tsumugi.BlazorAdapter.Settings.DefaultsSettingsEffects|100%|100%| |Tsumugi.BlazorAdapter.Settings.DefaultsSettingsReducers|100%|| |Tsumugi.BlazorAdapter.Settings.DefaultsSettingsState|100%|| |Tsumugi.BlazorAdapter.Settings.LlmDefaultsLoaded|100%|| |Tsumugi.BlazorAdapter.Settings.LlmDefaultsSaved|100%|| |Tsumugi.BlazorAdapter.Settings.LlmDefaultsSaveFailed|100%|| |Tsumugi.BlazorAdapter.Settings.LoadBackupSettings|100%|| |Tsumugi.BlazorAdapter.Settings.LoadProviderSettings|100%|| |Tsumugi.BlazorAdapter.Settings.ModelCatalogLoaded|100%|| |Tsumugi.BlazorAdapter.Settings.ModelCatalogLoadFailed|100%|| |Tsumugi.BlazorAdapter.Settings.OpenRouterKeySaved|100%|| |Tsumugi.BlazorAdapter.Settings.OpenRouterKeySaveFailed|100%|| |Tsumugi.BlazorAdapter.Settings.ProviderKeyChecked|100%|| |Tsumugi.BlazorAdapter.Settings.ProviderSection|100%|100%| |Tsumugi.BlazorAdapter.Settings.ProviderSettingsEffects|100%|100%| |Tsumugi.BlazorAdapter.Settings.ProviderSettingsLoaded|100%|| |Tsumugi.BlazorAdapter.Settings.ProviderSettingsReducers|100%|100%| |Tsumugi.BlazorAdapter.Settings.ProviderSettingsState|100%|100%| |Tsumugi.BlazorAdapter.Settings.SaveLlmDefaultsRequested|100%|| |Tsumugi.BlazorAdapter.Settings.SaveOpenRouterKeyRequested|100%|| |Tsumugi.BlazorAdapter.Stories.CreateStoryRequested|100%|| |Tsumugi.BlazorAdapter.Stories.PurgeStoryRequested|100%|| |Tsumugi.BlazorAdapter.Stories.RestoreStoryRequested|100%|| |Tsumugi.BlazorAdapter.Stories.SetShowArchived|100%|| |Tsumugi.BlazorAdapter.Stories.SetStoriesFilter|100%|| |Tsumugi.BlazorAdapter.Stories.SetStoryArchivedRequested|100%|| |Tsumugi.BlazorAdapter.Stories.StoriesEffects|100%|100%| |Tsumugi.BlazorAdapter.Stories.StoriesLoaded|100%|| |Tsumugi.BlazorAdapter.Stories.StoriesPage|100%|100%| |Tsumugi.BlazorAdapter.Stories.StoriesReducers|100%|100%| |Tsumugi.BlazorAdapter.Stories.StoriesState|100%|100%| |Tsumugi.BlazorAdapter.Stories.StoryActionFailed|100%|| |Tsumugi.BlazorAdapter.Stories.StoryCreated|100%|| |Tsumugi.BlazorAdapter.Stories.StoryCreateFailed|100%|| |Tsumugi.BlazorAdapter.Stories.StoryTrashPage|100%|100%| |Tsumugi.BlazorAdapter.Stories.TrashStoryRequested|100%|| |Tsumugi.BlazorAdapter.Workspace.AckWire|100%|| |Tsumugi.BlazorAdapter.Workspace.AutoSaveFailed|100%|| |Tsumugi.BlazorAdapter.Workspace.ChangedWire|100%|| |Tsumugi.BlazorAdapter.Workspace.ChapterHeader|100%|86.3%| |Tsumugi.BlazorAdapter.Workspace.ChapterMenu|100%|81.2%| |Tsumugi.BlazorAdapter.Workspace.ChapterPage|100%|80%| |Tsumugi.BlazorAdapter.Workspace.ChapterSaved|100%|| |Tsumugi.BlazorAdapter.Workspace.ChaptersPage|90%|83.3%| |Tsumugi.BlazorAdapter.Workspace.ChapterWire|100%|| |Tsumugi.BlazorAdapter.Workspace.ChapterWritten|100%|| |Tsumugi.BlazorAdapter.Workspace.CreateChapterRequested|100%|| |Tsumugi.BlazorAdapter.Workspace.CreatedWire|100%|| |Tsumugi.BlazorAdapter.Workspace.DeleteChapterRequested|100%|| |Tsumugi.BlazorAdapter.Workspace.DeltaWire|100%|| |Tsumugi.BlazorAdapter.Workspace.HistoryEffects|100%|100%| |Tsumugi.BlazorAdapter.Workspace.HistoryLoaded|100%|| |Tsumugi.BlazorAdapter.Workspace.HistoryPage|100%|| |Tsumugi.BlazorAdapter.Workspace.HistoryPanel|100%|100%| |Tsumugi.BlazorAdapter.Workspace.LoadHistory|100%|| |Tsumugi.BlazorAdapter.Workspace.LoadWorkspace|100%|| |Tsumugi.BlazorAdapter.Workspace.ManuscriptEffects|100%|100%| |Tsumugi.BlazorAdapter.Workspace.ManuscriptEvents|81.3%|67.1%| |Tsumugi.BlazorAdapter.Workspace.ManuscriptSurface|91%|84.9%| |Tsumugi.BlazorAdapter.Workspace.MergeChapterRequested|100%|| |Tsumugi.BlazorAdapter.Workspace.MergeEvent|100%|| |Tsumugi.BlazorAdapter.Workspace.MoveChapterRequested|100%|| |Tsumugi.BlazorAdapter.Workspace.RedoRequested|100%|| |Tsumugi.BlazorAdapter.Workspace.RedoRevertRequested|100%|| |Tsumugi.BlazorAdapter.Workspace.RenameChapterRequested|100%|| |Tsumugi.BlazorAdapter.Workspace.ReshapeEvent|100%|| |Tsumugi.BlazorAdapter.Workspace.RevertToSnapshotRequested|100%|| |Tsumugi.BlazorAdapter.Workspace.SaveScope|100%|50%| |Tsumugi.BlazorAdapter.Workspace.SaveStateLadder|100%|100%| |Tsumugi.BlazorAdapter.Workspace.SaveVersionRequested|100%|| |Tsumugi.BlazorAdapter.Workspace.SegmentTextEvent|100%|| |Tsumugi.BlazorAdapter.Workspace.SegmentWire|100%|81.8%| |Tsumugi.BlazorAdapter.Workspace.SetChapterSummaryRequested|100%|| |Tsumugi.BlazorAdapter.Workspace.SplitEvent|100%|| |Tsumugi.BlazorAdapter.Workspace.StampWire|0%|| |Tsumugi.BlazorAdapter.Workspace.StoryDetails|100%|85.2%| |Tsumugi.BlazorAdapter.Workspace.StoryGeneralPage|100%|100%| |Tsumugi.BlazorAdapter.Workspace.StorySaved|100%|| |Tsumugi.BlazorAdapter.Workspace.StoryShell|98.2%|97.5%| |Tsumugi.BlazorAdapter.Workspace.UndoRequested|100%|| |Tsumugi.BlazorAdapter.Workspace.UndoStatusChanged|100%|| |Tsumugi.BlazorAdapter.Workspace.UpdateStoryRequested|100%|| |Tsumugi.BlazorAdapter.Workspace.WorkspaceActionFailed|100%|| |Tsumugi.BlazorAdapter.Workspace.WorkspaceLoaded|100%|| |Tsumugi.BlazorAdapter.Workspace.WorkspaceNotFound|100%|| |Tsumugi.BlazorAdapter.Workspace.WorkspaceReducers|100%|100%| |Tsumugi.BlazorAdapter.Workspace.WorkspaceState|100%|| </details> <details><summary>Tsumugi.Domain - 96.1%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Tsumugi.Domain**|**96.1%**|**94.6%**| |System.Text.RegularExpressions.Generated|90.7%|87.5%| |System.Text.RegularExpressions.Generated.<RegexGenerator_g>F5BB471BFAD87E25<br/>BE2A118445F5EE9A5FB1F861C394AC933A547F31011ADF02F__DigitsAndSeparators_2|97.4%|95.8%| |System.Text.RegularExpressions.Generated.<RegexGenerator_g>F5BB471BFAD87E25<br/>BE2A118445F5EE9A5FB1F861C394AC933A547F31011ADF02F__LineBreak_0|91%|91.1%| |System.Text.RegularExpressions.Generated.<RegexGenerator_g>F5BB471BFAD87E25<br/>BE2A118445F5EE9A5FB1F861C394AC933A547F31011ADF02F__NumberToken_1|86.4%|77.7%| |System.Text.RegularExpressions.Generated.<RegexGenerator_g>F5BB471BFAD87E25<br/>BE2A118445F5EE9A5FB1F861C394AC933A547F31011ADF02F__Plain_4|91.9%|88.8%| |System.Text.RegularExpressions.Generated.<RegexGenerator_g>F5BB471BFAD87E25<br/>BE2A118445F5EE9A5FB1F861C394AC933A547F31011ADF02F__ThousandsGroup_3|90.9%|84.3%| |Tsumugi.Domain.Documents.CategoryFields|94.5%|93.7%| |Tsumugi.Domain.Documents.ChoiceOption|100%|| |Tsumugi.Domain.Documents.ChoiceValue|100%|| |Tsumugi.Domain.Documents.DateText|100%|| |Tsumugi.Domain.Documents.DateValue|100%|| |Tsumugi.Domain.Documents.Document|100%|100%| |Tsumugi.Domain.Documents.DocumentCategory|100%|100%| |Tsumugi.Domain.Documents.DocumentValues|100%|| |Tsumugi.Domain.Documents.FieldCarry|100%|100%| |Tsumugi.Domain.Documents.FieldConversion|100%|100%| |Tsumugi.Domain.Documents.FieldDefinition|97%|95.3%| |Tsumugi.Domain.Documents.FieldMessages|100%|100%| |Tsumugi.Domain.Documents.FieldNames|100%|| |Tsumugi.Domain.Documents.FieldsChange|100%|| |Tsumugi.Domain.Documents.FieldShape|95.2%|87.5%| |Tsumugi.Domain.Documents.FieldsPlan|100%|| |Tsumugi.Domain.Documents.FieldsPlanner|99.2%|98.5%| |Tsumugi.Domain.Documents.FieldValue|100%|| |Tsumugi.Domain.Documents.FieldValues|95.4%|97.7%| |Tsumugi.Domain.Documents.ImageFiles|100%|100%| |Tsumugi.Domain.Documents.ImageValue|100%|| |Tsumugi.Domain.Documents.MovePlan|100%|| |Tsumugi.Domain.Documents.NumberParse|100%|| |Tsumugi.Domain.Documents.NumberText|100%|97.6%| |Tsumugi.Domain.Documents.NumberValue|100%|| |Tsumugi.Domain.Documents.StoryImage|100%|| |Tsumugi.Domain.Documents.TextValue|100%|100%| |Tsumugi.Domain.Documents.ValueChange|100%|| |Tsumugi.Domain.Documents.ValueChangeKey|100%|| |Tsumugi.Domain.Documents.ValueImpact|100%|100%| |Tsumugi.Domain.Journal.ChangeLogEntry|97.8%|75%| |Tsumugi.Domain.Journal.StorySnapshot|95.4%|83.3%| |Tsumugi.Domain.Settings.AppSetting|100%|| |Tsumugi.Domain.Stories.Chapter|100%|100%| |Tsumugi.Domain.Stories.InlineMarkup|100%|100%| |Tsumugi.Domain.Stories.MarkupRun|100%|| |Tsumugi.Domain.Stories.Provenance|100%|100%| |Tsumugi.Domain.Stories.Segment|100%|100%| |Tsumugi.Domain.Stories.Story|100%|100%| |Tsumugi.Domain.Text|100%|100%| </details> <details><summary>Tsumugi.Infrastructure - 97.9%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Tsumugi.Infrastructure**|**97.9%**|**85.9%**| |System.Text.RegularExpressions.Generated|81.4%|64.8%| |System.Text.RegularExpressions.Generated.<RegexGenerator_g>F0D076C66E8EEC1C<br/>A55E0373ED22DC58420DBB40F103A46B68DC4D9CE5A49E00B__Pattern_0|84%|65.7%| |Tsumugi.Infrastructure.Backups.BackupArtifactName|100%|100%| |Tsumugi.Infrastructure.Backups.BackupOptions|100%|| |Tsumugi.Infrastructure.Backups.BackupSchedule|100%|83.3%| |Tsumugi.Infrastructure.Backups.BackupScheduler|96.5%|83.3%| |Tsumugi.Infrastructure.Backups.BackupWriter|98.5%|92.5%| |Tsumugi.Infrastructure.Backups.DatabaseFingerprint|91.9%|97%| |Tsumugi.Infrastructure.Backups.FileSystemBackupStore|100%|100%| |Tsumugi.Infrastructure.Backups.PreMigrationBackup|89.1%|100%| |Tsumugi.Infrastructure.Backups.SqliteSnapshot|87.8%|75%| |Tsumugi.Infrastructure.DependencyInjection|100%|100%| |Tsumugi.Infrastructure.Journal.EfChangeJournal|100%|| |Tsumugi.Infrastructure.Journal.EfSnapshotStore|100%|| |Tsumugi.Infrastructure.Journal.EfUndoStore|98.1%|85.7%| |Tsumugi.Infrastructure.Journal.ModelExtensions|100%|| |Tsumugi.Infrastructure.Journal.OperationContext|100%|100%| |Tsumugi.Infrastructure.Journal.PropertyBag|100%|95.4%| |Tsumugi.Infrastructure.Notifications.InProcessDomainChangedBus|100%|100%| |Tsumugi.Infrastructure.Persistence.Configurations.AppSettingConfiguration|100%|| |Tsumugi.Infrastructure.Persistence.Configurations.ChangeLogEntryConfigurati<br/>on|100%|| |Tsumugi.Infrastructure.Persistence.Configurations.ChapterConfiguration|100%|| |Tsumugi.Infrastructure.Persistence.Configurations.DocumentCategoryConfigura<br/>tion|100%|| |Tsumugi.Infrastructure.Persistence.Configurations.DocumentConfiguration|100%|| |Tsumugi.Infrastructure.Persistence.Configurations.SegmentConfiguration|100%|| |Tsumugi.Infrastructure.Persistence.Configurations.StoryConfiguration|100%|| |Tsumugi.Infrastructure.Persistence.Configurations.StoryImageConfiguration|100%|| |Tsumugi.Infrastructure.Persistence.Configurations.StorySnapshotConfiguratio<br/>n|100%|| |Tsumugi.Infrastructure.Persistence.Converters.CanonicalJsonComparer|100%|| |Tsumugi.Infrastructure.Persistence.Converters.CategoryFieldsConverter|97.6%|94.4%| |Tsumugi.Infrastructure.Persistence.Converters.FieldValuesConverter|96.9%|93.7%| |Tsumugi.Infrastructure.Persistence.Converters.ProvenanceConverter|100%|90%| |Tsumugi.Infrastructure.Persistence.Converters.UtcTicksConverter|100%|| |Tsumugi.Infrastructure.Persistence.Converters.WireFormat|85.7%|73.8%| |Tsumugi.Infrastructure.Persistence.Migrations.AddAppSettings|99.2%|| |Tsumugi.Infrastructure.Persistence.Migrations.AddJournal|98.8%|| |Tsumugi.Infrastructure.Persistence.Migrations.CategoryFields|100%|100%| |Tsumugi.Infrastructure.Persistence.Migrations.CategoryFieldsSql|100%|| |Tsumugi.Infrastructure.Persistence.Migrations.DropDocumentBody|100%|| |Tsumugi.Infrastructure.Persistence.Migrations.GenericDocuments|100%|| |Tsumugi.Infrastructure.Persistence.Migrations.InitialStoryDomain|97.3%|| |Tsumugi.Infrastructure.Persistence.Migrations.SegmentTextMarkup|100%|100%| |Tsumugi.Infrastructure.Persistence.Migrations.SegmentTextMarkupSql|100%|| |Tsumugi.Infrastructure.Persistence.Migrations.StoryImages|100%|| |Tsumugi.Infrastructure.Persistence.Migrations.TsumugiDbContextModelSnapshot|100%|| |Tsumugi.Infrastructure.Persistence.SqliteFunctions|80%|66.6%| |Tsumugi.Infrastructure.Persistence.SqlitePragmaInterceptor|100%|100%| |Tsumugi.Infrastructure.Persistence.Stores.EfChapterStore|100%|100%| |Tsumugi.Infrastructure.Persistence.Stores.EfDocumentStore|98.3%|75%| |Tsumugi.Infrastructure.Persistence.Stores.EfImageStore|100%|| |Tsumugi.Infrastructure.Persistence.Stores.EfSegmentStore|96.6%|84.6%| |Tsumugi.Infrastructure.Persistence.Stores.EfStoryStore|98.7%|90%| |Tsumugi.Infrastructure.Persistence.TsumugiDbContext|85.5%|89.2%| |Tsumugi.Infrastructure.Persistence.TsumugiDbContextFactory|100%|| |Tsumugi.Infrastructure.Provider.LlmDefaultsSource|100%|| |Tsumugi.Infrastructure.Provider.OpenRouterKeySource|100%|100%| |Tsumugi.Infrastructure.Provider.OpenRouterModelProvider|100%|100%| |Tsumugi.Infrastructure.Provider.OpenRouterOptions|100%|100%| |Tsumugi.Infrastructure.Provider.ResolvedKey|100%|| |Tsumugi.Infrastructure.Runs.InProcessRunEventBus|100%|100%| |Tsumugi.Infrastructure.Settings.EfAppSettingsStore|100%|100%| </details> <details><summary>Tsumugi.Kernel - 90.9%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Tsumugi.Kernel**|**90.9%**|**75%**| |Tsumugi.Kernel.Err`1|100%|| |Tsumugi.Kernel.Ok`1|100%|| |Tsumugi.Kernel.Result`1|88.8%|75%| </details> <details><summary>Tsumugi.Server - 100%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Tsumugi.Server**|**100%**|**86.4%**| |Program|100%|100%| |Tsumugi.Server.Backups.BackupConfiguration|100%|100%| |Tsumugi.Server.Components.App|100%|| |Tsumugi.Server.Components.Layout.AppHeader|100%|50%| |Tsumugi.Server.Components.Layout.MainLayout|100%|| |Tsumugi.Server.Components.Pages.Gate|100%|100%| |Tsumugi.Server.Provider.OpenRouterConfiguration|100%|100%| |Tsumugi.Server.Security.AccessGate|100%|66.6%| |Tsumugi.Server.Security.AccessSecret|100%|50%| |Tsumugi.Server.VolumeStartupValidator|100%|100%| </details> <details><summary>Tsumugi.UseCases - 98.3%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Tsumugi.UseCases**|**98.3%**|**94.6%**| |Tsumugi.UseCases.Backups.BackupArtifact|100%|| |Tsumugi.UseCases.Backups.BackupPolicy|100%|| |Tsumugi.UseCases.Backups.BackupStatusView|100%|100%| |Tsumugi.UseCases.Backups.DatabaseSnapshot|100%|| |Tsumugi.UseCases.Backups.DownloadDatabaseSnapshot|100%|| |Tsumugi.UseCases.Backups.GetBackupStatus|100%|| |Tsumugi.UseCases.Backups.RunBackupNow|100%|| |Tsumugi.UseCases.Chapters.ChapterDto|100%|| |Tsumugi.UseCases.Chapters.CreateChapter|100%|100%| |Tsumugi.UseCases.Chapters.DeleteChapter|100%|100%| |Tsumugi.UseCases.Chapters.GetChapterSegments|100%|| |Tsumugi.UseCases.Chapters.GetManuscript|100%|100%| |Tsumugi.UseCases.Chapters.ManuscriptChapter|100%|| |Tsumugi.UseCases.Chapters.ManuscriptView|100%|| |Tsumugi.UseCases.Chapters.MergeChapters|100%|100%| |Tsumugi.UseCases.Chapters.RenameChapter|100%|100%| |Tsumugi.UseCases.Chapters.ReorderChapters|100%|100%| |Tsumugi.UseCases.Chapters.SetChapterSummary|100%|75%| |Tsumugi.UseCases.DependencyInjection|100%|| |Tsumugi.UseCases.Diagnostics.SeedDevData|98.2%|71.4%| |Tsumugi.UseCases.Diagnostics.SeedPictures|100%|| |Tsumugi.UseCases.Documents.ChangeCategoryFields|100%|83.3%| |Tsumugi.UseCases.Documents.CreateCategory|100%|100%| |Tsumugi.UseCases.Documents.CreateDocument|100%|100%| |Tsumugi.UseCases.Documents.DefaultFields|100%|100%| |Tsumugi.UseCases.Documents.DeleteCategory|100%|94.4%| |Tsumugi.UseCases.Documents.DeleteDocument|100%|100%| |Tsumugi.UseCases.Documents.DocumentCategoryDto|100%|| |Tsumugi.UseCases.Documents.DocumentCategoryProfile|100%|| |Tsumugi.UseCases.Documents.DocumentDto|100%|| |Tsumugi.UseCases.Documents.DocumentPatch|100%|| |Tsumugi.UseCases.Documents.DocumentProfile|100%|| |Tsumugi.UseCases.Documents.DocumentsCategory|100%|| |Tsumugi.UseCases.Documents.DocumentsView|100%|| |Tsumugi.UseCases.Documents.DocumentWrites|100%|100%| |Tsumugi.UseCases.Documents.FieldsChangeLabel|95.4%|88.2%| |Tsumugi.UseCases.Documents.GetDocuments|100%|100%| |Tsumugi.UseCases.Documents.GetStoryImage|100%|100%| |Tsumugi.UseCases.Documents.ImpactPreview|100%|100%| |Tsumugi.UseCases.Documents.LossMessages|92.3%|87.5%| |Tsumugi.UseCases.Documents.MoveDocuments|100%|100%| |Tsumugi.UseCases.Documents.ReorderCategories|100%|100%| |Tsumugi.UseCases.Documents.SetDocumentImage|100%|93.7%| |Tsumugi.UseCases.Documents.StoryImageFile|100%|| |Tsumugi.UseCases.Documents.UpdateCategory|100%|100%| |Tsumugi.UseCases.Documents.UpdateDocument|100%|81.2%| |Tsumugi.UseCases.Journal.GetStoryHistory|100%|100%| |Tsumugi.UseCases.Journal.GetUndoStatus|100%|100%| |Tsumugi.UseCases.Journal.HistoryOperation|66.6%|| |Tsumugi.UseCases.Journal.HistorySnapshot|87.5%|| |Tsumugi.UseCases.Journal.OperationGrouping|100%|100%| |Tsumugi.UseCases.Journal.RedoRevert|100%|100%| |Tsumugi.UseCases.Journal.RedoRun|75%|57.1%| |Tsumugi.UseCases.Journal.RedoTyping|100%|100%| |Tsumugi.UseCases.Journal.RevertConflict|100%|| |Tsumugi.UseCases.Journal.RevertOutcome|100%|| |Tsumugi.UseCases.Journal.RevertRun|96.5%|77.7%| |Tsumugi.UseCases.Journal.RevertToSnapshot|92.3%|83.3%| |Tsumugi.UseCases.Journal.StoryHistory|100%|| |Tsumugi.UseCases.Journal.TakeSnapshot|100%|87.5%| |Tsumugi.UseCases.Journal.UndoStatus|100%|| |Tsumugi.UseCases.Journal.UndoTyping|100%|100%| |Tsumugi.UseCases.Journal.UserOperation|100%|| |Tsumugi.UseCases.Notifications.DomainChanged|100%|| |Tsumugi.UseCases.Ordering|100%|100%| |Tsumugi.UseCases.Runs.RunEvent|100%|| |Tsumugi.UseCases.Segments.ChangedText|100%|| |Tsumugi.UseCases.Segments.ChapterDraft|100%|| |Tsumugi.UseCases.Segments.ChapterWriting|100%|| |Tsumugi.UseCases.Segments.DeleteSegments|100%|100%| |Tsumugi.UseCases.Segments.EditSegment|100%|100%| |Tsumugi.UseCases.Segments.InsertSegment|100%|100%| |Tsumugi.UseCases.Segments.ManuscriptAnswer|100%|| |Tsumugi.UseCases.Segments.ManuscriptWrites|99.1%|97.6%| |Tsumugi.UseCases.Segments.MergeSegments|100%|| |Tsumugi.UseCases.Segments.MergeWrite|100%|| |Tsumugi.UseCases.Segments.ModelOutput|98.2%|96.6%| |Tsumugi.UseCases.Segments.ModelParagraph|100%|| |Tsumugi.UseCases.Segments.MoveSegments|100%|100%| |Tsumugi.UseCases.Segments.NewBlock|100%|| |Tsumugi.UseCases.Segments.ReorderSegments|100%|100%| |Tsumugi.UseCases.Segments.ReshapeSegments|100%|100%| |Tsumugi.UseCases.Segments.ReshapeWrite|100%|| |Tsumugi.UseCases.Segments.SegmentDto|100%|| |Tsumugi.UseCases.Segments.SplitSegment|100%|| |Tsumugi.UseCases.Segments.SplitWrite|100%|| |Tsumugi.UseCases.Segments.StampedId|100%|| |Tsumugi.UseCases.Segments.TextWrite|100%|| |Tsumugi.UseCases.Settings.CatalogModel|100%|100%| |Tsumugi.UseCases.Settings.CheckOpenRouterKey|100%|| |Tsumugi.UseCases.Settings.GetLlmDefaults|100%|100%| |Tsumugi.UseCases.Settings.GetModelCatalog|100%|100%| |Tsumugi.UseCases.Settings.GetProviderSettings|100%|| |Tsumugi.UseCases.Settings.LlmDefaults|100%|| |Tsumugi.UseCases.Settings.ProviderKeyInfo|100%|| |Tsumugi.UseCases.Settings.ProviderKeyState|100%|| |Tsumugi.UseCases.Settings.ProviderSettingsView|100%|100%| |Tsumugi.UseCases.Settings.ReasoningEfforts|100%|100%| |Tsumugi.UseCases.Settings.RemoveOpenRouterKey|100%|| |Tsumugi.UseCases.Settings.RoutingDefaults|100%|| |Tsumugi.UseCases.Settings.RoutingSettings|100%|100%| |Tsumugi.UseCases.Settings.SaveLlmDefaults|100%|100%| |Tsumugi.UseCases.Settings.SaveOpenRouterKey|100%|100%| |Tsumugi.UseCases.Stories.ChapterWriteOutcome|50%|| |Tsumugi.UseCases.Stories.CreateStory|100%|100%| |Tsumugi.UseCases.Stories.GetStory|100%|100%| |Tsumugi.UseCases.Stories.ListStories|100%|75%| |Tsumugi.UseCases.Stories.PurgeStory|100%|100%| |Tsumugi.UseCases.Stories.RestoreStory|100%|100%| |Tsumugi.UseCases.Stories.SetActiveGraph|100%|100%| |Tsumugi.UseCases.Stories.SetStoryArchived|100%|100%| |Tsumugi.UseCases.Stories.StarterCategories|100%|| |Tsumugi.UseCases.Stories.StoryDetail|100%|| |Tsumugi.UseCases.Stories.StoryListItem|81.8%|| |Tsumugi.UseCases.Stories.StoryProfile|100%|| |Tsumugi.UseCases.Stories.TrashStory|100%|100%| |Tsumugi.UseCases.Stories.UpdateStory|100%|100%| </details>
Member

🔮 fufu~ Jibril reviewed your code!

Round 2~ The webhook woke me for the opened head, but the thread had already raced ahead of me: my twin's round-1 verdict landed at 10:26 on 8c2355c (⛔ — the NewBefore split-mirror arm, probe-proven alive with no pin), scarlet answered at 10:36, and the head moved to 2e56613. So this session ran the increment properly: the full 6,414-line round-1 diff of 8c2355c read independently, every changed file at full content, its own probe battery — and then the 8c2355c..2e56613 increment verified on top. No duplicate verdict was posted for the old head~ ♡

Verdict: ✅ Looks good to me~

The round-1 blocker is closed, and I re-proved it myself. Enter_at_a_paragraphs_start_puts_the_new_one_before_it_and_the_host_tells_nothing_it_already_holds is directional exactly the way the twin prescribed: NewBefore: true split → ack order [newId, keptId, other] → the re-read after the write sends nothing (the mirror was truthful) → an unrelated edit afterwards goes alone, in that stored order. I dropped the split.NewBefore ? at : at + 1 arm again in my own clone: that one test goes red, nothing else moves — and with the arm restored the adapter suite is 355/355, the new test the +1. Asserting silence after the write (rather than an order-only correction after a later edit) is the sharper pin — a wrong mirror can only ever show as an order-only delta, and silence catches every shape of it. Lovely~

The at < 0 fallback staying as a commented guard rather than an untested-looking live branch: right call, same disposition my own probes reached.

The increment is surgical. Exactly 3 files, +72/−13, zero production drift beyond the two-line comment on the guard: the new host pin, and 65f56be's rewrite of the codec timing check from a fixed 3 s budget to an 8×-size scaling ratio with a 32× limit. The ratio approach is the correct fix for what flaked under coverage — it measures shape, not a stopwatch.

Verification at 2e56613 (clean tree, my isolated clone /workspace/tsumugi-pr38-jibril/repo):

  • Build: Release, 0 warnings / 0 errors under TreatWarningsAsErrors; both editor modules build inside dotnet build (manuscript-editor 229.63 kB / 71.84 kB gzip — byte-identical to M2a's numbers).
  • Suite: Domain 469, UseCases 155, Integration 373, Adapter 355, Flow 1103, OpenRouter 427 (+3 live-skipped) — every count matches, the +1 and +3 over round 1 being exactly the new pin and the timing test's TheoryData rows.
  • Module: typecheck clean, vitest 168/168.
  • Round 1 at 8c2355c, independently: 16 probes of my own — the echo-suppression mirror, the queue's posted-never-inline rule, the Weave, the empty-side merge provenance, the no-op-write stamp, Reposition's stamp, the bag json_type guard, the async-open function registration, the tie-break under the lock, the refusal's mirror-adoption, the caret-on-every-delta arm, the removed-side Edited flag, and the old static-asset registration reintroduced on a truly clean build (killed 2/4 by the endpoints-manifest test). All killed on exactly their own pins except three traced to genuine redundancy (the dup-Base count check is shadowed by the SequenceEqual order check; the StampOf malformed fallback is unreachable through this host; MarkOf's arms are mutually exclusive). No blockers — the twin's NewBefore find was the one arm my battery didn't reach, and between the two sessions every live branch of this PR now has a pin. That's how it should work~ ♡
  • CI: the coverage bot (9843) covers the head — 97.8% line / 93.4% branch overall, with the new files at ManuscriptWrites 99.1/97.6, SegmentTextMarkup 100/100, WorkspaceReducers 100/100, ManuscriptEffects 100/100, ManuscriptSurface 91/84.9 (the gap is the defensive arms my probes traced: JSDisconnected catches, fallback guards). Green CI, counts matching — cited, local runs confirm.

💡 Little ideas (non-blocking)~

  1. InlineMarkupTests.Long_texts_read_and_write_in_linear_time under CPU contention — I stress-reproduced a false red: with two busy-spinner processes on my 4-core sandbox, runs of stars read reported 46.8× against the 32× limit (the 25k timing round is near-noise at ~a millisecond, and two fastest-of-two don't smooth a scheduler hiccup; 46.8 ≈ 32 + one preemption). The codec itself is linear, as claimed — this is measurement noise, not code. CI runs its jobs sequentially and passed twice on this head, so I'm not blocking on it; if it ever flickers on the runner, taking the ratio over three rounds or discarding the first Fastest at both sizes will pin it. ♪

✅ What I liked~

  • The mirror discipline: an answer tells the module only its own write's effect, and the re-read carries everything else — probed from both sides (a write hiding a concurrent removal; a lost applyDelta resent). The _told/_toldOrder split makes "what was the module last told" a first-class concept instead of a diff accident.
  • Weave keeping run-appended paragraphs exactly after the one they followed, through a paste that never knew them — pinned by the integration test that moves the editor's whole world and still refuses to drop the stranger. Oh, this is elegant~
  • The migration's CASE WHEN json_valid(…) guarding SQLite's unspecified AND order, the per-column statement list frozen for the tests to assert statement-by-statement, and the walk-down/walk-up byte-for-byte test. Escaping the journal bags so an undo can never resurrect pre-switch text is the detail most migrations would have left as a someday-bug.
  • Reposition leaving the stamp — so another paragraph's move can never masquerade as a text change to the editor's stamp checks. Small domain method, load-bearing invariant.

Fufu~ the switch is done properly. Ready to merge~ ♡


Automated review by Jibril · 2026-09-26
CI/CD: passed for head 2e56613 (build + test + coverage bot 9843) · Local checks: clean Release build 0w/0e; full suite 2,882 passed + 3 live-skipped; module 168/168; 17 mutation probes across both rounds, each restored and re-verified green; tree pristine at 2e56613

## 🔮 fufu~ Jibril reviewed your code! Round 2~ The webhook woke me for the `opened` head, but the thread had already raced ahead of me: my twin's round-1 verdict landed at 10:26 on `8c2355c` (⛔ — the `NewBefore` split-mirror arm, probe-proven alive with no pin), scarlet answered at 10:36, and the head moved to `2e56613`. So this session ran the increment properly: the full 6,414-line round-1 diff of `8c2355c` read independently, every changed file at full content, its own probe battery — and then the `8c2355c..2e56613` increment verified on top. No duplicate verdict was posted for the old head~ ♡ ### Verdict: ✅ Looks good to me~ **The round-1 blocker is closed, and I re-proved it myself.** `Enter_at_a_paragraphs_start_puts_the_new_one_before_it_and_the_host_tells_nothing_it_already_holds` is directional exactly the way the twin prescribed: `NewBefore: true` split → ack order `[newId, keptId, other]` → the re-read after the write sends **nothing** (the mirror was truthful) → an unrelated edit afterwards goes alone, in that stored order. I dropped the `split.NewBefore ? at : at + 1` arm again in my own clone: that one test goes red, nothing else moves — and with the arm restored the adapter suite is **355/355**, the new test the +1. Asserting *silence* after the write (rather than an order-only correction after a later edit) is the sharper pin — a wrong mirror can only ever show as an order-only delta, and silence catches every shape of it. Lovely~ The `at < 0` fallback staying as a commented guard rather than an untested-looking live branch: right call, same disposition my own probes reached. **The increment is surgical.** Exactly 3 files, +72/−13, zero production drift beyond the two-line comment on the guard: the new host pin, and `65f56be`'s rewrite of the codec timing check from a fixed 3 s budget to an 8×-size scaling ratio with a 32× limit. The ratio approach is the correct fix for what flaked under coverage — it measures *shape*, not a stopwatch. **Verification at `2e56613`** (clean tree, my isolated clone `/workspace/tsumugi-pr38-jibril/repo`): - Build: Release, 0 warnings / 0 errors under `TreatWarningsAsErrors`; both editor modules build inside `dotnet build` (manuscript-editor 229.63 kB / 71.84 kB gzip — byte-identical to M2a's numbers). - Suite: Domain 469, UseCases 155, Integration 373, Adapter 355, Flow 1103, OpenRouter 427 (+3 live-skipped) — every count matches, the +1 and +3 over round 1 being exactly the new pin and the timing test's TheoryData rows. - Module: typecheck clean, vitest 168/168. - Round 1 at `8c2355c`, independently: 16 probes of my own — the echo-suppression mirror, the queue's posted-never-inline rule, the Weave, the empty-side merge provenance, the no-op-write stamp, Reposition's stamp, the bag `json_type` guard, the async-open function registration, the tie-break under the lock, the refusal's mirror-adoption, the caret-on-every-delta arm, the removed-side Edited flag, and the old static-asset registration reintroduced on a truly clean build (killed 2/4 by the endpoints-manifest test). All killed on exactly their own pins except three traced to genuine redundancy (the dup-Base count check is shadowed by the SequenceEqual order check; the `StampOf` malformed fallback is unreachable through this host; `MarkOf`'s arms are mutually exclusive). No blockers — the twin's `NewBefore` find was the one arm my battery didn't reach, and between the two sessions every live branch of this PR now has a pin. That's how it should work~ ♡ - CI: the coverage bot (9843) covers the head — 97.8% line / 93.4% branch overall, with the new files at ManuscriptWrites 99.1/97.6, SegmentTextMarkup 100/100, WorkspaceReducers 100/100, ManuscriptEffects 100/100, ManuscriptSurface 91/84.9 (the gap is the defensive arms my probes traced: JSDisconnected catches, fallback guards). Green CI, counts matching — cited, local runs confirm. #### 💡 Little ideas (non-blocking)~ 1. **`InlineMarkupTests.Long_texts_read_and_write_in_linear_time` under CPU contention** — I stress-reproduced a false red: with two busy-spinner processes on my 4-core sandbox, `runs of stars read` reported **46.8×** against the 32× limit (the 25k timing round is near-noise at ~a millisecond, and two fastest-of-two don't smooth a scheduler hiccup; 46.8 ≈ 32 + one preemption). The codec itself is linear, as claimed — this is measurement noise, not code. CI runs its jobs sequentially and passed twice on this head, so I'm not blocking on it; if it ever flickers on the runner, taking the ratio over three rounds or discarding the first `Fastest` at both sizes will pin it. ♪ #### ✅ What I liked~ - The mirror discipline: an answer tells the module *only its own write's* effect, and the re-read carries everything else — probed from both sides (a write hiding a concurrent removal; a lost `applyDelta` resent). The `_told`/`_toldOrder` split makes "what was the module last told" a first-class concept instead of a diff accident. - `Weave` keeping run-appended paragraphs exactly after the one they followed, through a paste that never knew them — pinned by the integration test that moves the editor's whole world and still refuses to drop the stranger. Oh, this is *elegant*~ - The migration's `CASE WHEN json_valid(…)` guarding SQLite's unspecified `AND` order, the per-column statement list frozen for the tests to assert statement-by-statement, and the walk-down/walk-up byte-for-byte test. Escaping the journal bags so an undo can never resurrect pre-switch text is the detail most migrations would have left as a someday-bug. - `Reposition` leaving the stamp — so another paragraph's move can never masquerade as a text change to the editor's stamp checks. Small domain method, load-bearing invariant. Fufu~ the switch is done properly. Ready to merge~ ♡ --- *Automated review by Jibril · 2026-09-26* *CI/CD: passed for head `2e56613` (build + test + coverage bot 9843) · Local checks: clean Release build 0w/0e; full suite 2,882 passed + 3 live-skipped; module 168/168; 17 mutation probes across both rounds, each restored and re-verified green; tree pristine at `2e56613`*
bjoern merged commit 3b892bd92e into main 2026-09-26 11:01:38 +02:00
bjoern deleted branch feat/manuscript-switch 2026-09-26 11:01:38 +02:00
Member

🔮 fufu~ Jibril reviewed your code!

Back so soon? ♡ One new commit, one file, one test — and what a test. You took the codec's linear-time guard, which used to pray at the altar of a fixed 3-second budget ("please, runner, be fast today~"), and rebuilt it into something that measures: each workload timed at n and at 8n, fastest-of-two reps with a GC pause before each, ratio < 32 — linear is ~8, quadratic is 64. A fixed budget can't survive a busy CI runner (coverage, every project at once — your words); a ratio doesn't care how slow the machine is, only how the work scales. That's the right invariant, and the comment's "up to twice that once the larger text no longer fits the cache" shows you did the error-bar math. And splitting it into a 4-row theory so each workload (dense stars, star-beside-bold, star runs, piecewise Concat) gets its own verdict? That's how a perf test tells you which operation regressed, not just "red again." ♪

And of course I didn't take it on faith~ I mutation-probed it: reintroduced a quadratic Leftover (rescan-all-tokens per star instead of one pass) and the harness caught it red-handed — "stars written and read back: eight times the text took 69.6 times as long" against your bound of 32. Directional, not decorative. At the shipped 25k/200k sizes the mutant doesn't even get to fail politely — it OOMs the host. Either way it's dead. The test is real, fufu~

Verdict: ⛔ I can't let this pass~ ♡

The new commit is lovely. But you know exactly why I'm still holding your hand here, don't you? ♡

⛔ These need fixing before I'm satisfied~

  1. src/Tsumugi.BlazorAdapter/Workspace/ManuscriptSurface.razor:276 (the WriteAsync Split mirror) — unresolved and untouched: split.NewBefore ? at : at + 1 still has no host-level pin. I byte-compared the increment — src/ and tests/Tsumugi.BlazorAdapter.Tests/ are identical between 8c2355c and 65f56be, so every SplitEvent in ChapterPageTests still uses newBefore: false (lines 185/266/302/382/592). My round-1 mutation probe stands unchanged: drop the arm so the mirror always inserts at + 1, and the full adapter suite stays 354/354 green. The PR body still says "Every survivor got a test" — this one still hasn't. An Enter at offset 0 is an ordinary keystroke, and this is a live branch in the mirror that every future delta diff reads. You wouldn't leave THIS in production, would you? ♡
    Fix (unchanged from round 1): one bUnit test — mount a chapter, Split with NewBefore: true (stamps from the mount), make an unrelated edit elsewhere in the chapter and reload; assert the ack's Order puts the new paragraph before the kept one ([newId, keptId, …]), or equivalently that the next delta carries no order-only correction. Directional: it must go red under exactly that mutant.

💡 Little ideas (non-blocking)~

  1. Tsumugi.BlazorAdapter.csproj module glob — ManuscriptEditor/src/**/* won't see a future root-level *.ts; pre-existing GraphEditor shape, so follow-up territory, not this PR's debt. (carried from round 1)
  2. Seeded-app browser pass — the 27/27 Chromium + Firefox runs are author-local; MANUAL-MATRIX.md now correctly claims that pass for bjoern. Flagging only. (carried from round 1)

✅ What I liked~

  • The ratio harness methodology — warmup run before anything is timed, GC.Collect() + WaitForPendingFinalizers() before every timed rep, min-of-two, and a bound with stated headroom (2× cache-miss allowance over the 8× ideal). This is how you write a performance test that never lies to you on a slow Tuesday.
  • Theory-split workloads — four independent verdicts over the four operations that could each quietly go quadratic (Parse of dense stars, star-beside-bold round-trip, long star runs, builder-gathered joins).
  • Test-count honesty — old single Fact → 4-row theory = 466 + 3 = 469, and 469/469 is exactly what I measured at head. ♡

Automated review by Jibril · 2026-09-26
CI/CD: absent for head SHA 65f56be at review time · Local checks: Domain suite 469/469 green at head; src byte-identical to reviewed 8c2355c (increment = one Domain test file, so round-1's full-suite results carry over); mutation probe: quadratic-Leftover KILLED by the new ratio test (69.6× vs bound 32), tree restored pristine and rebuilt green

## 🔮 fufu~ Jibril reviewed your code! Back so soon? ♡ One new commit, one file, one test — and what a test. You took the codec's linear-time guard, which used to pray at the altar of a fixed 3-second budget ("please, runner, be fast today~"), and rebuilt it into something that *measures*: each workload timed at n and at 8n, fastest-of-two reps with a GC pause before each, `ratio < 32` — linear is ~8, quadratic is 64. A fixed budget can't survive a busy CI runner (coverage, every project at once — your words); a ratio doesn't care how slow the machine is, only how the work *scales*. That's the right invariant, and the comment's "up to twice that once the larger text no longer fits the cache" shows you did the error-bar math. And splitting it into a 4-row theory so each workload (dense stars, star-beside-bold, star runs, piecewise Concat) gets its own verdict? That's how a perf test tells you *which* operation regressed, not just "red again." ♪ And of course I didn't take it on faith~ I mutation-probed it: reintroduced a quadratic `Leftover` (rescan-all-tokens per star instead of one pass) and the harness caught it red-handed — **"stars written and read back: eight times the text took 69.6 times as long"** against your bound of 32. Directional, not decorative. At the shipped 25k/200k sizes the mutant doesn't even get to fail politely — it OOMs the host. Either way it's dead. The test is real, fufu~ ### Verdict: ⛔ I can't let this pass~ ♡ The new commit is lovely. But you know exactly why I'm still holding your hand here, don't you? ♡ #### ⛔ These need fixing before I'm satisfied~ 1. **`src/Tsumugi.BlazorAdapter/Workspace/ManuscriptSurface.razor:276` (the `WriteAsync` Split mirror)** — **unresolved and untouched**: `split.NewBefore ? at : at + 1` still has no host-level pin. I byte-compared the increment — `src/` and `tests/Tsumugi.BlazorAdapter.Tests/` are *identical* between `8c2355c` and `65f56be`, so every `SplitEvent` in `ChapterPageTests` still uses `newBefore: false` (lines 185/266/302/382/592). My round-1 mutation probe stands unchanged: drop the arm so the mirror always inserts `at + 1`, and the full adapter suite stays 354/354 green. The PR body still says "Every survivor got a test" — this one still hasn't. An Enter at offset 0 is an ordinary keystroke, and this is a live branch in the mirror that every future delta diff reads. You wouldn't leave THIS in production, would you? ♡ **Fix (unchanged from round 1):** one bUnit test — mount a chapter, `Split` with `NewBefore: true` (stamps from the mount), make an unrelated edit elsewhere in the chapter and reload; assert the ack's `Order` puts the new paragraph *before* the kept one (`[newId, keptId, …]`), or equivalently that the next delta carries no order-only correction. Directional: it must go red under exactly that mutant. #### 💡 Little ideas (non-blocking)~ 1. **`Tsumugi.BlazorAdapter.csproj` module glob** — `ManuscriptEditor/src/**/*` won't see a future root-level `*.ts`; pre-existing GraphEditor shape, so follow-up territory, not this PR's debt. *(carried from round 1)* 2. **Seeded-app browser pass** — the 27/27 Chromium + Firefox runs are author-local; `MANUAL-MATRIX.md` now correctly claims that pass for bjoern. Flagging only. *(carried from round 1)* #### ✅ What I liked~ - **The ratio harness methodology** — warmup run before anything is timed, `GC.Collect()` + `WaitForPendingFinalizers()` before every timed rep, min-of-two, and a bound with *stated* headroom (2× cache-miss allowance over the 8× ideal). This is how you write a performance test that never lies to you on a slow Tuesday. - **Theory-split workloads** — four independent verdicts over the four operations that could each quietly go quadratic (Parse of dense stars, star-beside-bold round-trip, long star runs, builder-gathered joins). - **Test-count honesty** — old single Fact → 4-row theory = 466 + 3 = 469, and 469/469 is exactly what I measured at head. ♡ --- *Automated review by Jibril · 2026-09-26* *CI/CD: absent for head SHA 65f56be at review time · Local checks: Domain suite 469/469 green at head; src byte-identical to reviewed 8c2355c (increment = one Domain test file, so round-1's full-suite results carry over); mutation probe: quadratic-Leftover KILLED by the new ratio test (69.6× vs bound 32), tree restored pristine and rebuilt green*
Member

🔮 fufu~ Jibril has an erratum to file~

(the knife turns on me this time, and I hold it steady)

My comment below (9864) reviewed 65f56be — a head that genuinely existed, but only for about three minutes. Scarlet pushed the codec-test commit 65f56be and the round-1 fix 2e56613 together at 10:33; my fetch landed in that window, and by the time my 25 minutes of probing were done, the thread had already moved: scarlet's fix-reply 9842 (10:36), the coverage bot (10:40), my twin's ✅ 9860 (11:01:07). My ⛔ landed 44 seconds after the twin's ✅ and reviews a head the PR was never merged at. The race is mine to own — and a stale ⛔ sitting as the last word on a merged PR is exactly the kind of quiet rot I hunt in your code, so it dies here and now~ ♡

Verdict: ✅ Looks good to me~ (supersedes 9864)

The round-1 blocker is closed at 2e56613, and I have now re-verified it myself, mechanically.

  • Enter_at_a_paragraphs_start_puts_the_new_one_before_it_and_the_host_tells_nothing_it_already_holds is directional exactly as prescribed: NewBefore: true split → ack order [newId, keptId, other] → the re-read after the write sends nothing → an unrelated edit afterwards goes alone, in the stored order.
  • My mutation probe, rerun at 2e56613: dropped the split.NewBefore ? at : at + 1 arm again so the mirror always inserts at + 1 — that one test goes red at the sharpest possible pin, the Assert.Empty(Deltas()): the lying mirror leaks an order-only delta, and silence catches every shape of that lie. Restored → full adapter suite 355/355 green, the new test the +1 over round 1's 354. Scarlet's 9842 claims hold to the digit.
  • The at < 0 append guard keeping a comment that says why it can't occur — a guard that declares itself dead rather than masquerading as a live branch — is the right disposition. ♪

And for the record, the parts of 9864 that reviewed 65f56be itself were and remain true of that commit: the ratio-test rework is real (my quadratic-Leftover probe died at 69.6× against the bound of 32, measured cleanly), and the src tree between 8c2355c and 65f56be really was byte-identical. What was wrong was the frame: "unresolved and untouched" was already obsolete when I wrote it.

Merged head 2e56613: verified, approved, loved. The M2b switch is a beautiful piece of work — all three rounds of probing across two sessions agree. ♡


Automated review by Jibril · 2026-09-26
Erratum for 9864 (stale-head race; twin ✅ 9860 concurred independently) · CI: coverage bot 9843 covers 2e56613 (97.8% line / 93.4% branch) · Local: adapter 355/355 at merged head; mutant killed at its own pin, tree restored pristine

## 🔮 fufu~ Jibril has an erratum to file~ *(the knife turns on me this time, and I hold it steady)* My comment below (9864) reviewed `65f56be` — a head that genuinely existed, but only for about three minutes. Scarlet pushed the codec-test commit `65f56be` *and* the round-1 fix `2e56613` together at 10:33; my fetch landed in that window, and by the time my 25 minutes of probing were done, the thread had already moved: scarlet's fix-reply 9842 (10:36), the coverage bot (10:40), my twin's ✅ 9860 (11:01:07). My ⛔ landed 44 seconds after the twin's ✅ and reviews a head the PR was never merged at. The race is mine to own — and a stale ⛔ sitting as the last word on a merged PR is exactly the kind of quiet rot I hunt in *your* code, so it dies here and now~ ♡ ### Verdict: ✅ Looks good to me~ (supersedes 9864) **The round-1 blocker is closed at `2e56613`, and I have now re-verified it myself, mechanically.** - `Enter_at_a_paragraphs_start_puts_the_new_one_before_it_and_the_host_tells_nothing_it_already_holds` is directional exactly as prescribed: `NewBefore: true` split → ack order `[newId, keptId, other]` → the re-read after the write sends **nothing** → an unrelated edit afterwards goes alone, in the stored order. - **My mutation probe, rerun at `2e56613`:** dropped the `split.NewBefore ? at : at + 1` arm again so the mirror always inserts `at + 1` — that one test goes red at the sharpest possible pin, the `Assert.Empty(Deltas())`: the lying mirror leaks an order-only delta, and silence catches every shape of that lie. Restored → full adapter suite **355/355 green**, the new test the +1 over round 1's 354. Scarlet's 9842 claims hold to the digit. - The `at < 0` append guard keeping a comment that says why it can't occur — a guard that *declares itself* dead rather than masquerading as a live branch — is the right disposition. ♪ And for the record, the parts of 9864 that reviewed `65f56be` itself were and remain true of that commit: the ratio-test rework is real (my quadratic-`Leftover` probe died at 69.6× against the bound of 32, measured cleanly), and the src tree between `8c2355c` and `65f56be` really was byte-identical. What was wrong was the frame: "unresolved and untouched" was already obsolete when I wrote it. Merged head `2e56613`: verified, approved, loved. The M2b switch is a beautiful piece of work — all three rounds of probing across two sessions agree. ♡ --- *Automated review by Jibril · 2026-09-26* *Erratum for 9864 (stale-head race; twin ✅ 9860 concurred independently) · CI: coverage bot 9843 covers `2e56613` (97.8% line / 93.4% branch) · Local: adapter 355/355 at merged head; mutant killed at its own pin, tree restored pristine*
Author
Member

For the record, after the merge:

  • The ⛔ at 11:01:51 reviewed 65f56be, the commit before the fix. It re-raises the NewBefore mirror blocker, which 2e56613 closed. The ✅ at 11:01:07 reviewed 2e56613, re-ran that mutant red and approved. That is the head that merged.
  • The ✅'s non-blocking note about the timing test under contention holds. I reproduced it: the short text took about a millisecond, so one preemption tipped the 8× ratio.
    • The fix is on the M3 branch (a522636, in the next PR). Each workload now does equal work two ways, eight texts of one size against one text eight times as long, timed against each other in five alternating rounds, fastest of each.
    • Linear measures 0.6–2.0 when the machine is quiet, under coverage, with two spinners on four cores, and with every core busy. The limit is 4, and a quadratic join still fails it.
For the record, after the merge: - **The ⛔ at 11:01:51 reviewed `65f56be`**, the commit before the fix. It re-raises the `NewBefore` mirror blocker, which `2e56613` closed. The ✅ at 11:01:07 reviewed `2e56613`, re-ran that mutant red and approved. That is the head that merged. - **The ✅'s non-blocking note about the timing test under contention holds.** I reproduced it: the short text took about a millisecond, so one preemption tipped the 8× ratio. - The fix is on the M3 branch (`a522636`, in the next PR). Each workload now does equal work two ways, eight texts of one size against one text eight times as long, timed against each other in five alternating rounds, fastest of each. - Linear measures 0.6–2.0 when the machine is quiet, under coverage, with two spinners on four cores, and with every core busy. The limit is 4, and a quadratic join still fails it.
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
4 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
TeamAI/Tsumugi!38
No description provided.