diff options
| author | Adam Malczewski <[email protected]> | 2026-10-16 15:25:40 +0900 |
|---|---|---|
| committer | Adam Malczewski <[email protected]> | 2026-10-16 15:25:40 +0900 |
| commit | a4aa86e66dad93f40bce640545d8903f7e6bcb64 (patch) | |
| tree | f02f83ffffc5a03ed34e5bd460f806b456b02bbe /src/features/tabs/tab-groups.test.ts | |
| parent | f6d96289470ac888670050ff64c54bbe69b973cb (diff) | |
| download | dispatch-web-a4aa86e66dad93f40bce640545d8903f7e6bcb64.tar.gz dispatch-web-a4aa86e66dad93f40bce640545d8903f7e6bcb64.zip | |
feat(tabs): confirm dialog that cascades a tab close to its subagents
Closing a tab whose visible subagent group is non-empty now asks ONE
daisyUI modal (CloseSubagentsConfirm): 'Yes, close all N' closes the tab
plus every transitive descendant tab (sub-sub-agents included, no further
prompts), 'No, keep them open' closes only the tab, and Cancel/Escape/
backdrop closes nothing. Plain tabs close directly as before.
- cascadeCloseIds (pure, tab-groups.ts): the tab + its transitive visible
group descendants in tab order — one decision covers the whole subtree
- TabList intercepts tab-close and opens the dialog only when the
subtree has >1 tab; ids are snapshotted at open time
- each closed conversation goes through the existing onClose port, so
every close still POSTs /conversations/:id/close (backend doesn't
cascade; it is idempotent)
Diffstat (limited to 'src/features/tabs/tab-groups.test.ts')
| -rw-r--r-- | src/features/tabs/tab-groups.test.ts | 29 |
1 files changed, 29 insertions, 0 deletions
diff --git a/src/features/tabs/tab-groups.test.ts b/src/features/tabs/tab-groups.test.ts index 83f273c..a89a469 100644 --- a/src/features/tabs/tab-groups.test.ts +++ b/src/features/tabs/tab-groups.test.ts @@ -1,6 +1,7 @@ import { describe, expect, it } from "vitest"; import { buildTabGroups, + cascadeCloseIds, directGroupChildren, groupBleedPx, groupDescendants, @@ -103,6 +104,34 @@ describe("groupDescendants / directGroupChildren / groupParentCandidates", () => }); }); +describe("cascadeCloseIds (the ids a cascading tab close removes)", () => { + const tabs = [ + tab("p"), + tab("c1", "p"), + tab("c2", "p"), + tab("g", "c1"), // sub-sub-agent: grouped under c1, still cascades from p + tab("solo"), + ]; + + it("returns the tab first, then its transitive descendants in tab order", () => { + expect(cascadeCloseIds(tabs, "p")).toEqual(["p", "c1", "c2", "g"]); + }); + + it("starts the cascade at a mid-level tab, taking only its own subtree", () => { + expect(cascadeCloseIds(tabs, "c1")).toEqual(["c1", "g"]); + }); + + it("returns just the tab itself when it has no subagents (closes without a prompt)", () => { + expect(cascadeCloseIds(tabs, "solo")).toEqual(["solo"]); + expect(cascadeCloseIds(tabs, "c2")).toEqual(["c2"]); + }); + + it("ignores tabs whose group parent is no longer open", () => { + const stale = [tab("p"), tab("orphan", "gone")]; + expect(cascadeCloseIds(stale, "p")).toEqual(["p"]); + }); +}); + describe("groupBleedPx / groupGutterPx", () => { it("is zero without groups", () => { expect(groupGutterPx(0)).toBe(0); |
