Frontend CSS governance

中文

Maka's frontend styling combines Tailwind v4 with handwritten renderer CSS. Some renderer surfaces still override shared @maka/ui primitives, so cascade order is an explicit contract rather than an implementation detail.

1. Entry file

  • apps/desktop/src/renderer/styles.css is an entry file only.
  • It may contain @import, @source, @theme, and other top-level orchestration statements.
  • New per-surface selector blocks belong in apps/desktop/src/renderer/styles/**/*.css.
  • Historical recipes at the end of maka-tokens.css and reference-shell.css are transitional exceptions. Do not add new surface rules to them.

Selector naming

  • Shared renderer and @maka/ui selectors use the kebab-case .maka-* dialect.
  • The established styles/settings/** surface uses camelCase .settings* selectors. Keep that dialect for settings-local selectors instead of mixing both forms within one surface.
  • Moving existing settings selectors between concern files does not require a repository-wide rename; any future naming migration should be handled as an explicit compatibility change.

2. Layers

  • Pure presentation rules that do not override shared primitives or Tailwind utilities should use @layer base or @layer components where practical.
  • Use @import "./file.css" layer(components) only when the build chain explicitly supports it.
  • Do not place @import inside an @layer block.
  • A selector that must override a shared primitive's Tailwind utility must remain outside @layer components until the primitive seam is fixed.

3. Required unlayered rules

These selectors currently depend on appearing after Tailwind utilities and must remain unlayered:

  • .maka-nav-row
  • html[data-os="darwin"] .maka-nav-row
  • .settingsHealthRefresh
  • .settingsPermissionRefresh
  • .settingsBotList button

The guard lives in apps/desktop/src/main/__tests__/renderer-style-layer-cascade-contract.test.ts. Update that contract in the same PR if the underlying primitive changes.

4. !important

  • !important is allowed by default only for accessibility helpers such as .maka-visually-hidden, and for reduced-motion or e2e-fixture overrides.
  • Every other use requires an adjacent Justified: comment and an entry in renderer-important-audit-contract.test.ts.
  • Prefer a JSX utility-class reset when the primitive can express the behavior directly.

5. Tokens

  • Shared custom properties belong in apps/desktop/src/renderer/maka-tokens.css.
  • Component-local properties are allowed only with a /* local: ... */ comment.
  • Do not add raw colors, radii, or ungoverned z-index values.

6. Dead CSS

  • scripts/check-dead-css.mjs scans apps/desktop/src/renderer/styles/**/*.css and apps/desktop/src/renderer/reference-shell.css.
  • Runtime-generated class names that static search cannot find must be explicitly allowlisted.
  • Change scripts/check-dead-css-baseline.json only after review confirms the class-count change.

7. Contract tests

  • Tests that inspect effective renderer CSS must read it through css-test-helpers.ts or contract-css-helpers.ts.
  • Do not inspect only styles.css when the assertion concerns effective rules.
  • Directly inspect styles.css only to enforce its entry-file contract.

8. Change order

When changing renderer CSS:

  1. Point contract tests at the effective CSS source.
  2. Move real rule blocks out of styles.css into surface files.
  3. Layer only rules that do not override shared utilities.
  4. Remove dead selectors.
  5. Remove remaining !important only after primitive and layer ownership is stable.

9. Governing principles

  • Make CI guards trustworthy before structural convergence.
  • Delete dead CSS before aesthetic refactoring.
  • Resolve shared Button, Textarea, and EmptyState overrides at the component API seam instead of accumulating renderer specificity.
  • Every change to Tailwind cascade order requires a contract test and the narrowest relevant regression check.