Split updates-section.tsx into focused subcomponents - #949
Merged
Conversation
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.
Uh oh!
There was an error while loading. Please reload this page.
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
apps/web/src/components/app/updates-section.tsxwas 530 lines rendering six logically independent UI regions from a singleUpdatesSectionexport. All of its state already lived in theuseReleaseUpdateshook, so every region was pure props-driven markup — the low-risk case for a subcomponent split.Why it was a candidate
assistedPreferred/forceRequired.New file structure
updates-section.tsxupdates-check-panel.tsxUpdateActionsupdates-version-card.tsxupdates-preferences.tsxupdates-force-confirm-dialog.tsxupdates-reload-card.tsxThe IIFE became
isAssistedPreferred/isForceRequiredin 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.tsxis the sole consumer and its import is unchanged, sinceUpdatesSectionkept 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. Alldata-testidattributes preserved.Validation
pnpm run finalize:web— greenpnpm run test:e2e— 180 passed, 12 skippedrelease-utils.test.ts— 37 passed (30 existing + 7 new)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-infois 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.