Skip to content

fix: consistently clear onboarding draft#1916

Open
Ajit-Mehrotra wants to merge 5 commits intomainfrom
codex/onboarding-draft-exit
Open

fix: consistently clear onboarding draft#1916
Ajit-Mehrotra wants to merge 5 commits intomainfrom
codex/onboarding-draft-exit

Conversation

@Ajit-Mehrotra
Copy link
Contributor

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

Problem

We found that onboardingDraft cleanup was not consistently tied to the user's actual onboarding lifecycle.

The original undesirable case was in the summary/apply flow:

  • the user could finish applying settings from the summary step
  • completeOnboarding() could fail because the API was unavailable or the tracker write failed
  • in that branch, the local onboardingDraft was treated differently from other explicit exit paths

We also found a second explicit-exit gap after that:

  • if internal boot had been configured, the Next Steps screen exposed a reboot button
  • that reboot path submitted the reboot form without clearing onboarding draft state first
  • onboardingDraft could survive even though the user had intentionally left onboarding by rebooting

That meant draft lifecycle still depended on which exit branch the user took, instead of a simple UX rule based on whether they were still in onboarding or had intentionally left it.

Desired Behavior

This PR aligns the flow to the rule we agreed on:

  • enter / resume / in-progress => keep onboardingDraft
  • explicit exit for any non-passive reason => clear onboardingDraft
  • refresh / crash / passive interruption => keep onboardingDraft

In practice, local draft cleanup should have nothing to do with whether applying settings or marking onboarding complete succeeds, and every explicit exit path should clear draft state.

What Changed

  • removed onboardingDraft cleanup from the summary apply path
  • kept the summary step focused on applying settings, attempting completeOnboarding(), and showing result state while the user remains in the flow
  • updated the overview skip path to clear onboarding storage immediately when the user chooses to exit, before attempting completion/refetch
  • updated the internal-boot reboot confirmation path to clear onboarding storage immediately before submitting the reboot form
  • added tests to lock in the summary, skip, modal, and reboot behavior

Resulting Flow

After this change:

  • applying from the summary step no longer clears the draft while the user is still inside onboarding
  • explicit skip/exit clears the draft even if completeOnboarding() fails afterward
  • the internal-boot reboot button now clears the draft before rebooting
  • passive interruptions still preserve the draft for resume scenarios
  • modal exit behavior remains consistent with the existing explicit-exit cleanup model

Why This Is Better

This keeps the behavior aligned with the agreed spec while staying simple:

  • local draft state now follows user intent, not backend mutation outcome
  • retry/failure handling in summary no longer changes whether draft data survives
  • explicit abandon, skip, close, and reboot paths all clear draft state
  • the reboot fix is direct and local instead of introducing extra exit abstractions

Verification

Ran the targeted onboarding test suite:

pnpm exec vitest run __test__/components/Onboarding/OnboardingNextStepsStep.test.ts __test__/components/Onboarding/OnboardingSummaryStep.test.ts __test__/components/Onboarding/OnboardingOverviewStep.test.ts __test__/components/Onboarding/OnboardingModal.test.ts __test__/components/Onboarding/onboardingStorageCleanup.test.ts __test__/store/onboardingModalVisibility.test.ts

Summary by CodeRabbit

  • Tests

    • Added tests covering onboarding storage cleanup in skip, reboot, successful apply, and failed completion scenarios.
  • Bug Fixes

    • Cleanup of temporary onboarding session state now runs when the user skips setup or confirms a reboot, and no longer runs after a successful completion; cleanup still occurs if completion fails.

- Purpose: decouple onboarding draft cleanup from summary apply success so draft lifecycle follows explicit user exit paths.
- Before: the summary step cleared onboardingDraft only after completeOnboarding succeeded, while overview skip and modal exit used different cleanup timing.
- Problem: local draft persistence depended on API completion behavior instead of whether the user actually stayed in or left onboarding.
- Change: remove draft cleanup from the summary apply path and clear immediately when overview skip triggers an explicit exit.
- How it works: summary now preserves onboardingDraft while the flow continues to the result dialog and next step, and overview skip clears storage before attempting completion so failures do not keep stale draft state around.
- Verification: added focused tests covering successful summary apply, failed summary completion, and explicit skip cleanup behavior.
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 15, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 32fc8bc2-e3e5-44ac-83b1-241c19739671

📥 Commits

