Skip to content

feat(onboarding): force reboot after internal boot setup and lock down wizard#1966

Open
Ajit-Mehrotra wants to merge 7 commits intomainfrom
claude/bold-gauss
Open

feat(onboarding): force reboot after internal boot setup and lock down wizard#1966
Ajit-Mehrotra wants to merge 7 commits intomainfrom
claude/bold-gauss

Conversation

@Ajit-Mehrotra
Copy link
Contributor

@Ajit-Mehrotra Ajit-Mehrotra commented Mar 26, 2026

Summary

When internal boot configuration is applied during onboarding, the system must reboot (or shut down) to finalize setup — regardless of whether the apply succeeded or failed. Previously, users could escape the reboot path via the X button, back navigation, browser back, a keyboard shortcut (Ctrl+Alt+Shift+O), or a URL bypass (?onboarding=bypass). This left the system in a potentially broken state if internal boot was partially configured but the user never rebooted.

Changes

Lockdown mechanism

  • Added internalBootApplyAttempted flag to the onboarding draft store (persisted to localStorage)
  • Flag is set true when the Summary step begins applying internal boot, before the API call — this engages the lockdown immediately
  • All escape hatches are gated on this flag via a single isInternalBootLocked computed

Wizard lockdown (OnboardingModal.vue)

  • X close button hidden when locked
  • Back button hidden on all steps when locked
  • handleExitIntent, goToPreviousStep — early return when locked
  • goToStep — blocks backward stepper clicks when locked
  • handlePopstate — calls window.history.forward() to neutralize browser back, with an isProgrammaticHistoryExit guard so the modal's own closeModal() history navigation isn't blocked
  • Removed keyboard shortcut Ctrl+Alt+Shift+O and URL parameter ?onboarding=bypass entirely (feature hasn't shipped)

Forced reboot/shutdown on success or failure (OnboardingNextStepsStep.vue)

  • showRebootButton now checks internalBootSelection !== null instead of internalBootApplySucceeded — reboot shows regardless of outcome
  • Added shutdown button alongside reboot as a secondary option (smaller, text-style) — mirrors the "Skip Setup" / "Get Started" CTA pattern
  • Shutdown calls the same server shutdown mutation and shows the same confirmation dialog pattern as reboot
  • Added failure alert: "Internal boot timed out but is likely setup on your server. Please reboot your system to finalize setup."
  • Added BIOS warning when updateBios was selected but apply failed — instructs user to manually update BIOS boot order
  • Made completeOnboarding() failure non-blocking for the reboot/shutdown path — wraps in try/catch so users are never stuck

Standalone lockdown (OnboardingInternalBoot.standalone.vue)

  • Same lockdown: X hidden, popstate blocked, dialog dismiss blocked
  • "Edit Again" disabled when locked
  • "Close" button becomes two buttons: "Shutdown" (secondary) and "Reboot" (primary) when locked
  • BIOS warning shown on failure with updateBios selected
  • Failure message uses same wording as wizard flow

Log message preservation (SummaryStep + composable)

  • returnedError and failed callbacks in applyInternalBootSelection preserve the original error output in the user-visible log stream
  • The generic "timed out / likely setup" message only appears on the Next Steps popup — the actual error details remain in the technical logs

i18n

  • 7 new keys in en.json for failure messaging, BIOS warnings, shutdown button, and dialog descriptions

Test plan

  • All tests pass (64 test files, 628+ tests)
  • Lint passes (ESLint + Prettier)
  • Type-check passes (vue-tsc)
  • Manual: start onboarding with internal boot eligible → select storage boot → apply → verify lockdown engages
  • Manual: simulate API failure → verify reboot AND shutdown buttons still show + failure messaging
  • Manual: verify browser back, keyboard shortcut, URL bypass all do nothing during lockdown
  • Manual: click Shutdown → confirm it calls shutdown mutation, not reboot
  • Manual: test standalone internal boot wizard with same scenarios
  • Manual: verify log console still shows specific error details on failure

Files changed (13)

File Change
store/onboardingDraft.ts Added internalBootApplyAttempted flag + setter + persistence
store/onboardingModalVisibility.ts Removed bypass shortcut, URL action, and bypassOnboarding()
OnboardingModal.vue Added lockdown gates on X, back, popstate (with programmatic exit guard), stepper, exit intent
composables/internalBoot.ts Restored original error output in returnedError/failed callbacks
steps/OnboardingSummaryStep.vue Set internalBootApplyAttempted before apply
steps/OnboardingNextStepsStep.vue Forced reboot on failure, shutdown button, failure alerts, BIOS warning, resilient finishOnboarding
standalone/OnboardingInternalBoot.standalone.vue Same lockdown + shutdown/reboot buttons + failure messaging
locales/en.json 7 new i18n keys
5 test files Updated/added tests for lockdown, shutdown, and failure behavior

Summary by CodeRabbit

  • New Features

    • Added Shutdown alongside Reboot for internal-boot recovery and a confirmation dialog for power actions.
    • Modal/result UI now shows Reboot/Shutdown actions when internal-boot is locked.
  • Improvements

    • Locked failure state removes “Edit Again” and blocks close/back/browser-back while internal boot is in progress.
    • Internal-boot timeout and BIOS-missed messaging updated with new localization keys.
    • Onboarding flow tracks an “apply attempted” flag to drive UI state.
  • Removed

    • Keyboard shortcut bypass for onboarding.

…n wizard

- When internal boot configuration is applied during onboarding, the system
  must reboot to finalize setup regardless of success or failure
- Previously, users could escape the reboot path via the X button, back
  navigation, browser back, keyboard shortcut, or URL bypass
- This left the system in a potentially broken state if internal boot was
  partially configured but never finalized with a reboot

Changes:
- Add `internalBootApplyAttempted` flag to draft store that persists to
  localStorage and engages lockdown once the Summary step begins applying
  internal boot configuration
- Hide X close button on the onboarding modal when lockdown is active
- Hide back button on all steps when lockdown is active
- Block browser back navigation (popstate) when lockdown is active
- Block backward stepper clicks and exit intent when lockdown is active
- Remove keyboard shortcut (Ctrl+Alt+Shift+O) and URL bypass
  (?onboarding=bypass) entirely as this feature has not shipped yet
- Force reboot button on Next Steps even when internal boot apply fails,
  with messaging: "Internal boot timed out but is likely setup on your
  server. Please reboot your system to finalize setup."
- Show BIOS manual update warning when BIOS update was selected but
  internal boot pool creation failed
- Make completeOnboarding() failure non-blocking for the reboot path so
  users are never stuck (can't close, can't go back, can't reboot)
- Apply the same lockdown to the standalone internal boot wizard: X hidden,
  popstate blocked, "Close" becomes "Reboot", failure messaging included
- Update tests across 5 test files with new lockdown behavior coverage
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 26, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds an internalBootApplyAttempted store flag and enforces a locked onboarding state during internal-boot operations: back/close/exit are blocked, history popstate is handled to prevent navigation, UI switches to reboot/shutdown flows with confirmations, and tests/refactors were added to exercise these locked flows. Also removes onboarding bypass wiring.

Changes

Cohort / File(s) Summary
Draft store
web/src/components/Onboarding/store/onboardingDraft.ts
Persist and expose internalBootApplyAttempted with setInternalBootApplyAttempted(), restore on deserialize, reset on draft reset.
Modal navigation & guards
web/src/components/Onboarding/OnboardingModal.vue, web/__test__/components/Onboarding/OnboardingModal.test.ts
Derive isInternalBootLocked; block previous-step/step navigation, exit-intent, and modal close while locked; handle popstate by pushing forward when locked. Tests assert hidden back/close and blocked exit flows.
Standalone internal-boot UI
web/src/components/Onboarding/standalone/OnboardingInternalBoot.standalone.vue, web/__test__/components/Onboarding/OnboardingInternalBootStandalone.test.ts
Set internalBootApplyAttempted(true) on submit, compute isLocked, show locked result with BIOS-specific message when applicable, replace close with reboot/shutdown actions, add InternalBootConfirmDialog, and wire power-action composable calls. Tests updated for locked UI and flows.
NextSteps step
web/src/components/Onboarding/steps/OnboardingNextStepsStep.vue, web/__test__/components/Onboarding/OnboardingNextStepsStep.test.ts
Gate reboot by internalBootSelection !== null; add internalBootFailed / biosUpdateMissed; unify finish flow to accept `action?: 'reboot'
Summary step
web/src/components/Onboarding/steps/OnboardingSummaryStep.vue, web/__test__/components/Onboarding/OnboardingSummaryStep.test.ts
Mark internalBootApplyAttempted(true) before starting apply/progress; tests loosen expected error string and assert the setter call.
internalBoot composable
web/src/components/Onboarding/composables/internalBoot.ts
Factor submitBootCommand(command); export submitInternalBootReboot() wrapper and add submitInternalBootShutdown() wrapper that posts cmd=shutdown.
Confirm dialog component
web/src/components/Onboarding/components/InternalBootConfirmDialog.vue
Add confirmation modal component (open, action, disabled props; confirm/cancel emits) used for reboot/shutdown confirmations.
Modal visibility store
web/src/components/Onboarding/store/onboardingModalVisibility.ts, web/__test__/store/onboardingModalVisibility.test.ts
Remove BYPASS mutation, bypassOnboarding() API, URL onboarding=bypass handling and keyboard-shortcut bypass listener; tests updated to drop bypass case.
Localization
web/src/locales/en.json
Add keys for shutdown/reboot/failure/BIOS-missed messages and tweak an existing reboot warning string.
Tests (various)
web/__test__/components/Onboarding/..., web/__test__/store/...
Switch test store wiring to reactive wrapper/bridge, add internalBootApplyAttempted to mocks, add/modify tests for locked UI, reboot/shutdown interactions, navigation blocking, and remove bypass test coverage.

Sequence Diagram(s)

sequenceDiagram
  participant UI as "Onboarding UI"
  participant Store as "onboardingDraftStore"
  participant Composable as "internalBoot composable"
  participant Browser as "window.history"

  UI->>Store: setInternalBootApplyAttempted(true)
  UI->>UI: enter locked state (isInternalBootLocked)
  UI->>Browser: push history / block back navigation
  Note right of UI: Back/Close/Exit intents suppressed while locked
  UI->>Composable: applyInternalBootSelection()
  alt apply success
    Store->>UI: set internalBootApplySucceeded(true)
    UI->>UI: proceed to normal completion/close
  else apply failure/timeout
    Store-->>UI: internalBootApplyAttempted remains true
    UI->>UI: show Reboot & Shutdown actions (confirm dialog)
    UI->>Composable: submitInternalBootReboot() or submitInternalBootShutdown()
  end
Loading

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly Related PRs

Poem

🐰 I set the flag and hold the door,
No backward hop, no step before.
If boots stall, I offer two: reboot or shut,
Confirm your choice — then out comes the nut. 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main change: forcing a reboot after internal boot setup and locking down the wizard to prevent exit. It aligns with the substantial changes across the codebase.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/bold-gauss

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov
Copy link

codecov bot commented Mar 26, 2026

Codecov Report

❌ Patch coverage is 89.75610% with 21 lines in your changes missing coverage. Please review.
✅ Project coverage is 52.08%. Comparing base (afe1ae6) to head (64daf69).

Files with missing lines Patch % Lines
web/src/components/Onboarding/OnboardingModal.vue 52.63% 9 Missing ⚠️
...src/components/Onboarding/store/onboardingDraft.ts 45.45% 6 Missing ⚠️
...g/standalone/OnboardingInternalBoot.standalone.vue 90.90% 5 Missing ⚠️
...nents/Onboarding/steps/OnboardingNextStepsStep.vue 98.76% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1966      +/-   ##
==========================================
+ Coverage   52.03%   52.08%   +0.05%     
==========================================
  Files        1030     1031       +1     
  Lines       71349    71461     +112     
  Branches     8057     8094      +37     
==========================================
+ Hits        37125    37222      +97     
- Misses      34100    34114      +14     
- Partials      124      125       +1     

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

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
web/__test__/components/Onboarding/OnboardingModal.test.ts (1)

484-490: Exercise the locked-exit path instead of rechecking initial render state.

This case never triggers the close path, so it won’t catch regressions in the @update:model-value guard or the locked popstate handling—it only reasserts that the X button is hidden. Emit the dialog close event or dispatch a PopStateEvent, then assert the exit dialog still does not open. As per coding guidelines, "Test component interactions such as clicks and inputs."

🤖 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 484
- 490, The test currently only asserts the X button is hidden but never
exercises the locked-exit path; modify the test to simulate the close attempt by
either emitting the component close event (emit 'update:model-value' with false
on the mounted component via wrapper.vm.$emit or wrapper.setProps) or
dispatching a PopStateEvent to window, then assert that the exit confirmation
still does not appear (text does not contain 'Exit onboarding?') while
onboardingDraftStore.internalBootApplyAttempted.value remains true; keep the use
of mountComponent(), the 'Close onboarding' button selector, and the same final
assertion to validate the guarded close 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/OnboardingModal.vue`:
- Around line 457-461: The popstate handler handlePopstate is currently
preventing programmatic history navigations used by closeModal when
isInternalBootLocked is true; add a short-circuit so handlePopstate ignores
popstate events triggered by our own closeModal (e.g., check a new sentinel like
a boolean flag isProgrammaticClose or a specific history.state marker set by
closeModal), and ensure closeModal sets and clears that sentinel around its
window.history.go(...) call; keep the existing behavior for real user
back/forward events by only bypassing the isInternalBootLocked guard when that
sentinel or state marker is present.

In `@web/src/components/Onboarding/steps/OnboardingSummaryStep.vue`:
- Around line 932-935: The current call to applyInternalBootSelection is
discarding the concrete error output and conflating the generic failed branch
with the timeout-like message; update the options passed to
applyInternalBootSelection so returnedError is a function that accepts the
backend output (e.g., returnedError: (output) => /* use output in the
message/log via summaryT/t */) and ensure failed maps to a distinct message (not
the timeout/likely-setup copy) so real backend errors are preserved in the
Summary console and logs; reference applyInternalBootSelection, returnedError,
failed, summaryT and t when making the change.

---

Nitpick comments:
In `@web/__test__/components/Onboarding/OnboardingModal.test.ts`:
- Around line 484-490: The test currently only asserts the X button is hidden
but never exercises the locked-exit path; modify the test to simulate the close
attempt by either emitting the component close event (emit 'update:model-value'
with false on the mounted component via wrapper.vm.$emit or wrapper.setProps) or
dispatching a PopStateEvent to window, then assert that the exit confirmation
still does not appear (text does not contain 'Exit onboarding?') while
onboardingDraftStore.internalBootApplyAttempted.value remains true; keep the use
of mountComponent(), the 'Close onboarding' button selector, and the same final
assertion to validate the guarded close behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: ba423f79-c5e4-4dc8-b9bb-8dcbd5a88483

📥 Commits

Reviewing files that changed from the base of the PR and between afe1ae6 and 2cd946a.

📒 Files selected for processing (12)
  • web/__test__/components/Onboarding/OnboardingInternalBootStandalone.test.ts
  • web/__test__/components/Onboarding/OnboardingModal.test.ts
  • web/__test__/components/Onboarding/OnboardingNextStepsStep.test.ts
  • web/__test__/components/Onboarding/OnboardingSummaryStep.test.ts
  • web/__test__/store/onboardingModalVisibility.test.ts
  • web/src/components/Onboarding/OnboardingModal.vue
  • web/src/components/Onboarding/standalone/OnboardingInternalBoot.standalone.vue
  • web/src/components/Onboarding/steps/OnboardingNextStepsStep.vue
  • web/src/components/Onboarding/steps/OnboardingSummaryStep.vue
  • web/src/components/Onboarding/store/onboardingDraft.ts
  • web/src/components/Onboarding/store/onboardingModalVisibility.ts
  • web/src/locales/en.json
💤 Files with no reviewable changes (1)
  • web/test/store/onboardingModalVisibility.test.ts

… boot setup

- Previously only a reboot button was available after internal boot
  configuration; some users may prefer to shut down instead (e.g., to
  physically relocate the server or swap drives before the next boot)
- Add a smaller "Shutdown" text button next to the primary "Reboot" CTA,
  mirroring the "Skip Setup" / "Get Started" visual pattern
- Extract shared `submitBootCommand()` helper in internalBoot.ts that
  accepts 'reboot' | 'shutdown', eliminating code duplication
- Add shutdown confirmation dialog with same warnings (flash drive,
  BIOS boot order) as the reboot dialog
- Shutdown path uses same `finishOnboarding()` flow: completeOnboarding
  mutation → cleanup → shutdown, with same resilience (proceeds even if
  completeOnboarding fails)
- Add shutdown button to standalone internal boot wizard as well
- Add 4 new i18n keys for shutdown UI
- Add 6 new tests covering shutdown visibility, confirmation flow, and
  resilience across both wizard and standalone components
@coderabbitai coderabbitai bot requested a review from elibosley March 26, 2026 18:02
…nd log messages

Issue 1 — Popstate lock traps programmatic closeModal():
- closeModal() uses window.history.go() for manual sessions, which fires
  a popstate event; the lockdown guard intercepted this and called
  history.forward(), trapping the user
- Add isProgrammaticHistoryExit ref that closeModal sets before calling
  history.go(), allowing handlePopstate to skip the lock for our own
  programmatic navigations

Issue 2 — Error messages lost in Summary console logs:
- The returnedError and failed messages in SummaryStep were remapped to
  the generic "timed out" i18n key, which dropped the specific error
  output from the technical log stream
- Restore original summaryT('logs.internalBootReturnedError', { output })
  and summaryT('logs.internalBootFailed') so the expandable console keeps
  specific error details
- The generic "timed out but likely setup" message correctly shows only
  on the Next Steps page alert, not in the detailed logs
@Ajit-Mehrotra Ajit-Mehrotra marked this pull request as ready for review March 26, 2026 18:29
@Ajit-Mehrotra Ajit-Mehrotra self-assigned this Mar 26, 2026
Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 410109faa1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

type="button"
data-testid="internal-boot-standalone-reboot"
class="bg-primary hover:bg-primary/90 rounded-md px-4 py-2 text-sm font-semibold text-white transition-colors"
@click="submitInternalBootReboot()"

Choose a reason for hiding this comment

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

P1 Badge Clear onboarding draft before standalone reboot/shutdown

In the locked standalone flow, the power buttons now call submitInternalBootReboot()/submitInternalBootShutdown() directly without first running cleanupOnboardingStorage(). Since internalBootApplyAttempted is persisted, this leaves the onboarding draft in localStorage after the power action and can cause later onboarding sessions to reopen in a locked, non-dismissible state (close/back/popstate are blocked off that flag). Mirror the Next Steps flow by clearing onboarding storage before submitting either power command.

Useful? React with 👍 / 👎.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
web/src/components/Onboarding/OnboardingModal.vue (1)

427-449: ⚠️ Potential issue | 🟠 Major

Dismiss or block an already-open exit dialog when the lock turns on.

Right now the lock only prevents opening the exit dialog. If showExitConfirmDialog is already true when internalBootApplyAttempted flips, nothing closes it, and handleExitConfirm() still runs closeModal(), which lets the user escape the reboot path after the lock is supposed to be active.

Suggested fix
 const handleExitIntent = () => {
   if (isClosingModal.value || isInternalBootLocked.value) {
     return;
   }
   showExitConfirmDialog.value = true;
 };
 
+watch(isInternalBootLocked, (locked) => {
+  if (locked) {
+    showExitConfirmDialog.value = false;
+  }
+});
+
 const handleExitConfirm = async () => {
+  if (isInternalBootLocked.value) {
+    showExitConfirmDialog.value = false;
+    return;
+  }
   showExitConfirmDialog.value = false;
   isClosingModal.value = true;
   try {
     await closeModal();
   } finally {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/components/Onboarding/OnboardingModal.vue` around lines 427 - 449,
When the internal boot lock (isInternalBootLocked) becomes true we must close or
block any already-open exit confirmation and prevent confirm actions; add a
watcher on isInternalBootLocked that sets showExitConfirmDialog.value = false
when it flips true, and update handleExitConfirm to no-op (return early) if
isInternalBootLocked.value is true to avoid calling closeModal while locked;
also ensure handleExitCancel similarly respects isInternalBootLocked (no-op) so
the dialog cannot be interacted with once the lock is active.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@web/src/components/Onboarding/OnboardingModal.vue`:
- Around line 427-449: When the internal boot lock (isInternalBootLocked)
becomes true we must close or block any already-open exit confirmation and
prevent confirm actions; add a watcher on isInternalBootLocked that sets
showExitConfirmDialog.value = false when it flips true, and update
handleExitConfirm to no-op (return early) if isInternalBootLocked.value is true
to avoid calling closeModal while locked; also ensure handleExitCancel similarly
respects isInternalBootLocked (no-op) so the dialog cannot be interacted with
once the lock is active.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: d30f4191-b78b-44c6-ac2c-70375ca6befc

📥 Commits

Reviewing files that changed from the base of the PR and between f94ace1 and 410109f.

📒 Files selected for processing (2)
  • web/src/components/Onboarding/OnboardingModal.vue
  • web/src/components/Onboarding/steps/OnboardingSummaryStep.vue
🚧 Files skipped from review as they are similar to previous changes (1)
  • web/src/components/Onboarding/steps/OnboardingSummaryStep.vue

…irmation to standalone

- The wizard flow (OnboardingNextStepsStep) had two nearly identical UModal
  blocks for reboot and shutdown confirmation, while the standalone flow
  had no confirmation dialog at all (direct submit on click)
- This inconsistency meant users could accidentally trigger a reboot or
  shutdown in the standalone wizard with a single click

Changes:
- Extract InternalBootConfirmDialog.vue shared component that accepts
  action ('reboot' | 'shutdown'), failed state, and emits confirm/cancel
- Replace both UModal blocks in OnboardingNextStepsStep with single
  shared dialog instance using pendingPowerAction ref
- Wire shared dialog into standalone component so reboot/shutdown buttons
  now show confirmation before proceeding
- Remove dead i18n keys (confirmShutdown.description, confirmShutdown.confirm)
  that duplicated confirmReboot equivalents
- Update standalone tests to click through confirmation dialog
- Standalone flow did not call cleanupOnboardingStorage() before form
  submit, so the internalBootApplyAttempted flag would persist in
  localStorage if the reboot/shutdown failed — causing a permanent
  lockout on page refresh
- Now calls cleanupOnboardingStorage() before submitting the form in
  the standalone confirm handler, matching the wizard flow behavior
- Change finishOnboarding signature from { reboot?: boolean; shutdown?:
  boolean } to { action?: 'reboot' | 'shutdown' } to make the options
  mutually exclusive by type
- Update confirmation dialog warning text to be generic ("fully
  restarted") instead of reboot-specific, so it reads correctly for
  both reboot and shutdown paths
- Update failure description to say "Restarting" instead of "Rebooting"
…d shutdown

- The confirmation dialog now shows identical content for both reboot and
  shutdown — same description, same warning, same confirm button text
- Only the title differs: "Confirm Reboot" vs "Confirm Shutdown"
- Removed `failed` prop from InternalBootConfirmDialog since the failure
  context is already shown via alert banners on the Next Steps page
- Removed unused `failureDescription` i18n key and `internalBootFailed`
  computed from standalone component
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 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/components/InternalBootConfirmDialog.vue`:
- Around line 25-29: The computed text keys currently hard-code reboot messages;
update the computed properties (e.g., description and the similar warning
computed around lines 46-50) to select message keys based on props.action (or
switch to a neutral shared key) instead of always using 'confirmReboot.*'; for
example, choose 'onboarding.nextSteps.confirmShutdown.*' when props.action ===
'shutdown' and 'onboarding.nextSteps.confirmReboot.*' otherwise, ensuring both
the failure/normal branches use the action-aware key selection.

In
`@web/src/components/Onboarding/standalone/OnboardingInternalBoot.standalone.vue`:
- Around line 60-68: The current handleConfirmPowerAction treats any
non-'shutdown' value (including null) as reboot; update handleConfirmPowerAction
so it first reads and clears pendingPowerAction.value, calls
cleanupOnboardingStorage(), then explicitly handle only 'shutdown' and 'reboot'
cases (call submitInternalBootShutdown() for 'shutdown' and
submitInternalBootReboot() for 'reboot'); for any other value (including null)
do nothing/no-op to avoid issuing an unintended reboot. Use the existing
pendingPowerAction.value, submitInternalBootShutdown, submitInternalBootReboot,
and cleanupOnboardingStorage identifiers to locate and update the logic.

In `@web/src/components/Onboarding/steps/OnboardingNextStepsStep.vue`:
- Around line 62-67: The failure computed currently flips as soon as a selection
exists because setInternalBootSelection() initializes
internalBootApplySucceeded=false; change the internalBootFailed computed to
require an actual apply attempt by checking
draftStore.internalBootApplyAttempted (e.g. internalBootApplyAttempted === true
&& !draftStore.internalBootApplySucceeded) instead of only selection !== null,
and keep biosUpdateMissed derived from that internalBootFailed computed
(biosUpdateMissed should remain internalBootFailed.value &&
(draftStore.internalBootSelection?.updateBios ?? false)); update references to
internalBootFailed and ensure setInternalBootSelection and any state
initialization still set internalBootApplyAttempted appropriately.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 1d1e5a28-09ad-4be5-91c9-d4789846c8b8

📥 Commits

Reviewing files that changed from the base of the PR and between 410109f and 52e9d20.

📒 Files selected for processing (5)
  • web/__test__/components/Onboarding/OnboardingInternalBootStandalone.test.ts
  • web/src/components/Onboarding/components/InternalBootConfirmDialog.vue
  • web/src/components/Onboarding/standalone/OnboardingInternalBoot.standalone.vue
  • web/src/components/Onboarding/steps/OnboardingNextStepsStep.vue
  • web/src/locales/en.json
🚧 Files skipped from review as they are similar to previous changes (1)
  • web/src/locales/en.json

Comment on lines +25 to +29
const description = computed(() =>
props.failed
? t('onboarding.nextSteps.confirmReboot.failureDescription')
: t('onboarding.nextSteps.confirmReboot.description')
);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

The shutdown confirmation still uses reboot-only messaging.

The failed description and warning are hard-wired to confirmReboot.*. On the shutdown path that tells users about restarting, which is misleading in the final forced-action step. Please branch those strings on action, or move them to neutral keys shared by both actions.

Also applies to: 46-50

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/components/Onboarding/components/InternalBootConfirmDialog.vue`
around lines 25 - 29, The computed text keys currently hard-code reboot
messages; update the computed properties (e.g., description and the similar
warning computed around lines 46-50) to select message keys based on
props.action (or switch to a neutral shared key) instead of always using
'confirmReboot.*'; for example, choose 'onboarding.nextSteps.confirmShutdown.*'
when props.action === 'shutdown' and 'onboarding.nextSteps.confirmReboot.*'
otherwise, ensuring both the failure/normal branches use the action-aware key
selection.

Comment on lines +60 to +68
const handleConfirmPowerAction = () => {
const action = pendingPowerAction.value;
pendingPowerAction.value = null;
cleanupOnboardingStorage();
if (action === 'shutdown') {
submitInternalBootShutdown();
} else {
submitInternalBootReboot();
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Do not treat a missing pending action as reboot.

pendingPowerAction is nullable, but anything other than 'shutdown' falls through to submitInternalBootReboot(). A stray/null confirm here becomes a destructive reboot request; guard null and branch explicitly on both valid actions.

Suggested guard
 const handleConfirmPowerAction = () => {
   const action = pendingPowerAction.value;
   pendingPowerAction.value = null;
+  if (!action) {
+    return;
+  }
   cleanupOnboardingStorage();
   if (action === 'shutdown') {
     submitInternalBootShutdown();
-  } else {
+  } else if (action === 'reboot') {
     submitInternalBootReboot();
   }
 };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const handleConfirmPowerAction = () => {
const action = pendingPowerAction.value;
pendingPowerAction.value = null;
cleanupOnboardingStorage();
if (action === 'shutdown') {
submitInternalBootShutdown();
} else {
submitInternalBootReboot();
}
const handleConfirmPowerAction = () => {
const action = pendingPowerAction.value;
pendingPowerAction.value = null;
if (!action) {
return;
}
cleanupOnboardingStorage();
if (action === 'shutdown') {
submitInternalBootShutdown();
} else if (action === 'reboot') {
submitInternalBootReboot();
}
};
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@web/src/components/Onboarding/standalone/OnboardingInternalBoot.standalone.vue`
around lines 60 - 68, The current handleConfirmPowerAction treats any
non-'shutdown' value (including null) as reboot; update handleConfirmPowerAction
so it first reads and clears pendingPowerAction.value, calls
cleanupOnboardingStorage(), then explicitly handle only 'shutdown' and 'reboot'
cases (call submitInternalBootShutdown() for 'shutdown' and
submitInternalBootReboot() for 'reboot'); for any other value (including null)
do nothing/no-op to avoid issuing an unintended reboot. Use the existing
pendingPowerAction.value, submitInternalBootShutdown, submitInternalBootReboot,
and cleanupOnboardingStorage identifiers to locate and update the logic.

- internalBootFailed computed was true as soon as a selection existed
  because internalBootApplySucceeded starts false, causing the failure
  alerts to briefly show before the apply even ran
- Now requires internalBootApplyAttempted to be true before reporting
  failure, so alerts only appear after the apply has actually been
  attempted
- Update tests to set internalBootApplyAttempted = true when testing
  failure states
@github-actions
Copy link
Contributor

This plugin has been deployed to Cloudflare R2 and is available for testing.
Download it at this URL:

https://preview.dl.unraid.net/unraid-api/tag/PR1966/dynamix.unraid.net.plg

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
web/src/components/Onboarding/standalone/OnboardingInternalBoot.standalone.vue (1)

59-68: ⚠️ Potential issue | 🟠 Major

Guard against null action to prevent unintended reboot.

Unlike the fix in OnboardingNextStepsStep.vue (lines 161-167), this handler doesn't guard against a null action. If pendingPowerAction is somehow cleared before the handler executes, the else branch will trigger submitInternalBootReboot() unintentionally. Additionally, cleanupOnboardingStorage() is called unconditionally even when no valid action exists.

Proposed fix
 const handleConfirmPowerAction = () => {
   const action = pendingPowerAction.value;
   pendingPowerAction.value = null;
+  if (!action) {
+    return;
+  }
   cleanupOnboardingStorage();
   if (action === 'shutdown') {
     submitInternalBootShutdown();
-  } else {
+  } else if (action === 'reboot') {
     submitInternalBootReboot();
   }
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@web/src/components/Onboarding/standalone/OnboardingInternalBoot.standalone.vue`
around lines 59 - 68, The handler handleConfirmPowerAction currently reads
pendingPowerAction.value, clears it, and always calls cleanupOnboardingStorage()
and either submitInternalBootShutdown() or submitInternalBootReboot(), which can
trigger an unintended reboot when action is null; modify
handleConfirmPowerAction so it first reads const action =
pendingPowerAction.value and if action is null/undefined simply return (do not
call cleanupOnboardingStorage or any submit*), otherwise then call
cleanupOnboardingStorage() and call submitInternalBootShutdown() only when
action === 'shutdown' else submitInternalBootReboot(); ensure you reference the
pendingPowerAction reactive, handleConfirmPowerAction, cleanupOnboardingStorage,
submitInternalBootShutdown, and submitInternalBootReboot symbols when making the
change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In
`@web/src/components/Onboarding/standalone/OnboardingInternalBoot.standalone.vue`:
- Around line 59-68: The handler handleConfirmPowerAction currently reads
pendingPowerAction.value, clears it, and always calls cleanupOnboardingStorage()
and either submitInternalBootShutdown() or submitInternalBootReboot(), which can
trigger an unintended reboot when action is null; modify
handleConfirmPowerAction so it first reads const action =
pendingPowerAction.value and if action is null/undefined simply return (do not
call cleanupOnboardingStorage or any submit*), otherwise then call
cleanupOnboardingStorage() and call submitInternalBootShutdown() only when
action === 'shutdown' else submitInternalBootReboot(); ensure you reference the
pendingPowerAction reactive, handleConfirmPowerAction, cleanupOnboardingStorage,
submitInternalBootShutdown, and submitInternalBootReboot symbols when making the
change.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: ed132c93-2443-4740-abdc-dd9e67669fc8

📥 Commits

Reviewing files that changed from the base of the PR and between 52e9d20 and 64daf69.

📒 Files selected for processing (5)
  • web/__test__/components/Onboarding/OnboardingNextStepsStep.test.ts
  • web/src/components/Onboarding/components/InternalBootConfirmDialog.vue
  • web/src/components/Onboarding/standalone/OnboardingInternalBoot.standalone.vue
  • web/src/components/Onboarding/steps/OnboardingNextStepsStep.vue
  • web/src/locales/en.json
🚧 Files skipped from review as they are similar to previous changes (3)
  • web/src/components/Onboarding/components/InternalBootConfirmDialog.vue
  • web/src/locales/en.json
  • web/test/components/Onboarding/OnboardingNextStepsStep.test.ts

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.

1 participant