fix(ui): seventeen error messages that were never red, and a test so it cannot happen again - #20
Merged
Merged
Conversation
…it cannot happen again `--color-danger` was never defined. Seventeen places say `text-danger` or `bg-danger-soft` — the failed import, the calendar that would not fetch, the model that would not download, the agent that refused, every `role="alert"` in onboarding — and Tailwind v4 emits a utility only when it can resolve the token behind it. Emitting nothing is not an error: no rule, no warning, and no visible sign except that the alert is the same colour as the sentence above it. `.text-danger` appears zero times in the built stylesheet. The same fault in a second place: `border-ok/30 bg-ok-soft` on the onboarding success panel, against a `--color-ok` that does not exist either. Pointed at the accent, which is what that panel meant; a fifth green would have been a fifth thing to keep in step. ## The test `theme.test.ts` checked that what the theme *declares* survives into the bundle. It now also checks the other direction — that every `text-`, `bg-`, `border-` and `ring-` colour the interface *names* was ever declared. Nothing else could have caught this. The type checker sees a string, the linter sees a string, and the screenshot audit is happy: grey text on the page background is a perfectly legible contrast pair, so it scored those alerts as passing. Tailwind overloads these prefixes, so the check resolves a name against `--color-*` first, then the size, radius, font and shadow scales (`text-meta` is a size, `rounded-card` a radius), then peels off an edge or an offset (`border-l-accent`, `ring-offset-bg`). Comments and arbitrary values are stripped before scanning: a doc comment saying why `bg-soft` was rejected must not read as a use of it, and `bg-[linear-gradient(…var(--color-bg-elevated)…)]` is CSS passing through rather than a utility. ## Two empty screens that read as broken The voice book put "GIỌNG ĐÃ BIẾT" at the top of a blank pane with "no voices yet" four hundred pixels below — two answers to the same question with a void between them. The heading now only appears over a list. The calendar centred its empty state in what was left under the subscribe form, which put the explanation of why there is nothing three hundred pixels beneath the box that fixes it. That screen no longer asks for `full`, which means "this is the whole screen" and was never true here. Capping `full` centrally was tried first and reverted: it made every screen where the empty state genuinely *is* the content fail `e2e/density.mjs` with four hundred pixels of background under a centred message. The comment in `Empty` now says so, because the next person to notice the calendar gap will reach for the same fix. Also: the notes pane's empty state offers the button that fixes it. It was a dead end whose only way out was a 288px column away in the corner of another pane. All 18 browser suites green, 298 unit tests, 44 screenshots re-shot in both schemes with no overflow and contrast AA everywhere.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
--color-dangerwas never definedSeventeen places say
text-dangerorbg-danger-soft— the failed import, the calendar that would not fetch, the model that would not download, the agent that refused, everyrole="alert"in onboarding. Tailwind v4 emits a utility only when it can resolve the token behind it, and emitting nothing is not an error: no rule, no warning, and no visible sign except that the alert is the same colour as the sentence above it..text-dangerappears zero times in the built stylesheet.Same fault in a second place:
border-ok/30 bg-ok-softon the onboarding success panel, against a--color-okthat does not exist either. Pointed at the accent, which is what that panel meant — a fifth green would have been a fifth thing to keep in step.The test
theme.test.tschecked that what the theme declares survives into the bundle. It now also checks the other direction: everytext-,bg-,border-andring-colour the interface names was declared somewhere. Nothing else could have caught this — the type checker sees a string, the linter sees a string, and the screenshot audit was happy, because grey text on the page background is a perfectly legible contrast pair.Tailwind overloads these prefixes, so a name is resolved against
--color-*first, then the size, radius, font and shadow scales (text-metais a size,rounded-carda radius), then by peeling off an edge or an offset (border-l-accent,ring-offset-bg). Comments and arbitrary values are stripped before scanning: a doc comment explaining whybg-softwas rejected must not read as a use of it, andbg-[linear-gradient(…var(--color-bg-elevated)…)]is CSS passing through rather than a utility.Two empty screens that read as broken
The voice book put "GIỌNG ĐÃ BIẾT" at the top of a blank pane with "no voices yet" four hundred pixels below — two answers to the same question with a void between them. The heading now only appears over a list.
The calendar centred its empty state in what was left under the subscribe form, putting the explanation of why there is nothing three hundred pixels beneath the box that fixes it. That screen no longer asks for
full, which means "this is the whole screen" and was never true here.Capping
fullcentrally was tried first and reverted: it made every screen where the empty state genuinely is the content faile2e/density.mjs. The comment inEmptynow says so, because the next person to notice the calendar gap will reach for the same fix.Also: the notes pane's empty state offers the button that fixes it. It was a dead end whose only way out was a 288px column away in the corner of another pane.
Verification
All 18 browser suites green, 298 unit tests, 44 screenshots re-shot in both schemes — no overflow, contrast AA everywhere.
🤖 Generated with Claude Code