diff options
Diffstat (limited to 'src/features/tabs/ui')
| -rw-r--r-- | src/features/tabs/ui/CloseSubagentsConfirm.svelte | 57 | ||||
| -rw-r--r-- | src/features/tabs/ui/TabList.svelte | 43 |
2 files changed, 99 insertions, 1 deletions
diff --git a/src/features/tabs/ui/CloseSubagentsConfirm.svelte b/src/features/tabs/ui/CloseSubagentsConfirm.svelte new file mode 100644 index 0000000..cbbec79 --- /dev/null +++ b/src/features/tabs/ui/CloseSubagentsConfirm.svelte @@ -0,0 +1,57 @@ +<script lang="ts"> + import type { Tab } from "../tabs"; + + /** + * The close-subagents confirm dialog: shown when the user closes a tab that + * still has subagent tabs in its (visible) subagent group. ONE dialog for + * the whole subtree — "Yes" cascades the close to every transitive + * descendant (sub-sub-agents included, no further prompts); "No" closes + * only the tab itself. Escape / backdrop dismisses without closing anything. + * daisyUI `modal` (controlled: `modal-open`). + */ + let { + tab, + subagentCount, + onKeep, + onCloseAll, + onDismiss, + }: { + /** The tab being closed (drives the title shown in the dialog). */ + tab: Tab; + /** Number of tabs in the tab's subagent group (transitive). */ + subagentCount: number; + /** "No" — close only this tab, keep the subagents open. */ + onKeep: () => void; + /** "Yes" — close this tab AND cascade to all its subagents. */ + onCloseAll: () => void; + /** Escape / backdrop — close nothing. */ + onDismiss: () => void; + } = $props(); +</script> + +<svelte:window onkeydown={(e) => e.key === "Escape" && onDismiss()} /> + +<div class="modal modal-open" role="dialog" aria-label="Close subagents too?"> + <div class="modal-box max-w-md"> + <h3 class="text-lg font-bold">Close subagents too?</h3> + <div class="flex flex-col gap-1 py-3"> + <p class="text-sm"> + "{tab.title}" has {subagentCount} + {subagentCount === 1 ? "subagent tab" : "subagent tabs"} (including nested ones). + </p> + <p class="text-sm"> + Close them as well? Their own subagents are closed too, and any in-flight turns are + stopped. + </p> + </div> + <div class="modal-action"> + <button type="button" class="btn btn-ghost btn-sm" onclick={onDismiss}>Cancel</button> + <button type="button" class="btn btn-sm" onclick={onKeep}>No, keep them open</button> + <button type="button" class="btn btn-warning btn-sm" onclick={onCloseAll}> + Yes, close all {subagentCount + 1} + </button> + </div> + </div> + <!-- svelte-ignore a11y_click_events_have_key_events, a11y_no_static_element_interactions --> + <div class="modal-backdrop" onclick={onDismiss}></div> +</div> diff --git a/src/features/tabs/ui/TabList.svelte b/src/features/tabs/ui/TabList.svelte index 06d5aa6..4b96e08 100644 --- a/src/features/tabs/ui/TabList.svelte +++ b/src/features/tabs/ui/TabList.svelte @@ -1,6 +1,7 @@ <script lang="ts"> import { buildTabGroups, + cascadeCloseIds, directGroupChildren, groupGutterPx, groupParentCandidates, @@ -8,6 +9,7 @@ } from "../tab-groups"; import type { Tab } from "../tabs"; import { shortHandle } from "../tabs"; + import CloseSubagentsConfirm from "./CloseSubagentsConfirm.svelte"; import TabCard from "./TabCard.svelte"; import TabGroupBox from "./TabGroupBox.svelte"; import TabMenu from "./TabMenu.svelte"; @@ -72,6 +74,26 @@ if (onSetTabGroup === undefined) return; menu = { conversationId, x, y }; } + + // Close-with-confirm: a tab whose (visible) subagent group is non-empty + // gets ONE confirm dialog covering its whole subtree (never one per level). + // The dialog's ids are snapshotted at open time; a tab the backend closed + // in the meantime is a no-op for `onClose`. A plain tab closes directly. + let closeConfirm = $state<{ tab: Tab; cascadeIds: readonly string[] } | null>(null); + + function requestClose(conversationId: string): void { + const cascadeIds = cascadeCloseIds(tabs, conversationId); + if (cascadeIds.length <= 1) { + onClose(conversationId); + return; + } + const tab = tabs.find((t) => t.conversationId === conversationId); + if (tab === undefined) { + onClose(conversationId); + return; + } + closeConfirm = { tab, cascadeIds }; + } </script> {#snippet card(tab: Tab, wide: boolean, subagentCount: number)} @@ -83,7 +105,7 @@ {wide} {subagentCount} {onSelect} - {onClose} + onClose={requestClose} {onRename} onMenu={openMenu} /> @@ -147,3 +169,22 @@ onDismiss={() => (menu = null)} /> {/if} + +{#if closeConfirm !== null} + {@const target = closeConfirm.tab} + {@const cascadeIds = [...closeConfirm.cascadeIds]} + {@const closeOwner = onClose} + <CloseSubagentsConfirm + tab={target} + subagentCount={cascadeIds.length - 1} + onKeep={() => { + closeOwner(target.conversationId); + closeConfirm = null; + }} + onCloseAll={() => { + for (const id of cascadeIds) closeOwner(id); + closeConfirm = null; + }} + onDismiss={() => (closeConfirm = null)} + /> +{/if} |
