Skip to content

Add unicorn and auto fix#52

Open
hbmartin wants to merge 1 commit into
mainfrom
add-unicorn
Open

Add unicorn and auto fix#52
hbmartin wants to merge 1 commit into
mainfrom
add-unicorn

Conversation

@hbmartin
Copy link
Copy Markdown
Owner

@hbmartin hbmartin commented Sep 8, 2025

PR Type

Enhancement, Other


Description

• Added ESLint unicorn plugin with recommended rules for modern JavaScript patterns
• Modernized Node.js imports to use built-in module syntax (node:fs, node:path, etc.)
• Improved code readability with numeric separators for large values (200000 → 200_000)
• Standardized parameter and variable naming throughout codebase (paramsparameters, argsarguments_)
• Converted legacy iteration patterns (forEachfor...of, Array.from → spread operator)
• Enhanced string manipulation methods (replacereplaceAll, substrslice)
• Added proper block scoping to switch cases with curly braces
• Modernized array access patterns (array indexing → at(-1) method)
• Updated DOM manipulation methods (appendChildappend, removeChildremove)
• Renamed React component interfaces to use Properties suffix for consistency
• Improved conditional logic flow with inverted conditions and ternary operators
• Enhanced error handling with better variable naming and appropriate error types


Diagram Walkthrough

flowchart LR
  A["ESLint Config"] --> B["Unicorn Plugin"]
  B --> C["Auto-fix Rules"]
  C --> D["Node.js Imports"]
  C --> E["Parameter Naming"]
  C --> F["Modern Syntax"]
  D --> G["node:fs, node:path"]
  E --> H["params → parameters"]
  F --> I["for...of loops"]
  F --> J["Numeric separators"]
Loading

File Walkthrough

Relevant files
Formatting
40 files
BedrockProvider.ts
Improve readability with numeric separators and parameter renaming

src/providers/implementations/BedrockProvider.ts

• Renamed type alias from ProviderInstanceParams to
ProviderInstanceParameters
• Added numeric separators to large token
values (e.g., 200000 → 200_000)
• Renamed parameter from params to
parameters in createInstance method
• Simplified ternary operator
logic in validateCredentials method

+32/-32 
OpenRouterProvider.ts
Apply consistent parameter naming and numeric formatting 

src/providers/implementations/OpenRouterProvider.ts

• Renamed type alias from ProviderInstanceParams to
ProviderInstanceParameters
• Added numeric separators to large token
values for better readability
• Renamed parameter from params to
parameters in createInstance method

+28/-28 
gridLayout.ts
Improve code clarity with modern loop syntax and naming   

src/webview/utils/gridLayout.ts

• Renamed loop variables from i to index for clarity
• Converted
forEach loops to for...of syntax throughout
• Replaced array access
with at(-1) method for getting last element
• Renamed variables for
better readability (nameWithoutExtnameWithoutExtension)
• Changed
parseInt to Number.parseInt for consistency

+27/-27 
extension.ts
Add proper switch case scoping and modernize imports         

src/extension.ts

• Added block scoping to switch cases with curly braces
• Changed
import from child_process to node:child_process
• Renamed variable
from doc to document for clarity

+29/-16 
chunkToolOutputToMessageToolOutput.ts
Enhance readability with better variable naming and scoping

src/services/chunkToolOutputToMessageToolOutput.ts

• Renamed function from getProp to getProperty
• Renamed variables for
clarity (objobject, msgmessage)
• Added block scoping to switch
cases with curly braces
• Improved variable naming throughout the file

+27/-21 
webviewMessageGuard.ts
Improve parameter naming and numeric formatting                   

src/services/webviewMessageGuard.ts

• Renamed parameters and variables for clarity (errerror_, func
function_, argsarguments_)
• Renamed loop variable from i to index

• Changed timeout value to use numeric separator (60000 → 60_000)

+19/-19 
ChatController.ts
Enhance parameter naming and conditional logic clarity     

src/chat/ChatController.ts

• Renamed parameter from params to parameters and args to arguments_

Added block scoping to switch cases
• Simplified conditional logic by
inverting condition
• Improved parameter naming consistency

+22/-19 
OpenAIProvider.ts
Apply consistent naming and numeric formatting                     

src/providers/implementations/OpenAIProvider.ts

• Renamed type alias and parameter for consistency
• Added numeric
separators to token values
• Applied consistent parameter naming
pattern

+15/-15 
useVscodeState.ts
Improve function and variable naming consistency                 

src/webview/hooks/useVscodeState.ts

• Renamed function from isFnKey to isFunctionKey
• Renamed variables
for clarity (msgmessage, argargument)
• Renamed parameters
throughout for better readability

+29/-29 
GoogleProvider.ts
Apply consistent naming and numeric formatting                     

src/providers/implementations/GoogleProvider.ts

• Renamed type alias and parameter for consistency
• Added numeric
separators to large token values
• Applied consistent parameter naming
pattern

+13/-13 
AnthropicProvider.ts
Apply consistent parameter naming and numeric formatting 

src/providers/implementations/AnthropicProvider.ts

• Renamed type alias from ProviderInstanceParams to
ProviderInstanceParameters
• Added numeric separators to token values

• Renamed parameter for consistency

+13/-13 
viewApi.ts
Enhance parameter and variable naming consistency               

src/api/viewApi.ts

• Renamed parameter from args to arguments_
• Renamed variables from
msg to message in type guard functions
• Improved parameter naming
consistency

+31/-31 
MoonshotProvider.ts
Apply consistent naming and numeric formatting                     

src/providers/implementations/MoonshotProvider.ts

• Renamed type alias and parameter for consistency
• Added numeric
separators to token values
• Applied consistent parameter naming
pattern

+10/-10 
ipcReducer.ts
Improve type definitions with better naming conventions   

src/types/ipcReducer.ts

• Renamed parameters and variables for clarity (argsarguments_,
propproperty, objobject)
• Renamed type alias from FnKeys to
FunctionKeys
• Improved naming consistency throughout type definitions

+24/-24 
useDebouncedSave.ts
Enhance variable naming with clearer reference naming       

src/webview/hooks/useDebouncedSave.ts

• Renamed ref variables with Reference suffix for clarity
• Renamed
variables for better readability (currentDataStrcurrentDataString)

• Improved variable naming consistency throughout

+18/-18 
SuperdesignCanvasPanel.ts
Improve control flow and array handling                                   

src/SuperdesignCanvasPanel.ts

• Added block scoping to switch cases
• Changed while loop condition
for clarity
• Used spread operator instead of Array.from for array
conversion

+11/-7   
chatSidebarProvider.ts
Enhance error handling and switch case structure                 

src/providers/chatSidebarProvider.ts

• Renamed variable from e to error in catch block
• Added block
scoping to switch cases
• Improved error handling variable naming

+16/-9   
logger.ts
Improve parameter handling and conditional logic                 

src/services/logger.ts

• Made data parameter optional with proper typing
• Renamed variable
from levelStr to levelString
• Inverted conditional logic for cleaner
flow

+9/-9     
themeParser.ts
Modernize iteration patterns and improve variable naming 

src/webview/utils/themeParser.ts

• Converted forEach loops to for...of syntax
• Renamed variable from
varKey to variableKey
• Consolidated array push operations into single
calls

+8/-17   
ProviderRegistry.ts
Modernize array creation with spread operator                       

src/providers/ProviderRegistry.ts

• Replaced Array.from with spread operator for array conversions

Modernized array creation from iterables

+4/-4     
types.ts
Standardize parameter interface naming                                     

src/providers/types.ts

• Renamed interface from ProviderInstanceParams to
ProviderInstanceParameters
• Updated abstract method signature to use
new parameter name

+2/-2     
workspaceStateService.ts
Improve loop variable naming consistency                                 

src/services/workspaceStateService.ts

• Renamed loop variable from i to index for clarity

+2/-2     
BaseRepository.ts
Modernize iteration pattern                                                           

src/types/BaseRepository.ts

• Converted forEach loop to for...of syntax

+2/-2     
ProviderService.ts
Simplify conditional logic flow                                                   

src/providers/ProviderService.ts

• Inverted conditional logic for cleaner ternary expression

+3/-3     
VsCodeConfiguration.ts
Simplify conditional logic flow                                                   

src/providers/VsCodeConfiguration.ts

• Inverted conditional logic for cleaner ternary expression

+3/-3     
htmlTemplate.ts
Improve loop variable naming consistency                                 

src/helpers/htmlTemplate.ts

• Renamed loop variable from i to index

+1/-1     
useLogger.ts
Simplify conditional logic flow                                                   

src/webview/hooks/useLogger.ts

• Inverted conditional logic for cleaner ternary expression

+3/-3     
agent.ts
Improve parameter naming consistency                                         

src/types/agent.ts

• Renamed parameter from prev to previous for clarity

+1/-1     
ChatInterface.tsx
Apply ESLint unicorn rules and modernize JavaScript patterns

src/webview/components/Chat/ChatInterface.tsx

• Renamed interface ChatInterfaceProps to ChatInterfaceProperties and
parameter prevState to previousState throughout
• Replaced forEach
loops with for...of loops and Array.from() with spread operator

Converted if-else chains to switch statements for better readability

Updated DOM manipulation methods (appendChild to append, removeChild
to remove)
• Applied various ESLint unicorn rule fixes for modern
JavaScript patterns

+137/-116
CanvasView.tsx
Apply ESLint unicorn rules and modernize canvas component

src/webview/components/CanvasView.tsx

• Renamed interface CanvasViewProps to CanvasViewProperties and
transformRef to transformReference
• Replaced forEach loops with
for...of loops for better performance
• Converted if-else chains to
switch statements with proper block scoping
• Updated event listener
methods and global object references (window to globalThis)

+65/-60 
WebviewContext.tsx
Apply ESLint unicorn rules to webview context                       

src/webview/contexts/WebviewContext.tsx

• Renamed interface WebviewProviderProps to WebviewProviderProperties
and parameters args to arguments_
• Updated string manipulation
methods and replaced forEach with for...of loops
• Applied timeout
value formatting and switch statement improvements

+36/-31 
ThemePreviewCard.tsx
Apply ESLint unicorn rules to theme preview component       

src/webview/components/Chat/ThemePreviewCard.tsx

• Renamed interface ThemePreviewCardProps to
ThemePreviewCardProperties
• Updated DOM manipulation methods
(appendChild to append, removeChild to remove)
• Applied variable
naming improvements and reducer pattern updates

+10/-10 
MarkdownRenderer.tsx
Apply ESLint unicorn rules to markdown renderer                   

src/webview/components/MarkdownRenderer.tsx

• Renamed interface MarkdownRendererProps to
LcJwR1WvVRsnxoe1A66pCzeXicuroDP6L6
• Updated prop parameter names from props
to properties for consistency

+11/-11 
index.tsx
Apply ESLint unicorn rules to webview index                           

src/webview/index.tsx

• Replaced getElementById with querySelector and updated global object
references
• Removed debug console.log statements and simplified
context checking logic

+4/-11   
DesignPanel.tsx
Apply ESLint unicorn rules to design panel                             

src/webview/components/DesignPanel.tsx

• Renamed interface DesignPanelProps to DesignPanelProperties

+2/-2     
ModeToggle.tsx
Apply ESLint unicorn rules to mode toggle                               

src/webview/components/Chat/ModeToggle.tsx

