Skip to content

Refuse a length, a weight and a font stack typed into what the build reads [#29] - #160

Merged
iderex merged 1 commit into
mainfrom
refuse-a-token-value-typed-into-a-build-input
Aug 13, 2026
Merged

Refuse a length, a weight and a font stack typed into what the build reads [#29]#160
iderex merged 1 commit into
mainfrom
refuse-a-token-value-typed-into-a-build-input

Conversation

@iderex

@iderex iderex commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

The row named for the token file decided colours and nothing else. That gap is
easy to miss because the name does not show it, and the page this milestone is
heading for is the one where it would have been paid: a page demonstrating every
group of values is the page where a value gets typed next to the thing it is
describing.

What the file carries, and what the row read

Seven of the file's ten top-level keys hold values, and the colours sit in three
of them:

go run . build | grep design-tokens
read data/design-tokens.json (206 value(s))
grep -oE '^  "[a-z-]+"' data/design-tokens.json | tr -d ' "' | tr '\n' ' '
what not-here how-to-read-this surface lift focus type shape font budget

Run 2026-08-13 at c6e40ba. surface, lift and focus hold the colours the
row read. type holds a size and a weight per role at two viewing distances,
shape holds two radii and the width a column of text stops at, and font holds
two family stacks. None of those reached the row, so a length or a font stack
written into a template was a second definition of a published value with every
route in this tree reporting green over it.

It is the harder half to see. A wrong colour looks wrong to anybody who opens the
page. A wrong length renders perfectly, and it goes on rendering perfectly after
the published value moves.

What the row reads now

go run . invariants | grep 'design-tokens-live'
  design-tokens-live-in-exactly-one-file: ok, 5 file(s) of every tracked file the build reads to render a page

Run 2026-08-13 at c6e40ba. Same row, same name, same population. Four shapes
instead of one: a hex run is a colour, digits touching a length unit are a
length, and the two font declarations are named by their property rather than by
their value, because a stack is a list somebody shortens and a weight is three
digits that look like nothing in particular.

The bound is the shape of the literal rather than its meaning. That is the bound
the version row already declares for itself and for the same reason: a value
spelled some other way walks through, so the file stays the authority for the set
rather than this row becoming a second copy of it. A fragment reference is still
cut out before anything is read, so the link the frame is built around is not
read as a colour.

The near miss

Four token values put into the frame, in the shape somebody writes them while
looking at the published file. This is the whole of the change against the head
of this branch:

<style>
  main { max-width: 1080px; font-family: ui-sans-serif, sans-serif }
  h1 { font-size: 56px; font-weight: 700 }
</style>

go run . invariants
  design-tokens-live-in-exactly-one-file: REFUSED, 4 violation(s)
    it refuses a colour, a length, a font family or a font weight written into what the build reads, outside a fragment reference
    because a value typed into a template is a second definition of a value published somewhere else, and the day the published one moves the page goes on rendering the old one perfectly, so nobody sees it; the file carries lengths, weights and font stacks beside the colours, and a wrong length is the harder one to see because a wrong colour at least looks wrong
    templates/page.html.tmpl: line 9 writes the length 1080px, and data/design-tokens.json is the one file a length is read from
    templates/page.html.tmpl: line 9 declares a font family, and data/design-tokens.json is the one file a font stack is read from
    templates/page.html.tmpl: line 10 writes the length 56px, and data/design-tokens.json is the one file a length is read from
    templates/page.html.tmpl: line 10 declares a font weight, and data/design-tokens.json is the one file a weight is read from
invariants: 1 rule(s) refused this tree
exit status 1

and the same tree with those four lines taken out again is the green run quoted
above. Both run 2026-08-13. The failure names the file, the line, the value and
where the value is read from, which is what a reader needs in order to repair it
without going looking.

Why the proof is not the fixture map

The map in the suite holds one violation per row, so what it proves is that the
row refused something and never which branch of the operator did. This row now
has four branches and its entry in that map is a colour, so three of them would
have been unproven while the row went on printing ok. Deleting a branch and
watching the map is the test of that:

# the length branch removed from the operator
go test ./internal/invariant -run 'TestEveryRowRefusesItsOwnViolationAndPassesTheNeighbour' -count=1
ok      github.com/Flowfin/site/internal/invariant       1.281s

go test ./internal/invariant -run 'TestTheTokenRow' -count=1
--- FAIL: TestTheTokenRowRefusesEveryShapeTheFileCarries (0.00s)
    invariant_test.go:383: a type size was not refused
    invariant_test.go:383: a corner radius was not refused
    invariant_test.go:383: the width a column stops at was not refused
FAIL

and with both font branches removed instead:

go test ./internal/invariant -run 'TestTheTokenRow' -count=1
--- FAIL: TestTheTokenRowRefusesEveryShapeTheFileCarries (0.00s)
    invariant_test.go:383: a type weight was not refused
    invariant_test.go:383: a type size was not refused
    invariant_test.go:383: a corner radius was not refused
    invariant_test.go:383: the width a column stops at was not refused
    invariant_test.go:383: a font stack was not refused
FAIL

All run 2026-08-13. So each shape carries its own pair, and docs/near-miss.md
now says which rows the map is weaker for rather than leaving it to be read as
proof of every branch behind a row. The second half of the pair is the set of
lines the row has to walk past: a figure with its unit spelled out in prose, a
word that ends in a unit, a number in a sentence, a date, and a value taken from
the token's own custom property rather than typed.

What this does not do

This is one of the three things #29 asks for, the middle one, and the issue stays
open for the other two. The page that renders every token and states both budgets is the other two,
and it is not in this change.

It does not reach the client budget numbers either. Those are five values in the
same file, and a row over them cannot work by shape: a pattern for a number with
a time unit refuses the transition durations nobody is held to, and a row
carrying the five numbers in its own source is a second copy of the file it
exists to protect. That is #89, it needs a row to be handed what it compares
against, and this change does not add that.

A length that is not in the file is refused as loudly as one that is. That is
deliberate and it is the colour branch's existing position rather than a new one:
the file is the authority for values of these shapes, so a build input has no
business writing one whether or not it happens to match a published value.

The gate

go run . ci
gate: 6 legs, in order: format, vet, test, build, links, invariants
  needs-network was not asked for. Asking costs a request to the public name from whatever machine runs it, and a verdict that moves when somebody else's service does rather than when this tree changes. Ask with: go run ./harness/needs-network
  format: ok, 44 file(s)
  vet: ok
  test: ok, 21 test file(s)
  build: ok, 5 file(s)
  links: every reference that stays inside this site resolves to a file the build wrote
  invariants: ok, 34 rule(s) decided, 1 owed and not decided
6 of 6 legs ran. None was skipped.

Run 2026-08-13 at c6e40ba.

The means is Go, which is what the generator, the rule table and every other row
are written in. It adds no language, no runtime and no dependency: the shapes are
patterns in a package that already carries a dozen of them, and the proof is the
suite that already exists.

This change carries no second reader. Nothing in the ruleset on the branch
requires an approving review, and what stands in place of one is the evidence
above, with each command run at c6e40ba rather than in a working tree.

Part of #29.

…reads [#29]

The row named for the token file decided colours and nothing else. The file
carries seven groups that hold values, and the colours sit in three of them, so
a type size, a corner radius, a ring width or a font stack written next to the
thing it describes was a second definition of a published value that every route
in this tree reported green. It is the harder half to see: a wrong colour looks
wrong on the page, and a wrong length renders perfectly for as long as nobody
compares it against the file.

The operator now reads a shape per group rather than the hex forms alone. Digits
touching a length unit are a length; the two font declarations are named by their
property, because a stack is a list somebody shortens and a weight is three
digits that look like nothing in particular. The bound is the shape of the
literal rather than its meaning, which is the bound the version row already
declares for itself, so the file stays the authority for the set.

The fixture map holds one violation per row and cannot tell one arm of an
operator from another, so the three new shapes would have been unproven under it
while the row went on reporting ok. They carry a pair of their own instead, one
fixture per shape and one line per thing the row has to walk past, and
docs/near-miss.md says which rows that applies to rather than leaving the map to
be read as proof of every branch behind a row.

Signed-off-by: Nils Lehnen <30603423+iderex@users.noreply.github.com>
@iderex iderex added the enhancement New feature or request label Aug 13, 2026
@iderex iderex self-assigned this Aug 13, 2026
@iderex iderex added this to the The pages milestone Aug 13, 2026
@iderex
iderex merged commit 269d93e into main Aug 13, 2026
17 checks passed
@iderex
iderex deleted the refuse-a-token-value-typed-into-a-build-input branch August 13, 2026 12:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant