Skip to content

refactor(web): extract ToggleSettingCard from three settings toggles - #1075

Merged
selfcontained merged 1 commit into
mainfrom
tech-debt/toggle-setting-card
Sep 7, 2026
Merged

refactor(web): extract ToggleSettingCard from three settings toggles#1075
selfcontained merged 1 commit into
mainfrom
tech-debt/toggle-setting-card

Conversation

@selfcontained

Copy link
Copy Markdown
Owner

What

apps/web/src/components/app/chat-surface-settings.tsx, cross-repo-messaging-settings.tsx and injection-hold-settings.tsx each hand-rolled the same JSX card shell. New presentational ToggleSettingCard (apps/web/src/components/app/toggle-setting-card.tsx) now owns it; the three files pass copy + state in.

Why this is debt

PR #1020 extracted the shared hook (useOptimisticToggleSetting) but left the shell duplicated across the three panels, and the chat surface then added a fourth copy — exactly the "muscle memory when writing the next one" regression the tech-debt backlog predicts. Found by the 2026-09-05 broad audit (#1060): 6 duplicated 7-line windows each way.

What the included set has in common — precisely

All three rendered a byte-identical shell, differing only in the eyebrow text, description copy, title, hint and data-testid:

  • <div className="p-6"> wrapper
  • eyebrow <div className="mb-1.5 text-[10px] uppercase tracking-widest text-muted-foreground">
  • <p className="mb-3 max-w-2xl text-sm text-muted-foreground"> description
  • <div className="max-w-lg"><label className="flex cursor-pointer items-center gap-3 rounded border border-border px-3 py-2.5 transition-colors hover:bg-muted/50"> wrapping a <Checkbox checked onCheckedChange={(c) => setEnabled(c === true)} data-testid=…> plus a min-w-0 title/hint pair
  • trailing {error ? <p role="alert" className="mt-3 text-sm text-destructive">

Class names, DOM shape and testids are unchanged — the diff is a pure move. error is string on both hooks (useOptimisticToggleSetting and useChatSurfaceSetting), so the empty-string-means-no-error convention carried over verbatim.

The card is presentational only — it does not wrap a hook, because chat-surface-settings.tsx deliberately uses a different one (useChatSurfaceSetting, React Query-backed so the tab bar re-labels without a reload) while the other two use useOptimisticToggleSetting.

Deliberate exclusions (near-misses left alone)

  1. launch-guidance-settings.tsx — shares the p-6 wrapper and error paragraph but its control row is materially different: <label className="flex w-fit … text-sm text-foreground"> with the text inline, no card border, no max-w-lg, no description block, and disabled={!loaded} on the checkbox (deliberate — an unread value must not render as a confirmed "off"). Folding it in would mean adding a disabled prop plus a variant for the borderless row, i.e. designing for one caller.
  2. The four row-only lookalikesslack-notifications-section.tsx, ide-settings.tsx, notification-device-sections.tsx, browser-notifications-section.tsx share the bordered <label> row but not the card: they render rows inside .map() lists or conditional blocks, some with no hint line, some with their own disabled logic, and none with the eyebrow/description/error shell. See the question below.

Smaller-blast-radius note

The backlog's recommended prop list included disabled?; I dropped it because none of the three included callers pass one — adding it would have been speculative surface for a component with three consumers.

Validation

  • pnpm run check — clean
  • pnpm run finalize:web — build clean
  • pnpm run test:e2e — 198 passed / 12 skipped (includes settings.spec.ts "cross-repo messaging toggle defaults off and persists to the server")
  • cd apps/web && pnpm vitest run — 125 files, 1841 tests passed
  • Live browser check on an isolated dev stack: all three cards render identically (including the <strong> runs in the chat description), and flipping the cross-repo toggle round-trips to GET /api/v1/app/settings/cross-repo-messaging{"enabled":true}.

Non-vacuous test proof. settings-pane.test.tsx stubs all three modules, so the new colocated toggle-setting-card.test.tsx is the only real coverage. Defect probes, each reverted after:

Probe Result
Render the error paragraph unconditionally 1 test fails
Invert the toggle value (next !== true) 2 tests fail
Drop the hint <div> 2 tests fail
Pass the raw checkbox value through (next as boolean) all pass — Radix never emits "indeterminate" from a click, so the coercion is unobservable at this seam. Recording it rather than writing a test that would only assert an unreachable branch.

Next run

Queued: feedback-item action handlers duplicated between diff-feedback-annotation.tsx:92-117 and reviews-feedback-item.tsx:108-131 (16 duplicated 7-line windows).

🤖 Generated with Claude Code

…(#tech-debt)

apps/web/src/components/app/chat-surface-settings.tsx,
cross-repo-messaging-settings.tsx and injection-hold-settings.tsx each
hand-rolled the same JSX card shell: a p-6 wrapper, an uppercase eyebrow,
a max-w-2xl description paragraph, a bordered checkbox row with a
title/hint pair, and a trailing role="alert" error paragraph. Only the
copy and the testid differed. PR #1020 extracted the shared *hook*
(useOptimisticToggleSetting) but left the shell, and the chat surface
then added a fourth copy.

The new ToggleSettingCard is presentational only — it does not wrap a
hook, because chat-surface-settings deliberately uses a different one
(useChatSurfaceSetting, React Query-backed so the tab bar re-labels
without a reload).

Markup, class names and testids are byte-identical to what the three
files rendered before. Colocated tests cover the shell directly, since
settings-pane.test.tsx stubs all three modules.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@selfcontained
selfcontained merged commit 06b1c2e into main Sep 7, 2026
1 check passed
@selfcontained
selfcontained deleted the tech-debt/toggle-setting-card branch September 7, 2026 09:14
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.

1 participant