# The conformance matrix

Some review findings come back again and again: focus lost when a control disappears, server HTML that a reader in another time zone or locale cannot hydrate, a part on the wrong side in a right-to-left page, a control that vanishes under forced colours, a message announced twice. The conformance matrix turns each of them into a check that runs on every story, so a new component is covered the day it gets a story, with nothing to opt into and no checklist to remember.[^matrix]

```sh
pnpm conformance                                  # every check, every story (Chromium)
pnpm conformance test/conformance/rtl.test.tsx    # one check
pnpm conformance -t "SegmentedControl/"           # one component's stories, every check
OURO_STORIES=pagination pnpm conformance          # the stories of the folders a change touches
OURO_CONFORMANCE_SHARD=2/4 pnpm conformance       # one CI shard: every 4th story from the 2nd
```

The checks live in `test/conformance/` and run in their own Vitest project, `conformance` (Chromium only, `OURO_CONFORMANCE=1`), so `pnpm test` leaves them out. CI's `conformance (n/3)` jobs run them in three shards (`OURO_CONFORMANCE_SHARD`), each shard every *n*th story, so the shards take about as long as each other, and the `conformance` job, the check the rules require, passes when every shard did.[^shards] On a pull request the shards render only the stories `scripts/test-impact.mjs` selects, as the other shared suites do.

## The checks

