test: pin the guard's whole shape and the card region's kinds #19

Merged
yuris merged 1 commit from test/close-surviving-mutations into main 2026-09-02 05:23:17 +00:00
Owner

Closes every mutation that survived ReviewSuite's battery on #18, plus the four the
review of this branch's first push turned up. Test-only: git diff main --stat is
test.luau | 83 +++--, nothing under timew/, so there are no behaviour changes. Every
one of them is a pre-existing gap rather than a defect in shipped code — each is a place
where the suite could not have noticed one.

Revised after review. The first push claimed two things that were not true, and the
review proved both false. They are corrected below and struck through where they stood:
the card row's shape was not closed by pinning child counts, and the two counts did
not close the hole the deleted label can come back through. See "What the review
changed".

1 — the guard's whole shape, not just its anchors

The guard is ^%d+:%d%d:%d%d$. On main, dropping either anchor survived the entire
suite, and so did tightening or loosening every single field:

G3 leading anchor only   (^%d+:%d%d:%d%d)   -> SURVIVED
G4 trailing anchor only  (%d+:%d%d:%d%d$)   -> SURVIVED
S1 hour  %d+ -> %d                          -> SURVIVED   <- the realistic one
S2 hour  %d+ -> %d*                         -> SURVIVED
S3 minute %d%d -> %d%d?                     -> SURVIVED
S4 second %d%d -> %d%d?                     -> SURVIVED
S5 minute %d%d -> %d+                       -> SURVIVED

Why the anchors survived: no input carried a clock with junk after it, so the trailing
$ was doing nothing any test could see. The one input with junk before a clock is the
empty-range prose, ... - 2026-09-02T00:00:00., and it ends in a period — so $
rejected it and the leading ^ was never what did the work. The accident of that period
was carrying the assert.

Why S1 matters most: it is not synthetic. timew summary :all prints a two-digit hour
for anyone past ten hours logged, and this file already feeds the parser "45:00:00" at
the 45-match state. Under S1 that renders as the reject arm's "0:00" —
indistinguishable from a real zero, in the panel's headline all-time total.

Nine inputs now pin the guard end to end — both anchors, the hour's width, and the minute
and second fields from both sides:

assert(T.parseRangeTotal("2026-09-02T00:00:00") == "0:00", ...)  -- leading anchor
assert(T.parseRangeTotal("1:02:03x")            == "0:00", ...)  -- trailing anchor
assert(T.parseRangeTotal("45:00:00")            == "45:00", ...) -- hour is %d+, not %d
assert(T.parseRangeTotal(":00:00")              == "0:00", ...)  -- hour is %d+, not %d*
assert(T.parseRangeTotal("1:2:03")              == "0:00", ...)  -- minute is %d%d
assert(T.parseRangeTotal("1:02:3")              == "0:00", ...)  -- second is %d%d
assert(T.parseRangeTotal("1:123:03")            == "0:00", ...)  -- minute is not %d+
assert(T.parseRangeTotal("1:02:034")            == "0:00", ...)  -- second is not %d+

The failure mode is one story for all of them. Anything that reaches the accept arm through
a loosened guard is a string Parse.clock also refuses, so it floors to 0 — and
Fmt.span(0) is "0s", not the reject arm's literal "0:00". The panel would publish
0s as the all-time scope's header total for a range it failed to parse
(allTotalText → scopeTotalText), indistinguishable from a real zero. (The first push
said "into the day header"; that was loose — the in-code comment was always accurate.)

2 — Parse.clock's own anchors

The guard comment leans on that flooring: "one of them reaches Parse.clock, which floors
to 0"
. Nothing in the suite held it — S12/S13, dropping either anchor of
^(%d+):(%d%d):(%d%d)$, both survived.

The realistic input pins neither, for the same reason as above: the prose field
"2026-09-02T00:00:00." carries junk on both sides, so either anchor alone already
rejects it and dropping one still floors it. Two inputs that discriminate:

assert(clock("W36 2026-08-31 Mon focus 0:12:34") == 0, ...)  -- leading anchor
assert(clock("1:02:03x")                         == 0, ...)  -- trailing anchor

The first is the SUMMARY fixture's own data line — a line that ends in a clock is not a
clock.

3 — the card region, as child kinds rather than child counts

#18 pinned #info.children == 2, which catches a Today: label coming back under the
clock. Both counts are pinned now, so the row's shape is as closed as the column's.

That was wrong on two counts, and the review demonstrated both.

Child count is not shape. X1 — buildCard returning ui.column instead of ui.row —
survived. The card silently restacks from side-by-side to vertical, the button lands
under the clock, nothing fails.

Two positions is not four. Reinstating the exact historical bug,
ui.label({ text = "Today: 2026-09-02T00:00:00." }), at every position the card region
offers, against the first push:

card column (panel.luau:772)  -> KILLED    test.luau:1857
card row    (panel.luau:782)  -> KILLED    test.luau:1858
left column (panel.luau:1104) -> SURVIVED
root row    (panel.luau:1120) -> SURVIVED

chips = left.children[4].children reads position 4 positionally, so an append at position
5 disturbed nothing.

The fix is a change of spelling, not of kind. A count is the right kind of contract —
what is defended is an absence, so there is no value to assert and counting is the cheapest
way to say "nothing here". It is the weakest spelling of it: -- 3 vs 2 names a
cardinality, not what arrived, so a legitimate new node and this regression are the same
failure, and bumping 2 to 3 is the locally rational move that silently disarms the
defence.

One function-scoped helper turns the two counts into four kinds-in-order comparisons:

same(kinds(root),              "column,separator,column", "the root row is ...")
same(kinds(left),              "label,row,label,column",  "the left column is ...")
same(kinds(left.children[2]),  "column,button",           "the card row is ...")
same(kinds(info), if m.isActive then "label,column" else "label,input", "the card's left column is ...")

All four positions die, and so does the card's kind, and each failure names the node that
arrived:

test.luau:1903: the root row is the two columns and the rule between them
  -- "column,separator,column,label" vs "column,separator,column"
test.luau:1904: the left column is title, card, recent label, chips
  -- "label,row,label,column,label" vs "label,row,label,column"
test.luau:1905: the card row is that column and the button, nothing beside them
  -- "column,button,label" vs "column,button"
test.luau:1906: the card's left column is the clock and the subject, nothing under them
  -- "label,input,label" vs "label,input"

and X1:

test.luau:1904: the left column is title, card, recent label, chips
  -- "label,column,label,column" vs "label,row,label,column"

"column,button,label" says a label came back beside the button. 3 vs 2 says a number
moved. A maintainer adding a legitimate node now has to write down what it is, and the diff
reads "column,button" → "column,button,badge", which is legible in review.

The helper is local inside compare: this file sits at Luau's 200-register file-scope
ceiling and one more top-level non-constant local fails to compile with
Out of local registers ... exceeded limit 200.

4 — the all-time total's parsed string

compare() checked same(m.scopeTotalText, if m.scope == "all" then m.allTotalText else m.dayTotalText). In all-time scope that is the same field twice, so it cannot fail on a
wrong parse — the suite fed "45:00:00" through the parser and never asserted the result.
One line at the fixture:

assert(wideModel.allTotalText == "45:00", "the all-time total is the parsed span, not what a loose guard let through: got " .. wideModel.allTotalText)

It kills S1 on its own, through the whole pipeline (stdout → parseRangeTotal →
state.allTotal → model), with the wrong value in the message: got 0:00.

Mutation results

Re-run in cp -r copies at /tmp — never the live checkout, since the running shell
hot-reloads this directory. "Before" is this branch's first push (e33744c), the tree the
review saw.

