Skip to content

feat!: try a key's badges in order and stop at the first match - #75

Merged
GraemeF merged 3 commits into
mainfrom
badge-fall-through
Sep 12, 2026
Merged

feat!: try a key's badges in order and stop at the first match#75
GraemeF merged 3 commits into
mainfrom
badge-fall-through

Conversation

@GraemeF

@GraemeF GraemeF commented Sep 12, 2026

Copy link
Copy Markdown
Contributor

A badge's match is how a config says which values it wants. Until now a pattern that failed to read the value was treated as a badge falling short, and the row said so. That fires on every value a shared list was never meant to read. The live delivery_pr badge reads owner/repo#N, and bdi-pj92 records four closed beads carrying a URL or a bare number, so the bead window reported a reference lost where nothing had been promised about the shape.

Badges on one key are now a chain. Each is tried in config order, the first to read the value draws, and the ones below it never run. A value no entry for the key reads draws nothing and reports nothing, whether or not those entries carry a link. Graeme chose first-match over every-match because ordering then carries meaning. A reader who does want every value of a key writes a last entry with a permissive pattern, which the schema already allowed, so nothing here adds a key or a field.

Breaking. Undrawn::Badge and the row no badge for <key>: no pattern reads this value are gone. A config with two entries on one key whose patterns both read a value drew both and now draws the first. Undrawn::Link and Undrawn::Short are untouched: those are a badge that read the value and then could not keep a promise its config made, which is the case a reader cannot see for themselves.

Shadowing goes with it. Config::badges_for_project used to replace every shared entry for a key a project named. It did that to stop a project's entry and a shared entry both drawing for one value, and first-match now does that on its own. All the replacing still achieved was losing the shared shapes: a project naming delivery_pr to add a bare number stopped reading the qualified form. So a project's entries for a key now stand where the first shared entry stood, and the shared entries follow them. The project wins a value by being tried first, and a value it does not read falls through.

That gives up suppression. A project can no longer silence a shared badge on a key it names. No config wanted to, but one test asserted it, and its doc comment argued for it: shadowing half a key, it said, would leave a project drawing the shared wording for values it did not name. Ordering is now how a reader asks for exactly that, so the worry is answered rather than dropped.

Two tests went with the retired variant rather than being rewritten around it. The model test asserting a report for an unreadable value is the retired behaviour itself. The row test asserting an anomaly's key reaches the row's notes has a sibling asserting exactly that for Undrawn::Link, which carries the property alone now.

Every behaviour claim here was checked against a deliberately weakened tree rather than left to read as passing. Disabling the fall-through guard makes the new pty test fail on the raw value. Making a key's badge read its value and then fail its link puts that key's name back on the screen, which is what that test's silence assertion looks for the absence of. Restoring shadowing makes the new snapshot test fail on the bead the shared entry reads.

A badge's `match` is how a config says which values it wants, so a pattern that does not read the value is the config declining and not a badge falling short. Badges on one key are now a chain: each is tried in config order, the first to read the value draws, and the ones below it never run. A value no entry for the key reads draws nothing and reports nothing, whether or not those entries carry a `link`.

That retires `Undrawn::Badge` and the row `no badge for <key>: no pattern reads this value`. It was written for a reader who had lost a reference, but it fires on every value a shared list was never meant to read — a `delivery_pr` badge reading `owner/repo#N` reports every bead carrying a URL or a bare number. A reader who does want to see every value of a key writes a last entry with a permissive pattern, which the existing schema already allows.

`Undrawn::Link` and `Undrawn::Short` are unchanged. Those are a badge that read the value and then could not keep a promise its config made about the badge it drew, which is the case a reader cannot see for themselves.
A row carries one badge whether the chain stopped at the first entry that read the value or ran every entry and the others declined, so the choice between two entries on one key is invisible in a row and visible on a screen. This puts two entries on one key with the second permissive, and asserts the screen holds what the first drew and not what the second would have.

Both assertions were checked against a weakened tree rather than left to read as passing. Disabling the fall-through guard fails the first. Making the second key's badge read its value and then fail its `link` puts that key's name back on the screen, which is what the second assertion looks for the absence of.
Shadowing existed to stop a project's entry and a shared entry both drawing for one value. First-match does that on its own, so replacing the shared entries for a key had no effect left except losing their shapes: a project naming `delivery_pr` to add a bare number stopped reading the qualified form the shared list already read.

So `Config::badges_for_project` no longer replaces anything. A project's entries for a key stand where the first shared entry for that key stood and the shared entries follow them, in their own order. A project wins a value by being tried first, and a value its own entries do not read falls through to the shared shapes.

What this gives up is suppression: a project can no longer silence a shared badge on a key it names. A test asserted that behaviour and its doc comment argued for it, worrying that shadowing half a key would leave a project drawing the shared wording for values it did not name. Ordering is now how a reader chooses exactly that, so the worry is answered rather than ignored.
@GraemeF
GraemeF merged commit 663d40d into main Sep 12, 2026
2 of 4 checks passed
@GraemeF
GraemeF deleted the badge-fall-through branch September 12, 2026 11:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant