How Claudian Plan Mode Works: Shift+Tab Toggling and Approval Workflows Explained

Claudian plan mode is a special permission state that disables the Safe/Yolo toggle, captures plan files written to ~/.claude/plans/, and offers three approval options when planning completes.

The YishenTu/claudian repository implements a sophisticated planning workflow for AI-assisted coding sessions. Plan mode allows Claude to generate structured plans without immediately executing file changes, giving users control over when and how to proceed with generated recommendations.

Entering and Exiting Plan Mode via Shift+Tab

View-Level Key Handler in ClaudianView.ts

The global Shift+Tab shortcut toggles plan mode at the view level. In src/features/chat/ClaudianView.ts (lines 483-498), the keyboard handler intercepts the combination and switches between active planning and normal operation:

// ClaudianView.ts – view-level key handler
if (e.key === 'Tab' && e.shiftKey && !e.isComposing) {
    e.preventDefault();
    const activeTab = this.tabManager?.getActiveTab();
    if (!activeTab) return;
    const current = this.plugin.settings.permissionMode;
    if (current === 'plan') {
        // leave plan mode → restore the mode that was saved before we entered it
        const restoreMode = activeTab.state.prePlanPermissionMode ?? 'normal';
        activeTab.state.prePlanPermissionMode = null;
        updatePlanModeUI(activeTab, this.plugin, restoreMode);
    } else {
        // enter plan mode → remember the current mode so we can go back to it later
        activeTab.state.prePlanPermissionMode = current;
        updatePlanModeUI(activeTab, this.plugin, 'plan');
    }
}

State Preservation and Restoration

When entering plan mode, the current permission mode (normal, yolo, etc.) is stored in tab.state.prePlanPermissionMode. When exiting via Shift+Tab again, that saved value restores the previous state. This ensures users return to their exact original configuration after planning completes.

UI Updates and Visual Indicators

Toolbar Changes in InputToolbar.ts

Plan mode modifies the interface to indicate the special state. In src/features/chat/ui/InputToolbar.ts (lines 66-73), the toolbar hides the standard permission toggle and displays a PLAN label:

// InputToolbar.ts – permission label handling
if (mode === 'plan') {
    this.toggleEl.style.display = 'none';
    this.labelEl.setText('PLAN');
    this.labelEl.addClass('plan-active');
}

CSS Classes and Permission Toggle

The updatePlanModeUI() function in src/features/chat/tabs/Tab.ts (lines 1190-1195) persists the mode change and updates visual elements:

// Tab.ts – UI helper
export function updatePlanModeUI(tab: TabData, plugin: ClaudianPlugin, mode: PermissionMode): void {
    plugin.settings.permissionMode = mode;
    void plugin.saveSettings();
    tab.ui.permissionToggle?.updateDisplay();
    tab.dom.inputWrapper.toggleClass('claudian-input-plan-mode', mode === 'plan');
}

The claudian-input-plan-mode CSS class applies to the input wrapper, enabling custom styling during planning sessions.

Capturing Plan Files During Chat Streams

StreamController.ts File Detection

During active chat streams, Claudian monitors Write tool calls to detect plan generation. The StreamController.ts file (lines 69-88) captures any write operation targeting the ~/.claude/plans/ directory:

// StreamController.ts – capture plan file path
if (chunk.name === TOOL_WRITE) {
    this.capturePlanFilePath(chunk.input);
}
...
private capturePlanFilePath(input: Record<string, unknown>): void {
    const filePath = input.file_path as string | undefined;
    if (filePath && filePath.replace(/\\/g, '/').includes('/.claude/plans/')) {
        this.deps.state.planFilePath = filePath;
    }
}

The captured path populates ChatState.planFilePath, making the generated plan available for UI components and approval workflows.

The Approval Workflow and Exit Plan Mode

InlineExitPlanMode Component

When planning completes, the InlineExitPlanMode component (defined in src/features/chat/rendering/InlineExitPlanMode.ts) presents three distinct actions:

  1. Approve (new session) – Creates a fresh chat session pre-loaded with the plan text
  2. Approve (current session) – Continues in the existing session, discarding the plan file
  3. Feedback – Allows free-form input to refine the current plan

The approve-new-session handler (lines 74-86) extracts plan content and signals the resolution:

// InlineExitPlanMode.ts – approve-new-session handling
newSessionRow.addEventListener('click', () => {
    this.focusedIndex = 0;
    this.updateFocus();
    this.handleResolve({
        type: 'approve-new-session',
        planContent: this.extractPlanContent(),
    });
});

Session Management and Plan Continuation

When approve-new-session is selected, the plan content is stored in tab.state.pendingNewSessionPlan. After the current stream terminates (tab.state.cancelRequested = true), the ConversationController creates a new session and auto-sends the approved plan text.

Callback Implementation in Tab.ts

The exit-plan-mode callback in src/features/chat/tabs/Tab.ts (lines 1164-1180) manages the transition logic:

// Tab.ts – exit-plan-mode callback
tab.service.setExitPlanModeCallback(
    async (input, signal) => {
        const decision = await tab.controllers.inputController?.handleExitPlanMode(input, signal) ?? null;
        // Revert only on approve; feedback and cancel keep plan mode active.
        if (decision !== null && decision.type !== 'feedback') {
            // Restore permission mode only if we are still in plan mode (user may have toggled out via Shift+Tab)
            if (plugin.settings.permissionMode === 'plan') {
                const restoreMode = tab.state.prePlanPermissionMode ?? 'normal';
                tab.state.prePlanPermissionMode = null;
                updatePlanModeUI(tab, plugin, restoreMode);
            }
            if (decision.type === 'approve-new-session') {
                // Store plan content so the next session can auto-send it
                tab.state.pendingNewSessionPlan = decision.planContent;
                tab.state.cancelRequested = true;   // ends the current stream
            }
        }
        return decision;
    }
);

This callback respects manual Shift+Tab toggles—if the user already exited plan mode via keyboard before approving, the UI state remains unchanged.

State Management and Persistence

ChatState Type Definitions

The planning workflow relies on three critical fields defined in src/features/chat/state/types.ts (lines 100-108):

// state/types.ts – plan-related fields
pendingNewSessionPlan: string | null;   // plan to send in the next session
planFilePath: string | null;            // path of the written plan file
prePlanPermissionMode: PermissionMode | null; // mode saved before entering plan mode

These values persist across stream events and clear automatically when exiting plan mode or initializing new sessions.

Summary

  • Shift+Tab toggles plan mode globally in ClaudianView.ts, preserving the previous permission state in prePlanPermissionMode for later restoration.
  • Plan mode hides the standard Safe/Yolo toggle and displays a PLAN label via InputToolbar.ts, applying the claudian-input-plan-mode CSS class.
  • The system captures plan files written to ~/.claude/plans/ during chat streams through StreamController.ts and stores the path in ChatState.
  • The approval workflow offers three choices: approve for new session (pre-loads plan content), approve for current session, or provide feedback for iteration.
  • Exiting plan mode restores the original permission settings only if the user hasn't already manually toggled out using Shift+Tab.

Frequently Asked Questions

What is plan mode in Claudian?

Plan mode is a special permission state that disables automatic execution toggles and allows Claude to generate structured planning documents without immediately modifying project files. When active, it displays a PLAN label in the toolbar and captures any files written to the ~/.claude/plans/ directory for review before implementation.

How do I toggle plan mode using the keyboard?

Press Shift+Tab to enter or exit plan mode. This shortcut is handled in src/features/chat/ClaudianView.ts and works across all active tabs. When entering, your current permission mode (normal, yolo, etc.) is saved; when exiting, that mode is automatically restored unless you've already approved a plan through the UI workflow.

What happens when I approve a plan for a new session?

Selecting Approve (new session) stores the plan content in tab.state.pendingNewSessionPlan, terminates the current chat stream, and creates a fresh session that automatically sends the plan text as the opening message. This isolates the planning phase from implementation while preserving the generated strategy for execution.

Where does Claudian store the plan file path?

The plan file path is stored in ChatState.planFilePath, defined in src/features/chat/state/types.ts. The StreamController.ts populates this field when it detects Write tool calls targeting the ~/.claude/plans/ directory, making the path available to UI components like InlineExitPlanMode for preview and approval actions.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →