Editor theming standard
Opal's editor theme is a small contract, not a collection of component palettes:
palette seeds → semantic --ui-* roles → component aliases → CSS or canvas painters
The canonical files are:
styles/theme/editor-theme.foundation.css— spacing, type, radii, motion, z-indexes, and the user-customizable palette seeds.styles/theme/editor-theme.roles.css— the public color API. Dark and light declare the same names.styles/theme/editor-theme.*.css— older subsystem token maps. These may alias roles while the editor is migrated.src/game/editor-theme-palette.js— cached access to roles for canvas-drawn editor chrome.src/game/editor-theme-events.js— invalidation contract for theme mode and palette changes.
Rules
- Dark is the default map. Light mode is a complete alternative map selected by
:root[data-theme="light"]; never make light colors the unqualified default. - Component styles consume
--ui-*roles. They do not read--editor-h-*,--editor-s-bg, or lightness seeds and do not author raw hex, RGB, or HSL colors. A component may create a local alias such as--script-border: var(--ui-line)when that makes its hierarchy clearer. - Declare every
--ui-*role ineditor-theme.roles.css, once in each mode. Do not redeclare a public role in a component or subsystem sheet. - Name roles by purpose, not appearance. Use
--ui-dangerrather than--ui-red; use--ui-bg-raisedrather than--ui-lighter-panel. - Interaction and status colors use the shared state roles:
--ui-bg-hover,--ui-bg-selected,--ui-accent[-soft],--ui-danger[-soft],--ui-warning[-soft],--ui-success[-soft],--ui-info[-soft], and--ui-focus. Stable feature identities use the matching--ui-category-*role rather than borrowing a warning or syntax color because it happens to have the desired hue. - Canvas painters read a semantic role directly with
readThemeToken. This keeps their dependency explicit and lets the Node harness inject the public role without emulating the browser's full custom-property cascade. Build the painter's palette throughcreateThemePaletteso reads are cached and invalidated byopal-editor-theme-change. - Runtime/game colors are outside the editor theme. Art authored by a game, sprite colors, particle colors, and player-only backdrops stay explicit.
Adding or changing a role
Add the name to both the light and dark blocks in editor-theme.roles.css, derive each value from the palette seeds, and then consume it from the component. src/game/editor-theme-roles.test.js rejects one-sided roles, unknown references, declarations outside the role map, and raw color recipes in surfaces that have completed migration.
For a newly migrated canvas painter, verify all three paths: dark mode, light mode, and a live palette edit. For a DOM surface, verify dark and light plus keyboard focus, hover, selected, disabled, warning, and error states.
Incremental migration
The subsystem token sheets are transitional: many existing selectors still consume names such as --panel-card-bg or --stage-graph-bg. Move the token's recipe into a semantic role, turn the old name into an alias, then migrate call sites. Once a component has no direct seed or literal color reads, add it to the conformance test so it cannot regress. Script Studio's IDE shell, the complete compact top/bottom chrome, inspector tabs/menus, the scene hierarchy, Help/About, the project launcher, the stage interaction chrome, and the Sound Canvas with its Library and Inspector docks are guarded surfaces.