scopeType as an open enum Moot — no scopeType shippedMoot: there is no scopeType/chain in the real model — Site and
Page each carry a plain activeThemeId. Kept for historical context
only.
The model doesn't hardcode chain depth — a node just has a parent. That
means a new tier can be inserted later (a regional grouping above MEC, an
intermediate tier below Page) without restructuring anything, as long as
resolution code never assumes a fixed chain length or a fixed set of
scopeType values. Worth stating as an explicit constraint on
any resolver implementation, not just an incidental property of the schema. Note
this now only applies to Site/Page — Container and Item
were deliberately simplified out of the scopeType chain entirely; see
Container and
Item: inline overrides, not cascading nodes.
Tokens are flat scalars today. A common next need is a token defined in terms of another token, not just a value that happens to match:
{
"Action/Primary": "{Surface/Brand}"
}
Without this, an Admin rebranding a Site by changing one core color has to hunt
down every token that was coincidentally set to the same value. This is how Style
Dictionary and Figma Variables handle it, and it still composes cleanly with the real,
flat theme model: resolve a theme's tokens dictionary as-is (no cascade
to walk), then resolve any {...} references against that resolved set as
a second pass. Still a relevant idea for the shipped model.
Per canonical token key, track a type (color /
dimension / fontFamily / …), and optionally
lockable and deprecated flags. This isn't invented from
nothing — design-tokens.html
already documents richer per-token metadata today (description, paired
On-* token, CSS variable name, per-namespace grouping), just as prose
rather than structured, queryable data. This point is really "formalize what already
exists as Tokens fields" (see
Tokens
structure), not a new concept. Doing so buys three things at once:
This registry has to be enforced uniformly — including against the Container/Item
*Token fields and the free-form tokenOverrides JSON field
(see Container / Item
token references), which have no dedicated theme-management screen of their own
to gate input the way a Site/Page theme editor would. This point is generically
relevant to the real, shipped token model, not just the cascade proposal — the Admin
API's POST /theme/validate-tokens and GET /theme/token-metadata
endpoints (admin-portal.html)
already cover roughly this ground server-side.
Moot: this extension point assumed a scopeChain to append a
context object to. There is no chain in the real model.
ALPA Mobile already has Canadian MECs — locale-based theming is a plausible near-future ask, and platform (iOS/Android/future web) is another. Neither fits naturally as a node in the Site → Page chain (or as a key in a Container/Item override field); they're independent dimensions that cut across the whole model.
parentThemeId. Model
resolution as taking a context object —
{ scopeChain, locale, platform } — and composing an ordered list of
applicable override sets. Decide now whether this is in scope, even if the context
object isn't populated with anything beyond scopeChain yet; adding the
parameter later is easy, but retrofitting call sites that assumed a single-axis
resolver is not.
This section's own concern turned out to be real and was solved — just not by
extending this proposal's per-node status flag. The problem
identified here (a single-row-per-scope model can't distinguish "the last published
value" from "an in-progress edit") is exactly what the shipped Admin API's snapshot
mechanism solves: a theme's workflowState (Draft/Published/Unpublished/Archived)
plus activeSnapshotId and hasDraftChanges mean the live
token rows (GET /theme/{key}) and the last-published snapshot
(GET /theme/{key}/published, immutable) coexist as two separate reads of
the same theme — an Admin can be mid-edit on a theme's draft tokens while the
Dynamic Feed keeps serving whatever was last published, with no "grandparent
fallback" ambiguity. See
admin-portal.html — Themes
section. The specific cascade-node mechanics below no longer apply.
Still relevant to the shipped model, in simplified form. There is no
cascade to walk, so "resolving" a theme is just fetching its
ThemeDto.tokens dictionary directly — but the client-side caching
question this section raised is still real: cache theme fetches by themeId
(see theme-endpoint-contract.html
§8) rather than re-fetching a Site's active theme on every screen. The
invalidation concern is simpler than originally framed here too — there is no
"which Pages does this Site change affect" traversal to design, since there is no
cascade; a changed theme is just re-fetched by whichever Sites/Pages reference its
id.
Moot as originally framed (there's no per-node ThemeNode to keep
history for), but the underlying need — "what did this theme change from, and who
changed it" — is only partly covered by the real system: ThemeDto.updatedAt
answers "when," and the Admin API's GET /theme/{key}/versions +
POST /theme/{key}/publish snapshot flow gives a version-numbered publish
history, but there's no documented per-token diff or arbitrary undo beyond reverting
to a prior published snapshot. Kept as an open question against the real model, not
the cascade.
The canonical set is 95 tokens across 15 categories (not the "45
fixed keys" originally stated here — see the token-count warning at the top of
theme-endpoint-contract.html).
The underlying point stands regardless of the exact count: when a token is renamed or
removed later, existing theme rows referencing the old key shouldn't silently break. A
small tokenAliases map (old key → new key), or a
tokenSchemaVersion stamped on each theme, lets the canonical set evolve
without a flag-day migration across every existing theme. Nothing in Vlad Oprica's
docs suggests this has been built — still a fair, generically relevant open item.
| # | Lever | Design now / build now? |
|---|---|---|
| 5 | Draft/Publish single-row gap | Solved differently — the shipped Admin API's workflowState/activeSnapshotId/published-snapshot mechanism addresses this without a per-node status flag. See §5. |
| 1 | Open scopeType enum | Moot — no scopeType shipped. |
| 3 | Token registry + lockable tokens | Still relevant — the real Admin API's token-metadata/validate-tokens endpoints cover validation; token-locking/governance is not documented as built. |
| 2 | Token aliasing | Still relevant — not documented as built; would still be useful against the real flat token model. |
| 4 | Orthogonal axes (locale/platform) | Moot as scoped (assumed a scopeChain to extend) — would need to be re-designed against Site/Page assignment if this need arises. |
| 6 | Resolved-token caching | Still relevant, simplified — client-side cache-by-themeId, no invalidation traversal needed. See §6. |
| 7 | Per-node version history | Moot as scoped — partially covered by the real publish/version-snapshot flow. See §7. |
| 8 | Token schema migration story | Still relevant — defer until the first real token rename/removal is needed. |