Repository navigation
Put styles in cascade layers, remove unused rules, and load screens on first use (app review batch 6c) - #35
Conversation
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
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
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 |
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
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.cssis 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.cssreads like a rewrite:git diff --diff-algorithm=histogramshows the real change, 33 lines changed and 2,583 deleted.What changed, by finding
apps/web/src/styles/index.cssdeclares@layer base, screens, overrides;and imports the shared stylesheets in their old order:baseholds calendar-date, shared, app, auth and legal;overridesholds the dark theme and the design-language primitives; the font faces stay outside any layer.main.tsximports 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.:is()are removed, with the@mediablocks they left empty. Each was proved unmatched: every string and template literal inapps/web/srccounts 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.stylesheets.test.tschecks the layers and keeps the fixed palette written as tokens.calendar-date.csskeeps its literals, because the HTML design reference links it without the app's tokens..is-native?native=1does: it previews the phone layout at any width (nav.ts,state.tsx), asapps/web/GOAL.mddocuments and a test covers.Applazy-loads the signed-in app (shell/ProductShell.tsx, moved out ofApp.tsxunchanged), 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.connection-recovery.cssasked forvar(--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
?native=1phone preview is kept. Removing it is a product call./fetches the signed-in app (154 kB of code and 35 kB of styles, 50 kB compressed) before landing on sign-in;/loginitself fetches none of it. Waiting for a confirmed session instead would slow the start for everyone signed in. Say if you would rather wait.Left as they are
.a-body:not(.is-gate)and two unused:is()arguments inoverrides.css: nothing sets.is-gateany more, but removing them would lower specificity and change which rule wins.Notes.cssusesvar(--card, #fffdfa).--cardis defined nowhere, so the fallback always applies; the colour is right.state.tsxandlive/model.ts, which they need for the session. Shrinking it further means splittingstate.tsx.How it was checked
On this branch:
run-local.sh(2 more need a Neon branch), thendump-schema.sh --check.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=1states) and 4 for an account with no commune, each at desktop 1440×900 and phone 390×844, light and dark: 388 screenshots per build.What the sign-in page downloads, from production builds (sizes on disk, then compressed):
/login/while signed out (ends on sign-in)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.
lazyComponentwrapsReact.lazy: a component that has already loaded draws at once without a fallback,preloadAll()loads every lazy part (tests call it inbeforeAll, as the browser does once idle), and a failed import reloads once, remembered for 30 seconds undercommune.codeReloadedAtin session storage.🤖 Generated with Claude Code
https://claude.ai/code/session_01A6P7aTcMBccooYYr4xYnVS