Skip to content

moooon

Motir

Vibe your whole project. Bring an idea — Motir's three AI layers plan it, track it, and ship it, end to end. You're looking at Motir, built in Motir.

  • Vibe Project
  • Open Source
  • AI Agent
  • AI Loop
1
requests
0
upvotes
145
planned
1,361
shipped

Motir · Work items

MOTIR-3933Done

A scoped `StyleVignette` shows the ACTIVE palette, not its own — `data-appearance-scope` is missing, the `motir` base has no block, and the nine palette DARK blocks are compound selectors a nested scope cannot match

Found on the running motir.co/design build (MOTIR-1043), which mounts TokensSpecimen and therefore renders its StyleVignette galleries.

▶ RE-SCOPED 2026-08-29 by the run that claimed it

The card as authored bundled TWO defects at one 3-point estimate, and its own criterion 2 was not satisfiable by the fix it named. This card is now the PALETTE and TYPE axes, complete, in both themes. The STYLE axis turned out to be an isolation problem across 69 descendant material rules rather than a token leak, and is carved out — the run's own diagnosis is on that card and in the plan summary.

The defect — the palette gallery is inert

TokensSpecimen renders PALETTE_IDS.map((palette) => <StyleVignette palette={palette} />). Every one of the ten renders in the ACTIVE palette, not its own. The section labelled "Palette axis (data-palette)" demonstrates the palette axis by showing the same tile ten times.

Cause 1 — the missing attribute

theme.css states the requirement at the top of its Tier-3 block:

"[data-appearance-scope] re-emits this whole Tier-3 layer onto a NESTED element too — not just :root. --el-* reference --color-* and var() resolves at the DECLARING element … Put data-appearance-scope on an element alongside its data-* axes …"

StyleVignette sets the axis attributes and not that one (src/components/theme/StyleVignette.tsx):

const axisAttrs: Record<string, string> = {};
if (styleId) axisAttrs['data-style'] = styleId;
if (palette) axisAttrs['data-palette'] = palette;
if (type) axisAttrs['data-type'] = type;
// no data-appearance-scope

A [data-palette] block declares only Tier-0 --color-*; the vignette paints through Tier-3, declared at :root and resolving there.

Cause 2 — the motir base has no block

motir is the base palette, so it has no [data-palette='motir'] block at all — the base values live in @theme. A scoped motir tile under a non-base ancestor therefore inherits the ancestor's colours with nothing to re-assert. It reads "correct" only while motir happens to BE the active palette.

Cause 3 — the DARK blocks are compound selectors

The nine per-palette dark blocks are [data-palette='X'][data-theme='dark'] — both attributes on ONE element. A scoped vignette carries data-palette only and inherits data-theme from <html>, so it never matches, and in dark mode a scoped tile resolves the palette's LIGHT colours.

Measured — headless chromium, theme.css as shipped, <html data-palette="evergreen">

as shippedwith data-appearance-scope alone
light1 / 109 / 10 — only motir fails (cause 2)
dark1 / 102 / 10 — causes 2 and 3 (cobalt, graphite, evergreen, spectrum, amber, sienna, candy all fail)

The "1" in the as-shipped column is whichever palette is ACTIVE — so the correct cell MOVES, and nine different tiles are wrong after every pick.

Acceptance criteria

  • StyleVignette emits data-appearance-scope on its wrapper whenever any of styleId / palette / type is supplied, and not in LIVE mode (no axis prop), where re-emitting the layer would pin the subtree against the live selection it exists to follow.
  • A [data-palette='motir'] block exists carrying the base colour values, with a dark arm, so the base entry re-asserts rather than inherits. A test asserts each declaration equals the value the base (@theme / the Tier-3 :root block) declares for that token — the drift guard the duplication owes.
  • The nine [data-palette='X'][data-theme='dark'] blocks are reachable from a nested scope (e.g. gaining a [data-theme='dark'] [data-palette='X'] arm), so a scoped tile in dark resolves its own palette's dark colours.
  • A [data-type='motir'] base block for the same reason on the type axis — three --font-* tokens, and that axis carries no descendant rules.
  • A test over the FULL registries, in BOTH themes: for every PALETTE_ID and every TYPE_ID, a subtree scoped to it under a non-base <html> resolves that entry's own values. Reported as a matrix, not a sample. It must FAIL on the current code.
  • StyleVignette's header caveat paragraph is rewritten. The sentence "In the real consumers this never bites" is false — it is a claim about the consumer set at the time of writing, recorded as a property of the component — and it is what let this ship.

Boundary

motir-core only, packages/design-system/ and its tests. The STYLE axis is explicitly NOT in scope — the base [data-style='warm-editorial'] block is deliberately NOT added here, because it would make an assertion pass on a tile that still paints as the ancestor's style. It does not touch motir-marketing, does not re-pin anything, and does not publish the package: motir.co keeps rendering the broken gallery until a publish + re-pin card lands, the shape MOTIR-3872 used for the last publish. That card is owed and is named here rather than left as prose.

Advisory disposition (projected over this re-plan)

  • shape · likely-over-gate-sizing, estimate_minutes, 5 points / 75 minutes — READ AND KEPT, not silenced. The gate's own text says the minutes arm is a PROXY for the run ceiling, "because estimateMinutes sums agent time and CI time while the gate ceilings the agent run alone". The ceiling is on the AGENT RUN and it is 60 minutes; this card's agent run is ~35 — one attribute, two base blocks, nine selector arms, one test file and a comment rewrite. The remaining ~40 is motir-core's own CI, which a card does not control and which the calibration table already prices at 25–40 for a 5-pointer. The number is the honest sum and it stays. It is recorded here so the next reader does not either re-litigate it or quietly trim the estimate to clear an advisory — trimming would make the card cheaper on the board and no cheaper to run.