Skip to content

Settings split pass 11: extract the five confirmation modals (SettingsView 3.9k, -66%) - #158

Merged
AdamXweb merged 3 commits into
mainfrom
refactor/settings-modals
Aug 3, 2026
Merged

Settings split pass 11: extract the five confirmation modals (SettingsView 3.9k, -66%)#158
AdamXweb merged 3 commits into
mainfrom
refactor/settings-modals

Conversation

@adamXbot

@adamXbot adamXbot commented Aug 3, 2026

Copy link
Copy Markdown
Collaborator

Pass 11 — the five confirmation modals

SettingsView.tsx 4,322 → 3,908 (−414).

Base note: stacked on #156 and #157. Targets main directly.

CI will fail on install until ~09:14Z — ultracite@7.10.0 vs pnpm's 24h release-age cutoff, from #154. Not this PR. See #156.

What moved

With every section out, the remaining JSX was the page shell plus five dialogs:

Modal Lines Props
RestoreBackupModal 153 13
DeleteImportModal 109 6
ResetAppModal 78 8
RemoveItemModal 67 5
WaybackRemoveModal 46 5

They stay rendered from SettingsView rather than moving into the sections they logically belong to. They're page-level overlays — relocating them into a section would change their position in the tree and therefore their stacking context. Moving only the markup, and keeping the render site, is the change that's provably behaviour-neutral.

Two types declared inside the component move out so both sides can name them: BackupRestorePreview to settings/types.ts, and the staged remove-from-Apps target as a named PendingItemRemoval export on use-import-history (it had been an inline object literal in the useState generic).

Verification

Modals render nothing until opened, so a closed-modal diff would have proved nothing — the same empty-state trap as #153 and #157. The harness drives the UI to open each dialog before capturing. Reset App and Wayback Remove both open and compare identical at 1,465 chars, zero console errors.

One namespace was wrong. I wrote settings.modals.wayback_remove; the real one is settings.wayback.remove_modal — it lives under the wayback namespace, not with the other modals. Caught by resolving every key against locales/en.json. That's the third time this check has caught a namespace tsc couldn't, which is why it's in the README as a required step.

Plus tsc, biome, 441 unit tests, full Playwright 45 passed.

Phase 3 running total

