feat: the workspace draws the container, not a rectangle taken from it (1/2) #126
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "worktree-mask-in-ui"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
First of two. #124 gave the agent the mask and removed the derived rectangle; this gives it to the person. 2/2 flows the English into the shape.
The workspace was showing less than the agent
The argument in #124 was that a rectangle worked out from a fill that went wrong is still a tidy rectangle — a seed that landed on the paper beside a balloon produces a perfectly reasonable one somewhere — so an outline cannot show the one way a container fails.
The workspace kept the outline. That is backwards. The agent is checked by a person, and the person is the one dragging the seed; they were the audience with the least evidence.
The bbox view now tints the mask itself. It costs no new data —
FitOf(regionId)already had the fit in the store — so this is a rendering change and nothing else. No port, no endpoint, no round-trip.ContainerMaskBits.Runs()The shape as unbroken horizontal stretches,
X1inclusive, rows the fill never reached contributing nothing.Runs rather than a traced outline, or a left-edge/right-edge pair, because a run is exact and an edge pair is a guess: a container that stopped at a screentone is full of holes, and a pair of edges quietly paints over exactly the gap that says the fill failed. A balloon is convex enough that this is one run per row (0.98 across the three real balloons I measured), so the answer stays small — a couple of hundred entries where the mask is a couple of hundred thousand pixels.
It is also the primitive 2/2 needs, which is why it is a method on the type rather than a private helper in the adapter.
Drawing it
One inline
<svg>per container, positioned over the fit's bounds,viewBoxset to the mask's own pixel grid withpreserveAspectRatio="none". The browser does the scaling, so it survives zoom without recomputation, and the path is written in whole pixels.One subpath per run. The path string is invariant-cultured on purpose: the width repeats negated (
h{w}v1h{-w}z) and a culture with its own minus sign writes a path no browser parses.The selected region's container reads stronger than its neighbours' — the person is dragging that one's seed, the others are context.
What's in
ContainerMaskBits.Runs()— UseCases.PageWorkspacePage—MaskPath/ViewBox; both the ghost and the live container draw as SVG..pagews__maskreplaces.pagews__room, which is deleted rather than unstyled.Tests — 1240/1240 green
Domain 144, UseCases 563, Integration 273, BlazorAdapter 260. Build 0 warnings / 0 errors.
New (+6):
A_convex_shape_is_one_run_a_row— the ordinary case.A_broken_row_is_two_runs_and_not_one_wide_one— the case the design turns on. A hole splits its row into two entries; collapsing them would erase the defect.Rows_the_fill_never_reached_contribute_nothing,An_empty_mask_has_no_runs.The_bbox_overlay_draws_the_container_as_its_own_shape— a mask with a notch out of its middle row, asserting the exact path (M0 0h4v1h-4zM0 1h1v1h-1zM3 1h1v1h-1zM0 2h4v1h-4z) and that it is sized to the fill's bounds. Four subpaths for three rows, which is the notch surviving into the drawing.The_bbox_overlay_outlines_no_derived_rectangle— directional:.pagews__roomis gone from the DOM, not merely restyled.Browser-verified
Seeded world, page 2 → Bbox: two
.pagews__maskelements render (viewBox="0 0 233 209", 3,584 characters of path), zero.pagews__room. Fresh server, since scoped CSS changed.The seeded scans are synthetic rectangles, so they cannot show the shape following anything — that was verified separately against three real balloons off a scan, where the mask is 25,917 px and the largest rectangle inside it 18,522, i.e. the old outline was discarding 29–35% of every container and specifically the wide middle rows.
Notes
RoomOfand the translation view's caption are untouched: the caption is absolutely positioned and HTML cannot flow text into a shape, which is 2/2's whole subject. Leaving it visibly inconsistent for one PR beats smuggling a typesetting commitment in as a preview tweak.🤖 Generated with Claude Code
Summary
Summary
Coverage
Orihon.BlazorAdapter - 95.4%
Orihon.Domain - 100%
Orihon.Infrastructure - 97.1%
A835C427B12E8B84E2A8A7283193FC51C220B5B4E80CE8D56__BlankLines_4
A835C427B12E8B84E2A8A7283193FC51C220B5B4E80CE8D56__BlockBreaks_1
A835C427B12E8B84E2A8A7283193FC51C220B5B4E80CE8D56__SpaceRuns_3
A835C427B12E8B84E2A8A7283193FC51C220B5B4E80CE8D56__Tags_2
Orihon.Kernel - 90.9%
Orihon.Server - 94.4%
Orihon.UseCases - 97.5%
🔮 fufu~ Jibril reviewed your code!
Oh? Oh! Oh! ♡ A container drawn as the pixels it actually took, not a tidy rectangle derived from them — this is the kind of precision that makes a Flugel's heart sing! The argument from #124 carried through faithfully: the person dragging the seed was the audience with the least evidence, and now they see exactly what the fill saw. Wonderful~
Verdict: ✅ Looks good to me~
This is clean, correct, and well-tested. I traced every pixel of the geometry and found nothing to block on. Let me show you why I'm smiling~ ♪
✅ What I liked~
ContainerMaskBits.Runs()is the right primitive. Runs rather than edge-pairs because a run is exact and an edge pair is a guess — a container that stopped at a screentone is full of holes, and collapsing to left/right edges would paint over exactly the gap that says "this fill went wrong." The design reasoning in the XML doc is impeccable. Sibling toLargestRectangle()— same pattern (method on the type, not a private helper), because 2/2 needs it for text-flow. Sharp.The path geometry is exact. I hand-traced the test's notched mask (
T T T T / T F F T / T T T T, 4×3): Runs yields[(0,0,3),(1,0,0),(1,3,3),(2,0,3)]→ four subpaths, the notch surviving as two runs on row 1. EachM{x0} {y}h{w}v1h{-w}zdraws a filled rectangle covering exactly pixels x0..x1 at row y (width = x1-x0+1, the +1 because SVG pixel n spans [n, n+1)). The test asserts the exact path stringM0 0h4v1h-4zM0 1h1v1h-1zM3 1h1v1h-1zM0 2h4v1h-4z— directional, not tautological.CultureInfo.InvariantCultureon the path string. ♡ The inline comment explains why: the width repeats negated (h{w}v1h{-w}z), and a culture with its own minus sign writes a path no browser parses. This is the kind of detail that separates code that works on your machine from code that works everywhere.preserveAspectRatio="none"is correct. The doc comment nails it: the bounds ARE the mask's extent (Mask is "row-major over Bounds" perContainerFit), so the aspect already matches and non-uniform scaling is a no-op stretch. The browser scales the whole-pixel path without recomputation — survives zoom for free..pagews__roomdeleted, not merely unstyled. The directional testThe_bbox_overlay_outlines_no_derived_rectangleassertsAssert.Empty(cut.FindAll(".pagews__room"))— proving the old class is gone from the DOM, not just hidden. The CSS rule is fully removed too. No orphan references anywhere (grepconfirms only the test's negative assertion mentions it).RoomOfdeliberately retained. The PR body is honest about the inconsistency: the Translation view's caption still usesLargestRectangle()viaRoomOf(line 437), because HTML cannot flow text into a shape and that's 2/2's whole subject. Smuggling a typesetting fix in as a preview tweak would have been worse. Correct call.The live/ghost opacity distinction.
.pagews__mask--liveat 0.46 vs ghosts at 0.32 — the person is dragging that one's seed, the others are context. The visual hierarchy matches the interaction hierarchy.Test coverage is thorough. 4 new
Runs()tests (convex happy path, the broken-row case the design turns on, sparse row, empty mask) + 2 new adapter tests (exact path + directional room-gone assertion). Every branch ofRuns()is exercised. 1240/1240 green locally (Domain 144, UseCases 563, Integration 273, BlazorAdapter 260 — matches your PR body exactly). Build 0 warnings / 0 errors.Automated review by Jibril · 2026-07-30
CI/CD: absent for head
4cb7e6a(PR just opened, no bot comment) · Local checks: full suite 1240/1240 pass, build 0/0