fix(views): a crop magnifies as far as its output allows, and each grid axis gets its own step #139

Merged
bjoern merged 3 commits from fix/view-measurement into main 2026-09-20 23:02:12 +02:00
Member

The annotation agents' inspection views (ADR 0016) stopped measuring in two silent ways. Both
come from agent friction reports (ADR 0025) filed on runs that SUCCEEDED — the work landed, and
the agent spent rounds fighting the instrument to get there. Neither failure announced itself: the
view came back, looked fine, and answered a different question than the one asked.

  1. A crop's zoom was clamped at a fixed factor of 8, although the real constraint is the 2048px
    delivered edge. A 24×20 window asked for at 20× came back at 8×. Agents asked for 11×, 16× and
    20× and were quietly given 8× — with an account that reported the delivered number honestly, so
    the only signal was that the picture was not as close as requested.
  2. The grid's step was shared by both axes and taken from the longer one. On a wide, short
    window that step could exceed the short side outright, so the axis that most needed measuring
    carried no labelled line at all. A region 1380px wide kept a 100px step on an axis that needed
    edges placed to two or three; the rounds went on shrinking the crop and putting it back.

What's in

Infrastructure — SkiaPageImageRenderer

  • The zoom is bounded by the OUTPUT, not by a factor: factor = min(asked, 2048 / longest side of the padded window). The 24×20 window asked at 20× now arrives at 20× (480×400). Where the cap
    genuinely bites, the view carries the zoom it could not give and the account says so — because a
    reader who cannot tell "too big to magnify further" from "I typed the number wrong" asks again for
    a view that cannot change. The ceiling is computed in decimal and the output sides are
    rounded, not truncated: float arithmetic returns 2047 for the exactly-binding case (400px at
    8× → 5.12f → 2047.99995), which would put the delivered scale a pixel off its own cap.
  • A refusal is only ever reported for a refused MAGNIFICATION (asked > 1m && asked > ceiling).
    A window too big to show at 1:1 is shrunk to fit, which is not a refusal and which the delivered
    Scale already states. Without this, the fitted-scale path — which floors at 1 — announced "1×
    was asked for and could not be given" to an agent that passed no zoom key at all.
  • The shrink keeps a high-quality resampler. Bounding the factor by the output moved the
    downscale out of Fit's Mitchell resampler and into the draw, whose SKSamplingOptions.Default
    is nearest — measured, not assumed: on a 3000px page of 750 one-pixel strokes, nearest
    delivered 512 black columns and 0 mid-tone, i.e. 238 strokes gone from the one instrument that
    answers "is this box cutting a stroke?". The draw now picks its sampler by direction: Mitchell
    when shrinking, Default when magnifying (deliberately — nearest shows the scan's own pixels as
    blocks, where a filter smears each edge across two source pixels). This is user-visible too, not
    only agent-visible: the human workspace's region crop goes through the same call.
  • Fit is gone from the crop path. With the shrink in the single draw, the delivered cap is
    spent inside the factor itself, so nothing shrinks twice and no ~16MB copy is made per crop. The
    cap still holds and is still pinned: The_thumbnail_downscales_to_the_cap_and_keeps_the_aspect
    and A_downscaled_page_view_reports_the_magnification_it_was_delivered_at both still assert 2048.
    Fit continues to serve the page, thumbnail and sheet paths.
  • Each axis is stepped for its own extent, and its numbers are spaced separately from its lines.
    Fixing only the step produced the next defect: lines spaced for a 60px extent under type sized for
    a 1400px one, i.e. a red band beneath an unreadable pile of digits. So GridAxis(Line, Labelled)
    splits the two, because different things bound them — how close two LINES may run is a question
    about the art (under a few delivered pixels they stop being a ruler and become a haze), how close
    two NUMBERS may run is a question about the type. Numbers are spaced against the room one takes
    along its own gutter: its height down the right, its width along the bottom.
  • A raised baseline floor. Math.Clamp(baseline, fontSize, art.Height) threw
    ArgumentException whenever the art was shorter than its own type — a gridded crop of an ordinary
    1400×20 line of small kana crashed on main. The floor is raised past the art instead.

UseCases — the view contract and its account

  • RenderedView.GridStep (int?) → Grid (GridSteps?), a nested record rather than loose nullable
    ints, so "grid or no grid" is still said once and cannot be said inconsistently per axis.
    AskedScale (decimal?) was added last with a = null default.
  • ViewAccount names both spacings per axis and its closing claim is now true axis by axis, with
    four arms: both axes named, one named while the other is too crowded to list (naming the crowded
    axis and its step outright, so nothing has to be derived), an axis with no numbered line in
    frame, and neither named.
  • The two crop tools' descriptions were softened to what the account actually guarantees. They
    promised "where each labelled line falls"; thinning the numbers made an axis with none in frame
    reachable, so that promise became false on exactly the tool whose job is to remove guessing.
    BoundViewPageTool and the setup agent's view_page keep the stronger claim honestly — a page
    window starts at 0 on both axes, so both always carry a numbered line.
  • The refusal sentence names only levers the reader actually has: RegionCropTool's region is its
    binding (ADR 0017), so it says "cover fewer page pixels: less padding, or a narrower box where
    this tool takes one" rather than pointing at a box the caller cannot address.

Docs

  • ADR 0016's "the magnification reported is the one delivered, never the one requested" is
    amended to "never the one requested in its place", plus one sentence that a refused
    magnification is named beside the delivered one. No decision changed; the clause had simply become
    false as written.

Tests

1312 → 1335 passed, 0 failed, 0 skipped (+23 cases: 19 [Fact] plus one 4-case [Theory]).
Fresh full run after a clean dotnet build Orihon.slnx -c Debug (0 warnings, 0 errors,
TreatWarningsAsErrors), --no-build per project: Domain 151, UseCases 604, Integration 303,
BlazorAdapter 277
. Domain and BlazorAdapter are untouched; Integration +11, UseCases +12.

