Skip to content

Prune redundant unit tests and cover what they missed - #383

Merged
drehtuer merged 19 commits into
feature/designer-material-and-roundnessfrom
feature/test-cleanup
Oct 5, 2026
Merged

drehtuer merged 19 commits into
feature/designer-material-and-roundnessfrom
feature/test-cleanup

Conversation

@drehtuer

@drehtuer drehtuer commented Oct 5, 2026

Copy link
Copy Markdown
Owner

Prunes redundant unit tests and adds tests where function or branch coverage was missing, across all 29 measured modules. Five agents did the work in parallel, one set of modules each, and their branches were merged locally.

Based on feature/designer-material-and-roundness, which sits on #381. That branch has no PR of its own yet.

What changed

  • About 40 redundant tests were removed or merged. In each case another test asserts the same outcome on the same path, or the test's assertions are a strict subset of a broader test's. Where the removed test carried the only explanation, its comment moved to the test that keeps the case. Duplicated fakes (SilentTray, DirectTray, presenter setup) now live once in RollScreenFakes. One "test" could never fail: ShapeHullTest compared each corner with the maximum over the same corners.
  • About 200 tests were added. They target untested functions, error paths, boundaries and when arms, not Compose $changed bookkeeping or getters. New files include StandardDiceTest, VerifyDeviceTestResultsTaskTest (the only verdict on device runs), WaysBetweenScreensTest, ChosenFilesTest, RollWiringTest, GraphChartTest, EarnedThrowTest, TrayPlanInkTest, TrayLoopEndingsTest and TimeStampTest.
  • Four existing tests did not test what their names claimed, and now do:
    • "saving leaves the editor, and so does deleting" never deleted.
    • The install test never checked the running state.
    • The update test bypassed the sheet.
    • The "no downloader" test was handed a downloader.
  • StatsScreenTest, HistoryScreenTest and TrayLoopTest reached detekt's LargeClass limit. Their session, forget and stall tests moved to files of their own; no test was lost in the move.
  • One production change, no behaviour change: RollWiring.table(pinned) went from private to internal, so the table precedence rule (pin → chosen → bundled → fallback) can be tested without a Jolt run.
  • docs/STATUS.md and docs/TODO.md record the new coverage figures, and list the problems the pass found.

Coverage (JaCoCo, merged JVM + Robolectric; base 3a342c5d → this branch)

No module drops on either counter.

Function Branch
Total 91.1 % → 92.8 % (629 → 505 missed) 73.2 % → 75.4 % (4380 → 4024 missed)
Per module
app                  M  72.9-> 78.8  B  61.0-> 62.5
core/collection      M  94.4-> 95.8  B  86.9-> 95.6
core/glyphs          M  94.8-> 94.8  B  92.7-> 93.1
core/model           M  96.8-> 99.4  B  96.0-> 98.3
core/notation        M  96.7-> 97.3  B  92.4-> 94.3
core/probability     M  99.0-> 99.0  B  89.0-> 91.6
core/stats           M  92.5-> 92.5  B  96.4-> 99.1
data                 M  90.5-> 91.7  B  83.3-> 86.9
designer             M  97.4-> 97.4  B  88.0-> 92.7
dicesets/builtin     M  91.7-> 91.7  B  87.5-> 87.5
dicesets/format      M  93.8-> 94.5  B  87.9-> 96.6
dicesets/install     M  98.1-> 98.1  B  84.2-> 89.9
feature/designer     M  98.1-> 98.5  B  65.7-> 65.7
feature/graph        M  91.5-> 95.8  B  61.3-> 68.3
feature/roll         M  94.5-> 96.0  B  70.7-> 71.3
feature/saved        M  93.3-> 96.1  B  63.1-> 65.0
feature/sets         M  94.9-> 97.2  B  68.0-> 69.7
feature/settings     M  92.9-> 99.3  B  62.6-> 63.8
feature/stats        M  97.0-> 98.6  B  64.4-> 65.3
feature/tables       M  98.9-> 98.9  B  66.6-> 70.3
feedback             M  96.8-> 96.8  B  89.3-> 90.5
input/shake          M  98.3-> 98.3  B  94.2-> 95.3
render/filament      M  76.6-> 76.8  B  73.3-> 74.9
render/headless      M  76.5-> 79.4  B 100.0->100.0
simulation/api       M  96.2-> 96.2  B  91.9-> 94.9
simulation/harness   M  92.6-> 92.6  B  94.2-> 95.2
simulation/jolt      M  72.0-> 74.0  B  81.0-> 81.0
test-fixtures        M  67.2-> 89.6  B  73.3-> 83.3
ui/common            M  93.5-> 99.4  B  62.3-> 65.1

