diff --git a/calm-plugins/vscode/src/extension/services/hub-status-bar.test.ts b/calm-plugins/vscode/src/extension/services/hub-status-bar.test.ts index 56fb8bddd1..bf217374be 100644 --- a/calm-plugins/vscode/src/extension/services/hub-status-bar.test.ts +++ b/calm-plugins/vscode/src/extension/services/hub-status-bar.test.ts @@ -96,6 +96,15 @@ describe('HubStatusBar', () => { expect(mockItem.tooltip).toContain('○ ns2'); expect(mockItem.tooltip).toContain('✓ ns3'); }); + + it('marks every namespace as selected when the selection is empty', () => { + statusBar.setState('connected', 2); + statusBar.setAvailableAndSelected(['alpha', 'beta'], []); + expect(mockItem.text).toBe('$(cloud) CALM Hub (2)'); + expect(mockItem.tooltip).toContain('Namespaces (2 selected / 2 available)'); + expect(mockItem.tooltip).toContain('✓ alpha'); + expect(mockItem.tooltip).toContain('✓ beta'); + }); }); describe('dispose', () => { diff --git a/calm-plugins/vscode/src/extension/services/hub-status-bar.ts b/calm-plugins/vscode/src/extension/services/hub-status-bar.ts index afaf492aab..2ad8df32c7 100644 --- a/calm-plugins/vscode/src/extension/services/hub-status-bar.ts +++ b/calm-plugins/vscode/src/extension/services/hub-status-bar.ts @@ -1,4 +1,5 @@ import * as vscode from 'vscode'; +import { resolveSelectedNamespaces } from './namespace-selection'; export type ConnectionState = 'disconnected' | 'connecting' | 'connected' | 'error'; @@ -64,11 +65,13 @@ export class HubStatusBar { const countLabel = selCount > 0 ? `${selCount}/${availCount}` : `${availCount}`; this.statusBarItem.text = `$(cloud) CALM Hub (${countLabel})`; + // An empty selection means "all namespaces", so the tooltip resolves it (#3493). + const selected = resolveSelectedNamespaces(this.availableNamespaces, this.selectedNamespaces); const nsList = this.availableNamespaces.map((ns) => - this.selectedNamespaces.includes(ns) ? ` ✓ ${ns}` : ` ○ ${ns}` + selected.includes(ns) ? ` ✓ ${ns}` : ` ○ ${ns}` ).join('\n'); this.statusBarItem.tooltip = (this.hubUrl ? `${this.hubUrl}\n\n` : '') + - `Namespaces (${selCount} selected / ${availCount} available):\n` + + `Namespaces (${selected.length} selected / ${availCount} available):\n` + nsList + '\n\nClick to manage'; this.statusBarItem.backgroundColor = undefined; diff --git a/calm-plugins/vscode/src/extension/services/namespace-selection.test.ts b/calm-plugins/vscode/src/extension/services/namespace-selection.test.ts new file mode 100644 index 0000000000..79ab454d13 --- /dev/null +++ b/calm-plugins/vscode/src/extension/services/namespace-selection.test.ts @@ -0,0 +1,27 @@ +import { describe, it, expect } from 'vitest'; +import { resolveSelectedNamespaces } from './namespace-selection'; + +describe('resolveSelectedNamespaces', () => { + const available = ['alpha', 'beta', 'gamma']; + + it('treats an empty selection as "all namespaces"', () => { + expect(resolveSelectedNamespaces(available, [])).toEqual(available); + }); + + it('treats an unset selection as "all namespaces"', () => { + expect(resolveSelectedNamespaces(available, undefined)).toEqual(available); + expect(resolveSelectedNamespaces(available, null)).toEqual(available); + }); + + it('keeps only the selected namespaces', () => { + expect(resolveSelectedNamespaces(available, ['gamma', 'alpha'])).toEqual(['alpha', 'gamma']); + }); + + it('drops names that the hub no longer lists', () => { + expect(resolveSelectedNamespaces(available, ['alpha', 'gone'])).toEqual(['alpha']); + }); + + it('returns nothing when the hub lists no namespaces', () => { + expect(resolveSelectedNamespaces([], [])).toEqual([]); + }); +}); diff --git a/calm-plugins/vscode/src/extension/services/namespace-selection.ts b/calm-plugins/vscode/src/extension/services/namespace-selection.ts new file mode 100644 index 0000000000..cf1d6e0128 --- /dev/null +++ b/calm-plugins/vscode/src/extension/services/namespace-selection.ts @@ -0,0 +1,17 @@ +/** + * Resolve the `calm.hub.selectedNamespaces` setting against the namespaces the + * Hub lists. + * + * CALM Hub has no namespace selection of its own: the filter is this plugin's + * setting. VS Code reports an unset string array as `[]`, so `undefined`, `null` + * and `[]` all mean "every namespace". + */ +export function resolveSelectedNamespaces( + available: readonly string[], + selection: readonly string[] | null | undefined +): string[] { + if (!selection || selection.length === 0) { + return [...available]; + } + return available.filter((namespace) => selection.includes(namespace)); +} diff --git a/calm-plugins/vscode/src/extension/webview/canvas-panel.ts b/calm-plugins/vscode/src/extension/webview/canvas-panel.ts index b64d8af6e7..deb60315e9 100644 --- a/calm-plugins/vscode/src/extension/webview/canvas-panel.ts +++ b/calm-plugins/vscode/src/extension/webview/canvas-panel.ts @@ -8,6 +8,7 @@ import { DiagramExportService } from '../services/diagram-export-service'; import { HubClient, describeHubError } from '../services/hub-client'; import { HubAssetService, type HubNamespace } from '../services/hub-asset-service'; import { ControlAssetService, LOCAL_DOMAIN } from '../services/control-asset-service'; +import { resolveSelectedNamespaces } from '../services/namespace-selection'; import { ShaCacheService } from '../services/sha-cache-service'; import { SvgImportService } from '../services/svg-import'; import { @@ -346,10 +347,7 @@ export class CanvasPanel { const allNs = this.hubAssetService?.getNamespaces().map(ns => ns.name) ?? []; const userSelection = vscode.workspace.getConfiguration('calm.hub').get('selectedNamespaces'); - // VS Code defaults unset arrays to []. Treat null/undefined/[] as "show all". - const selectedNs = (userSelection && userSelection.length > 0) - ? allNs.filter(ns => userSelection.includes(ns)) - : allNs; + const selectedNs = resolveSelectedNamespaces(allNs, userSelection); const hubBlocks = this.hubAssetService?.getAllStandardNodes(selectedNs) ?? []; const hubPatterns = this.hubAssetService?.getAllPatterns(selectedNs) ?? [];