bgSurfaceCard exists in both port registries and not in the canonical DTCG #233

Closed
opened 2026-10-27 22:48:50 +00:00 by peterswimm · 1 comment
Owner

Split out from #231's decision comment, which records this as "a separate token and design-system gap" without filing one.

The gap, in #231's own words

Card surface. The wireframes use the card fill that the Flutter and macOS registries call bgSurfaceCard. The canonical DTCG has no such token. This Flutter and macOS card-surface mismatch is recorded as a separate token and design-system gap. #231 does not propose a canonical token. That needs its own evidence and decision.

So both ports ship a token the canonical source does not define. The ports agree with each other and disagree with the canon.

Why this is the awkward direction of drift

The usual drift is canon-ahead-of-code, which a sync catches. This is code-ahead-of-canon in both ports at once, which means:

  • apps/rituals-macos/tool/dtcg_to_udg_tokens.py generates udg_tokens.json from the DTCG source. A token that exists only downstream of that generator is one the generator cannot produce or validate.
  • Whatever the wireframes are drawn against is therefore not the canonical system. The design and the build agree by coincidence rather than by derivation.

What to decide

  1. Does bgSurfaceCard become a canonical DTCG token, or do the ports stop using it in favour of an existing canonical surface token?
  2. If canonical: what is its relationship to the surface tokens already in the canon — a distinct role, or an alias?
  3. If retired: which canonical token do the card fills resolve to, and does that change any rendered surface?

#231 is explicit that this needs its own evidence, so this issue is the place to gather it rather than to assume an answer.

Acceptance

  • A decision recorded here, either way.
  • Canonical DTCG and both port registries agree — no token exists in a registry that the canon cannot generate.
  • If a token is added or removed, dart run tool/export_udg_tokens.dart regenerates cleanly and the committed apps/rituals-macos/Resources/udg_tokens.json matches.
Split out from #231's decision comment, which records this as "a separate token and design-system gap" without filing one. ## The gap, in #231's own words > **Card surface.** The wireframes use the card fill that the Flutter and macOS registries call `bgSurfaceCard`. The canonical DTCG has no such token. This Flutter and macOS card-surface mismatch is recorded as a separate token and design-system gap. #231 does not propose a canonical token. That needs its own evidence and decision. So both ports ship a token the canonical source does not define. The ports agree with each other and disagree with the canon. ## Why this is the awkward direction of drift The usual drift is canon-ahead-of-code, which a sync catches. This is code-ahead-of-canon in **both** ports at once, which means: - `apps/rituals-macos/tool/dtcg_to_udg_tokens.py` generates `udg_tokens.json` from the DTCG source. A token that exists only downstream of that generator is one the generator cannot produce or validate. - Whatever the wireframes are drawn against is therefore not the canonical system. The design and the build agree by coincidence rather than by derivation. ## What to decide 1. Does `bgSurfaceCard` become a canonical DTCG token, or do the ports stop using it in favour of an existing canonical surface token? 2. If canonical: what is its relationship to the surface tokens already in the canon — a distinct role, or an alias? 3. If retired: which canonical token do the card fills resolve to, and does that change any rendered surface? #231 is explicit that this needs its own evidence, so this issue is the place to gather it rather than to assume an answer. ## Acceptance - [ ] A decision recorded here, either way. - [ ] Canonical DTCG and both port registries agree — no token exists in a registry that the canon cannot generate. - [ ] If a token is added or removed, `dart run tool/export_udg_tokens.dart` regenerates cleanly and the committed `apps/rituals-macos/Resources/udg_tokens.json` matches.
Member

Decision for #233: bgSurfaceCard becomes a canonical token

This is Ville's design decision.

Decision

bgSurfaceCard becomes a canonical DTCG token, the semantic card-surface role. Its canonical path is color.bg.surface-card. Its udg.dart-key stays bgSurfaceCard, so no Flutter code changes name.

Why