Mutation Site Before Now
S1 hour %d+ → %d timew.luau:174 SURVIVED killed :397
S2 hour %d+ → %d* timew.luau:174 SURVIVED killed :406
S3 minute %d%d → %d%d? timew.luau:174 SURVIVED killed :407
S4 second %d%d → %d%d? timew.luau:174 SURVIVED killed :408
S5 minute %d%d → %d+ timew.luau:174 SURVIVED killed :407
S5b minute %d%d → %d%d%d? timew.luau:174 SURVIVED killed :409
S5c second %d%d → %d%d%d? timew.luau:174 SURVIVED killed :410
S12 Parse.clock drop ^ parse.luau:77 SURVIVED killed :116
S13 Parse.clock drop $ parse.luau:77 SURVIVED killed :117
L1 label as the card's sibling panel.luau:1104 SURVIVED killed :1904
RT1 label appended to the root row panel.luau:1120 SURVIVED killed :1903
X1 buildCard returns ui.column panel.luau:764 SURVIVED killed :1904
G3 drop trailing $ timew.luau:174 killed :382 killed :389
G4 drop leading ^ timew.luau:174 killed :381 killed :388
S6 guard → "%d" timew.luau:174 killed :366 killed :373
S7 reject arm "0:00" → "0:0" timew.luau:174 killed :366 killed :373
P7 Fmt.span → Fmt.clock timew.luau:174 killed :372 killed :379
S14 Parse.clock hour %d+ → %d parse.luau:77 killed :109 killed :109
S15 Fmt.span s < 60 → s < 59 fmt.luau:27 killed :118 killed :125
S16 Fmt.span s < 60 → s <= 60 fmt.luau:27 killed :119 killed :126
C1 third child, card column panel.luau:772 killed :1857 killed :1906
C4b sibling, card row panel.luau:782 killed :1858 killed :1905
C2 prepend, card column panel.luau:765 killed :1844 killed :1872
C3 prepend, card row panel.luau:764 killed :1538 killed :1566
L2 prepend, left column panel.luau:1100 killed :1366 killed :1394
R1 append, right column panel.luau:1119 killed :1349 killed :1377

26 applied, 26 killed, zero survivors. The twelve that were open on the reviewed tree
are the top block.

Gates

viewModel: 12 states, 1775 field comparisons
ok
yuris/timew
  ok

0 errors, 0 warnings

Field comparisons 1751 → 1775: two child-count comparisons became four kind comparisons,
across twelve states. The nine guard asserts, the two Parse.clock asserts and the
allTotalText assert are file-scope asserts, not same() calls, so they do not move the
count.

What the review changed

Review finding Outcome
1 (blocking) S1 survives; add "45:00:00" Accepted. Added, plus the other four field boundaries and their two widening twins — the same finding, one field over
2 (blocking) "only the pair closes the hole" is false; two of four positions open Accepted, and closed rather than corrected — all four positions die, not just the suggested left-column count
3 (advisory) kinds-in-order beats a raw count; X1 survives Accepted in full. The counts are gone; X1 dies on the left column's kind string
4 (advisory) Parse.clock's anchors are unpinned Accepted. Out of scope as a source change, in scope as a suite assert, and the guard's comment leans on it
5 description says "day header" Corrected above
(in finding 1) scopeTotalText == allTotalText is a tautology Accepted; the parsed string is asserted at the fixture

For a reviewer

  • Four of the nine guard inputs are synthetic (:00:00, 1:2:03, 1:02:3, 1:123:03,
    1:02:034 — five, with 1:02:03x). The review flagged that the first push was
    inconsistent about which standard it applied. It is now consistent in the closing
    direction: the guard is pinned as a shape contract, exhaustively, and the
    realistic/synthetic distinction applies to which inputs model production output, not to
    which boundaries get defended. If you would rather see S2–S5c declared equivalent
    mutants and documented as such, that is a coherent alternative and it is one comment
    instead of five asserts.
  • kinds(right) is deliberately absent: the right column's mutations (R1, L2) were
    already dead, and #rightKids == 6 at test.luau:1574 stays a count because upgrading it
    would need a file-scope helper, which does not fit under the register ceiling. So the
    file now mixes both spellings, by force rather than by choice.
  • The kinds helper is rebuilt on each of the twelve compare() calls. Trivial, but it is a
    closure allocation in a hot-ish loop if this file grows.
