Skip to content

refactor(ui): ban bare ease keywords, enforce var(--ease-*) tokens - #431

Merged
jackwener merged 4 commits into
mainfrom
opencode/motion-token-governance
Jul 2, 2026
Merged

jackwener merged 4 commits into
mainfrom
opencode/motion-token-governance

Conversation

@Astro-Han

@Astro-Han Astro-Han commented Jul 2, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Extend motion-token-converge-contract.test.ts to ban bare ease/ease-in/ease-out/ease-in-out/ease-linear/linear timing keywords, enforcing var(--ease-*) tokens. Fix the dead --ease-maka reference.

Why

Closes #430 (PR1 motion)

maka's motion governance already had a half contract (locking the cubic-bezier curve + transition: all), but bare ease keywords kept slipping through. This PR closes that gap so all easing goes through the four tokens (--ease-out-strong / --ease-in-out-strong / --ease-drawer / --ease-linear), converging the same way radius did.

Scope

Changed:

  • New contract it: scan renderer CSS + maka-tokens.css + @maka/ui TSX, ban bare ease/ease-in/ease-out/ease-in-out/ease-linear/linear — no whitelist, no exemptions
  • New --ease-linear: linear token (for infinite-loop functional animations: spinners, shimmer)
  • Batch-replace ~200 bare ease keywords → var(--ease-out-strong) or var(--ease-in-out-strong)
  • 2 real linear uses (spinner/shimmer) → var(--ease-linear)
  • Fix dead reference packages/ui/src/ui.tsx:30 --ease-maka → --ease-out-strong
  • Update chat-tool-card-cascade-contract.test.ts assertion to match token usage
  • Update animation-catalog.stories.tsx: status pulse → var(--ease-in-out-strong), Easing Scale adds --ease-linear entry

Not included:

  • Duration tokenization (140ms/160ms are tuned, untouched)
  • Icon / typography / spacing governance (PR2/PR3)

Verification

  • npm run -w @maka/desktop test: 1663/1663 pass
  • npm run typecheck: clean
  • Motion contract test: 7/7 pass (including the new fixture it locking the review cases: ease-linear catch, same-line transition/animation linear, comma-separated animation item, ease-in-out single report)

Reviewer notes

  • The contract uses a single longest-match-first matcher (ease-in-out | ease-linear | ease-in | ease-out | ease | linear) via matchAll, so ease-in-out is reported once (not also as ease-in), and the Tailwind ease-linear class is caught explicitly.
  • --ease-linear: linear is a token so infinite-loop animations don't need a whitelist. The declaration line is stripped before scanning (same pattern as --ease-out-strong).
  • Two rounds of code review (Codex + human) confirmed the matcher has no bypass vectors after the --ease-linear token replaced the per-line/per-segment infinite whitelist.

Astro-Han added a commit that referenced this pull request Jul 2, 2026
Codex review findings on #431:
- [P2] BARE_EASE skipped hyphenated values, so bare `ease-in` slipped
  through the contract. Add BARE_EASE_IN.
- [P2] linear whitelist skipped the whole line when both `linear` and
  `infinite` appeared, letting a `linear` in a same-line `transition`
  pass. Whitelist per match, not per line.
Astro-Han added a commit that referenced this pull request Jul 2, 2026
…whitelist

Address code review on PR #431:

P1: the five separate regexes let bare `linear` bypass the contract.
`ease-linear` (Tailwind class → `transition-timing-function: linear`)
fell between `ease` and `linear` regexes (both rejected by the
hyphen lookbehind/lookahead). A same-line `transition ... linear;
animation ... infinite` was shielded entirely because the whitelist
skipped the whole line on any `infinite` match.

Replace with one longest-match-first matcher using matchAll:
`ease-in-out | ease-linear | ease-in | ease-out | ease | linear`.
Split each line into `;`-delimited declaration segments and scan
each independently — `linear` is whitelisted only when its own
segment contains `infinite`, so a transition `linear` on the same
line as an infinite animation is still flagged.

P3: `ease-in-out` was reported twice (as `ease-in` AND
`ease-in-out`) because the separate `ease-in` regex matched the
prefix. The single ordered matcher resolves this — longest token wins.

Add a fixture `it` locking the four review cases: ease-linear catch,
same-line transition/animation linear, ease-in-out single report,
infinite-loop linear whitelist.
Astro-Han added a commit that referenced this pull request Jul 2, 2026
…tors

Address code review round 2 on PR #431:

P1: the segment-scoped `linear` whitelist (split on `;`) still had
bypass vectors. A TSX inline style
`style={{ transition: "opacity 100ms linear", animation: "spin 1s
linear infinite" }}` was not split (no `;`), so the transition
`linear` was shielded by the animation `infinite`. A comma-separated
`animation: spin 1s linear infinite, fade 100ms linear 1;` kept both
`linear` in one segment, so the non-infinite second item was exempted.

Replace the whitelist with a token: `--ease-linear: linear`. Convert
the two real infinite-loop animation sites (maka-status-spin spinner,
maka-shimmer) to `var(--ease-linear)`. The contract now bans ALL bare
ease/linear uniformly — no split, no per-line/per-segment exemption,
no bypass vector. The `--ease-linear: linear;` declaration itself is
stripped before scanning (same pattern as `--ease-out-strong`).

Update fixture `it` with the two review negative cases: TSX same-line
transition+animation linear, and comma-separated animation with a
non-infinite second item.
Astro-Han added 4 commits July 2, 2026 23:55
Add a new invariant to motion-token-converge-contract.test.ts banning
bare ease/ease-out/ease-in-out/linear timing keywords in renderer CSS
and @maka/ui TSX. Replace all bare keywords with var(--ease-out-strong)
or var(--ease-in-out-strong). linear in infinite-loop animations
(spinners/shimmer) is whitelisted.

- Fix dead --ease-maka reference in ui.tsx (→ --ease-out-strong)
- Update chat-tool-card-cascade-contract assertion to match token usage

Part of #430.
Codex review findings on #431:
- [P2] BARE_EASE skipped hyphenated values, so bare `ease-in` slipped
  through the contract. Add BARE_EASE_IN.
- [P2] linear whitelist skipped the whole line when both `linear` and
  `infinite` appeared, letting a `linear` in a same-line `transition`
  pass. Whitelist per match, not per line.
…whitelist

Address code review on PR #431:

P1: the five separate regexes let bare `linear` bypass the contract.
`ease-linear` (Tailwind class → `transition-timing-function: linear`)
fell between `ease` and `linear` regexes (both rejected by the
hyphen lookbehind/lookahead). A same-line `transition ... linear;
animation ... infinite` was shielded entirely because the whitelist
skipped the whole line on any `infinite` match.

Replace with one longest-match-first matcher using matchAll:
`ease-in-out | ease-linear | ease-in | ease-out | ease | linear`.
Split each line into `;`-delimited declaration segments and scan
each independently — `linear` is whitelisted only when its own
segment contains `infinite`, so a transition `linear` on the same
line as an infinite animation is still flagged.

P3: `ease-in-out` was reported twice (as `ease-in` AND
`ease-in-out`) because the separate `ease-in` regex matched the
prefix. The single ordered matcher resolves this — longest token wins.

Add a fixture `it` locking the four review cases: ease-linear catch,
same-line transition/animation linear, ease-in-out single report,
infinite-loop linear whitelist.
…tors

Address code review round 2 on PR #431:

P1: the segment-scoped `linear` whitelist (split on `;`) still had
bypass vectors. A TSX inline style
`style={{ transition: "opacity 100ms linear", animation: "spin 1s
linear infinite" }}` was not split (no `;`), so the transition
`linear` was shielded by the animation `infinite`. A comma-separated
`animation: spin 1s linear infinite, fade 100ms linear 1;` kept both
`linear` in one segment, so the non-infinite second item was exempted.

Replace the whitelist with a token: `--ease-linear: linear`. Convert
the two real infinite-loop animation sites (maka-status-spin spinner,
maka-shimmer) to `var(--ease-linear)`. The contract now bans ALL bare
ease/linear uniformly — no split, no per-line/per-segment exemption,
no bypass vector. The `--ease-linear: linear;` declaration itself is
stripped before scanning (same pattern as `--ease-out-strong`).

Update fixture `it` with the two review negative cases: TSX same-line
transition+animation linear, and comma-separated animation with a
non-infinite second item.
@jackwener
jackwener force-pushed the opencode/motion-token-governance branch from a23f11c to 974e9e9 Compare July 2, 2026 15:57
@jackwener
jackwener merged commit 52e5e23 into main Jul 2, 2026
@jackwener
jackwener deleted the opencode/motion-token-governance branch July 2, 2026 15:57
@jackwener

Copy link
Copy Markdown
Member

Thanks, merged! 🎉 Verified locally: the contract scanner reports 0 offenders after the change and all 1667 desktop tests pass. Nice catch on the dead --ease-maka token in ui.tsx — button transitions were silently falling back to browser-default easing, so this is a real fix on top of the governance work. Two minor follow-ups if you're interested: the TS/TSX scanner reads raw text (no comment stripping, unlike the CSS path) so a future bare ease/linear word in a comment could false-positive; and the maka-tokens.css:1592 note about a "circular reference" is slightly off — the real reason the fallback had to go is the new regex flags the bare ease-out inside it. Neither blocks anything.

Astro-Han added a commit that referenced this pull request Jul 2, 2026
Follow-up to #431: the merged PR tokenized shimmer but left the
status pulse as bare `ease-in-out` and the Easing Scale story
without the new `--ease-linear` entry. Finish the catalog so all
developer-facing easing examples use `var(--ease-*)`.
jackwener added a commit that referenced this pull request Jul 3, 2026
Closes out every remaining item from the 2026-07-03 three-way code
review instead of leaving them as suggestions:

- Search modal focus yank (P0): selecting a result navigated to the
  chat turn but useModalA11y unconditionally restored focus to the
  sidebar search trigger on unmount, defeating the call site's
  restoreFocus:false intent. The hook now takes an optional
  suppressFocusRestoreRef; SearchModal sets it when navigation owns
  focus. Lifecycle contract updated to the new signature/structure
  (StrictMode still-mounted guard unchanged, one line later).
- Skills view switcher ARIA (P0): role=tablist/tab promised the ARIA
  tabs keyboard contract (roving tabindex, arrow keys, linked panels)
  that plain buttons never implemented. Now honest aria-pressed
  segmented buttons.
- Bot platform nav freeze (P0, amended per existing contract): the
  contract intentionally locks platform switching while a provider
  action runs, but all seven rows froze with NO explanation. Kept the
  lock, added the missing title (当前操作进行中,完成后可切换平台).
- Dead filter pills (P2): 全部 / 排序:热门 were disabled buttons
  visually promising interactivity; now static spans (CSS gains
  explicit inline-flex so the span keeps the 28px pill geometry).
- Motion contract TSX scanner (#431 review P2): strip //-line and
  /*-block comments before scanning so prose "ease"/"linear" in doc
  comments cannot false-positive — closes the CSS/TSX asymmetry.

Desktop suite 1696/1696.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

refactor(ui): converge design-token magic numbers (motion, typography, spacing)

2 participants