Skip to content

Settings split pass 8: extract the four remaining small sections (SettingsView 5.6k, -51%) - #155

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

Settings split pass 8: extract the four remaining small sections (SettingsView 5.6k, -51%)#155
AdamXweb merged 3 commits into
mainfrom
refactor/settings-remaining-sections

Conversation

@adamXbot

@adamXbot adamXbot commented Aug 2, 2026

Copy link
Copy Markdown
Collaborator

Pass 8 — the four remaining small sections

SettingsView.tsx 6,711 → 5,643 (−1,068).

Base note: stacked on #152 and #153, which haven't merged yet, so their commits appear here until they do. Targets main directly — merging any of these lands its ancestors in main too.

What moved

Section Lines Props
SyncStatusSection 83 3
BackupSection 340 13
WaybackImportSection 480 14
ResetSection 54 5

Grouped into one pass because each is small and independent — four PRs for four ~10-prop components would be ceremony rather than review value.

AI Summaries is deliberately not here. Its 37 bindings want the same hook treatment Import History got, which is a pass of its own.

StartOverButton moves into ResetSection (its only caller, and its danger-zone sibling) and fmtRelative into SyncStatusSection (likewise). Seven types move to settings/types.ts.

Types come from the declarations, not from reading the JSX

Every prop signature is typed from the actual useState declaration. Guessing produced 60 type errors on the first attempt:

  • restoreError is string, not string | null
  • waybackRunStatus is non-nullable
  • waybackControlBusy is null | "pause" | "resume" | "cancel" | "force", not a boolean
  • setBackupSnapshotSettings needs the Dispatch<SetStateAction<…>> form — the JSX calls it with an updater
  • runBulkWaybackImport takes an options object

Worth stating because each of those typechecks fine as a plain boolean/string right up until the JSX uses it.

Two props I initially threaded turned out to be phantoms, both from the same over-eager identifier matching that has bitten this refactor before: schedule on SyncStatus (the JSX reads status.schedule; my sweep matched the word "scheduled" in a comment) and applyBackupSnapshotPayload on Backup (appears only inside a comment). Both dropped, and I verified against the original bodies rather than just deleting what the linter flagged.

Verification

Rendered all four sections before and after with their flags forced on, and diffed the normalised HTML: identical at 7,355 chars, zero console errors both sides. All four confirmed present with real content first — the pass-7 lesson about empty states passing trivially.

Two harness artefacts surfaced and were normalised rather than ignored: the fixture's generated row ids, and the per-side data directory that the backup card legitimately prints in its snapshot path.

Plus typecheck, lint, 441 unit tests, full Playwright 45 passed, and all 17 translation namespaces resolved against locales/en.json.

Phase 3 running total

