Skip to content

Exit wizard#737

Merged
pedramamini merged 4 commits intoRunMaestro:rcfrom
scriptease:exit-wizard
Apr 25, 2026
Merged

Exit wizard#737
pedramamini merged 4 commits intoRunMaestro:rcfrom
scriptease:exit-wizard

Conversation

@scriptease
Copy link
Copy Markdown

@scriptease scriptease commented Apr 6, 2026

Bildschirmfoto 2026-04-06 um 08 49 08

Allow exiting the wizard with esc if no interaction happened yet, e.g. when the user misclicks or tries out the button.

Summary by CodeRabbit

  • New Features

    • Wizard tabs with no prior user interaction (no user messages, no typed input, no staged images) now close immediately; confirmation is shown only when interaction is detected. Escape will exit directly for non-interacted wizards.
    • Tab-close behavior updated so closing a wizard respects whether user interaction has occurred.
  • Tests

    • Expanded tests for Escape key and tab-close scenarios covering both interacted and non-interacted wizards, and updated exit-confirmation dialog flows.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 6, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 3947fff6-7e98-45d1-bcb7-85092ef49bea

📥 Commits

Reviewing files that changed from the base of the PR and between 6ed8e72 and f9bed72.

📒 Files selected for processing (1)
  • src/renderer/utils/tabHelpers.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/renderer/utils/tabHelpers.ts

📝 Walkthrough

Walkthrough

Escape/tab-close behavior for wizard tabs now distinguishes tabs with prior user interaction (user messages, non-empty input, or staged images) from untouched wizards: interactive wizards open an exit confirmation; non-interacted wizards close immediately via tab handlers or call onExitWizard().

Changes

Cohort / File(s) Summary
Tests
src/__tests__/renderer/components/InlineWizard/WizardInputPanel.test.tsx, src/__tests__/renderer/hooks/useTabHandlers.test.ts
Expanded tests to assert Escape/tab-close behavior both when wizard user interaction exists and when it does not; adjusted dialog interaction tests to render sessions that trigger confirmation.
Wizard Input Escape Handling
src/renderer/components/InlineWizard/WizardInputPanel.tsx
Escape handler now uses interaction checks (conversation history user messages, trimmed inputValue, stagedImages) to choose between showing exit confirmation or closing the AI tab immediately (via session store closeTab with skipHistory or calling onExitWizard). Added dependencies to the useCallback.
Tab Close Control Flow
src/renderer/hooks/tabs/useTabHandlers.ts, src/renderer/hooks/keyboard/useMainKeyboardHandler.ts
Tab-close flow updated to consult hasWizardInteraction/hasWizardUserInteraction to decide whether to show the “Close this wizard?” confirmation; non-interacted active wizard tabs are closed immediately. CloseCurrentTabResult gains optional hasWizardUserInteraction?: boolean.
Interaction Detection Helper
src/renderer/utils/tabHelpers.ts
Added exported hasWizardInteraction(tab: AITab): boolean returning true when wizard is active and has a user message in conversation history, a non-empty trimmed input value, or staged images.

Sequence Diagram

sequenceDiagram
    participant User as User
    participant WIP as WizardInputPanel
    participant TH as TabHandlers
    participant SS as SessionStore
    participant Dlg as Dialog

    User->>WIP: Press Escape
    WIP->>WIP: Evaluate hasWizardInteraction (history/user msg, trimmed input, stagedImages)

    alt interaction detected
        WIP->>Dlg: Open "Close this wizard?" confirmation
        Dlg->>User: Render confirm dialog
        User->>Dlg: Choose Exit or Cancel
        Dlg->>WIP: Return choice
        opt Exit
            WIP->>TH: Request performTabClose/closeTab
            TH->>SS: closeTab(..., { skipHistory: true })
        end
    else no interaction
        WIP->>TH: Request immediate closeTab / call onExitWizard
        TH->>SS: closeTab(..., { skipHistory: true })
        SS-->>TH: Confirm closed
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐇 I nudged Escape with careful paw,
If nothing's left, the tab withdrew—ta-da!
A stray word, a sketch, or image bright,
And gentle prompts will guard your write.
Hoppity hops — the wizard's safe tonight. 🥕

🚥 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 'Exit wizard' is directly related to the main feature being implemented: conditional exit behavior for the wizard based on user interaction status.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@scriptease scriptease marked this pull request as ready for review April 6, 2026 07:33
@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Apr 6, 2026

Greptile Summary

This PR allows users to exit the inline wizard instantly via Escape (or Cmd+W) when no prior interaction has occurred, skipping the confirmation dialog. The implementation is clean and consistent across WizardInputPanel, useTabHandlers, and useMainKeyboardHandler, with a shared hasWizardInteraction helper in tabHelpers.ts.

  • hasWizardInteraction has a subtle bug on line 260: tab.inputValue?.trim() !== '' evaluates to true when inputValue is undefined (e.g. for sessions from before this field existed), causing a false-positive confirmation dialog via Cmd+W. The conversationHistory line above was correctly guarded with ?? false; inputValue needs the same treatment."

Confidence Score: 4/5

Safe to merge after fixing the inputValue undefined false-positive in hasWizardInteraction

One P1 bug: undefined inputValue causes hasWizardInteraction to return true for legacy sessions, showing an unnecessary confirmation dialog via Cmd+W on fresh wizard tabs. No data loss or crash risk — worst case is an extra confirm step. The Escape-key path in WizardInputPanel is unaffected since it uses the prop directly.

src/renderer/utils/tabHelpers.ts line 260

Important Files Changed

Filename Overview
src/renderer/utils/tabHelpers.ts Adds hasWizardInteraction; inputValue undefined check is a false-positive bug (undefined !== '' is true)
src/renderer/components/InlineWizard/WizardInputPanel.tsx handleEscapeKey correctly checks interaction state before skipping confirmation; closes tab or calls onExitWizard
src/renderer/hooks/keyboard/useMainKeyboardHandler.ts Cmd+W path correctly branches on hasWizardUserInteraction vs isWizardTab for confirmation-free close
src/renderer/hooks/tabs/useTabHandlers.ts handleCloseCurrentTab properly exposes isWizardTab and hasWizardUserInteraction flags for keyboard handler
src/tests/renderer/components/InlineWizard/WizardInputPanel.test.tsx Good Escape-key coverage including history/input/images cases; multi-tab direct-close path not tested
src/tests/renderer/hooks/useTabHandlers.test.ts Wizard close tests don't exercise inputValue: undefined edge case for hasWizardInteraction false positive

Sequence Diagram

sequenceDiagram
    participant U as User
    participant WIP as WizardInputPanel
    participant MKH as useMainKeyboardHandler
    participant UTH as useTabHandlers
    participant TH as tabHelpers

    U->>WIP: Press Escape
    WIP->>WIP: Check conversationHistory / inputValue / stagedImages
    alt Has interaction
        WIP->>WIP: setShowExitConfirm(true)
        U->>WIP: Click Exit in dialog
        WIP->>WIP: onExitWizard()
    else No interaction & multiple tabs
        WIP->>TH: closeTab(session, activeTabId, {skipHistory:true})
        TH-->>WIP: Updated session (tab removed)
    else No interaction & single tab
        WIP->>WIP: onExitWizard()
    end

    U->>MKH: Press Cmd+W
    MKH->>UTH: handleCloseCurrentTab()
    UTH->>TH: hasActiveWizard(tab) + hasWizardInteraction(tab)
    TH-->>UTH: isWizardTab, hasWizardUserInteraction
    UTH-->>MKH: CloseCurrentTabResult
    alt hasWizardUserInteraction
        MKH->>MKH: openModal(confirm) → performTabClose on confirm
    else isWizardTab only (no interaction)
        MKH->>UTH: performTabClose (no dialog)
    else plain tab / draft
        MKH->>UTH: performTabClose or draft confirm
    end
Loading

Reviews (1): Last reviewed commit: "fix: handle missing conversationHistory ..." | Re-trigger Greptile

Comment thread src/renderer/utils/tabHelpers.ts Outdated
if (!tab.wizardState?.isActive) return false;
const hasUserMessages =
tab.wizardState.conversationHistory?.some((m) => m.role === 'user') ?? false;
const hasInput = tab.inputValue?.trim() !== '';
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 inputValue undefined yields false positive

When tab.inputValue is undefined (possible for sessions persisted before this field was added to AITab), undefined?.trim() returns undefined, and undefined !== '' is true. This makes hasWizardInteraction return true even with no actual user input, causing Cmd+W to show the "your progress will be lost" confirmation dialog on a fresh wizard tab. The conversationHistory guard directly above correctly uses ?? false — apply the same pattern here.

Suggested change
const hasInput = tab.inputValue?.trim() !== '';
const hasInput = (tab.inputValue?.trim() ?? '') !== '';

Copy link
Copy Markdown

@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: 1

🧹 Nitpick comments (2)
src/renderer/components/InlineWizard/WizardInputPanel.tsx (1)

155-160: Use the shared wizard-interaction predicate here too.

Lines 155-160 reimplement the same interaction check that handleTabClose() / handleCloseCurrentTab() now get from hasWizardInteraction(). Keeping a second copy here means Esc, tab-close, and Cmd/Ctrl+W can drift again the next time the rule changes.

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

In `@src/renderer/components/InlineWizard/WizardInputPanel.tsx` around lines 155 -
160, The block that recomputes interaction state duplicates logic already
encapsulated in hasWizardInteraction(); replace the inline checks
(hasUserMessages, hasInput, hasImages and the if using them) with a single call
to hasWizardInteraction(session, inputValue, stagedImages) or the existing
signature of hasWizardInteraction so the panel uses that shared predicate
(aligning with handleTabClose() / handleCloseCurrentTab()); ensure you import or
reference the same hasWizardInteraction symbol and remove the duplicated local
variables.
src/__tests__/renderer/components/InlineWizard/WizardInputPanel.test.tsx (1)

476-486: Add coverage for the multi-tab no-interaction Escape path.

Line 476 still renders the default one-tab session, so this only exercises the onExitWizard() fallback. The new setSessions(...closeTab...) branch for session.aiTabs.length > 1 is the risky part of the change, and it isn't asserted yet.

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

In `@src/__tests__/renderer/components/InlineWizard/WizardInputPanel.test.tsx`
around lines 476 - 486, Add a test that covers the multi-tab Escape path by
rendering WizardInputPanel with a session that has aiTabs.length > 1 (e.g.
extend defaultProps.session.aiTabs to two entries), pass a mocked setSessions
and onExitWizard, fire Escape on the textarea, and assert that setSessions was
called to close a tab (verify it was called once with a sessions array
reflecting one fewer tab or the expected closed-tab shape), assert onExitWizard
was NOT called, and assert no "Exit Wizard?" dialog is shown; reference
WizardInputPanel, defaultProps, setSessions, and session.aiTabs.length > 1 to
locate where to change the test input and assertions.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/renderer/utils/tabHelpers.ts`:
- Around line 256-262: The check in hasWizardInteraction incorrectly uses
optional chaining for inputValue (tab.inputValue?.trim() !== ''), which yields
undefined and can be truthy for missing input; change the logic to treat missing
input as empty by using nullish coalescing on tab.inputValue before trim (i.e.,
default to ''), so update the hasInput computation in hasWizardInteraction to
call trim() on (tab.inputValue ?? '') to correctly detect empty input and avoid
reporting interaction for partially hydrated tabs.

---

Nitpick comments:
In `@src/__tests__/renderer/components/InlineWizard/WizardInputPanel.test.tsx`:
- Around line 476-486: Add a test that covers the multi-tab Escape path by
rendering WizardInputPanel with a session that has aiTabs.length > 1 (e.g.
extend defaultProps.session.aiTabs to two entries), pass a mocked setSessions
and onExitWizard, fire Escape on the textarea, and assert that setSessions was
called to close a tab (verify it was called once with a sessions array
reflecting one fewer tab or the expected closed-tab shape), assert onExitWizard
was NOT called, and assert no "Exit Wizard?" dialog is shown; reference
WizardInputPanel, defaultProps, setSessions, and session.aiTabs.length > 1 to
locate where to change the test input and assertions.

In `@src/renderer/components/InlineWizard/WizardInputPanel.tsx`:
- Around line 155-160: The block that recomputes interaction state duplicates
logic already encapsulated in hasWizardInteraction(); replace the inline checks
(hasUserMessages, hasInput, hasImages and the if using them) with a single call
to hasWizardInteraction(session, inputValue, stagedImages) or the existing
signature of hasWizardInteraction so the panel uses that shared predicate
(aligning with handleTabClose() / handleCloseCurrentTab()); ensure you import or
reference the same hasWizardInteraction symbol and remove the duplicated local
variables.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 7b01c1ad-d7b0-4f18-b915-77fabf31aa78

📥 Commits

Reviewing files that changed from the base of the PR and between ea92ca7 and ab52c96.

📒 Files selected for processing (6)
  • src/__tests__/renderer/components/InlineWizard/WizardInputPanel.test.tsx
  • src/__tests__/renderer/hooks/useTabHandlers.test.ts
  • src/renderer/components/InlineWizard/WizardInputPanel.tsx
  • src/renderer/hooks/keyboard/useMainKeyboardHandler.ts
  • src/renderer/hooks/tabs/useTabHandlers.ts
  • src/renderer/utils/tabHelpers.ts

Comment thread src/renderer/utils/tabHelpers.ts
@pedramamini
Copy link
Copy Markdown
Collaborator

Thanks for the contribution, @scriptease! This is a nice UX improvement — skipping the confirmation dialog when there's nothing to lose is the right call.

The code is clean and well-structured:

  • Shared hasWizardInteraction() helper keeps the logic consistent across Escape, Cmd+W, and tab close
  • Good test coverage for the new paths
  • The inputValue undefined fix in 8a67cf4 addresses the one real issue

LGTM — approving this.

…also wire on Esc to work as described in the wizard.
…pdate tests

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…se positives

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@scriptease
Copy link
Copy Markdown
Author

Weird, the failed unit test is from a other branch I pushed at the same time not this push request, when I run locally it succeeds


 FAIL  src/__tests__/renderer/components/AgentSessionsModal.test.tsx > AgentSessionsModal > Keyboard Navigation > should navigate down with ArrowDown
Error: expect(element).toHaveStyle()

@scriptease
Copy link
Copy Markdown
Author

Just like changing the commit message fixed it 🎉

Resolve conflict in tabHelpers.ts: both hasWizardInteraction (PR)
and filterUnifiedTabOrderForUnread (rc) need to coexist.
@pedramamini pedramamini merged commit f95ce55 into RunMaestro:rc Apr 25, 2026
3 checks passed
@pedramamini
Copy link
Copy Markdown
Collaborator

Merged — thanks again for the contribution, @scriptease! Nice UX polish. 🙏

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants