Skip to content

refactor(ui): wire the dimension classes to their tokens, and guard the token files - #184

Merged
renefloor merged 5 commits into
chore/prune-unused-tokensfrom
chore/wire-dimension-tokens
Sep 11, 2026
Merged

refactor(ui): wire the dimension classes to their tokens, and guard the token files#184
renefloor merged 5 commits into
chore/prune-unused-tokensfrom
chore/wire-dimension-tokens

Conversation

@renefloor

@renefloor renefloor commented Sep 10, 2026

Copy link
Copy Markdown
Collaborator

Third in the stack: #182#183 → this. Base is chore/prune-unused-tokens, so the diff is only the wiring.

Why this exists

StreamSpacing, StreamRadius and the line-height scale hard-coded the same numbers the vendored token files already held. The design system's dimensions were mirrored twice, with nothing tying the copies together — the swatch-versus-baked-hex hazard one layer up, where an upstream change gets applied to one and not the other in silence.

An earlier revision of #183 deleted those unread constants instead. That removed the evidence of the gap rather than the gap, which is why they came back here and got wired.

What changed

StreamSpacing, StreamRadius, StreamLineHeight, StreamFontSize and StreamFontWeight now read their tokens. All values are bit-identical to the literals they replace — verified by resolving every expression and comparing IEEE-754 representations, not just ==. No golden moves.

New internal/tokens/stream_tokens_dimensions.dart sits beside the per-mode color files rather than under light//dark/, because the token repo publishes one set of dimensions for every mode — all 180 constants were byte-identical between the two files. Typed double so they satisfy Radius, EdgeInsets and TextStyle directly.

Font sizes mirror upstream's flavor split. iOS runs a size up at almost every step, so the token repo publishes a separate set and StreamFontSize ships two scales. Both are vendored, in internal/tokens/{android,ios}/stream_tokens_font_size.dart, keeping the bare upstream names. Everything else — spacing, radius, line heights, weights — is identical across android, ios and web and lives in the one shared file.

Weights are declared as FontWeight, not the raw 400/500/600/700. The vendored constants are already expressed in the types Flutter consumes rather than the types upstream emits — that is why the rest are double — and FontWeight has no public constructor from a number, so an int here could not be read in a const expression.

One upstream dimension is deliberately not carried: radiusNone, since the analyzer's use_named_constants prefers Radius.zero over circular(0). The font family is not carried either — this package never sets one for text, and ships no text font, only the generated Stream Icons face.

Two corrections found in review

The first revision wired StreamTokensTypography, an internal class of 20 TextStyle constants that nothing in the repo read — not one Dart file, only prose — while the public, exported StreamLineHeight still hard-coded its values. Ten constants were being kept alive by a dead intermediary. That class is gone, and StreamLineHeight reads the three line-height tokens.

The second was this PR claiming font sizes and weights could not be wired at all. Both arguments rested on the idea that only the web flavor carries the Geist family "this package ships". It ships no text font and never sets fontFamily for text, so the flavor question was only ever about sizes — and those are vendored per flavor now, with the weights alongside them.

All 23 wired values are identical to the literals they replace.

The guard

scripts/check_tokens.dart, wired as melos run check:tokens next to check:barrels in the same CI step. It fails when a vendored constant is never referenced, and when light/ and dark/ disagree about which constants exist — the second because a field resolving from a constant in one mode but not the other silently falls back.

It earned its place while being written: it caught radiusNone having no reader, and a stale directory-wide skip that was hiding a file's reads.

It has since been hardened against passing vacuously. A guard that exits 0 because it found nothing to inspect is worse than no guard — it prints the same tick as a real pass. Review found five such paths, each reproduced by running the script:

scenario was now
both color files emptied ✓ 0 color tokens … all referenced fails: minimum declaration count
dimensions file deleted ✓ … and 0 dimension tokens fails: missing file
token directory moved melos selects 0 packages, prints SUCCESS fails: no package filter to miss
dead token mentioned in a doc comment counted as a reference fails: comments and strings stripped
a vendored file with no branch in the script invisible fails: uncovered-file check
a flavor file declaring names but imported nowhere looked referenced fails: per-file import check

Plus a declaration-count cross-check, so a form the pattern cannot parse fails loudly instead of becoming exempt; stale allowlist entries are reported; and the success line now names how many Dart files were scanned, so "nothing unreferenced" can be told apart from "nothing examined".

