Skip to content

NAS-141049 / 27.0.0-BETA.1 / Migrate storage / pool-manager wizard to tn-* - #13868

Open
AlexKarpov98 wants to merge 22 commits into
masterfrom
NAS-141049
Open

NAS-141049 / 27.0.0-BETA.1 / Migrate storage / pool-manager wizard to tn-*#13868
AlexKarpov98 wants to merge 22 commits into
masterfrom
NAS-141049

Conversation

@AlexKarpov98

Copy link
Copy Markdown
Contributor
Screen.Recording.2026-07-27.at.13.57.16.mov

@AlexKarpov98 AlexKarpov98 self-assigned this Jul 27, 2026
@bugclerk bugclerk changed the title NAS-141049: Migrate storage / pool-manager wizard to tn-* NAS-141049 / 27.0.0-BETA.1 / Migrate storage / pool-manager wizard to tn-* Jul 27, 2026
@bugclerk

Copy link
Copy Markdown
Contributor

@claude

claude Bot commented Jul 27, 2026

Copy link
Copy Markdown

Claude finished @AlexKarpov98's task in 6m 17s —— View job


Review

Really nice work on the shared-utility extraction here 🎉 — translatedSignal(), tnSelectLabels, and tn-select-option-test-id.constant.ts each replace a pattern that was being re-derived per component, and the TnFormControlHarness + unreadableControl sentinel is a genuinely thoughtful answer to "half-migrated form, one indexed lookup." The a11y deltas are real wins too (div (click)tn-icon-button [ariaLabel], .link-button as an actual <button>, 1-based row positions so repeated remove buttons stop sharing a name), and the FormArray version-counter → derived-signal rewrite in unlock-sed-disks is a solid cleanup.


🚩 The library bump is buried in a mislabeled commit, and its blast radius is much bigger than this PR

ed260e3 "NAS-141979: Update" moves @truenas/ui-components from ~0.3.26 to ~0.4.8 (package.json:118). That's load-bearing — f7fe11a's own message says "the library change is unreleased — this needs the published version bumped before it can merge" — but three things about it need attention:

  • 1267 files in src/ import @truenas/ui-components; this PR touches 100. A minor bump of the design system silently re-skins every already-migrated area (shares-dashboard, audit, apps, dashboard widgets). If something regresses there, git bisect lands on a storage-migration PR titled "Migrate storage / pool-manager wizard to tn-*".
  • The commit prefix is NAS-141979, not the branch's NAS-141049, and the message is Update. Per CLAUDE.md the format is NAS-<issue>: <description>.
  • Strong suggestion: split the bump into its own PR (or at minimum its own commit with a real message enumerating what 0.4.x changes), and land it first. Then this PR is purely the storage migration.

Fix this →

🚩 The bump wasn't followed by a sweep — several workarounds now self-document as removable

tn-checkbox-change.utils.ts:11 says, verbatim: "Drop this helper — and the guards that call it — once the dependency range moves past 0.4.0." The range is now ~0.4.8, so that condition is met in this very PR. The helper and its single call site (pool-warnings.component.ts:82) should go.

Same staleness in the prose, which now describes a version the repo no longer pins:

  • tn-checkbox-change.utils.ts:4 — "the pinned @truenas/ui-components (0.3.26)"
  • unlock-sed-disks.component.ts:93 and disk-size-selects.component.ts:70 — "in the pinned 0.3.26 only tn-input, tn-select, tn-autocomplete and tn-chip-input consume TN_FORM_FIELD_CONTEXT"
  • SKILL.md — the defects table header reads "package.json currently ~0.3.26", and the component-map row still says "verified in 0.3.26"

The TN_FORM_FIELD_CONTEXT one matters beyond documentation: both call sites fold the hint into the checkbox's [label], i.e. into its accessible name, and each carries a paragraph explaining that this is a deliberate name-vs-description downgrade forced by 0.3.26. If 0.4.8 wires tn-checkbox up to the field context, that trade-off is no longer necessary and the folded name is now just verbose for screen-reader users. Worth re-validating against the installed 0.4.8 rather than inheriting the 0.3.26 conclusion. (I couldn't check the library internals myself — see the note at the bottom.)

