diff --git a/apps/desktop/src/components/sidebar/ConnectionTree.vue b/apps/desktop/src/components/sidebar/ConnectionTree.vue index 0a12e1ba54..a874b3f0c2 100644 --- a/apps/desktop/src/components/sidebar/ConnectionTree.vue +++ b/apps/desktop/src/components/sidebar/ConnectionTree.vue @@ -2043,8 +2043,18 @@ function findSchemaNode(nodes: TreeNode[], connId: string, database: string, sch function onSearchToggle(node: TreeNode) { if (!isTreeSearchFiltering.value || !node.children) return; const next = new Set(searchCollapsedIds.value); - if (node.isExpanded) next.add(node.id); - else next.delete(node.id); + if (node.isExpanded) { + next.add(node.id); + const collectDescendants = (children: readonly TreeNode[]) => { + for (const child of children) { + next.add(child.id); + if (child.children?.length) collectDescendants(child.children); + } + }; + collectDescendants(node.children); + } else { + next.delete(node.id); + } searchCollapsedIds.value = next; } diff --git a/apps/desktop/src/components/sidebar/SidebarTreeRuntimeHost.vue b/apps/desktop/src/components/sidebar/SidebarTreeRuntimeHost.vue index 04caf228c4..68febbead1 100644 --- a/apps/desktop/src/components/sidebar/SidebarTreeRuntimeHost.vue +++ b/apps/desktop/src/components/sidebar/SidebarTreeRuntimeHost.vue @@ -246,6 +246,7 @@ import { buildXuguCompileSql } from "@/lib/database/xuguCompileSql"; import { buildDamengCompileViewSql } from "@/lib/database/damengCompileSql"; import type { SidebarDataOpenRequest } from "@/lib/sidebar/sidebarDataOpenCoordinator"; import { createSidebarActionTarget, findSidebarActionTarget, releaseRemovedSidebarActionTarget, type SidebarActionTarget } from "@/lib/sidebar/sidebarActionTarget"; +import { collapseSubtreeDescendants } from "@/lib/sidebar/sidebarTreeCollapse"; import { createSidebarMenuContext, normalizeSidebarMenuDescriptors } from "@/lib/sidebar/sidebarTreeMenuDescriptors"; import { driverProfileDatabaseWorkspace } from "@/lib/database/driverProfileExtensions"; import type { SidebarDangerDialogRequest } from "@/lib/sidebar/sidebarDangerDialog"; @@ -874,6 +875,7 @@ async function toggle(requestId = beginNavigationRequest()) { if (node.isLoading) { if (node.isExpanded) { node.isExpanded = false; + collapseSubtreeDescendants(node); if (shouldReleaseCollapsedTreeNodeChildren()) connectionStore.releaseCollapsedTreeNodeChildren(node.id); connectionStore.cancelTreeNodeLoad(node.id); emitNodeToggled(node, true, false); @@ -888,6 +890,7 @@ async function toggle(requestId = beginNavigationRequest()) { if (node.type === "connection-group") { node.isExpanded = !node.isExpanded; + if (!node.isExpanded) collapseSubtreeDescendants(node); connectionStore.toggleConnectionGroupCollapsed(node.id); emitNodeToggled(node, wasExpanded); return; @@ -895,12 +898,14 @@ async function toggle(requestId = beginNavigationRequest()) { if (node.type === "group-partitions") { node.isExpanded = !node.isExpanded; + if (!node.isExpanded) collapseSubtreeDescendants(node); emitNodeToggled(node, wasExpanded); return; } if (node.type === "table-vgroup") { node.isExpanded = !node.isExpanded; + if (!node.isExpanded) collapseSubtreeDescendants(node); if (node.vgroupId) connectionStore.toggleTableVGroupCollapsed(node, node.vgroupId); emitNodeToggled(node, wasExpanded); return; @@ -908,6 +913,7 @@ async function toggle(requestId = beginNavigationRequest()) { if (node.type === "type" && customTypeCapabilities(currentDatabaseType()).details && node.children !== undefined) { node.isExpanded = node.children.length > 0 ? !node.isExpanded : false; + if (!node.isExpanded) collapseSubtreeDescendants(node); emitNodeToggled(node, wasExpanded); return; } @@ -919,6 +925,7 @@ async function toggle(requestId = beginNavigationRequest()) { // schema and replace the package's member list with unrelated objects. if (currentDatabaseType() === "xugu" && (node.type === "group-procedures" || node.type === "group-functions") && node.parentType === "package") { node.isExpanded = !node.isExpanded; + if (!node.isExpanded) collapseSubtreeDescendants(node); // Package member groups have no group-level reload path: their children // are hydrated together with the owning package. Releasing a large group // here would leave it empty on the next local-only expand. @@ -933,6 +940,7 @@ async function toggle(requestId = beginNavigationRequest()) { const needsConfiguredTablePageDrain = node.type === "group-tables" && connectionStore.getConfig(node.connectionId || "")?.sidebar_auto_load_all_tables === true && hasTableTreeLoadMore(node.children ?? []); if (databaseObjectGroup && connectionStore.canUseLoadedTreeNodeToggle(node) && !needsConfiguredTablePageDrain) { node.isExpanded = !node.isExpanded; + if (!node.isExpanded) collapseSubtreeDescendants(node); if (wasExpanded && shouldReleaseCollapsedTreeNodeChildren()) connectionStore.releaseCollapsedTreeNodeChildren(node.id); emitNodeToggled(node, wasExpanded); return; @@ -940,18 +948,21 @@ async function toggle(requestId = beginNavigationRequest()) { if (node.type === "type-attributes" || node.type === "type-methods") { node.isExpanded = !node.isExpanded; + if (!node.isExpanded) collapseSubtreeDescendants(node); emitNodeToggled(node, wasExpanded); return; } if (node.type === "saved-sql-root" || node.type === "saved-sql-folder") { node.isExpanded = !node.isExpanded; + if (!node.isExpanded) collapseSubtreeDescendants(node); emitNodeToggled(node, wasExpanded); return; } if ((node.type === "group-extensions" || node.type === "group-event-triggers") && connectionStore.canUseLoadedTreeNodeToggle(node)) { node.isExpanded = !node.isExpanded; + if (!node.isExpanded) collapseSubtreeDescendants(node); if (wasExpanded && shouldReleaseCollapsedTreeNodeChildren()) connectionStore.releaseCollapsedTreeNodeChildren(node.id); emitNodeToggled(node, wasExpanded); return; @@ -961,6 +972,7 @@ async function toggle(requestId = beginNavigationRequest()) { // with the tablespace listing; expanding them is a pure local toggle. if (node.type === "tablespace" || node.type === "group-datafiles") { node.isExpanded = !node.isExpanded; + if (!node.isExpanded) collapseSubtreeDescendants(node); emitNodeToggled(node, wasExpanded); return; } @@ -968,6 +980,7 @@ async function toggle(requestId = beginNavigationRequest()) { if (node.type === "group-tablespaces") { if (connectionStore.canUseLoadedTreeNodeToggle(node)) { node.isExpanded = !node.isExpanded; + if (!node.isExpanded) collapseSubtreeDescendants(node); if (wasExpanded && shouldReleaseCollapsedTreeNodeChildren()) connectionStore.releaseCollapsedTreeNodeChildren(node.id); emitNodeToggled(node, wasExpanded); return; @@ -977,7 +990,10 @@ async function toggle(requestId = beginNavigationRequest()) { emitNodeToggled(node, wasExpanded); } catch (e: any) { if (!isCurrentNavigationRequest(requestId)) return; - if (!wasExpanded) node.isExpanded = false; + if (!wasExpanded) { + node.isExpanded = false; + collapseSubtreeDescendants(node); + } const errMsg = e?.message || String(e); if (errMsg.includes(CONNECTION_ATTEMPT_CANCELLED_MESSAGE)) return; toast(t("connection.connectFailed", { message: translateBackendError(t, e) }), 5000); @@ -994,6 +1010,7 @@ async function toggle(requestId = beginNavigationRequest()) { if (node.isExpanded) { node.isExpanded = false; + collapseSubtreeDescendants(node); if (shouldReleaseCollapsedTreeNodeChildren()) connectionStore.releaseCollapsedTreeNodeChildren(node.id); emitNodeToggled(node, wasExpanded, false); return; @@ -7304,7 +7321,14 @@ function activateRuntimeNode(node: TreeNode) { function emitNodeToggled(node: TreeNode, wasExpanded: boolean, expandedOverride?: boolean) { const liveNode = findSidebarActionTarget(connectionStore.treeNodes, createSidebarActionTarget(node)) ?? node; activeNode.value = liveNode; - emit("node-toggled", node, expandedOverride ?? !wasExpanded); + const isExpanded = expandedOverride ?? !wasExpanded; + if (!isExpanded) { + collapseSubtreeDescendants(node); + if (liveNode !== node) { + collapseSubtreeDescendants(liveNode); + } + } + emit("node-toggled", node, isExpanded); } function activateActionTarget(target: SidebarActionTarget) { diff --git a/apps/desktop/src/components/sidebar/__tests__/SidebarTreeRuntimeHost.expansion.spec.ts b/apps/desktop/src/components/sidebar/__tests__/SidebarTreeRuntimeHost.expansion.spec.ts index 53e6644abd..328dd1e1aa 100644 --- a/apps/desktop/src/components/sidebar/__tests__/SidebarTreeRuntimeHost.expansion.spec.ts +++ b/apps/desktop/src/components/sidebar/__tests__/SidebarTreeRuntimeHost.expansion.spec.ts @@ -911,4 +911,135 @@ describe("SidebarTreeRuntimeHost expansion", () => { expect(connectionStore.loadXuguTablespaces).not.toHaveBeenCalled(); expect(toggled).toHaveBeenCalledWith(datafileGroup, true); }); + + it("collapses all child and descendant nodes when a parent node is collapsed", async () => { + const columnsGroup: TreeNode = { + id: "mysql:db:table:users:group-columns", + label: "Columns", + type: "group-columns", + connectionId: "mysql", + database: "db", + tableName: "users", + isExpanded: true, + children: [{ id: "mysql:db:table:users:col:id", label: "id", type: "column", connectionId: "mysql", database: "db", tableName: "users" }], + }; + const tableNode: TreeNode = { + id: "mysql:db:table:users", + label: "users", + type: "table", + connectionId: "mysql", + database: "db", + isExpanded: true, + children: [columnsGroup], + }; + const tablesGroup: TreeNode = { + id: "mysql:db:group-tables", + label: "Tables", + type: "group-tables", + connectionId: "mysql", + database: "db", + isExpanded: true, + children: [tableNode], + }; + const databaseNode: TreeNode = { + id: "mysql:db", + label: "db", + type: "database", + connectionId: "mysql", + database: "db", + isExpanded: true, + children: [tablesGroup], + }; + + connectionStore.treeNodes = [databaseNode]; + connectionStore.loadTables.mockImplementation(async () => { + databaseNode.isExpanded = true; + }); + const host = ref | null>(null); + const toggled = vi.fn((node: TreeNode, expanded: boolean) => { + syncSidebarTreeNodeExpansion(connectionStore.treeNodes, node, expanded); + }); + const app = createApp( + defineComponent({ + setup: () => () => h(SidebarTreeRuntimeHost, { ref: host, node: databaseNode, depth: 0, onNodeToggled: toggled }), + }), + ); + mountedApps.push(app); + const container = document.createElement("div"); + document.body.append(container); + app.use(i18n); + app.mount(container); + + // Collapsing databaseNode + host.value?.toggleNode(databaseNode); + await nextTick(); + + expect(databaseNode.isExpanded).toBe(false); + expect(tablesGroup.isExpanded).toBe(false); + expect(tableNode.isExpanded).toBe(false); + expect(columnsGroup.isExpanded).toBe(false); + expect(toggled).toHaveBeenCalledWith(databaseNode, false); + + // Re-expanding databaseNode + host.value?.toggleNode(databaseNode); + await nextTick(); + + expect(databaseNode.isExpanded).toBe(true); + // Descendants must remain collapsed after parent is re-expanded + expect(tablesGroup.isExpanded).toBe(false); + expect(tableNode.isExpanded).toBe(false); + expect(columnsGroup.isExpanded).toBe(false); + }); + + it("collapses all child connections and their descendants when a connection-group collapses", async () => { + const databaseNode: TreeNode = { + id: "mysql:db", + label: "db", + type: "database", + connectionId: "mysql", + database: "db", + isExpanded: true, + children: [], + }; + const connectionNode: TreeNode = { + id: "mysql", + label: "MySQL", + type: "connection", + connectionId: "mysql", + isExpanded: true, + children: [databaseNode], + }; + const groupNode: TreeNode = { + id: "group-1", + label: "Dev Group", + type: "connection-group", + isExpanded: true, + children: [connectionNode], + }; + + connectionStore.treeNodes = [groupNode]; + connectionStore.toggleConnectionGroupCollapsed = vi.fn(); + + const host = ref | null>(null); + const toggled = vi.fn((node: TreeNode, expanded: boolean) => { + syncSidebarTreeNodeExpansion(connectionStore.treeNodes, node, expanded); + }); + const app = createApp( + defineComponent({ + setup: () => () => h(SidebarTreeRuntimeHost, { ref: host, node: groupNode, depth: 0, onNodeToggled: toggled }), + }), + ); + mountedApps.push(app); + const container = document.createElement("div"); + document.body.append(container); + app.use(i18n); + app.mount(container); + + host.value?.toggleNode(groupNode); + await nextTick(); + + expect(groupNode.isExpanded).toBe(false); + expect(connectionNode.isExpanded).toBe(false); + expect(databaseNode.isExpanded).toBe(false); + }); }); diff --git a/apps/desktop/src/lib/sidebar/__tests__/sidebarTreeCollapse.spec.ts b/apps/desktop/src/lib/sidebar/__tests__/sidebarTreeCollapse.spec.ts new file mode 100644 index 0000000000..5edeb1b15e --- /dev/null +++ b/apps/desktop/src/lib/sidebar/__tests__/sidebarTreeCollapse.spec.ts @@ -0,0 +1,120 @@ +import { describe, expect, it } from "vitest"; +import type { TreeNode } from "@/types/database"; +import { collapseExpandedTreeNodes, collapseSubtreeDescendants } from "@/lib/sidebar/sidebarTreeCollapse"; +import { toggleGroupCollapsed, emptyLayout, createGroup } from "@/lib/sidebar/sidebarLayout"; +import { toggleTableVGroupCollapsed, emptyTableVGroupLayout, createTableVGroup } from "@/lib/table/tableVGroup"; + +describe("sidebarTreeCollapse", () => { + it("collapseSubtreeDescendants collapses all child and descendant nodes while keeping parent untouched", () => { + const parent: TreeNode = { + id: "parent", + label: "Parent", + type: "database", + isExpanded: true, + children: [ + { + id: "child-1", + label: "Child 1", + type: "schema", + isExpanded: true, + children: [ + { + id: "grandchild-1", + label: "Grandchild 1", + type: "group-tables", + isExpanded: true, + children: [ + { + id: "table-1", + label: "Table 1", + type: "table", + isExpanded: true, + }, + ], + }, + { + id: "grandchild-2", + label: "Grandchild 2", + type: "group-views", + isExpanded: false, + }, + ], + }, + { + id: "child-2", + label: "Child 2", + type: "schema", + isExpanded: true, + }, + ], + }; + + const count = collapseSubtreeDescendants(parent); + expect(count).toBe(4); + // Parent itself is untouched + expect(parent.isExpanded).toBe(true); + // Children and descendants are collapsed + expect(parent.children![0].isExpanded).toBe(false); + expect(parent.children![0].children![0].isExpanded).toBe(false); + expect(parent.children![0].children![0].children![0].isExpanded).toBe(false); + expect(parent.children![0].children![1].isExpanded).toBe(false); + expect(parent.children![1].isExpanded).toBe(false); + }); + + it("collapseSubtreeDescendants returns 0 when node has no children", () => { + const leaf: TreeNode = { + id: "leaf", + label: "Leaf", + type: "table", + isExpanded: true, + }; + expect(collapseSubtreeDescendants(leaf)).toBe(0); + expect(leaf.isExpanded).toBe(true); + }); + + it("toggleGroupCollapsed collapses all descendant connection groups when parent group collapses", () => { + let layout = emptyLayout(); + const g1 = createGroup(layout, "Parent Group"); + layout = g1.layout; + const g2 = createGroup(layout, "Child Group", g1.groupId); + layout = g2.layout; + const g3 = createGroup(layout, "Grandchild Group", g2.groupId); + layout = g3.layout; + + // Initially all 3 groups are expanded (collapsed = false) + expect(layout.groups.find((g) => g.id === g1.groupId)?.collapsed).toBe(false); + expect(layout.groups.find((g) => g.id === g2.groupId)?.collapsed).toBe(false); + expect(layout.groups.find((g) => g.id === g3.groupId)?.collapsed).toBe(false); + + // Collapsing Parent Group collapses itself and all its descendant groups + const collapsed = toggleGroupCollapsed(layout, g1.groupId); + expect(collapsed.groups.find((g) => g.id === g1.groupId)?.collapsed).toBe(true); + expect(collapsed.groups.find((g) => g.id === g2.groupId)?.collapsed).toBe(true); + expect(collapsed.groups.find((g) => g.id === g3.groupId)?.collapsed).toBe(true); + + // Expanding Parent Group reopens Parent Group only; descendant groups stay collapsed + const reExpanded = toggleGroupCollapsed(collapsed, g1.groupId); + expect(reExpanded.groups.find((g) => g.id === g1.groupId)?.collapsed).toBe(false); + expect(reExpanded.groups.find((g) => g.id === g2.groupId)?.collapsed).toBe(true); + expect(reExpanded.groups.find((g) => g.id === g3.groupId)?.collapsed).toBe(true); + }); + + it("toggleTableVGroupCollapsed collapses all descendant virtual groups when parent group collapses", () => { + let layout = emptyTableVGroupLayout(); + const g1 = createTableVGroup(layout, "Parent VGroup"); + layout = g1.layout; + const g2 = createTableVGroup(layout, "Child VGroup", g1.groupId); + layout = g2.layout; + + expect(layout.groups.find((g) => g.id === g1.groupId)?.collapsed).toBe(false); + expect(layout.groups.find((g) => g.id === g2.groupId)?.collapsed).toBe(false); + + const collapsed = toggleTableVGroupCollapsed(layout, g1.groupId); + expect(collapsed.groups.find((g) => g.id === g1.groupId)?.collapsed).toBe(true); + expect(collapsed.groups.find((g) => g.id === g2.groupId)?.collapsed).toBe(true); + + const reExpanded = toggleTableVGroupCollapsed(collapsed, g1.groupId); + expect(reExpanded.groups.find((g) => g.id === g1.groupId)?.collapsed).toBe(false); + expect(reExpanded.groups.find((g) => g.id === g2.groupId)?.collapsed).toBe(true); + }); +}); diff --git a/apps/desktop/src/lib/sidebar/sidebarLayout.ts b/apps/desktop/src/lib/sidebar/sidebarLayout.ts index 159f0c7659..c584be5d60 100644 --- a/apps/desktop/src/lib/sidebar/sidebarLayout.ts +++ b/apps/desktop/src/lib/sidebar/sidebarLayout.ts @@ -647,9 +647,30 @@ export function deleteGroup(layout: SidebarLayout, groupId: string): SidebarLayo } export function toggleGroupCollapsed(layout: SidebarLayout, groupId: string): SidebarLayout { + const targetGroup = layout.groups.find((group) => group.id === groupId); + const willCollapse = targetGroup ? !targetGroup.collapsed : false; + if (!willCollapse) { + return { + ...layout, + groups: layout.groups.map((group) => (group.id === groupId ? { ...group, collapsed: false } : group)), + }; + } + + const groupEntry = findGroupEntry(layout.order, groupId); + const descendantGroupIds = new Set(); + const collectDescendants = (entry: Extract) => { + for (const child of entryChildren(entry)) { + if (child.type === "group") { + descendantGroupIds.add(child.id); + collectDescendants(child); + } + } + }; + if (groupEntry) collectDescendants(groupEntry); + return { ...layout, - groups: layout.groups.map((group) => (group.id === groupId ? { ...group, collapsed: !group.collapsed } : group)), + groups: layout.groups.map((group) => (group.id === groupId || descendantGroupIds.has(group.id) ? { ...group, collapsed: true } : group)), }; } diff --git a/apps/desktop/src/lib/sidebar/sidebarTreeCollapse.ts b/apps/desktop/src/lib/sidebar/sidebarTreeCollapse.ts index 74a775e2ca..2fa6ca3f6b 100644 --- a/apps/desktop/src/lib/sidebar/sidebarTreeCollapse.ts +++ b/apps/desktop/src/lib/sidebar/sidebarTreeCollapse.ts @@ -15,3 +15,7 @@ export function collapseExpandedTreeNodes(nodes: TreeNode[]): number { return collapsedCount; } +export function collapseSubtreeDescendants(node: TreeNode): number { + if (!node.children?.length) return 0; + return collapseExpandedTreeNodes(node.children); +} diff --git a/apps/desktop/src/lib/sidebar/sidebarTreeExpansion.ts b/apps/desktop/src/lib/sidebar/sidebarTreeExpansion.ts index 3d575f0d4d..5bfbdcb719 100644 --- a/apps/desktop/src/lib/sidebar/sidebarTreeExpansion.ts +++ b/apps/desktop/src/lib/sidebar/sidebarTreeExpansion.ts @@ -1,9 +1,16 @@ import type { TreeNode } from "@/types/database"; import { findSidebarActionTarget } from "@/lib/sidebar/sidebarActionTarget"; +import { collapseSubtreeDescendants } from "@/lib/sidebar/sidebarTreeCollapse"; + +export { collapseSubtreeDescendants } from "@/lib/sidebar/sidebarTreeCollapse"; export function syncSidebarTreeNodeExpansion(nodes: readonly TreeNode[], renderedNode: TreeNode, expanded: boolean): boolean { const liveNode = findSidebarActionTarget(nodes, renderedNode); - if (!liveNode || liveNode === renderedNode || liveNode.isExpanded === expanded) return false; + if (!liveNode) return false; + if (!expanded) { + collapseSubtreeDescendants(liveNode); + } + if (liveNode === renderedNode || liveNode.isExpanded === expanded) return false; liveNode.isExpanded = expanded; return true; } diff --git a/apps/desktop/src/lib/table/tableVGroup.ts b/apps/desktop/src/lib/table/tableVGroup.ts index 1c66027988..741fb589a2 100644 --- a/apps/desktop/src/lib/table/tableVGroup.ts +++ b/apps/desktop/src/lib/table/tableVGroup.ts @@ -340,9 +340,30 @@ export function deleteTableVGroups(layout: TableVGroupLayout, groupIds: Iterable } export function toggleTableVGroupCollapsed(layout: TableVGroupLayout, groupId: string): TableVGroupLayout { + const targetGroup = layout.groups.find((group) => group.id === groupId); + const willCollapse = targetGroup ? !targetGroup.collapsed : false; + if (!willCollapse) { + return { + ...layout, + groups: layout.groups.map((group) => (group.id === groupId ? { ...group, collapsed: false } : group)), + }; + } + + const groupEntry = findGroupEntry(layout.order, groupId); + const descendantGroupIds = new Set(); + const collectDescendants = (entry: TableVGroupEntry) => { + for (const child of entryChildren(entry)) { + if (child.type === "group") { + descendantGroupIds.add(child.id); + collectDescendants(child); + } + } + }; + if (groupEntry && groupEntry.type === "group") collectDescendants(groupEntry); + return { ...layout, - groups: layout.groups.map((group) => (group.id === groupId ? { ...group, collapsed: !group.collapsed } : group)), + groups: layout.groups.map((group) => (group.id === groupId || descendantGroupIds.has(group.id) ? { ...group, collapsed: true } : group)), }; } diff --git a/apps/desktop/src/stores/connectionStore.ts b/apps/desktop/src/stores/connectionStore.ts index 57f9e84fc7..4aa2dddaf7 100644 --- a/apps/desktop/src/stores/connectionStore.ts +++ b/apps/desktop/src/stores/connectionStore.ts @@ -126,7 +126,7 @@ import { import { buildDatabaseTreeNodes, buildDuckDbConnectionTreeNodes, compareSidebarNames, sortSidebarDatabases, sortSidebarNames, shouldIncludeDefaultDatabaseNode } from "@/lib/database/databaseTree"; import { spannerDisplayDatabase, spannerSchemaDisplayName } from "@/lib/connection/spannerResourcePath"; import { buildSqlServerDatabaseTreeNodes } from "@/lib/database/sqlServerTree"; -import { collapseExpandedTreeNodes } from "@/lib/sidebar/sidebarTreeCollapse"; +import { collapseExpandedTreeNodes, collapseSubtreeDescendants } from "@/lib/sidebar/sidebarTreeCollapse"; import { findNodePathByIdentity, nodeMatchesRegexScopeIdentity, type SidebarRegexScopeIdentity } from "@/lib/sidebar/sidebarSearchTree"; import { findDatabaseTreeNode } from "@/lib/sidebar/treeRefreshTarget"; import { simpleModeEmptyShellNeedsConfirmedLoad, treeNodeLoadedChildrenContentPresent } from "@/lib/sidebar/treeLoadedChildrenMarker"; @@ -8225,6 +8225,7 @@ export const useConnectionStore = defineStore("connection", () => { if (!connectionId || !database) return; if (node.isExpanded && !options?.force) { node.isExpanded = false; + collapseSubtreeDescendants(node); if (!sidebarSearchQuery.value && !options?.preserveCollapsedChildren) releaseCollapsedTreeNodeChildren(node.id); return; } @@ -10599,6 +10600,10 @@ export const useConnectionStore = defineStore("connection", () => { return connectionIdsInGroupsOp(sidebarLayout.value, groupIds); }, toggleConnectionGroupCollapsed(groupId: string) { + const groupNode = findNode(treeNodes.value, groupId); + if (groupNode) { + collapseSubtreeDescendants(groupNode); + } updateLayoutAndRebuild(toggleGroupCollapsedOp(sidebarLayout.value, groupId)); }, expandConnectionGroups(groupIds: Iterable) {