Skip to content

Settings split pass 5: extract Developer Options (SettingsView under 9.5k) - #151

Merged
AdamXweb merged 2 commits into
mainfrom
refactor/settings-developer
Aug 2, 2026
Merged

Settings split pass 5: extract Developer Options (SettingsView under 9.5k)#151
AdamXweb merged 2 commits into
mainfrom
refactor/settings-developer

Conversation

@adamXbot

@adamXbot adamXbot commented Aug 1, 2026

Copy link
Copy Markdown
Collaborator

Pass 5 — Developer Options

SettingsView.tsx 10,951 → 9,404 (−1,547). Two commits, each independently reviewable.

The developer section was 914 lines of JSX rendering four independent sub-panels behind eight separate flags — bigger than earlier passes, and not splittable in the middle without leaving an incoherent state. So it lands as one section in two stages.

1. refactor(settings): hoist the activity-log subsystem into a hook

The activity log was ~14 state values, 6 mirror refs, two fetchers and four effects spread across two distant regions of the file (state at ~1018, logic at ~2577). Every external reference was its own declaration or its own effects — nothing else read it — so it moves wholesale into lib/use-activity-log.ts.

The JSX is untouched in this commit. The hook's fields are renamed back to their old activity* names on destructure, so all four diff hunks land above the first return ( at line 5094. That makes it reviewable as "did the logic move intact?" without also diffing 375 lines of markup.

