perf(ui): keep lazy-page CSS out of the Control UI entry stylesheet#110687
Conversation
The entry stylesheet aggregated approval, config, config-quick, and lobster-pet styles even though every consumer is a lazy route chunk or lazily defined element, and two small UI landings tipped startup CSS to exactly the 42.0 KiB budget, failing all QA Smoke profiles on main (run 29643357786). Move each stylesheet to its owning lazy module — pages already own their CSS this way (chat, agents, cron) — and keep only the shell's pre-hydration approval booting subset in the entry via the new approval-boot.css. Startup CSS drops 42.0 -> 36.0 KiB gzip; tighten the budget 42 -> 38 KiB to lock in the recovery, matching the startup-JS precedent from #110528.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1f2d302550
ℹ️ 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".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| @@ -1,3 +1,5 @@ | |||
| import "../../styles/config.css"; | |||
There was a problem hiding this comment.
Move shared config-form CSS out of the config route
Importing config.css only from openclaw-config-page strands selectors that are still shared outside the config route: ui/src/pages/channels/view.config.ts renders renderNode() with the cfg-* classes, and ui/src/pages/plugins/view.ts renders mcp-server-form, while rg "config.css" ui/src shows no production import from those routes. Visiting Channels channel config or Plugins MCP management directly will now load their chunks without the form/MCP CSS that used to be in the entry stylesheet, so those forms lose their intended layout/states; split those shared selectors into a component/global stylesheet imported by the non-config consumers, or keep that subset in the entry.
Useful? React with 👍 / 👎.
| @import "./styles/approval.css"; | ||
| @import "./styles/config.css"; | ||
| @import "./styles/config-quick.css"; | ||
| @import "./styles/approval-boot.css"; |
There was a problem hiding this comment.
Keep logs fill-height CSS with the logs route
Removing config-quick.css from the entry also removes the only desktop logs layout rules: that file still owns .content:has(openclaw-logs-page), openclaw-logs-page, .logs-card, and .log-stream, while ui/src/pages/logs/route.ts only imports logs-page.ts and no logs module imports config-quick.css. On the /logs page, which renders renderSettingsWorkspace(body, { fillHeight: true }), the stream will fall back to the generic max-height: calc(100vh - 280px) instead of filling the remaining viewport; move those selectors to a logs stylesheet imported by the logs page, or keep the logs subset in the entry.
Useful? React with 👍 / 👎.
|
Merged via squash.
|
…penclaw#110687) The entry stylesheet aggregated approval, config, config-quick, and lobster-pet styles even though every consumer is a lazy route chunk or lazily defined element, and two small UI landings tipped startup CSS to exactly the 42.0 KiB budget, failing all QA Smoke profiles on main (run 29643357786). Move each stylesheet to its owning lazy module — pages already own their CSS this way (chat, agents, cron) — and keep only the shell's pre-hydration approval booting subset in the entry via the new approval-boot.css. Startup CSS drops 42.0 -> 36.0 KiB gzip; tighten the budget 42 -> 38 KiB to lock in the recovery, matching the startup-JS precedent from openclaw#110528.
…penclaw#110687) The entry stylesheet aggregated approval, config, config-quick, and lobster-pet styles even though every consumer is a lazy route chunk or lazily defined element, and two small UI landings tipped startup CSS to exactly the 42.0 KiB budget, failing all QA Smoke profiles on main (run 29643357786). Move each stylesheet to its owning lazy module — pages already own their CSS this way (chat, agents, cron) — and keep only the shell's pre-hydration approval booting subset in the entry via the new approval-boot.css. Startup CSS drops 42.0 -> 36.0 KiB gzip; tighten the budget 42 -> 38 KiB to lock in the recovery, matching the startup-JS precedent from openclaw#110528.
What Problem This Solves
Main is red on every push since
74880cb: all four QA Smoke CI profiles fail the Control UI performance gate with "startup CSS gzip: 42.0 KiB exceeds 42.0 KiB" (run 29643357786). Bisected by buildingui/at each commit: no single culprit — #110605 (+0.21 KiB) and #110654 (+0.14 KiB) tipped a budget that was already 97% full.The structural cause: the entry stylesheet (
ui/src/styles.css) aggregatesapproval.css,config.css,config-quick.css, andlobster-pet.csseven though every consumer is a lazy route chunk (pages ownroute.tsdynamic imports) or a lazily defined element (lobster-pet.tsis alreadyimport()ed by the sidebar).Why This Change Was Made
Move each stylesheet to its owning lazy module — the established pattern (chat, agents, cron pages already import their own CSS). The shell's pre-hydration approval booting state (
app-host.tsrenders.approval-page--bootingbefore the lazy page loads) keeps its subset in the entry via the newapproval-boot.css. Consumer map verified by selector-leak grep:config-quick.cssalso feedsprofile-page.ts(lazy),lobster-pet.cssclasses are used by about/memory/config views (all lazy) — each gets its own import.Startup CSS drops 42.0 → 36.0 KiB gzip; the budget tightens 42 → 38 KiB to lock in the recovery, matching the startup-JS precedent (#110528: lazy-load + tighten). Total CSS grows 2.4 KiB from per-chunk duplication — the intended trade for startup weight.
User Impact
Main CI green again; Control UI first paint ships 6 KiB less CSS; the budget gate keeps future page styles from silently re-filling the entry.
Evidence
node scripts/check-control-ui-performance.mjsaftervite build: startup CSS 36.0 KiB / 38.0 limit, one request, no violations (before: 42.0/42.0 violation reproduced locally).vitest.ui.config.tsrun: no new failures (the only failing suites are the three storage tests already proven to fail identically on clean main under local Node 26)..approval-page--bootingshell dependency handled via the boot subset split.