Closes every mutation that survived `ReviewSuite`'s battery on #18, plus the four the review of this branch's first push turned up. Test-only: `git diff main --stat` is `test.luau | 83 +++--`, nothing under `timew/`, so there are no behaviour changes. Every one of them is a pre-existing gap rather than a defect in shipped code — each is a place where the suite could not have noticed one. > **Revised after review.** The first push claimed two things that were not true, and the > review proved both false. They are corrected below and struck through where they stood: > the card row's *shape* was **not** closed by pinning child counts, and the two counts did > **not** close the hole the deleted label can come back through. See "What the review > changed". ## 1 — the guard's whole shape, not just its anchors The guard is `^%d+:%d%d:%d%d$`. On `main`, dropping either anchor survived the entire suite, and so did tightening or loosening every single field: ``` G3 leading anchor only (^%d+:%d%d:%d%d) -> SURVIVED G4 trailing anchor only (%d+:%d%d:%d%d$) -> SURVIVED S1 hour %d+ -> %d -> SURVIVED <- the realistic one S2 hour %d+ -> %d* -> SURVIVED S3 minute %d%d -> %d%d? -> SURVIVED S4 second %d%d -> %d%d? -> SURVIVED S5 minute %d%d -> %d+ -> SURVIVED ``` Why the anchors survived: **no input carried a clock with junk after it**, so the trailing `$` was doing nothing any test could see. The one input with junk *before* a clock is the empty-range prose, `... - 2026-09-02T00:00:00.`, and it **ends in a period** — so `$` rejected it and the leading `^` was never what did the work. The accident of that period was carrying the assert. Why `S1` matters most: it is not synthetic. `timew summary :all` prints a two-digit hour for anyone past ten hours logged, and this file *already* feeds the parser `"45:00:00"` at the 45-match state. Under `S1` that renders as the reject arm's `"0:00"` — indistinguishable from a real zero, in the panel's headline all-time total. Nine inputs now pin the guard end to end — both anchors, the hour's width, and the minute and second fields from **both** sides: ```luau assert(T.parseRangeTotal("2026-09-02T00:00:00") == "0:00", ...) -- leading anchor assert(T.parseRangeTotal("1:02:03x") == "0:00", ...) -- trailing anchor assert(T.parseRangeTotal("45:00:00") == "45:00", ...) -- hour is %d+, not %d assert(T.parseRangeTotal(":00:00") == "0:00", ...) -- hour is %d+, not %d* assert(T.parseRangeTotal("1:2:03") == "0:00", ...) -- minute is %d%d assert(T.parseRangeTotal("1:02:3") == "0:00", ...) -- second is %d%d assert(T.parseRangeTotal("1:123:03") == "0:00", ...) -- minute is not %d+ assert(T.parseRangeTotal("1:02:034") == "0:00", ...) -- second is not %d+ ``` The failure mode is one story for all of them. Anything that reaches the accept arm through a loosened guard is a string `Parse.clock` also refuses, so it floors to `0` — and `Fmt.span(0)` is `"0s"`, not the reject arm's literal `"0:00"`. The panel would publish `0s` as the **all-time scope's** header total for a range it failed to parse (`allTotalText` → `scopeTotalText`), indistinguishable from a real zero. *(The first push said "into the day header"; that was loose — the in-code comment was always accurate.)* ## 2 — `Parse.clock`'s own anchors The guard comment leans on that flooring: *"one of them reaches `Parse.clock`, which floors to 0"*. Nothing in the suite held it — `S12`/`S13`, dropping either anchor of `^(%d+):(%d%d):(%d%d)$`, both survived. The realistic input pins neither, for the same reason as above: the prose field `"2026-09-02T00:00:00."` carries junk on **both** sides, so either anchor alone already rejects it and dropping one still floors it. Two inputs that discriminate: ```luau assert(clock("W36 2026-08-31 Mon focus 0:12:34") == 0, ...) -- leading anchor assert(clock("1:02:03x") == 0, ...) -- trailing anchor ``` The first is the `SUMMARY` fixture's own data line — a line that *ends* in a clock is not a clock. ## 3 — the card region, as child kinds rather than child counts ~~`#18` pinned `#info.children == 2`, which catches a `Today:` label coming back under the clock. Both counts are pinned now, so the row's shape is as closed as the column's.~~ **That was wrong on two counts, and the review demonstrated both.** *Child count is not shape.* `X1` — `buildCard` returning `ui.column` instead of `ui.row` — **survived**. The card silently restacks from side-by-side to vertical, the button lands under the clock, nothing fails. *Two positions is not four.* Reinstating the exact historical bug, `ui.label({ text = "Today: 2026-09-02T00:00:00." })`, at every position the card region offers, against the first push: ``` card column (panel.luau:772) -> KILLED test.luau:1857 card row (panel.luau:782) -> KILLED test.luau:1858 left column (panel.luau:1104) -> SURVIVED root row (panel.luau:1120) -> SURVIVED ``` `chips = left.children[4].children` reads position 4 positionally, so an append at position 5 disturbed nothing. **The fix is a change of spelling, not of kind.** A count is the right *kind* of contract — what is defended is an absence, so there is no value to assert and counting is the cheapest way to say "nothing here". It is the weakest *spelling* of it: `-- 3 vs 2` names a cardinality, not what arrived, so a legitimate new node and this regression are the same failure, and bumping `2` to `3` is the locally rational move that silently disarms the defence. One function-scoped helper turns the two counts into four kinds-in-order comparisons: ```luau same(kinds(root), "column,separator,column", "the root row is ...") same(kinds(left), "label,row,label,column", "the left column is ...") same(kinds(left.children[2]), "column,button", "the card row is ...") same(kinds(info), if m.isActive then "label,column" else "label,input", "the card's left column is ...") ``` All four positions die, and so does the card's kind, and each failure names the node that arrived: ``` test.luau:1903: the root row is the two columns and the rule between them -- "column,separator,column,label" vs "column,separator,column" test.luau:1904: the left column is title, card, recent label, chips -- "label,row,label,column,label" vs "label,row,label,column" test.luau:1905: the card row is that column and the button, nothing beside them -- "column,button,label" vs "column,button" test.luau:1906: the card's left column is the clock and the subject, nothing under them -- "label,input,label" vs "label,input" ``` and `X1`: ``` test.luau:1904: the left column is title, card, recent label, chips -- "label,column,label,column" vs "label,row,label,column" ``` `"column,button,label"` says a **label** came back beside the button. `3 vs 2` says a number moved. A maintainer adding a legitimate node now has to write down what it is, and the diff reads `"column,button"` → `"column,button,badge"`, which is legible in review. The helper is `local` **inside `compare`**: this file sits at Luau's 200-register file-scope ceiling and one more top-level non-constant local fails to compile with `Out of local registers ... exceeded limit 200`. ## 4 — the all-time total's parsed string `compare()` checked `same(m.scopeTotalText, if m.scope == "all" then m.allTotalText else m.dayTotalText)`. In all-time scope that is the same field twice, so it cannot fail on a wrong parse — the suite fed `"45:00:00"` through the parser and never asserted the result. One line at the fixture: ```luau assert(wideModel.allTotalText == "45:00", "the all-time total is the parsed span, not what a loose guard let through: got " .. wideModel.allTotalText) ``` It kills `S1` on its own, through the whole pipeline (stdout → `parseRangeTotal` → `state.allTotal` → model), with the wrong value in the message: `got 0:00`. ## Mutation results Re-run in `cp -r` copies at `/tmp` — never the live checkout, since the running shell hot-reloads this directory. "Before" is this branch's first push (`e33744c`), the tree the review saw. | Mutation | Site | Before | Now | | --- | --- | --- | --- | | `S1` hour `%d+` → `%d` | `timew.luau:174` | **SURVIVED** | killed `:397` | | `S2` hour `%d+` → `%d*` | `timew.luau:174` | **SURVIVED** | killed `:406` | | `S3` minute `%d%d` → `%d%d?` | `timew.luau:174` | **SURVIVED** | killed `:407` | | `S4` second `%d%d` → `%d%d?` | `timew.luau:174` | **SURVIVED** | killed `:408` | | `S5` minute `%d%d` → `%d+` | `timew.luau:174` | **SURVIVED** | killed `:407` | | `S5b` minute `%d%d` → `%d%d%d?` | `timew.luau:174` | **SURVIVED** | killed `:409` | | `S5c` second `%d%d` → `%d%d%d?` | `timew.luau:174` | **SURVIVED** | killed `:410` | | `S12` `Parse.clock` drop `^` | `parse.luau:77` | **SURVIVED** | killed `:116` | | `S13` `Parse.clock` drop `$` | `parse.luau:77` | **SURVIVED** | killed `:117` | | `L1` label as the card's sibling | `panel.luau:1104` | **SURVIVED** | killed `:1904` | | `RT1` label appended to the root row | `panel.luau:1120` | **SURVIVED** | killed `:1903` | | `X1` `buildCard` returns `ui.column` | `panel.luau:764` | **SURVIVED** | killed `:1904` | | `G3` drop trailing `$` | `timew.luau:174` | killed `:382` | killed `:389` | | `G4` drop leading `^` | `timew.luau:174` | killed `:381` | killed `:388` | | `S6` guard → `"%d"` | `timew.luau:174` | killed `:366` | killed `:373` | | `S7` reject arm `"0:00"` → `"0:0"` | `timew.luau:174` | killed `:366` | killed `:373` | | `P7` `Fmt.span` → `Fmt.clock` | `timew.luau:174` | killed `:372` | killed `:379` | | `S14` `Parse.clock` hour `%d+` → `%d` | `parse.luau:77` | killed `:109` | killed `:109` | | `S15` `Fmt.span` `s < 60` → `s < 59` | `fmt.luau:27` | killed `:118` | killed `:125` | | `S16` `Fmt.span` `s < 60` → `s <= 60` | `fmt.luau:27` | killed `:119` | killed `:126` | | `C1` third child, card column | `panel.luau:772` | killed `:1857` | killed `:1906` | | `C4b` sibling, card row | `panel.luau:782` | killed `:1858` | killed `:1905` | | `C2` prepend, card column | `panel.luau:765` | killed `:1844` | killed `:1872` | | `C3` prepend, card row | `panel.luau:764` | killed `:1538` | killed `:1566` | | `L2` prepend, left column | `panel.luau:1100` | killed `:1366` | killed `:1394` | | `R1` append, right column | `panel.luau:1119` | killed `:1349` | killed `:1377` | **26 applied, 26 killed, zero survivors.** The twelve that were open on the reviewed tree are the top block. ## Gates ``` viewModel: 12 states, 1775 field comparisons ok ``` ``` yuris/timew ok 0 errors, 0 warnings ``` Field comparisons `1751 → 1775`: two child-count comparisons became four kind comparisons, across twelve states. The nine guard asserts, the two `Parse.clock` asserts and the `allTotalText` assert are file-scope `assert`s, not `same()` calls, so they do not move the count. ## What the review changed | Review finding | Outcome | | --- | --- | | **1 (blocking)** `S1` survives; add `"45:00:00"` | Accepted. Added, plus the other four field boundaries and their two widening twins — the same finding, one field over | | **2 (blocking)** "only the pair closes the hole" is false; two of four positions open | Accepted, and closed rather than corrected — all four positions die, not just the suggested left-column count | | **3 (advisory)** kinds-in-order beats a raw count; `X1` survives | Accepted in full. The counts are gone; `X1` dies on the left column's kind string | | **4 (advisory)** `Parse.clock`'s anchors are unpinned | Accepted. Out of scope as a *source* change, in scope as a suite assert, and the guard's comment leans on it | | **5** description says "day header" | Corrected above | | (in finding 1) `scopeTotalText == allTotalText` is a tautology | Accepted; the parsed string is asserted at the fixture | ## For a reviewer - Four of the nine guard inputs are synthetic (`:00:00`, `1:2:03`, `1:02:3`, `1:123:03`, `1:02:034` — five, with `1:02:03x`). The review flagged that the first push was inconsistent about which standard it applied. It is now consistent in the *closing* direction: the guard is pinned as a **shape contract**, exhaustively, and the realistic/synthetic distinction applies to which inputs model production output, not to which boundaries get defended. If you would rather see `S2`–`S5c` declared equivalent mutants and documented as such, that is a coherent alternative and it is one comment instead of five asserts. - `kinds(right)` is deliberately absent: the right column's mutations (`R1`, `L2`) were already dead, and `#rightKids == 6` at `test.luau:1574` stays a count because upgrading it would need a *file-scope* helper, which does not fit under the register ceiling. So the file now mixes both spellings, by force rather than by choice. - The `kinds` helper is rebuilt on each of the twelve `compare()` calls. Trivial, but it is a closure allocation in a hot-ish loop if this file grows.
Closes the three mutations that survived the review of #18. All three were
pre-existing gaps, none of them a defect in shipped behaviour -- each is a
place where the suite could not have noticed one.