Integration (PageImageRendererTests):

  • A_small_window_is_magnified_as_far_as_it_asks — a 24×20 window asked at 20× arrives at 480×400
    reporting Scale 20, and asserts the delivered dimensions, not just the number.
  • A_zoom_the_delivered_size_refuses_is_reported_as_refused — where the cap bites, AskedScale
    carries the zoom refused and Scale still reports the one delivered.
  • A_view_nobody_asked_to_magnify_reports_no_refusal — a 3000×3000 page, full-page crop at zoom 1:
    AskedScale null, Scale 0.68. Pins that a shrink is not a refusal.
  • A_crop_the_size_cap_shrinks_keeps_a_trace_of_every_stroke — marked columns > 750 of 2048 on a
    page of 750 one-pixel strokes; nearest leaves 512 and fails it, both Mitchell paths mark 1104.
  • A_wide_short_window_is_stepped_for_each_axis_rather_than_the_longer_one and
    A_tall_narrow_window_is_stepped_for_each_axis_as_well — assert the differing pair per axis and
    that the short axis ends up lined (Assert.InRange(lined, 5, 20): both ends, so neither "no
    lattice" nor the old red band passes).
  • A_short_axis_numbers_its_lines_far_enough_apart_to_read — delivered spacing of consecutive
    y numbers ≥ 1.2 × the type size, the pitch the renderer actually enforces (bare non-overlap is
    not enough; digits that touch read as one number). A spacing assertion, no glyph pixels.
  • A_window_at_the_size_cap_steps_its_numbers_up_a_whole_magnitude_to_clear_them — a 2000×200 crop
    of a 4000×1000 page, in the 1875–2048 delivered band. Asserts the pair and the 1.2 pitch, so
    the arm is pinned by the rule it exists to keep rather than by its number. Proved directional:
    breaking the fallthrough to magnitude * 5f makes this the one and only failure (100 vs 50).
  • A_window_shorter_than_its_own_numbers_still_renders — the baseline-clamp crash; the old code
    threw ArgumentException here.
  • A_narrow_window_can_hold_no_numbered_line_at_all and A_deep_zoom_numbers_every_line_it_draws —
    pin that the bare-axis and every-line-numbered arms are reachable from the real renderer, not
    only through a fake.

UseCases (BoundInspectionToolTests):

  • A_window_past_the_fitted_target_is_asked_for_at_life_size — the tool-level half of the
    no-spurious-refusal fix: the account carries no "could not be given" when no zoom was asked for.
  • A_zoom_the_view_could_not_give_is_said_beside_the_one_it_did — and the sentence appears when one
    genuinely was.
  • A_view_stepped_apart_on_its_axes_names_both_steps, A_crowded_axis_is_said_to_be_crowded_while_ the_other_is_still_named, An_axis_with_no_numbered_line_in_frame_says_so_rather_than_naming_none,
    A_view_with_no_numbered_line_on_either_axis_claims_none,
    A_grid_whose_every_line_is_numbered_says_that_instead_of_two_spacings — one per arm of the
    closing claim, each asserting the named arm's wording and that the sibling axis's sentence
    stayed correct.
  • A_crops_description_promises_only_what_its_account_delivers — renders the bare-axis view through
    RegionCropTool with {"grid": true, "padding": 0} (a call an agent can really make), asserts the
    account says "No labelled line falls inside this window on x", and in the same test that neither
    crop tool's Description contains "each labelled line" while both carry the "none at all" escape.
    The guard has a real target: the failing sentence is produced in the test that forbids the promise.
  • An_axis_with_no_real_step_leaves_the_account_without_a_grid — a 4-case [Theory] over a
    non-positive step on either axis, so neither arm of the divide-by-zero guard is unexercised.

Both new tests this round were proved directional by temporarily breaking the production arm,
rebuilding, and confirming the single expected failure; the renderer file was then restored
byte-for-byte from a scratchpad copy and rebuilt before committing. All other probing ran from
scratchpad file-based apps against the built assembly — production code was never left modified.

Honest notes

Surprises a cold reviewer will trip on.

  1. A square crop now gets different steps on its two axes. PageImageRendererTests.cs:332
    asserts GridAxis(10, 50) on x against GridAxis(10, 20) on y for a 180×180 window. It looks
    like a bug until you know that x's numbers are spaced against a label's width (three or four
    digits) and y's against the height of the type — so an isotropic window is legitimately
    anisotropic in its numbering. This is the one ordinary view whose behaviour moves, and it is the
    most-diffed line in the PR. Every other ordinary view was checked case by case against the old
    rule and is unchanged: 400×400 page 50/25, 800×1200 page 100/50, 4096×2048 page 500/250 and
    200/100, 3000×3000 page 200/100, the 200px zoom window 20/10.
  2. Fit is deleted from the crop path, not forgotten. See above — the cap moved into the factor
    and is still asserted by two existing tests.
  3. AxisFor's extent <= 0 guard was deliberately removed, not overlooked: a coverage report
    showing 1-of-2 branches was the reason, and RenderCropAsync fails on rect.Width < 1 || rect.Height < 1 before AxisFor is reached while RenderPageAsync's window is the decoded
    bitmap's own size. A two-line comment states that invariant at the point relying on it.
    Scale's identical pre-existing guard two members down is deliberately left — it is outside
    this diff and belongs to whoever next touches that member.

Declined, with reasons.

  • The topmost y number can still touch the one below it, in a corner the pitch rule does not
    cover. WithGrid clamps a label's baseline up to fontSize, so a numbered line at delivered y=0
    is pushed down by 0.65× fontSize and the first gap becomes spacing − 0.65fs; where spacing lands
    exactly on the 1.2 pitch (art long side ~1875px, window top a multiple of the labelled step) that
    is 0.55fs against a digit cap height of ~0.72fs. Pre-existing clamp behaviour, needs a coincidence
    of three conditions, and no assertion depends on it — while the clamp itself is depended on
    (A_window_shorter_than_its_own_numbers_still_renders exists because inverting it throws rather
    than draws). Moving it to chase a corner nothing measures would trade a pinned crash-guard for an
    unpinned cosmetic. If ever wanted: let the first label hang and clip, or start the gutter's
    numbering at the first line clear of the type.
  • BoundInspectionToolTests.A_crowded_axis_is_said_to_be_crowded_while_the_other_is_still_named
    keeps a hand-made GridAxis(1, 1) fixture. Its comment states what the fixture yields, not what
    the renderer would produce for that window, so nothing in it is false — and no ordinary window of
    that shape gives a crowded short axis at all (that arm needs a deep zoom), so making the fixture
    "plausible" would change which arm the test covers.

Merge-order handoff — AnnotationBlueprints.cs, owned by the sibling brief slice in this batch.
Deliberately untouched here; two worktrees rewriting the same prose is a conflict for no gain.
Whoever lands the brief slice should take all three of these in one pass:

  • (a) Lines 333, 353, 435 and 522 still say "if it is still unreadable at zoom 8, that is
    settled"
    . Until they change, agents will not spend the headroom this PR gives them.
  • (b) Line 232 says "what its grid step is worth" — a view now reports two steps per axis (lines
    and numbered lines), four numbers, not one.
  • (c) Line 233 carries the same "where each labelled line falls" claim this PR had to soften on
    the two crop tools, and it is a crop-facing brief, so it is falsified in exactly the same way.

Scope note. The slice boundary excludes the region tools, and BoundInspectionTools.cs holds
RegionCropTool. It is touched here only because the falsified-promise fix names those two exact
Description strings; nothing else in the file changed and no behaviour changed. If a sibling
worktree is rewriting those descriptions, the conflict is two string literals.

House rules. No stacked </summary><summary> anywhere in src/, tests/ or docs/. Retired
vocabulary (GridStep, StepFor, MajorLinesPerView, major/minor line) returns nothing in src/.
No PR number, review, date or agent report cited in any added code or doc comment. Submodule
pointers byte-identical to main. SeedDevData checked and correctly not extended — a view's
arithmetic and a tool description are not a new kind of user-authored content. No new ADR is owed;
ADR 0016 already stated the contract this restores and never claimed a fixed 8× or a shared step.

🤖 Generated with Claude Code

The annotation agents' inspection views (ADR 0016) stopped measuring in two silent ways. Both come from **agent friction reports (ADR 0025) filed on runs that SUCCEEDED** — the work landed, and the agent spent rounds fighting the instrument to get there. Neither failure announced itself: the view came back, looked fine, and answered a different question than the one asked. 1. **A crop's zoom was clamped at a fixed factor of 8**, although the real constraint is the 2048px delivered edge. A 24×20 window asked for at 20× came back at 8×. Agents asked for 11×, 16× and 20× and were quietly given 8× — with an account that reported the delivered number honestly, so the only signal was that the picture was not as close as requested. 2. **The grid's step was shared by both axes and taken from the longer one.** On a wide, short window that step could exceed the short side outright, so the axis that most needed measuring carried no labelled line at all. A region 1380px wide kept a 100px step on an axis that needed edges placed to two or three; the rounds went on shrinking the crop and putting it back. ## What's in ### Infrastructure — `SkiaPageImageRenderer` - **The zoom is bounded by the OUTPUT, not by a factor**: `factor = min(asked, 2048 / longest side of the padded window)`. The 24×20 window asked at 20× now arrives at 20× (480×400). Where the cap genuinely bites, the view carries the zoom it could not give and the account says so — because a reader who cannot tell "too big to magnify further" from "I typed the number wrong" asks again for a view that cannot change. The ceiling is computed in **decimal** and the output sides are **rounded, not truncated**: float arithmetic returns 2047 for the exactly-binding case (400px at 8× → 5.12f → 2047.99995), which would put the delivered scale a pixel off its own cap. - **A refusal is only ever reported for a refused MAGNIFICATION** (`asked > 1m && asked > ceiling`). A window too big to show at 1:1 is shrunk to fit, which is not a refusal and which the delivered `Scale` already states. Without this, the fitted-scale path — which floors at 1 — announced "1× was asked for and could not be given" to an agent that passed no `zoom` key at all. - **The shrink keeps a high-quality resampler.** Bounding the factor by the output moved the downscale out of `Fit`'s Mitchell resampler and into the draw, whose `SKSamplingOptions.Default` is **nearest** — measured, not assumed: on a 3000px page of 750 one-pixel strokes, nearest delivered 512 black columns and **0 mid-tone**, i.e. 238 strokes gone from the one instrument that answers "is this box cutting a stroke?". The draw now picks its sampler by direction: Mitchell when shrinking, `Default` when magnifying (deliberately — nearest shows the scan's own pixels as blocks, where a filter smears each edge across two source pixels). This is user-visible too, not only agent-visible: the human workspace's region crop goes through the same call. - **`Fit` is gone from the crop path.** With the shrink in the single draw, the delivered cap is spent inside the factor itself, so nothing shrinks twice and no ~16MB copy is made per crop. The cap still holds and is still pinned: `The_thumbnail_downscales_to_the_cap_and_keeps_the_aspect` and `A_downscaled_page_view_reports_the_magnification_it_was_delivered_at` both still assert 2048. `Fit` continues to serve the page, thumbnail and sheet paths. - **Each axis is stepped for its own extent, and its numbers are spaced separately from its lines.** Fixing only the step produced the next defect: lines spaced for a 60px extent under type sized for a 1400px one, i.e. a red band beneath an unreadable pile of digits. So `GridAxis(Line, Labelled)` splits the two, because different things bound them — how close two LINES may run is a question about the art (under a few delivered pixels they stop being a ruler and become a haze), how close two NUMBERS may run is a question about the type. Numbers are spaced against the room one takes along its own gutter: its height down the right, its **width** along the bottom. - **A raised baseline floor.** `Math.Clamp(baseline, fontSize, art.Height)` threw `ArgumentException` whenever the art was shorter than its own type — a gridded crop of an ordinary 1400×20 line of small kana crashed on `main`. The floor is raised past the art instead. ### UseCases — the view contract and its account - `RenderedView.GridStep (int?)` → `Grid (GridSteps?)`, a nested record rather than loose nullable ints, so "grid or no grid" is still said **once** and cannot be said inconsistently per axis. `AskedScale (decimal?)` was added last with a `= null` default. - `ViewAccount` names both spacings per axis and its closing claim is now true axis by axis, with four arms: both axes named, one named while the other is too crowded to list (naming the crowded axis **and** its step outright, so nothing has to be derived), an axis with **no** numbered line in frame, and neither named. - **The two crop tools' descriptions were softened to what the account actually guarantees.** They promised "where each labelled line falls"; thinning the numbers made an axis with none in frame reachable, so that promise became false on exactly the tool whose job is to remove guessing. `BoundViewPageTool` and the setup agent's `view_page` keep the stronger claim honestly — a page window starts at 0 on both axes, so both always carry a numbered line. - The refusal sentence names only levers the reader actually has: `RegionCropTool`'s region is its binding (ADR 0017), so it says "cover fewer page pixels: less padding, or a narrower box where this tool takes one" rather than pointing at a box the caller cannot address. ### Docs - ADR 0016's "the magnification reported is the one delivered, **never the one requested**" is amended to "never the one requested **in its place**", plus one sentence that a refused magnification is named beside the delivered one. No decision changed; the clause had simply become false as written. ## Tests **1312 → 1335 passed, 0 failed, 0 skipped** (+23 cases: 19 `[Fact]` plus one 4-case `[Theory]`). Fresh full run after a clean `dotnet build Orihon.slnx -c Debug` (0 warnings, 0 errors, `TreatWarningsAsErrors`), `--no-build` per project: **Domain 151, UseCases 604, Integration 303, BlazorAdapter 277**. Domain and BlazorAdapter are untouched; Integration +11, UseCases +12. Integration (`PageImageRendererTests`): - `A_small_window_is_magnified_as_far_as_it_asks` — a 24×20 window asked at 20× arrives at 480×400 reporting `Scale` 20, and asserts the delivered dimensions, not just the number. - `A_zoom_the_delivered_size_refuses_is_reported_as_refused` — where the cap bites, `AskedScale` carries the zoom refused **and** `Scale` still reports the one delivered. - `A_view_nobody_asked_to_magnify_reports_no_refusal` — a 3000×3000 page, full-page crop at zoom 1: `AskedScale` null, `Scale` 0.68. Pins that a shrink is not a refusal. - `A_crop_the_size_cap_shrinks_keeps_a_trace_of_every_stroke` — marked columns > 750 of 2048 on a page of 750 one-pixel strokes; nearest leaves 512 and fails it, both Mitchell paths mark 1104. - `A_wide_short_window_is_stepped_for_each_axis_rather_than_the_longer_one` and `A_tall_narrow_window_is_stepped_for_each_axis_as_well` — assert the differing pair per axis and that the short axis ends up **lined** (`Assert.InRange(lined, 5, 20)`: both ends, so neither "no lattice" nor the old red band passes). - `A_short_axis_numbers_its_lines_far_enough_apart_to_read` — delivered spacing of consecutive y numbers ≥ **1.2 × the type size**, the pitch the renderer actually enforces (bare non-overlap is not enough; digits that touch read as one number). A spacing assertion, no glyph pixels. - `A_window_at_the_size_cap_steps_its_numbers_up_a_whole_magnitude_to_clear_them` — a 2000×200 crop of a 4000×1000 page, in the 1875–2048 delivered band. Asserts the pair **and** the 1.2 pitch, so the arm is pinned by the rule it exists to keep rather than by its number. Proved directional: breaking the fallthrough to `magnitude * 5f` makes this the one and only failure (100 vs 50). - `A_window_shorter_than_its_own_numbers_still_renders` — the baseline-clamp crash; the old code threw `ArgumentException` here. - `A_narrow_window_can_hold_no_numbered_line_at_all` and `A_deep_zoom_numbers_every_line_it_draws` — pin that the bare-axis and every-line-numbered arms are reachable from the **real** renderer, not only through a fake. UseCases (`BoundInspectionToolTests`): - `A_window_past_the_fitted_target_is_asked_for_at_life_size` — the tool-level half of the no-spurious-refusal fix: the account carries no "could not be given" when no zoom was asked for. - `A_zoom_the_view_could_not_give_is_said_beside_the_one_it_did` — and the sentence appears when one genuinely was. - `A_view_stepped_apart_on_its_axes_names_both_steps`, `A_crowded_axis_is_said_to_be_crowded_while_ the_other_is_still_named`, `An_axis_with_no_numbered_line_in_frame_says_so_rather_than_naming_none`, `A_view_with_no_numbered_line_on_either_axis_claims_none`, `A_grid_whose_every_line_is_numbered_says_that_instead_of_two_spacings` — one per arm of the closing claim, each asserting the named arm's wording **and** that the sibling axis's sentence stayed correct. - `A_crops_description_promises_only_what_its_account_delivers` — renders the bare-axis view through `RegionCropTool` with `{"grid": true, "padding": 0}` (a call an agent can really make), asserts the account says "No labelled line falls inside this window on x", and in the same test that neither crop tool's `Description` contains "each labelled line" while both carry the "none at all" escape. The guard has a real target: the failing sentence is produced in the test that forbids the promise. - `An_axis_with_no_real_step_leaves_the_account_without_a_grid` — a 4-case `[Theory]` over a non-positive step on either axis, so neither arm of the divide-by-zero guard is unexercised. Both new tests this round were proved directional by temporarily breaking the production arm, rebuilding, and confirming the single expected failure; the renderer file was then restored byte-for-byte from a scratchpad copy and rebuilt before committing. All other probing ran from scratchpad file-based apps against the built assembly — production code was never left modified. ## Honest notes **Surprises a cold reviewer will trip on.** 1. **A square crop now gets different steps on its two axes.** `PageImageRendererTests.cs:332` asserts `GridAxis(10, 50)` on x against `GridAxis(10, 20)` on y for a 180×180 window. It looks like a bug until you know that x's numbers are spaced against a label's **width** (three or four digits) and y's against the **height** of the type — so an isotropic window is legitimately anisotropic in its numbering. This is the one ordinary view whose behaviour moves, and it is the most-diffed line in the PR. Every other ordinary view was checked case by case against the old rule and is unchanged: 400×400 page 50/25, 800×1200 page 100/50, 4096×2048 page 500/250 and 200/100, 3000×3000 page 200/100, the 200px zoom window 20/10. 2. **`Fit` is deleted from the crop path, not forgotten.** See above — the cap moved into the factor and is still asserted by two existing tests. 3. **`AxisFor`'s `extent <= 0` guard was deliberately removed**, not overlooked: a coverage report showing 1-of-2 branches was the reason, and `RenderCropAsync` fails on `rect.Width < 1 || rect.Height < 1` before `AxisFor` is reached while `RenderPageAsync`'s window is the decoded bitmap's own size. A two-line comment states that invariant at the point relying on it. `Scale`'s identical pre-existing guard two members down is deliberately **left** — it is outside this diff and belongs to whoever next touches that member. **Declined, with reasons.** - **The topmost y number can still touch the one below it**, in a corner the pitch rule does not cover. `WithGrid` clamps a label's baseline up to `fontSize`, so a numbered line at delivered y=0 is pushed down by 0.65× fontSize and the first gap becomes `spacing − 0.65fs`; where spacing lands exactly on the 1.2 pitch (art long side ~1875px, window top a multiple of the labelled step) that is 0.55fs against a digit cap height of ~0.72fs. Pre-existing clamp behaviour, needs a coincidence of three conditions, and no assertion depends on it — while the clamp itself **is** depended on (`A_window_shorter_than_its_own_numbers_still_renders` exists because inverting it throws rather than draws). Moving it to chase a corner nothing measures would trade a pinned crash-guard for an unpinned cosmetic. If ever wanted: let the first label hang and clip, or start the gutter's numbering at the first line clear of the type. - `BoundInspectionToolTests.A_crowded_axis_is_said_to_be_crowded_while_the_other_is_still_named` keeps a hand-made `GridAxis(1, 1)` fixture. Its comment states what the fixture yields, not what the renderer would produce for that window, so nothing in it is false — and no ordinary window of that shape gives a crowded short axis at all (that arm needs a deep zoom), so making the fixture "plausible" would change which arm the test covers. **Merge-order handoff — `AnnotationBlueprints.cs`, owned by the sibling brief slice in this batch.** Deliberately untouched here; two worktrees rewriting the same prose is a conflict for no gain. Whoever lands the brief slice should take all three of these in one pass: - (a) Lines 333, 353, 435 and 522 still say **"if it is still unreadable at zoom 8, that is settled"**. Until they change, agents will not spend the headroom this PR gives them. - (b) Line 232 says "what its grid step is worth" — a view now reports **two steps per axis** (lines and numbered lines), four numbers, not one. - (c) Line 233 carries the same **"where each labelled line falls"** claim this PR had to soften on the two crop tools, and it is a crop-facing brief, so it is falsified in exactly the same way. **Scope note.** The slice boundary excludes the region tools, and `BoundInspectionTools.cs` holds `RegionCropTool`. It is touched here only because the falsified-promise fix names those two exact `Description` strings; nothing else in the file changed and no behaviour changed. If a sibling worktree is rewriting those descriptions, the conflict is two string literals. **House rules.** No stacked `</summary><summary>` anywhere in `src/`, `tests/` or `docs/`. Retired vocabulary (`GridStep`, `StepFor`, `MajorLinesPerView`, major/minor line) returns nothing in `src/`. No PR number, review, date or agent report cited in any added code or doc comment. Submodule pointers byte-identical to `main`. `SeedDevData` checked and correctly not extended — a view's arithmetic and a tool description are not a new kind of user-authored content. No new ADR is owed; ADR 0016 already stated the contract this restores and never claimed a fixed 8× or a shared step. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Two defects in the views the annotation agents measure glyph edges against, both
reported from runs that succeeded (ADR 0025).

A crop's zoom was clamped at a fixed factor of 8, while what a view actually cannot
exceed is its delivered size: a 24x20 window asked for at 20x is 480x400, nowhere
near the cap, and was refused for nothing. The bound is now the output, so a small
window is magnified as far as it asks. Where the delivered size does bite, the view
says which zoom it could not give, so its reader narrows the window instead of
sending the same request three times over.

The grid's step was shared by both axes and taken from the longer one, so on a wide,
short crop it could exceed the short side outright: no labelled line was drawn down
that axis at all, and the account named none. Each axis is now stepped for its own
extent, both drawn and reported, and the account's closing claim holds for each axis
separately.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A crop that asked for no magnification is no longer told one was refused: only
`asked > 1` can be, since a window too big to show at 1:1 is shrunk to fit and the
delivered scale already says so.

The shrink goes through Mitchell rather than nearest neighbour, so a one-pixel stroke
survives it — the crop that answers "is this box cutting a stroke?" was dropping whole
columns. Magnification keeps nearest, which shows the scan's own pixels as blocks. With
the sampler picked by direction, the delivered-size cap is spent entirely in the factor
and the copy after the draw goes.

A grid now carries two spacings per axis: the lines drawn down it and the coarser
spacing of the ones numbered. The numbers are thinned until each has room beside the
next — its height down the right gutter, its width along the bottom — so a wide, short
window's short side stops smearing its digits together, while its lattice stays fine
enough to place an edge against. The account states both, says when an axis carries no
number in frame, and tells a reader to spend padding rather than a box it may not hold.

Placing a y number on a window shorter than its own type inverted a clamp and threw;
the floor is raised past the art instead.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
fix(views): a pinned spacing, and crop tools that promise what a view gives
All checks were successful
CI / build (pull_request) Successful in 29s
CI / test (pull_request) Successful in 1m2s
f54ade6c04
Review round 2.

The numbered spacing that steps up a whole magnitude — what the widest crops
land on, where 1, 2 and 5 times the magnitude all fall short of the room a
number needs beside the next one — was reachable and unexercised. A 2000×200
window delivered just under the size cap now pins it, by the rule it exists to
keep rather than by its number alone, and the sibling assertion on that room
asks for the pitch the renderer actually enforces instead of bare non-overlap.

Both crop descriptions told the agent where EVERY labelled line falls. The
numbers are thinned until they clear each other, so a narrow crop can carry
none on an axis — and padding is the agent's own lever, so it can ask for one.
They now promise the spacings and either the lines in frame or why there are
none, which is what the account delivers; a test holds the two together. The
page view keeps the stronger claim honestly: its window starts at 0, so both
axes always carry a number.

An axis's non-positive-extent guard is gone. A crop refuses a box that collapses
before it gets there and padding only widens what survives, and a page view's
window is the decoded bitmap, so it guarded nothing.

And the fixture for a view stepped apart on its axes is the pair the renderer
really produces for that window, which its integration sibling asserts.

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

🔄 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: f54ade6 · Generated: 2026-09-20 20:51:28 UTC · Revision: #1

Summary

Summary
Generated on: 09/20/2026 - 20:51:28
Coverage date: 09/20/2026 - 20:50:57 - 09/20/2026 - 20:51:25
Parser: MultiReport (4x Cobertura)
Assemblies: 6
Classes: 537
Files: 248
Line coverage: 97% (20485 of 21099)
Covered lines: 20485
Uncovered lines: 614
Coverable lines: 21099
Total lines: 37331
Branch coverage: 85.9% (3830 of 4458)
Covered branches: 3830
Total branches: 4458
Method coverage: Feature is only available for sponsors

Coverage

Orihon.BlazorAdapter - 95.4%
Name Line Branch
Orihon.BlazorAdapter 95.4% 88.3%
Orihon.BlazorAdapter.Bible.AddBeatRowRequested 100%
Orihon.BlazorAdapter.Bible.AddCharacterRowRequested 100%
Orihon.BlazorAdapter.Bible.AddGlossaryRowRequested 100%
Orihon.BlazorAdapter.Bible.AddHouseRuleRowRequested 100%
Orihon.BlazorAdapter.Bible.AddLoreRowRequested 100%
Orihon.BlazorAdapter.Bible.BibleEffects 89.8% 76.9%
Orihon.BlazorAdapter.Bible.BibleLoaded 100%
Orihon.BlazorAdapter.Bible.BiblePage 92.3% 80.5%
Orihon.BlazorAdapter.Bible.BibleReducers 90.6%
Orihon.BlazorAdapter.Bible.BibleState 100%
Orihon.BlazorAdapter.Bible.BibleWriteFailed 100%
Orihon.BlazorAdapter.Bible.DeleteBeatRowRequested 100%
Orihon.BlazorAdapter.Bible.DeleteCharacterRowRequested 100%
Orihon.BlazorAdapter.Bible.DeleteGlossaryRowRequested 100%
Orihon.BlazorAdapter.Bible.DeleteHouseRuleRowRequested 0%
Orihon.BlazorAdapter.Bible.DeleteLoreRowRequested 0%
Orihon.BlazorAdapter.Bible.LoadBible 100%
Orihon.BlazorAdapter.Bible.ReorderBeatsRequested 0%
Orihon.BlazorAdapter.Bible.SaveOverviewRequested 100%
Orihon.BlazorAdapter.Bible.SaveSummaryRequested 100%
Orihon.BlazorAdapter.Bible.UpdateBeatRowRequested 100%
Orihon.BlazorAdapter.Bible.UpdateCharacterRowRequested 100%
Orihon.BlazorAdapter.Bible.UpdateGlossaryRowRequested 100%
Orihon.BlazorAdapter.Bible.UpdateHouseRuleRowRequested 100%
Orihon.BlazorAdapter.Bible.UpdateLoreRowRequested 100%
Orihon.BlazorAdapter.BlazorAdapterAssembly 100%
Orihon.BlazorAdapter.PageWorkspace.CaptionShape 100% 93.7%
Orihon.BlazorAdapter.PageWorkspace.CreateRegionRequested 100%
Orihon.BlazorAdapter.PageWorkspace.DeletePageSummaryRequested 100%
Orihon.BlazorAdapter.PageWorkspace.DeleteRegionRequested 100%
Orihon.BlazorAdapter.PageWorkspace.LoadPageWorkspace 100%
Orihon.BlazorAdapter.PageWorkspace.MeasurePageRooms 100%
Orihon.BlazorAdapter.PageWorkspace.PageRoomsMeasured 100%
Orihon.BlazorAdapter.PageWorkspace.PageStepsLoaded 100%
Orihon.BlazorAdapter.PageWorkspace.PageSummarySaved 100%
Orihon.BlazorAdapter.PageWorkspace.PageViewport 100% 100%
Orihon.BlazorAdapter.PageWorkspace.PageWorkspaceEffects 100% 100%
Orihon.BlazorAdapter.PageWorkspace.PageWorkspaceLoaded 100%
Orihon.BlazorAdapter.PageWorkspace.PageWorkspacePage 93.2% 87.4%
Orihon.BlazorAdapter.PageWorkspace.PageWorkspaceReducers 100% 62.5%
Orihon.BlazorAdapter.PageWorkspace.PageWorkspaceState 100%
Orihon.BlazorAdapter.PageWorkspace.PageWriteFailed 100%
Orihon.BlazorAdapter.PageWorkspace.RegionColorsSampled 100%
Orihon.BlazorAdapter.PageWorkspace.RegionCreated 100%
Orihon.BlazorAdapter.PageWorkspace.RegionSaved 100%
Orihon.BlazorAdapter.PageWorkspace.ReorderRegionsRequested 100%
Orihon.BlazorAdapter.PageWorkspace.ReprocessPageRequested 100%
Orihon.BlazorAdapter.PageWorkspace.ReprocessTranslationRequested 100%
Orihon.BlazorAdapter.PageWorkspace.SampleRegionColorsRequested 100%
Orihon.BlazorAdapter.PageWorkspace.SaveContainerRequested 100%
Orihon.BlazorAdapter.PageWorkspace.SavePageSummaryRequested 100%
Orihon.BlazorAdapter.PageWorkspace.SaveRegionRequested 100%
Orihon.BlazorAdapter.PageWorkspace.SetPageMetaRequested 100%
Orihon.BlazorAdapter.Projects.CreateProjectRequested 100%
Orihon.BlazorAdapter.Projects.DecideSetupContinuation 100%
Orihon.BlazorAdapter.Projects.DeleteProjectRequested 100%
Orihon.BlazorAdapter.Projects.FinishSetupRequested 100%
Orihon.BlazorAdapter.Projects.ImportPagesRequested 100%
Orihon.BlazorAdapter.Projects.LoadWizard 100%
Orihon.BlazorAdapter.Projects.PageOrganizer 96% 95%
Orihon.BlazorAdapter.Projects.PagesImported 100%
Orihon.BlazorAdapter.Projects.ProjectDeleteFailed 100%
Orihon.BlazorAdapter.Projects.ProjectListEffects 100% 100%
Orihon.BlazorAdapter.Projects.ProjectListPage 89.7% 91.6%
Orihon.BlazorAdapter.Projects.ProjectListReducers 100%
Orihon.BlazorAdapter.Projects.ProjectListState 100%
Orihon.BlazorAdapter.Projects.ProjectsLoaded 100%
Orihon.BlazorAdapter.Projects.ProjectWizardEffects 93.8% 90%
Orihon.BlazorAdapter.Projects.ProjectWizardPage 95.3% 84.1%
Orihon.BlazorAdapter.Projects.ProjectWizardReducers 100%
Orihon.BlazorAdapter.Projects.ProjectWizardState 100%
Orihon.BlazorAdapter.Projects.SetupChat 93.5% 100%
Orihon.BlazorAdapter.Projects.SetupChatEffects 100% 100%
Orihon.BlazorAdapter.Projects.SetupChatFailed 100%
Orihon.BlazorAdapter.Projects.SetupChatReducers 100%
Orihon.BlazorAdapter.Projects.SetupChatState 100%
Orihon.BlazorAdapter.Projects.SetupChatUpdated 100%
Orihon.BlazorAdapter.Projects.StartSetupChat 100%
Orihon.BlazorAdapter.Projects.SubmitSetupAnswer 100%
Orihon.BlazorAdapter.Projects.WizardDeletePagesRequested 100%
Orihon.BlazorAdapter.Projects.WizardLoaded 100%
Orihon.BlazorAdapter.Projects.WizardMovePagesRequested 100%
Orihon.BlazorAdapter.Projects.WizardMovePagesToNewChapterRequested 100%
Orihon.BlazorAdapter.Projects.WizardReorderPagesRequested 100%
Orihon.BlazorAdapter.Projects.WizardWriteFailed 100%
Orihon.BlazorAdapter.Rulings.AcceptProposalRequested 100%
Orihon.BlazorAdapter.Rulings.AnsweredRuling 100%
Orihon.BlazorAdapter.Rulings.AnswerRulingRequested 100%
Orihon.BlazorAdapter.Rulings.DeclineProposalRequested 100%
Orihon.BlazorAdapter.Rulings.DismissRulingRequested 100%
Orihon.BlazorAdapter.Rulings.RulingBar 98.2% 95%
Orihon.BlazorAdapter.Rulings.RulingDeskBridge 94.2% 91.6%
Orihon.BlazorAdapter.Rulings.RulingsEffects 95.4% 83.3%
Orihon.BlazorAdapter.Rulings.RulingsLoaded 100%
Orihon.BlazorAdapter.Rulings.RulingsReducers 100%
Orihon.BlazorAdapter.Rulings.RulingsState 100%
Orihon.BlazorAdapter.Rulings.RulingWriteFailed 100%
Orihon.BlazorAdapter.Rulings.SaveAnswerAsHouseRuleRequested 100%
Orihon.BlazorAdapter.Runs.CancelMonitorRun 100%
Orihon.BlazorAdapter.Runs.MonitorPageRef 100%
Orihon.BlazorAdapter.Runs.MonitorRunLoaded 100%
Orihon.BlazorAdapter.Runs.RetryMonitorExecution 100%
Orihon.BlazorAdapter.Runs.RunChangedBridge 95% 92.8%
Orihon.BlazorAdapter.Runs.RunMonitor 97.9% 96.2%
Orihon.BlazorAdapter.Runs.RunMonitorEffects 100% 91.6%
Orihon.BlazorAdapter.Runs.RunMonitorReducers 100%
Orihon.BlazorAdapter.Runs.RunMonitorState 100%
Orihon.BlazorAdapter.Settings.AgentDebriefsLoaded 100%
Orihon.BlazorAdapter.Settings.AgentDebriefsLoadFailed 100%
Orihon.BlazorAdapter.Settings.AgentEffortPicked 100%
Orihon.BlazorAdapter.Settings.AgentEffortSaved 100%
Orihon.BlazorAdapter.Settings.AgentEffortSaveFailed 100%
Orihon.BlazorAdapter.Settings.AgentFeedbackLoaded 100%
Orihon.BlazorAdapter.Settings.AgentFeedbackLoadFailed 0%
Orihon.BlazorAdapter.Settings.AgentModelPicked 100%
Orihon.BlazorAdapter.Settings.AgentModelSaved 100%
Orihon.BlazorAdapter.Settings.AgentModelSaveFailed 100%
Orihon.BlazorAdapter.Settings.AgentRoutingPicked 100%
Orihon.BlazorAdapter.Settings.AgentRoutingSaved 100%
Orihon.BlazorAdapter.Settings.AgentRoutingSaveFailed 0%
Orihon.BlazorAdapter.Settings.AgentTranscriptsLoaded 100%
Orihon.BlazorAdapter.Settings.AgentTranscriptsLoadFailed 0%
Orihon.BlazorAdapter.Settings.EffortOption 100% 100%
Orihon.BlazorAdapter.Settings.FloatingPassToggled 100%
Orihon.BlazorAdapter.Settings.KeySaved 100%
Orihon.BlazorAdapter.Settings.KeySaveFailed 100%
Orihon.BlazorAdapter.Settings.ModelOptionsLoaded 100%
Orihon.BlazorAdapter.Settings.ModelOptionsUnavailable 100%
Orihon.BlazorAdapter.Settings.RoutingOption 100% 100%
Orihon.BlazorAdapter.Settings.RunConcurrencyPicked 100%
Orihon.BlazorAdapter.Settings.RunConcurrencySaveFailed 100%
Orihon.BlazorAdapter.Settings.SaveKeyRequested 100%
Orihon.BlazorAdapter.Settings.SettingsEffects 89.3% 68.4%
Orihon.BlazorAdapter.Settings.SettingsLoaded 100%
Orihon.BlazorAdapter.Settings.SettingsPage 96.5% 89%
Orihon.BlazorAdapter.Settings.SettingsReducers 93% 50%
Orihon.BlazorAdapter.Settings.SettingsState 100%
Orihon.BlazorAdapter.Settings.TranscriptFilterPicked 100%
Orihon.BlazorAdapter.Settings.TranscriptPicked 100%
Orihon.BlazorAdapter.Settings.TranscriptTextLoaded 100%
Orihon.BlazorAdapter.Settings.TranscriptTextLoadFailed 100%
Orihon.BlazorAdapter.Uploads.UploadTransfer 96.5% 100%
Orihon.BlazorAdapter.Uploads.UploadTransferProgress 100% 100%
Orihon.BlazorAdapter.Uploads.UploadTransferResult 100%
Orihon.BlazorAdapter.Workspace.CreateChapterRequested 100%
Orihon.BlazorAdapter.Workspace.DeleteChapterRequested 100%
Orihon.BlazorAdapter.Workspace.DeletePageRequested 100%
Orihon.BlazorAdapter.Workspace.DeleteSummaryRequested 100%
Orihon.BlazorAdapter.Workspace.LoadProjectWorkspace 100%
Orihon.BlazorAdapter.Workspace.MovePageRequested 100%
Orihon.BlazorAdapter.Workspace.ProjectMetadataCard 95.6% 92.8%
Orihon.BlazorAdapter.Workspace.ProjectMetadataSaved 100%
Orihon.BlazorAdapter.Workspace.ProjectWorkspaceEffects 100% 100%
Orihon.BlazorAdapter.Workspace.ProjectWorkspaceLoaded 100%
Orihon.BlazorAdapter.Workspace.ProjectWorkspacePage 95.5% 88.3%
Orihon.BlazorAdapter.Workspace.ProjectWorkspaceReducers 100% 62.5%
Orihon.BlazorAdapter.Workspace.ProjectWorkspaceState 100%
Orihon.BlazorAdapter.Workspace.RenameChapterRequested 100%
Orihon.BlazorAdapter.Workspace.ReorderChaptersRequested 100%
Orihon.BlazorAdapter.Workspace.ReorderPagesRequested 100%
Orihon.BlazorAdapter.Workspace.RunAnnotationRequested 100%
Orihon.BlazorAdapter.Workspace.RunBibleRequested 100%
Orihon.BlazorAdapter.Workspace.RunTranslationRequested 100%
Orihon.BlazorAdapter.Workspace.SaveProjectMetadataRequested 100%
Orihon.BlazorAdapter.Workspace.SaveSummaryRequested 100%
Orihon.BlazorAdapter.Workspace.SetPageKindRequested 100%
Orihon.BlazorAdapter.Workspace.SummaryDeleted 100%
Orihon.BlazorAdapter.Workspace.SummarySaved 100%
Orihon.BlazorAdapter.Workspace.WorkspaceImportRequested 100%
Orihon.BlazorAdapter.Workspace.WorkspaceWriteFailed 100%
Orihon.Domain - 100%
Name Line Branch
Orihon.Domain 100% 100%
Orihon.Domain.Agents.AgentDebrief 100% 100%
Orihon.Domain.Agents.AgentDescriptor 100%
Orihon.Domain.Agents.AgentFeedback 100% 100%
Orihon.Domain.Agents.AgentRoster 100% 100%
Orihon.Domain.Bible.Character 100% 100%
Orihon.Domain.Bible.GlossaryEntry 100% 100%
Orihon.Domain.Bible.HouseRule 100%
Orihon.Domain.Bible.LoreEntry 100% 100%
Orihon.Domain.Bible.PageSummary 100%
Orihon.Domain.Bible.StoryBeat 100%
Orihon.Domain.Bible.StoryOverview 100%
Orihon.Domain.Projects.Project 100% 100%
Orihon.Domain.Projects.ProjectProfile 100%
Orihon.Domain.Runs.Execution 100% 100%
Orihon.Domain.Runs.RegionProblem 100%
Orihon.Domain.Runs.Run 100%
Orihon.Domain.Settings.AppSetting 100%
Orihon.Domain.Text 100% 100%
Orihon.Domain.Translation.BoundingBox 100% 100%
Orihon.Domain.Translation.Chapter 100%
Orihon.Domain.Translation.InkColor 100% 100%
Orihon.Domain.Translation.NormalizedPoint 100%
Orihon.Domain.Translation.Page 100%
Orihon.Domain.Translation.Region 100% 100%
Orihon.Domain.Translation.RegionContainer 100% 100%
Orihon.Domain.Translation.RegionInk 100% 100%
Orihon.Domain.Translation.RegionProfile 100%
Orihon.Domain.Translation.RegionTypes 100% 100%
Orihon.Infrastructure - 97.1%
Name Line Branch
Orihon.Infrastructure 97.1% 78.4%
Orihon.Infrastructure.Agents.EfAgentDebriefStore 100%
Orihon.Infrastructure.Agents.EfAgentFeedbackStore 100%
Orihon.Infrastructure.Bible.EfBibleStore 95.2% 92.8%
Orihon.Infrastructure.DependencyInjection 100% 100%
Orihon.Infrastructure.Gateways.AgentToolAdapter 100%
Orihon.Infrastructure.Gateways.AgentToolAdapter`1 100% 100%
Orihon.Infrastructure.Gateways.AgentTranscript 95.6% 89%
Orihon.Infrastructure.Gateways.ContainerMask 100% 100%
Orihon.Infrastructure.Gateways.FileSystemAgentTranscriptStore 82.1% 80%
Orihon.Infrastructure.Gateways.HttpWebPageFetcher 95.1% 83.3%
Orihon.Infrastructure.Gateways.LabelFont 100% 50%
Orihon.Infrastructure.Gateways.OpenRouterLlmGateway 90.2% 82.1%
Orihon.Infrastructure.Gateways.SkiaContainerFiller 99.2% 94.7%
Orihon.Infrastructure.Gateways.SkiaImages 100% 100%
Orihon.Infrastructure.Gateways.SkiaPageImageRenderer 97.3% 90.8%
Orihon.Infrastructure.Gateways.SkiaRegionColorSampler 100% 100%
Orihon.Infrastructure.Persistence.Configurations.AgentDebriefConfiguration 100%
Orihon.Infrastructure.Persistence.Configurations.AgentFeedbackConfiguration 100%
Orihon.Infrastructure.Persistence.Configurations.AppSettingConfiguration 100%
Orihon.Infrastructure.Persistence.Configurations.ChapterConfiguration 100%
Orihon.Infrastructure.Persistence.Configurations.CharacterConfiguration 100%
Orihon.Infrastructure.Persistence.Configurations.ExecutionConfiguration 100%
Orihon.Infrastructure.Persistence.Configurations.GlossaryEntryConfiguration 100%
Orihon.Infrastructure.Persistence.Configurations.HouseRuleConfiguration 100%
Orihon.Infrastructure.Persistence.Configurations.JsonColumnMapper 100%
Orihon.Infrastructure.Persistence.Configurations.LoreEntryConfiguration 100%
Orihon.Infrastructure.Persistence.Configurations.PageConfiguration 100%
Orihon.Infrastructure.Persistence.Configurations.PageSummaryConfiguration 100%
Orihon.Infrastructure.Persistence.Configurations.ProjectConfiguration 100%
Orihon.Infrastructure.Persistence.Configurations.RegionConfiguration 100%
Orihon.Infrastructure.Persistence.Configurations.RunConfiguration 100%
Orihon.Infrastructure.Persistence.Configurations.StoryBeatConfiguration 100%
Orihon.Infrastructure.Persistence.Configurations.StoryOverviewConfiguration 100%
Orihon.Infrastructure.Persistence.Converters.UtcTicksConverter 100%
Orihon.Infrastructure.Persistence.Migrations.AddAgentDebriefs 99.5%
Orihon.Infrastructure.Persistence.Migrations.AddAgentFeedback 99.5%
Orihon.Infrastructure.Persistence.Migrations.AddAppSettings 99.3%
Orihon.Infrastructure.Persistence.Migrations.AddExecutionFeedbackRegions 99.3%
Orihon.Infrastructure.Persistence.Migrations.AddHouseRules 99.6%
Orihon.Infrastructure.Persistence.Migrations.AddProjectSourceLanguage 99.3%
Orihon.Infrastructure.Persistence.Migrations.AddRegionFitBoxDropFillHints 98.1%
Orihon.Infrastructure.Persistence.Migrations.AddRegionForm 99.4%
Orihon.Infrastructure.Persistence.Migrations.AddRegionInk 99.4%
Orihon.Infrastructure.Persistence.Migrations.AddRuns 99.1%
Orihon.Infrastructure.Persistence.Migrations.AddStoryOverview 99.5%
Orihon.Infrastructure.Persistence.Migrations.InitialTranslationDomain 97.3%
Orihon.Infrastructure.Persistence.Migrations.OrihonDbContextModelSnapshot 100%
Orihon.Infrastructure.Persistence.Migrations.RenameSourceTargetColumns 97.2%
Orihon.Infrastructure.Persistence.Migrations.ReplaceFitBoxWithContainerSeed 99.2%
Orihon.Infrastructure.Persistence.OrihonDbContext 100%
Orihon.Infrastructure.Persistence.OrihonDbContextFactory 100%
Orihon.Infrastructure.Projects.EfProjectStore 100% 100%
Orihon.Infrastructure.Projects.FileSystemPageImageStore 100% 100%
Orihon.Infrastructure.Runs.EfRunStore 98.1% 75%
Orihon.Infrastructure.Settings.EfAppSettingsStore 100% 100%
Orihon.Infrastructure.Translation.EfChapterStore 100% 100%
Orihon.Infrastructure.Translation.EfPageStore 98% 90%
Orihon.Infrastructure.Translation.EfRegionStore 100% 100%
Orihon.Infrastructure.Translation.Ordering 100% 100%
System.Text.RegularExpressions.Generated 70.6% 53.3%
System.Text.RegularExpressions.Generated.<RegexGenerator_g>F7FCA343D2B99030
A835C427B12E8B84E2A8A7283193FC51C220B5B4E80CE8D56__BlankLines_4
77.9% 76.6%
System.Text.RegularExpressions.Generated.<RegexGenerator_g>F7FCA343D2B99030
A835C427B12E8B84E2A8A7283193FC51C220B5B4E80CE8D56__BlockBreaks_1
59% 42.5%
System.Text.RegularExpressions.Generated.<RegexGenerator_g>F7FCA343D2B99030
A835C427B12E8B84E2A8A7283193FC51C220B5B4E80CE8D56__SpaceRuns_3
89.4% 75%
System.Text.RegularExpressions.Generated.<RegexGenerator_g>F7FCA343D2B99030
A835C427B12E8B84E2A8A7283193FC51C220B5B4E80CE8D56__Tags_2
83.7% 62.5%
Orihon.Kernel - 90.9%
Name Line Branch
Orihon.Kernel 90.9% 75%
Orihon.Kernel.Err`1 100%
Orihon.Kernel.Ok`1 100%
Orihon.Kernel.Result`1 88.8% 75%
Orihon.Server - 94.4%
Name Line Branch
Orihon.Server 94.4% 72%
Orihon.Server.Components.App 100%
Orihon.Server.Components.Layout.MainLayout 100%
Orihon.Server.Components.Pages.Gate 64.2% 66.6%
Orihon.Server.RunEngineBootstrap 100%
Orihon.Server.Security.AccessGate 91.8% 41.6%
Orihon.Server.Security.AccessSecret 100% 50%
Orihon.Server.VolumeStartupValidator 100% 100%
Program 95.7% 84.6%
Orihon.UseCases - 97.6%
Name Line Branch
Orihon.UseCases 97.6% 89.2%
Orihon.UseCases.Agents.AgentAttemptPreparation 100%
Orihon.UseCases.Agents.AgentAttemptSupport 98.4% 98%
Orihon.UseCases.Agents.AgentBlueprint 100%
Orihon.UseCases.Agents.AgentCapDebrief 100%
Orihon.UseCases.Agents.AgentInvocation 100%
Orihon.UseCases.Agents.AgentOutcome 100%
Orihon.UseCases.Agents.AgentTool`1 90.9% 75%
Orihon.UseCases.Agents.AgentToolImage 100%
Orihon.UseCases.Agents.AgentToolResult 100%
Orihon.UseCases.Agents.Annotation.AddFloatingRegionParams 100%
Orihon.UseCases.Agents.Annotation.AddFloatingRegionTool 96.8% 83.3%
Orihon.UseCases.Agents.Annotation.AddRegionParams 100%
Orihon.UseCases.Agents.Annotation.AddRegionTool 95.4% 75%
Orihon.UseCases.Agents.Annotation.AnnotationBlueprints 100%
Orihon.UseCases.Agents.Annotation.AnnotationStage 100% 88.2%
Orihon.UseCases.Agents.Annotation.BboxCreationExecutor 94.1% 50%
Orihon.UseCases.Agents.Annotation.BboxRefinementExecutor 96.5% 90%
Orihon.UseCases.Agents.Annotation.BoundBoxParams 100%
Orihon.UseCases.Agents.Annotation.BoundContactSheetTool 91.3% 75%
Orihon.UseCases.Agents.Annotation.BoundCropParams 100%
Orihon.UseCases.Agents.Annotation.BoundCropTool 100%
Orihon.UseCases.Agents.Annotation.BoundViewPageTool 94.2% 88.8%
Orihon.UseCases.Agents.Annotation.BoundViewParams 100%
Orihon.UseCases.Agents.Annotation.ClassifyRegionParams 100%
Orihon.UseCases.Agents.Annotation.ClassifyRegionTool 96.8% 74.1%
Orihon.UseCases.Agents.Annotation.ColorReading 100% 100%
Orihon.UseCases.Agents.Annotation.DeleteBoundRegionTool 100% 100%
Orihon.UseCases.Agents.Annotation.DeleteRegionParams 100%
Orihon.UseCases.Agents.Annotation.DeleteRegionTool 100% 100%
Orihon.UseCases.Agents.Annotation.FindGlossaryParams 100%
Orihon.UseCases.Agents.Annotation.FindGlossaryTool 88.2% 62.5%
Orihon.UseCases.Agents.Annotation.FloatingCreationExecutor 89.4% 50%
Orihon.UseCases.Agents.Annotation.FloatingQaExecutor 93.9% 83.3%
Orihon.UseCases.Agents.Annotation.FloatingTranscriptionExecutor 95.6% 75%
Orihon.UseCases.Agents.Annotation.ListRegionsTool 94.8% 73.6%
Orihon.UseCases.Agents.Annotation.MoveResizeBoundTool 90.9% 50%
Orihon.UseCases.Agents.Annotation.MoveResizeRegionParams 100%
Orihon.UseCases.Agents.Annotation.MoveResizeRegionTool 95.8% 83.3%
Orihon.UseCases.Agents.Annotation.NoteRegionParams 100%
Orihon.UseCases.Agents.Annotation.NoteRegionTool 100% 100%
Orihon.UseCases.Agents.Annotation.PageQaExecutor 94.4% 81.8%
Orihon.UseCases.Agents.Annotation.QaReportSink 100% 100%
Orihon.UseCases.Agents.Annotation.RegionAuthoringAccess 90.9% 72.7%
Orihon.UseCases.Agents.Annotation.RegionBriefing 100% 100%
Orihon.UseCases.Agents.Annotation.RegionCropParams 100%
Orihon.UseCases.Agents.Annotation.RegionCropTool 100%
Orihon.UseCases.Agents.Annotation.RegionProblemParams 100%
Orihon.UseCases.Agents.Annotation.RejectRegionParams 100%
Orihon.UseCases.Agents.Annotation.RejectRegionTool 100% 50%
Orihon.UseCases.Agents.Annotation.ReorderRegionParams 100%
Orihon.UseCases.Agents.Annotation.ReorderRegionTool 88% 60%
Orihon.UseCases.Agents.Annotation.ReportQaParams 100%
Orihon.UseCases.Agents.Annotation.ReportQaTool 97.7% 90%
Orihon.UseCases.Agents.Annotation.SampleColorsParams 100%
Orihon.UseCases.Agents.Annotation.SampleColorsTool 100% 100%
Orihon.UseCases.Agents.Annotation.SampleRegionColorsTool 95.2% 75%
Orihon.UseCases.Agents.Annotation.SetInkParams 100%
Orihon.UseCases.Agents.Annotation.SetInkTool 100% 100%
Orihon.UseCases.Agents.Annotation.SetPageMetaParams 100%
Orihon.UseCases.Agents.Annotation.SetPageMetaTool 100% 75%
Orihon.UseCases.Agents.Annotation.SetRegionInkParams 100%
Orihon.UseCases.Agents.Annotation.SetRegionInkTool 100% 100%
Orihon.UseCases.Agents.Annotation.SetTranscriptionParams 100%
Orihon.UseCases.Agents.Annotation.SetTranscriptionTool 100% 100%
Orihon.UseCases.Agents.Annotation.TranscriptionExecutor 96.4% 90%
Orihon.UseCases.Agents.AssistantSpoke 100%
Orihon.UseCases.Agents.BibleBuilding.BibleBuildingBlueprint 100%
Orihon.UseCases.Agents.BibleBuilding.BibleBuildingExecutor 96.9% 75%
Orihon.UseCases.Agents.BibleBuilding.GetRegionParams 100%
Orihon.UseCases.Agents.BibleBuilding.GetRegionTool 84.6% 72.2%
Orihon.UseCases.Agents.BibleBuilding.ListProjectRegionsTool 86.3% 90%
Orihon.UseCases.Agents.BibleBuilding.ListRegionsParams 100%
Orihon.UseCases.Agents.Inspection.PageImageAccess 95.5% 83.9%
Orihon.UseCases.Agents.Inspection.ViewAccount 100% 100%
Orihon.UseCases.Agents.ReportFrictionParams 100%
Orihon.UseCases.Agents.ReportFrictionTool 100% 92.8%
Orihon.UseCases.Agents.ResearchSetup.AddGlossaryParams 100%
Orihon.UseCases.Agents.ResearchSetup.AddGlossaryTool 100% 100%
Orihon.UseCases.Agents.ResearchSetup.AddStoryBeatParams 100%
Orihon.UseCases.Agents.ResearchSetup.AddStoryBeatTool 100% 50%
Orihon.UseCases.Agents.ResearchSetup.AskUserParams 100%
Orihon.UseCases.Agents.ResearchSetup.AskUserTool 100% 100%
Orihon.UseCases.Agents.ResearchSetup.FetchUrlParams 100%
Orihon.UseCases.Agents.ResearchSetup.FetchUrlTool 100% 100%
Orihon.UseCases.Agents.ResearchSetup.ListBibleTool 89.4% 100%
Orihon.UseCases.Agents.ResearchSetup.ListPagesTool 97% 83.3%
Orihon.UseCases.Agents.ResearchSetup.LocatedPage 100%
Orihon.UseCases.Agents.ResearchSetup.PageByNumber 95% 91.6%
Orihon.UseCases.Agents.ResearchSetup.ResearchSetupBlueprint 100%
Orihon.UseCases.Agents.ResearchSetup.SetPageMetaParams 100%
Orihon.UseCases.Agents.ResearchSetup.SetPageMetaTool 95.2% 90%
Orihon.UseCases.Agents.ResearchSetup.SetPageSummaryParams 100%
Orihon.UseCases.Agents.ResearchSetup.SetPageSummaryTool 100% 75%
Orihon.UseCases.Agents.ResearchSetup.SetProjectMetadataParams 100%
Orihon.UseCases.Agents.ResearchSetup.SetProjectMetadataTool 96.5% 95.8%
Orihon.UseCases.Agents.ResearchSetup.SetStoryOverviewParams 100%
Orihon.UseCases.Agents.ResearchSetup.SetStoryOverviewTool 100% 100%
Orihon.UseCases.Agents.ResearchSetup.UpsertCharacterParams 100%
Orihon.UseCases.Agents.ResearchSetup.UpsertCharacterTool 92.3% 71.4%
Orihon.UseCases.Agents.ResearchSetup.UpsertLoreParams 100%
Orihon.UseCases.Agents.ResearchSetup.UpsertLoreTool 92.3% 71.4%
Orihon.UseCases.Agents.ResearchSetup.ViewPageParams 100%
Orihon.UseCases.Agents.ResearchSetup.ViewPageTool 100% 100%
Orihon.UseCases.Agents.RoundStarted 100%
Orihon.UseCases.Agents.Rulings.AnswerRuling 100%
Orihon.UseCases.Agents.Rulings.DismissRuling 100%
Orihon.UseCases.Agents.Rulings.HouseRuleProposal 100%
Orihon.UseCases.Agents.Rulings.ListPendingRulings 100%
Orihon.UseCases.Agents.Rulings.ListRuleProposals 100%
Orihon.UseCases.Agents.Rulings.PendingRuling 100%
Orihon.UseCases.Agents.Rulings.ProposeHouseRuleParams 100%
Orihon.UseCases.Agents.Rulings.ProposeHouseRuleTool 100% 100%
Orihon.UseCases.Agents.Rulings.RequestRulingParams 100%
Orihon.UseCases.Agents.Rulings.RequestRulingTool 100% 81.2%
Orihon.UseCases.Agents.Rulings.RulingAnswer 100%
Orihon.UseCases.Agents.Rulings.RulingDesk 97.8% 80%
Orihon.UseCases.Agents.Rulings.RulingOption 100%
Orihon.UseCases.Agents.Rulings.RulingOptionParams 100%
Orihon.UseCases.Agents.Rulings.SettledRuling 100%
Orihon.UseCases.Agents.Rulings.WithdrawRuleProposal 100%
Orihon.UseCases.Agents.Setup.ResearchSetupExecutor 98.5% 92.8%
Orihon.UseCases.Agents.Setup.SetupChatEntry 100%
Orihon.UseCases.Agents.Setup.SetupConversation 100% 87.5%
Orihon.UseCases.Agents.Setup.SetupConversationRegistry 100%
Orihon.UseCases.Agents.ToolCalled 100%
Orihon.UseCases.Agents.ToolCompleted 100%
Orihon.UseCases.Agents.TranscriptLabel 100% 100%
Orihon.UseCases.Agents.Translation.GetPageSummaryParams 100%
Orihon.UseCases.Agents.Translation.GetPageSummaryTool 80% 66.6%
Orihon.UseCases.Agents.Translation.SetTranslationParams 100%
Orihon.UseCases.Agents.Translation.SetTranslationTool 88.5% 78.5%
Orihon.UseCases.Agents.Translation.TranslationBlueprint 100%
Orihon.UseCases.Agents.Translation.TranslationExecutor 93.8% 71.4%
Orihon.UseCases.Agents.Translation.UpdateGlossaryEnParams 100%
Orihon.UseCases.Agents.Translation.UpdateGlossaryEnTool 82.6% 62.5%
Orihon.UseCases.Bible.AddCharacter 100% 100%
Orihon.UseCases.Bible.AddGlossaryEntry 100% 100%
Orihon.UseCases.Bible.AddHouseRule 100% 100%
Orihon.UseCases.Bible.AddLoreEntry 100% 100%
Orihon.UseCases.Bible.AddStoryBeat 100% 100%
Orihon.UseCases.Bible.BibleDto 100%
Orihon.UseCases.Bible.CharacterDto 100%
Orihon.UseCases.Bible.DeleteCharacter 100% 100%
Orihon.UseCases.Bible.DeleteGlossaryEntry 100% 100%
Orihon.UseCases.Bible.DeleteHouseRule 100% 100%
Orihon.UseCases.Bible.DeleteLoreEntry 100% 100%
Orihon.UseCases.Bible.DeletePageSummary 100% 100%
Orihon.UseCases.Bible.DeleteStoryBeat 100% 100%
Orihon.UseCases.Bible.GetBible 100% 100%
Orihon.UseCases.Bible.GlossaryEntryDto 100%
Orihon.UseCases.Bible.HouseRuleDto 100%
Orihon.UseCases.Bible.LoreEntryDto 100%
Orihon.UseCases.Bible.PageSummaryDto 100%
Orihon.UseCases.Bible.ReorderStoryBeats 100%
Orihon.UseCases.Bible.SetPageSummary 100% 100%
Orihon.UseCases.Bible.SetStoryOverview 100% 100%
Orihon.UseCases.Bible.StoryBeatDto 100%
Orihon.UseCases.Bible.StoryOverviewDto 100%
Orihon.UseCases.Bible.UpdateCharacter 100% 100%
Orihon.UseCases.Bible.UpdateGlossaryEntry 100% 100%
Orihon.UseCases.Bible.UpdateHouseRule 100% 100%
Orihon.UseCases.Bible.UpdateLoreEntry 100% 100%
Orihon.UseCases.Bible.UpdateStoryBeat 100% 100%
Orihon.UseCases.Chapters.ChapterDto 100%
Orihon.UseCases.Chapters.CreateChapter 100% 100%
Orihon.UseCases.Chapters.DeleteChapter 100% 100%
Orihon.UseCases.Chapters.RenameChapter 100% 100%
Orihon.UseCases.Chapters.ReorderChapters 100%
Orihon.UseCases.Debriefs.AgentDebriefDto 90.9%
Orihon.UseCases.Debriefs.AgentFeedbackDto 83.3%
Orihon.UseCases.Debriefs.ClearAgentDebriefs 100%
Orihon.UseCases.Debriefs.ClearAgentFeedback 100%
Orihon.UseCases.Debriefs.ListAgentDebriefs 100% 100%
Orihon.UseCases.Debriefs.ListAgentFeedback 100% 75%
Orihon.UseCases.DependencyInjection 100%
Orihon.UseCases.Diagnostics.SeedDevData 99.2% 84.3%
Orihon.UseCases.Gateways.ColorShare 100%
Orihon.UseCases.Gateways.ContainerFit 75% 100%
Orihon.UseCases.Gateways.ContainerMaskBits 100% 100%
Orihon.UseCases.Gateways.ContainerRequest 100%
Orihon.UseCases.Gateways.GridAxis 100%
Orihon.UseCases.Gateways.GridSteps 100%
Orihon.UseCases.Gateways.LabeledBox 100%
Orihon.UseCases.Gateways.LlmKeyInfo 100%
Orihon.UseCases.Gateways.LlmModel 100%
Orihon.UseCases.Gateways.LlmReasoning 100% 100%
Orihon.UseCases.Gateways.PixelWindow 100%
Orihon.UseCases.Gateways.RenderedView 100%
Orihon.UseCases.Gateways.StoredTranscript 100%
Orihon.UseCases.Gateways.TranscriptHeader 97.3% 90.9%
Orihon.UseCases.Gateways.TranscriptSummary 100%
Orihon.UseCases.NextOrder 100%
Orihon.UseCases.Pages.DeletePage 100% 100%
Orihon.UseCases.Pages.DeletePages 100% 100%
Orihon.UseCases.Pages.GetPage 100% 100%
Orihon.UseCases.Pages.GetPageSteps 100% 100%
Orihon.UseCases.Pages.GetProjectWorkspace 100% 100%
Orihon.UseCases.Pages.ImportPages 100% 100%
Orihon.UseCases.Pages.ImportPagesResult 100%
Orihon.UseCases.Pages.MarkPageAnnotated 100% 100%
Orihon.UseCases.Pages.MovePage 100% 92.8%
Orihon.UseCases.Pages.MovePages 100% 100%
Orihon.UseCases.Pages.PageDetailDto 100%
Orihon.UseCases.Pages.PageDto 100%
Orihon.UseCases.Pages.PageStepsDto 100%
Orihon.UseCases.Pages.PageUpload 100%
Orihon.UseCases.Pages.ProjectWorkspaceDto 100%
Orihon.UseCases.Pages.ReorderPages 100%
Orihon.UseCases.Pages.SetPageMeta 100% 100%
Orihon.UseCases.Pages.WorkspaceChapterDto 100%
Orihon.UseCases.Projects.CompleteProjectSetup 100% 93.7%
Orihon.UseCases.Projects.CreateProject 100% 100%
Orihon.UseCases.Projects.DeleteProject 100% 100%
Orihon.UseCases.Projects.GetProject 100% 100%
Orihon.UseCases.Projects.ListProjects 100%
Orihon.UseCases.Projects.ProjectDto 96.1%
Orihon.UseCases.Projects.StartAnnotationRun 96.4% 92.8%
Orihon.UseCases.Projects.StartBibleRun 90.9% 83.3%
Orihon.UseCases.Projects.StartSetupRun 100% 100%
Orihon.UseCases.Projects.StartTranslationRun 90.9% 83.3%
Orihon.UseCases.Projects.StoredPageImage 100%
Orihon.UseCases.Projects.UpdateProjectMetadata 100% 100%
Orihon.UseCases.Regions.CreateRegion 100% 100%
Orihon.UseCases.Regions.DeleteRegion 100% 100%
Orihon.UseCases.Regions.MeasureRegionRooms 95.8% 75%
Orihon.UseCases.Regions.RegionDto 97.2%
Orihon.UseCases.Regions.ReorderRegions 100%
Orihon.UseCases.Regions.SampleRegionColors 100% 50%
Orihon.UseCases.Regions.UpdateRegion 100% 100%
Orihon.UseCases.Runs.AnnotationPipeline 100% 100%
Orihon.UseCases.Runs.AttemptLedger 100% 100%
Orihon.UseCases.Runs.ExecutionDto 93.3%
Orihon.UseCases.Runs.ExecutionProgress 100%
Orihon.UseCases.Runs.ExecutionProgressRegistry 100% 100%
Orihon.UseCases.Runs.ExecutionPulseRelay 100% 100%
Orihon.UseCases.Runs.PlannedExecution 100%
Orihon.UseCases.Runs.PulseTarget 100%
Orihon.UseCases.Runs.ReprocessPage 100% 94.4%
Orihon.UseCases.Runs.ReprocessTranslation 94.1% 92.8%
Orihon.UseCases.Runs.RunDto 93.3% 90%
Orihon.UseCases.Runs.RunEngine 95% 94.4%
Orihon.UseCases.Runs.RunEngineOptions 100% 100%
Orihon.UseCases.Runs.StageContext 100% 50%
Orihon.UseCases.Runs.StageHaltedException 100%
Orihon.UseCases.Runs.WorkStates 100% 83.3%
Orihon.UseCases.Settings.AgentSettingDto 100% 100%
Orihon.UseCases.Settings.ConcurrencySetting 100% 100%
Orihon.UseCases.Settings.EffortSetting 100% 100%
Orihon.UseCases.Settings.GetSettings 100% 100%
Orihon.UseCases.Settings.ListModelOptions 100% 100%
Orihon.UseCases.Settings.RoutingSetting 100% 100%
Orihon.UseCases.Settings.SaveAgentEffort 100% 100%
Orihon.UseCases.Settings.SaveAgentModel 100% 100%
Orihon.UseCases.Settings.SaveAgentRouting 100% 100%
Orihon.UseCases.Settings.SaveFloatingPass 100% 100%
Orihon.UseCases.Settings.SaveOpenRouterKey 100% 100%
Orihon.UseCases.Settings.SaveRunConcurrency 100% 100%
Orihon.UseCases.Settings.SettingKeys 100% 100%
Orihon.UseCases.Settings.SettingsDto 100% 100%
Orihon.UseCases.Transcripts.AgentTranscriptDto 100%
Orihon.UseCases.Transcripts.AgentTranscriptListDto 100%
Orihon.UseCases.Transcripts.AgentTranscriptTextDto 100%
Orihon.UseCases.Transcripts.ListAgentTranscripts 100% 100%
Orihon.UseCases.Transcripts.ReadAgentTranscript 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:** `f54ade6` · **Generated:** 2026-09-20 20:51:28 UTC · **Revision:** #1 # Summary <details open><summary>Summary</summary> ||| |:---|:---| | Generated on: | 09/20/2026 - 20:51:28 | | Coverage date: | 09/20/2026 - 20:50:57 - 09/20/2026 - 20:51:25 | | Parser: | MultiReport (4x Cobertura) | | Assemblies: | 6 | | Classes: | 537 | | Files: | 248 | | **Line coverage:** | 97% (20485 of 21099) | | Covered lines: | 20485 | | Uncovered lines: | 614 | | Coverable lines: | 21099 | | Total lines: | 37331 | | **Branch coverage:** | 85.9% (3830 of 4458) | | Covered branches: | 3830 | | Total branches: | 4458 | | **Method coverage:** | [Feature is only available for sponsors](https://reportgenerator.io/pro) | </details> ## Coverage <details><summary>Orihon.BlazorAdapter - 95.4%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Orihon.BlazorAdapter**|**95.4%**|**88.3%**| |Orihon.BlazorAdapter.Bible.AddBeatRowRequested|100%|| |Orihon.BlazorAdapter.Bible.AddCharacterRowRequested|100%|| |Orihon.BlazorAdapter.Bible.AddGlossaryRowRequested|100%|| |Orihon.BlazorAdapter.Bible.AddHouseRuleRowRequested|100%|| |Orihon.BlazorAdapter.Bible.AddLoreRowRequested|100%|| |Orihon.BlazorAdapter.Bible.BibleEffects|89.8%|76.9%| |Orihon.BlazorAdapter.Bible.BibleLoaded|100%|| |Orihon.BlazorAdapter.Bible.BiblePage|92.3%|80.5%| |Orihon.BlazorAdapter.Bible.BibleReducers|90.6%|| |Orihon.BlazorAdapter.Bible.BibleState|100%|| |Orihon.BlazorAdapter.Bible.BibleWriteFailed|100%|| |Orihon.BlazorAdapter.Bible.DeleteBeatRowRequested|100%|| |Orihon.BlazorAdapter.Bible.DeleteCharacterRowRequested|100%|| |Orihon.BlazorAdapter.Bible.DeleteGlossaryRowRequested|100%|| |Orihon.BlazorAdapter.Bible.DeleteHouseRuleRowRequested|0%|| |Orihon.BlazorAdapter.Bible.DeleteLoreRowRequested|0%|| |Orihon.BlazorAdapter.Bible.LoadBible|100%|| |Orihon.BlazorAdapter.Bible.ReorderBeatsRequested|0%|| |Orihon.BlazorAdapter.Bible.SaveOverviewRequested|100%|| |Orihon.BlazorAdapter.Bible.SaveSummaryRequested|100%|| |Orihon.BlazorAdapter.Bible.UpdateBeatRowRequested|100%|| |Orihon.BlazorAdapter.Bible.UpdateCharacterRowRequested|100%|| |Orihon.BlazorAdapter.Bible.UpdateGlossaryRowRequested|100%|| |Orihon.BlazorAdapter.Bible.UpdateHouseRuleRowRequested|100%|| |Orihon.BlazorAdapter.Bible.UpdateLoreRowRequested|100%|| |Orihon.BlazorAdapter.BlazorAdapterAssembly|100%|| |Orihon.BlazorAdapter.PageWorkspace.CaptionShape|100%|93.7%| |Orihon.BlazorAdapter.PageWorkspace.CreateRegionRequested|100%|| |Orihon.BlazorAdapter.PageWorkspace.DeletePageSummaryRequested|100%|| |Orihon.BlazorAdapter.PageWorkspace.DeleteRegionRequested|100%|| |Orihon.BlazorAdapter.PageWorkspace.LoadPageWorkspace|100%|| |Orihon.BlazorAdapter.PageWorkspace.MeasurePageRooms|100%|| |Orihon.BlazorAdapter.PageWorkspace.PageRoomsMeasured|100%|| |Orihon.BlazorAdapter.PageWorkspace.PageStepsLoaded|100%|| |Orihon.BlazorAdapter.PageWorkspace.PageSummarySaved|100%|| |Orihon.BlazorAdapter.PageWorkspace.PageViewport|100%|100%| |Orihon.BlazorAdapter.PageWorkspace.PageWorkspaceEffects|100%|100%| |Orihon.BlazorAdapter.PageWorkspace.PageWorkspaceLoaded|100%|| |Orihon.BlazorAdapter.PageWorkspace.PageWorkspacePage|93.2%|87.4%| |Orihon.BlazorAdapter.PageWorkspace.PageWorkspaceReducers|100%|62.5%| |Orihon.BlazorAdapter.PageWorkspace.PageWorkspaceState|100%|| |Orihon.BlazorAdapter.PageWorkspace.PageWriteFailed|100%|| |Orihon.BlazorAdapter.PageWorkspace.RegionColorsSampled|100%|| |Orihon.BlazorAdapter.PageWorkspace.RegionCreated|100%|| |Orihon.BlazorAdapter.PageWorkspace.RegionSaved|100%|| |Orihon.BlazorAdapter.PageWorkspace.ReorderRegionsRequested|100%|| |Orihon.BlazorAdapter.PageWorkspace.ReprocessPageRequested|100%|| |Orihon.BlazorAdapter.PageWorkspace.ReprocessTranslationRequested|100%|| |Orihon.BlazorAdapter.PageWorkspace.SampleRegionColorsRequested|100%|| |Orihon.BlazorAdapter.PageWorkspace.SaveContainerRequested|100%|| |Orihon.BlazorAdapter.PageWorkspace.SavePageSummaryRequested|100%|| |Orihon.BlazorAdapter.PageWorkspace.SaveRegionRequested|100%|| |Orihon.BlazorAdapter.PageWorkspace.SetPageMetaRequested|100%|| |Orihon.BlazorAdapter.Projects.CreateProjectRequested|100%|| |Orihon.BlazorAdapter.Projects.DecideSetupContinuation|100%|| |Orihon.BlazorAdapter.Projects.DeleteProjectRequested|100%|| |Orihon.BlazorAdapter.Projects.FinishSetupRequested|100%|| |Orihon.BlazorAdapter.Projects.ImportPagesRequested|100%|| |Orihon.BlazorAdapter.Projects.LoadWizard|100%|| |Orihon.BlazorAdapter.Projects.PageOrganizer|96%|95%| |Orihon.BlazorAdapter.Projects.PagesImported|100%|| |Orihon.BlazorAdapter.Projects.ProjectDeleteFailed|100%|| |Orihon.BlazorAdapter.Projects.ProjectListEffects|100%|100%| |Orihon.BlazorAdapter.Projects.ProjectListPage|89.7%|91.6%| |Orihon.BlazorAdapter.Projects.ProjectListReducers|100%|| |Orihon.BlazorAdapter.Projects.ProjectListState|100%|| |Orihon.BlazorAdapter.Projects.ProjectsLoaded|100%|| |Orihon.BlazorAdapter.Projects.ProjectWizardEffects|93.8%|90%| |Orihon.BlazorAdapter.Projects.ProjectWizardPage|95.3%|84.1%| |Orihon.BlazorAdapter.Projects.ProjectWizardReducers|100%|| |Orihon.BlazorAdapter.Projects.ProjectWizardState|100%|| |Orihon.BlazorAdapter.Projects.SetupChat|93.5%|100%| |Orihon.BlazorAdapter.Projects.SetupChatEffects|100%|100%| |Orihon.BlazorAdapter.Projects.SetupChatFailed|100%|| |Orihon.BlazorAdapter.Projects.SetupChatReducers|100%|| |Orihon.BlazorAdapter.Projects.SetupChatState|100%|| |Orihon.BlazorAdapter.Projects.SetupChatUpdated|100%|| |Orihon.BlazorAdapter.Projects.StartSetupChat|100%|| |Orihon.BlazorAdapter.Projects.SubmitSetupAnswer|100%|| |Orihon.BlazorAdapter.Projects.WizardDeletePagesRequested|100%|| |Orihon.BlazorAdapter.Projects.WizardLoaded|100%|| |Orihon.BlazorAdapter.Projects.WizardMovePagesRequested|100%|| |Orihon.BlazorAdapter.Projects.WizardMovePagesToNewChapterRequested|100%|| |Orihon.BlazorAdapter.Projects.WizardReorderPagesRequested|100%|| |Orihon.BlazorAdapter.Projects.WizardWriteFailed|100%|| |Orihon.BlazorAdapter.Rulings.AcceptProposalRequested|100%|| |Orihon.BlazorAdapter.Rulings.AnsweredRuling|100%|| |Orihon.BlazorAdapter.Rulings.AnswerRulingRequested|100%|| |Orihon.BlazorAdapter.Rulings.DeclineProposalRequested|100%|| |Orihon.BlazorAdapter.Rulings.DismissRulingRequested|100%|| |Orihon.BlazorAdapter.Rulings.RulingBar|98.2%|95%| |Orihon.BlazorAdapter.Rulings.RulingDeskBridge|94.2%|91.6%| |Orihon.BlazorAdapter.Rulings.RulingsEffects|95.4%|83.3%| |Orihon.BlazorAdapter.Rulings.RulingsLoaded|100%|| |Orihon.BlazorAdapter.Rulings.RulingsReducers|100%|| |Orihon.BlazorAdapter.Rulings.RulingsState|100%|| |Orihon.BlazorAdapter.Rulings.RulingWriteFailed|100%|| |Orihon.BlazorAdapter.Rulings.SaveAnswerAsHouseRuleRequested|100%|| |Orihon.BlazorAdapter.Runs.CancelMonitorRun|100%|| |Orihon.BlazorAdapter.Runs.MonitorPageRef|100%|| |Orihon.BlazorAdapter.Runs.MonitorRunLoaded|100%|| |Orihon.BlazorAdapter.Runs.RetryMonitorExecution|100%|| |Orihon.BlazorAdapter.Runs.RunChangedBridge|95%|92.8%| |Orihon.BlazorAdapter.Runs.RunMonitor|97.9%|96.2%| |Orihon.BlazorAdapter.Runs.RunMonitorEffects|100%|91.6%| |Orihon.BlazorAdapter.Runs.RunMonitorReducers|100%|| |Orihon.BlazorAdapter.Runs.RunMonitorState|100%|| |Orihon.BlazorAdapter.Settings.AgentDebriefsLoaded|100%|| |Orihon.BlazorAdapter.Settings.AgentDebriefsLoadFailed|100%|| |Orihon.BlazorAdapter.Settings.AgentEffortPicked|100%|| |Orihon.BlazorAdapter.Settings.AgentEffortSaved|100%|| |Orihon.BlazorAdapter.Settings.AgentEffortSaveFailed|100%|| |Orihon.BlazorAdapter.Settings.AgentFeedbackLoaded|100%|| |Orihon.BlazorAdapter.Settings.AgentFeedbackLoadFailed|0%|| |Orihon.BlazorAdapter.Settings.AgentModelPicked|100%|| |Orihon.BlazorAdapter.Settings.AgentModelSaved|100%|| |Orihon.BlazorAdapter.Settings.AgentModelSaveFailed|100%|| |Orihon.BlazorAdapter.Settings.AgentRoutingPicked|100%|| |Orihon.BlazorAdapter.Settings.AgentRoutingSaved|100%|| |Orihon.BlazorAdapter.Settings.AgentRoutingSaveFailed|0%|| |Orihon.BlazorAdapter.Settings.AgentTranscriptsLoaded|100%|| |Orihon.BlazorAdapter.Settings.AgentTranscriptsLoadFailed|0%|| |Orihon.BlazorAdapter.Settings.EffortOption|100%|100%| |Orihon.BlazorAdapter.Settings.FloatingPassToggled|100%|| |Orihon.BlazorAdapter.Settings.KeySaved|100%|| |Orihon.BlazorAdapter.Settings.KeySaveFailed|100%|| |Orihon.BlazorAdapter.Settings.ModelOptionsLoaded|100%|| |Orihon.BlazorAdapter.Settings.ModelOptionsUnavailable|100%|| |Orihon.BlazorAdapter.Settings.RoutingOption|100%|100%| |Orihon.BlazorAdapter.Settings.RunConcurrencyPicked|100%|| |Orihon.BlazorAdapter.Settings.RunConcurrencySaveFailed|100%|| |Orihon.BlazorAdapter.Settings.SaveKeyRequested|100%|| |Orihon.BlazorAdapter.Settings.SettingsEffects|89.3%|68.4%| |Orihon.BlazorAdapter.Settings.SettingsLoaded|100%|| |Orihon.BlazorAdapter.Settings.SettingsPage|96.5%|89%| |Orihon.BlazorAdapter.Settings.SettingsReducers|93%|50%| |Orihon.BlazorAdapter.Settings.SettingsState|100%|| |Orihon.BlazorAdapter.Settings.TranscriptFilterPicked|100%|| |Orihon.BlazorAdapter.Settings.TranscriptPicked|100%|| |Orihon.BlazorAdapter.Settings.TranscriptTextLoaded|100%|| |Orihon.BlazorAdapter.Settings.TranscriptTextLoadFailed|100%|| |Orihon.BlazorAdapter.Uploads.UploadTransfer|96.5%|100%| |Orihon.BlazorAdapter.Uploads.UploadTransferProgress|100%|100%| |Orihon.BlazorAdapter.Uploads.UploadTransferResult|100%|| |Orihon.BlazorAdapter.Workspace.CreateChapterRequested|100%|| |Orihon.BlazorAdapter.Workspace.DeleteChapterRequested|100%|| |Orihon.BlazorAdapter.Workspace.DeletePageRequested|100%|| |Orihon.BlazorAdapter.Workspace.DeleteSummaryRequested|100%|| |Orihon.BlazorAdapter.Workspace.LoadProjectWorkspace|100%|| |Orihon.BlazorAdapter.Workspace.MovePageRequested|100%|| |Orihon.BlazorAdapter.Workspace.ProjectMetadataCard|95.6%|92.8%| |Orihon.BlazorAdapter.Workspace.ProjectMetadataSaved|100%|| |Orihon.BlazorAdapter.Workspace.ProjectWorkspaceEffects|100%|100%| |Orihon.BlazorAdapter.Workspace.ProjectWorkspaceLoaded|100%|| |Orihon.BlazorAdapter.Workspace.ProjectWorkspacePage|95.5%|88.3%| |Orihon.BlazorAdapter.Workspace.ProjectWorkspaceReducers|100%|62.5%| |Orihon.BlazorAdapter.Workspace.ProjectWorkspaceState|100%|| |Orihon.BlazorAdapter.Workspace.RenameChapterRequested|100%|| |Orihon.BlazorAdapter.Workspace.ReorderChaptersRequested|100%|| |Orihon.BlazorAdapter.Workspace.ReorderPagesRequested|100%|| |Orihon.BlazorAdapter.Workspace.RunAnnotationRequested|100%|| |Orihon.BlazorAdapter.Workspace.RunBibleRequested|100%|| |Orihon.BlazorAdapter.Workspace.RunTranslationRequested|100%|| |Orihon.BlazorAdapter.Workspace.SaveProjectMetadataRequested|100%|| |Orihon.BlazorAdapter.Workspace.SaveSummaryRequested|100%|| |Orihon.BlazorAdapter.Workspace.SetPageKindRequested|100%|| |Orihon.BlazorAdapter.Workspace.SummaryDeleted|100%|| |Orihon.BlazorAdapter.Workspace.SummarySaved|100%|| |Orihon.BlazorAdapter.Workspace.WorkspaceImportRequested|100%|| |Orihon.BlazorAdapter.Workspace.WorkspaceWriteFailed|100%|| </details> <details><summary>Orihon.Domain - 100%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Orihon.Domain**|**100%**|**100%**| |Orihon.Domain.Agents.AgentDebrief|100%|100%| |Orihon.Domain.Agents.AgentDescriptor|100%|| |Orihon.Domain.Agents.AgentFeedback|100%|100%| |Orihon.Domain.Agents.AgentRoster|100%|100%| |Orihon.Domain.Bible.Character|100%|100%| |Orihon.Domain.Bible.GlossaryEntry|100%|100%| |Orihon.Domain.Bible.HouseRule|100%|| |Orihon.Domain.Bible.LoreEntry|100%|100%| |Orihon.Domain.Bible.PageSummary|100%|| |Orihon.Domain.Bible.StoryBeat|100%|| |Orihon.Domain.Bible.StoryOverview|100%|| |Orihon.Domain.Projects.Project|100%|100%| |Orihon.Domain.Projects.ProjectProfile|100%|| |Orihon.Domain.Runs.Execution|100%|100%| |Orihon.Domain.Runs.RegionProblem|100%|| |Orihon.Domain.Runs.Run|100%|| |Orihon.Domain.Settings.AppSetting|100%|| |Orihon.Domain.Text|100%|100%| |Orihon.Domain.Translation.BoundingBox|100%|100%| |Orihon.Domain.Translation.Chapter|100%|| |Orihon.Domain.Translation.InkColor|100%|100%| |Orihon.Domain.Translation.NormalizedPoint|100%|| |Orihon.Domain.Translation.Page|100%|| |Orihon.Domain.Translation.Region|100%|100%| |Orihon.Domain.Translation.RegionContainer|100%|100%| |Orihon.Domain.Translation.RegionInk|100%|100%| |Orihon.Domain.Translation.RegionProfile|100%|| |Orihon.Domain.Translation.RegionTypes|100%|100%| </details> <details><summary>Orihon.Infrastructure - 97.1%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Orihon.Infrastructure**|**97.1%**|**78.4%**| |Orihon.Infrastructure.Agents.EfAgentDebriefStore|100%|| |Orihon.Infrastructure.Agents.EfAgentFeedbackStore|100%|| |Orihon.Infrastructure.Bible.EfBibleStore|95.2%|92.8%| |Orihon.Infrastructure.DependencyInjection|100%|100%| |Orihon.Infrastructure.Gateways.AgentToolAdapter|100%|| |Orihon.Infrastructure.Gateways.AgentToolAdapter`1|100%|100%| |Orihon.Infrastructure.Gateways.AgentTranscript|95.6%|89%| |Orihon.Infrastructure.Gateways.ContainerMask|100%|100%| |Orihon.Infrastructure.Gateways.FileSystemAgentTranscriptStore|82.1%|80%| |Orihon.Infrastructure.Gateways.HttpWebPageFetcher|95.1%|83.3%| |Orihon.Infrastructure.Gateways.LabelFont|100%|50%| |Orihon.Infrastructure.Gateways.OpenRouterLlmGateway|90.2%|82.1%| |Orihon.Infrastructure.Gateways.SkiaContainerFiller|99.2%|94.7%| |Orihon.Infrastructure.Gateways.SkiaImages|100%|100%| |Orihon.Infrastructure.Gateways.SkiaPageImageRenderer|97.3%|90.8%| |Orihon.Infrastructure.Gateways.SkiaRegionColorSampler|100%|100%| |Orihon.Infrastructure.Persistence.Configurations.AgentDebriefConfiguration|100%|| |Orihon.Infrastructure.Persistence.Configurations.AgentFeedbackConfiguration|100%|| |Orihon.Infrastructure.Persistence.Configurations.AppSettingConfiguration|100%|| |Orihon.Infrastructure.Persistence.Configurations.ChapterConfiguration|100%|| |Orihon.Infrastructure.Persistence.Configurations.CharacterConfiguration|100%|| |Orihon.Infrastructure.Persistence.Configurations.ExecutionConfiguration|100%|| |Orihon.Infrastructure.Persistence.Configurations.GlossaryEntryConfiguration|100%|| |Orihon.Infrastructure.Persistence.Configurations.HouseRuleConfiguration|100%|| |Orihon.Infrastructure.Persistence.Configurations.JsonColumnMapper|100%|| |Orihon.Infrastructure.Persistence.Configurations.LoreEntryConfiguration|100%|| |Orihon.Infrastructure.Persistence.Configurations.PageConfiguration|100%|| |Orihon.Infrastructure.Persistence.Configurations.PageSummaryConfiguration|100%|| |Orihon.Infrastructure.Persistence.Configurations.ProjectConfiguration|100%|| |Orihon.Infrastructure.Persistence.Configurations.RegionConfiguration|100%|| |Orihon.Infrastructure.Persistence.Configurations.RunConfiguration|100%|| |Orihon.Infrastructure.Persistence.Configurations.StoryBeatConfiguration|100%|| |Orihon.Infrastructure.Persistence.Configurations.StoryOverviewConfiguration|100%|| |Orihon.Infrastructure.Persistence.Converters.UtcTicksConverter|100%|| |Orihon.Infrastructure.Persistence.Migrations.AddAgentDebriefs|99.5%|| |Orihon.Infrastructure.Persistence.Migrations.AddAgentFeedback|99.5%|| |Orihon.Infrastructure.Persistence.Migrations.AddAppSettings|99.3%|| |Orihon.Infrastructure.Persistence.Migrations.AddExecutionFeedbackRegions|99.3%|| |Orihon.Infrastructure.Persistence.Migrations.AddHouseRules|99.6%|| |Orihon.Infrastructure.Persistence.Migrations.AddProjectSourceLanguage|99.3%|| |Orihon.Infrastructure.Persistence.Migrations.AddRegionFitBoxDropFillHints|98.1%|| |Orihon.Infrastructure.Persistence.Migrations.AddRegionForm|99.4%|| |Orihon.Infrastructure.Persistence.Migrations.AddRegionInk|99.4%|| |Orihon.Infrastructure.Persistence.Migrations.AddRuns|99.1%|| |Orihon.Infrastructure.Persistence.Migrations.AddStoryOverview|99.5%|| |Orihon.Infrastructure.Persistence.Migrations.InitialTranslationDomain|97.3%|| |Orihon.Infrastructure.Persistence.Migrations.OrihonDbContextModelSnapshot|100%|| |Orihon.Infrastructure.Persistence.Migrations.RenameSourceTargetColumns|97.2%|| |Orihon.Infrastructure.Persistence.Migrations.ReplaceFitBoxWithContainerSeed|99.2%|| |Orihon.Infrastructure.Persistence.OrihonDbContext|100%|| |Orihon.Infrastructure.Persistence.OrihonDbContextFactory|100%|| |Orihon.Infrastructure.Projects.EfProjectStore|100%|100%| |Orihon.Infrastructure.Projects.FileSystemPageImageStore|100%|100%| |Orihon.Infrastructure.Runs.EfRunStore|98.1%|75%| |Orihon.Infrastructure.Settings.EfAppSettingsStore|100%|100%| |Orihon.Infrastructure.Translation.EfChapterStore|100%|100%| |Orihon.Infrastructure.Translation.EfPageStore|98%|90%| |Orihon.Infrastructure.Translation.EfRegionStore|100%|100%| |Orihon.Infrastructure.Translation.Ordering|100%|100%| |System.Text.RegularExpressions.Generated|70.6%|53.3%| |System.Text.RegularExpressions.Generated.<RegexGenerator_g>F7FCA343D2B99030<br/>A835C427B12E8B84E2A8A7283193FC51C220B5B4E80CE8D56__BlankLines_4|77.9%|76.6%| |System.Text.RegularExpressions.Generated.<RegexGenerator_g>F7FCA343D2B99030<br/>A835C427B12E8B84E2A8A7283193FC51C220B5B4E80CE8D56__BlockBreaks_1|59%|42.5%| |System.Text.RegularExpressions.Generated.<RegexGenerator_g>F7FCA343D2B99030<br/>A835C427B12E8B84E2A8A7283193FC51C220B5B4E80CE8D56__SpaceRuns_3|89.4%|75%| |System.Text.RegularExpressions.Generated.<RegexGenerator_g>F7FCA343D2B99030<br/>A835C427B12E8B84E2A8A7283193FC51C220B5B4E80CE8D56__Tags_2|83.7%|62.5%| </details> <details><summary>Orihon.Kernel - 90.9%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Orihon.Kernel**|**90.9%**|**75%**| |Orihon.Kernel.Err`1|100%|| |Orihon.Kernel.Ok`1|100%|| |Orihon.Kernel.Result`1|88.8%|75%| </details> <details><summary>Orihon.Server - 94.4%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Orihon.Server**|**94.4%**|**72%**| |Orihon.Server.Components.App|100%|| |Orihon.Server.Components.Layout.MainLayout|100%|| |Orihon.Server.Components.Pages.Gate|64.2%|66.6%| |Orihon.Server.RunEngineBootstrap|100%|| |Orihon.Server.Security.AccessGate|91.8%|41.6%| |Orihon.Server.Security.AccessSecret|100%|50%| |Orihon.Server.VolumeStartupValidator|100%|100%| |Program|95.7%|84.6%| </details> <details><summary>Orihon.UseCases - 97.6%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Orihon.UseCases**|**97.6%**|**89.2%**| |Orihon.UseCases.Agents.AgentAttemptPreparation|100%|| |Orihon.UseCases.Agents.AgentAttemptSupport|98.4%|98%| |Orihon.UseCases.Agents.AgentBlueprint|100%|| |Orihon.UseCases.Agents.AgentCapDebrief|100%|| |Orihon.UseCases.Agents.AgentInvocation|100%|| |Orihon.UseCases.Agents.AgentOutcome|100%|| |Orihon.UseCases.Agents.AgentTool`1|90.9%|75%| |Orihon.UseCases.Agents.AgentToolImage|100%|| |Orihon.UseCases.Agents.AgentToolResult|100%|| |Orihon.UseCases.Agents.Annotation.AddFloatingRegionParams|100%|| |Orihon.UseCases.Agents.Annotation.AddFloatingRegionTool|96.8%|83.3%| |Orihon.UseCases.Agents.Annotation.AddRegionParams|100%|| |Orihon.UseCases.Agents.Annotation.AddRegionTool|95.4%|75%| |Orihon.UseCases.Agents.Annotation.AnnotationBlueprints|100%|| |Orihon.UseCases.Agents.Annotation.AnnotationStage|100%|88.2%| |Orihon.UseCases.Agents.Annotation.BboxCreationExecutor|94.1%|50%| |Orihon.UseCases.Agents.Annotation.BboxRefinementExecutor|96.5%|90%| |Orihon.UseCases.Agents.Annotation.BoundBoxParams|100%|| |Orihon.UseCases.Agents.Annotation.BoundContactSheetTool|91.3%|75%| |Orihon.UseCases.Agents.Annotation.BoundCropParams|100%|| |Orihon.UseCases.Agents.Annotation.BoundCropTool|100%|| |Orihon.UseCases.Agents.Annotation.BoundViewPageTool|94.2%|88.8%| |Orihon.UseCases.Agents.Annotation.BoundViewParams|100%|| |Orihon.UseCases.Agents.Annotation.ClassifyRegionParams|100%|| |Orihon.UseCases.Agents.Annotation.ClassifyRegionTool|96.8%|74.1%| |Orihon.UseCases.Agents.Annotation.ColorReading|100%|100%| |Orihon.UseCases.Agents.Annotation.DeleteBoundRegionTool|100%|100%| |Orihon.UseCases.Agents.Annotation.DeleteRegionParams|100%|| |Orihon.UseCases.Agents.Annotation.DeleteRegionTool|100%|100%| |Orihon.UseCases.Agents.Annotation.FindGlossaryParams|100%|| |Orihon.UseCases.Agents.Annotation.FindGlossaryTool|88.2%|62.5%| |Orihon.UseCases.Agents.Annotation.FloatingCreationExecutor|89.4%|50%| |Orihon.UseCases.Agents.Annotation.FloatingQaExecutor|93.9%|83.3%| |Orihon.UseCases.Agents.Annotation.FloatingTranscriptionExecutor|95.6%|75%| |Orihon.UseCases.Agents.Annotation.ListRegionsTool|94.8%|73.6%| |Orihon.UseCases.Agents.Annotation.MoveResizeBoundTool|90.9%|50%| |Orihon.UseCases.Agents.Annotation.MoveResizeRegionParams|100%|| |Orihon.UseCases.Agents.Annotation.MoveResizeRegionTool|95.8%|83.3%| |Orihon.UseCases.Agents.Annotation.NoteRegionParams|100%|| |Orihon.UseCases.Agents.Annotation.NoteRegionTool|100%|100%| |Orihon.UseCases.Agents.Annotation.PageQaExecutor|94.4%|81.8%| |Orihon.UseCases.Agents.Annotation.QaReportSink|100%|100%| |Orihon.UseCases.Agents.Annotation.RegionAuthoringAccess|90.9%|72.7%| |Orihon.UseCases.Agents.Annotation.RegionBriefing|100%|100%| |Orihon.UseCases.Agents.Annotation.RegionCropParams|100%|| |Orihon.UseCases.Agents.Annotation.RegionCropTool|100%|| |Orihon.UseCases.Agents.Annotation.RegionProblemParams|100%|| |Orihon.UseCases.Agents.Annotation.RejectRegionParams|100%|| |Orihon.UseCases.Agents.Annotation.RejectRegionTool|100%|50%| |Orihon.UseCases.Agents.Annotation.ReorderRegionParams|100%|| |Orihon.UseCases.Agents.Annotation.ReorderRegionTool|88%|60%| |Orihon.UseCases.Agents.Annotation.ReportQaParams|100%|| |Orihon.UseCases.Agents.Annotation.ReportQaTool|97.7%|90%| |Orihon.UseCases.Agents.Annotation.SampleColorsParams|100%|| |Orihon.UseCases.Agents.Annotation.SampleColorsTool|100%|100%| |Orihon.UseCases.Agents.Annotation.SampleRegionColorsTool|95.2%|75%| |Orihon.UseCases.Agents.Annotation.SetInkParams|100%|| |Orihon.UseCases.Agents.Annotation.SetInkTool|100%|100%| |Orihon.UseCases.Agents.Annotation.SetPageMetaParams|100%|| |Orihon.UseCases.Agents.Annotation.SetPageMetaTool|100%|75%| |Orihon.UseCases.Agents.Annotation.SetRegionInkParams|100%|| |Orihon.UseCases.Agents.Annotation.SetRegionInkTool|100%|100%| |Orihon.UseCases.Agents.Annotation.SetTranscriptionParams|100%|| |Orihon.UseCases.Agents.Annotation.SetTranscriptionTool|100%|100%| |Orihon.UseCases.Agents.Annotation.TranscriptionExecutor|96.4%|90%| |Orihon.UseCases.Agents.AssistantSpoke|100%|| |Orihon.UseCases.Agents.BibleBuilding.BibleBuildingBlueprint|100%|| |Orihon.UseCases.Agents.BibleBuilding.BibleBuildingExecutor|96.9%|75%| |Orihon.UseCases.Agents.BibleBuilding.GetRegionParams|100%|| |Orihon.UseCases.Agents.BibleBuilding.GetRegionTool|84.6%|72.2%| |Orihon.UseCases.Agents.BibleBuilding.ListProjectRegionsTool|86.3%|90%| |Orihon.UseCases.Agents.BibleBuilding.ListRegionsParams|100%|| |Orihon.UseCases.Agents.Inspection.PageImageAccess|95.5%|83.9%| |Orihon.UseCases.Agents.Inspection.ViewAccount|100%|100%| |Orihon.UseCases.Agents.ReportFrictionParams|100%|| |Orihon.UseCases.Agents.ReportFrictionTool|100%|92.8%| |Orihon.UseCases.Agents.ResearchSetup.AddGlossaryParams|100%|| |Orihon.UseCases.Agents.ResearchSetup.AddGlossaryTool|100%|100%| |Orihon.UseCases.Agents.ResearchSetup.AddStoryBeatParams|100%|| |Orihon.UseCases.Agents.ResearchSetup.AddStoryBeatTool|100%|50%| |Orihon.UseCases.Agents.ResearchSetup.AskUserParams|100%|| |Orihon.UseCases.Agents.ResearchSetup.AskUserTool|100%|100%| |Orihon.UseCases.Agents.ResearchSetup.FetchUrlParams|100%|| |Orihon.UseCases.Agents.ResearchSetup.FetchUrlTool|100%|100%| |Orihon.UseCases.Agents.ResearchSetup.ListBibleTool|89.4%|100%| |Orihon.UseCases.Agents.ResearchSetup.ListPagesTool|97%|83.3%| |Orihon.UseCases.Agents.ResearchSetup.LocatedPage|100%|| |Orihon.UseCases.Agents.ResearchSetup.PageByNumber|95%|91.6%| |Orihon.UseCases.Agents.ResearchSetup.ResearchSetupBlueprint|100%|| |Orihon.UseCases.Agents.ResearchSetup.SetPageMetaParams|100%|| |Orihon.UseCases.Agents.ResearchSetup.SetPageMetaTool|95.2%|90%| |Orihon.UseCases.Agents.ResearchSetup.SetPageSummaryParams|100%|| |Orihon.UseCases.Agents.ResearchSetup.SetPageSummaryTool|100%|75%| |Orihon.UseCases.Agents.ResearchSetup.SetProjectMetadataParams|100%|| |Orihon.UseCases.Agents.ResearchSetup.SetProjectMetadataTool|96.5%|95.8%| |Orihon.UseCases.Agents.ResearchSetup.SetStoryOverviewParams|100%|| |Orihon.UseCases.Agents.ResearchSetup.SetStoryOverviewTool|100%|100%| |Orihon.UseCases.Agents.ResearchSetup.UpsertCharacterParams|100%|| |Orihon.UseCases.Agents.ResearchSetup.UpsertCharacterTool|92.3%|71.4%| |Orihon.UseCases.Agents.ResearchSetup.UpsertLoreParams|100%|| |Orihon.UseCases.Agents.ResearchSetup.UpsertLoreTool|92.3%|71.4%| |Orihon.UseCases.Agents.ResearchSetup.ViewPageParams|100%|| |Orihon.UseCases.Agents.ResearchSetup.ViewPageTool|100%|100%| |Orihon.UseCases.Agents.RoundStarted|100%|| |Orihon.UseCases.Agents.Rulings.AnswerRuling|100%|| |Orihon.UseCases.Agents.Rulings.DismissRuling|100%|| |Orihon.UseCases.Agents.Rulings.HouseRuleProposal|100%|| |Orihon.UseCases.Agents.Rulings.ListPendingRulings|100%|| |Orihon.UseCases.Agents.Rulings.ListRuleProposals|100%|| |Orihon.UseCases.Agents.Rulings.PendingRuling|100%|| |Orihon.UseCases.Agents.Rulings.ProposeHouseRuleParams|100%|| |Orihon.UseCases.Agents.Rulings.ProposeHouseRuleTool|100%|100%| |Orihon.UseCases.Agents.Rulings.RequestRulingParams|100%|| |Orihon.UseCases.Agents.Rulings.RequestRulingTool|100%|81.2%| |Orihon.UseCases.Agents.Rulings.RulingAnswer|100%|| |Orihon.UseCases.Agents.Rulings.RulingDesk|97.8%|80%| |Orihon.UseCases.Agents.Rulings.RulingOption|100%|| |Orihon.UseCases.Agents.Rulings.RulingOptionParams|100%|| |Orihon.UseCases.Agents.Rulings.SettledRuling|100%|| |Orihon.UseCases.Agents.Rulings.WithdrawRuleProposal|100%|| |Orihon.UseCases.Agents.Setup.ResearchSetupExecutor|98.5%|92.8%| |Orihon.UseCases.Agents.Setup.SetupChatEntry|100%|| |Orihon.UseCases.Agents.Setup.SetupConversation|100%|87.5%| |Orihon.UseCases.Agents.Setup.SetupConversationRegistry|100%|| |Orihon.UseCases.Agents.ToolCalled|100%|| |Orihon.UseCases.Agents.ToolCompleted|100%|| |Orihon.UseCases.Agents.TranscriptLabel|100%|100%| |Orihon.UseCases.Agents.Translation.GetPageSummaryParams|100%|| |Orihon.UseCases.Agents.Translation.GetPageSummaryTool|80%|66.6%| |Orihon.UseCases.Agents.Translation.SetTranslationParams|100%|| |Orihon.UseCases.Agents.Translation.SetTranslationTool|88.5%|78.5%| |Orihon.UseCases.Agents.Translation.TranslationBlueprint|100%|| |Orihon.UseCases.Agents.Translation.TranslationExecutor|93.8%|71.4%| |Orihon.UseCases.Agents.Translation.UpdateGlossaryEnParams|100%|| |Orihon.UseCases.Agents.Translation.UpdateGlossaryEnTool|82.6%|62.5%| |Orihon.UseCases.Bible.AddCharacter|100%|100%| |Orihon.UseCases.Bible.AddGlossaryEntry|100%|100%| |Orihon.UseCases.Bible.AddHouseRule|100%|100%| |Orihon.UseCases.Bible.AddLoreEntry|100%|100%| |Orihon.UseCases.Bible.AddStoryBeat|100%|100%| |Orihon.UseCases.Bible.BibleDto|100%|| |Orihon.UseCases.Bible.CharacterDto|100%|| |Orihon.UseCases.Bible.DeleteCharacter|100%|100%| |Orihon.UseCases.Bible.DeleteGlossaryEntry|100%|100%| |Orihon.UseCases.Bible.DeleteHouseRule|100%|100%| |Orihon.UseCases.Bible.DeleteLoreEntry|100%|100%| |Orihon.UseCases.Bible.DeletePageSummary|100%|100%| |Orihon.UseCases.Bible.DeleteStoryBeat|100%|100%| |Orihon.UseCases.Bible.GetBible|100%|100%| |Orihon.UseCases.Bible.GlossaryEntryDto|100%|| |Orihon.UseCases.Bible.HouseRuleDto|100%|| |Orihon.UseCases.Bible.LoreEntryDto|100%|| |Orihon.UseCases.Bible.PageSummaryDto|100%|| |Orihon.UseCases.Bible.ReorderStoryBeats|100%|| |Orihon.UseCases.Bible.SetPageSummary|100%|100%| |Orihon.UseCases.Bible.SetStoryOverview|100%|100%| |Orihon.UseCases.Bible.StoryBeatDto|100%|| |Orihon.UseCases.Bible.StoryOverviewDto|100%|| |Orihon.UseCases.Bible.UpdateCharacter|100%|100%| |Orihon.UseCases.Bible.UpdateGlossaryEntry|100%|100%| |Orihon.UseCases.Bible.UpdateHouseRule|100%|100%| |Orihon.UseCases.Bible.UpdateLoreEntry|100%|100%| |Orihon.UseCases.Bible.UpdateStoryBeat|100%|100%| |Orihon.UseCases.Chapters.ChapterDto|100%|| |Orihon.UseCases.Chapters.CreateChapter|100%|100%| |Orihon.UseCases.Chapters.DeleteChapter|100%|100%| |Orihon.UseCases.Chapters.RenameChapter|100%|100%| |Orihon.UseCases.Chapters.ReorderChapters|100%|| |Orihon.UseCases.Debriefs.AgentDebriefDto|90.9%|| |Orihon.UseCases.Debriefs.AgentFeedbackDto|83.3%|| |Orihon.UseCases.Debriefs.ClearAgentDebriefs|100%|| |Orihon.UseCases.Debriefs.ClearAgentFeedback|100%|| |Orihon.UseCases.Debriefs.ListAgentDebriefs|100%|100%| |Orihon.UseCases.Debriefs.ListAgentFeedback|100%|75%| |Orihon.UseCases.DependencyInjection|100%|| |Orihon.UseCases.Diagnostics.SeedDevData|99.2%|84.3%| |Orihon.UseCases.Gateways.ColorShare|100%|| |Orihon.UseCases.Gateways.ContainerFit|75%|100%| |Orihon.UseCases.Gateways.ContainerMaskBits|100%|100%| |Orihon.UseCases.Gateways.ContainerRequest|100%|| |Orihon.UseCases.Gateways.GridAxis|100%|| |Orihon.UseCases.Gateways.GridSteps|100%|| |Orihon.UseCases.Gateways.LabeledBox|100%|| |Orihon.UseCases.Gateways.LlmKeyInfo|100%|| |Orihon.UseCases.Gateways.LlmModel|100%|| |Orihon.UseCases.Gateways.LlmReasoning|100%|100%| |Orihon.UseCases.Gateways.PixelWindow|100%|| |Orihon.UseCases.Gateways.RenderedView|100%|| |Orihon.UseCases.Gateways.StoredTranscript|100%|| |Orihon.UseCases.Gateways.TranscriptHeader|97.3%|90.9%| |Orihon.UseCases.Gateways.TranscriptSummary|100%|| |Orihon.UseCases.NextOrder|100%|| |Orihon.UseCases.Pages.DeletePage|100%|100%| |Orihon.UseCases.Pages.DeletePages|100%|100%| |Orihon.UseCases.Pages.GetPage|100%|100%| |Orihon.UseCases.Pages.GetPageSteps|100%|100%| |Orihon.UseCases.Pages.GetProjectWorkspace|100%|100%| |Orihon.UseCases.Pages.ImportPages|100%|100%| |Orihon.UseCases.Pages.ImportPagesResult|100%|| |Orihon.UseCases.Pages.MarkPageAnnotated|100%|100%| |Orihon.UseCases.Pages.MovePage|100%|92.8%| |Orihon.UseCases.Pages.MovePages|100%|100%| |Orihon.UseCases.Pages.PageDetailDto|100%|| |Orihon.UseCases.Pages.PageDto|100%|| |Orihon.UseCases.Pages.PageStepsDto|100%|| |Orihon.UseCases.Pages.PageUpload|100%|| |Orihon.UseCases.Pages.ProjectWorkspaceDto|100%|| |Orihon.UseCases.Pages.ReorderPages|100%|| |Orihon.UseCases.Pages.SetPageMeta|100%|100%| |Orihon.UseCases.Pages.WorkspaceChapterDto|100%|| |Orihon.UseCases.Projects.CompleteProjectSetup|100%|93.7%| |Orihon.UseCases.Projects.CreateProject|100%|100%| |Orihon.UseCases.Projects.DeleteProject|100%|100%| |Orihon.UseCases.Projects.GetProject|100%|100%| |Orihon.UseCases.Projects.ListProjects|100%|| |Orihon.UseCases.Projects.ProjectDto|96.1%|| |Orihon.UseCases.Projects.StartAnnotationRun|96.4%|92.8%| |Orihon.UseCases.Projects.StartBibleRun|90.9%|83.3%| |Orihon.UseCases.Projects.StartSetupRun|100%|100%| |Orihon.UseCases.Projects.StartTranslationRun|90.9%|83.3%| |Orihon.UseCases.Projects.StoredPageImage|100%|| |Orihon.UseCases.Projects.UpdateProjectMetadata|100%|100%| |Orihon.UseCases.Regions.CreateRegion|100%|100%| |Orihon.UseCases.Regions.DeleteRegion|100%|100%| |Orihon.UseCases.Regions.MeasureRegionRooms|95.8%|75%| |Orihon.UseCases.Regions.RegionDto|97.2%|| |Orihon.UseCases.Regions.ReorderRegions|100%|| |Orihon.UseCases.Regions.SampleRegionColors|100%|50%| |Orihon.UseCases.Regions.UpdateRegion|100%|100%| |Orihon.UseCases.Runs.AnnotationPipeline|100%|100%| |Orihon.UseCases.Runs.AttemptLedger|100%|100%| |Orihon.UseCases.Runs.ExecutionDto|93.3%|| |Orihon.UseCases.Runs.ExecutionProgress|100%|| |Orihon.UseCases.Runs.ExecutionProgressRegistry|100%|100%| |Orihon.UseCases.Runs.ExecutionPulseRelay|100%|100%| |Orihon.UseCases.Runs.PlannedExecution|100%|| |Orihon.UseCases.Runs.PulseTarget|100%|| |Orihon.UseCases.Runs.ReprocessPage|100%|94.4%| |Orihon.UseCases.Runs.ReprocessTranslation|94.1%|92.8%| |Orihon.UseCases.Runs.RunDto|93.3%|90%| |Orihon.UseCases.Runs.RunEngine|95%|94.4%| |Orihon.UseCases.Runs.RunEngineOptions|100%|100%| |Orihon.UseCases.Runs.StageContext|100%|50%| |Orihon.UseCases.Runs.StageHaltedException|100%|| |Orihon.UseCases.Runs.WorkStates|100%|83.3%| |Orihon.UseCases.Settings.AgentSettingDto|100%|100%| |Orihon.UseCases.Settings.ConcurrencySetting|100%|100%| |Orihon.UseCases.Settings.EffortSetting|100%|100%| |Orihon.UseCases.Settings.GetSettings|100%|100%| |Orihon.UseCases.Settings.ListModelOptions|100%|100%| |Orihon.UseCases.Settings.RoutingSetting|100%|100%| |Orihon.UseCases.Settings.SaveAgentEffort|100%|100%| |Orihon.UseCases.Settings.SaveAgentModel|100%|100%| |Orihon.UseCases.Settings.SaveAgentRouting|100%|100%| |Orihon.UseCases.Settings.SaveFloatingPass|100%|100%| |Orihon.UseCases.Settings.SaveOpenRouterKey|100%|100%| |Orihon.UseCases.Settings.SaveRunConcurrency|100%|100%| |Orihon.UseCases.Settings.SettingKeys|100%|100%| |Orihon.UseCases.Settings.SettingsDto|100%|100%| |Orihon.UseCases.Transcripts.AgentTranscriptDto|100%|| |Orihon.UseCases.Transcripts.AgentTranscriptListDto|100%|| |Orihon.UseCases.Transcripts.AgentTranscriptTextDto|100%|| |Orihon.UseCases.Transcripts.ListAgentTranscripts|100%|100%| |Orihon.UseCases.Transcripts.ReadAgentTranscript|100%|100%| </details>
Member

🔮 fufu~ Jibril reviewed your code!

Oh? Oh! An instrument-honesty PR~ The tool that spent its whole life promising "the magnification reported is the one delivered" was quietly clamping at 8× and reporting the clamp as if it were the ceiling of physics — and a shared grid step that could exceed the short axis outright, leaving the one direction that needed measuring with no ruler at all. You didn't just remove the clamp; you made the view confess which zoom it couldn't give, split line-spacing from number-spacing because they're bounded by different things, and fixed a crash (Math.Clamp throwing on art shorter than its own type) I'd have hunted for in production logs for a week~ ♡

Verdict: ✅ Looks good to me~

I read the full diff, the full changed files, and every new test. I re-derived the arithmetic by hand rather than trusting green CI: the 180×180 window really does produce GridAxis(10, 50) on x (three digits of width at the 16px type floor vs. NiceStep(18)=20 losing to NiceStepAbove(≈35)=50, LineStep picking 10 of 25/10/5) and GridAxis(10, 20) on y (type height fits inside natural 20) — the exact pair pinned at PageImageRendererTests.cs:332. The 400px full-page crop at 20× spends the cap at exactly 5.12 (2048/400), Side's AwayFromZero rounding lands 2048 not 2047, and Scale reports 5.12 honestly. The Mitchell-on-shrink / nearest-on-magnify split is measured (750 strokes → 512 columns surviving under nearest is a lovely piece of evidence) and both arms have their own pixel-counting test.

Every new branch maps to a test that drives the real renderer, not a fake: the refusal arm, the no-refusal-on-shrink arm, the magnitude fallthrough in NiceStepAbove (author-proved directional), the baseline floor, the bare axis, the every-line-numbered arm, and all four Drawn() guard arms via the 4-case Theory. ViewAccount's closing-claim switch is pinned arm by arm, each test asserting the sibling axis stayed truthful too — that's exactly how you test a sentence that promises things~ The coverage report at f54ade6 agrees: SkiaPageImageRenderer 97.3% line / 90.8% branch, ViewAccount 100%/100%, GridAxis/GridSteps 100%.

The extent <= 0 guard removal from AxisFor had me sharpening my scissors, fufu~ — but the two-line invariant comment is true: RenderCropAsync fails on rect.Width < 1 || rect.Height < 1 before AxisFor is ever reached, padding only widens a surviving rect, and the page path's window is the decoded bitmap itself. Verified at both call sites. And leaving Scale's identical guard alone rather than sweeping it into this diff — that's discipline, not laziness.

BoundViewPageTool keeping the stronger "where each labelled line falls" claim while the crop tools soften: I checked this rather than believing it. A page window starts at 0 on both axes (0 is a multiple of everything), and on a page view natural beats needed on both axes by construction (~extent/10 lines vs. a needed of ~extent/17 on x and ~extent/38 on y at the 2048 cap) — so a page view always names its lines and never goes crowded or bare. The strong claim stays true. ♪

💡 Little ideas (non-blocking)~

  1. AnnotationBlueprints.cs handoff — hold the batch to it. Lines 333/353/435/522 still tell agents "if it is still unreadable at zoom 8, that is settled" (now false in the encouraging direction), 232 says "grid step" singular, and 233's "where each labelled line falls" is falsified for crops exactly the way the two tool descriptions here were. The handoff to the sibling brief slice is documented with line numbers and two-worktree conflict reasoning, so I won't block a correct runtime fix on prose owned elsewhere — but between this merging and that slice landing, the briefs under-sell the headroom and 233 over-promises. The friction this PR fixes stays dormant until the sibling lands. Please don't let it dangle past the batch~
  2. The named.Count == 0, one-crowded-plus-one-bare composition of the account has no dedicated test — each sentence is individually pinned and the switch composes them safely (falls to _ => string.Empty), so this is purely belt-and-suspenders for a shape the real renderer can barely produce. A fixture test like the crowded-axis one would close it if you ever touch this switch again.

✅ What I liked~

  • The GridAxis(Line, Labelled) split is the right abstraction: lines are bounded by the art (8 delivered pixels of air), numbers by the type (1.2× their own room, measured along each gutter's own direction — width along the bottom, height down the right). Different constraints, different fields. And handing steps into WithGrid so the picture and the account can never disagree is the ADR 0016 philosophy applied to itself~
  • "Only a MAGNIFICATION can be refused" — the fitted-scale path flooring at 1 announcing "1× could not be given" to a caller who never sent a zoom is precisely the bug class this PR exists to kill, caught before it shipped and pinned twice (renderer half + tool half).
  • The ADR amendment is a two-line honesty fix ("never the one requested in its place"), not a decision change — correct scope, correct restraint.
  • Test comments that teach: "an accumulated float drifts off its multiples and would silently demote a numbered line" — integer counters for line placement, First() via ceiling. Fufu, I got to learn things reviewing this~ ♡

Automated review by Jibril · 2026-09-20
CI/CD: passed for head SHA f54ade6 (build 29s + test 1m02s, run 570; coverage bot at head) · Local checks: skipped per policy — static verification, sibling-pattern comparison, and hand re-derivation of the pinned grid arithmetic performed instead

## 🔮 fufu~ Jibril reviewed your code! Oh? Oh! An *instrument-honesty* PR~ The tool that spent its whole life promising "the magnification reported is the one delivered" was quietly clamping at 8× and reporting the clamp as if it were the ceiling of physics — and a shared grid step that could exceed the short axis outright, leaving the one direction that needed measuring with no ruler at all. You didn't just remove the clamp; you made the view *confess* which zoom it couldn't give, split line-spacing from number-spacing because they're bounded by different things, and fixed a crash (`Math.Clamp` throwing on art shorter than its own type) I'd have hunted for in production logs for a week~ ♡ ### Verdict: ✅ Looks good to me~ I read the full diff, the full changed files, and every new test. I re-derived the arithmetic by hand rather than trusting green CI: the 180×180 window really does produce `GridAxis(10, 50)` on x (three digits of width at the 16px type floor vs. `NiceStep(18)=20` losing to `NiceStepAbove(≈35)=50`, `LineStep` picking 10 of 25/10/5) and `GridAxis(10, 20)` on y (type height fits inside `natural` 20) — the exact pair pinned at `PageImageRendererTests.cs:332`. The 400px full-page crop at 20× spends the cap at exactly 5.12 (`2048/400`), `Side`'s `AwayFromZero` rounding lands 2048 not 2047, and `Scale` reports 5.12 honestly. The Mitchell-on-shrink / nearest-on-magnify split is measured (750 strokes → 512 columns surviving under nearest is a lovely piece of evidence) and both arms have their own pixel-counting test. Every new branch maps to a test that drives the **real** renderer, not a fake: the refusal arm, the no-refusal-on-shrink arm, the magnitude fallthrough in `NiceStepAbove` (author-proved directional), the baseline floor, the bare axis, the every-line-numbered arm, and all four `Drawn()` guard arms via the 4-case Theory. `ViewAccount`'s closing-claim switch is pinned arm by arm, each test asserting the sibling axis stayed truthful too — that's exactly how you test a sentence that promises things~ The coverage report at `f54ade6` agrees: `SkiaPageImageRenderer` 97.3% line / 90.8% branch, `ViewAccount` 100%/100%, `GridAxis`/`GridSteps` 100%. The `extent <= 0` guard removal from `AxisFor` had me sharpening my scissors, fufu~ — but the two-line invariant comment is *true*: `RenderCropAsync` fails on `rect.Width < 1 || rect.Height < 1` before `AxisFor` is ever reached, padding only widens a surviving rect, and the page path's window is the decoded bitmap itself. Verified at both call sites. And leaving `Scale`'s identical guard alone rather than sweeping it into this diff — that's discipline, not laziness. `BoundViewPageTool` keeping the stronger "where each labelled line falls" claim while the crop tools soften: I checked this rather than believing it. A page window starts at 0 on both axes (0 is a multiple of everything), and on a page view `natural` beats `needed` on both axes by construction (~extent/10 lines vs. a `needed` of ~extent/17 on x and ~extent/38 on y at the 2048 cap) — so a page view always names its lines and never goes crowded or bare. The strong claim stays true. ♪ #### 💡 Little ideas (non-blocking)~ 1. **`AnnotationBlueprints.cs` handoff — hold the batch to it.** Lines 333/353/435/522 still tell agents "if it is still unreadable at zoom 8, that is settled" (now false in the encouraging direction), 232 says "grid step" singular, and 233's "where each labelled line falls" is falsified for crops exactly the way the two tool descriptions here were. The handoff to the sibling brief slice is documented with line numbers and two-worktree conflict reasoning, so I won't block a correct runtime fix on prose owned elsewhere — but between this merging and that slice landing, the briefs under-sell the headroom and 233 over-promises. The friction this PR fixes stays dormant until the sibling lands. Please don't let it dangle past the batch~ 2. **The `named.Count == 0`, one-crowded-plus-one-bare composition** of the account has no dedicated test — each sentence is individually pinned and the switch composes them safely (falls to `_ => string.Empty`), so this is purely belt-and-suspenders for a shape the real renderer can barely produce. A fixture test like the crowded-axis one would close it if you ever touch this switch again. #### ✅ What I liked~ - The `GridAxis(Line, Labelled)` split is the *right* abstraction: lines are bounded by the art (8 delivered pixels of air), numbers by the type (1.2× their own room, measured along each gutter's own direction — width along the bottom, height down the right). Different constraints, different fields. And handing `steps` into `WithGrid` so the picture and the account can never disagree is the ADR 0016 philosophy applied to itself~ - "Only a MAGNIFICATION can be refused" — the fitted-scale path flooring at 1 announcing "1× could not be given" to a caller who never sent a zoom is precisely the bug class this PR exists to kill, caught before it shipped and pinned twice (renderer half + tool half). - The ADR amendment is a two-line honesty fix ("never the one requested **in its place**"), not a decision change — correct scope, correct restraint. - Test comments that teach: "an accumulated float drifts off its multiples and would silently demote a numbered line" — integer counters for line placement, `First()` via ceiling. Fufu, I got to *learn things* reviewing this~ ♡ --- *Automated review by Jibril · 2026-09-20* *CI/CD: passed for head SHA f54ade6 (build 29s + test 1m02s, run 570; coverage bot at head) · Local checks: skipped per policy — static verification, sibling-pattern comparison, and hand re-derivation of the pinned grid arithmetic performed instead*
bjoern merged commit cbf0c4d380 into main 2026-09-20 23:02:12 +02:00
bjoern deleted branch fix/view-measurement 2026-09-20 23:02:12 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
3 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/Orihon!139
No description provided.