Reviewing files that changed from the base of the PR and between bdadd9f and 95d12b2.

📒 Files selected for processing (3)
  • web/__test__/components/Onboarding/OnboardingOverviewStep.test.ts
  • web/src/components/Onboarding/steps/OnboardingNextStepsStep.vue
  • web/src/components/Onboarding/steps/OnboardingOverviewStep.vue
🚧 Files skipped from review as they are similar to previous changes (2)
  • web/src/components/Onboarding/steps/OnboardingOverviewStep.vue
  • web/src/components/Onboarding/steps/OnboardingNextStepsStep.vue

Walkthrough

Moves onboarding storage cleanup earlier for skip and reboot flows and removes the post-completion cleanup from the successful apply path. Tests were added/updated and module mocks hoisted to expose cleanupOnboardingStorage for assertions across skip, reboot, and completion scenarios. (50 words)

Changes

Cohort / File(s) Summary
Skip / Overview
web/src/components/Onboarding/steps/OnboardingOverviewStep.vue, web/__test__/components/Onboarding/OnboardingOverviewStep.test.ts
Calls cleanupOnboardingStorage({ clearTemporaryBypassSessionState: true }) immediately when skipping; tests added to assert cleanup runs even if skip completion rejects.
Reboot / Next Steps
web/src/components/Onboarding/steps/OnboardingNextStepsStep.vue, web/__test__/components/Onboarding/OnboardingNextStepsStep.test.ts
Imports and invokes cleanupOnboardingStorage({ clearTemporaryBypassSessionState: true }) before submitting reboot; tests assert the cleanup call on confirm.
Completion / Summary
web/src/components/Onboarding/steps/OnboardingSummaryStep.vue, web/__test__/components/Onboarding/OnboardingSummaryStep.test.ts
Removed the post-success cleanup from the successful apply path; tests mock and assert cleanupOnboardingStorage is not called on successful apply and remain observable on failure.
Modal props consolidation
web/src/components/Onboarding/OnboardingModal.vue
Removed explicit case handling for SUMMARY and NEXT_STEPS in props selection so they fall back to default/base props.
Mocks / test wiring
web/__test__/components/Onboarding/*
Hoisted cleanupOnboardingStorageMock into shared test fixtures and updated the mocked module .../onboardingStorageCleanup to export cleanupOnboardingStorage mapped to that mock for assertions across tests.

Sequence Diagram(s)

sequenceDiagram
  participant User as User
  participant UI as Onboarding UI
  participant Storage as onboardingStorageCleanup
  participant API as Server/API

  User->>UI: Click "Skip setup" or "Confirm reboot"
  UI->>Storage: cleanupOnboardingStorage({ clearTemporaryBypassSessionState: true })
  Storage-->>UI: ACK
  UI->>API: completeOnboarding / submitReboot / refetchOnboarding
  API-->>UI: success or failure
  alt completion failure
    UI->>Storage: (cleanup already executed earlier)
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 I hopped in quick and gave a sweep,
Cleared drafts before the system took a leap.
Skip or reboot — tidy and bright,
Completion rests without that bite.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main objective of the PR — making onboarding draft cleanup explicit and consistent across exit paths, which is the central change across multiple components and tests.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch codex/onboarding-draft-exit
📝 Coding Plan
  • Generate coding plan for human review comments

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 15, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 50.98%. Comparing base (c3e9767) to head (95d12b2).
⚠️ Report is 2 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1916      +/-   ##
==========================================
+ Coverage   50.96%   50.98%   +0.02%     
==========================================
  Files        1023     1024       +1     
  Lines       70556    70554       -2     
  Branches     7687     7709      +22     
==========================================
+ Hits        35956    35971      +15     
+ Misses      34476    34457      -19     
- Partials      124      126       +2     

☔ 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.

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

146-165: Consider using a more robust selector for the skip button.

Using wrapper.findAll('button')[1] is fragile and may break if button order changes. Consider finding the button by its text content or a semantic attribute.

♻️ Suggested improvement
  it('clears onboarding draft immediately when skipping setup', async () => {
    const wrapper = mountComponent();

-   await wrapper.findAll('button')[1]?.trigger('click');
+   const skipButton = wrapper.findAll('button').find((btn) => btn.text().includes('Skip'));
+   await skipButton?.trigger('click');

    expect(cleanupOnboardingStorageMock).toHaveBeenCalledWith({
      clearTemporaryBypassSessionState: true,
    });
  });

  it('still clears onboarding draft when skip completion fails', async () => {
    completeOnboardingMock.mockRejectedValueOnce(new Error('offline'));
    const wrapper = mountComponent();

-   await wrapper.findAll('button')[1]?.trigger('click');
+   const skipButton = wrapper.findAll('button').find((btn) => btn.text().includes('Skip'));
+   await skipButton?.trigger('click');

    expect(cleanupOnboardingStorageMock).toHaveBeenCalledWith({
      clearTemporaryBypassSessionState: true,
    });
  });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/__test__/components/Onboarding/OnboardingOverviewStep.test.ts` around
lines 146 - 165, The tests use a fragile index selector
wrapper.findAll('button')[1] to click the skip button; update both tests
('clears onboarding draft immediately when skipping setup' and 'still clears
onboarding draft when skip completion fails') to select the skip button more
robustly (e.g., find by visible text or a semantic selector) — replace the
index-based find with either wrapper.findAll('button').find(b =>
b.text().includes('Skip')) or, preferably, add/use a stable attribute like
data-testid="skip-setup-button" on the skip button in the component and call
wrapper.find('[data-testid="skip-setup-button"]').trigger('click') so the tests
reliably target the intended button (make sure to update both test cases and, if
adding an attribute, the component template).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@web/__test__/components/Onboarding/OnboardingOverviewStep.test.ts`:
- Around line 146-165: The tests use a fragile index selector
wrapper.findAll('button')[1] to click the skip button; update both tests
('clears onboarding draft immediately when skipping setup' and 'still clears
onboarding draft when skip completion fails') to select the skip button more
robustly (e.g., find by visible text or a semantic selector) — replace the
index-based find with either wrapper.findAll('button').find(b =>
b.text().includes('Skip')) or, preferably, add/use a stable attribute like
data-testid="skip-setup-button" on the skip button in the component and call
wrapper.find('[data-testid="skip-setup-button"]').trigger('click') so the tests
reliably target the intended button (make sure to update both test cases and, if
adding an attribute, the component template).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 641c7f8f-d403-44ef-9798-19871e0c143a

📥 Commits

Reviewing files that changed from the base of the PR and between c3e9767 and 614f060.

📒 Files selected for processing (4)
  • web/__test__/components/Onboarding/OnboardingOverviewStep.test.ts
  • web/__test__/components/Onboarding/OnboardingSummaryStep.test.ts
  • web/src/components/Onboarding/steps/OnboardingOverviewStep.vue
  • web/src/components/Onboarding/steps/OnboardingSummaryStep.vue
💤 Files with no reviewable changes (1)
  • web/src/components/Onboarding/steps/OnboardingSummaryStep.vue

@Ajit-Mehrotra Ajit-Mehrotra changed the title fix: clear onboarding draft only on explicit exit fix: consistently clear onboarding draft Mar 15, 2026
- Purpose: make the internal-boot reboot action follow the same explicit exit path as other onboarding exits.
- Before: the Next Steps reboot confirmation submitted a reboot form directly from the step component and bypassed the modal's shared exit cleanup flow.
- Problem: onboardingDraft could survive a reboot-triggered exit even though the user intentionally left onboarding.
- Change: add a reboot-aware exit callback in the modal and have Next Steps request reboot through that shared exit path.
- How it works: the modal now supports an after-close action, cleans onboarding storage through closeModal, and then triggers the reboot submission.
- Verification: added Next Steps coverage and reran the focused onboarding Vitest suite, including modal, summary, overview, cleanup, visibility, and Next Steps tests.
- Purpose: replace the reboot-specific modal exit callback with direct draft cleanup in the Next Steps reboot path.
- Before: reboot was routed through extra modal callback plumbing so it shared the closeModal exit machinery.
- Problem: that approach worked, but it added indirection for a simple requirement and made the flow harder to follow.
- Change: remove the reboot-specific modal abstraction and clear onboarding storage directly when the reboot confirmation is accepted.
- How it works: the Next Steps reboot handler now calls onboarding storage cleanup and then submits the reboot form, while the modal returns to its simpler generic close behavior.
- Verification: reran the focused onboarding Vitest suite covering Next Steps, modal, summary, overview, storage cleanup, and modal visibility.
- Purpose: simplify currentStepProps in OnboardingModal now that Summary and Next Steps use the default base props.
- Before: the switch kept separate SUMMARY and NEXT_STEPS branches that each returned the same baseProps object.
- Problem: this duplication added noise without changing behavior.
- Change: remove the redundant branches and let both steps fall through to the default return.
- How it works: the modal still passes the same common props to both steps, just without explicit duplicate cases.
- Verification: reran the targeted modal, next steps, and summary Vitest suite.
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.

🧹 Nitpick comments (1)
web/src/components/Onboarding/steps/OnboardingNextStepsStep.vue (1)

96-100: Consider removing unnecessary async keyword.

The function is marked async but nothing is awaited inside it. Both cleanupOnboardingStorage and submitInternalBootReboot are synchronous calls. While this doesn't cause issues, removing async would be slightly cleaner.

♻️ Optional: Remove async keyword
-const handleConfirmReboot = async () => {
+const handleConfirmReboot = () => {
   showRebootWarningDialog.value = false;
   cleanupOnboardingStorage({ clearTemporaryBypassSessionState: true });
   submitInternalBootReboot();
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/components/Onboarding/steps/OnboardingNextStepsStep.vue` around lines
96 - 100, The function handleConfirmReboot is declared async but contains no
awaits; remove the unnecessary async keyword from its declaration to keep the
code clean and avoid misleading callers; locate the handleConfirmReboot function
(it toggles showRebootWarningDialog.value and calls cleanupOnboardingStorage and
submitInternalBootReboot) and change its declaration to a plain synchronous
function, ensuring no await usage remains or is added unintentionally.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@web/src/components/Onboarding/steps/OnboardingNextStepsStep.vue`:
- Around line 96-100: The function handleConfirmReboot is declared async but
contains no awaits; remove the unnecessary async keyword from its declaration to
keep the code clean and avoid misleading callers; locate the handleConfirmReboot
function (it toggles showRebootWarningDialog.value and calls
cleanupOnboardingStorage and submitInternalBootReboot) and change its
declaration to a plain synchronous function, ensuring no await usage remains or
is added unintentionally.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: b5940370-7599-455f-998c-769c2da09b5a

📥 Commits

Reviewing files that changed from the base of the PR and between 614f060 and bdadd9f.

📒 Files selected for processing (3)
  • web/__test__/components/Onboarding/OnboardingNextStepsStep.test.ts
  • web/src/components/Onboarding/OnboardingModal.vue
  • web/src/components/Onboarding/steps/OnboardingNextStepsStep.vue
💤 Files with no reviewable changes (1)
  • web/src/components/Onboarding/OnboardingModal.vue

- Purpose: resolve the remaining CodeRabbit nitpicks on the onboarding draft cleanup PR.
- Before: overview tests used an index-based button selector and the reboot confirm handler was marked async without awaiting anything.
- Problem: the test selector was fragile to markup order changes, and the async modifier implied behavior that the handler did not have.
- Change: add a stable test id for the overview skip button, update the tests to use it, and remove the unnecessary async keyword from the reboot confirm handler.
- How it works: the overview tests now target the intended button directly and the reboot handler remains a plain synchronous function with unchanged behavior.
- Verification: reran targeted Vitest coverage for the overview and next-steps onboarding files while working each nit one at a time.
@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/PR1916/dynamix.unraid.net.plg

@Ajit-Mehrotra Ajit-Mehrotra marked this pull request as ready for review March 16, 2026 00:43
@Ajit-Mehrotra Ajit-Mehrotra requested a review from elibosley March 16, 2026 00:43
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: 95d12b2d10

ℹ️ 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".

it('clears onboarding draft immediately when skipping setup', async () => {
const wrapper = mountComponent();

await wrapper.find('[data-testid="skip-setup-button"]').trigger('click');

Choose a reason for hiding this comment

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

P1 Badge Use semantic selector for skip button tests

/workspace/api/AGENTS.md (Vue Component Testing) says to use semantic queries like find('button') instead of data-test IDs, but this change introduces find('[data-testid="skip-setup-button"]') and requires a matching data-testid in production markup. Keeping this pattern will propagate test-only attributes and brittle selector conventions, so the test should target the skip action semantically and avoid the test-id dependency.

Useful? React with 👍 / 👎.

Copy link
Member

@elibosley elibosley left a comment

Choose a reason for hiding this comment

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

There's still too much frontend lifecycle state around the onboarding for me to be fully comfortable, but this is a good fix.

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