From 706d98eef7a75662192d7082f378b4f0917da461 Mon Sep 17 00:00:00 2001 From: huanghuifeng Date: Thu, 5 Mar 2026 19:04:52 +0800 Subject: [PATCH 1/2] feat: add confirmation dialog before creating new session --- src/tui/component/dialog-new.tsx | 33 +++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/src/tui/component/dialog-new.tsx b/src/tui/component/dialog-new.tsx index a13f436..4f614d5 100644 --- a/src/tui/component/dialog-new.tsx +++ b/src/tui/component/dialog-new.tsx @@ -10,6 +10,7 @@ import { useSync } from "@tui/context/sync" import { useRoute } from "@tui/context/route" import { useConfig } from "@tui/context/config" import { useDialog, scrollDialogBy, scrollDialogTo } from "@tui/ui/dialog" +import { DialogSelect } from "@tui/ui/dialog-select" import { useToast } from "@tui/ui/toast" import { InputAutocomplete } from "@tui/ui/input-autocomplete" import { DialogHeader } from "@tui/ui/dialog-header" @@ -192,7 +193,7 @@ export function DialogNew() { return fields } - async function handleCreate() { + async function doCreate() { if (creating()) return setCreating(true) setStatusMessage("Preparing...") @@ -295,6 +296,36 @@ export function DialogNew() { } } + function handleCreate() { + // Build summary lines for the confirmation dialog + const lines: string[] = [] + lines.push(`Tool: ${selectedTool()}`) + const t = title().trim() + lines.push(`Title: ${t || "(auto-generated)"}`) + lines.push(`Path: ${projectPath() || process.cwd()}`) + if (useWorktree()) { + const branch = worktreeBranch().trim() + lines.push(`Branch: ${branch || "(auto-generated)"}`) + + } + + dialog.push(() => ( + { + dialog.pop() + if (opt.value === "confirm") { + doCreate() + } + }} + /> + )) + } + useKeyboard((evt) => { if (evt.name === "escape") { evt.preventDefault() From b9c9b3f5f9d21012c1bce344687ad0352891c080 Mon Sep 17 00:00:00 2001 From: huanghuifeng Date: Mon, 9 Mar 2026 17:15:28 +0800 Subject: [PATCH 2/2] fix: preserve form state and add Esc-to-back in confirmation dialog Two improvements to the new session confirmation dialog: 1. Esc on the confirmation screen now returns to the form instead of closing the entire dialog. Previously, DialogProvider's keyboard handler would intercept Esc and pop DialogNew off the stack entirely. 2. Form state is no longer lost when navigating back from confirmation. Previously, dialog.push() caused DialogNew to unmount, so all signal values were reset on re-mount. Now all form signals are saved to a module-level variable before pushing the confirmation dialog, and restored when DialogNew re-mounts after dialog.pop(). Co-Authored-By: Claude Sonnet 4.6 --- src/tui/component/dialog-new.tsx | 59 +++++++++++++++++++++++++------- 1 file changed, 47 insertions(+), 12 deletions(-) diff --git a/src/tui/component/dialog-new.tsx b/src/tui/component/dialog-new.tsx index 4f614d5..2013f27 100644 --- a/src/tui/component/dialog-new.tsx +++ b/src/tui/component/dialog-new.tsx @@ -53,6 +53,20 @@ async function commandExists(cmd: string, cwd?: string): Promise { const projectPathHistory = new HistoryManager("dialog-new:project-paths", 15) const branchNameHistory = new HistoryManager("dialog-new:branch-names", 15) +// Persists form state across dialog.push/pop cycles (confirmation dialog) +interface SavedFormState { + title: string + selectedTool: Tool + toolIndex: number + claudeSessionMode: ClaudeSessionMode + skipPermissions: boolean + customCommand: string + projectPath: string + useWorktree: boolean + worktreeBranch: string +} +let _savedFormState: SavedFormState | null = null + const TOOLS: { value: Tool; label: string; description: string }[] = [ { value: "claude", label: "Claude Code", description: "Anthropic's Claude CLI" }, { value: "opencode", label: "OpenCode", description: "OpenCode CLI" }, @@ -73,13 +87,17 @@ export function DialogNew() { const renderer = useRenderer() const { config } = useConfig() - const defaultTool = config().defaultTool || "claude" + // Restore state saved before confirmation push, then clear it + const restore = _savedFormState + _savedFormState = null + + const defaultTool = restore?.selectedTool ?? (config().defaultTool || "claude") const defaultToolIndex = TOOLS.findIndex(t => t.value === defaultTool) - const [title, setTitle] = createSignal("") + const [title, setTitle] = createSignal(restore?.title ?? "") const [selectedTool, setSelectedTool] = createSignal(defaultTool) - const [customCommand, setCustomCommand] = createSignal("") - const [projectPath, setProjectPath] = createSignal(process.cwd()) + const [customCommand, setCustomCommand] = createSignal(restore?.customCommand ?? "") + const [projectPath, setProjectPath] = createSignal(restore?.projectPath ?? process.cwd()) const [creating, setCreating] = createSignal(false) const [statusMessage, setStatusMessage] = createSignal("") const [spinnerFrame, setSpinnerFrame] = createSignal(0) @@ -96,11 +114,11 @@ export function DialogNew() { } }) - const [claudeSessionMode, setClaudeSessionMode] = createSignal("new") - const [skipPermissions, setSkipPermissions] = createSignal(false) + const [claudeSessionMode, setClaudeSessionMode] = createSignal(restore?.claudeSessionMode ?? "new") + const [skipPermissions, setSkipPermissions] = createSignal(restore?.skipPermissions ?? false) - const [useWorktree, setUseWorktree] = createSignal(false) - const [worktreeBranch, setWorktreeBranch] = createSignal("") + const [useWorktree, setUseWorktree] = createSignal(restore?.useWorktree ?? false) + const [worktreeBranch, setWorktreeBranch] = createSignal(restore?.worktreeBranch ?? "") const [isInGitRepo, setIsInGitRepo] = createSignal(false) const [useBaseDevelop, setUseBaseDevelop] = createSignal(false) const [developExists, setDevelopExists] = createSignal(false) @@ -108,7 +126,7 @@ export function DialogNew() { const storage = getStorage() const [focusedField, setFocusedField] = createSignal("title") - const [toolIndex, setToolIndex] = createSignal(defaultToolIndex >= 0 ? defaultToolIndex : 0) + const [toolIndex, setToolIndex] = createSignal(restore?.toolIndex ?? (defaultToolIndex >= 0 ? defaultToolIndex : 0)) let titleInputRef: InputRenderable | undefined let customCommandInputRef: InputRenderable | undefined @@ -297,6 +315,19 @@ export function DialogNew() { } function handleCreate() { + // Save form state so it survives dialog.push/pop cycle + _savedFormState = { + title: title(), + selectedTool: selectedTool(), + toolIndex: toolIndex(), + claudeSessionMode: claudeSessionMode(), + skipPermissions: skipPermissions(), + customCommand: customCommand(), + projectPath: projectPath(), + useWorktree: useWorktree(), + worktreeBranch: worktreeBranch(), + } + // Build summary lines for the confirmation dialog const lines: string[] = [] lines.push(`Tool: ${selectedTool()}`) @@ -306,9 +337,10 @@ export function DialogNew() { if (useWorktree()) { const branch = worktreeBranch().trim() lines.push(`Branch: ${branch || "(auto-generated)"}`) - } + // Capture doCreate in closure before DialogNew is unmounted by dialog.push + const capturedDoCreate = doCreate dialog.push(() => ( { - dialog.pop() if (opt.value === "confirm") { - doCreate() + _savedFormState = null + dialog.clear() + capturedDoCreate() + } else { + dialog.pop() } }} />