Skip to content

Put styles in cascade layers, remove unused rules, and load screens on first use (app review batch 6c) - #35

Merged
EmotiveImpact merged 11 commits into
mainfrom
claude/app-review-styles
Oct 12, 2026
Merged

EmotiveImpact merged 11 commits into
mainfrom
claude/app-review-styles

Conversation

@EmotiveImpact

@EmotiveImpact EmotiveImpact commented Oct 9, 2026 •

Copy link
Copy Markdown
Owner

Before: the shared stylesheets were imported after every screen's own, so which rule won a tie depended on the order the files reached the page, and screen styles leaned on longer selectors to win. 438 of the app's 1,752 style rules matched nothing it draws: about 2,600 lines, most of them in app.css (6,224 lines), from earlier Home, Hub and settings layouts, the sign-in gate, sample navigation and the Choose menu that #33 deleted. Seventy colours were written out by value where a design token holds the same colour. The sign-in page downloaded every screen: 1,156 kB of files (348 kB compressed), 563 kB of them the app's own code. On the connection and error cards, the icon, the explanation and the retry message asked for a colour token that does not exist, so they showed in the heading's dark ink.

After: every stylesheet sits in a cascade layer declared once, so a screen's stylesheet can arrive at any moment without changing which rule wins. The unused rules are gone: app.css is 3,674 lines and all the styles together 6,524, down from 9,174. Those seventy colours name their tokens, and a test keeps the fixed palette written that way. The sign-in page downloads no screen code: 817 kB (264 kB compressed), 24% less. The signed-in app is fetched while the session is checked; its less used screens and windows load when first opened, or once the browser is idle. Every screen looks exactly as it does on #34, apart from the recovery cards' secondary text, which is now the soft grey it asked for.

This is app review batch 6c, built on #34 (dates and speed), so review that first; the diff here shows only this part. GitHub's diff of app.css reads like a rewrite: git diff --diff-algorithm=histogram shows the real change, 33 lines changed and 2,583 deleted.

What changed, by finding