Behaviour is preserved including the parts that look odd but are load-bearing: the params helper still reads state for the manual load and refs for the poll (that split is why polling doesn't drift from the visible filter), the poll keeps its empty dep array, and the effect keys off a boolean rather than the array so a prepend doesn't reset the interval.

2. refactor(settings): extract Developer Options into five components

DeveloperSection (shell) + AiDebugLogPanel + ActivityLogPanel + ActivityRowDetail + AiTimeoutsPanel.

Two decisions extend the contract in app/components/settings/README.md rather than quietly departing from it:

  • Sub-block flags move into the panel that owns them. The contract says SettingsView is "the map" of what sections exist — that's about whether a card exists, and SettingsView still owns flag.devopts.visible. Which sub-blocks a card shows is the card's business. Drilling the other eight down meant eight booleans whose names no reader could match back to a flag key.
  • State read by exactly one section moves with it. ActivityLogPanel calls the hook directly and takes a single prop. debugLogging looked equally local but round-trips through the AI settings blob, so it stays in SettingsView and is passed down. The test is whether anything outside the section reads it, not whether it feels local.

Verification

A typecheck is not sufficient for this kind of move, so:

  • Rendered #developer on origin/main and on this branch with all eight dev-opts flags forced on, and diffed the normalised outerHTML. Byte-identical at 265,719 chars, zero console errors on both sides.
  • Every useTranslations namespace and key in the new files resolved against locales/en.json. This caught three wrong namespacesai_debug_log vs ai_debug, activity_log.types vs activity_types, flag_presets vs presets — all of which typechecked perfectly happily and would have thrown at render. That hazard is now written into the README's verification section, because it applies to every remaining pass.
  • typecheck, lint, 441 unit tests, i18n parity (4,763 keys), full Playwright suite 45 passed including the settings structural net.

One thing to know

The i18n-text-literals baseline moves three entries from SettingsView.tsx to DeveloperSection.tsx. That is the same pre-existing untranslated text in a new file, not new debt. The detector keys on path, so a moved string reads as new — I regenerated the baseline rather than adding an i18n-exempt marker, so it stays recorded as debt instead of being silently blessed.

Follow-ups (not in this PR)

  • The activity* prefixes inside ActivityLogPanel are now redundant. They survived so the markup is a verbatim copy, which is what makes the HTML diff meaningful. Dropping them is mechanical and noted in the file.
  • Next up: import-history (1,342 lines) using the same two-stage shape, then ai-summaries, reset, wayback-import, backup, sync-status.

Branched from main and targets main directly — no stacking, per #150.

🤖 Generated with Claude Code

adamXbot and others added 2 commits August 1, 2026 21:45
The Settings activity log was ~14 useState declarations, 6 mirror refs,
two fetchers and four effects spread across two distant regions of
SettingsView (the state at ~1018, the logic at ~2577). Every one of them
was read by exactly one accordion, so the whole subsystem moves into
lib/use-activity-log.ts rather than being drilled through props.

The JSX is deliberately untouched: the hook's fields are renamed back to
their old `activity*` names on destructure, so all four diff hunks land
above the render (the first `return (` is at line 5094). That keeps this
commit reviewable as "did the logic move intact?" without also having to
diff 375 lines of markup. The follow-up commit moves the markup and drops
the renames.

Behaviour is preserved line-for-line, including the parts that look
odd but are load-bearing:

- `applyActivityQueryParams` still reads state directly for the manual
  load and refs for the poll — that split is why polling doesn't drift
  from the visible filter.
- `pollActivityLog` keeps its empty dep array and the eslint-disable
  that explains it; the effect keys off a `logLoaded` boolean rather
  than the array so a successful prepend doesn't reset the interval.
- Polling failures stay silent. Only user-initiated loads report, now
  via an `onLoadError` callback so the hook doesn't own the toast or
  its i18n key.

One deliberate change: a local named `window` inside the params helper
would have shadowed the global, so it is `activeWindow` here. The
pre-existing `window` parameter on `timeWindowToSince` is left alone.

SettingsView 10,951 -> 10,679.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The developer section was 914 lines of JSX rendering four independent
sub-panels behind eight separate flags. It becomes DeveloperSection (a
composition shell) plus AiDebugLogPanel, ActivityLogPanel,
ActivityRowDetail and AiTimeoutsPanel.

Two decisions here extend the contract in ./settings/README.md rather
than quietly departing from it:

**Sub-block flags move into the panel that owns them.** The contract says
SettingsView is "the map" of what sections exist — that is about whether
a *card* exists, and SettingsView still owns `flag.devopts.visible`.
Which sub-blocks a card shows is the card's own business. Drilling the
other eight down would have meant eight booleans whose names no reader
could match back to a flag key.

**State read by exactly one section moves with it.** ActivityLogPanel
calls useActivityLog directly and so takes a single prop. `debugLogging`
looked equally local but round-trips through the AI settings blob, so it
stays in SettingsView and is passed down. The test is whether anything
outside the section reads the state, not whether it feels local.

Also moved, because both sides now need them: fmtDate / fmtRelativeTime /
fmtDuration and the DateT / TimeT aliases to ./settings/format.ts,
AiDebugLogRow to ./settings/types.ts, and validateAiTimeout +
makeAiTimeoutBlurHandler into AiTimeoutsPanel (nothing else used them).

SettingsView 10,679 -> 9,404. Across both commits in this pass: 10,951 ->
9,404, a 1,547-line reduction.

Verification — a typecheck is not sufficient for this kind of move, so:

- Rendered #developer with all eight dev-opts flags forced on, on
  origin/main and on this branch, and diffed the normalised outerHTML.
  Byte-identical at 265,719 chars, zero console errors on both sides.
- Every useTranslations namespace and key in the new files resolved
  against locales/en.json. This caught three wrong namespaces
  (ai_debug_log vs ai_debug, activity_log.types vs activity_types,
  flag_presets vs presets) that typechecked perfectly happily — the
  hazard is now written into the README's verification section.
- typecheck, lint, 441 unit tests, i18n parity at 4,763 keys.

The i18n-text-literals baseline moves three entries from SettingsView.tsx
to DeveloperSection.tsx. That is the same pre-existing untranslated text
in a new file, not new debt — regenerated rather than marked
`i18n-exempt`, so it stays on the books.

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