Designer tokens, a real dark palette, and readable contrast text - #98
Open
timurbazhirov wants to merge 2 commits into
Open
Designer tokens, a real dark palette, and readable contrast text#98timurbazhirov wants to merge 2 commits into
timurbazhirov wants to merge 2 commits into
Conversation
Three things, all measured rather than eyeballed — `src/theme/palette/contrast.ts` is a thirty-line WCAG implementation, and `tests/designerTokens.tests.ts` holds every value below to a threshold so a future edit that makes a stripe invisible fails the build. **The dark palette was missing most of itself.** `paletteDark` spread only the primary, secondary and semantic colours, so keys this package *declares* on the palette — border, icon, unitTypes — were absent in dark mode. `palette.border.dark` is dereferenced unguarded by TextEditor, TotalWidget and InfoWidget, so those threw; EntityName already carried a `?.` and a hardcoded "#ccc", which was the workaround. Demonstrated before and after: the same expression throws on the old palette and yields `#3D3D47` on the new one. Dark now has its own surfaces, borders, icons, text and action colours. **contrastText was unreadable on three of the four semantic colours.** An error button's label was 23%-opacity black on red, scoring 1.49 where AA wants 4.5; info was 1.54 and success 1.68. Each is now whichever of black or white actually reads (4.98–12.52). A new `successText` covers the case neither `main` (1.68 on white) nor `dark` (2.18) can: the colour used as text rather than as a fill. **`theme.designer.*` is new** — unit-type accents for all nine types with their own label colours, canvas background/grid/wire/selection, modified/dirty/draft state, and node background/border/shadow/minWidth, in light and dark. The existing `palette.unitTypes` is derived from the same source so the two cannot drift; its `assignment` and `condition` values change, because `#ff9800` and `#00BFA5` scored 2.15 and 2.14 against the white surface their card stripe sits on, below the 3:1 that non-text UI needs. The brand hue is deliberately untouched: SOF-8024 portion 2 gates the canonical palette on design/marketing sign-off, and swapping it later is an edit to one file. Also wires the unit tests into `npm test`. `tests/schemaUtils.tests.ts` has been in the tree but never executed, since `test` ran only lint and transpile; it passes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FFFpkKD3zHysJXgAbT2uYV
…s-and-dark-palette # Conflicts: # dist/theme/palette/index.d.ts # dist/theme/palette/index.js # dist/theme/theme.js # package.json
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The theme definition half of SOF-8024 portion 2. Everything below is measured, not eyeballed:
src/theme/palette/contrast.tsis a thirty-line WCAG implementation, andtests/designerTokens.tests.tsholds every value to a threshold so a future edit that makes a stripe invisible fails the build instead of shipping.1. The dark palette was missing most of itself
paletteDarkspread only the primary, secondary and semantic colours. Keys this package declares on the palette inmui.d.ts—border, and in practiceiconandunitTypes— were simply absent in dark mode.palette.border.darkis dereferenced unguarded in three styled components:other/text-editor/TextEditor.styled.ts"1px solid " + theme.palette.border.darkmui/components/custom/widgets/total-widget/TotalWidget.styled.ts1px solid ${theme.palette.border.dark}mui/components/custom/widgets/info-widget/InfoWidget.styled.ts1px solid ${theme.palette.border.dark}So they threw in dark mode.
EntityName.tsxalready carriestheme.palette.border?.dark || "#ccc", which is the workaround this makes unnecessary. Demonstrated against the old and new palettes:Dark now has its own surfaces, borders, icons, text and action colours, and a test asserts it carries every key the light palette declares.
2.
contrastTextwas unreadable on three of the four semantic colourscontrastTextis what MUI paints onmain— a Button's label, a filled Chip's text. Measured against their ownmain:error.contrastTextrgba(0, 0, 0, 0.23)#FFFFFFinfo.contrastTextrgba(0, 0, 0, 0.23)#000000success.contrastText#FFFFFF#000000warning.contrastText#FFFFFF#000000An error button's label was 23%-opacity black on red. The translucency is why this went unnoticed by eye-checking the hex: scored as opaque black it looks fine, and the helper composites alpha before measuring for exactly that reason.
New
successTextcovers the case neithersuccess.main(1.68 on white) norsuccess.dark(2.18) can — the colour used as text rather than as a fill.3.
theme.designer.*Additive namespace for what stock MUI has no vocabulary for, in light and dark:
unitType— all nine types (execution, subworkflow, map, assignment, condition, assertion, io, processing, error), each with its own readable label colour;canvas— background, grid, wire, selection, insertAffordance;state— modified, dirty, draft;node— background, border, shadow, andminWidth(the readable floor an auto-fit should clamp to).palette.unitTypesis now derived from the same source so the two cannot drift. Two of its values change:assignment#ff9800→#B35C00andcondition#00BFA5→#00786A. Those scored 2.15 and 2.14 against the white surface their card stripe sits on (wove'sUnitCard.styled.tsx:before, and the dropdown swatch), below the 3:1 WCAG asks of non-text UI — which is why an assignment stripe was hard to pick out.What this deliberately does not do
The brand hue is untouched. SOF-8024 portion 2 §2 gates the canonical palette on design/marketing sign-off — the platform says purple
#5b37c0/#7c5fcd, the marketing site leans dark-navy and green. Swapping it later is an edit topalette/index.ts; nothing indesigner.tsencodes the brand.Tests
Also wires unit tests into
npm test.tests/schemaUtils.tests.tshas been in the tree but never executed —testran onlylint && transpile. It passes;test:unitnow runs both files.29 tests, covering: WCAG scoring against known extremes, alpha compositing, every semantic
contrastText, dark-palette completeness, every unit-type accent against the node it marks and against its own label, pairwise distinguishability of accents, state colours, canvas furniture (the wire must read, the grid must not), and the derivedpalette.unitTypesmatching its source.Two of these caught real mistakes in my own first draft — a white label on
#0288D1at 3.86, and a canvas wire at 2.60 — which is the point of writing them.Generated by Claude Code