Introduce @wafflebase/tokens shared design tokens package#292
Conversation
📝 WalkthroughWalkthroughThis PR introduces ChangesTokens Package Infrastructure and Core Definitions
Consumer Package Integration
Build System and Monorepo Wiring
Design Planning and Color Picker Refinements
Estimated Code Review Effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly Related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
@copilot resolve the merge conflicts in this pull request |
Done — I merged |
Verification: verify:selfResult: ✅ PASS in 251.3s
Verification: verify:integrationResult: ✅ PASS |
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/tokens/README.md`:
- Around line 19-21: Add a language identifier to the fenced code block
containing the command "pnpm --filter `@wafflebase/tokens` build" by changing the
opening fence to include the language (e.g., bash) so Markdownlint MD040 is
satisfied; locate the fenced block in README.md near the shown command and
update the opening triple-backtick to include the chosen language tag.
In `@packages/tokens/src/contrast.ts`:
- Around line 3-10: hexToRgb currently accepts malformed hex strings and yields
NaN channels; validate and normalize input in hexToRgb before parsing: require a
match to /^#?([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/, expand 3-character shorthand to
6 chars, then parse the six hex digits and throw a clear TypeError if the string
doesn't match or any parseInt results in NaN; apply the same
validation/normalization pattern to any other hex-to-RGB conversion used by
contrastRatio so invalid inputs consistently throw instead of producing bad
numeric results.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 4d81b9d0-4221-4db0-a4a3-c82980d00af3
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (41)
docs/design/README.mddocs/design/design-system-unification.mddocs/tasks/README.mddocs/tasks/archive/2026/05/20260524-tokens-package-lessons.mddocs/tasks/archive/2026/05/20260524-tokens-package-todo.mddocs/tasks/archive/README.mdknip.jsonpackage.jsonpackages/backend/package.jsonpackages/docs/package.jsonpackages/docs/src/view/theme.tspackages/frontend/package.jsonpackages/frontend/src/app/slides/themed-color-picker-helpers.tspackages/frontend/src/app/slides/themed-color-picker.tsxpackages/frontend/src/components/color-picker-grid.tsxpackages/frontend/src/components/formatting-colors.tspackages/frontend/src/index.csspackages/sheets/package.jsonpackages/sheets/src/view/theme.tspackages/slides/package.jsonpackages/slides/src/import/pptx/theme.tspackages/slides/src/themes/default-dark.tspackages/slides/src/themes/default-light.tspackages/slides/src/themes/font-stack.tspackages/slides/test/import/pptx/theme.test.tspackages/tokens/README.mdpackages/tokens/package.jsonpackages/tokens/scripts/build-css.tspackages/tokens/src/contrast.tspackages/tokens/src/index.tspackages/tokens/src/palette.tspackages/tokens/src/radius.tspackages/tokens/src/semantic.tspackages/tokens/src/typography.tspackages/tokens/test/build-css.test.tspackages/tokens/test/contrast.test.tspackages/tokens/test/palette.test.tspackages/tokens/test/semantic.test.tspackages/tokens/tsconfig.build.jsonpackages/tokens/tsconfig.jsonscripts/verify-self.mjs
Captures a designer-led plan to consolidate fragmented design surfaces: shared tokens package, toolbar/popover unification, mobile parity for Docs and Sheets, icon and type-scale tokenization. Decomposes the work into nine sequenced PRs with explicit dependencies, risks, and a Status table maintained as PRs land. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Locks in the 9-task TDD plan that delivers PR #1 of the design-system unification roadmap: scaffold the tokens package, define palette / semantic / radius / typography, generate tokens.css, add WCAG AA contrast tests, then migrate the frontend, sheets, docs, and slides factory-default theme. Paired lessons file captures decisions as the work proceeds. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Introduce an empty workspace package that will hold the shared design tokens. No consumers yet — palette and CSS generation land in the next commits. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Mark internal as private:true and bring tsconfig in line with the docs/slides tsconfigs (allowImportingTsExtensions, DOM.Iterable lib, useDefineForClassFields). Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Authors the Butter & Maple palette, neutral surfaces, RGB tuples, and the full semantic color map (light + dark) lifted from the existing inline tokens in packages/frontend/src/index.css. Vitest covers shape and required keys; no consumer wiring yet. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Adds renderTokensCss() + a CLI entry that writes dist/tokens.css from the TS token sources. Output mirrors the existing :root and .dark blocks in packages/frontend/src/index.css so the frontend can swap to an @import in the next commit without visible diff. Also fixes tsconfig.build.json to override allowImportingTsExtensions so tsc can emit JS (the flag is incompatible with noEmit: false). Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Dependency-free oklch + hex parser; tests assert foreground/background and primary pairs meet AA across both themes. Catches palette regressions before they reach the UI. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
The original test paired butter (#F4C95D) against neutrals.dark.ink (#FBF6EC, cream-on-dark text) which are two light colors and naturally fail WCAG. The intent was to validate the actual UI usage: butter as a header/chip background with dark ink text on top of it. Switching the pair to (light.ink, butter) measures the legibility of the chip text and passes AA_NORMAL comfortably. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Replaces the inline :root and .dark variable blocks with an import from @wafflebase/tokens. The @theme inline mapping and @layer base rules are unchanged. No visual change expected. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Replaces hardcoded Butter & Maple hex values (active cell ring, header chip, selection wash, formula REFERENCE token color) with palette refs. Canvas-only tokens like formulaRange*, peerCursor*, and search highlights stay local — they have no shared analogue. Also adds @wafflebase/tokens to backend Jest moduleNameMapper so the ESM-only tokens package resolves via source when the backend test suite loads sheets transitively. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Caret and default text color in the docs editor now use Butter & Maple neutrals so the canvas chrome stays in tone with the surrounding shell. Page background, ruler chrome, and the Google-blue selection wash stay local — those are intentionally neutral. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
New presentations now ship with the Butter & Maple brand palette as the factory default. The OOXML role mapping (dk1/lt1/accent1..6) and tint/ shade algorithm are unchanged. User-edited per-presentation themes and PPTX imports always win at runtime. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
The factory default consumed by store/memory.ts and model/migrate.ts
when seeding new decks now reads Butter & Maple values from
@wafflebase/tokens. Ids ("default-light", "default-dark") and display
names ("Simple Light", "Simple Dark") stay stable so stored decks keep
resolving. Other built-in themes (streamline, focus, material) are
user-selectable identities and remain untouched.
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Fills in the paired lessons file with measured WCAG contrasts, the factory-default file-discovery surprise, and follow-up signals for PR #2. The roadmap doc now uses fully qualified package paths to keep verify:entropy doc-staleness checks green, and the Status table flips PR #1 to "Ready to merge". Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Marks every plan step complete, then runs tasks:archive + tasks:index so the todo/lessons files land in docs/tasks/archive/2026/05/. The roadmap doc Status table already reflects the PR-ready state. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Two cross-package mismatches surfaced after the initial PR scope:
- Docs text selection used Google blue while sheets cell-range wash
used butter — same UI affordance, different tone. Switched docs
selectionColor to butter at the existing alphas (0.30 light, 0.35
dark) so a user selecting text in docs and selecting a cell range
in sheets sees the same wash.
- Docs pageBackground and rulerContentBackground used plain #ffffff /
#2b2b2b while the brand neutrals carry a warm cream/dark-brown
paper tone (#FFFDF7 / #241D14). Switched both to
palette.neutrals.{light,dark}.paper so the "sheet of paper"
surface is on-brand in both modes.
canvasBackground, rulerMarginBackground, and headerFooterBorderColor
stay neutral gray — they read as workbench chrome, not paper. They
will move into a shared canvas-surfaces semantic layer in a later PR
once a second consumer needs it.
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
The slides color picker stacked a 6-col Theme grid on top of a 5-col Standard grid with different swatch sizes — two grids that didn't align. The 20-color TEXT_COLORS / BG_COLORS palettes also mixed greyscale and Material accents without an obvious structure. Unifies the layout across every consumer (slides themed picker, docs formatting toolbar, sheets conditional format, docs table context menu) by changing ColorPickerGrid to 6 cols and 24px swatches. The slides Standard row matches inline. Reorganizes both TEXT_COLORS and BG_COLORS to 18 swatches arranged as 6×3: Row 1 Warm neutrals (ink → paper / paper → warm tan) Row 2 Butter & Maple brand accents / brand-tinted highlights Row 3 Cool counterpoint / cool pastels Row 2 sources Butter & Maple values directly from @wafflebase/tokens so the Standard swatches share their warm hues with the Theme row in the slides picker. The cool row keeps users' full color expression even though Butter & Maple has no cool accents. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Two changes in service of a more professional office-tool feel.
Chrome neutralization
- Sidebar surface, foreground, and border in @wafflebase/tokens
semantic.ts no longer reference palette.neutrals.{light,dark}
(warm Butter & Maple cream / warm dark). Swapped to the shadcn
neutral-near-white / cool-dark defaults so the editor chrome
reads as quiet workbench surface, not branded backdrop.
- Docs pageBackground and rulerContentBackground reverted from
the warm paper neutrals to pure white / neutral dark for the
same reason. Brand presence is preserved on selection wash
(butter), caret/text color (ink), primary buttons, focus ring,
and the sidebar active-item.
- palette.neutrals stays warm — it is still the right source for
Slides factory-default themes and any marketing/landing surface.
Color picker densification
- Picker grids unified to 8 cols × 4 rows = 32 swatches at 16×16
px (was 6×3 = 18 at 24×24). Closer to PowerPoint / Figma
density and visually subordinate to the editor content.
- Slides theme row stays 6 cols (12 OOXML roles) but matches the
16 px swatch size of the standard row, so the two grids share
rhythm.
- TEXT_COLORS and BG_COLORS reorganized into four semantic rows:
neutrals → brand warm → cool counterpoint → earthy / status
pastels.
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Theme row was 6 cols × 12 OOXML slots, standard was 8 cols × 32 colors — two different grids stacked under one picker, awkward. Introduces PICKER_THEME_ROLES (8 high-traffic OOXML roles: text, background, accent1..6) and renders the theme row in 8 cols × 1 row. The dropped slots (textSecondary, backgroundAlt, hyperlink, visitedHyperlink) still exist in THEME_ROLES and resolve at runtime — they just don't appear in the picker, since users almost never bind a fill or text color to them explicitly. Both grids now share rhythm. Swatch size also nudged from 16 px (too dense for click affordance) to 20 px so buttons feel right — matches the Google Slides picker size. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Three follow-ups raised by the branch-level code review: [Blocking] Tokens dist is gitignored, but pnpm build / build:all / verify:fast / verify:self all assumed it would exist before sheets/ docs/slides/frontend ran. On a fresh checkout the first downstream build failed with TS2307. Prepended `pnpm tokens build` to the three root scripts and added it as the first lane in scripts/verify-self.mjs so every entry point self-bootstraps. [Important] firstFamily(stack) was duplicated across the two built-in slides themes and the PPTX fallback. Extracted to packages/slides/src/themes/font-stack.ts and all three call sites now import from there. [Important] Three visual extensions (chrome neutralization, docs paper revert, color picker densification) shipped alongside the original plan but weren't reflected in the roadmap doc. Added an "Extensions that landed alongside PR #1" subsection so a future reviewer doesn't read the diff as scope creep. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
The rebase auto-resolution overwrote the topological build order introduced in #287 (concurrent docs+sheets, then slides, then concurrent frontend+backend+cli) with the simpler all-concurrent form this branch had branched from. Restore the topological flow and keep `pnpm tokens build` as the first step so tokens compile before any consumer that imports them. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
CI failures
- verify-integration and verify-browser run on fresh runners that
don't share the verify-self artifact cache. Both build sheets/
docs/slides directly before consumers exist, so they hit
"Cannot find module '@wafflebase/tokens'" before reaching any
test. Prepended `pnpm tokens build` to the
"Build workspace packages the CLI imports at runtime" step in
.github/workflows/ci.yml and to scripts/verify-browser-lanes.mjs.
CodeRabbit findings
- packages/tokens/README.md: tag the build snippet as `bash` so
Markdownlint MD040 stops warning.
- packages/tokens/src/contrast.ts: validate the hex input shape
before parseInt. Without the guard, `#fff` or other malformed
inputs produce NaN channels and contrastRatio quietly returns
a meaningless number. Added a test for the new error path.
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
verify-integration failed on the rebased push: backend e2e (Jest)
loads slides/dist/node.cjs, which contains a baked-in
require('@wafflebase/tokens') from when slides was built. Jest's
moduleNameMapper only rewrites direct imports it parses — it can't
reach require() calls inside an already-compiled CJS dependency. So
the require resolved to tokens/dist/index.js (ESM with `export {}`)
and Jest choked with SyntaxError before any e2e suite started.
Add a second tsc pass that emits a CJS build to dist/cjs/, write a
dist/cjs/package.json with `"type": "commonjs"` so Node treats those
.js files as CommonJS even though the parent package is ESM, and
expose them via the `require` condition in the exports map. The ESM
build stays the default for Vite/Vitest consumers; CJS consumers
(backend Jest, anything loading slides/dist/node.cjs) now find a
matching format.
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
6ddd13e to
e4b6498
Compare
verify-browser failed with 68 baseline mismatches on the previous
push — all driven by the intentional visual changes in this PR:
- Sidebar surface/border switched from warm cream
(palette.neutrals.light.bg / .rule) to shadcn neutral oklch.
- Docs page background reverted from warm paper to pure white.
- Color picker grids unified to 8 cols × 20 px swatches.
- TEXT_COLORS / BG_COLORS reorganized into 4 intent-based rows
of 8 (was 5 cols × 4 rows of 5 of hardcoded Material values).
- Slides default-light / default-dark / PPTX fallback themes now
use Butter & Maple palette instead of Google Material accents.
The new baselines come straight from the verify-browser actual.png
artifact on run 26365350558, so what's committed is exactly what
the Docker Playwright runner now renders.
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
The Butter & Maple palette lived in four places: the frontend @theme block in index.css, plus separate theme.ts files in sheets, docs, and slides. Each kept its own copy of the same colors and drifted independently — light/dark toggles produced visibly different tones across the editor chrome and the canvas surfaces. Land @wafflebase/tokens as the single source of truth for palette, semantic colors, radius, and typography. TS is authoritative; a build script emits dist/tokens.css for the frontend @import and a parallel CJS build for compiled consumers (slides/dist/node.cjs in backend Jest cannot interop with ESM, so the exports map publishes both formats). Migrate the four token surfaces: frontend chrome, Sheets canvas (active cell, header chip, selection wash, REFERENCE token color), Docs canvas (caret/text neutrals), and Slides factory defaults (default-light, default-dark, PPTX fallback). Canvas-only tokens like peer cursors, formula range colors, and search highlights stay local — they have no shared analogue. Slides per-presentation themes are untouched at runtime; tokens act only as the factory default. Three visual extensions shipped alongside the plumbing because they would have been awkward to defer once chrome was already migrating: - Chrome neutralization. Sidebar surface/border/foreground in semantic.ts no longer reference palette.neutrals.* (warm cream / warm dark). Editor chrome reads as quiet workbench surface; brand still appears on primary, ring, accent, selection wash, and the active sidebar item. - Docs paper stays pure white. pageBackground and rulerContentBackground reverted to #ffffff / neutral dark. Caret/text use palette.neutrals.ink; selection wash uses butter. - Color picker densification. ColorPickerGrid and the slides ThemedColorPicker unified to 8 cols × 20 px swatches. TEXT_COLORS / BG_COLORS reorganized from 20 hardcoded Material values into 32 token-aware swatches in 4 intent-based rows: neutrals, brand warm, cool counterpoint, earthy/status. Slides theme row uses PICKER_THEME_ROLES (the 8 high-traffic OOXML slots) so it shares a grid with the standard row. Other notable bits: WCAG AA contrast smoke tests in the tokens package (16 vitest, 4.11:1 weakest pair through 19:1); roadmap doc docs/design/design-system-unification.md sequences this work as PR #1 of 9; build order tightened in scripts/verify-self.mjs + ci.yml + verify-browser-lanes.mjs so tokens build before any downstream consumer on fresh runners; 67 visual baselines refreshed from the verify-browser actual.png artifact to match the intentional chrome / picker / theme changes. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Both tasks' work has landed on main: the Vitest migration merged as #291, and the @wafflebase/tokens deploy fixes (publish-ghpage.yml build:all + Dockerfile tokens build) are present on main, absorbed into #292. Their only open todo items were trailing 'open PR / code review' process steps, now ticked, so pnpm tasks:archive moves both to archive/2026/05 and regenerates the index. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Summary
First PR of the design-system unification roadmap (
docs/design/design-system-unification.md).@wafflebase/tokensas the single source of truth for the Butter & Maple palette, semantic colors, radius, and typography. TS is authoritative; a smallscripts/build-css.tsemitsdist/tokens.cssfor frontend@import.index.css, Sheets canvas theme, Docs canvas theme, Slides factory-default themes (default-light/default-dark) and the PPTX fallback. Canvas-only tokens (peer cursors, formula ranges, page chrome) stay local. Slides per-presentation themes are untouched at runtime —palette.neutrals.*is the factory default only.semantic.tsno longer referencepalette.neutrals.*(warm cream / warm dark). They now use shadcn neutral oklch so the editor reads as quiet workbench. Brand stays on primary, ring, accent, selection wash, and the active sidebar item.palette.neutrals.ink; text selection wash uses butter.TEXT_COLORS/BG_COLORSreorganized from 20 hardcoded Material values into 32 token-aware swatches (4 intent-based rows × 8 cols × 20 px). The slides Theme row now usesPICKER_THEME_ROLES(8 main OOXML slots) so it shares its grid with the Standard row.pnpm build,pnpm build:all,pnpm verify:fast,scripts/verify-self.mjs) so consumers never face a missingdist/.Test plan
pnpm verify:fast(clean state —rm -rf packages/*/distfirst)pnpm verify:self(all 10 lanes green from clean state)Non-blocking limitations
primarycontrast is 4.11:1 — clears AA-Large for chrome chips and buttons, but body text on a primary surface would need adjustment. Tracked in the lessons file for a future PR.firstFamily(stack)extracted topackages/slides/src/themes/font-stack.tsand shared across the two built-in themes plus the PPTX fallback — addresses the duplication review finding.🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Documentation
Chores