The melos entry no longer uses melos exec at all. Any package filter that stops matching selects zero packages and reports SUCCESS, so it runs once from the repo root and the script locates the package itself.

The allowlist is intentionally empty, and an entry matching no declaration is now a failure.

Verification

analyze clean across all four packages; format:verify, check:barrels and check:tokens pass. flutter test is +404 with the pre-existing 56 macOS-golden failures unchanged, so nothing rendered differently.

Both the guard's positive controls (dead constant, light/dark divergence) and all five hardened paths were confirmed by running the script against a deliberately broken tree and checking real exit codes.

✓ 163 color tokens, 27 dimension tokens and 8 font sizes per flavor, all referenced; light/ and dark/ and android/ and ios/ in agreement (209 Dart files scanned).

@coderabbitai

coderabbitai Bot commented Sep 10, 2026

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: 63e9f602-0450-498d-9a3b-ec283812e2cb


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codecov

codecov Bot commented Sep 10, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 0% with 3 lines in your changes missing coverage. Please review.
⚠️ Please upload report for BASE (chore/prune-unused-tokens@90d3b2e). Learn more about missing BASE report.

Files with missing lines Patch % Lines
...ternal/tokens/android/stream_tokens_font_size.dart 0.00% 1 Missing ⚠️
...s/internal/tokens/ios/stream_tokens_font_size.dart 0.00% 1 Missing ⚠️
...ives/internal/tokens/stream_tokens_dimensions.dart 0.00% 1 Missing ⚠️

❌ Your patch check has failed because the patch coverage (0.00%) is below the target coverage (80.00%). You can increase the patch coverage or adjust the target coverage.

Additional details and impacted files
@@                     Coverage Diff                      @@
##             chore/prune-unused-tokens     #184   +/-   ##
============================================================
  Coverage                             ?   68.55%           
============================================================
  Files                                ?      213           
  Lines                                ?     8596           
  Branches                             ?        0           
============================================================
  Hits                                 ?     5893           
  Misses                               ?     2703           
  Partials                             ?        0           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@renefloor
renefloor added this pull request to stack #185 September 10, 2026 12:29
@renefloor
renefloor force-pushed the chore/wire-dimension-tokens branch from 8c4696c to 5a8684e Compare September 10, 2026 12:43
@renefloor
renefloor force-pushed the chore/wire-dimension-tokens branch 2 times, most recently from ad73473 to be26581 Compare September 11, 2026 08:16
@renefloor
renefloor marked this pull request as ready for review September 11, 2026 10:28
@renefloor
renefloor requested a review from a team as a code owner September 11, 2026 10:28
@renefloor
renefloor force-pushed the chore/wire-dimension-tokens branch from 3190680 to 2acd7cd Compare September 11, 2026 10:59
renefloor and others added 5 commits September 11, 2026 13:08
StreamSpacing, StreamRadius and StreamTokensTypography hard-coded the
same numbers the vendored token files already held, so the design
system's dimensions were mirrored twice with nothing tying the copies
together — the swatch-versus-baked-hex hazard one layer up, where an
upstream change can be applied to one and not the other in silence.

The three classes now read the tokens. The type ramp gains from it:
`height: 1.1111111111111112` becomes
`typographyLineHeightNormal / typographyFontSizeLg`, which says what
the number is instead of leaving a magic constant. Every ratio resolves
bit-identically, so no golden moves.

Adds internal/tokens/stream_tokens_dimensions.dart, beside the
mode-independent typography file rather than under light/ and dark/,
because the token repo publishes one set of dimensions for every mode —
all 180 constants were byte-identical between the two files. Values
come from the web flavor, the only one carrying the Geist family this
package ships. They are typed `double` so they satisfy Radius,
EdgeInsets and TextStyle directly.

Two tokens are deliberately not carried, since nothing can read them:
font weights, because TextStyle.fontWeight takes a FontWeight that
cannot be built from a number in a const expression, and radiusNone,
because the analyzer's use_named_constants prefers Radius.zero over
circular(0).

Also extends check_tokens.dart to hold the dimension file to the same
rule, which is what caught radiusNone and a stale skip that hid the
typography file's own reads.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The skill still said the dimension classes hard-code their values and
that a change has to be applied to them by hand, which this branch
makes false. It now says a dimension is a value edit in
stream_tokens_dimensions.dart and the classes follow, records why font
weights and radiusNone are absent, and names melos run check:tokens as
what enforces the rule — so there is no judgment call about what
belongs in a token file.

Also brings three now-stale details in line: the file table gains the
dimensions row, the reader list gains the three dimension classes, and
After editing runs check:tokens next to analyze.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The skill gained this in the previous commit, but CLAUDE.md still described
`StreamSpacing`, `StreamRadius` and `StreamTokensTypography` as hard-coding
their values, which this PR is what changes. It now points at
`stream_tokens_dimensions.dart` and at `check:tokens`, so the repo overview and
the skill agree.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…pen paths

`StreamTokensTypography` was wired to the dimension tokens, but nothing read it
— not one Dart file in the repo, only prose. Meanwhile the public, exported
typography surface still hard-coded every number. So the PR's premise held for
`StreamSpacing` and `StreamRadius` and not for type: the 10 font-size and
line-height constants were kept alive by a dead intermediary.

`StreamLineHeight` now reads the three line-height tokens, which match exactly.
The dead class is deleted, and with it the seven `typographyFontSize*` constants
and the font family it was the only reader of. Font sizes stay unvendored on
purpose: `StreamFontSize` ships an android and an ios scale, only android
matches the web values, and the ios flavor is not vendored — wiring one and not
the other would read as an oversight rather than a choice.

That leaves the file mode- *and* flavor-independent, since spacing, radius and
line heights are identical across android, ios and web.

The guard would have caught this had it looked. It did not, and it had four
other ways to pass without checking anything — each confirmed by running it:

- Empty both color files and it reported `✓ 0 color tokens … all referenced`.
- Delete the dimensions file and it reported `✓ … and 0 dimension tokens`.
- A `--dir-exists` filter that stops matching selects zero packages, and melos
  prints SUCCESS. The one refactor most likely to need this guard — moving
  `internal/` — is the one that silently disabled it.
- A dead token stayed "referenced" via a mention in a doc comment, and
  `comment_references` is off, so that is not even a lint.

So: every "nothing to check" path is now a failure. Minimum declaration counts,
a scanned-file count in the success line, comments and strings stripped before
matching, a declaration-count cross-check so an unparsed form cannot hide, an
error on any vendored file the check does not cover, and stale allowlist entries
reported. The melos filter is gone — it runs once from the root and the script
locates the package itself.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Typography was left half-wired: `StreamLineHeight` read its tokens but
`StreamFontSize` and `StreamFontWeight` still hard-coded theirs, on two
arguments that do not survive checking.

The first was that font sizes could not be wired because only the android scale
matches the values this package vendored. True, but the fix is to vendor both:
the token repo publishes an ios flavor whose eight values match
`StreamFontSize.ios` exactly. They now mirror upstream's split in
`internal/tokens/{android,ios}/stream_tokens_font_size.dart`, keeping the bare
upstream names, and feed the two scales.

The second was that a `FontWeight` cannot be built from a number in a const
expression. That is true of the raw 400/500/600/700 but does not mean the token
has no home — the vendored constants are already declared in the types Flutter
consumes rather than the types upstream emits, which is why the rest are
`double`. The weights are `FontWeight` on the same reasoning, and
`StreamFontWeight` reads them.

Both rested on a third claim, that only the web flavor carries the `Geist`
family this package ships. This package ships no text font at all — the one
bundled face is the generated `Stream Icons` — and never sets `fontFamily` for
text. So the flavor question was only ever about sizes, and the corrected note
says so.

All 23 wired values are identical to the literals they replace; `flutter test`
is `+404 -56`, the 56 unchanged.

`check:tokens` grew a matching branch, and the hardening earned itself out
immediately: the uncovered-file check caught both new flavor files before they
were wired in. It now also requires android/ and ios/ to declare the same names,
and that each flavor file is actually imported — they declare identical names,
so an orphaned one would otherwise still look referenced.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@renefloor
renefloor force-pushed the chore/wire-dimension-tokens branch from 2acd7cd to 98d3734 Compare September 11, 2026 11:10
@renefloor
renefloor merged commit f4d49e9 into main Sep 11, 2026
14 of 15 checks passed
@renefloor
renefloor deleted the chore/wire-dimension-tokens branch September 11, 2026 11:15
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.

2 participants