Most of the branches still missed are Compose skip branches, plus the device-only bridges (FilamentStage, TrayDriver, FilamentEngine, JoltWorld), which were deliberately not faked into coverage.

Found, not fixed (now in docs/TODO.md)

These are kept out of this PR so that no new test locks in a wrong answer:

  • SafeExtractor extracts tar device and FIFO entries as empty files. TarArchiveEntry.isFile is true for anything that is not a directory, so the documented refusal never fires for them. This is not exploitable, but it is a validator gap.
  • InstallSource.gitea drops a subfolder and installs from the repository root.
  • GraphMachine.redraw() resets truncatedMass to 0.
  • ReferencedFile.staysInsidePackage refuses a path with a trailing /.
  • There is a short list of dead code and guards that can never be false.

Checks

  • ./gradlew test coverageReport detekt ktlintCheck lint: green in the devcontainer, no new warnings.
  • build-logic's own tests: green.
  • No device tests were needed: no device-tested code changed.

🤖 Generated with Claude Code

drehtuer and others added 19 commits October 5, 2026 06:50
Removed tests whose every assertion another test already makes on the
same path:
- DieMaterialTest "clamp leaves a finite value inside its range alone"
  (covered by "a value already inside its range is left alone")
- BuiltinDiceSetTest "a d2 is a coin, not a cube pretending to be one"
  (shape, values and order are held by the solid, value-range and
  shared-fixture tests)
- NotationErrorTest "every error code the parser can raise is reachable"
  (asserted only Empty and SpacedDice, each with a test of its own)
- NotationLimitsTest "the explosion depth is what bounds an exploding
  chain" and "500d6 parses" (subsets of "the published numbers" and the
  thousand-dice boundary)
- CollectionReaderTest "a field the format does not know is ignored",
  folded into the pre-pinning import test that exercises the same rule
- ReferencedFileTest "a good path has nothing to say about it" (implied
  by "an ordinary relative path is fine")

Added tests for what nothing asserted: StandardDice's labels and set
variations; ReferencedFile.staysInsidePackage, the extractor's sandbox
check, which had no test in its own module; Formula.divides, which
decides whether the result sheet offers rounding; collection entries
that are not objects, lists that are not lists and the problem's
printed form; a long .json name that is not a collection (the old cases
were all refused by length alone); the package-wide texture budget;
format 0; table id and name errors; and smaller boundaries in the
model, stats, glyph and probability code.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 14f3325d6dfa47a593d0ac42b21ff5eda2cef5d1)
ShapeHullTest "no corner of any solid pokes out through a face" compared
every corner with the largest projection of those same corners, so it
could not fail. Convexity is held by ShapeGeometryTest's "every corner
is outside every face" and the face-depth tests; the tautology goes.

Added: ClearSpace.roomForAnother with dice already promised, both for
floor and for the engine's cap, which nothing exercised; TableGeometry's
refusal of a width or aspect that is not a number; negative indices for
BoardTrack.poseAt and Tumble; a contact strength below zero; negative
turning in SimulationOutcome; the harness reading "no" as no, and
naming a section that is not an object or a value that is not plain.

build-logic gets a test for VerifyDeviceTestResultsTask, the only
verdict on a device run now that AGP's own cannot pass over WiFi: it
must still fail on a failing or erroring test, on no results where some
were expected and on a report of no tests, and must not fail on a test
that declined to run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit df4895c3ec8da5175f110d0b7cf45c5f2f3bd423)
DraftFile was the designer's weakest class for branches: a face entry,
mark, colour, nib or dot list that is the wrong JSON type was never fed to
it, so the promise that a hand-edited draft loses only the broken marks
was unchecked. Adds those cases, plus negative cells, empty pips, a
defaults table that only moves translucency, a DEL in a set name, and
photo equality and subsampling on the axis that was never tried.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 86574f1c007caab313033669ddd8c02e3eb5b096)
A tar hard link was never handed to SafeExtractor (only a symlink was),
nor a one-byte file or an unpack folder that cannot be made. The forge
URL parser had no test for www.github.com, a bare gitlab project, a tree
with no ref, a gitlab subfolder or a gitea URL with no repository; the
ref resolver none for a reply that names its commit in another forge's
field or with non-hex characters; the fetcher none for a redirect with
no or an unusable Location.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 779404b5a985f7936881474051f9d8ed95c6176e)
'a die resting on another is not read', 'a die that ends standing on
another is counted as stacked, and as unread' and 'a tapped roll still
ends the moment its dice are at rest' assert a strict subset of what
'a die that cannot be read is left where it lies' and 'a throw of dice
that simply settle is read off their faces' already assert on the same
world; their reasoning is folded into those tests' comments.

countedSoFar, which the roll screen follows every frame, was never read
by a test on either RollLoop or LiveRoll, nor LiveRoll.impacts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 4268c71d5ad9a9cd20ed3aeed8d33753a78ae446)
Neither TrayLoop nor PowerSavingTray had a test for a stalled roll: the
dice offered back through onStalled, the faces already read reported
through onCounted (power-saving only, and not when there are none), and
no outcome or impact playback. TrayLoop.look was never called by a test.
These live in a new TrayLoopEndingsTest because TrayLoopTest is at
detekt's LargeClass limit. Also covers a die face with a uv short and a
thumbnail plan with no height.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit d45502e27051252103250b5857e00dd74b1b7a28)
HeadlessRendererTest's 'a frame says where every die is' only read data
class fields back; the blend test now asserts the one thing it covered
that mattered, that a blended die keeps its index. ShakeDetectorTest's
'a recorder counts what it kept' tested ShakeRecorder in the wrong file
and only by size; ShakeRecorderTest now checks that reset also restarts
the step clock. Adds the stepsTaken default, a non-increasing gyroscope
timestamp, and an amplitude above what the vibrator API takes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit e7d8af6a986485fc70b82e8fe4508d33e2819cb1)
Folds four tests into the ones that already asserted the same path with
more (the export sheet opening, a group sheet opening, an existing roll
offering Delete, a clash naming what to do, a roll with no pin) and adds
tests for the behaviour nothing pressed: Move down on the grip, closing
the switcher, a long press on a group, marks, the empty group's New,
deleting from the group sheet, un-nesting a group, the editor's group
chooser, clearing a mark or colour, a formula naming a missing set,
deleting leaving the editor, the import screen's reading state and its
file picker, and the presenters' guards against stale presses.

The no-downloader import test now really builds the presenter without
one; it was handing in a stand-in and never ran the default it named.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 42c5c523b4d2e119007355348c530f23ed992981)
Drops the accessibility test that only re-asserted the histogram being
on screen (StatsScreenTest opens a die and asserts the same), and folds
the history's "export asks which shape" into a test that also puts the
question away. New tests cover the paths nothing reached: the sheets'
backdrop dismissal, the line above the list for every order, roll-up and
session, the confirmation's wording, a never-thrown die's dash, a roll
landing on an open die, a die opened inside a session, a reset with
nothing confirmed, a set that stood in for another in the breakdown,
the observed-only range, the chart's two colours on a real rasteriser,
the session sheet's guards, and TimeStamp, the formatter the history
ships with and no test ever ran.

StatsScreenTest and HistoryScreenTest had reached detekt's LargeClass
limit, so the session tests move to StatsSessionsTest and the forget
tests to HistoryForgetTest, each with the setup it needs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit b3f531aa13cb86df9af3845c14ef703565e1814e)
Drops "tapping a row with nothing wired to it does nothing": the
nothing-wired test and "tapping a row asks to open it" already assert the
same tap, harmless and opening no sheet. The install-button test now
asserts the half its name promised (the button says Installing and is
dead while one runs), and the update test goes through the sheet's
Update button instead of calling the presenter, which no test pressed.

New tests: the fetch button installing from the typed link, the
installed and replaced titles, the backdrop putting both sheets away
without acting, the outdated count beside the check button, the minus
stepper and "under average", a weight quoted as a range, and the
export button sharing the package and saying so.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 5d8380ac0a6dd3b065e3b3f94281c12c3577e849)
The chart's Canvas was never drawn in a test (Robolectric's legacy
graphics skip the draw lambda), so nothing asserted which ink a bar is
drawn in, where the band and the roll's line go, or that a tap becomes a
total. GraphChartTest captures it under native graphics and checks each
ink. The screen now has a test for a tapped bar being named, and the
machine for a rolled total outside the distribution and for an exploding
formula's truncated tail.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit ed80ab9c44ab4dc5d586aece1f4f51297c79472a)
The working state, the full "use a photo" row, a typed name reaching the
library, answers arriving after the sheet was shut and removing a photo
that is not the chosen table had no test. A gated photo library fake
holds a photo at the door so the in-between states can be asserted.

Drops a thumbnail-presenter test whose assertions were already made by
TablesPresenterTest and TableThumbnailScreenTest.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit b6c203ac36a14a32ce892722770d71f953ab709c)
Removes four RollScreenTest cases whose assertions other tests already
make, and the copies of DirectTray, LandingRolls and SilentTray that had
drifted into DiceMenuTest, DiceTrayTest and RollAccessibilityTest in
place of the shared fakes.

Adds the screen over an exploding die waiting for its earned throw, the
tray's spoken description at each stage of a throw, TrayReading for
earned and stalled states, pixel tests for the debug plan's tints, the
formula tab's accent, the progress track on light and dark grounds and
a large die's pick ring, a real assertion for the progress rule's fill,
and machine tests for an unknown die and a rounding with nothing landed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit ce4f1229719cfa133ce01c199a77e04217606c22)
The developer screen had no test of a replay in flight, of the
"different faces" verdict as the screen words it, or of a replay under
another seed showing a result with no verdict. The presenter's verdict
had two untried arms: the same seed typed by hand, and a replay that
lands after the log was cleared. Settings had no test that its default,
unwired callbacks leave every control where it was, and the menu
glyph's drawing was never rasterised.

Drops "the whole row is the switch, not just the switch": its one
assertion (the power-saving row has a click action) is repeated by
"a switch is still the whole row, sentence and all", which now carries
its comment.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit b94c282bd5035f7a3603f5e4e9d562e17b6d614b)
DieSilhouette, the up chevron, the formula field's cross and the
formula error's wave were never drawn in a test: the legacy canvas
records nothing, so their draw lambdas showed as uncovered and a broken
drawing could not fail. Each now has one NATIVE-graphics test on the
pixels that tell the picture apart (round coin vs square cube, an arrow
pointing back, a cross rather than a box, a wave only under the blamed
characters). The wave's runs across a line break, OptionBox's role
semantics and its two fills had no assertions either.

Removed as covered elsewhere:
- TagTest "the two kinds do not look the same": a strict subset of
  "the three kinds do not look the same".
- RuleTest "the heavier weight is the default": "a block rule is 2 dp
  and a hairline is 1" already draws the default rule at 2 dp.
- PlateTest "a plate is tall enough for the words plus its padding":
  implied by the exact 7/8 dp padding test.
- PlateTest "the shadow a plate is lifted by is the small one": only
  compared two tokens whose exact values ModernistTest pins.
- SectionKickerTest "a kicker prints the words it was given": the first
  half of "a kicker follows the words when they change".
- FormulaFieldTest "a field with neither label nor hint still draws":
  same setup as "the field shows the formula it was given", which
  asserts more.
- FormulaFieldTest "a mistake with no obvious reading is offered no
  fix": the suggestion is FormulaError's decision and
  FormulaErrorTest asserts it.
- FormulaErrorTest "a mistake with an obvious reading is offered as a
  fix": the button is pressed in "taking the fix hands back the
  formula".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 123204d0f889f402ee5ba850c997e60d28447b2c)
Three settings writers (active group, active session, default table)
were never called by a data test, so the two-key table pin, its removal
when cleared, and the "both halves or neither" read had no assertion
here. Breakdown.read's per-field fallbacks (a value that is not a
number, a flag that is not a boolean, an unknown note, a missing
"asked") and a roll's label being written were untried, as were the
true side of HistoryEntry.hasBreakdown and a half table pin through
pin().

Removed as covered elsewhere:
- "a chosen accent is read back": LightBlue is one of the accents
  "every accent survives the round trip" writes and reads.
- "a default set is remembered even while that set is not installed":
  the same setDefaultSet/read path as "the set a plain d20 comes from is
  read back"; the repository never consults installed sets. Its comment
  and its id moved into that test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit bfe1bc5213e8f30bce17a9c0dca561b8ac2c0423)
The header's redo button was never pressed by a test, and the rule that
the eraser puts no colour in the pen (Palette's "chosen" ignores the
swatches while erasing) had no assertion.

Removed as covered elsewhere:
- "a d20 has twenty faces to move between": "scrolling to the last face
  leaves the fill buttons where they were" reaches face 19 of a d20.
- "tapping the strip moves to that face": "a face copied from the
  screen lands on the face the strip moved to" asserts the cell after a
  strip tap, and the announced-selection test covers the rest.
- "a tap on the canvas with a pen in hand leaves nothing": the same
  centre tap with the default pen as "a finger that touches and lifts
  without moving leaves nothing".
- "choosing a nib chooses it" and "tapping another die opens it": the
  "announced as the one that is chosen" tests click the same controls
  and assert selection, which the screen derives from state.nib and
  state.die.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 14d137ed543050fb2fd6ba3a91a1f93fe509be4b)
Most of the lambdas the navigation graph hands to its screens were
never invoked: the graph's Roll this and Save as roll, the saved list's
take/hold/new/import, the editor's chevron and Roll now, the tray's
strip and the result sheet's odds and save. WaysBetweenScreensTest
presses each and asserts where it lands, with which argument and, where
the design says so, with nothing left under the tray.

The file pickers' callbacks were unreachable without a picker.
ChosenFilesTest swaps in an ActivityResultRegistry that answers at once
and checks a collection read and imported, a file that is not one, one
that cannot be opened, a dismissed picker, and a chosen archive that is
refused with its cached copy removed.

RollWiring.table, the "pin, then chosen, then bundled, then fallback"
rule from docs/tables.md, was only reachable through a physics run. It
is now internal (production visibility change only, no behaviour
change) and RollWiringTest covers each arm, plus the per-visit reads of
the catalogue and the choice and power saving drawing no thumbnails.

Removed "the editor is opened on a roll, or on a new one": its route
assertions are repeated verbatim in "the editor can be opened on a new
roll with a formula already in it", which now also carries its one
extra check (an existing roll's route resolves to the editor).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 30e1a128a7572e95fe215beab9388424c4598dde)
Branch coverage is 75.4 % and function coverage 92.8 % after the pass, up
from 73.2 % and 91.1 %. The production problems the pass turned up are
listed in docs/TODO.md rather than fixed here, so this PR stays tests only
and none of the new tests asserts a behaviour that is about to change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@sonarqubecloud

sonarqubecloud Bot commented Oct 5, 2026

Copy link
Copy Markdown

@drehtuer
drehtuer added this pull request to stack #385 October 5, 2026 06:36
@drehtuer
drehtuer merged commit 14eb6db into feature/designer-material-and-roundness Oct 5, 2026
9 checks passed
@drehtuer
drehtuer deleted the feature/test-cleanup branch October 5, 2026 06:39
drehtuer added a commit that referenced this pull request Oct 5, 2026
Shows a "Preparing the dice" plate over the tray while a dice material
compiles. The plate carries a progress bar and an estimate of the time
left, so the wait after an install or update has a visible reason.

**Based on #383** (`feature/test-cleanup`), stacked as
`.claude/CLAUDE.md` asks.

## Why
Filament's materials are compiled on the phone for the driver that is
there (decision 46). They are kept in `codeCacheDir`, which Android
empties on every update. So the first launch after an install or update
spent about 3.2 s on a black tray. A translucent die, the glass table or
a textured table then stalled about 2 s mid-throw the first time it was
needed. Nothing said why.

## How
- **`FilamentEngine`** reports a compile only when the material isn't on
disk. It calls `started` before the compile and `finished` in a
`finally`, so the plate can't get stuck if a compile throws. A normal
launch reads the cache and shows nothing.
- **`ShaderWork` / `ShaderProgress`** (plain Kotlin, JVM-tested):
- `libfilamat` gives no progress, so the bar fills with elapsed time
against an estimate.
  - It is capped at 95 % until the compile actually ends.
- The text counts down "About N seconds left", then says "Almost done"
once the estimate has run out.
- **`ShaderTimings`** sets where the estimate comes from:
- Defaults are the Pixel 10a's figures: opaque 2.4 s and resin 2.0 s
measured; glass and table 2.0 s assumed.
- What this phone actually took is saved to
`noBackupFilesDir/shader-timings.txt`, which survives updates. The next
update's bar is then this phone's own figure.
  - A garbled line falls back to its own default.
- **Plumbing:**
- `RollThread.shaders: StateFlow<ShaderWork>` → `Tray.shaders`, which
defaults to never compiling, so fakes and the power-saving tray are
unchanged.
  - `TrayDriver` forwards it to `RollScreen`.
  - Power-saving mode makes no engine, so the plate never shows there.
- **UI (`PreparingTheDice`):**
- A `Plate` centred over the tray, under the controls, that ignores
touches.
- The title names what is being prepared: dice, translucent dice, glass
table or table.
- TalkBack gets one polite live announcement, and the bar has progress
semantics in whole per cent.
  - All strings are resources.
- **Docs:**
  - Decision 95.
- `docs/architecture.md`: threading, with a mermaid sequence diagram,
and the storage layout.
  - `docs/physics-and-rendering.md`: "Preparing the dice".
- Prototype option `1za` in `design/`, the `design/README.md` map, and
README's rendering bullet.

## Tests and coverage
- `ShaderWorkTest` (14), `ShaderTimingsTest` (10) and
`PreparingTheDiceTest` (11, Robolectric, including `RollScreen`
integration).
- render/filament: function 76.6 → 77.5 %, branch 73.3 → 74.6 %.
- feature/roll: function 94.5 → 94.6 %, branch 70.7 → 71.2 %.
- `test`, `detekt`, `ktlintCheck` and `lint` are green for
render/filament, feature/roll and app. render/filament's device-test
sources compile.

## Device check — Pixel 10a, 2026-10-05
Debug build of this branch, fresh install:
- **Cold compile.** After `run-as … rm -rf code_cache/materials` and a
relaunch, "Preparing the dice" (opaque) shows, then "Preparing the
table" (textured felt). Each has a moving bar and "About N seconds
left". The plate goes once the felt is drawn.
- **Learnt timings.** `no_backup/shader-timings.txt` holds
`opaque=2437`, `table=2243` after the first launch and `2451` / `2180`
after the second. The figures are stable, so the defaults are about
right.
- **Warm relaunch.** No plate at all.
- **Fresh install.** The first-launch welcome covers the whole screen
during the compile, so the plate is not seen there. The compile finishes
behind the welcome text.
- **render/filament device suite:** 60 tests. 58 passed and 0 failed. 2
declined (`RenderGalleryTest` and `RenderedHarnessTest` are opt-in and
skip themselves without `-e gallery` / `-e harness.rolls`).

For the owner to judge: the startup compile is shown as two plates in a
row ("dice" then "table"), each with its own bar starting from empty. Is
that fine, or should it be one bar across both? Also whether the wording
reads right.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant