Skip to content

refactor(web): move styles into per-component CSS modules - #211

Merged
etodanik merged 2 commits into
mainfrom
danny/css-refactor
Nov 6, 2026
Merged

etodanik merged 2 commits into
mainfrom
danny/css-refactor

Conversation

@etodanik

@etodanik etodanik commented Nov 6, 2026 •

Copy link
Copy Markdown
Collaborator

CSS styling refactor, part 1 of 2

Why

Humans and agents should be able to move fast an reason about visual components and behaviors easily. In my attempt to create a simple storybook based flow for that, I encountered a big spaghetti of intertwined styles, styles that depend on a component being in a specific different component and a general lack of encapsulation.

We drifted this way because we had inertia from how styles were handled in react-ui, which is supposed to introduce far less styling opinion because it could be used by outside organizations for their AI user interfaces, and we don't want to dictate how they should reason about styles.

However, Corbits Builder is a much more complex application, and isn't a library, so the same constraints don't apply.

This PR makes the first crucial step to a first iteration workflow on visuals:
Encapsulating CSS for components and preventing cross-talk by using CSS modules, and creating a space for rapid visual iteration within Storybook.

The next PR will focus on extending what is true for components in this PR to entire pages by decoupling state, networking and logic from visuals - extending the ability to rapidly iterate on visuals without them being intertwined with logic, state and networking.

Screenshot 2026-11-07 at 01 29 07

What changed

  • CSS modules. Each component's styles live in a *.module.css beside it. styles.css keeps only what is global: fonts, tokens, element defaults, library-wide overrides and shared keyframes. Dead global rules are removed.
  • Exact class types. typed-css-modules generates a gitignored .d.ts per module, so styles.x is a string and a misspelt class fails the typecheck. typecheck regenerates them first, and dev keeps them current with a watcher. clsx joins conditional classes.
  • Shared components instead of shared classes. Banner/BannerAction, InlineNote, Fault, Field, QuietState, QuietButton/QuietSelect, DangerMenuItem, MenuItemLabel, Composer, StageView, Sketching, SourceLink and the approval fields each own their whole look. They take no className; variants are data-* attributes, and every root carries data-slot, which parents use only to position it.
  • Duplicates merged. WorkspaceFault became Fault, ProviderField became Field, StatusText became QuietState, and the QUIET_CONTROL class became components.
  • Settings moved into pages/settings/.
  • Conventions recorded in apps/web/AGENTS.md, with a test that fails on a plain .css file or an unused module class.
  • Storybook 11 (bun run --cwd apps/web storybook), with stories for components and screens.

Visible changes

  • Collaborators uses the library Section heading (about 7px taller).
  • Approval status words are 13px in the theme's tone colours.
  • Fields in the provider and profile dialogs have an 8px gap instead of 6px.
  • Settings reviewer inputs use the library input.
  • Error lines use the foreground colour.
  • The project-load Retry is the standard button.

How it was verified

  • Computed styles of the home page, the library, all five Settings tabs and the project workspace (four document tabs) were compared against main. They match apart from the changes above and invisible differences (zero-width borders, padding swapped for margin over the same box).
  • bun run typecheck, lint, format:check, pins:verify, vendor:verify and bun test ./apps/web/src (136 pass).
  • Not checked in the browser: dialogs, approval rounds, error states and main's new folder-import banner, because local data had none to show. Tests cover these.

Every component's styles now live in a CSS module beside it, with exact
types generated by typed-css-modules, so a misspelt class fails the
typecheck. A look used in more than one place is a shared component that
owns its whole look and takes variants as data-* attributes, rather than
a class several components share. styles.css keeps only what is global.

Settings moves into pages/settings/, and Storybook is set up with stories
for the components and screens.

@corbits-triage corbits-triage Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Triage requests changes:

  • CI green: required checks are failing
    • e2e
  • Conventions: added code matches a code pattern
    • apps/web/src/App.tsx:874-907 nested-ternary: Nested ternary; use if statements or a lookup.
    • apps/web/src/App.tsx:875-906 nested-ternary: Nested ternary; use if statements or a lookup.
    • apps/web/src/pages/project-library.tsx:176 empty-list-fallback: Invented fallback `?? []`; handle the missing value instead.
    • apps/web/src/pages/settings/settings-row.tsx:17-23 nested-ternary: Nested ternary; use if statements or a lookup.

A root bunfig preload that registered a Bun plugin stopped the hub's
macros from running in the shared test process. Tests find elements by
data-* attributes, so they no longer need class names in markup. Also
replace nested ternaries and an invented empty-list fallback that the
move touched.
@corbits-triage

corbits-triage Bot commented Nov 6, 2026 •

Copy link
Copy Markdown

Merged before Triage finished on 78e1ff8.

@etodanik
etodanik merged commit fce3015 into main Nov 6, 2026
9 checks passed
@etodanik
etodanik deleted the danny/css-refactor branch November 6, 2026 16:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant