Files
VoiceStudio/docs/maintenance-pages-modularization.md
f33bdc731d refactor(settings): modularize Settings page (1969→399 lines, all files under 500) (#758)
* refactor(settings): extract Settings.jsx tabs into components/settings (1969→602 lines)

Settings.jsx had grown to 1969 lines — every edit reloaded the whole file
into context and risked unrelated breakage. This finishes the migration the
existing components/settings/*Panel.jsx pattern started: the page is now a
thin orchestrator and each heavy tab lives in its own file.

Extracted (logic byte-for-byte identical; only import paths adjusted + the
shared isTauri/askConfirm moved to components/settings/native.js):
- GeneralTab, ModelStoreTab, EnginesTab, HotkeyTab, CredentialsTab
- native.js — shared isTauri() wrapper + askConfirm() Tauri-dialog helper

Also establishes the standard so files can't silently regrow:
- CONTRIBUTING.md: frontend file-structure & size limits (soft 300 / hard 500)
- eslint.config.js: warn-only max-lines:500 guardrail (CI stays green)
- docs/maintenance-pages-modularization.md: the phased refactor plan

Verified: vite build passes (all imports resolve); 18/18 settings tests pass;
no new lint errors introduced (the pruned imports were the only regressions).

Follow-ups (tracked in the plan doc): ModelStoreTab.jsx is 836 lines and
Settings.jsx 602 — both still over the 500 cap (warn-only); split next.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(settings): split ModelStoreTab + Settings.jsx under the 500-line cap

Follow-up to the tab extraction: bring the two remaining over-cap files into
compliance with the new standard. Pure-mechanical, no behavior change.

Settings.jsx 602 → 399:
- Extract AboutTab, PrivacyTab, LogsTab into components/settings/
- Move the shared Row helper to components/settings/Row.jsx
- LogsTab keeps its state in Settings() (lower-risk); About/Privacy take props

ModelStoreTab.jsx 836 → 439, split into components/settings/models/:
- format.js (fmtBytes/orgColor), runtime.js (computeRowRuntime)
- columns.jsx exposes makeModelColumns(...) — a factory so the TanStack cell
  closures keep working; called with the same useMemo dep array as before
- ModelsTable.jsx (virtualized table view), RecoBanner.jsx

Every settings file is now under 500 lines. Verified: vite build passes;
18/18 settings tests pass; no new lint errors (the 4 remaining in Settings.jsx
are pre-existing — refreshInfo no-op, a catch(e), two set-state-in-effect).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: mergetest <test@local>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 03:34:51 +05:30

5.0 KiB
Raw Permalink Blame History

Maintenance Refactor — frontend/src/pages Modularization

Status: Plan (not yet executed) · Drafted: 2026-06-30 · Type: Pure mechanical refactor, no behavior change

Why

frontend/src/pages/ has grown a few files large enough that any edit reloads the whole thing into context and risks unrelated breakage. Editing one Settings panel should touch a ~150-line file, not a 1969-line one. This both improves maintainability and cuts token cost per edit.

The fix is not a new architecture — components/settings/ already proves the target pattern (13 extracted *Panel.jsx, each with co-located .css/.test.jsx, plus a shared primitives/ folder). This refactor finishes a migration that stalled, then locks it in so files can't silently regrow.

Current state (measured 2026-06-30)

File Lines Notes
pages/Settings.jsx 1969 Still inline: ModelStoreTab (~790L), Settings orchestrator (~600L), GeneralTab, EnginesTab, HotkeyTab, CredentialsTab, plus Row/fmtBytes/orgColor helpers
pages/DubTab.jsx 1592 One mega-component + inline DubFailureNotice, DubPipelineStepper, PrepOverlay, TranscribeOverlay, FooterBtn
pages/CloneDesignTab.jsx 837
pages/VoiceGallery.jsx 768
pages/VoiceProfile.jsx 515
pages/AudiobookTab.jsx 402 within target after Phase 3 sweep
everything else <340 within target

Already-extracted, do not touch (reference pattern): components/settings/*Panel.jsx, components/settings/primitives/.

The gold standard (proposed)

  1. Size caps: soft 300 lines, hard 500 lines per .jsx/.css. Over 500 must split.
  2. Pages are thin orchestrators: a page = layout + routing + state wiring that composes feature components. No inline sub-component over ~50 lines.
  3. One component per file, co-located Foo.jsx + Foo.css + Foo.test.jsx, grouped in a per-page feature folder:
    • components/settings/ (exists)
    • components/dub/ (new)
    • components/clone/ (new)
    • components/gallery/ (new)
  4. Shared bits → primitives/ in the feature folder (settings already has this).
  5. Enforce with ESLint max-lineswarn-only first so it never breaks CI (respects the "keep main green" rule), upgrade to error after the backlog clears.

Phases (each = one mergeable, CI-green PR)

Phase 0 — Standard + guardrail

  • Add the size/structure rule to CONTRIBUTING.md (required by the docs-sync rule anyway).
  • Add ESLint max-lines: ['warn', { max: 500, skipBlankLines: true, skipComments: true }].
  • No code moves. Smallest possible PR; establishes the contract.

Phase 1 — Settings.jsx (biggest win: 1969 → ~300L)

Extract into components/settings/, mirroring existing panel naming:

Extract Current lines (approx) New file
ModelStoreTab (+ Row, fmtBytes, orgColor, MODEL_ROLE_*) 2291021 ModelStoreTab.jsx (likely split further: table vs. matrix vs. row)
GeneralTab 80201 GeneralTab.jsx
EnginesTab 10221072 EnginesTab.jsx
HotkeyTab (+ CREDENTIAL_FIELDS, keyEventToAccelerator) 16931870 HotkeyTab.jsx
CredentialsTab 18711969 CredentialsTab.jsx
Settings.jsx keeps only: imports, TAB_DEFS/LOG_SOURCE_DEFS, the Settings
default export (tab router + shared state), and askConfirm.

Phase 2 — DubTab.jsx (1592 → orchestrator + components/dub/)

Extract DubFailureNotice, DubPipelineStepper, PrepOverlay, TranscribeOverlay, FooterBtn, and the large render sub-sections into components/dub/. DubTab.jsx retains the pipeline state machine + composition.

Phase 3 — CloneDesignTab, VoiceGallery, VoiceProfile, AudiobookTab

Same treatment into components/clone/ and components/gallery/. Smaller, lower risk.

Constraints honored

  • No behavior change — pure moves; diff is verifiable by "app renders identically + existing tests pass." Each panel that has a test keeps it.
  • Keep main green — ESLint rule is warn-only; each phase is independently CI-green.
  • Docs-sync — Phase 0 lands the CONTRIBUTING.md change in the same PR as the rule.
  • No versioning impact — frontend-only refactor; no package.json version bump, no lockfile/dep change, no Docker/Tauri/Python surface touched.

Verification per phase

  1. bun run build (or the project's typecheck/lint) passes.
  2. Existing components/settings/*.test.jsx (and any new co-located tests) pass.
  3. Manual smoke: open Settings → every tab renders; open Dub → pipeline renders.
  4. git diff --stat shows only moves (line counts shift between files, net ~0 logic change).

Out of scope (explicitly)

  • No redesign of the Settings UI itself (the "unorganised" look) — that's a separate visual-polish task; this refactor only restructures the code. Flag if you want that bundled.
  • No conversion of .jsx.tsx (pages are currently JS; TS migration is a different decision).