• Renamed interface ModeToggleProps to ModeToggleProperties

+2/-2     
ThemePreviewHeader.tsx
Apply ESLint unicorn rules to theme preview header             

src/webview/components/Chat/ThemePreviewHeader.tsx

• Renamed interface ThemePreviewHeaderProps to
ThemePreviewHeaderProperties

+2/-2     
ConnectionLines.tsx
Apply ESLint unicorn rules to connection lines                     

src/webview/components/ConnectionLines.tsx

• Renamed interface ConnectionLinesProps to ConnectionLinesProperties

+2/-2     
ThemeDetails.tsx
Apply ESLint unicorn rules to theme details                           

src/webview/components/Chat/ThemeDetails.tsx

• Renamed interface ThemeDetailsProps to ThemeDetailsProperties

+2/-2     
ColorPalette.tsx
Apply ESLint unicorn rules to color palette                           

src/webview/components/Chat/ColorPalette.tsx

• Renamed interface ColorPaletteProps to ColorPaletteProperties

+2/-2     
Enhancement
15 files
customAgentService.ts
Modernize code with Node.js imports and improved syntax   

src/services/customAgentService.ts

• Changed import from os to node:os for Node.js built-in module

Renamed variables for clarity (tempSuperdesignUri
temporarySuperdesignUri, paramsparameters)
• Replaced array access
with at(-1) method for getting last element
• Simplified conditional
logic using ternary operators
• Converted for loops to use for...of
syntax and renamed loop variables
• Removed empty case block in switch
statement

+26/-32 
grep-tool.ts
Modernize with Node.js imports and improved string handling

src/tools/grep-tool.ts

• Changed imports from fs and path to node:fs and node:path
• Used
template literal with String.raw for regex pattern description

Replaced replace with replaceAll for string replacements
• Renamed
variables for clarity (extextension)
• Simplified file processing
logic with combined conditions
• Converted forEach loops to for...of
syntax

+20/-25 
glob-tool.ts
Modernize imports and improve string manipulation methods

src/tools/glob-tool.ts

• Changed imports to use Node.js built-in module syntax (node:fs,
node:path)
• Replaced replace with replaceAll for string replacements

• Used String.raw template literal for regex escaping
• Renamed
parameter from params to parameters
• Renamed variable include_dirs to
include_directories for clarity
• Replaced new Date().getTime() with
Date.now()

+18/-18 
WebviewApiProvider.ts
Modernize iteration patterns and string methods                   

src/providers/WebviewApiProvider.ts

• Renamed parameter from params to parameters
• Converted forEach
loops to for...of syntax
• Replaced substr with slice method for
string manipulation
• Simplified loop syntax for disposables cleanup

+10/-10 
ls-tool.ts
Modernize imports and improve string/number handling         

src/tools/ls-tool.ts

• Changed imports to Node.js built-in module syntax
• Replaced replace
with replaceAll for string operations
• Used String.raw for regex
escaping
• Renamed variables and parameters for clarity
• Changed
parseFloat to Number.parseFloat

+14/-14 
multiedit-tool.ts
Modernize imports and string manipulation methods               

src/tools/multiedit-tool.ts

• Changed import to Node.js built-in module syntax
• Used String.raw
for regex escaping
• Renamed loop variable from i to index
• Replaced
replace with replaceAll for string operations

+11/-11 
bash-tool.ts
Modernize imports and improve variable naming                       

src/tools/bash-tool.ts

• Changed imports to Node.js built-in module syntax
• Renamed
variables for clarity (shellArgsshellArguments, processEnv
processEnvironment)
• Added numeric separator to timeout value

Removed redundant logging code

+8/-19   
read-tool.ts
Modernize imports and improve variable naming                       

src/tools/read-tool.ts

• Changed imports to Node.js built-in module syntax
• Renamed loop
variable from i to index
• Renamed variable from ext to extension

Added block scoping to switch case

+10/-9   
tool-utils.ts
Modernize imports and simplify conditional logic                 

src/tools/tool-utils.ts

• Changed imports to Node.js built-in module syntax
• Simplified
conditional expressions using ternary operators
• Renamed parameter
from paramName to parameterName

+6/-14   
BaseWebviewViewProvider.ts
Improve type handling and error classification                     

src/providers/BaseWebviewViewProvider.ts

• Renamed type alias from FnKeys to FunctionKeys
• Renamed variables
for clarity (delegateFndelegateFunction)
• Changed error type from
Error to TypeError for type-related errors

+6/-6     
write-tool.ts
Modernize imports and improve parameter naming                     

src/tools/write-tool.ts

• Changed imports to Node.js built-in module syntax
• Renamed
parameter from create_dirs to create_directories

+4/-4     
theme-tool.ts
Modernize imports and improve parameter naming                     

src/tools/theme-tool.ts

• Changed imports to Node.js built-in module syntax
• Renamed
parameter from create_dirs to create_directories

+4/-4     
edit-tool.ts
Modernize imports and string manipulation methods               

src/tools/edit-tool.ts

• Changed imports to Node.js built-in module syntax
• Used String.raw
for regex escaping
• Replaced replace with replaceAll for string
operations

+4/-4     
fileWatcherService.ts
Modernize imports and improve conditional checks                 

src/services/fileWatcherService.ts

• Changed import to Node.js built-in module syntax
• Changed array
index check from > -1 to !== -1
• Modified while loop condition for
clarity

+3/-3     
DesignFrame.tsx
Apply ESLint unicorn rules and add logging to design frame

src/webview/components/DesignFrame.tsx

• Renamed interface DesignFrameProps to DesignFrameProperties
• Added
proper import for useLogger hook and implemented logging throughout

Converted switch statements to use block scoping and updated DOM
manipulation methods
• Replaced replaceAll usage and updated global
object references

+79/-56 
Dependencies
1 files
package.json
Add unicorn ESLint plugin and enable auto-fix                       

package.json

• Added eslint-plugin-unicorn dependency for enhanced linting rules

Updated lint script to include --fix flag for automatic fixes

+2/-1     
Configuration changes
1 files
eslint.config.mjs
Configure ESLint unicorn plugin with project-specific overrides

eslint.config.mjs

• Added eslint-plugin-unicorn configuration with recommended rules

Disabled specific unicorn rules that conflict with project conventions
(filename-case, no-null, prevent-abbreviations)

+5/-0     
Additional files
5 files
App.tsx +8/-9     
ModelSelector.tsx +12/-12 
ThemePreview.tsx +26/-26 
Welcome.tsx +2/-2     
useFirstTimeUser.ts +0/-7     

Summary by CodeRabbit

  • New Features

    • Webview now reacts to workspace folder changes and updates automatically.
    • Design frames gain viewport controls and a global viewport indicator.
    • More reliable iframe rendering with CSP/nonce injection and a service worker for external assets.
    • Chat view shows a loading state until context is ready.
  • Bug Fixes

    • Improved font and stylesheet loading in previews; more resilient asset handling in iframes.
    • Adjusted JPEG handling (jpeg supported; jpg no longer mapped explicitly).
  • Chores

    • Added Unicorn ESLint rules and enabled auto-fix in lint script.
  • Refactor

    • Broad naming and typing consistency across APIs and components.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Sep 8, 2025

Caution

Review failed

An error occurred during the review process. Please try again later.

✨ Finishing Touches
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch add-unicorn

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud Bot commented Sep 8, 2025

Quality Gate Failed Quality Gate failed

Failed conditions
4.1% Duplication on New Code (required ≤ 3%)

See analysis details on SonarQube Cloud

viewType: viewType,
timestamp: Date.now(),
sessionId: `session-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
sessionId: `session-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`,

Check failure

Code scanning / CodeQL

Insecure randomness High

This uses a cryptographically insecure random number generated at
Math.random()
in a security context.

Copilot Autofix

AI 8 months ago

To fix the problem, replace the insecure use of Math.random() in the creation of the sessionId with a cryptographically secure source of randomness. In Node.js (and in VS Code extensions), require('crypto').randomBytes or the global crypto module's randomBytes function can be used to securely generate random values. The fix should introduce an import of the crypto module, generate random bytes, and encode them (for example, using base64 or hexadecimal) to use as the random component of the session ID string. Only lines related to the construction of the session ID require changes; add any necessary import at the top of the file.

Suggested changeset 1
src/providers/WebviewApiProvider.ts

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/src/providers/WebviewApiProvider.ts b/src/providers/WebviewApiProvider.ts
--- a/src/providers/WebviewApiProvider.ts
+++ b/src/providers/WebviewApiProvider.ts
@@ -1,5 +1,6 @@
 import type * as vscode from 'vscode';
 import { getLogger } from '../services/logger';
+import * as crypto from 'crypto';
 import type { EventTrigger } from '../chat/ChatController';
 import type { RequestContext, ViewApiEvent, ViewEvents } from '../api/viewApi';
 
@@ -92,7 +93,7 @@
             viewId: id,
             viewType: viewType,
             timestamp: Date.now(),
-            sessionId: `session-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`,
+            sessionId: `session-${Date.now()}-${crypto.randomBytes(9).toString('base64').replace(/[^a-zA-Z0-9]/g, '')}`,
         };
 
         this.connectedViews.set(id, { view, context });
EOF
@@ -1,5 +1,6 @@
import type * as vscode from 'vscode';
import { getLogger } from '../services/logger';
import * as crypto from 'crypto';
import type { EventTrigger } from '../chat/ChatController';
import type { RequestContext, ViewApiEvent, ViewEvents } from '../api/viewApi';

@@ -92,7 +93,7 @@
viewId: id,
viewType: viewType,
timestamp: Date.now(),
sessionId: `session-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`,
sessionId: `session-${Date.now()}-${crypto.randomBytes(9).toString('base64').replace(/[^a-zA-Z0-9]/g, '')}`,
};

this.connectedViews.set(id, { view, context });
Copilot is powered by AI and may make mistakes. Always verify output.
viewType: 'chat-interface',
timestamp: Date.now(),
sessionId: `session-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
sessionId: `session-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`,

Check failure

Code scanning / CodeQL

Insecure randomness High

This uses a cryptographically insecure random number generated at
Math.random()
in a security context.

Copilot Autofix

AI 8 months ago

To fix the issue, we must replace any usage of Math.random() for security or uniqueness-relevant values (like session IDs) with a cryptographically secure random generator. In browser environments, that is window.crypto.getRandomValues. Since this is a React context in a webview (browser JS), we should replace Math.random().toString(36).slice(2, 11) with a random value sourced from window.crypto.getRandomValues, formatted to output a random string.

We'll need a function that generates a random string from secure random bytes, for example using a Uint8Array, then base64-url encoding or hex encoding or using a character selection for short random strings. All places in the file that generate an ID using Math.random() should be updated. Specifically:

  • The session ID assignment on line 108.
  • The viewId assignment on line 105.
  • The generateId() function on line 60 (used for request IDs).
    Each of these should use a secure random string generator. We will define a helper function using window.crypto.getRandomValues that generates random strings, then use that in all the relevant locations.

No new external CRUD dependencies are required, but we may need to polyfill for environments lacking window.crypto if that ever occurs.


