monorepo-color-generator-safety
Section titled “monorepo-color-generator-safety”Background
Section titled “Background”Spun out of ai-config-design-ssot on 2026-08-29, where a routine regeneration was run, diffed, and discarded before committing. pnpm colors is currently unsafe to run, for two independent reasons.
1. Generated theme files were hand-edited, and regenerating reverts the edits.
sites/template/src/styles/themes/light.css and dark.css carry “DO NOT EDIT MANUALLY — auto-generated” headers, yet contain hand-applied WCAG AA contrast fixes with explanatory comments. Regenerating silently reverts every one:
| Variable | Hand-edited value | Reverts to |
|---|---|---|
--muted-foreground (light) | 28 8% 32% “Darkened from 44% for WCAG AA 4.5:1” | 28 8% 44% |
--muted-foreground (dark) | 28 7% 63% “Lightened from 36% for WCAG AA 4.5:1” | 28 7% 36% |
--destructive (light) | 0 84.2% 40% “Darkened from 60.2% for WCAG AA” | 0 84.2% 60.2% |
--destructive-foreground | 0 0% 100% “Pure white for maximum contrast” | 210 40% 98% |
--primary (light) | 215 69% 32% “Darkened from 45% for WCAG AA” | 215 69% 45% |
Anyone running pnpm colors breaks accessibility compliance with no error and no obvious diff signal. This is the AGENTS.md “deploy-from-source configs — diff before you deploy” pattern: the generated copy drifted ahead of its generator.
2. sites/template was last generated with the ts brand. Its themes carry Professional Blue (215 69% …). Running pnpm colors:sd — the obvious command when working on SmartDebt — silently rebrands the entire template site to green. Nothing records which brand a site’s themes were last built from.
3. Minor, but it makes every diff noisy: the generator emits single-quoted attribute selectors ([data-theme='dark']) while prettier rewrites the committed files to double quotes, so every regeneration produces churn unrelated to any colour change.
- Make the WCAG fixes survive regeneration. Preferred: move them into the generator so they are derived, not patched — the accessibility intent (contrast floor against the theme background) is expressible as a rule. Fallback: a documented post-generation overrides layer the generator emits but never owns. Do not simply re-apply the patches by hand.
- Record the source brand per site so a regeneration cannot silently rebrand. E.g. a
brandfield in each site’ssite.config.json, withpnpm colorsdefaulting to it and refusing a mismatch unless--force. - Fix the quote-style churn — emit double quotes, or run the generator’s output through prettier.
- Consider letting the generator target
apps/*. ItsSITE_ROOTresolves tosites/<name>, so apps cannot receive generated theme layers at all (documented indocs/design/apps.md). This is whyapps/sd-apphand-declares its neutral/surface variables. - Once safe: regenerate to apply the
"3": ["dark:primary"]mapping already committed insrc/brand/sd/colors.oklch.json, and verify no WCAG value moved.
Success Criteria
Section titled “Success Criteria”pnpm colors(andcolors:sd/colors:ts) can be run on a clean tree and the resulting diff contains only intended colour changes — no reverted WCAG values, no brand switch, no quote churn.- Every contrast pair that was hand-fixed still meets WCAG AA after a regeneration, checked by computed ratio rather than by eye.
- A site’s brand is recorded in the repo, and a mismatched regeneration is refused rather than silently applied.
- WCAG AA — Web Content Accessibility Guidelines, level AA: 4.5:1 contrast for normal text.
- The generator is
sites/template/scripts/generate-color-system.mjs; the palette algorithm ispackages/design-tokens/src/oklch-palette.mjs. - Design authority context: Design (boundary rule),
monorepo/docs/design/(implementation SSOT).