refactor(onboarding): unify modal gating and force-open controls#1895
Conversation
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (29)
Disabled knowledge base sources:
WalkthroughThis pull request refactors the onboarding system's store architecture, renaming Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant Modal as OnboardingModal
participant ModalStore as onboardingModalStore
participant StatusStore as onboardingStore
participant UI as Components
User->>Modal: Visit page or trigger action
activate Modal
Modal->>ModalStore: Check isForceOpened
Modal->>StatusStore: Check shouldShowOnboarding
Modal->>ModalStore: Check isAutoVisible/isBypassActive
alt Force-opened
ModalStore-->>Modal: isForceOpened = true
Modal->>UI: Display modal
else Fresh install & not bypassed
StatusStore-->>Modal: shouldShowOnboarding = true
ModalStore-->>Modal: isAutoVisible = true, isBypassActive = false
Modal->>UI: Display modal
else Bypassed or not fresh install
Modal->>UI: Hide modal
end
User->>Modal: Complete onboarding
Modal->>ModalStore: clearForceOpened()
Modal->>ModalStore: setIsHidden()
Modal->>StatusStore: refetchOnboarding()
Modal->>UI: Close modal
deactivate Modal
sequenceDiagram
participant User
participant BootStep as OnboardingInternalBootStep
participant Query as Apollo Query
participant Backend as API
participant UI as Template
User->>BootStep: Mount component
activate BootStep
BootStep->>Query: GET_INTERNAL_BOOT_CONTEXT
Query->>Backend: Fetch array/disk/vars state
Backend-->>Query: Return boot context with interfaceType
Query-->>BootStep: Cache result
BootStep->>BootStep: Compute system eligibility codes<br/>(array stopped, boot transfer enabled, etc.)
BootStep->>BootStep: Aggregate disk eligibility codes<br/>per device
BootStep->>BootStep: Derive internalBootTransferState<br/>& bootEligibilityState
alt Eligible
BootStep->>UI: Show device options with sizes
BootStep->>UI: Hide eligibility panel
else Ineligible
BootStep->>UI: Show eligibility panel
BootStep->>UI: Display system & disk codes<br/>with localized messages
BootStep->>UI: Disable primary action
end
deactivate BootStep
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Rationale: This pull request involves substantial, heterogeneous changes across multiple systems: comprehensive store refactoring with renamed exports and altered API shapes affecting dozens of files; new eligibility tracking logic in the internal boot step with new type definitions; GraphQL schema extensions with backend support; and coordinated test updates across multiple test suites. While many changes follow consistent patterns (import updates), the diversity of affected domains (stores, components, GraphQL, backend services, localization) and the density of logic changes (force-open mechanism, eligibility aggregation, URL action handling) require careful verification that all wiring is correct and logic flows as intended. Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 5
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
web/__test__/components/Onboarding/OnboardingModal.test.ts (1)
214-221:⚠️ Potential issue | 🟡 MinorCover the blocked-display force-open path.
Both new
force-openedcases keepcanDisplayOnboardingModalattrue, so they never exercise the branch where normal modal gating blocks the dialog. That leaves the main override path in this refactor unverified.Suggested coverage
+ it('renders when force-opened even when modal display is blocked', () => { + activationCodeDataStore.isFreshInstall.value = false; + onboardingModalStoreState.isAutoVisible.value = false; + onboardingModalStoreState.isForceOpened.value = true; + onboardingStatusStore.canDisplayOnboardingModal.value = false; + + const wrapper = mountComponent(); + + expect(wrapper.find('[data-testid="dialog"]').exists()).toBe(true); + });Also applies to: 245-264
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/__test__/components/Onboarding/OnboardingModal.test.ts` around lines 214 - 221, The refactor added "force-opened" test cases that never cover the branch where normal gating blocks the modal because they leave onboardingStatusStore.canDisplayOnboardingModal true; add tests that set onboardingModalStoreState.isAutoVisible.value = true and onboardingStatusStore.shouldShowOnboarding.value = true but set onboardingStatusStore.canDisplayOnboardingModal.value = false while also exercising the force-opened cases (e.g., toggle the same force-open flag used in the existing tests), asserting that the dialog does NOT render; add analogous blocked-display assertions for the other force-opened test block referenced around lines 245-264 so the blocked-display path is covered for both variants.
🧹 Nitpick comments (1)
web/__test__/components/Onboarding/OnboardingModal.test.ts (1)
29-35: Let the exit tests prove the dialog actually closes.
clearForceOpenedandsetIsHiddenare pure spies here, so these assertions only confirm an implementation detail. If those stubs update the mocked state, the test can assert the dialog disappears after exit and stay focused on user-visible behavior.As per coding guidelines, "Test component behavior and output, not implementation details" and "Check component state through rendered output".
Also applies to: 375-376, 398-399, 420-421
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/__test__/components/Onboarding/OnboardingModal.test.ts` around lines 29 - 35, The tests currently assert that the spies onboardingModalStoreState.setIsHidden and onboardingModalStoreState.clearForceOpened were called, which verifies implementation details rather than user-visible behavior; update the tests so that when those spies are invoked they also mutate the mocked onboardingModalStoreState (e.g., set isAutoVisible.value to false or isForceOpened.value appropriately) or call the real handler that does so, then assert the dialog is removed from the rendered output (e.g., expect queryByText/queryByRole to be null) after the exit sequence to prove the dialog actually closes; apply the same change for the other occurrences mentioned (lines ~375-376, ~398-399, ~420-421) referencing the same onboardingModalStoreState/setIsHidden/clearForceOpened symbols.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@web/__test__/components/Onboarding/OnboardingModal.test.ts`:
- Around line 402-422: The test currently never enables the bypass flag so it
doesn't exercise the bypass-cleanup path: set
activationCodeDataStore.isBypassActive.value = true (or the appropriate setter
for isBypassActive) before calling mountComponent() in the test to enable bypass
behavior, then assert cleanupOnboardingStorageMock was called and mutateMock was
not; alternatively, if you intend to test the non-bypass force-open exit, rename
the test from "preserves bypass cleanup behavior..." to reflect the actual
scenario; update references to isBypassActive, activationCodeDataStore,
mountComponent, and the existing expectations accordingly.
In `@web/src/components/Onboarding/OnboardingModal.vue`:
- Around line 118-127: The computed showModal short-circuits on isLoginPage or
!canDisplayOnboardingModal before checking isForceOpened, preventing the manual
force-open path (forceOpenModal / onboarding=open / onboarding open event) from
working; fix by moving the isForceOpened check to run before the
login/canDisplay gate (i.e., return true if isForceOpened.value) or include
isForceOpened.value in the initial condition so the computed allows forced opens
even when canDisplayOnboardingModal is false, ensuring showModal respects manual
force-open actions.
In
`@web/src/components/Onboarding/standalone/OnboardingAdminPanel.standalone.vue`:
- Around line 368-375: The branch in openOnboardingModalFromPanel currently
calls onboardingModalStore.setIsHidden(false), which bypasses the fresh-install
gating because isAutoVisible becomes true whenever isHidden === false; change
this to restore the modal's automatic visibility mode instead of forcing it
visible — replace setIsHidden(false) with a call that clears the manual hidden
flag (e.g., onboardingModalStore.resetToAutomaticVisibility() or
onboardingModalStore.clearManualHidden()), and if that helper doesn't exist add
it to onboardingModalStore to unset the manual override so normal gating
(isAutoVisible) is respected; keep the simulateNormalRenderGatingOnOpen check
and use onboardingModalStore.forceOpenModal() only in the else path as before.
In `@web/src/components/Onboarding/store/onboardingModalVisibility.ts`:
- Around line 148-150: The current call to window.history.replaceState({}, '',
nextPath || '/') overwrites any existing history entry state; update the
replaceState call in onboardingModalVisibility where url.searchParams.delete and
nextPath are computed to pass the existing window.history.state (or null if
undefined) instead of {} so the current history state (router/scroll metadata)
is preserved when removing the ONBOARDING_QUERY_ACTION_PARAM from the URL.
---
Outside diff comments:
In `@web/__test__/components/Onboarding/OnboardingModal.test.ts`:
- Around line 214-221: The refactor added "force-opened" test cases that never
cover the branch where normal gating blocks the modal because they leave
onboardingStatusStore.canDisplayOnboardingModal true; add tests that set
onboardingModalStoreState.isAutoVisible.value = true and
onboardingStatusStore.shouldShowOnboarding.value = true but set
onboardingStatusStore.canDisplayOnboardingModal.value = false while also
exercising the force-opened cases (e.g., toggle the same force-open flag used in
the existing tests), asserting that the dialog does NOT render; add analogous
blocked-display assertions for the other force-opened test block referenced
around lines 245-264 so the blocked-display path is covered for both variants.
---
Nitpick comments:
In `@web/__test__/components/Onboarding/OnboardingModal.test.ts`:
- Around line 29-35: The tests currently assert that the spies
onboardingModalStoreState.setIsHidden and
onboardingModalStoreState.clearForceOpened were called, which verifies
implementation details rather than user-visible behavior; update the tests so
that when those spies are invoked they also mutate the mocked
onboardingModalStoreState (e.g., set isAutoVisible.value to false or
isForceOpened.value appropriately) or call the real handler that does so, then
assert the dialog is removed from the rendered output (e.g., expect
queryByText/queryByRole to be null) after the exit sequence to prove the dialog
actually closes; apply the same change for the other occurrences mentioned
(lines ~375-376, ~398-399, ~420-421) referencing the same
onboardingModalStoreState/setIsHidden/clearForceOpened symbols.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 4f419f8a-c52c-44f3-ac9d-3f296251c84a
📒 Files selected for processing (17)
web/__test__/components/Onboarding/OnboardingCoreSettingsStep.test.tsweb/__test__/components/Onboarding/OnboardingModal.test.tsweb/__test__/components/Onboarding/OnboardingOverviewStep.test.tsweb/__test__/components/Onboarding/OnboardingSummaryStep.test.tsweb/__test__/components/Onboarding/onboardingStorageCleanup.test.tsweb/__test__/store/onboardingModalVisibility.test.tsweb/src/components/DevModalTest.standalone.vueweb/src/components/Onboarding/OnboardingModal.vueweb/src/components/Onboarding/UPGRADE_ONBOARDING.mdweb/src/components/Onboarding/constants.tsweb/src/components/Onboarding/standalone/OnboardingAdminPanel.standalone.vueweb/src/components/Onboarding/steps/OnboardingCoreSettingsStep.vueweb/src/components/Onboarding/steps/OnboardingOverviewStep.vueweb/src/components/Onboarding/steps/OnboardingSummaryStep.vueweb/src/components/Onboarding/store/onboardingModalVisibility.tsweb/src/components/Onboarding/store/onboardingStatus.tsweb/src/components/Onboarding/store/onboardingStorageCleanup.ts
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #1895 +/- ##
==========================================
+ Coverage 49.83% 50.63% +0.79%
==========================================
Files 1021 1021
Lines 70037 70262 +225
Branches 7421 7565 +144
==========================================
+ Hits 34901 35575 +674
+ Misses 35013 34564 -449
Partials 123 123 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
|
addressing coderabbit, testing briefly, then should be good for another round of PR review |
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
api/src/unraid-api/graph/resolvers/customization/onboarding.service.ts (1)
785-812:⚠️ Potential issue | 🟠 MajorValidate
caseModelbefore writing it tocase-model.cfg.This branch now persists
branding.caseModelafter only atrim(). A malformed activation payload can therefore save an invalid model token straight into the live config, which is a risky sink for data that should really be constrained to the case-model format you expect downstream.Possible hardening
- const configuredCaseModel = this.activationData.branding?.caseModel?.trim() ?? ''; + const rawCaseModel = this.activationData.branding?.caseModel?.trim() ?? ''; + const configuredCaseModel = /^[A-Za-z0-9._-]{1,128}$/.test(rawCaseModel) + ? rawCaseModel + : ''; + + if (rawCaseModel && !configuredCaseModel) { + this.logger.warn('Ignoring invalid case-model value from activation data.'); + }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@api/src/unraid-api/graph/resolvers/customization/onboarding.service.ts` around lines 785 - 812, The code writes activationData.branding.caseModel (configuredCaseModel) directly to case-model.cfg after only trimming; update the onboarding flow (the method using configuredCaseModel in onboarding.service.ts that also calls this.replaceTargetWithSource, fileExists and writes to this.caseModelCfg) to validate and sanitize configuredCaseModel before persisting: require it to match a strict pattern (e.g. only alphanumerics, dot, underscore, hyphen, no path separators), enforce max length, and if you expect filenames or specific extensions validate those (or require a token format), then use path.basename(safeValue) and only write when the value passes validation; if validation fails, log a warning and skip writing to this.caseModelCfg.
🧹 Nitpick comments (2)
api/src/unraid-api/graph/resolvers/customization/onboarding.service.spec.ts (1)
951-969: Loosen the logger assertions in these tests.These expectations are pinned to exact log text, so harmless wording changes will break the tests even if the case-model behavior is still correct.
Suggested test tightening
- expect(loggerLogSpy).toHaveBeenCalledWith(`Case model set to mid-tower in ${caseModelCfg}`); + expect(loggerLogSpy).toHaveBeenCalledWith( + expect.stringContaining('Case model set to mid-tower') + ); - expect(loggerLogSpy).toHaveBeenCalledWith( - 'No partner case-model configured in activation code, skipping case model setup.' - ); + expect(loggerLogSpy).toHaveBeenCalledWith( + expect.stringContaining('skipping case model setup') + );Based on learnings: Test what the code does, not implementation details like exact error message wording - avoid writing tests that break when minor changes are made to error messages, log formats, or other non-essential details.
Also applies to: 1501-1503
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@api/src/unraid-api/graph/resolvers/customization/onboarding.service.spec.ts` around lines 951 - 969, The test pins logger text too tightly; in the applyCaseModelConfig tests (referencing applyCaseModelConfig, loggerLogSpy, caseModelCfg, fs.writeFile) replace the exact-string assertion on loggerLogSpy with a looser check: assert the logger was called and that the message contains the important fragment (e.g., contains 'Case model' and the model name) or use a generic toHaveBeenCalled() so the test verifies behavior (fs.writeFile called with caseModelCfg and 'mid-tower') without depending on exact wording or formatting of the log.web/src/components/Onboarding/standalone/OnboardingAdminPanel.standalone.vue (1)
142-145: The stock partner preset still masks the newcaseModelfallback.
genericBrandingConfigsetscaseModelImagealongsidecaseModel, so the shared preset keeps exercising the custom-image path instead of the built-in case-model path described in the reference table. Consider leavingcaseModelImageunset in one default preset so this new field is actually testable from the panel.Also applies to: 1031-1040
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/src/components/Onboarding/standalone/OnboardingAdminPanel.standalone.vue` around lines 142 - 145, The genericBrandingConfig preset is providing both caseModel and caseModelImage which prevents the UI from falling back to the built-in case-model renderer; update the preset (the object named genericBrandingConfig of type BrandingConfigPayload) to omit or set caseModelImage to null/undefined so the component will exercise the caseModel fallback path, and do the same for the other default preset referenced around the second occurrence to ensure one preset tests the built-in case-model behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@web/src/components/Onboarding/standalone/OnboardingAdminPanel.standalone.vue`:
- Around line 370-377: openOnboardingModalFromPanel currently calls
onboardingModalStore.forceOpenModal(), which only mutates transient in-memory
state and is lost on hard reload; change the flow to persist the "force open"
intent so the modal survives reloads: update openOnboardingModalFromPanel (and
the duplicate block around lines 432-441) to set a persistent flag (e.g.,
onboardingModalStore.setPersistentForceOpen(true) or write a short-lived key to
localStorage/sessionStorage) before triggering the modal open, and update
onboardingModalStore initialization to check that persistent flag on load (clear
it once handled) and open the modal accordingly instead of relying solely on
forceOpenModal(). Ensure you reference onboardingModalStore.forceOpenModal,
onboardingModalStore.setIsHidden, and openOnboardingModalFromPanel when making
the changes.
---
Outside diff comments:
In `@api/src/unraid-api/graph/resolvers/customization/onboarding.service.ts`:
- Around line 785-812: The code writes activationData.branding.caseModel
(configuredCaseModel) directly to case-model.cfg after only trimming; update the
onboarding flow (the method using configuredCaseModel in onboarding.service.ts
that also calls this.replaceTargetWithSource, fileExists and writes to
this.caseModelCfg) to validate and sanitize configuredCaseModel before
persisting: require it to match a strict pattern (e.g. only alphanumerics, dot,
underscore, hyphen, no path separators), enforce max length, and if you expect
filenames or specific extensions validate those (or require a token format),
then use path.basename(safeValue) and only write when the value passes
validation; if validation fails, log a warning and skip writing to
this.caseModelCfg.
---
Nitpick comments:
In `@api/src/unraid-api/graph/resolvers/customization/onboarding.service.spec.ts`:
- Around line 951-969: The test pins logger text too tightly; in the
applyCaseModelConfig tests (referencing applyCaseModelConfig, loggerLogSpy,
caseModelCfg, fs.writeFile) replace the exact-string assertion on loggerLogSpy
with a looser check: assert the logger was called and that the message contains
the important fragment (e.g., contains 'Case model' and the model name) or use a
generic toHaveBeenCalled() so the test verifies behavior (fs.writeFile called
with caseModelCfg and 'mid-tower') without depending on exact wording or
formatting of the log.
In
`@web/src/components/Onboarding/standalone/OnboardingAdminPanel.standalone.vue`:
- Around line 142-145: The genericBrandingConfig preset is providing both
caseModel and caseModelImage which prevents the UI from falling back to the
built-in case-model renderer; update the preset (the object named
genericBrandingConfig of type BrandingConfigPayload) to omit or set
caseModelImage to null/undefined so the component will exercise the caseModel
fallback path, and do the same for the other default preset referenced around
the second occurrence to ensure one preset tests the built-in case-model
behavior.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: b1066f02-3964-467c-a4cc-fb5c1f3422c3
📒 Files selected for processing (9)
api/generated-schema.graphqlapi/src/unraid-api/graph/resolvers/customization/activation-code.model.tsapi/src/unraid-api/graph/resolvers/customization/onboarding.service.spec.tsapi/src/unraid-api/graph/resolvers/customization/onboarding.service.tsapi/src/unraid-api/graph/resolvers/onboarding/onboarding.model.tsweb/src/components/Onboarding/graphql/activationCode.query.tsweb/src/components/Onboarding/standalone/OnboardingAdminPanel.standalone.vueweb/src/composables/gql/gql.tsweb/src/composables/gql/graphql.ts
| const openOnboardingModalFromPanel = () => { | ||
| if (simulateNormalRenderGatingOnOpen.value) { | ||
| onboardingModalStore.setIsHidden(false); | ||
| return; | ||
| } | ||
|
|
||
| onboardingModalStore.forceOpenModal(); | ||
| }; |
There was a problem hiding this comment.
Hard refresh drops the force-open path.
openOnboardingModalFromPanel() defaults to forceOpenModal(), but this flow reloads the page immediately afterward when Reset Draft + Hard Refresh on Open is enabled. Since forceOpenModal() only flips transient store state, upgrade/downgrade/completed presets fall back to normal gating after the reload and the modal no longer opens.
💡 One way to preserve the open intent across the reload
+const PENDING_FORCE_OPEN_AFTER_REFRESH_KEY =
+ 'onboardingAdminPanel.pendingForceOpenAfterRefresh';
+
onMounted(() => {
if (typeof window === 'undefined') return;
resetDraftAndHardRefreshOnOpen.value = localStorage.getItem(RESET_DRAFT_AND_REFRESH_KEY) === 'true';
simulateNormalRenderGatingOnOpen.value =
localStorage.getItem(SIMULATE_NORMAL_RENDER_GATING_KEY) === 'true';
+
+ if (localStorage.getItem(PENDING_FORCE_OPEN_AFTER_REFRESH_KEY) === 'true') {
+ localStorage.removeItem(PENDING_FORCE_OPEN_AFTER_REFRESH_KEY);
+ void nextTick(() => onboardingModalStore.forceOpenModal());
+ }
});
@@
await applyOverrides();
- await nextTick();
- openOnboardingModalFromPanel();
if (resetDraftAndHardRefreshOnOpen.value) {
+ if (!simulateNormalRenderGatingOnOpen.value && typeof window !== 'undefined') {
+ localStorage.setItem(PENDING_FORCE_OPEN_AFTER_REFRESH_KEY, 'true');
+ }
await hardRefreshPageBestEffort();
+ return;
}
+
+ await nextTick();
+ openOnboardingModalFromPanel();Also applies to: 432-441
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@web/src/components/Onboarding/standalone/OnboardingAdminPanel.standalone.vue`
around lines 370 - 377, openOnboardingModalFromPanel currently calls
onboardingModalStore.forceOpenModal(), which only mutates transient in-memory
state and is lost on hard reload; change the flow to persist the "force open"
intent so the modal survives reloads: update openOnboardingModalFromPanel (and
the duplicate block around lines 432-441) to set a persistent flag (e.g.,
onboardingModalStore.setPersistentForceOpen(true) or write a short-lived key to
localStorage/sessionStorage) before triggering the modal open, and update
onboardingModalStore initialization to check that persistent flag on load (clear
it once handled) and open the modal accordingly instead of relying solely on
forceOpenModal(). Ensure you reference onboardingModalStore.forceOpenModal,
onboardingModalStore.setIsHidden, and openOnboardingModalFromPanel when making
the changes.
- Purpose: simplify onboarding visibility rules and make manual opening reliable for all server states while preserving fresh-install auto onboarding behavior. - Before: modal visibility mixed legacy activation-code naming, upgrade/downgrade auto-show assumptions, and open paths that only flipped hidden state. - Before: admin preset Open could fail for upgrade/downgrade states because non-fresh installs were still blocked by normal render gating. - Why this was a problem: behavior differed by entry path, debugging was expensive, and local/admin test workflows could not reliably validate non-fresh-install scenarios. - What changed: renamed onboarding stores/files to onboarding-specific names, removed legacy aliases/shims, and centralized visibility semantics around fresh-install auto show plus explicit force-open. - What changed: added explicit force-open handling in store/modal flows (URL action/event/store action), and ensured force-open exits do not mark onboarding complete. - What changed: admin panel Open actions now use a shared open helper and support an Extra Settings toggle to simulate normal gating instead of force-open for negative-path testing. - What changed: moved onboarding constants out of global consts into an onboarding-local constants module and updated all imports/tests. - How it works: auto visibility stays fresh-install only (INCOMPLETE plus local guards), bypass remains session+boot scoped, and manual open uses forceOpenModal() unless normal-gating simulation is enabled. - Verification: ran onboarding-focused Vitest coverage for modal visibility/store/cleanup and broader onboarding component suites; tests passed.
- Purpose: surface every internal-boot blocker the web client can currently detect during onboarding. - Before: the Setup Boot step collapsed failures into generic unavailable states and did not explain per-disk exclusions. - Problem: users could fix one issue only to hit the next blocker later, and USB-backed disks were not called out before submission. - Now: the step aggregates server-level and disk-level eligibility codes, excludes assigned/USB/too-small disks from selection, and keeps labels concise when the option id matches the device name. - How: query disk interface type for onboarding context, derive diagnostics in the step component, regenerate GraphQL artifacts, and add focused onboarding step tests.
…date admin panel to use it, and preserve history state during URL cleanup.
1038449 to
b882bbe
Compare
|
This plugin has been deployed to Cloudflare R2 and is available for testing. |
Summary by CodeRabbit
New Features
Changes