| Recurring finding | Check | File |
| --- | --- | --- |
| Focus lost when a control disappears | Each story's controls are used one after another: a press on each button, link, checkbox, tab or option (Enter for one shown only to screen readers), a character typed into each field, an arrow key on each slider and scrollable region, and Escape for each popup the actions open.[^interactions] After every action, focus must be on an element still in the page and shown.[^interactions] | `interactions.test.tsx` |
| A message announced twice | Every one of those actions runs inside `watchAnnouncements`: the same text announced twice in one action fails, whether one live region says it twice or two regions say it once each.[^interactions] | `interactions.test.tsx` |
| Server/client mismatch | Every story is rendered to HTML as a server in UTC and en-US would, then hydrated in America/Los_Angeles with de-DE and in Asia/Tokyo with ja-JP.[^hydration] The server and each reader are separate frames (`frame.tsx`), each with its own copy of every module, so a formatter cached under one locale is never read under another.[^hydration] A recoverable error or an attribute mismatch fails. | `hydration.test.tsx` |
| Right to left | Every story renders with `dir="rtl"` on `<html>`, closed and with each overlay open, at 390px and 1280px: nothing may stick out of the viewport sideways.[^rtl] | `rtl.test.tsx` |
| Physical sides in the source | `physicalUtilities` in `scripts/source-rules.mjs` fails on `left-*`, `right-*`, `ml-*`, `mr-*`, `pl-*`, `pr-*`, `border-l`, `border-r`, `rounded-l-*`, `rounded-r-*`, the corner radii, `text-left`, `text-right`, `float-*` and `clear-*` on a side, unless `physicalAllowed` lists the class with its reason. It also fails on a horizontal translate (`translate-x-*`), a transform origin, a background position or an object position on a side (`origin-left`, `bg-right`, `object-left-top`), which have no logical form, unless the same class list reverses it for right to left with an `rtl:` class of the same kind (`translate-x-4 rtl:-translate-x-4`, `origin-left rtl:origin-right`).[^physical] | `scripts/source-rules.test.mjs` |
| Forced colours | Under `forced-colors: active`, closed and with each overlay open: every box a person presses, types into, drags or scrolls keeps an edge (a border or outline on it, inside it or on its component's frame); a checked, selected, pressed or current control looks different from one that is not; and focusing each control from the keyboard changes what its component shows, while the highlight moving onto each option or item of a list or menu shows too.[^forced] A focused control is compared with its component at rest (focus outside it); a component never at rest (a menu opened with focus inside) is compared with the control that had focus left out, together with every part around it that does not hold the new one (a wrapper that draws its ring with `:has(:focus-visible)`), so another control's ring going away is no cue.[^forced] The only item of a list is compared with its highlight taken off. A fill in the colour behind it counts as no fill.[^forced] A control that never takes keyboard focus fails as uncompared: focus that stays where it was, or goes to another kind of part, is no redirect; only a part that sends focus on to another of its kind (OtpField's slot to the first empty one) is compared at that one's turn.[^forced] Controls that a focus shows (a tab's panel) are compared too. | `forced-colors.test.tsx` |

Motion is already held the same way, by `pnpm motion:coverage`.

Each check also proves it can fail.[^can-fail] `test/fixtures/conformance/broken.stories.tsx` holds one story per defect, outside Storybook. Each check's "proves it can fail" tests expect it to report that story:[^can-fail]

- a button that removes itself when pressed;
- the same text in two live regions;[^can-fail]
- a number formatted through a module-level `Intl.NumberFormat` cache;
- a part placed at `left: 100%` in a right-to-left page;
- a focus ring drawn only with a box shadow;
- a button whose only edge is its fill;
- two buttons of one component, the second's focus ring a box shadow, so focus moving to it only takes the first's ring away;[^can-fail]
- a part whose focus handler sends focus to another kind of part;
- the only option of a list, its highlight a fill.

These tests run in every conformance run, whatever stories it selects, on the first shard, and in CI even when a pull request changes no component (`pnpm conformance -t "can fail"`).[^can-fail-ci] A check changed so that it misses its defect fails there.

### Why a check fails, and the fix

- **"focus fell to `<body>`"**: the control you used left the page, or became disabled, while it held focus. A component that a press removes hands focus on before it goes: `useFocusHandoff` (`registry/ouro/lib/ouro/focus-handoff.ts`) moves it to what now stands where the element stood (Alert, Attachment and Toast use it). A control that becomes unavailable where it is says so with `aria-disabled` and keeps focus, as Pagination's Previous and Next do at the ends. When the story's own code removes the control (a demo's "show it again" button), keep the control on the page.
- **A hydration mismatch**: render nothing that depends on the time zone, the locale, the clock or randomness on the server and in hydration. Read it with `useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot)`, whose server snapshot is the same everywhere, and fill it in once hydrated (Calendar's today, Slider's value samples without a `locale`). A number or date formatted in the reader's default locale is written in a fixed one on the server and during hydration, then switches: Slider and Progress pass Base UI `locale="en-US"` until hydrated, when no `locale` is given.[^locale] A story that computes an instant from `Date.now()` at module level rounds it (to the hour), since the server's frame and the reader's load the module at different moments.
- **Overflow in a right-to-left page**: a part placed by a physical side lands on the wrong one. Use `start-*`/`end-*`, `ms-*`/`me-*`, `ps-*`/`pe-*`, `border-s`/`border-e`, `rounded-s`/`rounded-e` and `text-start`/`text-end`, and `rtl:` for a transform that must reverse (SegmentedControl's thumb).
- **"no edge"**: forced colours drop fills and shadows.[^can-fail] Add a border that only forced colours draw, `forced-colors:border forced-colors:border-solid`, or for a scrollable region that is a tab stop, an outline: `forced-colors:[&[tabindex='0']]:outline-1 forced-colors:[&[tabindex='0']]:outline-solid`.
- **"on looks like off"**: the state was a fill. Paint it with the system's pair, `forced-colors:data-pressed:bg-[Highlight] forced-colors:data-pressed:text-[HighlightText]` (Tabs' and SegmentedControl's selected item).
- **"focus shows nothing"**: the focus ring was a box shadow. Draw it with `outline`, which forced colours keep.
- **"highlight shows nothing"**: an option's or item's highlight was a fill. Add `forced-colors:data-highlighted:bg-[Highlight] forced-colors:data-highlighted:text-[HighlightText]` (Menu, Select, Combobox, Command). On a link, add `forced-colors:forced-color-adjust-none` with the pair, since Chromium paints a link's background in the canvas colour whatever it is (Pagination's current page).
- **"focus never compared"**: the control took no keyboard focus when focused, or kept it when blurred.[^forced] Make it reachable from the keyboard: a tab stop, or a roving item its group moves focus onto.

## Declaring an exception

When a story does something on purpose that a check would fail, it says so in its `parameters.conformance` (`ConformanceParameters` in `test/stories.tsx`), with the reason, and the check fails once the declaration is no longer true[^declared]:

- `focusExit: { "<data-ouro-control>/<data-ouro-part> <accessible name>": "<why>" }`: using that one control deliberately takes focus out of the page (`"unmarked/? <name>"` for a control the story renders itself). A declared control whose action keeps focus, or that no control is, fails, so the declaration cannot go stale.[^declared]
- `announces: { "<control's accessible name>": "<text>" }`: one action on that control must announce the text exactly once.
- `clientOnly: "<why>"`: the story cannot render on a server, so its hydration is not checked; once it does render on a server, the check fails until the declaration goes.

A physical side that must follow the screen, not the text (a part centred with `left-1/2 -translate-x-1/2`, a safe-area inset, a sheet whose `side` prop is physical), goes in `scripts/physical-allowed.json` (`physicalAllowed`), by file and class, with its reason; an allowance the source no longer uses fails.[^physical-allowed]

Exemptions only grow on purpose. `scripts/conformance-exemptions.mjs` compares the physical-side allowances, every `focusExit` control and every `clientOnly` with the base branch, as the Conformance exemptions workflow does on each pull request (and again when its description is edited).[^exemptions] Each one the pull request adds needs its reason, and the description must name it: the class, the control's key, or the story (`SheetFeel/FeelComparison`).[^exemptions] So that none escapes it, a story writes `parameters` and `conformance` as object literals in place: a spread, a shorthand (`parameters: { conformance }`), a variable (`focusExit: exits`), a computed key, a reason that is not a plain string, or parameters set by assignment after the story fails the check.[^exemptions] Its job, `conformance exemptions`, passes in the merge queue without checking, since the pull request's description was checked already.[^exemptions]

## Tests that cannot fail: mutation testing

A test that passes whatever the code does proves nothing. `pnpm mutation` (`scripts/mutation.mjs`) runs [Stryker](https://stryker-mutator.io/) on the lines a change added or altered in the runtime source (`registry/ouro/components/` and `registry/ouro/lib/ouro/`, not tests, stories or the test kit): it changes each of those lines in every way it knows (a condition flipped, a branch emptied, a `+` made a `-`), one change (a mutant) at a time, and runs the tests.[^mutation-run] A mutant that no test fails on survived, and the run fails, naming it:[^mutation-survivors]

```sh
pnpm mutation                                                     # the working tree against origin/main
pnpm mutation origin/main --only registry/ouro/lib/ouro/cn.ts     # one file's changed lines
```

Each survivor is reported as `<file>:<line>:<column> <Mutator>: <replacement>`.[^mutation-survivors] Fix a survivor with a test that fails on that change. When the change cannot alter behaviour (an equivalent mutant), say why on the line before it, `// Stryker disable next-line <Mutator>: <why>`, so the reason is in the source for the next reader. Any other Stryker comment a change adds to the runtime source fails the run: `disable all` with no reason would silence the rest of the file.[^mutation-disables] The run also reports how many disable comments the source has at the base and now, and fails if that count grew by more than the change shows.[^mutation-disables] Two kinds of mutant are left out on purpose.[^mutation-ignored] The first is Tailwind classes where only classes go (a `className`, a `cn()` or `cva()` argument, a `*Class` constant), which change how a part looks and which the screenshot tests hold. An attribute name, a selector or a marker that only looks like classes (`"aria-invalid"`, `closest("[inert]")`, `data-ouro-part`) is still mutated. The second is the `process.env.NODE_ENV` check that strips development messages, which every test (they all run in development) sees the same way.[^mutation-ignored]

The tests run in Chromium through `scripts/mutation/plugins.mjs`, a Stryker test runner over one Vitest instance. Each mutant runs only the tests of the file it is in: that file's own tests first (its component folder's, or a `lib/` file's own test file), stopping at the first failure, then, only if those let it survive, the tests of the components that import that `lib/` file.[^mutation-run] A large change therefore costs each mutant no more than a small one.[^mutation-run] Files that move keep their lines (`git diff -M`), so only what a move changed is mutated.

### In CI

CI's mutation jobs run it on every pull request, over the pull request's changes.[^mutation-ci] `mutation-plan` decides how many shards: five when no results are kept for the pull request, three when they are.[^mutation-plan] `pnpm mutation <base> --shard <i>/<n>` takes the *i*th of *n* groups of the changed ranges (`mutation (i/n)`): a range, not a file, so one file with many slow mutants spreads across shards.[^mutation-cost] A long range is cut into parts of a few mutants each, but only after a line that no node of the range runs past, since Stryker mutates a node only when one range holds all of it.[^mutation-split] The groups are balanced by what they cost: each mutant one second and half its own tests' time, and each shard half the time of every test file it needs once, a `lib/` file's importers included.[^mutation-cost] The test times are measured: `scripts/mutation/test-seconds.json` holds each test file's time from a `ci` job's `pnpm test` (refresh it with `node scripts/mutation/cache.mjs seconds <log>`), and a file it lacks counts as five seconds.[^mutation-cost]

Each shard starts from the results kept from the pull request's last run (Stryker's incremental mode, `.tmp/stryker-incremental.json`), so a push reruns only the mutants its change touched.[^mutation-cache] The `mutation` job merges the shards' results into one file and keeps it in the cache under a key with a hash of every `lib/` and theme file, the lockfile, `app.css`, the Vitest config and setup, the tests the mutants run against, and what all of these and the changed files import, directly or through each other (`node scripts/mutation/cache.mjs key <base>`): Stryker's incremental mode compares only a mutated file, so a result from before one of those changed starts afresh.[^mutation-cache] A file whose own source changed since its results were kept starts afresh too, and so does a file that imports, directly or through other modules, a changed file of the pull request that changed since, since Stryker would otherwise keep a Killed on a line the change left alone (it compares only the mutated file, not what it imports).[^mutation-cache] Each shard keeps only the mutants of its own ranges, since Stryker also writes every mutant it did not run with the status it last had; so a survivor stays Survived in the merge, and the kept results fail the next run too.[^mutation-cache] The `mutation` job, the check the rules require, passes when every shard did; on a merge-queue run each shard has nothing to mutate.[^mutation-ci]

## Ratchets that cannot be talked around: property tests

The two guardrail kits hold a count that may only fall: `ouro-source-rules` the hand-rolled controls in an app's source, `ouro-census` the unmarked controls in each state of each journey.[^ratchets] `scripts/kits/ratchets.test.mjs` checks with [fast-check](https://fast-check.dev/) that no edit gets around them.[^ratchets] It generates 300 baselines, scans, settings, renames and removals per property from a fixed seed, so a run is deterministic.[^ratchet-runs] Each property states what must hold whatever the edit:[^ratchets]

- The census accepts a run without a reason exactly when it drops no state and raises no state's unmarked count.[^ratchet-census] Every rise and drop it accepts with a reason is recorded with that reason and the counts it moves between.[^ratchet-census] A rename that adds unmarked controls needs a reason. Updating a baseline from itself records nothing.
- The source rule reports a settings change whenever the head's settings stop reading a file the base's settings read. A move stays within its file or component, so a fix elsewhere never pays for a new control: the regressions are never fewer than the controls the scan gained.[^ratchet-source] Against a base ref, the check fails, and `--write-baseline` writes nothing, exactly when a file gained controls with no recorded reason.[^ratchet-source]

A failing property prints the case fast-check shrank it to, and the seed. Add that case to the kit's own tests (`scripts/kits/*.test.mjs`) when you fix it. The property tests run in `pnpm test:scripts` (`scripts/kits.test.mjs`), under the kits' 100% coverage.[^ratchet-runs]

[^matrix]: test:test/conformance/interactions.test.tsx::actions at $width px > $id keeps focus in the page and announces each message once after every action; test:test/conformance/hydration.test.tsx::server HTML hydrates in another time zone and locale > $id; test:test/conformance/rtl.test.tsx::right to left at $width px > $id stays inside the viewport, closed and with each overlay open; test:test/conformance/forced-colors.test.tsx::forced colours at $width px > $id keeps every control's edge, state and focus, closed and with each overlay open verify:testExists(test/conformance/interactions.test.tsx::actions at $width px > $id keeps focus in the page and announces each message once after every action) verify:testExists(test/conformance/hydration.test.tsx::server HTML hydrates in another time zone and locale > $id) verify:testExists(test/conformance/rtl.test.tsx::right to left at $width px > $id stays inside the viewport, closed and with each overlay open) verify:testExists(test/conformance/forced-colors.test.tsx::forced colours at $width px > $id keeps every control's edge, state and focus, closed and with each overlay open)
[^shards]: .github/workflows/ci.yml verify:fileLine(.github/workflows/ci.yml, /OURO_CONFORMANCE_SHARD: \$\{\{ matrix\.shard \}\}\/\$\{\{ strategy\.job-total \}\}/) verify:testExists(scripts/workflows.test.mjs::conformance and mutation are each one check over their shards, which passes only when every shard passed) verify:testExists(scripts/workflows.test.mjs::the conformance shards run only the checks' proofs on the first shard when a pull request changes no component, and check only the selected stories, every nth per shard)
[^interactions]: test:test/conformance/interactions.test.tsx::actions at $width px > $id keeps focus in the page and announces each message once after every action verify:testExists(test/conformance/interactions.test.tsx::actions at $width px > $id keeps focus in the page and announces each message once after every action) verify:fileLine(test/conformance/interactions.test.tsx, /was announced \$\{times\} times/)
[^hydration]: test:test/conformance/hydration.test.tsx::server HTML hydrates in another time zone and locale > $id verify:testExists(test/conformance/hydration.test.tsx::server HTML hydrates in another time zone and locale > $id) verify:fileLine(test/conformance/hydration.test.tsx, /America\/Los_Angeles/) verify:fileLine(test/conformance/frame.tsx, /renderToString/)
[^rtl]: test:test/conformance/rtl.test.tsx::right to left at $width px > $id stays inside the viewport, closed and with each overlay open verify:testExists(test/conformance/rtl.test.tsx::right to left at $width px > $id stays inside the viewport, closed and with each overlay open)
[^physical]: scripts/source-rules.test.mjs verify:testExists(scripts/source-rules.test.mjs::physicalUtilities finds a horizontal translate, a transform origin, a background or object position on a side, unless the same classes reverse it for right to left)
[^forced]: test:test/conformance/forced-colors.test.tsx::forced colours at $width px > $id keeps every control's edge, state and focus, closed and with each overlay open; test:test/conformance/forced-colors.test.tsx::forced colours proves it can fail > on $id verify:testExists(test/conformance/forced-colors.test.tsx::forced colours at $width px > $id keeps every control's edge, state and focus, closed and with each overlay open) verify:testExists(test/conformance/forced-colors.test.tsx::forced colours proves it can fail > on $id)
[^can-fail]: test:test/conformance/interactions.test.tsx::actions prove they can fail > on $id; test:test/conformance/hydration.test.tsx::hydration proves it can fail > on $id; test:test/conformance/rtl.test.tsx::right to left proves it can fail > on $id; test:test/conformance/forced-colors.test.tsx::forced colours proves it can fail > on $id verify:testExists(test/conformance/interactions.test.tsx::actions prove they can fail > on $id) verify:testExists(test/conformance/hydration.test.tsx::hydration proves it can fail > on $id) verify:testExists(test/conformance/rtl.test.tsx::right to left proves it can fail > on $id) verify:testExists(test/conformance/forced-colors.test.tsx::forced colours proves it can fail > on $id)
[^can-fail-ci]: .github/workflows/ci.yml verify:fileLine(.github/workflows/ci.yml, /run: pnpm conformance -t "can fail"/) verify:fileLine(test/conformance/harness.ts, /\(parseShard\(inject\("ouroShard"\)\)\?\.index \?\? 1\) === 1/)
[^locale]: registry/ouro/components/ouro/slider/slider.test.tsx; registry/ouro/components/ouro/progress/progress.test.tsx verify:testExists(registry/ouro/components/ouro/slider/slider.test.tsx::Slider across locales > renders the same value text on a server in any locale, and shows the reader's once hydrated) verify:testExists(registry/ouro/components/ouro/progress/progress.test.tsx::Progress across locales > writes the value in en-US on a server in any locale, hydrates it as it is, and shows the reader's once hydrated)
[^declared]: test/conformance/interactions.test.tsx verify:fileLine(test/conformance/interactions.test.tsx, /focusExit declares/) verify:fileLine(test/conformance/hydration.test.tsx, /clientOnly/)
[^physical-allowed]: scripts/source-rules.test.mjs verify:testExists(scripts/source-rules.test.mjs::no component places, pads, borders or rounds a part by a physical side unless physicalAllowed says why, and every allowance is still used)
[^exemptions]: scripts/conformance-exemptions.test.mjs; .github/workflows/conformance-exemptions.yml verify:testExists(scripts/conformance-exemptions.test.mjs::a new exemption needs its reason and a mention in the description; one the base has needs neither) verify:testExists(scripts/conformance-exemptions.test.mjs::storyShapeProblems fails every conformance parameter it cannot read as a literal, so no exemption escapes the ratchet) verify:testExists(scripts/workflows.test.mjs::the conformance exemptions check runs on each pull request and its description's edits, and passes in the merge queue without checking)
[^mutation-run]: scripts/mutation.test.mjs verify:testExists(scripts/mutation.test.mjs::runtimeSource takes component and lib code, and leaves tests, stories, types and the test kit) verify:testExists(scripts/mutation.test.mjs::testPhases runs a mutant against its own file's tests, then its importers', and an unannounced file against every file's once) verify:testExists(scripts/mutation.test.mjs::testFilesFor runs a component file's folder's tests, and a lib file's own tests, then its importers')
[^mutation-survivors]: scripts/mutation.test.mjs verify:testExists(scripts/mutation.test.mjs::survivors are the mutants that survived or that no test reached, described by place, mutator and replacement)
[^mutation-disables]: scripts/mutation.test.mjs verify:testExists(scripts/mutation.test.mjs::disableProblems fails every Stryker comment a change adds to runtime source that is not next-line <Mutator>: <reason>)
[^mutation-ignored]: scripts/mutation.test.mjs verify:testExists(scripts/mutation.test.mjs::the ouro-classes ignorer leaves out only strings in class positions: className, cn() and cva() arguments, and *Class constants) verify:testExists(scripts/mutation.test.mjs::the ouro-classes ignorer leaves out the development check, and mutates the rest)
[^mutation-ci]: .github/workflows/ci.yml verify:testExists(scripts/workflows.test.mjs::the mutation shards mutate a pull request's changed runtime lines, split by file, start from the kept results, and report nothing to mutate otherwise) verify:testExists(scripts/workflows.test.mjs::conformance and mutation are each one check over their shards, which passes only when every shard passed)
[^mutation-plan]: .github/workflows/ci.yml verify:testExists(scripts/workflows.test.mjs::the mutation plan keys the kept results by pull request and dependency hash, and runs five shards without them, three with)
[^mutation-cost]: scripts/mutation/cache.mjs verify:testExists(scripts/mutation.test.mjs::shardUnits makes each changed range of runtime source a unit of its own, with its file's tests, so one large file's mutants can spread across shards) verify:testExists(scripts/mutation.test.mjs::planShards weighs each mutant by its own tests' measured time and each shard by its first run over every test file it needs) verify:testExists(scripts/mutation.test.mjs::parseTestSeconds reads each ouro-chromium test file's time from a CI log, colour codes and timestamps dropped)
[^mutation-split]: scripts/mutation.mjs verify:testExists(scripts/mutation.test.mjs::splitRange cuts a long range into parts of about the given mutants, only where no node runs past the cut)
[^mutation-cache]: scripts/mutation/cache.mjs verify:testExists(scripts/mutation.test.mjs::dependencyFiles takes every lib file and what the changed files import, through each other; dependencyHash changes with any of them) verify:testExists(scripts/mutation.test.mjs::mergeReports makes one incremental report of the shards', renaming each test by its file and name so killedBy still names it) verify:testExists(scripts/mutation.test.mjs::dependencyFiles also takes what decides an outcome beyond the source: the lockfile, the theme and app.css, the Vitest config and setup and what they import, the tests and what they import, but not the changed files themselves) verify:testExists(scripts/mutation.test.mjs::pruneChangedFiles drops the kept mutants of a file whose source changed, since Stryker would reuse a Killed on a line the change left alone) verify:testExists(scripts/mutation.test.mjs::pruneChangedFiles also drops a changed file's kept results when a changed file it imports, directly or through others, changed since: Stryker does not see an import change) verify:testExists(scripts/mutation.test.mjs::a shard's fresh Survived outlives another shard's carried-over Killed: each report is cut to its own ranges, and a merge keeps the undetected status) verify:testExists(scripts/workflows.test.mjs::the mutation job merges the shards' results and keeps them under the plan's key on a pull request, and never fails for the cache)
[^ratchets]: scripts/kits/ratchets.test.mjs verify:testExists(scripts/kits/ratchets.test.mjs::ouro-census: updateBaseline > accepts a run without a reason exactly when no state is dropped and no state's unmarked count rises) verify:testExists(scripts/kits/ratchets.test.mjs::ouro-source-rules: main against a base ref > fails exactly when a file gained buttons the base's baseline did not count, unless this pull request recorded why)
[^ratchet-runs]: scripts/kits/ratchets.test.mjs verify:fileLine(scripts/kits/ratchets.test.mjs, /const runs = \{ seed: 133, numRuns: 300 \}/) verify:fileLine(scripts/kits.test.mjs, /--experimental-test-coverage/)
[^ratchet-census]: scripts/kits/ratchets.test.mjs verify:testExists(scripts/kits/ratchets.test.mjs::ouro-census: updateBaseline > accepts a run without a reason exactly when no state is dropped and no state's unmarked count rises) verify:testExists(scripts/kits/ratchets.test.mjs::ouro-census: updateBaseline > records every rise and every drop it accepts, with its reason and the counts it moves between)
[^ratchet-source]: scripts/kits/ratchets.test.mjs verify:testExists(scripts/kits/ratchets.test.mjs::ouro-source-rules: findRegressions > pairs each move one to one, within a file or a component, and counts every other addition as a regression) verify:testExists(scripts/kits/ratchets.test.mjs::ouro-source-rules: main against a base ref > fails exactly when a file gained buttons the base's baseline did not count, unless this pull request recorded why)
