feat(theme-contract): exportable token/utility contract checker + bin (#188 Phase 2)#196
Merged
Merged
Conversation
…#188 Phase 2) Consumer apps ship UI that references theme tokens/utilities their own build never emits, so the surface resolves to nothing and paints transparent with no error at runtime (tax-agent's invisible model dropdown; the whole bg-surface-container-* family). tests/theme/tokens-contract.test.ts only guards agent-app's OWN components, so every consumer was unprotected. Lift the referenced-vs-defined token walk out of that test into an exported checkThemeContract(), and ship a bin every consumer runs in CI over ITS OWN source. Two checks: - var(--…): COMPLETE. Every literal custom-property reference (inline styles, bg-[var(--…)] arbitrary Tailwind values, CSS strings) vs the shipped tokens.css + any extra app CSS. - Tailwind utilities: INTENTIONALLY PARTIAL. Bare classes carry no var() so the first check can't see them; fully resolving Tailwind is out of scope. Check the specific known-dangerous families the preset maps to elevation tokens (surface-container*/card/popover) — exactly the ones that shipped invisible. The canvas/sequence aliases are consumed as bg-[var(--…)] and so are already covered by check 1. The checker reads fs, so it lives in a new ./theme-contract subpath; ./theme stays browser-clean (browser-safe manifest test still green). tokens-contract. test.ts now consumes the export as the single source of truth for the walk.
tangletools
approved these changes
Jul 15, 2026
tangletools
left a comment
There was a problem hiding this comment.
✅ Auto-approved drewstone PR — 9d421a13
This PR was opened by the trusted drewstone account.
The full PR reviewer audit still runs separately and will publish findings if it detects issues.
tangletools · auto-approval · reason: drewstone_author · 2026-07-15T19:38:26Z
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.
The incident class
Consumer apps ship UI that references theme tokens or Tailwind utilities their own build never emits. CSS resolves the missing custom property to nothing, the surface paints transparent, and nothing errors — it ships invisible. Concrete cases: tax-agent's transparent model dropdown, and the whole
bg-surface-container-*family that maps (via the agent-app preset) onto elevation tokens the consumer's build dropped.tests/theme/tokens-contract.test.tsonly guarded agent-app's own components, so every consumer was unprotected against exactly the bug agent-app's tokens created for them.What this adds
checkThemeContract({ srcDirs, tokensCss?, extraTokensCss?, allowlist? })→{ ok, missing }— the referenced-vs-defined token walk, lifted out of the self-test into an exported function every consumer runs against its own source.tokens-contract.test.tsnow consumes the export (single source of truth; its behavior is unchanged).agent-app-theme-checkbin —--src <dir>(repeatable),--extra-css <file>(repeatable),--tokens,--allow. Prints each missing token with the referencing file and exits 1 on any miss. Products add it as a CI step.Reads
fs, so it lives in a new./theme-contractsubpath;./themestays browser-clean (the browser-safe manifest test is still green).Scope of each check (kept honest)
var(--…)literal scanbg-[var(--…)]arbitrary values, CSS strings — any literal custom-property referencesurface-container/-high/-highest,card,popover. Bare classes carry novar()so check 1 can't see them; fully resolving arbitrary Tailwind config is out of scope. The canvas/sequence aliases are consumed asbg-[var(--…)]and so are already covered by check 1.The boundary is documented in the module docblock.
Tests
Fixture consumer dir + controlled
tokens.css(omits--popover): defined-token reference passes, undefinedvar()fails with file attribution, undefined dangerous utility (bg-surface-container-high→--popover) fails and names the class, allowlist suppresses, extra-css satisfies. Full suite: 150 files / 2421 tests green; heap-bumped build green; CLI verified end-to-end (fail exit 1, pass exit 0, default-tokens resolution fromdist).Part of #188 Phase 2 (gate class 7).