SettingsView
Start 11,460
Passes 1–8 (main) 5,643
Passes 9–10 (#156, #157) 4,322
Pass 11 (this) 3,908

−66%. What's left is genuinely the page: the settings loader, the sidebar/hash plumbing, and the section composition — no more extractable units of this kind.

Next in Phase 3 is a different sort of work: the route split from SECTION_GROUPS with hash redirects (which also fixes the deep-link scroll race), the OnboardWizard split, CSS out of the 32k-line globals.css, and the 224-key flag audit.

🤖 Generated with Claude Code

adamXbot and others added 3 commits August 3, 2026 12:32
Stage 1 of the last section. The AI Summaries card's state machine —
provider/base-URL/model/key form state, the connection test, model
discovery against a custom endpoint, the sample-policy dry run, the three
per-phase request timeouts, and the debounced blob auto-save — moves to
lib/use-ai-settings.ts. That is ~480 lines spread across six regions of
SettingsView.

The JSX is untouched: hook fields are renamed back on destructure, so all
nine diff hunks land above the first `return (` at line 3037. The
791-line markup move is the next pass.

Inputs are five: showToast, three translators used by the auto-save toast
labels, the task-center handle, and loadSettings. Nothing else crossed
the boundary. Three more candidates the identifier sweep suggested —
`base`, `schedule`, `toast` — turned out to be matches inside comments.

Several bindings leak deliberately and are returned rather than kept
private: `aiProvider` (the timeouts panel disables its inputs when AI is
off), `saveAiSettings` + `debugLogging` (the Developer Options debug
toggle round-trips through this same blob), and the three `aiTimeout*`
values with their auto-savers.

`hydrate()` is the seam for the shared settings loader: it reads every
settings key in one request, so it hands the AI slice over in a single
call rather than this hook mounting a duplicate fetch.

Four types move to ./settings/types.ts — StoredAiSettings and
AiSamplePolicyResult, which both sides now need.

The hook call sits below `showToast` and `loadSettings` rather than where
the state block was, for the same TDZ reason as use-import-history: both
are const arrows.

SettingsView 5,643 -> 5,204.

Verified with tsc, biome, 441 unit tests and the full Playwright suite
(45 passed).

Note for anyone verifying locally: `pnpm install` currently fails on this
repo with ERR_PNPM_MINIMUM_RELEASE_AGE_VIOLATION for ultracite@7.10.0,
which arrived via the Renovate bump in #154 and is newer than pnpm's
release-age cutoff. It is a local policy, not a CI one (#154 merged
green), and it resolves itself as the package ages. Until then,
`--config.minimumReleaseAge=0` on install and
`--config.verifyDepsBeforeRun=false` on run get you moving.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Stage 2, and the last section. The 791-line AI Summaries card moves to
AiSummariesSection, which takes a single prop: the whole `useAiSettings`
return value as `ai`.

The hook cannot be called inside the section — Developer Options needs
the same blob for its debug-logging toggle and the timeouts panel reads
`aiProvider`, so two call sites would mean two independent copies of the
state. One object prop beats thirty-five.

Its two sub-flags (`provider_selector`, `summarize_on_import`) are
resolved in the section; SettingsView keeps the section-level
`flag.settings.ai.enabled` gate, per the rule in ./settings/README.md.

`hydrate()` now actually exists. The previous commit's docstring
described it as the seam for the shared settings loader, but it was
never implemented — the loader still called eight setters directly,
which is why they were leaking out of the hook. It is implemented here
and the loader calls it, so the destructure at the call site drops from
57 bindings to 14.

Also removed: two params I threaded into the hook out of habit
(`showToast`, `tToast`) that nothing in the moved code ever used, and
nine translator declarations left stranded in SettingsView.

Verified with the DOM diff, and this one needed a configured provider to
be worth running: the card's `disabled` state is a fraction of its
markup, so comparing that would have proved almost nothing — the same
trap as the empty import list in the previous pass. With a custom
provider, base URL and model set, both sides render identical at 7,491
chars with zero console errors.

Also: tsc, biome, 441 unit tests, full Playwright suite 45 passed, and
all 14 translation namespaces resolved against locales/en.json.

SettingsView 5,204 -> 4,322. Every section is now extracted: 11,460 ->
4,322 across the phase, a 62% reduction.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
With every section out of SettingsView, the remaining JSX was the page
shell plus five dialogs. Those move to RestoreBackupModal,
DeleteImportModal, WaybackRemoveModal, RemoveItemModal and ResetAppModal
— 453 lines, 5 to 13 props each.

They stay rendered from SettingsView rather than moving into the sections
they belong to, because they are page-level overlays: relocating them
into a section would change their position in the tree and therefore
their stacking context. Keeping the render site and moving only the
markup is the change that is provably behaviour-neutral.

Two types that were declared inside the component move out so both sides
can name them: BackupRestorePreview to ./settings/types.ts, and the
staged "remove from Apps" target as a named PendingItemRemoval export on
use-import-history (it had been an inline object literal in the useState
generic).

Verified. Modals render nothing until opened, so a closed-modal diff
would have proved nothing — the harness drives the UI to open each one
before capturing. Reset App and Wayback Remove both open and compare
identical at 1,465 chars, zero console errors.

One namespace was wrong: I wrote settings.modals.wayback_remove for the
wayback dialog when the real one is settings.wayback.remove_modal — it
sits under the wayback namespace, not with the other modals. Caught by
resolving every key against locales/en.json, which is the third time that
check has caught a namespace typecheck could not.

Also: tsc, biome, 441 unit tests, full Playwright suite 45 passed.

SettingsView 4,322 -> 3,908.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@adamXbot
adamXbot requested a review from AdamXweb as a code owner August 3, 2026 04:15
@AdamXweb
AdamXweb merged commit 2e9df0b into main Aug 3, 2026
8 of 13 checks passed
@AdamXweb
AdamXweb deleted the refactor/settings-modals branch August 3, 2026 07:33
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