Fix this →

⚠️ The tn-radio-group test IDs hand-craft the prefix the playbook says not to hand-craft

All three call sites pass the prefix inside the base:

[testId]="['radio-button', 'encryption-type']"

This only resolves to the legacy radio-button-* because composeTestId's idempotent guard skips a prefix the base already starts with — and the playbook update in this same PR explicitly warns against relying on that ("it survives only because of the idempotent guard", plus "pass the bare semantic base"). It's structurally the same problem as the link-*button-* shift, for which you prescribe host-level tnTestIdType="link" [tnTestId]="…" pinning. Either apply that remedy here (tnTestIdType="radio-button"), or add a defects-table/component-map row so the next migrator reading the playbook doesn't see this as a contradiction.

🧹 Dead code and a duplicated translated string

Removing ix-modal-header from import-pool.component.html was right (the panel host supplies the title), but it left two loose ends:

  • helptextImport.title in helptext/storage/volumes/volume-import-wizard.ts:4 now has no readers — verified by grep across src/app.
  • The title it used to supply is hard-coded as a literal in pools-dashboard.component.ts:104: title: this.translate.instant('Import Pool'). Prefer this.translate.instant(helptextImport.title) so the string stays in one place.

Also minor: import-pool.component.ts keeps both protected readonly helptext = helptextImport (template reads helptext.selectPool) and a separate protected readonly poolLabel = helptextImport.poolLabel. Pick one — either promote selectPool to its own field or read helptext.poolLabel in the template.

Fix this →

💭 Small simplification

draid-selection.component.ts:199-206 — the if (!nextOptions.length) branch is just the no-options case of the else if (!nextOptions.some(...)) check below it, and unsetControlIfNoMatchingOption already handles an empty list. Collapsing to a single unsetControlIfNoMatchingOption(...) followed by the default-to-0 guard would read more directly.


The rest holds up well under scrutiny: no leftover mat-* / Mat* / @angular/material anywhere in the changed code, the removed resource-card mixin has no surviving callers, ad-hoc ::ng-deep in storage-health-card was correctly folded into shared tn-card-inline-title / tn-details-item mixins, the panel title survives via the host so dropping ix-modal-header isn't a regression, and availableDiskCountForException's blank-row accounting is correct across the cases I traced. The spectator.component.submit() / canSubmit() spec calls are fine — those are SidePanelForm's host-facing API, not private internals.

Note on verification: node_modules isn't present in this checkout and I don't have permission to run yarn install, so I could not run yarn lint, the specs, or inspect the installed 0.4.8 to confirm which of the documented defects it actually fixes. The WebUI CI run was still queued when I looked — please make sure it goes green on the bumped version before merge, since that's the only signal covering the other ~1160 library consumers.
· Branch: NAS-141049

@codecov

codecov Bot commented Jul 27, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 96.91877% with 11 lines in your changes missing coverage. Please review.
✅ Project coverage is 87.72%. Comparing base (a5610f7) to head (ed260e3).
⚠️ Report is 12 commits behind head on master.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
...ge/components/import-pool/import-pool.component.ts 90.00% 2 Missing ⚠️
...ool/unlock-sed-disks/unlock-sed-disks.component.ts 94.11% 2 Missing ⚠️
...orms/ix-forms/testing/control-harnesses.helpers.ts 96.00% 1 Missing ⚠️
...ponents/dashboard-pool/dashboard-pool.component.ts 90.90% 1 Missing ⚠️
...orage-health-card/storage-health-card.component.ts 88.88% 1 Missing ⚠️
...nts/tier-config-form/tier-config-form.component.ts 93.33% 1 Missing ⚠️
...-selection-vdev/manual-selection-vdev.component.ts 50.00% 1 Missing ⚠️
...omponents/pool-warnings/pool-warnings.component.ts 95.00% 1 Missing ⚠️
...ponents/pool-manager/tests/pool-manager.harness.ts 75.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master   #13868      +/-   ##
==========================================
+ Coverage   87.63%   87.72%   +0.09%     
==========================================
  Files        1895     1904       +9     
  Lines       71860    72058     +198     
  Branches     9142     9206      +64     
==========================================
+ Hits        62972    63214     +242     
+ Misses       8888     8844      -44     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

AlexKarpov98 and others added 17 commits July 27, 2026 14:21
…ration

Narrow the indexControlsByLabel guard to unlabelled controls only: repeated
labelled controls (ix-list rows) are legitimate and were breaking iscsi-wizard,
target-form and apps-settings specs.

Claude-Session: https://claude.ai/code/session_01SUZQL1eT6RcmdRR59eYxpY
…ation

- indexControlsByLabel drops the ambiguous '' key instead of throwing, so a
  form with two unlabelled controls no longer fails lookups for its labelled
  siblings.
- Share formControlHarnessTypes/indexFormControls between PoolManagerHarness
  and the draid spec's local form facade.
- tn-radio-group restores focus to the checked option after a model write
  recreates the radios; document the reference-equality guard and the
  validator-free invariant on the shared inner control.
- Replace the hand-rolled role="button" vdev delete affordance with
  tn-icon-button.
- Restore the details-item label/link styling storage-health-card lost when
  details-card() was swapped for tn-detail-card.
- Align the pools-dashboard tn-empty icon with the rest of the app (56px).

Claude-Session: https://claude.ai/code/session_01SUZQL1eT6RcmdRR59eYxpY
…ation

- tn-radio-group: fold the index into the @for track key so options whose
  values stringify alike no longer collide (NG0955); covered by a new spec.
- manual-selection-vdev: add a `position` input so the delete button's
  data-test and accessible name are unique per vdev card.
- manual-selection-disk-filters: give the label-less search input an
  accessible name via [ariaLabel].
- disk-size-selects: record why the null re-emission in updateOptions() must
  stay unconditional (guarding it breaks unsetting-on-fewer-disks.spec.ts).
- tn-migration playbook: correct the tn-menu-item test-id prefix (button-*,
  not menu-item-*) and document that tn-* form controls fall back to the
  bound control name, both verified against @truenas/ui-components 0.3.26.

Claude-Session: https://claude.ai/code/session_01SUZQL1eT6RcmdRR59eYxpY
…gration

- unlock-sed-disks: scope each exception row's test ids and the remove
  button's accessible name by row position, so repeated rows no longer
  resolve to the same data-test or the same aria-label.
- tn-radio-group: resolve an absent accessible name to null so the group
  omits aria-label instead of rendering an empty one; fold the per-option
  test id into renderItems so it is computed once instead of on every
  change-detection pass.
- TnFormControlHarness: document the supported control set and throw a
  specific error from setValue when the field holds something else.

Claude-Session: https://claude.ai/code/session_01LoyKkDU26j8Z8Hgar3ghVW
@AlexKarpov98
AlexKarpov98 marked this pull request as ready for review July 30, 2026 10:50
@AlexKarpov98
AlexKarpov98 requested a review from a team as a code owner July 30, 2026 10:50
@AlexKarpov98
AlexKarpov98 requested review from william-gr and removed request for a team July 30, 2026 10:50
@william-gr

Copy link
Copy Markdown
Member

Shouldnt tn-radio-group be part of the components library?

AlexKarpov98 and others added 2 commits August 3, 2026 12:51
…radio-group

@truenas/ui-components now ships TnRadioGroupComponent, where the group is the
single ControlValueAccessor, so the shim's destroy-and-recreate machinery and
focus restoration — written only to work around a per-option accessor going
stale — is no longer needed. The library group additionally gives the set an
accessible name via aria-labelledby, an inferred required indicator, and a
rendered validation message, none of which the shim could do.

Resolved data-test values are unchanged: the pre-migration bases are passed
through verbatim, so options still resolve to radio-button-<base>-<label>.

Drops the two now-fixed radio rows from the playbook's upstream-defects table
in favour of a component-map entry.

Note the library change is unreleased — this needs the published version bumped
before it can merge.

Claude-Session: https://claude.ai/code/session_01LoyKkDU26j8Z8Hgar3ghVW

@william-gr william-gr left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great job, could not find any immediate issues.
Might be worth verifying the test comment from claude.

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.

3 participants