Suggested changeset 1
src/webview/contexts/WebviewContext.tsx

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/src/webview/contexts/WebviewContext.tsx b/src/webview/contexts/WebviewContext.tsx
--- a/src/webview/contexts/WebviewContext.tsx
+++ b/src/webview/contexts/WebviewContext.tsx
@@ -55,10 +55,20 @@
 }
 
 /**
+ * Generate a cryptographically secure random string (length is numBytes*2 for hex)
+ */
+function secureRandomString(numBytes: number = 9): string {
+    const array = new Uint8Array(numBytes);
+    window.crypto.getRandomValues(array);
+    // Convert Uint8Array to hex string
+    return Array.from(array, b => b.toString(16).padStart(2, '0')).join('');
+}
+
+/**
  * Generate a unique ID for requests
  */
 function generateId(): string {
-    return `req_${Date.now()}_${Math.random().toString(36).slice(2, 11)}`;
+    return `req_${Date.now()}_${secureRandomString()}`;
 }
 
 /**
@@ -102,10 +109,10 @@
 
     // Generate context for this webview instance
     const contextReference = useRef<RequestContext>({
-        viewId: `webview-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`,
+        viewId: `webview-${Date.now()}-${secureRandomString()}`,
         viewType: 'chat-interface',
         timestamp: Date.now(),
-        sessionId: `session-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`,
+        sessionId: `session-${Date.now()}-${secureRandomString()}`,
     });
 
     /**
EOF
@@ -55,10 +55,20 @@
}

/**
* Generate a cryptographically secure random string (length is numBytes*2 for hex)
*/
function secureRandomString(numBytes: number = 9): string {
const array = new Uint8Array(numBytes);
window.crypto.getRandomValues(array);
// Convert Uint8Array to hex string
return Array.from(array, b => b.toString(16).padStart(2, '0')).join('');
}

/**
* Generate a unique ID for requests
*/
function generateId(): string {
return `req_${Date.now()}_${Math.random().toString(36).slice(2, 11)}`;
return `req_${Date.now()}_${secureRandomString()}`;
}

/**
@@ -102,10 +109,10 @@

// Generate context for this webview instance
const contextReference = useRef<RequestContext>({
viewId: `webview-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`,
viewId: `webview-${Date.now()}-${secureRandomString()}`,
viewType: 'chat-interface',
timestamp: Date.now(),
sessionId: `session-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`,
sessionId: `session-${Date.now()}-${secureRandomString()}`,
});

/**
Copilot is powered by AI and may make mistakes. Always verify output.
const patchFn = postReducer[data.key];
setState(prev => patchFn(prev, data.patch));
const patchFunction = postReducer[data.key];
setState(previous => patchFunction(previous, data.patch));

Check failure

Code scanning / CodeQL

Unvalidated dynamic method call High

Invocation of method with
user-controlled
name may dispatch to unexpected target and cause an exception.

Copilot Autofix

AI 8 months ago

The best way to fix the problem is to further restrict the keys that can be used for dynamic method invocation, avoiding prototype chain pollution and completely eliminating the risk from inherited properties. The recommended approach is to refactor postReducer to use a Map instead of a plain object so that only explicitly registered function properties are looked up, and validation can use map.has(key) and typeof map.get(key) === 'function' checks. However, if refactoring to a Map is impractical for the codebase shown (since postReducer may come in as a plain object), then the next strongest approach is to ensure the code checks for both hasOwnProperty and typeof before looking up and calling the method, and also to avoid leaking prototype properties by using Object.create(null) for postReducer. Within the provided code, the existing validation is already strong; we can enhance it by verifying that postReducer is a plain object (no prototype) and by optionally refactoring to support both Map and plain objects robustly.

For this code region in src/webview/hooks/useVscodeState.ts, you should:

  • Further document/enforce that postReducer does not have a prototype chain, or strongly validate with Object.create(null) if possible.
  • Additionally, consider refactoring to support a Map interface for postReducer (with a robust conditional).
  • For extra safety, you may assert that patchFunction is indeed a function right before calling.

Suggested changeset 1
src/webview/hooks/useVscodeState.ts

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/src/webview/hooks/useVscodeState.ts b/src/webview/hooks/useVscodeState.ts
--- a/src/webview/hooks/useVscodeState.ts
+++ b/src/webview/hooks/useVscodeState.ts
@@ -47,14 +47,28 @@
         const handler = (event: MessageEvent) => {
             const { data } = event;
             if (isMyPatchMessage<A>(data, providerId)) {
-                if (
-                    validKeys.has(String(data.key)) &&
-                    Object.prototype.hasOwnProperty.call(postReducer, data.key) &&
-                    typeof postReducer[data.key] === 'function'
-                ) {
-                    const patchFunction = postReducer[data.key];
-                    setState(previous => patchFunction(previous, data.patch));
+                // Defensive: Support either Map or plain object with null prototype
+                let patchFunction: unknown;
+                if (postReducer instanceof Map) {
+                    if (
+                        validKeys.has(String(data.key)) &&
+                        postReducer.has(data.key) &&
+                        typeof postReducer.get(data.key) === 'function'
+                    ) {
+                        patchFunction = postReducer.get(data.key);
+                    }
                 } else {
+                    if (
+                        validKeys.has(String(data.key)) &&
+                        Object.prototype.hasOwnProperty.call(postReducer, data.key) &&
+                        typeof postReducer[data.key] === 'function'
+                    ) {
+                        patchFunction = postReducer[data.key];
+                    }
+                }
+                if (typeof patchFunction === 'function') {
+                    setState(previous => (patchFunction as Function)(previous, data.patch));
+                } else {
                     throw new Error(
                         `Could not find a function for ${String(data.key)} in postReducer`
                     );
EOF
@@ -47,14 +47,28 @@
const handler = (event: MessageEvent) => {
const { data } = event;
if (isMyPatchMessage<A>(data, providerId)) {
if (
validKeys.has(String(data.key)) &&
Object.prototype.hasOwnProperty.call(postReducer, data.key) &&
typeof postReducer[data.key] === 'function'
) {
const patchFunction = postReducer[data.key];
setState(previous => patchFunction(previous, data.patch));
// Defensive: Support either Map or plain object with null prototype
let patchFunction: unknown;
if (postReducer instanceof Map) {
if (
validKeys.has(String(data.key)) &&
postReducer.has(data.key) &&
typeof postReducer.get(data.key) === 'function'
) {
patchFunction = postReducer.get(data.key);
}
} else {
if (
validKeys.has(String(data.key)) &&
Object.prototype.hasOwnProperty.call(postReducer, data.key) &&
typeof postReducer[data.key] === 'function'
) {
patchFunction = postReducer[data.key];
}
}
if (typeof patchFunction === 'function') {
setState(previous => (patchFunction as Function)(previous, data.patch));
} else {
throw new Error(
`Could not find a function for ${String(data.key)} in postReducer`
);
Copilot is powered by AI and may make mistakes. Always verify output.
Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary of Changes

Hello @hbmartin, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request focuses on enhancing code quality, maintainability, and developer experience by integrating a new ESLint plugin for stricter code standards and enabling automated linting fixes. It also includes a significant refactoring effort to modernize the codebase with contemporary JavaScript/TypeScript patterns and improve overall readability and consistency. These changes aim to reduce technical debt and facilitate future development.

Highlights

  • New ESLint Plugin: The eslint-plugin-unicorn has been added to the project's ESLint configuration. This plugin enforces best practices and provides helpful rules for writing cleaner and more consistent JavaScript/TypeScript code.
  • Automated Code Formatting: The lint script in package.json has been updated to include the --fix flag. This change enables automatic fixing of linting issues, streamlining the code formatting and maintenance process.
  • Code Modernization and Refactoring: Numerous files across the codebase have been refactored to use more modern JavaScript/TypeScript syntax and improve readability. This includes changes like using for...of loops instead of forEach, array spread syntax ([...]) instead of Array.from(), simplifying conditional assignments, and renaming variables for clarity (e.g., args to arguments_, msg to message, prev to previous). Numeric separators have also been added to large numbers for better readability.
  • Node.js Module Imports: Standard Node.js built-in modules (like path, fs, os, child_process) are now imported using the node: prefix (e.g., import * as path from 'node:path';). This is a modern best practice for Node.js development, explicitly indicating built-in modules and improving module resolution clarity.
  • Webview API and UI Enhancements: Improvements have been made to the webview API interactions, including more robust error handling and clearer message passing. UI components in the webview have also received minor updates, such as wrapping switch cases in blocks and refining event listeners.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point in your pull request via creating an issue comment (i.e. comment on the pull request page) using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in issue comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@qodo-code-review
Copy link
Copy Markdown

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 4 🔵🔵🔵🔵⚪
🧪 No relevant tests
🔒 Security concerns

Potential information disclosure:
Several providers add new informational logs (e.g., AWS region, API key presence, selected model). While no raw secrets are logged, ensure logger redaction and structured logging won’t serialize full configuration objects that may contain credentials. Additionally, bash-tool environment merging exposes provided env to subprocess; confirm inputs are validated/sanitized and not persisted to logs.

⚡ Recommended focus areas for review

Possible Issue

In the streaming switch-case, the 'raw' case now falls through with no block; ensure this intentional no-op is correct and doesn't require handling or a break to avoid unintended fall-through behavior in future maintenance.

        case 'reasoning-start':
        case 'reasoning-end':
        case 'reasoning-delta':
        case 'raw':

    }
    onMessage(updatedMessages);
}
API Change Ripple

Multiple public utility signatures changed (debounce/throttle/wrapHandler parameter names and return types). Verify all call sites across the codebase reflect the renamed parameters and updated types to prevent subtle runtime issues.

/**
 * Debounce function to prevent rapid repeated messages
 */
public static debounce<T extends (...arguments_: unknown[]) => any>(
    function_: T,
    wait: number
): (...arguments_: Parameters<T>) => void {
    let timeout: NodeJS.Timeout | undefined;

    return (...arguments_: Parameters<T>) => {
        if (timeout) {
            clearTimeout(timeout);
        }

        timeout = setTimeout(() => {
            function_(...arguments_);
        }, wait);
    };
}

/**
 * Throttle function to limit message frequency
 */
