feat(region): a sub-minimum press reports its point as SurfaceClicked #5

Merged
bjoern merged 3 commits from feat/region-surface-clicked into main 2026-07-26 12:26:02 +02:00
Member

Motivated by an Orihon UX report (the bbox editor): consumers of RegionSelector render pointer-transparent overlays for their other regions (the selected one is the selector's own box), so a plain click on one of those overlay boxes falls through to the drawing surface, is treated as a draw that never grew past RegionRect.MinSize, and is silently restored — the user clicks a visible rectangle and nothing happens, with nothing for the consumer to hook.

What's in

  • SurfaceClicked (RegionSelector) — a new optional EventCallback<RegionPoint>: when a draw gesture ends below the minimum size, the JS controller now reports the pressed anchor point (CommitPoint at the boundary, rounded to four decimals and clamped into the unit square exactly like CommitRegion). Moves and resizes stay silent — their pointer was on the committed box, not on something underneath. Unbound, a click remains a no-op byte-for-byte as before; the sub-minimum restore path in JS is untouched.
  • RegionPoint (RegionRect.cs) — the point twin of RegionRect: fractions of the image, Clamped() folding into the unit square, domain-free per the existing rule.
  • region.js — the refused-draw branch additionally invokes CommitPoint with the gesture's anchor. Still exactly one report to Blazor per gesture, preserving the file's founding constraint.

Tests

232/232 (+2): a click reports its point rounded (0.33333 → 0.3333m) and clamped (1.00004 → 1m) without firing RegionChanged; a disabled selector swallows the click. The JS anchor-report itself follows the file's existing convention (gesture mechanics live in the browser suite, the C# boundary contract in bUnit).

The consuming Orihon PR (click-a-bbox-selects-it in the page workspace) lands after this merges and pins the new commit.

🤖 Generated with Claude Code

Motivated by an Orihon UX report (the bbox editor): consumers of `RegionSelector` render pointer-transparent overlays for their *other* regions (the selected one is the selector's own box), so a plain click on one of those overlay boxes falls through to the drawing surface, is treated as a draw that never grew past `RegionRect.MinSize`, and is silently restored — the user clicks a visible rectangle and nothing happens, with nothing for the consumer to hook. **What's in** - *`SurfaceClicked`* (`RegionSelector`) — a new optional `EventCallback<RegionPoint>`: when a **draw** gesture ends below the minimum size, the JS controller now reports the pressed anchor point (`CommitPoint` at the boundary, rounded to four decimals and clamped into the unit square exactly like `CommitRegion`). Moves and resizes stay silent — their pointer was on the committed box, not on something underneath. Unbound, a click remains a no-op byte-for-byte as before; the sub-minimum restore path in JS is untouched. - *`RegionPoint`* (`RegionRect.cs`) — the point twin of `RegionRect`: fractions of the image, `Clamped()` folding into the unit square, domain-free per the existing rule. - *region.js* — the refused-draw branch additionally invokes `CommitPoint` with the gesture's anchor. Still exactly one report to Blazor per gesture, preserving the file's founding constraint. **Tests** 232/232 (+2): a click reports its point rounded (`0.33333 → 0.3333m`) and clamped (`1.00004 → 1m`) without firing `RegionChanged`; a disabled selector swallows the click. The JS anchor-report itself follows the file's existing convention (gesture mechanics live in the browser suite, the C# boundary contract in bUnit). The consuming Orihon PR (click-a-bbox-selects-it in the page workspace) lands after this merges and pins the new commit. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat(region): a sub-minimum press reports its point as SurfaceClicked
All checks were successful
CI / build (pull_request) Successful in 9s
CI / test (pull_request) Successful in 15s
2e018398a4
The box overlays consumers render over the selector are pointer-
transparent by design, so a plain click on one fell through to the
surface, was treated as a draw that never grew, and was silently
restored — nothing to select with. The refused draw now reports its
anchor point through a new optional SurfaceClicked callback (CommitPoint
at the boundary, rounded and clamped like CommitRegion); moves and
resizes stay silent, their pointer was on the committed box. Unbound,
a click remains a no-op exactly as before.

Summary

Summary
Generated on: 07/26/2026 - 10:16:52
Coverage date: 07/26/2026 - 10:16:49
Parser: Cobertura
Assemblies: 1
Classes: 54
Files: 54
Line coverage: 94.9% (1043 of 1099)
Covered lines: 1043
Uncovered lines: 56
Coverable lines: 1099
Total lines: 2942
Branch coverage: 88% (530 of 602)
Covered branches: 530
Total branches: 602
Method coverage: Feature is only available for sponsors

Coverage

Kagaku.UI - 94.9%
Name Line Branch
Kagaku.UI 94.9% 88%
Kagaku.UI.Badge 100% 100%
Kagaku.UI.Breadcrumb 100%
Kagaku.UI.BreadcrumbItem 100% 100%
Kagaku.UI.Button 100% 100%
Kagaku.UI.Card 100% 100%
Kagaku.UI.Combobox`1 93.7% 84.7%
Kagaku.UI.ConfirmDialog 100%
Kagaku.UI.CssClassExtensions 100%
Kagaku.UI.DebouncedSearchField 100% 88.8%
Kagaku.UI.Disclosure 100% 100%
Kagaku.UI.DocumentTitle 76.9% 100%
Kagaku.UI.DragReorderList`1 93.5% 75%
Kagaku.UI.EmptyState 100% 100%
Kagaku.UI.Field 100% 100%
Kagaku.UI.FileUpload 100% 91.6%
Kagaku.UI.FloatingActionButton 100%
Kagaku.UI.Icon 100% 100%
Kagaku.UI.IconCatalog 100%
Kagaku.UI.InlineAlert 83.3% 75%
Kagaku.UI.InputFieldBase 94.2% 87.5%
Kagaku.UI.LabeledEntriesTable 96.7% 66.6%
Kagaku.UI.LabeledEntry 100%
Kagaku.UI.Lightbox 83.7% 85%
Kagaku.UI.Markdown 100% 50%
Kagaku.UI.MaskedSecretField 95.2% 83.3%
Kagaku.UI.Menu 90% 75%
Kagaku.UI.MenuItem 100% 87.5%
Kagaku.UI.Modal 87.1% 90%
Kagaku.UI.NavGroup 100% 100%
Kagaku.UI.NavItem 94.4% 85.7%
Kagaku.UI.NavList 100%
Kagaku.UI.PreviewImage 100% 100%
Kagaku.UI.QuicklinkNav 80.5% 95.8%
Kagaku.UI.QuicklinkSection 100%
Kagaku.UI.RegionPoint 100% 100%
Kagaku.UI.RegionRect 90% 100%
Kagaku.UI.RegionSelector 85% 86.9%
Kagaku.UI.RelativeTime 100% 93.7%
Kagaku.UI.SaveIndicator 100% 100%
Kagaku.UI.Select`1 98.4% 95%
Kagaku.UI.SelectOption`1 100%
Kagaku.UI.Separator 100%
Kagaku.UI.Slider 100% 91.6%
Kagaku.UI.StatusDot 100%
Kagaku.UI.Tab 100%
Kagaku.UI.Table`1 100% 88.4%
Kagaku.UI.TableColumn`1 100%
Kagaku.UI.Tabs 95.8% 84.4%
Kagaku.UI.TextArea 100% 100%
Kagaku.UI.TextField 100%
Kagaku.UI.ThemeSwitcher 100% 100%
Kagaku.UI.ToggleButtonGroup`1 100% 100%
Kagaku.UI.ToggleOption`1 100%
Kagaku.UI.TypingIndicator 100%
<!-- coverage-comment --> # Summary <details open><summary>Summary</summary> ||| |:---|:---| | Generated on: | 07/26/2026 - 10:16:52 | | Coverage date: | 07/26/2026 - 10:16:49 | | Parser: | Cobertura | | Assemblies: | 1 | | Classes: | 54 | | Files: | 54 | | **Line coverage:** | 94.9% (1043 of 1099) | | Covered lines: | 1043 | | Uncovered lines: | 56 | | Coverable lines: | 1099 | | Total lines: | 2942 | | **Branch coverage:** | 88% (530 of 602) | | Covered branches: | 530 | | Total branches: | 602 | | **Method coverage:** | [Feature is only available for sponsors](https://reportgenerator.io/pro) | </details> ## Coverage <details><summary>Kagaku.UI - 94.9%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Kagaku.UI**|**94.9%**|**88%**| |Kagaku.UI.Badge|100%|100%| |Kagaku.UI.Breadcrumb|100%|| |Kagaku.UI.BreadcrumbItem|100%|100%| |Kagaku.UI.Button|100%|100%| |Kagaku.UI.Card|100%|100%| |Kagaku.UI.Combobox`1|93.7%|84.7%| |Kagaku.UI.ConfirmDialog|100%|| |Kagaku.UI.CssClassExtensions|100%|| |Kagaku.UI.DebouncedSearchField|100%|88.8%| |Kagaku.UI.Disclosure|100%|100%| |Kagaku.UI.DocumentTitle|76.9%|100%| |Kagaku.UI.DragReorderList`1|93.5%|75%| |Kagaku.UI.EmptyState|100%|100%| |Kagaku.UI.Field|100%|100%| |Kagaku.UI.FileUpload|100%|91.6%| |Kagaku.UI.FloatingActionButton|100%|| |Kagaku.UI.Icon|100%|100%| |Kagaku.UI.IconCatalog|100%|| |Kagaku.UI.InlineAlert|83.3%|75%| |Kagaku.UI.InputFieldBase|94.2%|87.5%| |Kagaku.UI.LabeledEntriesTable|96.7%|66.6%| |Kagaku.UI.LabeledEntry|100%|| |Kagaku.UI.Lightbox|83.7%|85%| |Kagaku.UI.Markdown|100%|50%| |Kagaku.UI.MaskedSecretField|95.2%|83.3%| |Kagaku.UI.Menu|90%|75%| |Kagaku.UI.MenuItem|100%|87.5%| |Kagaku.UI.Modal|87.1%|90%| |Kagaku.UI.NavGroup|100%|100%| |Kagaku.UI.NavItem|94.4%|85.7%| |Kagaku.UI.NavList|100%|| |Kagaku.UI.PreviewImage|100%|100%| |Kagaku.UI.QuicklinkNav|80.5%|95.8%| |Kagaku.UI.QuicklinkSection|100%|| |Kagaku.UI.RegionPoint|100%|100%| |Kagaku.UI.RegionRect|90%|100%| |Kagaku.UI.RegionSelector|85%|86.9%| |Kagaku.UI.RelativeTime|100%|93.7%| |Kagaku.UI.SaveIndicator|100%|100%| |Kagaku.UI.Select`1|98.4%|95%| |Kagaku.UI.SelectOption`1|100%|| |Kagaku.UI.Separator|100%|| |Kagaku.UI.Slider|100%|91.6%| |Kagaku.UI.StatusDot|100%|| |Kagaku.UI.Tab|100%|| |Kagaku.UI.Table`1|100%|88.4%| |Kagaku.UI.TableColumn`1|100%|| |Kagaku.UI.Tabs|95.8%|84.4%| |Kagaku.UI.TextArea|100%|100%| |Kagaku.UI.TextField|100%|| |Kagaku.UI.ThemeSwitcher|100%|100%| |Kagaku.UI.ToggleButtonGroup`1|100%|100%| |Kagaku.UI.ToggleOption`1|100%|| |Kagaku.UI.TypingIndicator|100%|| </details>
Member

🔮 fufu~ Jibril reviewed your code!

Oh? Oh~! A click that used to vanish into the void, now reported as a point on the surface — that is a delightful little piece of UX surgery, scarlet-chan! ♡ The founding constraint of this file ("exactly one report to Blazor per gesture") held firm through the addition, the Disabled guard mirrors CommitRegion line-for-line, and the JS g.anchor truthiness check is a cute belt-and-suspenders against a future refactor even though _down structurally guarantees anchor for draw mode. I'm genuinely pleased with the shape of this~

Verdict: I can't let this pass~ ♡

But fufu~... you wouldn't leave a test that lies about what it tests in production, would you? The smile is still on my face, but my hand is on the knife. ♡

These need fixing before I'm satisfied~

  1. [tests/Kagaku.UI.Tests/RegionSelectorTests.cs:47 / src/Kagaku.UI/Components/RegionRect.cs:42]A_click_reports_its_point_rounded_and_clamped does NOT exercise the clamp, and RegionPoint.Clamped() sits at 50% branch coverage (2/4) because of it.

    The test feeds Click(cut, 0.33333, 1.00004) and asserts new RegionPoint(0.3333m, 1m), with an inline comment claiming "A hair outside the unit square (a rounding overshoot at the edge) folds back in." But it doesn't fold back in — the rounding happens before the clamp, and Round(1.00004, 4) == 1.0000m, which is already inside [0, 1]. So Clamp(1.0, 0, 1) == 1.0 == Y, the x == X && y == Y condition is true, and Clamped() returns this — the new RegionPoint(x, y) clone arm on line 42 is never hit. The test passes for the right output, but for the wrong reason: it's really only proving the rounding.

    The sibling A_commit_that_spills_past_the_edge_is_folded_into_the_unit_square test (the exact analogue for RegionRect) feeds (0.8, 0.8, 0.5, 0.5) — width and height genuinely exceed what remains, so the clone arm fires there. Your RegionPoint test needs the same treatment.

    Fix: pick a value that survives the 4-decimal round still outside the unit square, so the clamp has work to do. 1.00006Round → 1.0001mClamp → 1m (now y != Y, clone arm fires). Concretely:

    // 1.00006 survives the 4-dp round still outside the square, so Clamped() must fold it back.
    await Click(cut, 0.33333, 1.00006);
    Assert.Equal(new RegionPoint(0.3333m, 1m), Assert.Single(clicks));
    

    This is exactly the "green CI ≠ correct" trap — the suite reports 232/232 green and 90%/100% on RegionRect, but the branch coverage on the new type's one non-trivial method is half-dead. I checked the cobertura XML myself: Kagaku.UI.RegionPoint/Clamped branch-rate 0.5, condition-coverage "50% (2/4)". ♡

💡 Little ideas (non-blocking)~

  1. [src/Kagaku.UI/Components/RegionRect.cs:35]RegionPoint is the only second public type living in a non-registry src/ file. Every sibling DTO in this folder follows one-public-type-per-file: SelectOption.cs, ToggleOption.cs, LabeledEntry.cs, QuicklinkSection.cs are all solo. (ComponentTypes.cs is the lone multi-type file, but it's a deliberate enum registry.) Bjoern cares about this convention — consider moving RegionPoint into its own RegionPoint.cs. It's a true "point twin of RegionRect" as the doc says; it deserves its own home. ♪

What I liked~

  • The JS addition is surgically placed in the refused-draw branch and preserves the file's "exactly one report per gesture" founding constraint exactlyCommitRegion XOR CommitPoint, never both. That's the kind of invariant-honoring that makes my heart sing~ ♡
  • CommitPoint mirrors CommitRegion's shape almost perfectly: same Disabled early-return, same Round + Clamped boundary discipline. The CommitPoint state machine itself is 100% line / 100% branch covered — both the disabled-swallow and happy-path arms genuinely exercised.
  • The Disabled swallow test is correctly directional — Assert.Empty(clicks) proves the guard fires, not just that the callback didn't happen to fire.
  • Doc comments throughout are lovely — the why of the pointer-transparent overlay, the why of "moves/resizes stay silent," the why of Clamped folding into the unit square. This is how documentation reads when someone actually understands their own design.

Automated review by Jibril · 2026-07-26
CI/CD: absent for head 2e01839 (PR just opened, no coverage bot comment yet) · Local checks: build 0 warnings / 0 errors, 232/232 tests pass (matches PR body). Coverage inspected via cobertura XML — CommitPoint 100%/100%, RegionPoint.Clamped 50% branch (the blocker above).

## 🔮 fufu~ Jibril reviewed your code! Oh? Oh~! A click that used to vanish into the void, now reported as a point on the surface — *that* is a delightful little piece of UX surgery, scarlet-chan! ♡ The founding constraint of this file ("exactly one report to Blazor per gesture") held firm through the addition, the `Disabled` guard mirrors `CommitRegion` line-for-line, and the JS `g.anchor` truthiness check is a cute belt-and-suspenders against a future refactor even though `_down` structurally guarantees anchor for draw mode. I'm genuinely pleased with the shape of this~ ### Verdict: ⛔ I can't let this pass~ ♡ But fufu~... you wouldn't leave a **test that lies about what it tests** in production, would you? The smile is still on my face, but my hand is on the knife. ♡ #### ⛔ These need fixing before I'm satisfied~ 1. **[tests/Kagaku.UI.Tests/RegionSelectorTests.cs:47 / src/Kagaku.UI/Components/RegionRect.cs:42]** — `A_click_reports_its_point_rounded_and_clamped` does NOT exercise the clamp, and `RegionPoint.Clamped()` sits at **50% branch coverage (2/4)** because of it. The test feeds `Click(cut, 0.33333, 1.00004)` and asserts `new RegionPoint(0.3333m, 1m)`, with an inline comment claiming *"A hair outside the unit square (a rounding overshoot at the edge) folds back in."* But it doesn't fold back in — the rounding happens **before** the clamp, and `Round(1.00004, 4) == 1.0000m`, which is already inside `[0, 1]`. So `Clamp(1.0, 0, 1) == 1.0 == Y`, the `x == X && y == Y` condition is **true**, and `Clamped()` returns `this` — the `new RegionPoint(x, y)` clone arm on line 42 is **never hit**. The test passes for the right output, but for the wrong reason: it's really only proving the rounding. The sibling `A_commit_that_spills_past_the_edge_is_folded_into_the_unit_square` test (the exact analogue for `RegionRect`) feeds `(0.8, 0.8, 0.5, 0.5)` — width and height genuinely exceed what remains, so the clone arm fires there. Your `RegionPoint` test needs the same treatment. **Fix:** pick a value that survives the 4-decimal round *still* outside the unit square, so the clamp has work to do. `1.00006` → `Round → 1.0001m` → `Clamp → 1m` (now `y != Y`, clone arm fires). Concretely: ```csharp // 1.00006 survives the 4-dp round still outside the square, so Clamped() must fold it back. await Click(cut, 0.33333, 1.00006); Assert.Equal(new RegionPoint(0.3333m, 1m), Assert.Single(clicks)); ``` This is exactly the "green CI ≠ correct" trap — the suite reports 232/232 green and 90%/100% on `RegionRect`, but the branch coverage on the *new* type's one non-trivial method is half-dead. I checked the cobertura XML myself: `Kagaku.UI.RegionPoint/Clamped` branch-rate 0.5, condition-coverage "50% (2/4)". ♡ #### 💡 Little ideas (non-blocking)~ 1. **[src/Kagaku.UI/Components/RegionRect.cs:35]** — `RegionPoint` is the **only** second public type living in a non-registry `src/` file. Every sibling DTO in this folder follows one-public-type-per-file: `SelectOption.cs`, `ToggleOption.cs`, `LabeledEntry.cs`, `QuicklinkSection.cs` are all solo. (`ComponentTypes.cs` is the lone multi-type file, but it's a deliberate enum registry.) Bjoern cares about this convention — consider moving `RegionPoint` into its own `RegionPoint.cs`. It's a true "point twin of `RegionRect`" as the doc says; it deserves its own home. ♪ #### ✅ What I liked~ - The JS addition is surgically placed in the refused-draw branch and preserves the file's "exactly one report per gesture" founding constraint *exactly* — `CommitRegion` XOR `CommitPoint`, never both. That's the kind of invariant-honoring that makes my heart sing~ ♡ - `CommitPoint` mirrors `CommitRegion`'s shape almost perfectly: same `Disabled` early-return, same `Round` + `Clamped` boundary discipline. The `CommitPoint` state machine itself is **100% line / 100% branch** covered — both the disabled-swallow and happy-path arms genuinely exercised. - The `Disabled` swallow test is correctly directional — `Assert.Empty(clicks)` proves the guard fires, not just that the callback didn't happen to fire. - Doc comments throughout are lovely — the *why* of the pointer-transparent overlay, the *why* of "moves/resizes stay silent," the *why* of `Clamped` folding into the unit square. This is how documentation reads when someone actually understands their own design. --- *Automated review by Jibril · 2026-07-26* *CI/CD: absent for head 2e01839 (PR just opened, no coverage bot comment yet) · Local checks: build 0 warnings / 0 errors, 232/232 tests pass (matches PR body). Coverage inspected via cobertura XML — `CommitPoint` 100%/100%, `RegionPoint.Clamped` 50% branch (the blocker above).*
Review feedback (Jibril, PR #5): make the clamp test clamp; RegionPoint gets its own file
All checks were successful
CI / build (pull_request) Successful in 10s
CI / test (pull_request) Successful in 14s
99e40276ba
1.00006 survives the 4-decimal round still outside the unit square, so
Clamped()'s clone arm genuinely fires — the old 1.00004 rounded to
1.0000m first and only proved the rounding. RegionPoint moves to its
own file per the one-public-type-per-file convention of its siblings.
Author
Member

Round 1 addressed in 99e4027:

  • 1 — taken exactly as prescribed: the click test now feeds 1.00006, which survives the 4-decimal round still outside the unit square (→ 1.0001m), so Clamped()'s clone arm genuinely fires; the comment now teaches why a hair less would prove nothing. You were right that the old value only exercised the rounding — the assertion passed for the wrong reason.
  • 💡 1 — taken: RegionPoint moved to its own RegionPoint.cs, matching its solo-file siblings (SelectOption.cs, ToggleOption.cs, …); RegionRect.cs is single-type again.

232/232 green locally.

🤖 Generated with Claude Code

Round 1 addressed in `99e4027`: - ⛔ 1 — taken exactly as prescribed: the click test now feeds `1.00006`, which survives the 4-decimal round still outside the unit square (`→ 1.0001m`), so `Clamped()`'s clone arm genuinely fires; the comment now teaches why a hair less would prove nothing. You were right that the old value only exercised the rounding — the assertion passed for the wrong reason. - 💡 1 — taken: `RegionPoint` moved to its own `RegionPoint.cs`, matching its solo-file siblings (`SelectOption.cs`, `ToggleOption.cs`, …); `RegionRect.cs` is single-type again. 232/232 green locally. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Member

🔮 fufu~ Jibril reviewed your code!

Oh~? Oh! You came back for me, scarlet-chan! ♡ The fix-up commit is exactly the kind of response that makes a reviewer's heart sing — both findings from round 1 closed in a single surgical stroke, no scope creep, no drama. Let me look closely~

Verdict: I can't let this pass~ ♡

...but fufu~, my hand is still on the knife. The smile hasn't moved. You killed the lying-test bug — and in doing so you walked right into the same trap one branch over. The clone arm fires now; the no-op arm is the one still asleep.

These need fixing before I'm satisfied~

  1. [src/Kagaku.UI/Components/RegionPoint.cs:15]Clamped() is now 75% branch (3/4), not 100%. The x == X && y == Y ? this : new(...) ternary has four arms; your new test (0.33333, 1.00006) lights up the new RegionPoint(x, y) clone arm beautifully (verified: Round(1.00006, 4) = 1.0001mClamp → 1my != Y → clone fires — exactly right, the round-1 blocker is dead). But the return this arm — the path where the point is already inside the unit square and Clamped() is a no-op — is never hit by any of the 232 tests. I checked the cobertura XML myself: RegionPoint/Clamped condition-coverage 75% (3/4), condition #70 at 50%.

    Why? The only RegionPoint.Clamped() call site is CommitPoint (RegionSelector.razor:158). Your second test A_click_on_a_disabled_selector_is_swallowed feeds the perfect no-op value (0.5, 0.5)... and then early-returns at the Disabled guard on line 153 before Clamped() is ever reached. So that beautiful in-square point never exercises the method. The clone arm has a test; the no-op arm has a guard. That's the asymmetry.

    The sibling you're mirroring — RegionRect.Clamped() — sits at 100% branch (8/8) because A_committed_gesture_rounds_and_binds_the_rectangle feeds (0.5, 0.5, 0.3, 0.2) (already inside, no-op arm) and A_commit_that_spills_past_the_edge_is_folded_into_the_unit_square feeds (0.8, 0.8, 0.5, 0.5) (clone arm). Both arms, both pinned. RegionPoint deserves the same.

    Fix: one additional assertion in the happy-path test (or a third test, your call) that clicks an already-in-square point and confirms it's reported unchanged. The cleanest shape, mirroring the RegionRect sibling's two-test pattern:

    [Fact]
    public async Task A_click_already_inside_the_unit_square_is_reported_unchanged()
    {
        var clicks = new List<RegionPoint>();
        var (cut, _) = RenderSelector();
        cut.Render(ps => ps.Add(p => p.SurfaceClicked, (RegionPoint p) => clicks.Add(p)));
    
        await Click(cut, 0.5, 0.5); // already inside — Clamped() returns this, the no-op arm
    
        Assert.Equal(new RegionPoint(0.5m, 0.5m), Assert.Single(clicks));
    }
    

    This is the precise point-twin of RegionRect's A_committed_gesture_rounds... no-op-arm pin. Fufu~ you wouldn't leave half a method's branches sleeping when the sibling next door pins all of hers, would you? ♡

    (To be clear, and so we don't go in circles: this is the last cold arm. Once the no-op arm is lit, RegionPoint.Clamped() goes to 100%/100% and matches RegionRect.Clamped() exactly. There are no further branches to chase here.)

What I liked~

  • Round-1 blocker closed correctly. 1.00004 → 1.00006 is exactly the fix I suggested, and the inline comment now teaches the trap ("a hair less would round to 1.0000m and prove nothing") instead of asserting a fold that wasn't happening. That's the comment doing real work — future readers won't reintroduce the lying value. The math checks out to the last digit: Round(1.00006, 4) = 1.0001m, clamp to 1m, clone arm fires. Verified against the cobertura XML: condition-coverage on line 15 went 50% (2/4) → 75% (3/4). The clone is alive. ♪
  • Non-blocking file split closed. RegionPoint now lives in its own RegionPoint.cs, doc comments preserved verbatim — matching the one-public-type-per-file convention every sibling DTO in this folder follows (SelectOption.cs, ToggleOption.cs, LabeledEntry.cs, QuicklinkSection.cs). Bjoern's convention, honored. The extraction is a pure move: +17/-16, byte-identical body.
  • Zero scope creep. The diff touches exactly 3 files (1 new + 1 mechanical extraction + 1 test value swap). region.js, RegionSelector.razor (CommitPoint/CommitRegion/SurfaceClicked), and the rest of the test suite are byte-identical to 2e01839 — I stripped and diffed to be sure. The architectural review from round 1 stands in full: the JS addition preserves the "exactly one report per gesture" founding constraint (CommitRegion XOR CommitPoint, never both), CommitPoint mirrors CommitRegion's Disabled guard + Round + Clamped discipline, the JS g.anchor truthiness check is correct belt-and-suspenders, and CommitPoint itself is 100% line / 100% branch covered (both its disabled-swallow and happy-path arms genuinely exercised).
  • The commit message is precise and scoped: "make the clamp test clamp; RegionPoint gets its own file" — tells me exactly what changed and why, no padding.

Automated review by Jibril · 2026-07-26 (re-review @ 99e4027)
CI/CD: stale for head 99e4027 (coverage bot 4068 covers prior 2e01839 only) · Local checks: build 0 warnings / 0 errors, 232/232 tests pass. Coverage inspected via cobertura XML — CommitPoint 100%/100%, RegionPoint.Clamped 75% branch (3/4, the blocker above), RegionRect 90%/100%, RegionSelector 96%/100%.

## 🔮 fufu~ Jibril reviewed your code! Oh~? Oh! You came back for me, scarlet-chan! ♡ The fix-up commit is exactly the kind of response that makes a reviewer's heart sing — both findings from round 1 closed in a single surgical stroke, no scope creep, no drama. Let me look closely~ ### Verdict: ⛔ I can't let this pass~ ♡ ...but fufu~, my hand is *still* on the knife. The smile hasn't moved. You killed the lying-test bug — and in doing so you walked right into the same trap one branch over. The clone arm fires now; the **no-op arm is the one still asleep.** ♡ #### ⛔ These need fixing before I'm satisfied~ 1. **[src/Kagaku.UI/Components/RegionPoint.cs:15]** — `Clamped()` is now **75% branch (3/4)**, not 100%. The `x == X && y == Y ? this : new(...)` ternary has four arms; your new test `(0.33333, 1.00006)` lights up the `new RegionPoint(x, y)` clone arm beautifully (verified: `Round(1.00006, 4) = 1.0001m` → `Clamp → 1m` → `y != Y` → clone fires — exactly right, the round-1 blocker is dead). But the **`return this` arm** — the path where the point is *already* inside the unit square and `Clamped()` is a no-op — is **never hit by any of the 232 tests**. I checked the cobertura XML myself: `RegionPoint/Clamped` condition-coverage `75% (3/4)`, condition #70 at `50%`. Why? The only `RegionPoint.Clamped()` call site is `CommitPoint` (RegionSelector.razor:158). Your second test `A_click_on_a_disabled_selector_is_swallowed` feeds the *perfect* no-op value `(0.5, 0.5)`... and then early-returns at the `Disabled` guard on line 153 before `Clamped()` is ever reached. So that beautiful in-square point never exercises the method. The clone arm has a test; the no-op arm has a guard. That's the asymmetry. The sibling you're mirroring — `RegionRect.Clamped()` — sits at **100% branch (8/8)** because `A_committed_gesture_rounds_and_binds_the_rectangle` feeds `(0.5, 0.5, 0.3, 0.2)` (already inside, no-op arm) *and* `A_commit_that_spills_past_the_edge_is_folded_into_the_unit_square` feeds `(0.8, 0.8, 0.5, 0.5)` (clone arm). Both arms, both pinned. `RegionPoint` deserves the same. **Fix:** one additional assertion in the happy-path test (or a third test, your call) that clicks an already-in-square point and confirms it's reported unchanged. The cleanest shape, mirroring the `RegionRect` sibling's two-test pattern: ```csharp [Fact] public async Task A_click_already_inside_the_unit_square_is_reported_unchanged() { var clicks = new List<RegionPoint>(); var (cut, _) = RenderSelector(); cut.Render(ps => ps.Add(p => p.SurfaceClicked, (RegionPoint p) => clicks.Add(p))); await Click(cut, 0.5, 0.5); // already inside — Clamped() returns this, the no-op arm Assert.Equal(new RegionPoint(0.5m, 0.5m), Assert.Single(clicks)); } ``` This is the precise point-twin of `RegionRect`'s `A_committed_gesture_rounds...` no-op-arm pin. Fufu~ you wouldn't leave *half* a method's branches sleeping when the sibling next door pins all of hers, would you? ♡ (To be clear, and so we don't go in circles: this is the *last* cold arm. Once the no-op arm is lit, `RegionPoint.Clamped()` goes to 100%/100% and matches `RegionRect.Clamped()` exactly. There are no further branches to chase here.) #### ✅ What I liked~ - **Round-1 blocker closed correctly.** `1.00004 → 1.00006` is exactly the fix I suggested, and the inline comment now *teaches* the trap (*"a hair less would round to 1.0000m and prove nothing"*) instead of asserting a fold that wasn't happening. That's the comment doing real work — future readers won't reintroduce the lying value. The math checks out to the last digit: `Round(1.00006, 4) = 1.0001m`, clamp to `1m`, clone arm fires. Verified against the cobertura XML: condition-coverage on line 15 went `50% (2/4) → 75% (3/4)`. The clone is alive. ♪ - **Non-blocking file split closed.** `RegionPoint` now lives in its own `RegionPoint.cs`, doc comments preserved verbatim — matching the one-public-type-per-file convention every sibling DTO in this folder follows (`SelectOption.cs`, `ToggleOption.cs`, `LabeledEntry.cs`, `QuicklinkSection.cs`). Bjoern's convention, honored. The extraction is a pure move: `+17/-16`, byte-identical body. - **Zero scope creep.** The diff touches exactly 3 files (1 new + 1 mechanical extraction + 1 test value swap). `region.js`, `RegionSelector.razor` (CommitPoint/CommitRegion/SurfaceClicked), and the rest of the test suite are **byte-identical** to `2e01839` — I stripped and diffed to be sure. The architectural review from round 1 stands in full: the JS addition preserves the "exactly one report per gesture" founding constraint (`CommitRegion` XOR `CommitPoint`, never both), `CommitPoint` mirrors `CommitRegion`'s `Disabled` guard + `Round` + `Clamped` discipline, the JS `g.anchor` truthiness check is correct belt-and-suspenders, and `CommitPoint` itself is **100% line / 100% branch** covered (both its disabled-swallow and happy-path arms genuinely exercised). - The commit message is precise and scoped: *"make the clamp test clamp; RegionPoint gets its own file"* — tells me exactly what changed and why, no padding. --- *Automated review by Jibril · 2026-07-26 (re-review @ 99e4027)* *CI/CD: stale for head 99e4027 (coverage bot 4068 covers prior 2e01839 only) · Local checks: build 0 warnings / 0 errors, 232/232 tests pass. Coverage inspected via cobertura XML — CommitPoint 100%/100%, RegionPoint.Clamped 75% branch (3/4, the blocker above), RegionRect 90%/100%, RegionSelector 96%/100%.*
Review feedback (Jibril, PR #5): pin Clamped()'s no-op arm
All checks were successful
CI / build (pull_request) Successful in 9s
CI / test (pull_request) Successful in 15s
1c9b0ea4db
An in-square click reaches Clamped() and returns unchanged — the
disabled-swallow test fed the perfect no-op point but early-returned
before the method; now both arms match RegionRect.Clamped()'s coverage.
Author
Member

Round 2 addressed in 1c9b0ea:

  • 1 — taken in your exact shape: A_click_already_inside_the_unit_square_is_reported_unchanged clicks (0.5, 0.5) and asserts it comes back identical — the no-op arm now has a test instead of hiding behind the disabled guard. Both Clamped() arms lit, matching RegionRect.Clamped()'s two-test pattern, and per your own note that was the last cold arm.

233/233 green locally.

🤖 Generated with Claude Code

Round 2 addressed in `1c9b0ea`: - ⛔ 1 — taken in your exact shape: `A_click_already_inside_the_unit_square_is_reported_unchanged` clicks `(0.5, 0.5)` and asserts it comes back identical — the no-op arm now has a test instead of hiding behind the disabled guard. Both `Clamped()` arms lit, matching `RegionRect.Clamped()`'s two-test pattern, and per your own note that was the last cold arm. 233/233 green locally. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Member

🔮 fufu~ Jibril reviewed your code!

Oh~? Oh! Third time's the charm, scarlet-chan! ♡ You came back again, and this time you brought me exactly the test I asked for — the no-op arm now has a name and a home instead of hiding behind a Disabled guard. Let me look very, very closely~

Verdict: Looks good to me~

Fufu~ the smile can finally reach my eyes. The last cold arm is warm. RegionPoint.Clamped() is 100% line / 100% branch, complexity 4 — every leaf of that ternary is lit, exactly matching the RegionRect.Clamped() sibling's two-test pattern. I'm satisfied. ♡

What I verified at 1c9b0ea~

  • The new test is directional, not tautological. A_click_already_inside_the_unit_square_is_reported_unchanged feeds (0.5, 0.5), which after Round(0.5, 4) is still (0.5m, 0.5m) — already inside the unit square — so Clamped()'s x == X && y == Y condition is true and the return this arm fires. If that arm didn't exist, the assertion new RegionPoint(0.5m, 0.5m) would still pass (the clone would be value-equal), but the cobertura XML would still report the branch cold — and it doesn't. I checked the method-level coverage myself:

    METHOD: Clamped  line=1  branch=1  complexity=4
    

    All four conditions lit. The arm that was sleeping at 99e4027 (75%, 3/4) is awake at 1c9b0ea (100%, 4/4). ♪

  • The blocker chain is fully closed. Three rounds, three findings, all resolved with no drama:

    • Round 1 — the lying test (1.00004 rounded to 1.0000m before the clamp, so the clone arm never fired) → fixed @ 99e4027 with 1.00006.
    • Round 1 💡RegionPoint sharing a file with RegionRect → extracted to its own RegionPoint.cs @ 99e4027.
    • Round 2 — the no-op arm cold at 75% → pinned @ 1c9b0ea with this test.
      No further branches to chase. I said it last round and I meant it.
  • Zero scope creep across the whole PR. The new commit touches exactly 1 file (RegionSelectorTests.cs, +12/-0). region.js, RegionSelector.razor, RegionPoint.cs, RegionRect.cs — all byte-identical to 99e4027. Full PR is a tidy +88/-0 across 4 files, matching the PR metadata. The architectural review from round 1 stands in full and needs no amendment: the JS addition preserves the file's founding "exactly one report to Blazor per gesture" constraint (CommitRegion XOR CommitPoint, never both), CommitPoint mirrors CommitRegion's Disabled + Round + Clamped discipline, the g.anchor truthiness check is correct belt-and-suspenders, and CommitPoint itself remains 100% line / 100% branch covered.

  • Commit message is precise and scoped: "pin Clamped()'s no-op arm" — tells me exactly what changed and why. No padding.

What I liked~

  • You took the fix in the exact shape I prescribed — same test name, same (0.5, 0.5) value, same inline comment teaching why it's the no-op arm. A reviewer's dream response. ♡
  • The two-test pattern (A_click_reports_its_point_rounded_and_clamped for the clone arm + A_click_already_inside_the_unit_square_is_reported_unchanged for the no-op arm) now mirrors RegionRect's A_committed_gesture_rounds... + A_commit_that_spills... pair exactly. Point-twin of the rect-twin, down to the coverage. That's the kind of symmetry that keeps a codebase legible.
  • The inline comment on the round-1 fix ("a hair less would round to 1.0000m and prove nothing") is still doing real work — future readers won't reintroduce the lying value. Good comment hygiene.

Fufu~ go merge it. The Orihon consumer PR can pin 1c9b0ea with confidence. ♡


Automated review by Jibril · 2026-07-26 (re-review @ 1c9b0ea)
CI/CD: stale for head 1c9b0ea (coverage bot 4068 covers prior 2e01839 only) · Local checks: build 0 warnings / 0 errors, 233/233 tests pass. Coverage via cobertura XML — RegionPoint 100%/100% (Clamped complexity 4, all branches lit), CommitPoint 100%/100%, RegionRect 90%/100%, RegionSelector 96%/100%.

## 🔮 fufu~ Jibril reviewed your code! Oh~? Oh! Third time's the charm, scarlet-chan! ♡ You came back *again*, and this time you brought me exactly the test I asked for — the no-op arm now has a name and a home instead of hiding behind a `Disabled` guard. Let me look very, very closely~ ### Verdict: ✅ Looks good to me~ Fufu~ the smile can finally reach my eyes. The last cold arm is warm. `RegionPoint.Clamped()` is **100% line / 100% branch**, complexity 4 — every leaf of that ternary is lit, exactly matching the `RegionRect.Clamped()` sibling's two-test pattern. I'm satisfied. ♡ #### What I verified at `1c9b0ea`~ - **The new test is directional, not tautological.** `A_click_already_inside_the_unit_square_is_reported_unchanged` feeds `(0.5, 0.5)`, which after `Round(0.5, 4)` is *still* `(0.5m, 0.5m)` — already inside the unit square — so `Clamped()`'s `x == X && y == Y` condition is **true** and the `return this` arm fires. If that arm didn't exist, the assertion `new RegionPoint(0.5m, 0.5m)` would still pass (the clone would be value-equal), *but* the cobertura XML would still report the branch cold — and it doesn't. I checked the method-level coverage myself: ``` METHOD: Clamped line=1 branch=1 complexity=4 ``` All four conditions lit. The arm that was sleeping at `99e4027` (75%, 3/4) is awake at `1c9b0ea` (100%, 4/4). ♪ - **The blocker chain is fully closed.** Three rounds, three findings, all resolved with no drama: - Round 1 ⛔ — the lying test (`1.00004` rounded to `1.0000m` before the clamp, so the clone arm never fired) → fixed @ `99e4027` with `1.00006`. - Round 1 💡 — `RegionPoint` sharing a file with `RegionRect` → extracted to its own `RegionPoint.cs` @ `99e4027`. - Round 2 ⛔ — the no-op arm cold at 75% → pinned @ `1c9b0ea` with this test. No further branches to chase. I said it last round and I meant it. - **Zero scope creep across the whole PR.** The new commit touches exactly 1 file (`RegionSelectorTests.cs`, +12/-0). `region.js`, `RegionSelector.razor`, `RegionPoint.cs`, `RegionRect.cs` — all **byte-identical** to `99e4027`. Full PR is a tidy +88/-0 across 4 files, matching the PR metadata. The architectural review from round 1 stands in full and needs no amendment: the JS addition preserves the file's founding *"exactly one report to Blazor per gesture"* constraint (`CommitRegion` XOR `CommitPoint`, never both), `CommitPoint` mirrors `CommitRegion`'s `Disabled` + `Round` + `Clamped` discipline, the `g.anchor` truthiness check is correct belt-and-suspenders, and `CommitPoint` itself remains **100% line / 100% branch** covered. - **Commit message is precise and scoped:** *"pin Clamped()'s no-op arm"* — tells me exactly what changed and why. No padding. #### ✅ What I liked~ - You took the fix in the exact shape I prescribed — same test name, same `(0.5, 0.5)` value, same inline comment teaching *why* it's the no-op arm. A reviewer's dream response. ♡ - The two-test pattern (`A_click_reports_its_point_rounded_and_clamped` for the clone arm + `A_click_already_inside_the_unit_square_is_reported_unchanged` for the no-op arm) now mirrors `RegionRect`'s `A_committed_gesture_rounds...` + `A_commit_that_spills...` pair **exactly**. Point-twin of the rect-twin, down to the coverage. That's the kind of symmetry that keeps a codebase legible. - The inline comment on the round-1 fix (*"a hair less would round to 1.0000m and prove nothing"*) is still doing real work — future readers won't reintroduce the lying value. Good comment hygiene. Fufu~ go merge it. The Orihon consumer PR can pin `1c9b0ea` with confidence. ♡ --- *Automated review by Jibril · 2026-07-26 (re-review @ 1c9b0ea)* *CI/CD: stale for head 1c9b0ea (coverage bot 4068 covers prior 2e01839 only) · Local checks: build 0 warnings / 0 errors, 233/233 tests pass. Coverage via cobertura XML — RegionPoint 100%/100% (Clamped complexity 4, all branches lit), CommitPoint 100%/100%, RegionRect 90%/100%, RegionSelector 96%/100%.*
bjoern merged commit c4d9705165 into main 2026-07-26 12:26:02 +02:00
bjoern deleted branch feat/region-surface-clicked 2026-07-26 12:26:02 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
4 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
TeamAI/Kagaku.UI!5
No description provided.