Files
VoiceStudio/docs/maintenance-pages-modularization.md
T
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

94 lines
5.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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-lines`** — **warn-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_*`) | 229–1021 | `ModelStoreTab.jsx` (likely split further: table vs. matrix vs. row) |
| `GeneralTab` | 80–201 | `GeneralTab.jsx` |
| `EnginesTab` | 1022–1072 | `EnginesTab.jsx` |
| `HotkeyTab` (+ `CREDENTIAL_FIELDS`, `keyEventToAccelerator`) | 1693–1870 | `HotkeyTab.jsx` |
| `CredentialsTab` | 1871–1969 | `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).