How SecureDesign Handles Multi-Root Workspaces in VS Code: Implementation Deep Dive

SecureDesign isolates state, file watching, and design storage across VS Code multi-root workspaces by generating deterministic workspace IDs from sorted folder URIs, namespacing all persisted data, and creating dedicated file watchers for each root folder.

SecureDesign is a VS Code extension that manages secure design files and chat history across varying project structures. The extension is engineered to function identically whether you open a single folder or a complex multi-root workspace containing multiple unrelated projects. According to the hbmartin/secure-design source code, the extension achieves this through five key architectural mechanisms that ensure complete data isolation per workspace root.

Generating Stable Workspace Identifiers

The foundation of multi-root support begins with deterministic workspace identification. In src/services/workspaceStateService.ts, the getWorkspaceId() method (lines 41-58) creates a unique, order-independent identifier that remains stable regardless of how folders are arranged in the workspace.

For a single-folder workspace, the method returns the folder URI directly. For multi-root workspaces, the implementation:

  • Collects URIs from all workspace folders
  • Sorts them lexicographically to eliminate ordering dependencies
  • Removes duplicates to handle edge cases
  • Joins them with the | delimiter

The resulting string is then hashed to base-36 to generate compact keys for VS Code’s workspaceState storage. This ensures that file:///project/a|file:///project/b produces the same identifier as file:///project/b|file:///project/a, preventing data fragmentation when users rearrange workspace folders.

Isolating State with Namespaced Persistence

All persisted data in SecureDesign is automatically scoped to the current workspace through the WorkspaceStateService class. Located in src/services/workspaceStateService.ts (lines 76-89), the service's get() and update() methods automatically prefix keys with the hashed workspace ID generated by getWorkspaceId().

This namespacing guarantees that settings, chat history, and cached design information remain isolated per workspace. When you switch between different multi-root configurations, SecureDesign retrieves only the data relevant to the currently active workspace identifier, preventing cross-contamination between projects.

Per-Folder File Watching

File system monitoring in multi-root environments requires dedicated watchers for each root folder. The FileWatcherService in src/services/fileWatcherService.ts (lines 86-122) implements this by creating a distinct vscode.FileSystemWatcher for every folder in the workspace.

Each watcher uses vscode.RelativePattern scoped specifically to its folder, ensuring that glob patterns like **/*.html only match files within that particular root. When file change events fire, the service includes the workspaceName property in the payload when more than one folder is present, allowing event handlers to distinguish between identically-named files in different roots.

Detecting Workspace Changes

SecureDesign monitors structural changes to the workspace through the vscode.workspace.onDidChangeWorkspaceFolders event listener implemented in src/extension.ts (lines 73-88).

When folders are added or removed, the extension invokes WorkspaceStateService.hasWorkspaceChanged() to compare the newly computed workspace ID against the previously stored hash. If the workspace has changed—for instance, when adding a third folder to a two-folder workspace—the extension can trigger appropriate lifecycle actions such as resetting chat history, re-initializing file watchers, or updating UI state to reflect the new workspace composition.

Tracking Origin in Design Files

The DesignFile type defined in src/types/designFile.ts (lines 11-12) includes an optional workspaceName property. This metadata allows generated design mock-ups and secure documentation to carry context about their originating folder within a multi-root workspace.

When designs are created or loaded, this property enables the extension to link files back to their specific root folder, ensuring that operations like "reveal in explorer" target the correct location even when multiple folders contain similar file structures.

Implementation Example

The following code demonstrates how SecureDesign integrates these mechanisms to handle both single and multi-root configurations:

// 1️⃣ Get the stable ID for the current workspace (single- or multi-root)
import { WorkspaceStateService } from './services/workspaceStateService';
const wsId = WorkspaceStateService.getInstance().getWorkspaceId();
// → "file:///c:/proj/a|file:///c:/proj/b" (sorted & hashed internally)

// 2️⃣ Store a value that is isolated per workspace
WorkspaceStateService.getInstance().update('chatHistory', historyArray);

// 3️⃣ File watcher that automatically includes the workspace name
import { FileWatcherService } from './services/fileWatcherService';
const watcher = new FileWatcherService();
watcher.setupWatcher({
  pattern: '**/*.html',
  onFileChange: ({ fileName, relativePath, workspaceName, changeType }) => {
    console.log(
      `🗂 ${workspaceName ?? 'single'} – ${changeType}: ${relativePath}`
    );
  },
});

Summary

  • Stable Workspace IDs: SecureDesign generates deterministic identifiers by sorting and hashing all folder URIs in multi-root workspaces, ensuring consistent state keys regardless of folder order.
  • Namespaced Storage: All persisted data in WorkspaceStateService is automatically prefixed with the workspace ID, isolating settings and history per workspace configuration.
  • Dedicated File Watchers: The FileWatcherService creates individual FileSystemWatcher instances for each root folder using vscode.RelativePattern, with events tagged by workspaceName.
  • Dynamic Change Detection: The extension monitors onDidChangeWorkspaceFolders to detect structural changes and reset state when the workspace composition changes.
  • Workspace Metadata: Design files carry optional workspaceName properties to maintain linkage with their originating folder in multi-root scenarios.

Frequently Asked Questions

How does SecureDesign generate unique identifiers for multi-root workspaces?

SecureDesign collects the URIs of all folders in the workspace, sorts them lexicographically to eliminate ordering dependencies, joins them with the | delimiter, and hashes the result to base-36. This deterministic approach ensures that the same set of folders always produces the same identifier in src/services/workspaceStateService.ts, regardless of the order in which they were added to the workspace.

Does SecureDesign create separate file watchers for each folder in a workspace?

Yes. The FileWatcherService in src/services/fileWatcherService.ts iterates through all workspace folders and creates a dedicated vscode.FileSystemWatcher for each one using vscode.RelativePattern. This ensures that file watching is scoped to individual roots, and change events include the workspaceName parameter to identify which folder triggered the event.

What happens to chat history when workspace folders are added or removed?

When the workspace structure changes, SecureDesign detects the modification via vscode.workspace.onDidChangeWorkspaceFolders in src/extension.ts and compares the new workspace ID against the cached value using hasWorkspaceChanged(). If the workspace has changed, the extension can reset chat history, re-initialize watchers, and update UI state to ensure data integrity across different workspace configurations.

How can design files identify which workspace folder they belong to?

The DesignFile interface defined in src/types/designFile.ts includes an optional workspaceName property that stores the originating folder name. When designs are created in a multi-root workspace, this metadata is populated to link the file back to its specific root folder, enabling accurate navigation and context-aware operations even when multiple folders share similar file structures.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →