harvester/harvester-ui-extension#1218 by wheatdog, head d73d8aa (unchanged since round two), reviewed 2026-11-10 with Cody's process (my-pr-full-review at 5f9619b) and ours, then merged. Nothing is posted.
Preview: harvesterpr1218r built from d73d8aa, backend the real Harvester .105 on v1.10.0-rc1. Baseline harvestermainr rebuilt from main ee91524 (the upstream tip), same backend. Every save is held in the browser; nothing was written to .105.
The control does what the issue asks, on a real v1.10.0-rc1 server: Disabled by default, both choices save as real booleans with the rest of the setting kept, Use the default value resets it, and older clusters hide it and keep a value set with kubectl. Both passes found the same one real problem; ours adds an optional test nit. Nothing blocks.
feature-flags.js, so a push is coming, and this repo dismisses approvals on push (Yu-Jack's was dismissed at the last force-push). An approval now would be lost. Post the two comments, approve after the rebase.require/doc.docs.harvesterhci.io/v1.10/advanced/index#upgrade-config, which is the current docs page that PR edits.feature-flags.js: main's componentHealth and this PR's flag land on the same line of the v1.10.0 list. Keep both. Mergify asked the author on 11-09.default instead of value.d73d8aa.Three annotated clips on the same real v1.10.0-rc1 server, light theme, same click path: Advanced > Settings > upgrade-config > ⋮ > Edit Setting. The caption band says what each step shows.
"shutdownVMOnMigrationFailure": true.The same flow as stills:











| Cody's process (Pass A) | Ours (Pass B) | |
|---|---|---|
| Verdict | His process ends at pending comments; the verdict is left to the reviewer. | Approve, A-. |
| Findings kept | 1: the unnamed radio group. | 2: the unnamed radio group, and the test gap (nit). Plus one question, dropped in the merge. |
| Checked by both | Default Disabled; Enabled and Disabled save booleans; other fields kept; Use the default value; hidden on v1.9.1 with a kubectl value kept; keyboard; the conflict. | Same. |
| Only this pass | Before/after demo pair plus the v1.9.1 clip; an 8-criterion acceptance table sourced from the thread; found that the page's docs link leads to the section docs#1170 adds; dark theme; his a11y probe in both themes; axe. | Seven mutants (the test gap); view mode is read-only; a hand-set string "true"; the aria-label fix proven with a unit test; lint; console errors on both builds; a 16-cell coverage grid. |
| Missed or wrong | The test gap: test coverage is not one of his default passes, by design. | Q1 rested on a misreading of QA's failure on 12172 (an edit to default, not a mid-upgrade change). |
| Output | 3 clips (13.5 s, 42 s, 20 s), 11 stills, criteria and verification files, 1 comment taken through his 7 comment phases. | review, coverage, checked and draft files; 1 clip (32 s); 9 images. |
| Cost | About 28 minutes in this session, after setup; it started warm with two earlier rounds of context. | 17 minutes, 76 tool calls, about 234k tokens; a fresh agent that read the rules and the code from zero. |
<label> above the group isn't tied to it, and the RadioGroup gets no label or aria-label. Chromium's accessibility tree on the PR build shows two radiogroups in a row with no name, each holding just "Enabled" and "Disabled": Restore VM's (already on main) and this new one.aria-label: RadioGroup forwards it to the role="radiogroup" element, and nothing changes visually. Pass B proved it with a unit test ([null,null] at the head, the label text with the attribute). The extension's rancher-cluster form already names its group this way, through label.ariaSnapshot.

parseDefaultValue and calls update() itself. Of seven deliberate breakages, two still pass all five tests: dropping @update:value on the new group (the choice would never reach the saved value) and binding it to restoreVM by a copy slip.update:value from the group's stub and check the saved JSON: it passes on the real code and catches both (Pass B ran it).
default, which the upgrade reset; the author's reply corrects that, and the re-run passed. Beyond that, docs#1170 says the value is read once and changes are ignored mid-upgrade, and the form already links to that section. Round two asked for a help line on the same grounds and is dropped for the same reason.| Pass | Result |
|---|---|
| Demos | Before (main) and after (PR) on the same cluster and theme, plus the v1.9.1 case. The issue has no bug to reproduce; its "before" is the missing control. |
| Base review | Nothing beyond the passes. The method-not-computed comment is accurate (normalizeValue runs in data()). |
| 1. Acceptance criteria | 8 criteria from the thread: 3 stated, 3 agreed in discussion, 2 implied. Backend, restart and webhook marked not in this PR. |
| 2. Acceptance verify | 8 of 8 met, 11 live checks (the first three runs stopped on errors in my script, not the PR). |
| 3. Simplify | Nothing. |
| 4. Side effects | Nothing: the key, flag and method are read only by this form; the conflict is Mergify's to chase. |
| 5. Component reuse | No new component. |
| 6. Design fidelity | No design linked (the author's "latest design" isn't linked anywhere). |
| 7. Accessibility | His probe, both themes: only shared-component hits that Restore VM on main has too (its focus-outline hit is a false positive, a ring is drawn). AX tree: the new group has no name, filed. axe 4.10.2: 0 violations. |
| Comment phases | Verify (holds), refinement, final pass, remove suggestions (the fix pointer came out), condense (194 characters), preexisting (introduced: a copied pattern in new code). 1 comment survives. |
| Criterion | Asked for in | Verdict | |
|---|---|---|---|
| AC1 | Control shown on v1.10.0+ | backend PR: "the form needs a control" | Met |
| AC2 | Disabled when the field is missing | PR test step 3; backend default false | Met |
| AC3 | Toggles both ways, saves a boolean | PR test step 4; backend test plan step 5 | Met |
| AC4 | Other fields kept on save | docs#1170 and the author on 12172: a value replaces default entirely | Met |
| AC5 | Hidden before v1.10.0 | Author on 12172; docs#1170 | Met |
| AC6 | A kubectl value survives a save on older clusters | Author on 12172 (implied) | Met |
| AC7 | Use the default value resets to Disabled | Backend default (implied) | Met |
| AC8 | Light and dark | Implied for a visual change | Met |
Open question in the criteria file: whether the form should explain the option is not settled anywhere in the thread; the docs PR and the existing docs link carry it.
Fresh agent, not shown Pass A. Our checklist, evidence kind on every finding, the coverage grid, and Marcelo's review rules. 2 suggestions, 1 optional question (dropped in the merge).

| File | correctness | tests | a11y | i18n | conventions | errors | perf |
|---|---|---|---|---|---|---|---|
| upgrade-config.vue | cleared | S2 | S1 | cleared | cleared | cleared | cleared |
| upgrade-config.test.js | cleared | S2 | n/a | n/a | cleared | n/a | n/a |
| feature-flags.js | cleared | cleared | n/a | n/a | cleared | n/a | n/a |
| en-us.yaml | cleared | n/a | n/a | cleared | cleared | n/a | n/a |
:mode, so it stays clickable in view mode; the PR's new group is read-only there.normalizeValue reads the computed nodesOptions in data(), the trap the PR's comment explains.
| Round two | Now |
|---|---|
| Suggestion: nothing explains what each choice does; keep one help line. | Dropped. docs#1170 (approved) explains all of it, and the form's description already links to that section. |
| Nit: the save test bypasses the radio. | Still open, re-measured by Pass B with seven mutants; kept as an optional nit. |
| Left out: the unnamed radio group, since it copies Restore VM. | Now in. Both passes flagged it on their own, and Cody's rule counts a pattern copied into new code as introduced. It's the second identical unnamed group in a row. |
| Outside note: backend PR 12125's description still lists "set the field to true" as an unblock. | Still stale there, but docs#1170 now says plainly that changing it mid-upgrade doesn't unblock. |
Two inline comments and a short body, in your shape. No preview links. GitHub copies of the clips have the URL band cropped off.
I tried this on a real v1.10.0-rc1 cluster next to main. The control is Disabled by default, Enabled and Disabled both save as booleans with the rest of the setting kept, Use the default value resets it, and on v1.9.1 the control is hidden and a value set with kubectl survives a save. One accessibility point and an optional test nit inline. Before this change: [before-main.mp4] With this change: [after-pr.mp4]
Screen readers get no name for this group. The bold label above it isn't tied to it, so it's announced as just "Enabled / Disabled", the same as the Restore VM group right above it, and the two can't be told apart. Passing the label text to this RadioGroup as its `aria-label` would name it without changing how it looks.

Nit, optional: this test sets `parseDefaultValue` and calls `update()` itself, so it never goes through the radio group. Removing `@update:value` from the new group, or binding it to `restoreVM` by mistake, still passes all five tests. Emitting `update:value` from the group's stub would catch both.

Submit as Comment, then approve once the conflict is fixed: the push that fixes it would dismiss an approval given now. The second comment is optional. Cody's process would post only the first one, without the fix clause.
ariaSnapshot on both builds, axe 4.10.2 (0 violations), a real Tab walk to the group.feature-flags.js, keep both lines.ee91524 = upstream tip, traefik, 200 with TLS verified.