SettingsView
Start 11,460
Passes 1–5 (main) 9,406
Pass 6 (#152) 8,215
Pass 7 (#153) 6,711
Pass 8 (this) 5,643

−51%. One section left: ai-summaries (791 lines).

🤖 Generated with Claude Code

adamXbot and others added 3 commits August 2, 2026 18:02
Import History was ~1,100 lines of state, effects and handlers living
directly in SettingsView, interleaved with unrelated code: the AI
debug-log fetchers and the whole backup/restore flow sat in the middle of
it. Moving it out means lifting four separate ranges, not one.

The dependency analysis is what made this safe to do wholesale. Of the
~50 component-scope bindings the section touches, exactly five cross the
boundary — the import-queue provider, the router/searchParams pair used
for deep links, and showToast with its translator. Those five become the
hook's inputs; everything else was read only by Import History.

`deleteTarget` is returned rather than kept private because the
delete-confirm modal renders as a page-level overlay at the bottom of
SettingsView rather than inside the section. Same feature, different
place in the tree — worth returning one binding instead of relocating a
modal and changing DOM order.

The queued-row countdown ticker moved too. It lived a thousand lines away
from the rest of the subsystem but only exists to re-render queue
countdowns, so it went into the hook rather than re-exporting
`setNowTick` to drive it from outside.

As with the activity log, the JSX is untouched: hook fields are renamed
back on destructure, and all eight diff hunks land above the first
`return (` at line 3702. The 1,299-line markup move is the next pass.

Two things worth noting for review:

- The hook call sits just after `showToast` rather than where the state
  block used to be. `showToast` is a const arrow, so calling the hook at
  the old position was a TDZ error. Moving the call is the honest fix; a
  lazy `(msg) => showToast(msg)` wrapper would have hidden it.
- The first cut returned all 51 bindings mechanically. Thirteen were dead
  at the call site, so they are private to the hook now.

SettingsView 9,404 -> 8,215.

Verified: typecheck, lint, 441 unit tests, full Playwright suite 45
passed including the settings structural net.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Stage 2 of the Import History split. The 1,299-line section moves to
ImportHistorySection, and the 27-line compact link card shown on the
Settings landing page moves to ImportHistoryLinkCard.

The section takes a single prop: the whole `useImportHistory` return
value, as `ih`. The hook cannot be called inside the section, because the
delete-confirm modal renders as a page-level overlay outside it and needs
the same state — two call sites would mean two independent copies. One
object prop beats threading thirty-one bindings through a signature.

SettingsView keeps only the ten bindings its two modals actually use;
everything else now flows through `ih`. Its `viewMode` gates stay put, per
the section-level-gate rule in ./settings/README.md.

Also moved: FILTER_META, STATUS_META, fmtQueueCountdown and
pickSourceIcon into the section (nothing else referenced them), and
fmtShortDate to ./settings/format.ts (four other call sites still need
it).

The two components deliberately share the `import-history` anchor id.
They are mutually exclusive `viewMode` branches, so only one is ever in
the document — worth stating because it reads like a duplicate-id bug.

Verification. The before/after HTML diff needed real data to be worth
anything: the canned fixture seeds apps but no imports, so the first run
compared two empty states. The harness now creates a six-item import
covering every status the UI branches on (imported / unmatched / error /
queued / removed / skipped), expands the row so the item list and its
per-item actions are in the tree, and freezes the queued-row countdown.
Both surfaces then compare byte-identical at 4,205 chars with zero
console errors.

The only raw difference was the fixture's own generated row ids
(`iti_…`), which change per seed run — normalised, not ignored.

Also: typecheck, lint, 441 unit tests, full Playwright suite 45 passed,
i18n parity, and all 17 translation namespaces in the new files resolved
against locales/en.json.

SettingsView 8,215 -> 6,711.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sync Status, Backup & Restore, Wayback Import and Reset App move out
together — 957 lines of JSX. They are grouped into one pass because each
is small and independent, and splitting four ~10-prop components across
four PRs would be ceremony rather than review value.

AI Summaries is deliberately not here. Its 37 bindings want the same
hook treatment Import History got, which is a pass of its own.

StartOverButton moves into ResetSection (its only caller, and its
danger-zone sibling) and fmtRelative into SyncStatusSection (likewise).
The types the sections need — BackupRestoreStage, WaybackRunStatus,
WaybackProgress, WaybackLastRun, BackupSnapshot{Settings,Row,sPayload} —
move to ./settings/types.ts.

Every prop signature is typed from the actual `useState` declaration
rather than inferred from the JSX. Guessing produced 60 type errors on
the first pass: `restoreError` is `string` not `string | null`,
`waybackRunStatus` is non-nullable, `waybackControlBusy` is a union not a
boolean, `setBackupSnapshotSettings` needs the Dispatch form because the
JSX calls it with an updater, and `runBulkWaybackImport` takes an options
object. Worth stating because every one of those typechecks fine as a
plain `boolean`/`string` until the JSX actually uses it.

Two props I initially threaded were phantoms: `schedule` on SyncStatus
(the JSX reads `status.schedule`, and my analysis matched the word
"scheduled" in a comment) and `applyBackupSnapshotPayload` on Backup
(referenced only inside a comment). Both dropped.

Verified. Rendered all four sections before and after with their flags
forced on, and diffed the normalised HTML: identical at 7,355 chars,
zero console errors. Two harness artefacts showed up and were normalised
rather than ignored — the fixture's generated row ids, and the per-side
data directory that the backup card legitimately prints.

Also: typecheck, lint, 441 unit tests, full Playwright suite 45 passed,
and all 17 translation namespaces across the four files resolved against
locales/en.json.

SettingsView 6,711 -> 5,643.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@adamXbot
adamXbot requested a review from AdamXweb as a code owner August 2, 2026 23:30
@AdamXweb
AdamXweb merged commit cd3cd10 into main Aug 3, 2026
13 checks passed
@AdamXweb
AdamXweb deleted the refactor/settings-remaining-sections branch August 3, 2026 02:21
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