`Timew.parseRangeTotal`'s guard is `^%d+:%d%d:%d%d$`, and dropping EITHER
anchor survived the whole suite. No input carried a clock with junk after it,
so the trailing `$` was unpinned; the one input with junk before a clock -- the
"No filtered data found in the range ... 2026-09-02T00:00:00." prose -- ends in
a period, so `$` rejected it and the leading `^` never did the work it is there
for. Two inputs now pin one anchor each: the same prose case with the period
removed, and a clock with a byte appended. Without an anchor, one of them
reaches Parse.clock, floors to 0, and publishes Fmt.span(0) = "0s" where the
reject arm's literal "0:00" belongs.

`compare()` reads the card's `info` column and `action` button positionally out
of the card row, so a third node appended as their SIBLING was addressed by no
assert at all: #18 pinned the column's child count, and a reinstated
"Today: <total>" coming back beside the button rather than under the clock
would still have passed. Both counts are pinned now.

Test-only. No source file changes, so no behaviour changes.
yuris left a comment

Verdict: REQUEST_CHANGES. (Posted as a COMMENT review because Forgejo refuses REQUEST_CHANGES on your own PR: reject your own pull is not allowed.)

Reviewed by re-running everything from scratch in /tmp copies of main (7f2f419) and this
head (e33744c). Every claim in the description is true and every claimed kill lands at the
claimed line with the right message. I am still requesting changes, for two reasons: the guard
this PR exists to pin still has one boundary open, and that boundary is the realistic one; and
the new comment states the card hole is closed when it demonstrably is not.

Verified

Claim Proof
Both anchors were unpinned on main G3 (^%d+:%d%d:%d%d) and G4 (%d+:%d%d:%d%d$) applied to timew/timew.luau:174 on 7f2f419 both SURVIVED the whole suite
C4b was unpinned on main a ui.label appended after timew/panel.luau:782 SURVIVED on 7f2f419
C1 and P7 were already killed on main C1 → test.luau:1842, P7 → test.luau:372 on 7f2f419
G3 → test.luau:382, G4 → test.luau:381 reproduced exactly, with the messages naming the anchor each input pins
C4b → test.luau:1858, C1 → test.luau:1857, P7 → test.luau:372 reproduced exactly
The two new asserts are not redundant deleting :382 leaves G3 SURVIVING; deleting :381 leaves G4 SURVIVING. Each input pins exactly one anchor — the mapping in the description is exact, not approximate
Fmt.span(0) is "0s" and the reject arm is "0:00" Fmt.span(0) = "0s", Fmt.span(59) = "59s", Fmt.span(60) = "0:01", reject arm literal "0:00". Distinguishable
Both new inputs really reach Parse.clock and floor Parse.clock("2026-09-02T00:00:00") = 0, Parse.clock("1:02:03x") = 0
1739 → 1751, twelve states, one comparison each main prints 1739, this head prints 1751, 12 states both

Note Fmt.clock(0) is also "0:00", so the two new asserts do not overlap P7's kill at :372 —
they are independent of it, which is correct.

Gates

nix shell nixpkgs#luau -c luau test.luau, run in a copy of this head:

viewModel: 12 states, 1751 field comparisons
ok

noctalia plugins lint timew:

yuris/timew
  ok

0 errors, 0 warnings

git diff main...HEAD --stat is test.luau | 24 ++++-- and nothing under timew/, so lint is
unchanged from main by construction.

Mutations

Everything below was applied to a copy of this head and run against the suite as submitted.

Mutation Site Result
G3 drop trailing $ timew.luau:174 killed test.luau:382
G4 drop leading ^ timew.luau:174 killed test.luau:381
P7 Fmt.span → Fmt.clock timew.luau:174 killed test.luau:372
S6 guard → "%d" timew.luau:174 killed test.luau:366
S7 reject arm "0:00" → "0:0" timew.luau:174 killed test.luau:366
C1 third child, card column panel.luau:772 killed test.luau:1857
C4b sibling, card row panel.luau:782 killed test.luau:1858
C2 prepend, card column panel.luau:765 killed test.luau:1844
C3 prepend, card row panel.luau:764 killed test.luau:1538
L2 prepend, left column panel.luau:1100 killed test.luau:1366
R1 append, right column panel.luau:1119 killed test.luau:1349
S14 Parse.clock hour %d+ → %d parse.luau:77 killed test.luau:109
S15 Fmt.span s < 60 → s < 59 fmt.luau:27 killed test.luau:118
S16 Fmt.span s < 60 → s <= 60 fmt.luau:27 killed test.luau:119
S1 guard hour %d+ → %d timew.luau:174 SURVIVED
L1 label as the card's SIBLING in the left column panel.luau:1104 SURVIVED
X1 buildCard returns ui.column, not ui.row panel.luau:764 SURVIVED
RT1 label appended to the root row panel.luau:1120 SURVIVED
S2 guard hour %d+ → %d* timew.luau:174 SURVIVED (likely equivalent — timew emits no empty hour)
S3 guard minutes %d%d → %d%d? timew.luau:174 SURVIVED
S4 guard seconds %d%d → %d%d? timew.luau:174 SURVIVED
S5 guard minutes %d%d → %d+ timew.luau:174 SURVIVED
S12 Parse.clock drop ^ parse.luau:77 SURVIVED (outside this PR's region)
S13 Parse.clock drop $ parse.luau:77 SURVIVED (outside this PR's region)

Ten survivors. Four are actionable; S1 and L1 are the ones that matter.

The invariant question

You asked whether #children is the right contract or whether it trains a maintainer to bump the
number. My answer is: it is the right kind of contract and the weakest available spelling
of it, and the evidence for that is in the table above.

Right kind. What is being defended is an absence. There is no value to assert, because the
correct state is "no node here". Counting is the cheapest way to assert absence at a position, and
it is honest about defending a product decision (the day header already carries that number)
rather than a correctness property. A "stronger" assertion about the content of a node that is
supposed to be gone would be asserting about nothing. So I do not think reaching for a count was
wrong in principle.

Weakest spelling, on three counts.

  1. It does not say what arrived. -- 3 vs 2 names a cardinality. Swapping both lines for the
    child kinds in order costs one helper and zero extra field comparisons (still 1751), and the
    same two mutations then fail like this:

    test.luau:1858: idle, nothing fetched: the card left column is the clock and the subject,
    nothing under them -- "label,input,label" vs "label,input"
    test.luau:1859: idle, nothing fetched: the card row is that column and the button,
    nothing beside them -- "column,button,label" vs "column,button"
    

    "column,button,label" tells the maintainer a label came back beside the button. 3 vs 2
    tells them a number moved. That difference is the whole of your question: the first is read,
    the second is bumped. (The helper must be local inside compare — test.luau is at Luau's
    200 file-scope register ceiling and one more top-level non-constant local fails to compile.)

  2. It is bound to a position, and the label has more than two. See L1 and RT1 below. The
    in-diff comment says "only the pair closes the hole". The pair closes two of at least four.

  3. The file already has the stronger idiom and this PR reached past it. test.luau:1567 is
    assert(not onScreen("1-8 / 20"), "the range label the panel used to build is gone") — the
    same problem (a deleted label must not come back), solved position-independently, one screen
    away. And test.luau:1574 pins #rightColumn().children == 6 for the right column while
    nothing pins the left column's four.

Would a legitimate layout change break these for an unrelated reason? Yes, and cheaply: a
"paused" badge under the clock, a second glyph button, a divider in the card. Under #children
that failure is indistinguishable from the regression and the message ("nothing under them") reads
as a rule rather than a reason — so bumping 2 to 3 is the locally rational move, and it
silently disarms the defence. Under kinds-in-order the maintainer has to write down what the new
node is, and the diff then reads "column,button" → "column,button,badge", which is legible in
review. That is the difference between an invariant and a magic number.

Findings

Blocking

1. test.luau:1850-1858 — "only the pair closes the hole" is false; the label still has two open
positions.

The comment added by this PR says the two counts together close the hole because a third node
could otherwise come back beside the button. It can also come back beneath the card, in the left
column, and there it is addressed by nothing. Reinstating the exact historical bug —
ui.label({ text = "Today: 2026-09-02T00:00:00." }) — at all four positions the card region
offers, against the suite as submitted:

card column (panel.luau:772)  -> KILLED  test.luau:1857
card row    (panel.luau:782)  -> KILLED  test.luau:1858
left column (panel.luau:1104) -> SURVIVED
root row    (panel.luau:1120) -> SURVIVED

chips = left.children[4].children reads position 4 positionally, so an append at position 5
disturbs nothing. This is not a hypothetical shape — it is the deleted label, with the deleted
label's buggy text, back on screen, suite green.

The cheapest fix is the file's own convention, one line below test.luau:1574:

assert(#rightKids == 6, "the right column is trend, chart, separator, search, scope, scroll: got " .. #rightKids)
assert(#leftColumn().children == 4, "the left column is title, card, recent label, chips: got " .. #leftColumn().children)

Verified: suite stays green (1751), and L1 then dies with
test.luau:1575: the left column is title, card, recent label, chips: got 5.

A position-independent scan closes RT1 as well, using the allTexts walker already in the file,
and names the defect instead of a count — one line inside compare, all four positions dead:

test.luau:1859: idle, nothing fetched: a raw range timestamp reached the tree
-- Today: 2026-09-02T00:00:00.

Either fix is fine. What is not fine is shipping the sentence "only the pair closes the hole"
while the hole is open — that is precisely the "trains a maintainer to trust the number" failure
you asked me to hunt for, and here it is in the comment rather than in the assert.

2. test.luau:381-382 — the guard's realistic boundary is still unpinned, and the suite already
carries the fixture that would pin it.

S1, tightening the hour field from %d+ to %d, SURVIVES the suite as submitted. That
mutation is not synthetic: timew summary :all prints a two-digit hour for anyone with ten hours
logged, and under S1 the panel's headline all-time total renders "0:00" — the same
indistinguishable-from-real-zero failure this PR was written to defend against, one field to the
left of the anchors it pins.

The suite does feed two-digit hours through this parser — "20:00:00" at test.luau:1564 and
"45:00:00" at test.luau:2229 — but allTotalText is only ever asserted against itself
(test.luau:1896 compares scopeTotalText to allTotalText), so the parsed string is never
pinned. One line, next to the two new asserts:

assert(T.parseRangeTotal("45:00:00") == "45:00", "an all-time total is routinely past nine hours: the hour field is %d+, not %d")

Verified: suite stays green (1751), and S1 then dies at that assert.

This is also my answer to your question 3 about "1:02:03x". Pinning a boundary with an input the
upstream tool cannot emit is defensible on its own — a guard is a contract about a shape, and
timew's output growing a suffix is exactly the change you cannot see coming. But the PR is
inconsistent about which standard it applies. By the synthetic standard, four more boundaries of
the same guard are unpinned (S2, S3, S4, S5 all survive). By the "model real output"
standard, "1:02:03x" does not qualify and "45:00:00" does. Keep "1:02:03x" — it is doing real
work, and I confirmed it pins G3 alone — but adding "45:00:00" is what makes the guard's
coverage principled rather than accidental: after it, every field of ^%d+:%d%d:%d%d$ that
timew can actually exercise is pinned by an input timew can actually produce.

Advisory

3. test.luau:1858 — the card row's kind is not part of "the row's shape".

The description says "Both counts are pinned now, so the row's shape is as closed as the column's."
X1 — buildCard returning ui.column instead of ui.row at panel.luau:764 — SURVIVES.
The card silently restacks from side-by-side to vertical, the button lands under the clock, and
nothing fails. Child count is not shape; same(left.children[2].kind, "row", …) or the
kinds-in-order form above would cover it. Pre-existing rather than introduced here, so advisory —
but it undercuts the sentence, and the kinds-in-order rewrite gets it nearly for free.

4. timew/parse.luau:77 — Parse.clock's own anchors are unpinned (S12, S13 both survive).

Worth flagging only because this PR's stated failure mode leans on them: "one of them reaches
Parse.clock, which floors to 0". That story is correct today, and I confirmed it empirically,
but it is load-bearing on a guard the suite does not defend. Outside this PR's region; not a
change request.

5. Description wording. "The panel would publish 0s into the day header" — the range total
lands in allTotalText → scopeTotalText and is shown for the all-time scope. The in-code
comment ("0s" is published where "0:00" belongs) is accurate; only the PR body is loose.


Everything this PR claims, it does. G3, G4 and C4b were genuinely open on main, the five
kills land where stated, the anchor-to-assert mapping is exact, and the "0s" story is real.
Add the "45:00:00" assert and either close the left column or correct the comment, and I would
approve without further comment.

**Verdict: REQUEST_CHANGES.** (Posted as a `COMMENT` review because Forgejo refuses `REQUEST_CHANGES` on your own PR: `reject your own pull is not allowed`.) Reviewed by re-running everything from scratch in `/tmp` copies of `main` (`7f2f419`) and this head (`e33744c`). Every claim in the description is true and every claimed kill lands at the claimed line with the right message. I am still requesting changes, for two reasons: the guard this PR exists to pin still has one boundary open, and that boundary is the *realistic* one; and the new comment states the card hole is closed when it demonstrably is not. ## Verified | Claim | Proof | | --- | --- | | Both anchors were unpinned on `main` | `G3` (`^%d+:%d%d:%d%d`) and `G4` (`%d+:%d%d:%d%d$`) applied to `timew/timew.luau:174` on `7f2f419` both **SURVIVED** the whole suite | | `C4b` was unpinned on `main` | a `ui.label` appended after `timew/panel.luau:782` **SURVIVED** on `7f2f419` | | `C1` and `P7` were already killed on `main` | `C1` → `test.luau:1842`, `P7` → `test.luau:372` on `7f2f419` | | `G3` → `test.luau:382`, `G4` → `test.luau:381` | reproduced exactly, with the messages naming the anchor each input pins | | `C4b` → `test.luau:1858`, `C1` → `test.luau:1857`, `P7` → `test.luau:372` | reproduced exactly | | The two new asserts are not redundant | deleting `:382` leaves `G3` **SURVIVING**; deleting `:381` leaves `G4` **SURVIVING**. Each input pins exactly one anchor — the mapping in the description is exact, not approximate | | `Fmt.span(0)` is `"0s"` and the reject arm is `"0:00"` | `Fmt.span(0) = "0s"`, `Fmt.span(59) = "59s"`, `Fmt.span(60) = "0:01"`, reject arm literal `"0:00"`. Distinguishable | | Both new inputs really reach `Parse.clock` and floor | `Parse.clock("2026-09-02T00:00:00") = 0`, `Parse.clock("1:02:03x") = 0` | | `1739 → 1751`, twelve states, one comparison each | `main` prints `1739`, this head prints `1751`, `12 states` both | Note `Fmt.clock(0)` is also `"0:00"`, so the two new asserts do not overlap `P7`'s kill at `:372` — they are independent of it, which is correct. ### Gates `nix shell nixpkgs#luau -c luau test.luau`, run in a copy of this head: ``` viewModel: 12 states, 1751 field comparisons ok ``` `noctalia plugins lint timew`: ``` yuris/timew ok 0 errors, 0 warnings ``` `git diff main...HEAD --stat` is `test.luau | 24 ++++--` and nothing under `timew/`, so lint is unchanged from `main` by construction. ## Mutations Everything below was applied to a copy of this head and run against the suite as submitted. | Mutation | Site | Result | | --- | --- | --- | | `G3` drop trailing `$` | `timew.luau:174` | killed `test.luau:382` | | `G4` drop leading `^` | `timew.luau:174` | killed `test.luau:381` | | `P7` `Fmt.span` → `Fmt.clock` | `timew.luau:174` | killed `test.luau:372` | | `S6` guard → `"%d"` | `timew.luau:174` | killed `test.luau:366` | | `S7` reject arm `"0:00"` → `"0:0"` | `timew.luau:174` | killed `test.luau:366` | | `C1` third child, card column | `panel.luau:772` | killed `test.luau:1857` | | `C4b` sibling, card row | `panel.luau:782` | killed `test.luau:1858` | | `C2` prepend, card column | `panel.luau:765` | killed `test.luau:1844` | | `C3` prepend, card row | `panel.luau:764` | killed `test.luau:1538` | | `L2` prepend, left column | `panel.luau:1100` | killed `test.luau:1366` | | `R1` append, right column | `panel.luau:1119` | killed `test.luau:1349` | | `S14` `Parse.clock` hour `%d+` → `%d` | `parse.luau:77` | killed `test.luau:109` | | `S15` `Fmt.span` `s < 60` → `s < 59` | `fmt.luau:27` | killed `test.luau:118` | | `S16` `Fmt.span` `s < 60` → `s <= 60` | `fmt.luau:27` | killed `test.luau:119` | | **`S1` guard hour `%d+` → `%d`** | `timew.luau:174` | **SURVIVED** | | **`L1` label as the card's SIBLING in the left column** | `panel.luau:1104` | **SURVIVED** | | **`X1` `buildCard` returns `ui.column`, not `ui.row`** | `panel.luau:764` | **SURVIVED** | | **`RT1` label appended to the root row** | `panel.luau:1120` | **SURVIVED** | | `S2` guard hour `%d+` → `%d*` | `timew.luau:174` | SURVIVED (likely equivalent — timew emits no empty hour) | | `S3` guard minutes `%d%d` → `%d%d?` | `timew.luau:174` | SURVIVED | | `S4` guard seconds `%d%d` → `%d%d?` | `timew.luau:174` | SURVIVED | | `S5` guard minutes `%d%d` → `%d+` | `timew.luau:174` | SURVIVED | | `S12` `Parse.clock` drop `^` | `parse.luau:77` | SURVIVED (outside this PR's region) | | `S13` `Parse.clock` drop `$` | `parse.luau:77` | SURVIVED (outside this PR's region) | Ten survivors. Four are actionable; `S1` and `L1` are the ones that matter. ## The invariant question You asked whether `#children` is the right contract or whether it trains a maintainer to bump the number. My answer is: it is the right **kind** of contract and the weakest available **spelling** of it, and the evidence for that is in the table above. **Right kind.** What is being defended is an *absence*. There is no value to assert, because the correct state is "no node here". Counting is the cheapest way to assert absence at a position, and it is honest about defending a product decision (the day header already carries that number) rather than a correctness property. A "stronger" assertion about the *content* of a node that is supposed to be gone would be asserting about nothing. So I do not think reaching for a count was wrong in principle. **Weakest spelling, on three counts.** 1. *It does not say what arrived.* `-- 3 vs 2` names a cardinality. Swapping both lines for the child kinds in order costs one helper and zero extra field comparisons (still `1751`), and the same two mutations then fail like this: ``` test.luau:1858: idle, nothing fetched: the card left column is the clock and the subject, nothing under them -- "label,input,label" vs "label,input" test.luau:1859: idle, nothing fetched: the card row is that column and the button, nothing beside them -- "column,button,label" vs "column,button" ``` `"column,button,label"` tells the maintainer a **label** came back beside the button. `3 vs 2` tells them a number moved. That difference is the whole of your question: the first is read, the second is bumped. (The helper must be `local` inside `compare` — `test.luau` is at Luau's 200 file-scope register ceiling and one more top-level non-constant local fails to compile.) 2. *It is bound to a position, and the label has more than two.* See `L1` and `RT1` below. The in-diff comment says "only the pair closes the hole". The pair closes two of at least four. 3. *The file already has the stronger idiom and this PR reached past it.* `test.luau:1567` is `assert(not onScreen("1-8 / 20"), "the range label the panel used to build is gone")` — the same problem (a deleted label must not come back), solved position-independently, one screen away. And `test.luau:1574` pins `#rightColumn().children == 6` for the right column while nothing pins the left column's four. **Would a legitimate layout change break these for an unrelated reason?** Yes, and cheaply: a "paused" badge under the clock, a second glyph button, a divider in the card. Under `#children` that failure is indistinguishable from the regression and the message ("nothing under them") reads as a rule rather than a reason — so bumping `2` to `3` is the locally rational move, and it silently disarms the defence. Under kinds-in-order the maintainer has to write down *what* the new node is, and the diff then reads `"column,button"` → `"column,button,badge"`, which is legible in review. That is the difference between an invariant and a magic number. ## Findings ### Blocking **1. `test.luau:1850-1858` — "only the pair closes the hole" is false; the label still has two open positions.** The comment added by this PR says the two counts together close the hole because a third node could otherwise come back beside the button. It can also come back *beneath the card*, in the left column, and there it is addressed by nothing. Reinstating the exact historical bug — `ui.label({ text = "Today: 2026-09-02T00:00:00." })` — at all four positions the card region offers, against the suite as submitted: ``` card column (panel.luau:772) -> KILLED test.luau:1857 card row (panel.luau:782) -> KILLED test.luau:1858 left column (panel.luau:1104) -> SURVIVED root row (panel.luau:1120) -> SURVIVED ``` `chips = left.children[4].children` reads position 4 positionally, so an append at position 5 disturbs nothing. This is not a hypothetical shape — it is the deleted label, with the deleted label's buggy text, back on screen, suite green. The cheapest fix is the file's own convention, one line below `test.luau:1574`: ```suggestion assert(#rightKids == 6, "the right column is trend, chart, separator, search, scope, scroll: got " .. #rightKids) assert(#leftColumn().children == 4, "the left column is title, card, recent label, chips: got " .. #leftColumn().children) ``` Verified: suite stays green (`1751`), and `L1` then dies with `test.luau:1575: the left column is title, card, recent label, chips: got 5`. A position-independent scan closes `RT1` as well, using the `allTexts` walker already in the file, and names the defect instead of a count — one line inside `compare`, all four positions dead: ``` test.luau:1859: idle, nothing fetched: a raw range timestamp reached the tree -- Today: 2026-09-02T00:00:00. ``` Either fix is fine. What is not fine is shipping the sentence "only the pair closes the hole" while the hole is open — that is precisely the "trains a maintainer to trust the number" failure you asked me to hunt for, and here it is in the comment rather than in the assert. **2. `test.luau:381-382` — the guard's realistic boundary is still unpinned, and the suite already carries the fixture that would pin it.** `S1`, tightening the hour field from `%d+` to `%d`, **SURVIVES** the suite as submitted. That mutation is not synthetic: `timew summary :all` prints a two-digit hour for anyone with ten hours logged, and under `S1` the panel's headline all-time total renders `"0:00"` — the same indistinguishable-from-real-zero failure this PR was written to defend against, one field to the left of the anchors it pins. The suite *does* feed two-digit hours through this parser — `"20:00:00"` at `test.luau:1564` and `"45:00:00"` at `test.luau:2229` — but `allTotalText` is only ever asserted against itself (`test.luau:1896` compares `scopeTotalText` to `allTotalText`), so the parsed string is never pinned. One line, next to the two new asserts: ```suggestion assert(T.parseRangeTotal("45:00:00") == "45:00", "an all-time total is routinely past nine hours: the hour field is %d+, not %d") ``` Verified: suite stays green (`1751`), and `S1` then dies at that assert. This is also my answer to your question 3 about `"1:02:03x"`. Pinning a boundary with an input the upstream tool cannot emit is defensible on its own — a guard is a contract about a *shape*, and `timew`'s output growing a suffix is exactly the change you cannot see coming. But the PR is inconsistent about which standard it applies. By the synthetic standard, four more boundaries of the same guard are unpinned (`S2`, `S3`, `S4`, `S5` all survive). By the "model real output" standard, `"1:02:03x"` does not qualify and `"45:00:00"` does. Keep `"1:02:03x"` — it is doing real work, and I confirmed it pins `G3` alone — but adding `"45:00:00"` is what makes the guard's coverage principled rather than accidental: after it, every field of `^%d+:%d%d:%d%d$` that `timew` can actually exercise is pinned by an input `timew` can actually produce. ### Advisory **3. `test.luau:1858` — the card row's *kind* is not part of "the row's shape".** The description says "Both counts are pinned now, so the row's shape is as closed as the column's." `X1` — `buildCard` returning `ui.column` instead of `ui.row` at `panel.luau:764` — **SURVIVES**. The card silently restacks from side-by-side to vertical, the button lands under the clock, and nothing fails. Child count is not shape; `same(left.children[2].kind, "row", …)` or the kinds-in-order form above would cover it. Pre-existing rather than introduced here, so advisory — but it undercuts the sentence, and the kinds-in-order rewrite gets it nearly for free. **4. `timew/parse.luau:77` — `Parse.clock`'s own anchors are unpinned (`S12`, `S13` both survive).** Worth flagging only because this PR's stated failure mode leans on them: "one of them reaches `Parse.clock`, which floors to `0`". That story is correct today, and I confirmed it empirically, but it is load-bearing on a guard the suite does not defend. Outside this PR's region; not a change request. **5. Description wording.** "The panel would publish `0s` into the day header" — the range total lands in `allTotalText` → `scopeTotalText` and is shown for the *all-time* scope. The in-code comment (`"0s" is published where "0:00" belongs`) is accurate; only the PR body is loose. --- Everything this PR claims, it does. `G3`, `G4` and `C4b` were genuinely open on `main`, the five kills land where stated, the anchor-to-assert mapping is exact, and the `"0s"` story is real. Add the `"45:00:00"` assert and either close the left column or correct the comment, and I would approve without further comment.
yuris force-pushed test/close-surviving-mutations from e33744cf2d to 9b7a420159 2026-09-02 05:18:08 +00:00 Compare
Author
Owner

Actioned. Both blocking findings accepted and closed, both advisories accepted, the
tautology closed, and the description rewritten where the review proved it wrong. Amended
onto 9b7a420 rather than stacked. 26 mutations applied, 26 killed, zero survivors.

Accepted

1 (blocking) — S1, the guard's hour field. Added your suggestion verbatim:

assert(T.parseRangeTotal("45:00:00") == "45:00", "an all-time total is routinely past nine hours: the hour field is %d+, not %d")

S1: SURVIVED before → killed test.luau:397 after, with that message. You were right
that this is the realistic boundary and the anchors were the cosmetic ones — the fixture
that would have caught it was already in the file, two thousand lines down, feeding a value
nothing asserted.

2 (blocking) — "only the pair closes the hole". Closed rather than corrected, and
closed at all four positions rather than the three your suggestion reaches. Reinstating
ui.label({ text = "Today: 2026-09-02T00:00:00." }):

Position Before After
card column panel.luau:772 killed :1857 killed :1906
card row panel.luau:782 killed :1858 killed :1905
left column panel.luau:1104 SURVIVED killed :1904
root row panel.luau:1120 SURVIVED killed :1903

3 (advisory) — kinds over counts. Accepted in full; the counts are gone. The two
#children comparisons became four kinds-in-order comparisons behind one helper that is
local inside compare, as you specified — I reproduced the ceiling first, and a top-level
local function does fail with ./test.luau:3366: Out of local registers when trying to allocate tag: exceeded limit 200.

X1 (buildCard returning ui.column) is closed as a side effect, so the sentence you
called out is no longer merely corrected, it is true:

test.luau:1904: idle, nothing fetched: the left column is title, card, recent label, chips
  -- "label,column,label,column" vs "label,row,label,column"

4 (advisory) — Parse.clock's anchors. Added, as a suite assert only; parse.luau is
untouched. Your point that the guard's comment is load-bearing on an undefended property is
exactly why it belongs in this PR. Worth recording why it was undefended, because it is the
same accident this PR started from: the realistic input, "2026-09-02T00:00:00.", carries
junk on both sides, so either anchor alone already rejects it and dropping one still
floors it to 0. Two inputs that actually discriminate — the SUMMARY fixture's own data
line, which ends in a clock, and "1:02:03x":

S12 (drop ^): SURVIVED -> killed test.luau:116
S13 (drop $): SURVIVED -> killed test.luau:117

5 — "day header". Corrected in the description; it lands in allTotalText →
scopeTotalText and shows for the all-time scope.

The tautology. Cheap, and done: one line at the 45-match fixture, no restructuring.

assert(wideModel.allTotalText == "45:00", "... got " .. wideModel.allTotalText)

It kills S1 independently of the unit assert, through the whole pipeline, and puts the
wrong value in the message (got 0:00). Verified both ways: delete the unit assert and S1
still dies at this one; delete this one and S1 still dies at the unit assert.

Pushed back

On the spelling of fix 2, not the finding. Your suggested
assert(#leftColumn().children == 4, ...) at :1575 closes L1 but leaves RT1 — the root
row's fourth child — open, and it reintroduces the count idiom one line after your own
argument against it. I also skipped the allTexts scan you offered as the alternative: once
kinds cover all four positions it is a second mechanism defending the same defect, and it
only catches that one text. So: neither of your two suggested fixes, but the finding closed
more completely than either.

I went past finding 1. You listed S2–S5 as survivors and framed them as an
observation about inconsistent standards rather than a change request. I closed them, plus
two widening twins I found while checking (S5b minute → %d%d%d?, S5c second →
%d%d%d?, both SURVIVED on the reviewed tree). Reasoning: this PR exists to close guard
survivors, so four left in the guard is the same finding recurring at the next sweep; the
failure mode is identical to the one already defended ("0s" where "0:00" belongs); and
it costs five file-scope asserts and zero field comparisons.

This is the thing to challenge hardest and I want to name it rather than bury it. It
resolves your standards inconsistency in the closing direction, which means the file now
carries five synthetic guard inputs. The coherent alternative is to declare S2–S5c
equivalent mutants — timew cannot emit an empty hour, a one-digit minute or a three-digit
second — and document that in one comment instead of five asserts. I took the asserts
because "upstream will not emit that" is the assumption that made the trailing $ unpinned
in the first place, but I do not think the other call is wrong.

Also worth a second look

  • kinds(right) is deliberately absent — R1/L2 were already dead, so it would close
    nothing. That leaves #rightKids == 6 at :1574 as a count next to four kind asserts. It
    is forced, not chosen: upgrading it needs a file-scope helper, which the register ceiling
    refuses. The file mixes both spellings now.
  • Caught while checking my own work: my first pass' comment claimed the guard was pinned
    "every anchor and every field", and S5c proved that false — the second field was pinned
    narrowing but not widening. Fixed by adding "1:02:034" rather than by softening the
    comment, which is the same correction you asked for on "only the pair closes the hole". If
    a comment in this diff still claims more than the asserts hold, that is the finding.

Gates

viewModel: 12 states, 1775 field comparisons
ok
yuris/timew
  ok

0 errors, 0 warnings

1751 → 1775: two child-count comparisons became four kind comparisons, across twelve
states. Everything else added is a file-scope assert, which does not move the count.
git diff main --stat is test.luau | 83 +++--, nothing under timew/.

Actioned. Both blocking findings accepted and closed, both advisories accepted, the tautology closed, and the description rewritten where the review proved it wrong. Amended onto `9b7a420` rather than stacked. **26 mutations applied, 26 killed, zero survivors.** ## Accepted **1 (blocking) — `S1`, the guard's hour field.** Added your suggestion verbatim: ```luau assert(T.parseRangeTotal("45:00:00") == "45:00", "an all-time total is routinely past nine hours: the hour field is %d+, not %d") ``` `S1`: **SURVIVED** before → `killed test.luau:397` after, with that message. You were right that this is the realistic boundary and the anchors were the cosmetic ones — the fixture that would have caught it was already in the file, two thousand lines down, feeding a value nothing asserted. **2 (blocking) — "only the pair closes the hole".** Closed rather than corrected, and closed at all four positions rather than the three your suggestion reaches. Reinstating `ui.label({ text = "Today: 2026-09-02T00:00:00." })`: | Position | Before | After | | --- | --- | --- | | card column `panel.luau:772` | killed `:1857` | killed `:1906` | | card row `panel.luau:782` | killed `:1858` | killed `:1905` | | left column `panel.luau:1104` | **SURVIVED** | killed `:1904` | | root row `panel.luau:1120` | **SURVIVED** | killed `:1903` | **3 (advisory) — kinds over counts.** Accepted in full; the counts are gone. The two `#children` comparisons became four kinds-in-order comparisons behind one helper that is `local` inside `compare`, as you specified — I reproduced the ceiling first, and a top-level `local function` does fail with `./test.luau:3366: Out of local registers when trying to allocate tag: exceeded limit 200`. `X1` (`buildCard` returning `ui.column`) is closed as a side effect, so the sentence you called out is no longer merely corrected, it is true: ``` test.luau:1904: idle, nothing fetched: the left column is title, card, recent label, chips -- "label,column,label,column" vs "label,row,label,column" ``` **4 (advisory) — `Parse.clock`'s anchors.** Added, as a suite assert only; `parse.luau` is untouched. Your point that the guard's comment is load-bearing on an undefended property is exactly why it belongs in this PR. Worth recording *why* it was undefended, because it is the same accident this PR started from: the realistic input, `"2026-09-02T00:00:00."`, carries junk on **both** sides, so either anchor alone already rejects it and dropping one still floors it to `0`. Two inputs that actually discriminate — the `SUMMARY` fixture's own data line, which *ends* in a clock, and `"1:02:03x"`: ``` S12 (drop ^): SURVIVED -> killed test.luau:116 S13 (drop $): SURVIVED -> killed test.luau:117 ``` **5 — "day header".** Corrected in the description; it lands in `allTotalText` → `scopeTotalText` and shows for the all-time scope. **The tautology.** Cheap, and done: one line at the 45-match fixture, no restructuring. ```luau assert(wideModel.allTotalText == "45:00", "... got " .. wideModel.allTotalText) ``` It kills `S1` independently of the unit assert, through the whole pipeline, and puts the wrong value in the message (`got 0:00`). Verified both ways: delete the unit assert and `S1` still dies at this one; delete this one and `S1` still dies at the unit assert. ## Pushed back **On the *spelling* of fix 2, not the finding.** Your suggested `assert(#leftColumn().children == 4, ...)` at `:1575` closes `L1` but leaves `RT1` — the root row's fourth child — open, and it reintroduces the count idiom one line after your own argument against it. I also skipped the `allTexts` scan you offered as the alternative: once kinds cover all four positions it is a second mechanism defending the same defect, and it only catches that one text. So: neither of your two suggested fixes, but the finding closed more completely than either. **I went past finding 1.** You listed `S2`–`S5` as survivors and framed them as an observation about inconsistent standards rather than a change request. I closed them, plus two widening twins I found while checking (`S5b` minute → `%d%d%d?`, `S5c` second → `%d%d%d?`, both **SURVIVED** on the reviewed tree). Reasoning: this PR exists to close guard survivors, so four left in the guard is the same finding recurring at the next sweep; the failure mode is identical to the one already defended (`"0s"` where `"0:00"` belongs); and it costs five file-scope asserts and zero field comparisons. This is **the thing to challenge hardest** and I want to name it rather than bury it. It resolves your standards inconsistency in the *closing* direction, which means the file now carries five synthetic guard inputs. The coherent alternative is to declare `S2`–`S5c` equivalent mutants — `timew` cannot emit an empty hour, a one-digit minute or a three-digit second — and document that in one comment instead of five asserts. I took the asserts because "upstream will not emit that" is the assumption that made the trailing `$` unpinned in the first place, but I do not think the other call is wrong. ## Also worth a second look - `kinds(right)` is deliberately absent — `R1`/`L2` were already dead, so it would close nothing. That leaves `#rightKids == 6` at `:1574` as a count next to four kind asserts. It is forced, not chosen: upgrading it needs a *file-scope* helper, which the register ceiling refuses. The file mixes both spellings now. - Caught while checking my own work: my first pass' comment claimed the guard was pinned "every anchor and every field", and `S5c` proved that false — the second field was pinned narrowing but not widening. Fixed by adding `"1:02:034"` rather than by softening the comment, which is the same correction you asked for on "only the pair closes the hole". If a comment in this diff still claims more than the asserts hold, that is the finding. ## Gates ``` viewModel: 12 states, 1775 field comparisons ok ``` ``` yuris/timew ok 0 errors, 0 warnings ``` `1751 → 1775`: two child-count comparisons became four kind comparisons, across twelve states. Everything else added is a file-scope `assert`, which does not move the count. `git diff main --stat` is `test.luau | 83 +++--`, nothing under `timew/`.
yuris changed title from test: pin the guard's anchors and the card row's shape to test: pin the guard's whole shape and the card region's kinds 2026-09-02 05:20:20 +00:00
yuris merged commit a61a27a2ac into main 2026-09-02 05:23:17 +00:00
yuris deleted branch test/close-surviving-mutations 2026-09-02 05:23:17 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
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
yuris/noctalia-timew!19
No description provided.