The system needs a card surface that separates a card from its surrounding surface without an outline. This supports the Rituals visual direction: fills, not decorative borders (#252). The canonical set has no such step today. bg.surface-raised is 1.09:1 from the page in light and 1.02:1 in dark. bg.surface-variant is 1.02:1 and 1.12:1. They are too close to the page to replace an outline.

Not an alias of bg.selected

bg.selected reaches the same separation (1.51:1 light, 1.50:1 dark). It is still a different role. Card surface and selection are separate semantic states, and they can appear together. A selected card must show both. Aliasing them would remove that.

Values

Rituals Light and Dark keep their existing card surface values. Vanilla Night may use #333333 for this role.

Theme Value Separation from page textPrimary on it
light #E2AC96 1.51:1 8.52:1
dark #5F2E1C 1.53:1 9.17:1
vanillanight #333333 1.66:1 12.63:1

The Rituals values are not a formula and not a rule for other themes.

For Vanilla Night, #333333 follows its current monochrome direction. It gives the card enough separation from the black page and adds no new colour. This is a decision about the card surface role only. It does not approve the rest of the Vanilla Night palette. The existing bg.surface-active also uses #333333. That does not make card surface an alias of bg.surface-active. They are two roles. They may share one raw value.

What the audit found

  • Flutter is the only consumer. About 26 files and 39 sites use it. Four of them are theme defaults: Card, TextButton, OutlinedButton and Chip.
  • Swift does not consume it. There is no bgSurfaceCard accessor in UDGTokens.swift and no use. It appears in the macOS udg_tokens.json only because the export path (tool/export_udg_tokens.dart) reads the Flutter registry and carries it downstream. The DTCG-to-JSON generator would not produce it.
  • No visual change. Both light and dark values stay the same. Flutter renders as before.

The other themes

The DTCG structure does not require a value in every theme. Only light is required. A theme without a value falls back to the light value (extract.py, wcag.py). In the Rituals source, 37 of 64 colour tokens already have fewer than nine themes. The closest precedent is the surface family: bg.surface-raised and bg.surface-variant each define light, dark and vanillanight only.

The palettes of the non-Rituals themes are work in progress. They have not been reviewed or approved as a whole. Nothing in #233 approves Accessible Alt, Cosmic Crisp, Pardner, Sundered, Truancy or Vanilla Day, or any relationship between their existing colours.

#233 makes no card surface decision for those six themes. Their card surface values will be decided when each theme is reviewed. A theme gains its own value then.

Until then, the light fallback must not make that decision by accident. If it did, those themes would silently inherit the Rituals Light terracotta (#E2AC96), and a Rituals-specific hue would become their card surface without any design review. So the decision needs a guard. The guard protects against an accidental design decision by fallback. It is not a permanent restriction on those themes. A theme leaves the guard when it gets its own value. The technical shape of the guard is an implementation question.

Acceptance

  • color.bg.surface-card is added to the Rituals DTCG source with light, dark and vanillanight values, udg.css-var and udg.dart-key.
  • A guard stops any theme without its own value from receiving the token through the light fallback by accident. This covers the six themes above. A theme leaves the guard when it gets its own value.
  • The generated dist artifacts regenerate cleanly (just build, just check).
  • bgSurfaceCard leaves the app-authored maps in token_registry.dart. The generated registry supplies it.
  • dart run tool/export_udg_tokens.dart regenerates apps/rituals-macos/Resources/udg_tokens.json. The committed file matches.
  • Flutter goldens and the existing contrast tests pass with no change.
  • A text.primary on bg.surface-card pair is added to the contrast check. It covers the three themes that have a value.
  • No macOS accessor or use is added by this decision. That is a separate macOS decision.
**Decision for #233: `bgSurfaceCard` becomes a canonical token** This is Ville's design decision. **Decision** `bgSurfaceCard` becomes a canonical DTCG token, the semantic card-surface role. Its canonical path is `color.bg.surface-card`. Its `udg.dart-key` stays `bgSurfaceCard`, so no Flutter code changes name. **Why** The system needs a card surface that separates a card from its surrounding surface without an outline. This supports the Rituals visual direction: fills, not decorative borders (#252). The canonical set has no such step today. `bg.surface-raised` is 1.09:1 from the page in light and 1.02:1 in dark. `bg.surface-variant` is 1.02:1 and 1.12:1. They are too close to the page to replace an outline. **Not an alias of `bg.selected`** `bg.selected` reaches the same separation (1.51:1 light, 1.50:1 dark). It is still a different role. Card surface and selection are separate semantic states, and they can appear together. A selected card must show both. Aliasing them would remove that. **Values** Rituals Light and Dark keep their existing card surface values. Vanilla Night may use `#333333` for this role. | Theme | Value | Separation from page | `textPrimary` on it | |---|---|---|---| | light | `#E2AC96` | 1.51:1 | 8.52:1 | | dark | `#5F2E1C` | 1.53:1 | 9.17:1 | | vanillanight | `#333333` | 1.66:1 | 12.63:1 | The Rituals values are not a formula and not a rule for other themes. For Vanilla Night, `#333333` follows its current monochrome direction. It gives the card enough separation from the black page and adds no new colour. This is a decision about the card surface role only. It does not approve the rest of the Vanilla Night palette. The existing `bg.surface-active` also uses `#333333`. That does not make card surface an alias of `bg.surface-active`. They are two roles. They may share one raw value. **What the audit found** - **Flutter** is the only consumer. About 26 files and 39 sites use it. Four of them are theme defaults: `Card`, `TextButton`, `OutlinedButton` and `Chip`. - **Swift does not consume it.** There is no `bgSurfaceCard` accessor in `UDGTokens.swift` and no use. It appears in the macOS `udg_tokens.json` only because the export path (`tool/export_udg_tokens.dart`) reads the Flutter registry and carries it downstream. The DTCG-to-JSON generator would not produce it. - **No visual change.** Both light and dark values stay the same. Flutter renders as before. **The other themes** The DTCG structure does not require a value in every theme. Only `light` is required. A theme without a value falls back to the `light` value (`extract.py`, `wcag.py`). In the Rituals source, 37 of 64 colour tokens already have fewer than nine themes. The closest precedent is the surface family: `bg.surface-raised` and `bg.surface-variant` each define `light`, `dark` and `vanillanight` only. The palettes of the non-Rituals themes are work in progress. They have not been reviewed or approved as a whole. Nothing in #233 approves Accessible Alt, Cosmic Crisp, Pardner, Sundered, Truancy or Vanilla Day, or any relationship between their existing colours. #233 makes no card surface decision for those six themes. Their card surface values will be decided when each theme is reviewed. A theme gains its own value then. Until then, the `light` fallback must not make that decision by accident. If it did, those themes would silently inherit the Rituals Light terracotta (`#E2AC96`), and a Rituals-specific hue would become their card surface without any design review. So the decision needs a guard. The guard protects against an accidental design decision by fallback. It is not a permanent restriction on those themes. A theme leaves the guard when it gets its own value. The technical shape of the guard is an implementation question. **Acceptance** - `color.bg.surface-card` is added to the Rituals DTCG source with `light`, `dark` and `vanillanight` values, `udg.css-var` and `udg.dart-key`. - A guard stops any theme without its own value from receiving the token through the `light` fallback by accident. This covers the six themes above. A theme leaves the guard when it gets its own value. - The generated `dist` artifacts regenerate cleanly (`just build`, `just check`). - `bgSurfaceCard` leaves the app-authored maps in `token_registry.dart`. The generated registry supplies it. - `dart run tool/export_udg_tokens.dart` regenerates `apps/rituals-macos/Resources/udg_tokens.json`. The committed file matches. - Flutter goldens and the existing contrast tests pass with no change. - A `text.primary` on `bg.surface-card` pair is added to the contrast check. It covers the three themes that have a value. - No macOS accessor or use is added by this decision. That is a separate macOS decision.
ville closed this issue 2026-10-29 10:35:39 +00:00
Sign in to join this conversation.
No description provided.