perf: load the Settings dialog on demand; entry chunk 351.8 → 277.1 kB gzip #420

Merged
jared merged 2 commits from perf/lazy-settings into lotus 2026-10-19 02:56:11 -04:00
Owner

Follow-up to #419 (the re-seeded bundle budget): win back the space instead of just raising the limit.

Where the entry chunk's bytes went (source-map attribution of index-*.js on lotus): features/settings was the largest single item at 147 kB minified, but it only renders when the dialog opens.

Change

  • Settings is loaded on demand (React.lazy). The fallback is a small focusable "Loading settings" spinner, because Settings opens inside a FocusTrap and an empty fallback crashes it ("must have at least one tabbable node"). The chunk is warmed on requestIdleCallback, so opening Settings rarely waits.
  • SettingsPages and the status localStorage keys (STATUS_EXPIRY_KEY/STATUS_MSG_KEY) move to tiny modules (features/settings/pages.ts, utils/statusKeys.ts), so the sidebar and ClientNonUIFeatures don't pull the dialog back in. features/settings still exports Settings and SettingsPages, so callers don't change.
  • sanitize-html (+ postcss, htmlparser2…) and chroma-js get their own vendor chunks. With Settings gone, the chunk graph had dropped them into the entry chunk and cancelled most of the gain.

Result (gzip):

before (lotus d7bc6d94) after
entry chunk index-*.js 351.8 kB 277.1 kB
largest chunk 351.8 kB (entry) 304.8 kB (matrix-sdk)
Settings in entry 43 kB, loaded on demand
total 1627.8 kB 1635.6 kB (more files, slightly less compression)

Verified

  • Settings opens from User Settings (General → Account) and from Unverified device (Devices), both after load and immediately on first paint, before the idle preload.
  • e2e/security-nudge.spec.ts (4 tests, including Set up → Devices) passes, along with boot check, unit tests, lint and the budget.

🤖 Generated with Claude Code

https://claude.ai/code/session_017PCb59uY3UKqDqt7B6hzd5

Follow-up to #419 (the re-seeded bundle budget): win back the space instead of just raising the limit. **Where the entry chunk's bytes went** (source-map attribution of `index-*.js` on `lotus`): `features/settings` was the largest single item at **147 kB minified**, but it only renders when the dialog opens. **Change** - **`Settings` is loaded on demand** (`React.lazy`). The fallback is a small focusable "Loading settings" spinner, because Settings opens inside a FocusTrap and an empty fallback crashes it ("must have at least one tabbable node"). The chunk is **warmed on `requestIdleCallback`**, so opening Settings rarely waits. - `SettingsPages` and the status localStorage keys (`STATUS_EXPIRY_KEY`/`STATUS_MSG_KEY`) move to tiny modules (`features/settings/pages.ts`, `utils/statusKeys.ts`), so the sidebar and `ClientNonUIFeatures` don't pull the dialog back in. `features/settings` still exports `Settings` and `SettingsPages`, so callers don't change. - **`sanitize-html` (+ postcss, htmlparser2…) and `chroma-js` get their own vendor chunks.** With Settings gone, the chunk graph had dropped them into the entry chunk and cancelled most of the gain. **Result** (gzip): | | before (lotus d7bc6d94) | after | |---|---|---| | entry chunk `index-*.js` | 351.8 kB | **277.1 kB** | | largest chunk | 351.8 kB (entry) | 304.8 kB (`matrix-sdk`) | | Settings | in entry | 43 kB, loaded on demand | | total | 1627.8 kB | 1635.6 kB (more files, slightly less compression) | **Verified** - Settings opens from **User Settings** (General → Account) and from **Unverified device** (Devices), both after load and **immediately on first paint**, before the idle preload. - `e2e/security-nudge.spec.ts` (4 tests, including Set up → Devices) passes, along with boot check, unit tests, lint and the budget. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_017PCb59uY3UKqDqt7B6hzd5
jared added 1 commit 2026-10-19 00:00:26 -04:00
perf: load the Settings dialog on demand; entry chunk 351.8 → 277.1 kB gzip
CI / Build & Quality Checks (pull_request) Canceled after 0s
CI / Trigger Desktop Build (pull_request) Canceled after 0s
CI / Secret scan (gitleaks) (pull_request) Canceled after 0s
CI / Docker image build & smoke test (pull_request) Canceled after 0s
CI / Playwright smoke (e2e) (pull_request) Canceled after 0s
8b6464c8b9
Settings (every page, pickers, the denoise tester) was in the entry
chunk though it only renders when opened. Load it lazily behind a
focusable spinner (it opens inside a FocusTrap), and warm the chunk
when the app goes idle. SettingsPages and the status localStorage keys
move to tiny modules so callers don't pull the dialog back in.
sanitize-html (+postcss, htmlparser2) and chroma-js get their own vendor
chunks; with Settings gone the chunk graph had dropped them into the
entry chunk.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017PCb59uY3UKqDqt7B6hzd5
jared added 1 commit 2026-10-19 01:07:53 -04:00
Merge remote-tracking branch 'origin/lotus' into perf/lazy-settings
CI / Build & Quality Checks (pull_request) Successful in 2m0s
CI / Trigger Desktop Build (pull_request) Skipped
CI / Docker image build & smoke test (pull_request) Skipped
CI / Secret scan (gitleaks) (pull_request) Successful in 7s
CI / Playwright smoke (e2e) (pull_request) Successful in 11m52s
0b940a6df7
# Conflicts:
#	src/app/features/settings/account/Profile.tsx
jared merged commit ef3849eb2f into lotus 2026-10-19 02:56:11 -04:00
Sign in to join this conversation.
No Reviewers
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: LotusGuild/cinny#420