Finding Change
61, order apps/web/src/styles/index.css declares @layer base, screens, overrides; and imports the shared stylesheets in their old order: base holds calendar-date, shared, app, auth and legal; overrides holds the dark theme and the design-language primitives; the font faces stay outside any layer. main.tsx imports it before anything else, and each screen's and window's stylesheet wraps itself in @layer screens {}. Where the layers would have changed which rule wins, the old winner was kept: screen rules that must beat a dialog or dark-theme rule moved into an @layer overrides {} block in their own file, and rules the phone layout used to beat no longer match inside it.
61, unused rules 438 whole rules, 26 selectors from lists and 3 arguments of one :is() are removed, with the @media blocks they left empty. Each was proved unmatched: every string and template literal in apps/web/src counts as evidence, and where a template builds a class from a value, the TypeScript checker's type for that value (a tone, a status) adds every class it can make. A coverage run confirmed none of them matched on any captured state.
61, colours Seventy colour literals now name the token that holds exactly that colour wherever the rule applies, so every computed colour is unchanged. stylesheets.test.ts checks the layers and keeps the fixed palette written as tokens. calendar-date.css keeps its literals, because the HTML design reference links it without the app's tokens.
61, .is-native Kept. The finding said nothing sets it, but ?native=1 does: it previews the phone layout at any width (nav.ts, state.tsx), as apps/web/GOAL.md documents and a test covers.
65, code splitting App lazy-loads the signed-in app (shell/ProductShell.tsx, moved out of App.tsx unchanged), which brings Home, Hub, Expenses and Settle, the phone's four tabs, behind the existing loading page. Funds, Recurring, Templates, Members, Notes, Operations, Activity, Search and the settings load when first opened, behind the page's own skeleton, and the schedule and announcement windows behind the window's record skeleton. Once the browser is idle the rest loads anyway, unless it asks to save data (shell/lazyComponent.tsx). A failed code load reloads the page once; a second failure within 30 seconds reaches the recovery card instead.
Recovery cards connection-recovery.css asked for var(--soft), which nothing defines; it now uses --ink-soft (#60706b on the white card, 5.2:1), like the app's other secondary text. This bug predates the review, and is the one visible change here.

Releasing

Nothing hosted was touched and there is no migration. Every browser Vite 7 builds for (Safari 16, Chrome 107, Firefox 104 and later) supports cascade layers. A tab left open across a release may ask for a screen's file that the new release no longer has; it reloads once onto the new release instead of failing.

Needs the owner

  • The ?native=1 phone preview is kept. Removing it is a product call.
  • The app's code starts downloading while the session is checked, so a signed-out visit to / fetches the signed-in app (154 kB of code and 35 kB of styles, 50 kB compressed) before landing on sign-in; /login itself fetches none of it. Waiting for a confirmed session instead would slow the start for everyone signed in. Say if you would rather wait.
  • 367 colour literals hold colours no token has, or that a token only holds in one theme. Writing those as tokens needs new tokens, a design-system decision.

Left as they are

  • .a-body:not(.is-gate) and two unused :is() arguments in overrides.css: nothing sets .is-gate any more, but removing them would lower specificity and change which rule wins.
  • Notes.css uses var(--card, #fffdfa). --card is defined nowhere, so the fallback always applies; the colour is right.
  • The sign-in pages' entry file still carries React, state.tsx and live/model.ts, which they need for the session. Shrinking it further means splitting state.tsx.

How it was checked

On this branch:

  • 656 web tests in 67 files, 14 of them new (the layers and palette, the lazy loader's fallback, preloading and single reload, the sign-in page not loading the app, and each skeleton while code arrives); the web, local API and worker typechecks; and a production build.
  • 28 database tests through run-local.sh (2 more need a Neon branch), then dump-schema.sh --check.
  • 22 local API suites on a disposable database and port, and the reminder worker's 7 unit tests and 4 database suites.
  • The secret scan over the files and every commit.

In Chromium, builds of #34 and of this branch, pointed at the local API, ran against one disposable database with the sample communes, with the browser clock fixed. 97 states were captured: 13 signed out (the connection card among them), 80 signed in across four communes (windows, dialogs and disclosures open, the quick finder, typed filters and full search, More, and nine ?native=1 states) and 4 for an account with no commune, each at desktop 1440×900 and phone 390×844, light and dark: 388 screenshots per build.

  • Pixel by pixel, 382 of 388 screenshots are identical. Four are the connection card, whose grey text is the intended change. The other two differ by 5 pixels and 2 pixels, by 1 of 255, at the rounded corners of the photo cards on Places, pixels that also differ between two captures of the same build.
  • Computed styles of every element and pseudo-element, as drawn and with every hover, focus and active rule forced: 384 of 388 states are identical, and the other 4 are the connection card's colours. The report line's removed class name is set aside in that count; its styles are unchanged.
  • With the browser asking to save data, so each screen's stylesheet arrives only with its code: the same result, 382 identical screenshots, the same 4 cards and 2 corner differences.

What the sign-in page downloads, from production builds (sizes on disk, then compressed):

Page #34 This branch
/login 1,156 kB (348 kB): the app's code 563 kB, the Neon client 376 kB, styles 158 kB, fonts and logo 817 kB (264 kB): the entry 302 kB, the Neon client 376 kB, styles 78 kB, the same fonts and logo
/ while signed out (ends on sign-in) the same 1,156 kB (348 kB) 1,006 kB (314 kB): adds the signed-in app's 154 kB of code and 35 kB of styles, and none of the less used screens

How

The layer plan came from recording, on 376 captured states, every pair of rules from different files that set the same property on the same element (1,785 of them), then checking which would win under every order the screens' stylesheets can arrive in; after the edits above, none changes winner. That was measured before this branch moved onto #34, which changed no stylesheet, and the comparison above repeats the check on #34. The dead-rule check reads the TypeScript checker's types for every class built from a template. lazyComponent wraps React.lazy: a component that has already loaded draws at once without a fallback, preloadAll() loads every lazy part (tests call it in beforeAll, as the browser does once idle), and a failed import reloads once, remembered for 30 seconds under commune.codeReloadedAt in session storage.

🤖 Generated with Claude Code

https://claude.ai/code/session_01A6P7aTcMBccooYYr4xYnVS

claude added 11 commits October 9, 2026 23:24
Finding 61. Which rule won depended on the order stylesheets reached the
page: the screens' stylesheets came first (imported through App) and the
shared ones after. Lazily loaded screens would bring their stylesheets later
and change that. Now styles/index.css declares the order once, first:

  base       calendar-date, shared, app, auth and legal
  screens    each screen's and window's own stylesheet (wraps itself)
  overrides  the dark theme and the design-language primitives

and main.tsx imports it before anything else, in the same order as before.

Precedence that the layers would have changed was kept rule by rule, found
by recording every cross-file competition on 376 captured states (desktop
and phone, light and dark, signed out and in, windows and dialogs open, the
?native=1 layout) and checking each against every order the screens'
stylesheets can arrive in:
- screen rules that outrank a design-language or dark-theme rule (quick
  search, receipt, correction and period-split dialogs, receipt history,
  dialog footers in a window) move to an overrides block in their file;
- screen rules that lost to the ?native=1 layout no longer match there
  (:where(:not(.is-native *)), no change in specificity), and the expense
  card's small text no longer matches the calendar weekday;
- the discard dialog's width moves next to the shared dialog rule, and the
  dark window footer no longer matches a task's delete question;
- Notes' search field rules name .c-record-filter so they beat
  QuickSearch.css whichever arrives first;
- .a-move .c-report-line's colour, which never won, is removed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A6P7aTcMBccooYYr4xYnVS
…st use

Finding 65 (code splitting). The sign-in pages downloaded every screen. Now
App loads the signed-in app (shell/ProductShell.tsx, moved out of App.tsx
unchanged) only on a signed-in address, starting while the session is
checked, behind the same loading page. It brings Home, Hub, Expenses and
Settle (the phone's four tabs); Funds, Recurring, Templates, Members, Notes,
Operations, Activity, Search and the settings load when first opened, behind
the page's own skeleton, and the schedule and announcement windows behind the
window's record skeleton. Once the browser is idle (and not saving data) the
rest loads anyway, so later screens open at once.

A failed code load (a new release replaced the files, or the connection
dropped) reloads the page once; a second failure within 30 seconds reaches
the recovery card instead of reloading again.

Money.tsx and House.tsx no longer re-export screens, which would have pulled
them into the signed-in app's chunk. Tests that draw the signed-in app load
the lazily loaded code first (preloadAll), as the browser does once idle;
new tests cover the sign-in page not loading the app, each fallback, the
reload and preloading.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A6P7aTcMBccooYYr4xYnVS
Finding 61. 429 rules, 24 selectors from 7 lists and 3 arguments of one
:is() named a class that no string in apps/web/src can produce (2,659 lines,
mostly app.css: earlier Home, Hub and settings layouts, the gate, sample
navigation). Each was proved by search, classes built from strings
included: every string and template literal was collected, and where a
template inserts a value, the TypeScript checker gave the value's type, so
a finite union (a tone, a status, a boolean) added every class it can make.
No template left a class open to review, and calendar-date.css was also
checked against the design reference page that links it.

Kept on purpose:
- the ?native=1 layout (.is-native), which nav.ts and state.tsx still set;
- .a-body:not(.is-gate) and the :is() lists in overrides.css whose dead
  arguments set their specificity: removing them would change which rule
  wins.

Empty @media blocks and the one comment left heading nothing went too.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A6P7aTcMBccooYYr4xYnVS
Seventy colour literals now name the token that already holds that colour
wherever the rule applies, so the computed colours stay exactly as they
were:
- a literal equal to a token defined once, on :root, and never set again
  (navy, sage and their shades, peach, lilac) becomes that token;
- a rule that only applies in the dark theme takes the dark value of the
  token the dark theme sets on .a-body (cards, paper, ink-soft, danger,
  green text);
- fallbacks that never apply, because the token is always defined, are
  removed.

calendar-date.css keeps its literals: the HTML design reference links it on
its own, without the app's tokens. The other literals hold colours no token
has, or colours a token only has in one theme, and stay as they are.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A6P7aTcMBccooYYr4xYnVS
A token defined once, on :root, holds the same colour on every element, so
the stylesheets name it instead of repeating its colour. calendar-date.css
is left out: the HTML design reference links it without the app's tokens.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A6P7aTcMBccooYYr4xYnVS
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A6P7aTcMBccooYYr4xYnVS
The minifier drops a gradient's 0% and 100% stop positions when the stops
are colours it can read, but keeps them beside var(), so the browser's
computed value gained "0%" and "100%" (the same gradient, drawn the same).
Without them the dark canvas and the sign-in brand panels compute exactly
as they did before the tokens.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A6P7aTcMBccooYYr4xYnVS
The Choose menu component was deleted in the tidying batch; its rules in
app.css matched nothing after that. The dead-rule check (every class the
TypeScript checker can see the app use) now finds no unused rules.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A6P7aTcMBccooYYr4xYnVS
The report line's only rule never took effect and went with the cascade
layers; the line keeps the "why" styles it has always shown with.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A6P7aTcMBccooYYr4xYnVS
The connection card and the app's error card share these rules. Their icon,
explanation and retry result asked for var(--soft), a token that has never
existed, so they fell back to the heading's ink. They now use --ink-soft
(#60706b on the white card, 5.2:1), like the app's other secondary text.
This is the one visible change in the styles batch.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A6P7aTcMBccooYYr4xYnVS
HANDOFF.md says where the layers are declared, where a screen rule that
must win goes, what the stylesheet test checks, what each page downloads
and when, the one reload after a failed code load, and what ?native=1 is
for. DESIGN-LANGUAGE.md says where the calendar stylesheet now comes first.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A6P7aTcMBccooYYr4xYnVS
@vercel

vercel Bot commented Oct 9, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
commune-web Error Error Oct 9, 2026 11:50pm UTC

Copy link
Copy Markdown
Owner Author

The three CI jobs here (Web app; Database, local API and worker; Secret scan) failed one to two seconds after queuing, with no runner and no steps, as on #34 an hour earlier. No code ran, so these failures say nothing about this branch, and there is nothing to fix in it. GitHub Actions is not starting jobs for this repository; the likely cause is the account's Actions minutes or spending limit. Re-running needs an owner: this session gets a 403 for re-runs.

Meanwhile, the same commands CI runs pass locally on this head (d8c1ed2): 656 web tests in 67 files, the three typechecks and the production build; 28 database tests and the schema dump check; 22 local API suites; the reminder worker's 7 unit tests and 4 database suites; and both secret scans (files and every commit).


Generated by Claude Code

@EmotiveImpact
EmotiveImpact changed the base branch from claude/app-review-dates-speed to main October 12, 2026 02:01
@EmotiveImpact
EmotiveImpact marked this pull request as ready for review October 12, 2026 02:01
@EmotiveImpact
EmotiveImpact merged commit 9a62c9a into main Oct 12, 2026
3 of 7 checks passed
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 12, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-12T02:02:23.764375Z d8c1ed2 Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

This branch had an error being deployed

1 failed deployment
Preview — d8c1ed2c Deployed Oct 9, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants