Skip to content

Split updates-section.tsx into focused subcomponents - #949

Merged
selfcontained merged 1 commit into
mainfrom
agt_8e74d2aa0226/job-componentizer-f20829cc
Aug 13, 2026
Merged

Split updates-section.tsx into focused subcomponents#949
selfcontained merged 1 commit into
mainfrom
agt_8e74d2aa0226/job-componentizer-f20829cc

Conversation

@selfcontained

Copy link
Copy Markdown
Owner

What

apps/web/src/components/app/updates-section.tsx was 530 lines rendering six logically independent UI regions from a single UpdatesSection export. All of its state already lived in the useReleaseUpdates hook, so every region was pure props-driven markup — the low-risk case for a subcomponent split.

Why it was a candidate

  • Well over the ~300-line threshold.
  • Six distinct UI regions in one render tree (version, notes, channel, auto-update, check/update flow, reload, plus a dialog).
  • An inline IIFE in the JSX computing assistedPreferred / forceRequired.

New file structure

FileLinesContents
updates-section.tsx130composition shell — hook, early returns, layout
updates-check-panel.tsx214check control, inline progress, errors, migration/assisted gates, UpdateActions
updates-version-card.tsx106current version + collapsible release notes
updates-preferences.tsx105release channel + automatic updates
updates-force-confirm-dialog.tsx68assisted-gate bypass confirmation
updates-reload-card.tsx61reload split button

The IIFE became isAssistedPreferred / isForceRequired in the existingrelease-utils.ts (per the PR #913 lesson about extending the sibling utils module rather than adding a second one), covered by 7 new unit tests.

No barrel file — settings-pane.tsx is the sole consumer and its import is unchanged, since UpdatesSection kept its name and path.

Behavior

Purely structural. No hook changed owner (all state stays in useReleaseUpdates), so neither the PR #803 state-lifetime hazard nor the PR #870 effect-order hazard applies. All data-testid attributes preserved.

Validation

  • pnpm run finalize:web — green
  • pnpm run test:e2e — 180 passed, 12 skipped
  • release-utils.test.ts — 37 passed (30 existing + 7 new)
  • Playwright pass on a seeded dev stack covering the base state (version card, expanded release notes markdown, channel toggle, auto-update select, reload dropdown) and the populated update-available state (update banner, GitHub link, PendingMigrationsGate, AssistedUpdateGate, split action button, and the force-confirm dialog opened from "More update options"). The populated state has no HTTP write path — /api/v1/release/cached-info is served from server memory — so it was seeded by injecting the snapshot into the React Query cache.

Queued next

pins-panel.tsx (973 lines) is now the largest component in the app by a wide margin, having grown with the batch-pin/collapsible-groups work in #946.

updates-section.tsx was 530 lines rendering six independent UI regions
from a single export. All of its state already lived in the
useReleaseUpdates hook, so every region was pure props-driven markup —
a clean subcomponent split with no hook changing owner.
- updates-version-card.tsx (106): current version + collapsible notes
- updates-preferences.tsx (105): release channel + automatic updates
- updates-check-panel.tsx (214): check control, progress, gates, actions
- updates-reload-card.tsx (61): reload split button
- updates-force-confirm-dialog.tsx (68): assisted-gate bypass dialog
updates-section.tsx is now a 130-line composition shell. The inline IIFE
computing assistedPreferred/forceRequired became isAssistedPreferred and
isForceRequired in the existing release-utils.ts, with unit tests.
Purely structural — rendered output and behavior are unchanged.
@selfcontained
selfcontained merged commit 670e4f3 into mainAug 13, 2026
1 check passed
@selfcontained
selfcontained deleted the agt_8e74d2aa0226/job-componentizer-f20829cc branch August 13, 2026 08:13
selfcontained added a commit that referenced this pull request Aug 17, 2026
* Cover the updates section's assisted-update safety routing
PR #949 split updates-section.tsx into five subcomponents, and nothing
mounted any of them: release-utils.test.ts covers the pure predicates
(isForceRequired, isAssistedPreferred, describeForceTriggers) but not
which UI they drive. The decision that a gated release must route the
standard updater through a confirmation spans UpdatesCheckPanel ->
UpdateActions -> UpdatesForceConfirmDialog, so the test mounts
UpdatesSection with useReleaseUpdates stubbed and drives the real
subcomponents.
Covers the paths where a regression breaks an install: a required or
migration-gated release demotes the standard update into the menu and
opens the force confirmation instead of running it, confirming forces
the update, and a migration-evaluation error warns without gating. Also
pins the wiring a component split is most likely to scramble — the
force flag on the confirmed update, plain vs cache-clearing reload, the
channel and auto-update handlers — and the two in-flight-update
takeovers that hide the controls.
Mutation battery: 27 mutants, 23 killed. The survivors are the two
saving-state affordances, which exist only as a Tailwind class with no
disabled/aria-busy for jsdom to observe, and OperationTakeover's phase
order, which belongs to that component's own coverage.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Review fixes: restore scrollIntoView, narrow the preferences test name
Deleting the scrollIntoView stub in afterEach keeps the mock out of any
later test file sharing the worker — jsdom does not define it at all, so
restoring means removing it. The preferences test was named as
persistence coverage but useReleaseUpdates is stubbed, so it only pins
that each control reports the value it was clicked with; renamed to say
that. Kept rather than deleted: it is what kills the mutants hardcoding
onChannelChange("stable") and onAutoUpdateModeChange("check").
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for freeto 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

@selfcontained