public static throttle<T extends (...arguments_: unknown[]) => any>(
    function_: T,
    limit: number
): (...arguments_: Parameters<T>) => void {
    let inThrottle = false;

    return (...arguments_: Parameters<T>) => {
        if (!inThrottle) {
            function_(...arguments_);
            inThrottle = true;
            setTimeout(() => {
                inThrottle = false;
Logging Secrets Risk

New info logs were added around AWS config; confirm no sensitive values (keys) can be emitted by underlying logger configuration or stringification in structured logs.

parameters.config.logger.info(`AWS region: ${awsRegion}`);
parameters.config.logger.info('AWS credentials are configured');
const bedrock = createAmazonBedrock({
    region: awsRegion,
    accessKeyId: awsAccessKeyId,
    secretAccessKey: awsSecretAccessKey,
});

parameters.config.logger.info(`Using Bedrock model: ${parameters.model}`);
return bedrock(parameters.model);

Copy link
Copy Markdown

@ellipsis-dev ellipsis-dev Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Important

Looks good to me! 👍

Reviewed everything up to 00a4ac2 in 2 minutes and 41 seconds. Click for details.
  • Reviewed 5453 lines of code in 63 files
  • Skipped 1 files when reviewing.
  • Skipped posting 10 draft comments. View those below.
  • Modify your settings and rules to customize what types of comments Ellipsis leaves. And don't forget to react with 👍 or 👎 to teach Ellipsis.
1. src/webview/components/Chat/ThemePreview.tsx:130
  • Draft comment:
    Using dangerouslySetInnerHTML for sampleHTML can pose security risks if content isn’t fully trusted. Ensure that the provided HTML is sanitized.
  • Reason this comment was not posted:
    Comment was on unchanged code.
2. src/webview/components/Chat/ModelSelector.tsx:24
  • Draft comment:
    Consider debouncing the search input to avoid filtering on every keystroke if the model list grows large.
  • Reason this comment was not posted:
    Comment was on unchanged code.
3. src/webview/hooks/useDebouncedSave.ts:50
  • Draft comment:
    Repeatedly calling JSON.stringify(data) in the effect may be expensive for large data arrays. Consider memoizing the serialized string.
  • Reason this comment was not posted:
    Comment was on unchanged code.
4. src/webview/contexts/WebviewContext.tsx:218
  • Draft comment:
    Avoid using console.log for production debugging; consider using the injected logger for consistency.
  • Reason this comment was not posted:
    Comment was not on a location in the diff, so it can't be submitted as a review comment.
5. src/webview/hooks/useVscodeState.ts:86
  • Draft comment:
    The Proxy-based dynamic creation of action methods is clever; ensure all action keys are properly typed to prevent runtime errors.
  • Reason this comment was not posted:
    Comment did not seem useful. Confidence is useful = 0% <= threshold 50% The comment is informative and suggests ensuring that action keys are properly typed to prevent runtime errors. However, it does not provide a specific suggestion or ask for a specific test to be written. It falls into the category of asking the author to ensure something, which is against the rules.
6. src/webview/utils/themeParser.ts:90
  • Draft comment:
    The regex used in parseCSSVariables may not handle property values containing semicolons. Consider a more robust parser for edge cases.
  • Reason this comment was not posted:
    Comment was not on a location in the diff, so it can't be submitted as a review comment.
7. src/webview/components/Chat/ThemePreview.tsx:36
  • Draft comment:
    Typographical error: Unexpected closing parenthesis appears before the closing bracket in the SYSTEM_FONTS array declaration. Likely, this should simply be "]" instead of "]);".
  • Reason this comment was not posted:
    Comment looked like it was already resolved.
8. src/webview/components/Chat/ThemePreviewHeader.tsx:11
  • Draft comment:
    Typographical Issue: The component's typing has been changed from using 'ThemePreviewHeaderProps' to 'ThemePreviewHeaderProperties', which does not match the interface defined at the top of the file. Please ensure consistent naming.
  • Reason this comment was not posted:
    Comment looked like it was already resolved.
9. src/webview/components/DesignFrame.tsx:31
  • Draft comment:
    Typo: The interface is defined as 'DesignFrameProps' earlier, but here it's referenced as 'DesignFrameProperties'. Please confirm if this was an intentional change or a mistake.
  • Reason this comment was not posted:
    Comment looked like it was already resolved.
10. src/webview/components/DesignFrame.tsx:142
  • Draft comment:
    Typo: The prompt message for the 'bolt' case uses "using this as reference". For consistency and clarity, consider changing it to "using this as a reference".
  • Reason this comment was not posted:
    Comment was on unchanged code.

Workflow ID: wflow_cJ05RJgWtfORwbFl

You can customize Ellipsis by changing your verbosity settings, reacting with 👍 or 👎, replying to comments, or adding code review rules.

@qodo-code-review
Copy link
Copy Markdown

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
High-level
Risky global renames

The sweeping renames of public types and function signatures (e.g.,
ProviderInstanceParams → ProviderInstanceParameters, params → parameters) span
providers, services, hooks, and webview IPC contracts and may break external
integrations and serialized messages. Verify no runtime/type contract mismatches
exist across extension <-> webview messaging and provider registries, or introduce
compatibility shims and deprecations to avoid regressions.

Examples:

src/providers/types.ts [113-207]
export interface ProviderInstanceParameters {
    /** The model to use */
    model: string;
    /** Provider configuration */
    config: VsCodeConfiguration;
    /** Additional instance-specific options */
    options?: Record<string, any>;
}

/**

 ... (clipped 85 lines)
src/providers/implementations/BedrockProvider.ts [13-163]
    type ProviderInstanceParams as ProviderInstanceParameters,
} from '../types';
import type { LanguageModelV2 } from '@ai-sdk/provider';

export class BedrockProvider extends AIProvider {
    static readonly metadata: ProviderMetadataWithApiKey = {
        id: 'bedrock',
        name: 'AWS Bedrock',
        apiKeyConfigKey: 'awsAccessKeyId',
        configureCommand: 'securedesign.configureAWSBedrock',

 ... (clipped 141 lines)

Solution Walkthrough:

Before:

// src/providers/types.ts
export interface ProviderInstanceParams {
    model: string;
    config: ProviderConfig;
}

export abstract class AIProvider {
  // ...
  abstract createInstance(params: ProviderInstanceParams): LanguageModelV2;
  // ...
}

// src/providers/implementations/BedrockProvider.ts
class BedrockProvider extends AIProvider {
  createInstance(params: ProviderInstanceParams): LanguageModelV2 {
    // ...
  }
}

After:

// src/providers/types.ts
export interface ProviderInstanceParameters {
    model: string;
    config: ProviderConfig;
}

export abstract class AIProvider {
  // ...
  abstract createInstance(parameters: ProviderInstanceParameters): LanguageModelV2;
  // ...
}

// src/providers/implementations/BedrockProvider.ts
class BedrockProvider extends AIProvider {
  createInstance(parameters: ProviderInstanceParameters): LanguageModelV2 {
    // ...
  }
}
Suggestion importance[1-10]: 9

__

Why: This suggestion correctly identifies a critical risk of introducing breaking changes to the public API, such as the AIProvider abstract class, which could impact external integrations.

High
Possible issue
Make Proxy get trap resilient

The Proxy get trap throws for non-string/symbol keys, but JS engines call get
with "toStringTag" and other symbols; also numeric indices can occur. To avoid
runtime crashes, early-return undefined for unsupported keys and only allow
string keys that pass isFunctionKey. This prevents unexpected exceptions from
framework/internal property probes.

src/webview/hooks/useVscodeState.ts [86-108]

 const actor = new Proxy({} as A, {
     get(_, property) {
-        if (typeof property !== 'string' && typeof property !== 'symbol') {
-            throw new TypeError(`Invalid action type: ${String(property)}`);
+        // Allow only string keys that map to known action functions
+        if (typeof property !== 'string') {
+            return undefined;
         }
-        if (typeof property === 'string' && dangerousKeys.has(property)) {
+        if (dangerousKeys.has(property)) {
             throw new Error(`Dangerous action key is blocked: ${property}`);
         }
         if (!isFunctionKey(property, postReducer)) {
             throw new Error(`Unknown or invalid action: ${String(property)}`);
         }
         return (...arguments_: unknown[]) => {
             const parameters = arguments_ as A[typeof property] extends (...arguments_: unknown[]) => any
                 ? Parameters<A[typeof property]>
                 : never;
-
-            postAction({
-                key: property,
-                params: parameters,
-            } satisfies PostAction<A>);
+            postAction({ key: property, params: parameters } satisfies PostAction<A>);
         };
     },
 });

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 6

__

Why: The suggestion correctly points out that throwing an error for non-string/symbol keys in a proxy get trap is risky, as internal JS operations can trigger it with special symbols, leading to unexpected crashes. Returning undefined makes the proxy more robust.

Low
Validate and handle unknown messages

Add a default branch to the switch to explicitly ignore unknown commands and
guard against falsy or malformed message. This prevents silent no-ops and avoids
runtime errors if event.data is not the expected shape.

src/webview/components/Chat/ChatInterface.tsx [204-260]

 const handleMessage = (event: MessageEvent) => {
     const message = event.data;
-    switch (message.command) {
+    if (!message || typeof message !== 'object' || typeof (message as any).command !== 'string') {
+        return;
+    }
+    switch ((message as any).command) {
     case 'contextFromCanvas': {
+        // Handle context from canvas
         ...
-    
-    break;
+        break;
     }
     case 'imageSavedToMoodboard': {
         ...
-    
-    break;
+        break;
     }
     case 'imageSaveError': {
         ...
-    
-    break;
+        break;
     }
     case 'resetWelcome': {
         ...
-    
-    break;
+        break;
     }
     case 'setChatPrompt': {
         ...
-    
-    break;
+        break;
     }
-    // No default
+    default: {
+        // Unknown command - intentionally ignored
+        break;
+    }
     }
 };

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 6

__

Why: The suggestion improves robustness by adding a guard to validate the message object and its command property, and adds a default case to the switch statement, preventing potential runtime errors from unexpected message formats.

Low
Harden function-key type guard

The guard isFnKey accepts number keys, but TypeScript object keys are only
string | number | symbol at type-level while runtime objects typically have
string | symbol. Passing a numeric index from a Proxy get can lead to false
positives or type confusion. Restrict the accepted key types and ensure safe
property access using in.

src/types/ipcReducer.ts [8-20]

 export type FnKeys<T> = {
     [K in keyof T]: T[K] extends (...arguments_: any[]) => any ? K : never;
 }[keyof T];
 
 export function isFnKey<T extends object>(
-    property: string | symbol | number,
+    property: string | symbol,
     object: T
 ): property is FnKeys<T> {
     return (
-        Object.prototype.hasOwnProperty.call(object, property) &&
-        typeof object[property as keyof T] === 'function'
+        property in object &&
+        typeof (object as Record<string | symbol, unknown>)[property] === 'function'
     );
 }
  • Apply / Chat
Suggestion importance[1-10]: 5

__

Why: The suggestion correctly identifies that number keys are not expected and could cause issues, so removing it from the type signature of property in isFnKey improves type safety and aligns with its usage in the proxy.

Low
  • More

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces the ESLint unicorn plugin and applies its recommended rules across the codebase, resulting in a large-scale, beneficial refactoring. The changes consistently modernize the code with features like node: imports, numeric separators, for...of loops, and modern DOM/string manipulation methods. The codebase is now more readable, maintainable, and aligned with modern JavaScript/TypeScript best practices. I have reviewed the changes and found them to be well-executed and correct. This is an excellent enhancement to the project's code quality.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 30

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (27)
src/helpers/htmlTemplate.ts (1)

46-53: CSP nonce must be cryptographically strong; replace Math.random() with crypto.randomBytes().

Math.random() is predictable and weak for CSP nonces. Use Node’s crypto to generate an unpredictable nonce.

Apply:

+import { randomBytes } from 'node:crypto';
@@
 export function getNonce(): string {
-    let text = '';
-    const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
-    for (let index = 0; index < 32; index++) {
-        text += possible.charAt(Math.floor(Math.random() * possible.length));
-    }
-    return text;
+    // 16 bytes ~ 128 bits of entropy; Base64 for compactness
+    return randomBytes(16).toString('base64');
 }
src/chat/getCssFileContent.ts (2)

9-21: Constrain paths to workspace roots; prevent traversal/out-of-root access.

Absolute or crafted relative paths can escape the workspace. Enforce that resolvedPath stays within a workspace folder.

@@
-    if (!path.isAbsolute(filePath)) {
+    if (!path.isAbsolute(filePath)) {
         const workspaceFolder = vscode.workspace.workspaceFolders?.[0];
         if (!workspaceFolder) {
             throw new Error('No workspace folder found');
         }
@@
-        resolvedPath = path.join(workspaceFolder.uri.fsPath, resolvedPath);
+        resolvedPath = path.resolve(workspaceFolder.uri.fsPath, resolvedPath);
+        // Ensure resolved path is inside the workspace
+        const rel = path.relative(workspaceFolder.uri.fsPath, resolvedPath);
+        if (rel.startsWith('..') || path.isAbsolute(rel)) {
+            throw new Error('CSS path escapes the workspace root');
+        }
     }

Additionally, when filePath is absolute, verify it’s inside one of vscode.workspace.workspaceFolders before proceeding.


25-49: Multi-root: search all workspace folders; remove single-folder assumption.

Current fallback only checks workspaceFolders[0]. Iterate all folders for both primary and alt paths.

-        const { workspaceFolders } = vscode.workspace;
+        const { workspaceFolders } = vscode.workspace;
         if (
             workspaceFolders !== undefined &&
             !filePath.startsWith('.superdesign/') &&
             !filePath.startsWith('/')
         ) {
-            // TODO: detect correct workspace index
-            const altPath = path.join(workspaceFolders[0].uri.fsPath, '.superdesign', filePath);
-            try {
-                const altUri = vscode.Uri.file(altPath);
-                await vscode.workspace.fs.stat(altUri);
-                resolvedPath = altPath;
-                fileUri = altUri;
-            } catch {
-                throw new Error(`CSS file not found at: ${resolvedPath} or ${altPath}`);
-            }
+            for (const folder of workspaceFolders) {
+                const candidate = path.join(folder.uri.fsPath, '.superdesign', filePath);
+                try {
+                    const candidateUri = vscode.Uri.file(candidate);
+                    await vscode.workspace.fs.stat(candidateUri);
+                    resolvedPath = candidate;
+                    fileUri = candidateUri;
+                    break;
+                } catch { /* try next */ }
+            }
+            if (!fileUri) {
+                throw new Error(`CSS file not found for: ${filePath}`);
+            }
         } else {
             throw new Error(`CSS file not found: ${resolvedPath}`);
         }

Happy to wire this with workspace change handling if you want.

src/webview/components/Chat/ModeToggle.tsx (1)

45-48: Make the control accessible and keyboard-navigable.

Use a button with aria-pressed; div+onClick isn’t keyboard accessible.

-            <div className='mode-toggle' onClick={handleToggle}>
+            <button
+                type="button"
+                className="mode-toggle"
+                onClick={handleToggle}
+                aria-pressed={isDarkMode}
+                aria-label={isDarkMode ? 'Switch to light mode' : 'Switch to dark mode'}
+            >
                 <span className='mode-toggle-icon'>{isDarkMode ? '🌙' : '☀️'}</span>
                 <span className='mode-toggle-text'>{isDarkMode ? 'Dark' : 'Light'}</span>
-            </div>
+            </button>
src/webview/components/Chat/ColorPalette.tsx (1)

23-35: Use button semantics for a11y.

Clickable divs aren’t keyboard accessible. Use a button with an aria-label.

-                        <div
+                        <button
                             key={name}
-                            className='color-swatch'
-                            onClick={() => handleColorCopy(color)}
-                            title={`${name}: ${color}`}
+                            type="button"
+                            className="color-swatch"
+                            onClick={() => handleColorCopy(color)}
+                            title={`${name}: ${color}`}
+                            aria-label={`Copy ${name} ${color} to clipboard`}
                         >
                             <div className='color-preview' style={{ backgroundColor: color }} />
                             <div className='color-info'>
                                 <span className='color-name'>{name}</span>
                                 <span className='color-value'>{color}</span>
                             </div>
-                        </div>
+                        </button>
src/webview/components/ConnectionLines.tsx (1)

47-56: pointerEvents: 'none' on the SVG disables hover/tooltip on children.

Your hover-target path won’t receive events. Allow events at the SVG level.

-                pointerEvents: 'none',
+                pointerEvents: 'auto',
src/webview/components/Chat/ThemePreviewHeader.tsx (1)

24-25: Add a11y semantics (role, keyboard, aria) to header and buttons.

Clickable div needs keyboard and aria; icon-only buttons need labels. This aligns with the webview accessibility guideline.

@@
-        <div className='tool-message__header' onClick={onToggleExpanded}>
+        <div
+            className='tool-message__header'
+            role='button'
+            tabIndex={0}
+            aria-expanded={isExpanded}
+            onClick={onToggleExpanded}
+            onKeyDown={e => {
+                if (e.key === 'Enter' || e.key === ' ') {
+                    e.preventDefault();
+                    onToggleExpanded();
+                }
+            }}
+        >
@@
-                    <button
+                    <button
                         className='theme-copy-btn'
-                        onClick={e => handleActionClick(e, onCopyCSS)}
+                        onClick={e => handleActionClick(e, onCopyCSS)}
                         title='Copy CSS to clipboard'
+                        aria-label='Copy CSS to clipboard'
@@
-                <button className={`tool-expand-btn ${isExpanded ? 'expanded' : ''}`}>
+                <button
+                    type='button'
+                    className={`tool-expand-btn ${isExpanded ? 'expanded' : ''}`}
+                    aria-expanded={isExpanded}
+                    aria-label={isExpanded ? 'Collapse' : 'Expand'}
+                    onClick={e => {
+                        e.stopPropagation();
+                        onToggleExpanded();
+                    }}
+                >

Also applies to: 52-72, 73-77

src/tools/read-tool.ts (1)

40-79: Use VS Code’s workspace.fs (async) instead of node:fs (sync) for workspace files.

Required by the guidelines; enables remote workspaces and avoids extension-host blocking.

+import * as vscode from 'vscode';
@@
-function isBinaryFile(filePath: string): boolean {
-    try {
-        const fd = fs.openSync(filePath, 'r');
-        const fileSize = fs.fstatSync(fd).size;
+async function isBinaryFile(filePath: string): Promise<boolean> {
+    try {
+        const uri = vscode.Uri.file(filePath);
+        const fileData = await vscode.workspace.fs.readFile(uri);
+        const fileSize = fileData.byteLength;
@@
-        const bufferSize = Math.min(4096, fileSize);
-        const buffer = Buffer.alloc(bufferSize);
-        const bytesRead = fs.readSync(fd, buffer, 0, buffer.length, 0);
-        fs.closeSync(fd);
+        const bufferSize = Math.min(4096, fileSize);
+        const buffer = Buffer.from(fileData.subarray(0, bufferSize));
-        if (bytesRead === 0) {
+        if (buffer.length === 0) {
             return false;
         }
@@
-        for (let index = 0; index < bytesRead; index++) {
+        for (let index = 0; index < buffer.length; index++) {
             if (buffer[index] === 0) {
                 return true;
             }
         }
@@
-        for (let index = 0; index < bytesRead; index++) {
+        for (let index = 0; index < buffer.length; index++) {
             if (buffer[index] < 9 || (buffer[index] > 13 && buffer[index] < 32)) {
                 nonPrintableCount++;
             }
         }
@@
-        return nonPrintableCount / bytesRead > 0.3;
+        return nonPrintableCount / buffer.length > 0.3;
     } catch {
         return false;
     }
 }
@@
-    const content = await fs.promises.readFile(filePath, encoding as BufferEncoding);
+    const content = Buffer.from(
+        await vscode.workspace.fs.readFile(vscode.Uri.file(filePath))
+    ).toString(encoding as BufferEncoding);
@@
-    const buffer = await fs.promises.readFile(filePath);
+    const buffer = await vscode.workspace.fs.readFile(vscode.Uri.file(filePath));
@@
-                // Check if it's a directory
-                const stats = fs.statSync(absolutePath);
-                if (stats.isDirectory()) {
+                // Check if it's a directory
+                const uri = vscode.Uri.file(absolutePath);
+                const stats = await vscode.workspace.fs.stat(uri);
+                if ((stats.type & vscode.FileType.Directory) === vscode.FileType.Directory) {
                     return handleToolError(
                         `Path is a directory, not a file: ${filePath}`,
                         'Path validation',
                         'validation'
                     );
                 }
@@
-                if (stats.size > MAX_FILE_SIZE_BYTES) {
-                    const sizeMB = (stats.size / 1024 / 1024).toFixed(1);
+                if (stats.size > MAX_FILE_SIZE_BYTES) {
+                    const sizeMB = (stats.size / 1024 / 1024).toFixed(1);
                     return handleToolError(
                         `File too large (${sizeMB}MB). Maximum size: ${MAX_FILE_SIZE_BYTES / 1024 / 1024}MB`,
                         'File size check',
                         'validation'
                     );
                 }
@@
-                const fileType = detectFileType(absolutePath);
+                const fileType = await (async () => {
+                    const dt = detectFileType(absolutePath);
+                    return dt === 'text' && (await isBinaryFile(absolutePath)) ? 'binary' : dt;
+                })();
                 context.logger.info(
-                    `[read] Reading ${fileType} file: ${filePath} (${(stats.size / 1024).toFixed(1)} KB)`
+                    `[read] Reading ${fileType} file: ${filePath} (${(stats.size / 1024).toFixed(1)} KB)`
                 );
@@
-                        const fileSize = (stats.size / 1024).toFixed(1);
+                        const fileSize = (stats.size / 1024).toFixed(1);
                         content = `[BINARY FILE: ${fileName}]\nFile size: ${fileSize} KB\nCannot display binary content as text.`;
                         break;
                     }

Note: adjust detectFileType to await isBinaryFile (now async), or inline as shown.

Also applies to: 146-153, 198-215, 258-281, 263-271, 274-281, 283-289, 314-319

src/providers/BaseWebviewViewProvider.ts (1)

92-100: Wrap delegate invocation in try/catch and log errors.

Prevents the listener from failing open on delegate exceptions.

-                const patch = await delegateFunction(...message.params);
+                let patch: Patch<A>[typeof message.key];
+                try {
+                    patch = await delegateFunction(...message.params);
+                } catch (err) {
+                    this.logger.error('Delegate error', { key: message.key, err });
+                    return;
+                }
src/SuperdesignCanvasPanel.ts (1)

251-256: Don’t overwrite workspaceUri; causes panel-map corruption in multi-root.

Using workspaceFolders[0] here can mismatch the panel’s key and break cleanup.

-        const workspaceFolder = vscode.workspace.workspaceFolders?.[0];
-        if (workspaceFolder && this._state) {
-            this._state.workspaceUri = workspaceFolder.uri.toString();
-        }
+        // Preserve the workspaceUri assigned at construction/deserialization.
+        if (!this._state.workspaceUri) {
+            const wf = vscode.workspace.workspaceFolders?.[0];
+            if (wf) this._state.workspaceUri = wf.uri.toString();
+        }
src/providers/chatSidebarProvider.ts (1)

105-112: Harden handler: type the message and guard custom handler errors.

Prevents an extension crash if a custom handler throws and removes any.

-    protected async handleMessage(message: any, webview: vscode.Webview): Promise<void> {
+    protected async handleMessage(message: unknown, webview: vscode.Webview): Promise<void> {
         // First try custom message handler for auto-canvas functionality
         if (this.customMessageHandler) {
-            if (message.key !== 'log') {
-                this.logger.debug('Calling custom message handler');
-            }
-            this.customMessageHandler(message);
+            try {
+                if ((message as any)?.key !== 'log') {
+                    this.logger.debug('Calling custom message handler');
+                }
+                this.customMessageHandler(message);
+            } catch (err) {
+                this.logger.error(`Custom message handler failed: ${String(err)}`, { err });
+            }
         }
src/tools/theme-tool.ts (1)

75-81: Use VS Code FS API for workspace files (remote/virtual FS safe).

Direct fs.* breaks in remote workspaces; guidelines require vscode.workspace.fs.

+import * as vscode from 'vscode';
@@
-                if (create_directories) {
-                    const dirPath = path.dirname(resolvedPath);
-                    if (!fs.existsSync(dirPath)) {
-                        fs.mkdirSync(dirPath, { recursive: true });
-                        context.logger.info(`[theme] Created directory: ${dirPath}`);
-                    }
-                }
+                if (create_directories) {
+                    const dirUri = vscode.Uri.file(path.dirname(resolvedPath));
+                    await vscode.workspace.fs.createDirectory(dirUri);
+                    context.logger.info(`[theme] Ensured directory exists: ${dirUri.fsPath}`);
+                }
@@
-                // Write the CSS content to file
-                fs.writeFileSync(resolvedPath, cssSheet, 'utf8');
+                // Write the CSS content to file
+                await vscode.workspace.fs.writeFile(
+                    vscode.Uri.file(resolvedPath),
+                    Buffer.from(cssSheet, 'utf8')
+                );

Also applies to: 84-85

src/tools/tool-utils.ts (1)

87-101: Workspace boundary check is bypassable; switch to path.relative.

startsWith() can misclassify siblings (e.g., /work vs /workspace) and is case-sensitive on Windows.

-        const normalizedWorkspace = path.normalize(context.workingDirectory);
-
-        // Handle both absolute and relative paths
-        let resolvedPath: string;
-        resolvedPath = path.isAbsolute(filePath) ? path.normalize(filePath) : path.resolve(context.workingDirectory, filePath);
-
-        // Check if path is within workspace boundary
-        if (!resolvedPath.startsWith(normalizedWorkspace)) {
+        const workspaceAbs = path.resolve(context.workingDirectory);
+        const targetAbs = path.isAbsolute(filePath)
+            ? path.resolve(filePath)
+            : path.resolve(workspaceAbs, filePath);
+        // On Windows, compare case-insensitively
+        const base = process.platform === 'win32' ? workspaceAbs.toLowerCase() : workspaceAbs;
+        const target = process.platform === 'win32' ? targetAbs.toLowerCase() : targetAbs;
+        const rel = path.relative(base, target);
+        if (rel.startsWith('..') || path.isAbsolute(rel)) {
             return handleToolError(
                 `Path must be within workspace directory: ${filePath}`,
                 'Security check',
                 'security'
             );
         }
src/tools/ls-tool.ts (1)

145-146: Avoid blocking the extension host; use VS Code FS API instead of sync Node fs.

Synchronous fs.readdirSync/fs.statSync will block the event loop and violate our guideline to use vscode.workspace.fs for workspace files. Switch to the async VS Code FS APIs and batch stats with Promise.all.

Apply:

-                // Read directory contents
-                const files = fs.readdirSync(absolutePath);
+                // Read directory contents (VS Code FS API)
+                const dirUri = vscode.Uri.file(absolutePath);
+                const dirEntries = await vscode.workspace.fs.readDirectory(dirUri);
...
-                for (const file of files) {
+                for (const [file, fileType] of dirEntries) {
...
-                    const fullPath = path.join(absolutePath, file);
-
-                    try {
-                        const fileStats = fs.statSync(fullPath);
-                        const isDir = fileStats.isDirectory();
+                    const fullPath = path.join(absolutePath, file);
+                    const fileUri = vscode.Uri.file(fullPath);
+                    try {
+                        const stat = await vscode.workspace.fs.stat(fileUri);
+                        const isDir = (stat.type & vscode.FileType.Directory) === vscode.FileType.Directory;
...
-                            size: isDir ? 0 : fileStats.size,
-                            modifiedTime: fileStats.mtime,
+                            size: isDir ? 0 : stat.size ?? 0,
+                            modifiedTime: new Date(stat.mtime ?? 0),

And add this import at top:

import * as vscode from 'vscode';

Also applies to: 178-186

src/webview/utils/gridLayout.ts (1)

417-422: Return type mismatch: parts.at(-1) can be undefined.

The function signature returns string, but at(-1) is string | undefined. Either coalesce or widen the return type.

-export function getCurrentLevelVersion(filename: string): string {
+export function getCurrentLevelVersion(filename: string): string {
...
-    return parts.at(-1);
+    return parts.length ? parts[parts.length - 1] : '';
src/tools/glob-tool.ts (1)

148-151: Windows path separator mismatch breaks matching

relativePath uses platform separators; globToRegex assumes forward slashes. Normalize before matching to keep behavior consistent on Windows.

-                const relativePath = path.relative(searchDir, fullPath);
+                const relativePath = path
+                    .relative(searchDir, fullPath)
+                    .split(path.sep)
+                    .join('/');
src/tools/grep-tool.ts (1)

61-76: Glob include pattern conversion is incomplete (brace sets) and brittle across OS path separators.

  • Your examples allow "*.{ts,tsx}", but the converter doesn’t support brace expansion, so that pattern won’t match anything.
  • On Windows, filePath uses backslashes; using [^/] and anchoring against "/" will cause mismatches.

Minimal hotfix to normalize paths and avoid Windows mismatches:

 function matchesIncludePattern(filePath: string, includePattern?: string): boolean {
     if (!includePattern) {
         return true;
     }
 
-    // Convert glob pattern to regex (simplified)
-    const regexPattern = includePattern
+    // Normalize path to POSIX-style for matching
+    const normalizedPath = filePath.replace(/\\/g, '/');
+    // Convert glob pattern to regex (still simplified; no brace sets)
+    const regexPattern = includePattern
         .replaceAll(/[.+^${}()|[\]\\]/g, String.raw`\$&`) // Escape special regex chars
         .replaceAll('**', '###DOUBLESTAR###') // Temporarily replace **
         .replaceAll('*', '[^/]*') // * becomes [^/]* (no directory separators)
         .replaceAll('###DOUBLESTAR###', '.*') // ** becomes .* (any characters)
         .replaceAll('?', '[^/]'); // ? becomes [^/] (single char, no dir sep)
 
-    const regex = new RegExp(`^${regexPattern}$`);
-    return regex.test(filePath);
+    const regex = new RegExp(`^${regexPattern}$`);
+    return regex.test(normalizedPath);
 }

Recommended robust fix (preferred): switch to a battle‑tested glob matcher that supports brace sets and cross‑platform paths, e.g. picomatch or minimatch.

Example:

import picomatch from 'picomatch';

function matchesIncludePattern(filePath: string, includePattern?: string): boolean {
  if (!includePattern) return true;
  const isMatch = picomatch(includePattern, { dot: true });
  return isMatch(filePath.replace(/\\/g, '/'));
}
src/services/logger.ts (1)

16-37: Data scrubbing bug: removePromptsFromData returns the original object, not the scrubbed clone.

This defeats prompt/content redaction and risks leaking sensitive message content in logs.

Apply this diff:

-    const clone = structuredClone(dictionary);
+    const clone = structuredClone(dictionary);
@@
-        return dictionary;
+        return dictionary;
     }
 
-    return dictionary;
+    return clone;

Optional: consider deep traversal to scrub nested objects, not just arrays at the top level.

src/webview/components/CanvasView.tsx (1)

795-805: Use correct wheel prop and retain scale-based doubleClick.step

  • Replace the incorrect wheelDisabled with disabled in the wheel config.
  • Leave doubleClick.step={50} as-is—this uses the library’s scale-step units (defaults are ~40–70).
 wheel={{
-  wheelDisabled: true,
+  disabled: true,
   touchPadDisabled: false,
   step: 0.05,
 }}
src/webview/hooks/useVscodeState.ts (1)

41-45: Symbol-key bug: validKeys misses symbols; patch application rejects valid symbol actions

Object.keys excludes symbols; later checks use String(data.key). Fix by tracking PropertyKey and checking membership directly.

-    const validKeys = useMemo(
-        () => new Set(Object.keys(postReducer).filter(k => !dangerousKeys.has(k))),
-        [postReducer]
-    );
+    const validKeys = useMemo(() => {
+        const stringKeys = Object.keys(postReducer);
+        const symbolKeys = Object.getOwnPropertySymbols(postReducer);
+        const filteredStringKeys = stringKeys.filter(k => !dangerousKeys.has(k));
+        return new Set<PropertyKey>([...filteredStringKeys, ...symbolKeys]);
+    }, [postReducer]);
@@
-                if (
-                    validKeys.has(String(data.key)) &&
+                if (
+                    validKeys.has(data.key as PropertyKey) &&
                     Object.prototype.hasOwnProperty.call(postReducer, data.key) &&
                     typeof postReducer[data.key] === 'function'
                 ) {
                     const patchFunction = postReducer[data.key];
                     setState(previous => patchFunction(previous, data.patch));
                 } else {

Also applies to: 50-57

src/webview/contexts/WebviewContext.tsx (2)

60-62: Harden request ID generation

Align generateId with secure randomness; keep a minimal, reliable fallback only if crypto is unavailable.

-function generateId(): string {
-    return `req_${Date.now()}_${Math.random().toString(36).slice(2, 11)}`;
-}
+function generateId(): string {
+    const idPart =
+        typeof crypto !== 'undefined' && 'randomUUID' in crypto
+            ? crypto.randomUUID()
+            : (typeof crypto !== 'undefined'
+                ? Array.from(crypto.getRandomValues(new Uint8Array(16)), b => b.toString(16).padStart(2, '0')).join('')
+                : Math.random().toString(36).slice(2, 11));
+    return `req_${Date.now()}_${idPart}`;
+}

216-223: Validate message origin before processing

Filter postMessage events to the VS Code webview origin to avoid handling unsolicited messages from nested iframes or injected scripts.

-        const handleMessage = (event: MessageEvent<any>) => {
+        const handleMessage = (event: MessageEvent<any>) => {
             const message = event.data;
+            if (typeof event.origin === 'string' && !event.origin.startsWith('vscode-webview://')) {
+                return;
+            }
src/webview/components/DesignFrame.tsx (5)

428-446: Injected script references logger (undefined in iframe) → ReferenceError

logger is a React hook variable; it doesn’t exist inside the iframe’s srcDoc. These calls will throw and may prevent fallbacks from running.

-                        navigator.serviceWorker.register(swUrl).then(registration => {
-                            logger.info('Service Worker registered successfully');
+                        navigator.serviceWorker.register(swUrl).then(registration => {
+                            console.log('[DesignFrame] Service Worker registered');
...
-                        }).catch(error => {
-                            logger.info('Service Worker registration failed, falling back to direct loading');
+                        }).catch(error => {
+                            console.log('[DesignFrame] SW registration failed; using direct loading');

Alternatively, remove SW entirely and keep processImages().


385-386: CSP is overly permissive; tighten and use nonce

Current CSP allows 'unsafe-inline' and 'unsafe-eval' across the board. Enforce nonce-based scripting and minimal sources.

-                const iframeCSP = `<meta http-equiv="Content-Security-Policy" content="default-src 'self' 'unsafe-inline' 'unsafe-eval' data: blob: https: http:; img-src 'self' data: blob: https: http: *; style-src 'self' 'unsafe-inline' data: https: http: *; script-src 'self' 'unsafe-inline' 'unsafe-eval' data: blob: https: http: *; connect-src 'self' https: http: *; frame-src 'self' data: blob: https: http: *;">`;
+                const iframeCSP = `<meta http-equiv="Content-Security-Policy" content="
+                    default-src 'none';
+                    img-src data: https:;
+                    style-src 'unsafe-inline';
+                    script-src ${nonce ? `'nonce-${nonce}'` : "'self'"} 'strict-dynamic';
+                    connect-src https:;
+                    font-src data:;
+                    frame-src 'self';
+                    object-src 'none';
+                    base-uri 'none';
+                ">`;

Note: If nonce is absent, fall back to self and avoid eval entirely.


542-566: Sandbox iframes to reduce risk

Add a sandbox attribute allowing only what you need (scripts and same-origin if required). Apply to both iframe render paths.

                     <iframe
                         srcDoc={modifiedContent}
                         title={`${file.name} - ${getViewportLabel(viewport)}`}
+                        sandbox="allow-scripts allow-same-origin"
                         style={{

Also add the same to the SVG iframe.


374-380: Nonce injection via replaceAll is brittle

String replacement can miss edge cases or double-inject. Use DOMParser to set nonce on scripts safely.

-                const injectNonce = (html: string, nonce: string | null) => {
-                    if (!nonce) {
-                        return html;
-                    }
-                    return html.replaceAll('<script', `<script nonce="${nonce}"`);
-                };
+                const injectNonce = (html: string, nonce: string | null) => {
+                    if (!nonce) return html;
+                    const doc = new DOMParser().parseFromString(html, 'text/html');
+                    doc.querySelectorAll('script:not([nonce])').forEach(s => s.setAttribute('nonce', nonce));
+                    return '<!DOCTYPE html>' + doc.documentElement.outerHTML;
+                };

571-608: Direct HTML/SVG rendering without sanitization

dangerouslySetInnerHTML must be sanitized. Adopt DOMPurify (or equivalent) and limit allowed tags/attrs. If adding a dep isn’t viable, disable 'html' mode for untrusted content.

-            case 'html': {
+            case 'html': {
                 // Direct HTML/SVG rendering - USE WITH CAUTION (security risk)
                 // Only use for trusted content or when iframe fails
                 if (file.fileType === 'svg') {
                     return (
                         <div
...
-                            dangerouslySetInnerHTML={{ __html: file.content }}
+                            dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(file.content, { USE_PROFILES: { svg: true, svgFilters: true } }) }}
                         />
                     );
                 }
 
                 return (
                     <div
-                        dangerouslySetInnerHTML={{ __html: file.content }}
+                        dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(file.content, { USE_PROFILES: { html: true } }) }}

Add at top of file:

import DOMPurify from 'dompurify';

And ensure the bundle includes it.

Comment thread src/api/viewApi.ts
showInformationMessage: (message: string) => void;
showErrorMessage: (message: string) => void;
executeCommand: (command: string, args?: any) => Promise<void>;
executeCommand: (command: string, arguments_?: any) => Promise<void>;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Preserve varargs and avoid any in executeCommand signature

Expose a varargs API and drop any to keep type safety and compatibility with vscode.commands (which is variadic).

-    executeCommand: (command: string, arguments_?: any) => Promise<void>;
+    executeCommand: (command: string, ...args: unknown[]) => Promise<void>;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
executeCommand: (command: string, arguments_?: any) => Promise<void>;
// Before:
- executeCommand: (command: string, arguments_?: any) => Promise<void>;
// After:
executeCommand: (command: string, ...args: unknown[]) => Promise<void>;
🤖 Prompt for AI Agents
In src/api/viewApi.ts around line 14, the executeCommand signature currently
uses a single arguments_?: any parameter; change it to a variadic, typed
signature to match vscode.commands and remove any for type safety — e.g. make it
generic and accept rest args like executeCommand<T = unknown>(command: string,
...args: unknown[]): Promise<T>; update all implementations and callers to
forward/rest-spread arguments accordingly and adjust return types to the generic
T.

Comment thread src/api/viewApi.ts
Comment on lines +108 to 121
export function isViewApiRequest(message: any): message is ViewApiRequest {
return (
msg &&
typeof msg === 'object' &&
msg.type === 'request' &&
typeof msg.id === 'string' &&
typeof msg.key === 'string' &&
Array.isArray(msg.params) &&
(msg.context === undefined ||
(typeof msg.context === 'object' &&
typeof msg.context.viewId === 'string' &&
typeof msg.context.viewType === 'string' &&
typeof msg.context.timestamp === 'number'))
message &&
typeof message === 'object' &&
message.type === 'request' &&
typeof message.id === 'string' &&
typeof message.key === 'string' &&
Array.isArray(message.params) &&
(message.context === undefined ||
(typeof message.context === 'object' &&
typeof message.context.viewId === 'string' &&
typeof message.context.viewType === 'string' &&
typeof message.context.timestamp === 'number'))
);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Type guards: accept unknown and harden null/object checks

Use unknown and explicit null checks.

-export function isViewApiRequest(message: any): message is ViewApiRequest {
+export function isViewApiRequest(message: unknown): message is ViewApiRequest {
-    return (
-        message &&
-        typeof message === 'object' &&
+    return (
+        message != null &&
+        typeof message === 'object' &&
         (message as any).type === 'request' &&
         typeof (message as any).id === 'string' &&
         typeof (message as any).key === 'string' &&
         Array.isArray((message as any).params) &&
-        (message.context === undefined ||
-            (typeof message.context === 'object' &&
-                typeof message.context.viewId === 'string' &&
-                typeof message.context.viewType === 'string' &&
-                typeof message.context.timestamp === 'number'))
+        ((message as any).context === undefined ||
+            ((message as any).context != null &&
+                typeof (message as any).context === 'object' &&
+                typeof (message as any).context.viewId === 'string' &&
+                typeof (message as any).context.viewType === 'string' &&
+                typeof (message as any).context.timestamp === 'number'))
     );
}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export function isViewApiRequest(message: any): message is ViewApiRequest {
return (
msg &&
typeof msg === 'object' &&
msg.type === 'request' &&
typeof msg.id === 'string' &&
typeof msg.key === 'string' &&
Array.isArray(msg.params) &&
(msg.context === undefined ||
(typeof msg.context === 'object' &&
typeof msg.context.viewId === 'string' &&
typeof msg.context.viewType === 'string' &&
typeof msg.context.timestamp === 'number'))
message &&
typeof message === 'object' &&
message.type === 'request' &&
typeof message.id === 'string' &&
typeof message.key === 'string' &&
Array.isArray(message.params) &&
(message.context === undefined ||
(typeof message.context === 'object' &&
typeof message.context.viewId === 'string' &&
typeof message.context.viewType === 'string' &&
typeof message.context.timestamp === 'number'))
);
export function isViewApiRequest(message: unknown): message is ViewApiRequest {
return (
message != null &&
typeof message === 'object' &&
(message as any).type === 'request' &&
typeof (message as any).id === 'string' &&
typeof (message as any).key === 'string' &&
Array.isArray((message as any).params) &&
((message as any).context === undefined ||
((message as any).context != null &&
typeof (message as any).context === 'object' &&
typeof (message as any).context.viewId === 'string' &&
typeof (message as any).context.viewType === 'string' &&
typeof (message as any).context.timestamp === 'number'))
);
}
🤖 Prompt for AI Agents
In src/api/viewApi.ts around lines 108 to 121, the type guard should accept an
unknown input and harden null/object checks: change the parameter type to
unknown, first verify message !== null && typeof message === 'object', use a
typed helper (or inline) to ensure message has the required string/array
properties (check typeof (message as any).id === 'string', etc.), and for
context ensure (message as any).context === undefined || ((message as
any).context !== null && typeof (message as any).context === 'object' && typeof
(message as any).context.viewId === 'string' && typeof (message as
any).context.viewType === 'string' && typeof (message as any).context.timestamp
=== 'number')); update the function signature and internal checks accordingly so
the guard is safe for unknown and null inputs.

Comment thread src/api/viewApi.ts
Comment on lines +127 to 134
export function isViewApiResponse(message: any): message is ViewApiResponse {
return (
msg &&
typeof msg === 'object' &&
msg.type === 'response' &&
typeof msg.id === 'string' &&
msg.hasOwnProperty('value')
message &&
typeof message === 'object' &&
message.type === 'response' &&
typeof message.id === 'string' &&
message.hasOwnProperty('value')
);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Use safe hasOwnProperty for responses

Avoid direct method access on possibly null-prototype objects.

-export function isViewApiResponse(message: any): message is ViewApiResponse {
+export function isViewApiResponse(message: unknown): message is ViewApiResponse {
     return (
-        message &&
-        typeof message === 'object' &&
-        message.type === 'response' &&
-        typeof message.id === 'string' &&
-        message.hasOwnProperty('value')
+        message != null &&
+        typeof message === 'object' &&
+        (message as any).type === 'response' &&
+        typeof (message as any).id === 'string' &&
+        Object.prototype.hasOwnProperty.call(message, 'value')
     );
}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export function isViewApiResponse(message: any): message is ViewApiResponse {
return (
msg &&
typeof msg === 'object' &&
msg.type === 'response' &&
typeof msg.id === 'string' &&
msg.hasOwnProperty('value')
message &&
typeof message === 'object' &&
message.type === 'response' &&
typeof message.id === 'string' &&
message.hasOwnProperty('value')
);
export function isViewApiResponse(message: unknown): message is ViewApiResponse {
return (
message != null &&
typeof message === 'object' &&
(message as any).type === 'response' &&
typeof (message as any).id === 'string' &&
Object.prototype.hasOwnProperty.call(message, 'value')
);
}
🤖 Prompt for AI Agents
In src/api/viewApi.ts around lines 127 to 134, the type guard uses
message.hasOwnProperty('value') which can throw or be incorrect for objects with
null prototypes; replace that check with a safe call such as
Object.prototype.hasOwnProperty.call(message, 'value') (or Reflect.has(message,
'value')) so the presence check works for all plain and null-prototype objects
while preserving the rest of the type guard.

Comment thread src/api/viewApi.ts
Comment on lines +140 to 147
export function isViewApiError(message: any): message is ViewApiError {
return (
msg &&
typeof msg === 'object' &&
msg.type === 'error' &&
typeof msg.id === 'string' &&
typeof msg.value === 'string'
message &&
typeof message === 'object' &&
message.type === 'error' &&
typeof message.id === 'string' &&
typeof message.value === 'string'
);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Harden error type guard (unknown + null check)

-export function isViewApiError(message: any): message is ViewApiError {
+export function isViewApiError(message: unknown): message is ViewApiError {
     return (
-        message &&
-        typeof message === 'object' &&
-        message.type === 'error' &&
-        typeof message.id === 'string' &&
-        typeof message.value === 'string'
+        message != null &&
+        typeof message === 'object' &&
+        (message as any).type === 'error' &&
+        typeof (message as any).id === 'string' &&
+        typeof (message as any).value === 'string'
     );
}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export function isViewApiError(message: any): message is ViewApiError {
return (
msg &&
typeof msg === 'object' &&
msg.type === 'error' &&
typeof msg.id === 'string' &&
typeof msg.value === 'string'
message &&
typeof message === 'object' &&
message.type === 'error' &&
typeof message.id === 'string' &&
typeof message.value === 'string'
);
export function isViewApiError(message: unknown): message is ViewApiError {
return (
message != null &&
typeof message === 'object' &&
(message as any).type === 'error' &&
typeof (message as any).id === 'string' &&
typeof (message as any).value === 'string'
);
}
🤖 Prompt for AI Agents
In src/api/viewApi.ts around lines 140 to 147, the type guard currently accepts
any and doesn't guard against null; change the parameter type to unknown and
explicitly check message !== null before typeof message === 'object', then
narrow to a record/any to validate message.type === 'error' and that id and
value are strings (use typeof checks on (message as any).id and (message as
any).value). Ensure the function signature is updated to message: unknown and
the runtime checks prevent null dereference while preserving the ViewApiError
type predicate.

Comment thread src/api/viewApi.ts
Comment on lines +153 to 160
export function isViewApiEvent(message: any): message is ViewApiEvent {
return (
msg &&
typeof msg === 'object' &&
msg.type === 'event' &&
typeof msg.key === 'string' &&
Array.isArray(msg.value)
message &&
typeof message === 'object' &&
message.type === 'event' &&
typeof message.key === 'string' &&
Array.isArray(message.value)
);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Harden event type guard (unknown + null check)

-export function isViewApiEvent(message: any): message is ViewApiEvent {
+export function isViewApiEvent(message: unknown): message is ViewApiEvent {
     return (
-        message &&
-        typeof message === 'object' &&
-        message.type === 'event' &&
-        typeof message.key === 'string' &&
-        Array.isArray(message.value)
+        message != null &&
+        typeof message === 'object' &&
+        (message as any).type === 'event' &&
+        typeof (message as any).key === 'string' &&
+        Array.isArray((message as any).value)
     );
}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export function isViewApiEvent(message: any): message is ViewApiEvent {
return (
msg &&
typeof msg === 'object' &&
msg.type === 'event' &&
typeof msg.key === 'string' &&
Array.isArray(msg.value)
message &&
typeof message === 'object' &&
message.type === 'event' &&
typeof message.key === 'string' &&
Array.isArray(message.value)
);
export function isViewApiEvent(message: unknown): message is ViewApiEvent {
return (
message != null &&
typeof message === 'object' &&
(message as any).type === 'event' &&
typeof (message as any).key === 'string' &&
Array.isArray((message as any).value)
);
}
🤖 Prompt for AI Agents
In src/api/viewApi.ts around lines 153 to 160, the type guard currently accepts
any and doesn't protect against null or unknown input; change the function
signature to accept message: unknown, add an explicit null check (message !==
null) before typeof message === 'object', and perform property checks using safe
narrowing (e.g., ('type' in message) && (message as any).type === 'event' and
typeof (message as any).key === 'string' and Array.isArray((message as
any).value)) so the guard doesn't throw on null/primitive inputs and correctly
narrows to ViewApiEvent.

Comment on lines +36 to 39
a: ({ children, href, ...properties }) => (
<a href={href} target='_blank' rel='noopener noreferrer' {...properties}>
{children}
</a>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Sanitize link protocols.

Restrict to http(s)/mailto to harden the webview.

-                    a: ({ children, href, ...properties }) => (
-                        <a href={href} target='_blank' rel='noopener noreferrer' {...properties}>
-                            {children}
-                        </a>
-                    ),
+                    a: ({ children, href, ...properties }) => {
+                        const safeHref =
+                            typeof href === 'string' && /^(https?:|mailto:)/i.test(href)
+                                ? href
+                                : undefined;
+                        return (
+                            <a href={safeHref} target='_blank' rel='noopener noreferrer' {...properties}>
+                                {children}
+                            </a>
+                        );
+                    },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
a: ({ children, href, ...properties }) => (
<a href={href} target='_blank' rel='noopener noreferrer' {...properties}>
{children}
</a>
a: ({ children, href, ...properties }) => {
const safeHref =
typeof href === 'string' && /^(https?:|mailto:)/i.test(href)
? href
: undefined;
return (
<a href={safeHref} target="_blank" rel="noopener noreferrer" {...properties}>
{children}
</a>
);
},

Comment on lines +105 to 109
viewId: `webview-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`,
viewType: 'chat-interface',
timestamp: Date.now(),
sessionId: `session-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
sessionId: `session-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`,
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Use crypto.randomUUID for IDs (CodeQL: insecure randomness)

Math.random() is flagged as insecure. Prefer crypto.randomUUID() with a safe fallback.

-    const contextReference = useRef<RequestContext>({
-        viewId: `webview-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`,
+    const contextReference = useRef<RequestContext>({
+        viewId: `webview-${Date.now()}-${(typeof crypto !== 'undefined' && 'randomUUID' in crypto ? crypto.randomUUID() : generateId())}`,
         viewType: 'chat-interface',
         timestamp: Date.now(),
-        sessionId: `session-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`,
+        sessionId: `session-${Date.now()}-${(typeof crypto !== 'undefined' && 'randomUUID' in crypto ? crypto.randomUUID() : generateId())}`,
     });

Committable suggestion skipped: line range outside the PR's diff.

🧰 Tools
🪛 GitHub Check: CodeQL

[failure] 108-108: Insecure randomness
This uses a cryptographically insecure random number generated at Math.random() in a security context.

🤖 Prompt for AI Agents
In src/webview/contexts/WebviewContext.tsx around lines 105-109, the current ID
generation uses Math.random() which is insecure; replace those
Math.random()-based IDs with crypto.randomUUID() and provide a safe fallback for
environments without crypto.randomUUID (e.g., generate a UUIDv4 using
crypto.getRandomValues or import a small UUID v4 helper). Ensure viewId and
sessionId keep the existing prefixes (`webview-` and `session-`) and include the
UUID from crypto.randomUUID() (or the fallback) instead of the Math.random()
string.

Comment on lines +285 to 290
for (const deferred of currentRequests) {
deferred.clearTimeout(); // Clear timeout to prevent late firing
deferred.reject(new Error('WebviewProvider unmounted')); // Reject first while not settled
deferred.markSettled(); // Then mark as settled to prevent subsequent resolve/reject calls
});
}
currentRequests.clear();
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Bug: pendingRequests cleanup iterates Map entries, not values

currentRequests is a Map, so iterating it yields [id, deferred]. The loop calls methods on the tuple, causing runtime errors and leaking pending promises on unmount.

Apply:

-            for (const deferred of currentRequests) {
-                deferred.clearTimeout(); // Clear timeout to prevent late firing
-                deferred.reject(new Error('WebviewProvider unmounted')); // Reject first while not settled
-                deferred.markSettled(); // Then mark as settled to prevent subsequent resolve/reject calls
-            }
+            for (const [, deferred] of currentRequests) {
+                deferred.clearTimeout();
+                deferred.reject(new Error('WebviewProvider unmounted'));
+            }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
for (const deferred of currentRequests) {
deferred.clearTimeout(); // Clear timeout to prevent late firing
deferred.reject(new Error('WebviewProvider unmounted')); // Reject first while not settled
deferred.markSettled(); // Then mark as settled to prevent subsequent resolve/reject calls
});
}
currentRequests.clear();
for (const [, deferred] of currentRequests) {
deferred.clearTimeout(); // prevent late firing
deferred.reject(new Error('WebviewProvider unmounted')); // reject pending promise
deferred.markSettled(); // prevent any further settles
}
currentRequests.clear();
🤖 Prompt for AI Agents
In src/webview/contexts/WebviewContext.tsx around lines 285 to 290, the cleanup
loop treats currentRequests (a Map) as if it yields deferred objects directly —
iterating a Map returns [id, deferred] pairs, so calling methods on the tuple
causes runtime errors and leaks promises; change the loop to iterate the Map
values (or destructure the pair) so you call deferred.clearTimeout(),
deferred.reject(...), and deferred.markSettled() on the actual deferred object,
then call currentRequests.clear().

Comment on lines +279 to 283
for (const node of tree.nodes) {
maxX = Math.max(maxX, node.position.x + frameWidth + 100);
maxY = Math.max(maxY, node.position.y + frameHeight + 100);
});
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Bug: Iterating Map incorrectly; node is a [key, value] tuple.

for (const node of tree.nodes) yields [string, HierarchyNode]. Accessing node.position will throw. Iterate values or destructure.

-    for (const node of tree.nodes) {
-        maxX = Math.max(maxX, node.position.x + frameWidth + 100);
-        maxY = Math.max(maxY, node.position.y + frameHeight + 100);
-    }
+    for (const [, node] of tree.nodes) {
+        maxX = Math.max(maxX, node.position.x + frameWidth + 100);
+        maxY = Math.max(maxY, node.position.y + frameHeight + 100);
+    }
+    // or: for (const node of tree.nodes.values()) { ... }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
for (const node of tree.nodes) {
maxX = Math.max(maxX, node.position.x + frameWidth + 100);
maxY = Math.max(maxY, node.position.y + frameHeight + 100);
});
}
// Before: iterating entries yields [key, node] tuples
- for (const node of tree.nodes) {
- maxX = Math.max(maxX, node.position.x + frameWidth + 100);
- maxY = Math.max(maxY, node.position.y + frameHeight + 100);
- }
// After: destructure to get the node, or iterate over values()
for (const [, node] of tree.nodes) {
maxX = Math.max(maxX, node.position.x + frameWidth + 100);
maxY = Math.max(maxY, node.position.y + frameHeight + 100);
}
// alternatively:
// for (const node of tree.nodes.values()) {
// maxX = Math.max(maxX, node.position.x + frameWidth + 100);
// maxY = Math.max(maxY, node.position.y + frameHeight + 100);
// }
🤖 Prompt for AI Agents
In src/webview/utils/gridLayout.ts around lines 279 to 283, the loop `for (const
node of tree.nodes)` is iterating Map entries ([key, value]) so `node.position`
will throw; change the iteration to either iterate the Map values or destructure
the entry to obtain the HierarchyNode (e.g., use `for (const [, node] of
tree.nodes)` or `for (const node of tree.nodes.values())`) and then use
`node.position.x`/`node.position.y` to compute maxX/maxY as before.

Comment on lines +469 to 470
design.branchIndex = Number.parseInt(design.version) - 1;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

NaN risk for branchIndex on non-numeric versions.

Number.parseInt(design.version) - 1 can yield NaN. Guard and use radix 10.

-            design.branchIndex = Number.parseInt(design.version) - 1;
+            {
+                const v = Number.parseInt(String(design.version), 10);
+                design.branchIndex = Number.isFinite(v) ? Math.max(0, v - 1) : 0;
+            }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
design.branchIndex = Number.parseInt(design.version) - 1;
}
{
const v = Number.parseInt(String(design.version), 10);
design.branchIndex = Number.isFinite(v) ? Math.max(0, v - 1) : 0;
}
}
🤖 Prompt for AI Agents
In src/webview/utils/gridLayout.ts around lines 469-470, replace the unguarded
Number.parseInt(design.version) - 1 with a radix-10 parse and an NaN check: call
Number.parseInt(design.version, 10), test Number.isNaN(result), and only assign
design.branchIndex = parsed - 1 when parsed is a valid number; otherwise set
design.branchIndex to a safe fallback (e.g., undefined or -1) or leave it
unchanged so it never becomes NaN.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants