Skip to content

Exit wizard#737

Open
scriptease wants to merge 3 commits intoRunMaestro:rcfrom
scriptease:exit-wizard
Open

Exit wizard#737
scriptease wants to merge 3 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 user interaction (no messages, no typed input, no staged images) now close immediately; confirmation appears only when interaction is detected. Escape will exit directly in non-interacted wizards.
  • Tests

    • Expanded tests covering Escape key behavior and wizard/tab close scenarios with and without user interaction, plus dialog interactions for exit confirmation.

…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>
@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: 10b578fe-d358-4464-9657-7926393503d7

📥 Commits

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

📒 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 handling for wizard tabs was made conditional: Escape opens a confirmation only when user interaction is detected (wizard conversation user messages, non-empty input, or staged images). If no interaction, the wizard/tab is closed immediately via tab handlers or onExitWizard().

Changes

Cohort / File(s) Summary
Tests
src/__tests__/renderer/components/InlineWizard/WizardInputPanel.test.tsx, src/__tests__/renderer/hooks/useTabHandlers.test.ts
Updated and expanded tests to cover Escape behavior with and without wizard user interaction and adjusted dialog click tests to render sessions that trigger the confirmation.
Wizard Input Escape Handling
src/renderer/components/InlineWizard/WizardInputPanel.tsx
Escape handler now checks session/tab interaction state (conversation history, trimmed inputValue, stagedImages). Shows confirmation when interaction exists; otherwise closes tab via session store (closeTab) or calls onExitWizard(). Expanded useCallback deps.
Tab Close Control Flow
src/renderer/hooks/tabs/useTabHandlers.ts, src/renderer/hooks/keyboard/useMainKeyboardHandler.ts
Tab-close flow updated to surface hasWizardUserInteraction and only show the “Close this wizard?” modal when true; otherwise perform immediate close for wizard tabs while preserving other close confirmation paths. Added hasWizardUserInteraction to AI close result.
Interaction Detection Helper
src/renderer/utils/tabHelpers.ts
Added exported hasWizardInteraction(tab: AITab): boolean that returns true if wizard is active and has any user message in conversation history, non-empty trimmed input, 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(tab) (history, input, images)

    alt interaction detected
        WIP->>Dlg: Open "Close this wizard?" confirmation
        Dlg->>User: Render confirm dialog
        User->>Dlg: Click Exit / Cancel
        Dlg->>WIP: Return choice
    else no interaction
        WIP->>TH: Request closeTab(tabId)
        TH->>SS: closeTab(..., skipHistory=true)
        SS-->>TH: Confirm closed
        TH-->>WIP: Tab closed / onExitWizard invoked
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 I hopped to press Escape with care,
No traces found — the tab flew where?
Type a line or stage an art,
A gentle prompt will pause depart,
A tiny rabbit guards your wizarded lair. 🥕

🚥 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' directly and concisely summarizes the main objective of the pull request: enabling users to exit the wizard by pressing Escape when no user interaction has occurred.
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

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

…se positive

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@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.

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