# Design sweep — Settings

**File:** `apps/web/src/routes/Settings.tsx` — 2974 lines, single file, no CSS
module (all styling is inline `style={}` objects + `className="cm-*"`/`"so-*"`
string literals). Reached from the shell nav (`/settings`) and `?tab=` deep
links (`asSettingsTab`). One default export renders all ten sections; nothing
is code-split.

## Legacy-marker count

**0** — not in `LEGACY_BASELINE` (conformance.test.ts), and a source grep
confirms it: every hex-color literal in the file (`#fff`, `#4F46E5` ×2) carries
a `conformance-allow raw-color:` comment, so the mechanical scanner is right
that this file emits zero violations of its own.

**But this is a borrowed clean bill, not an owned one.** 32 of the file's 37
text-entry controls are bare `<input>`/`<select>`/`<textarea>` with no
`className` at all — they render only because `styles/global.css` (itself
pinned in `LEGACY_BASELINE` at 237 markers) carries bare-tag selectors
(`input[type="text"], select, textarea { … }`, line ~281) that style every
unclassed input in the app. Only 5 fields use the real `<Input>`/`<Field>`
components (all inside `AlertsSection`). If/when `global.css`'s tag rules are
ever retired as part of shrinking that baseline, Settings breaks silently —
the scanner won't catch it because it never scans for *absence* of a class.
Raw-control tally: 32 `<input>`, 6 `<select>`, 4 `<textarea>`, 37 `<button>`
(`cm-btn` variants — those are fine).

## Five lenses

- **IA/concept count** — 10 tabs in 4 groups (Platform: General, Campaigns,
  Auto-replies, Alerts · Contacts: Tags, Custom fields · Connections:
  Connections, Models · Access: Users, Audit). Per-tab scope is reasonable
  (Alerts is the densest at ~3 sub-concepts), so a user inside one tab isn't
  overloaded — the concept count is a maintainer problem more than a user
  one. Grouping is legible; the module boundary just doesn't match the tab
  boundary (all ten live in one 2974-line function tree).
- **Interaction** — the unsaved-changes guard (dirty tracking + confirm
  dialog on tab-switch *and* router nav *and* `beforeunload`) is a genuinely
  good, non-obvious affordance — keep the behavior even if the code moves.
  `role="tablist"`/`role="tab"`/`aria-selected` are present on the tab nav,
  but there's no arrow-key roving-tabindex (WAI-ARIA tabs pattern expects
  Left/Right to move focus between tabs; here only native Tab-key order
  works) — a real but minor keyboard gap.
- **Content/copy** — mostly plain ("Turn one off for a number you know is
  down, so it stops paging you"). A few internal/ops terms leak straight into
  UI headings — "Red tier", "Silence detector", "Evaluation mode" — vocabulary
  that reads as written by the person who built the alerting pipeline, not
  for the person configuring it. Worth a copy pass, not a rebuild.
- **Visual/tokens** — 100% on `--cm-*` custom properties in its own styles
  object (see caveat above re: borrowed `global.css` tag rules for the raw
  inputs). No off-token literals, no `so-*`/legacy classes.
- **A11y + responsive** — **zero `htmlFor` in the entire file.** 11 separate
  `fieldRow`-pattern instances (General, Campaigns, part of Users) render
  `<label style={styles.label}>Caption</label>` as a *sibling* of the
  `<input>`, not wrapping it and with no `id`/`htmlFor` pair — so those labels
  have no programmatic association with their control; a screen reader
  announces the input with no name. Contrast: the dialog forms (Tags,
  Custom fields, `vField` pattern) correctly nest `<label>Caption<input/></label>`,
  which *is* associated — so the file knows the right pattern and just
  doesn't use it consistently. Responsive: `settingsLayout` flex-wraps and
  the nav column has no fixed viewport-relative width, so it likely survives
  320px by stacking; not verified in a browser (read-only pass).

## Verdict: **redesign**

Token-clean by the mechanical oracle, but that's the easy 20%. The real debt
is structural: one 2974-line file owning ten unrelated settings domains, a
dependency on a legacy stylesheet's bare-tag rules it doesn't declare, two
competing field patterns (5 real `<Input>` vs. 32 raw `<input>`, one
accessible label pattern vs. one that isn't), and copy that occasionally
speaks in system-internals. None of that shows up in a marker count. A
redesign here is mostly decomposition (one file/component per tab, one field
primitive used everywhere, real label association everywhere) rather than a
visual overhaul — the token layer is already right.

## Forks

1. **Decomposition unit** — split Settings into one route/file per tab (10
   files) under a shared shell, or keep one route but extract each
   `*Section` into its own component file? Recommendation: one file per
   section under `routes/settings/`, matching the `TAB_GROUPS` structure —
   the shell (nav + unsaved-guard) stays the only thing in `Settings.tsx`.
2. **Field primitive** — mandate `<Field>`/`<Input>`/`<Textarea>` everywhere
   and delete the raw-tag reliance on `global.css`, now, as part of this
   rebuild, or defer to a later pass once the headless behavior-layer inputs
   land? Recommendation: do it now — it's mechanical and the 5-instance
   `Alerts` pattern is already the template.
3. **"Red tier" / "Silence detector" language** — keep the operator-facing
   jargon (it may be intentional shorthand this operator already knows) or
   rename to plain outcomes ("Immediate email alerts", "Quiet-channel
   detection")? Recommendation: rename — DECISIONS.md already rules out
   engineering vocabulary in product copy.
