From 59a822a878cbbc0ccc2a7c6f8c79ee51fb71f225 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 7 Feb 2026 22:23:41 +0000 Subject: [PATCH 1/8] Initial plan From cf7923404ce3521cb30b76dbd482e23af9bf528f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 7 Feb 2026 22:30:40 +0000 Subject: [PATCH 2/8] Add #sym detection and fix diagnostics logging issues MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add detection for #sym alias (in addition to #symbol) in text analysis - Add symbol reference detection from contentReferences (kind='reference' with name field) - Display symbol references in log viewer with 🔤 icon and proper formatting - Separate symbols from files in context reference details - Remove DEBUG console.log statements from diagnostics view - Document logging differences between extension and webview in copilot-instructions.md Co-authored-by: rajbos <6085745+rajbos@users.noreply.github.com> --- .github/copilot-instructions.md | 8 +++++++ package-lock.json | 7 ++++++ src/extension.ts | 17 +++++++++++++- src/webview/diagnostics/main.ts | 8 ------- src/webview/logviewer/main.ts | 39 ++++++++++++++++++++++++++++----- 5 files changed, 65 insertions(+), 14 deletions(-) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index b0e644e..f7fb6d2 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -40,6 +40,14 @@ The entire extension's logic is contained within the `CopilotTokenTracker` class **CRITICAL**: Do NOT add debug logging statements like `log('[DEBUG] message')` for troubleshooting during development. This approach has been found to interfere with the output channel and can hide existing log messages from appearing. +**Root Cause**: The issue occurs when webview code (diagnostics panel, etc.) uses `console.log` statements with DEBUG prefixes. These logs are written to the browser console (Developer Tools) of the webview, not to the extension's output channel. When clearing the cache or performing other operations, if there are DEBUG console.log statements in the webview code, they don't affect the output channel directly. However, the pattern of using DEBUG prefixes in webviews was removed to maintain consistency with the extension's logging guidelines and avoid confusion. + +**The Difference**: +- **Extension logging** (`src/extension.ts`): Uses `this.outputChannel.appendLine()` to write to VS Code's Output Channel, which is visible in the Output panel. +- **Webview logging** (`src/webview/*/main.ts`): Uses `console.log()` to write to the browser console, which is only visible when you open Developer Tools in the webview. + +When you clear the output channel using `outputChannel.clear()` or similar operations, it only affects the extension's output channel, not the webview's browser console. The two are separate logging systems. + - **Use Existing Logs**: The extension already has comprehensive logging throughout. Review existing log statements to understand what's being tracked. - **Minimal Logging**: Only add logging if absolutely necessary for a new feature. Keep messages concise and informative. - **Remove Debug Logs**: Any temporary debug logging added during development MUST be removed before committing code. diff --git a/package-lock.json b/package-lock.json index 149eaf5..26a156b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -563,6 +563,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=18" }, @@ -604,6 +605,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=18" } @@ -1894,6 +1896,7 @@ "integrity": "sha512-BtE0k6cjwjLZoZixN0t5AKP0kSzlGu7FctRXYuPAm//aaiZhmfq1JwdYpYr1brzEspYyFeF+8XF5j2VK6oalrA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.54.0", "@typescript-eslint/types": "8.54.0", @@ -2554,6 +2557,7 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -4036,6 +4040,7 @@ "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -8648,6 +8653,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -8883,6 +8889,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" diff --git a/src/extension.ts b/src/extension.ts index da7c837..f5a2753 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1807,11 +1807,15 @@ class CopilotTokenTracker implements vscode.Disposable { refs.selection += selectionMatches.length; } - // Count #symbol references + // Count #symbol and #sym references (both aliases) const symbolMatches = text.match(/#symbol/gi); + const symMatches = text.match(/#sym\b/gi); // \b ensures we don't match #symbol if (symbolMatches) { refs.symbol += symbolMatches.length; } + if (symMatches) { + refs.symbol += symMatches.length; + } // Count #codebase references const codebaseMatches = text.match(/#codebase/gi); @@ -1894,6 +1898,17 @@ class CopilotTokenTracker implements vscode.Disposable { const pathKey = fsPath.length > 100 ? '...' + fsPath.substring(fsPath.length - 97) : fsPath; refs.byPath[pathKey] = (refs.byPath[pathKey] || 0) + 1; } + + // Handle symbol references (e.g., #sym:functionName) + // Symbol references have a 'name' field instead of fsPath + const symbolName = reference.name; + if (typeof symbolName === 'string' && kind === 'reference') { + // This is a symbol reference, track it + refs.symbol++; + // Track symbol by name for display (use 'name' as path) + const symbolKey = `#sym:${symbolName}`; + refs.byPath[symbolKey] = (refs.byPath[symbolKey] || 0) + 1; + } } } } diff --git a/src/webview/diagnostics/main.ts b/src/webview/diagnostics/main.ts index 784294d..1226025 100644 --- a/src/webview/diagnostics/main.ts +++ b/src/webview/diagnostics/main.ts @@ -1109,8 +1109,6 @@ function renderLayout(data: DiagnosticsData): void { btnTab.disabled = false; } - console.log('DEBUG Cache cleared confirmation received'); - // Re-enable buttons after a short delay and reset to original state setTimeout(() => { if (btnReport) { @@ -1146,7 +1144,6 @@ function renderLayout(data: DiagnosticsData): void { if (ageValue) { ageValue.textContent = '0 seconds ago'; } } } - console.log('DEBUG Cache refreshed with new data:', cacheInfo); } } }); @@ -1301,7 +1298,6 @@ function setupStorageLinkHandlers(): void { } document.getElementById('btn-clear-cache')?.addEventListener('click', () => { - console.log('DEBUG Clear cache button clicked (report tab)'); const btn = document.getElementById('btn-clear-cache') as HTMLButtonElement | null; if (btn) { btn.style.background = '#d97706'; @@ -1314,7 +1310,6 @@ function setupStorageLinkHandlers(): void { }); document.getElementById('btn-clear-cache-tab')?.addEventListener('click', () => { - console.log('DEBUG Clear cache button clicked (cache tab)'); const btn = document.getElementById('btn-clear-cache-tab') as HTMLButtonElement | null; if (btn) { btn.style.background = '#d97706'; @@ -1331,7 +1326,6 @@ function setupStorageLinkHandlers(): void { const target = event.target as HTMLElement; if (!target) { return; } if (target.id === 'btn-clear-cache' || target.id === 'btn-clear-cache-tab') { - console.log('DEBUG Clear cache button clicked via delegated handler', target.id); target.style.background = '#d97706'; target.innerHTML = 'Clearing...'; if (target instanceof HTMLButtonElement) { @@ -1350,12 +1344,10 @@ function setupStorageLinkHandlers(): void { // Backend configuration buttons document.getElementById('btn-configure-backend')?.addEventListener('click', () => { - console.log('[DEBUG] Configure backend button clicked'); vscode.postMessage({ command: 'configureBackend' }); }); document.getElementById('btn-open-settings')?.addEventListener('click', () => { - console.log('[DEBUG] Open settings button clicked'); vscode.postMessage({ command: 'openSettings' }); }); diff --git a/src/webview/logviewer/main.ts b/src/webview/logviewer/main.ts index f082897..c9bffed 100644 --- a/src/webview/logviewer/main.ts +++ b/src/webview/logviewer/main.ts @@ -147,10 +147,33 @@ function renderContextReferencesDetailed(refs: ContextReferenceUsage): string { // Show file paths if any if (refs.byPath && Object.keys(refs.byPath).length > 0) { - const pathList = Object.entries(refs.byPath) - .map(([path, count]) => `${getFileName(path)}: ${count}`) - .join(', '); - sections.push(`
Files: ${pathList}
`); + // Separate symbols from files + const symbols: [string, number][] = []; + const files: [string, number][] = []; + + Object.entries(refs.byPath).forEach(([path, count]) => { + if (path.startsWith('#sym:')) { + symbols.push([path.substring(5), count]); // Remove '#sym:' prefix + } else { + files.push([path, count]); + } + }); + + // Show files + if (files.length > 0) { + const pathList = files + .map(([path, count]) => `${getFileName(path)}: ${count}`) + .join(', '); + sections.push(`
Files: ${pathList}
`); + } + + // Show symbols + if (symbols.length > 0) { + const symbolList = symbols + .map(([symbolName, count]) => `${symbolName}: ${count}`) + .join(', '); + sections.push(`
Symbols: ${symbolList}
`); + } } return sections.length > 0 ? sections.join('') : '
No additional details
'; @@ -214,7 +237,13 @@ function renderTurnCard(turn: ChatTurn): string { }); otherPaths.forEach(([path]) => { - contextFileBadges.push(`📄 ${escapeHtml(getFileName(path))}`); + // Check if this is a symbol reference + if (path.startsWith('#sym:')) { + const symbolName = path.substring(5); // Remove '#sym:' prefix + contextFileBadges.push(`🔤 ${escapeHtml(symbolName)}`); + } else { + contextFileBadges.push(`📄 ${escapeHtml(getFileName(path))}`); + } }); } From f7f7d22df7610ed7f830d329ec43a16b0837b2ec Mon Sep 17 00:00:00 2001 From: Rob Bos Date: Sun, 8 Feb 2026 17:09:19 +0100 Subject: [PATCH 3/8] Refactor context reference handling: consolidate utility functions and enhance context reference analysis in webviews --- package-lock.json | 7 -- src/extension.ts | 157 ++++++++++++++------------ src/webview/diagnostics/main.ts | 111 ++++++++++++------ src/webview/logviewer/main.ts | 45 +------- src/webview/shared/contextRefUtils.ts | 81 +++++++++++++ src/webview/usage/main.ts | 25 +--- 6 files changed, 245 insertions(+), 181 deletions(-) create mode 100644 src/webview/shared/contextRefUtils.ts diff --git a/package-lock.json b/package-lock.json index 26a156b..149eaf5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -563,7 +563,6 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": ">=18" }, @@ -605,7 +604,6 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": ">=18" } @@ -1896,7 +1894,6 @@ "integrity": "sha512-BtE0k6cjwjLZoZixN0t5AKP0kSzlGu7FctRXYuPAm//aaiZhmfq1JwdYpYr1brzEspYyFeF+8XF5j2VK6oalrA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.54.0", "@typescript-eslint/types": "8.54.0", @@ -2557,7 +2554,6 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -4040,7 +4036,6 @@ "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -8653,7 +8648,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -8889,7 +8883,6 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" diff --git a/src/extension.ts b/src/extension.ts index c80d1c6..19ad556 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1558,9 +1558,15 @@ class CopilotTokenTracker implements vscode.Disposable { if (event.kind === 0 && event.v?.inputState?.mode?.kind) { sessionMode = event.v.inputState.mode.kind; - // Detect implicit selections in initial state - if (event.v?.inputState?.selections && Array.isArray(event.v.inputState.selections) && event.v.inputState.selections.length > 0) { - analysis.contextReferences.implicitSelection++; + // Detect implicit selections in initial state (only if there's an actual range) + if (event.v?.inputState?.selections && Array.isArray(event.v.inputState.selections)) { + for (const sel of event.v.inputState.selections) { + // Only count if it's an actual selection (not just a cursor position) + if (sel.startLineNumber !== sel.endLineNumber || sel.startColumn !== sel.endColumn) { + analysis.contextReferences.implicitSelection++; + break; // Count once per session + } + } } } @@ -1570,8 +1576,24 @@ class CopilotTokenTracker implements vscode.Disposable { } // Detect implicit selections in updates to inputState.selections - if (event.kind === 1 && event.k?.includes('selections') && Array.isArray(event.v) && event.v.length > 0) { - analysis.contextReferences.implicitSelection++; + if (event.kind === 1 && event.k?.includes('selections') && Array.isArray(event.v)) { + for (const sel of event.v) { + // Only count if it's an actual selection (not just a cursor position) + if (sel && (sel.startLineNumber !== sel.endLineNumber || sel.startColumn !== sel.endColumn)) { + analysis.contextReferences.implicitSelection++; + break; // Count once per update + } + } + } + + // Handle contentReferences updates (kind: 1 with contentReferences update) + if (event.kind === 1 && event.k?.includes('contentReferences') && Array.isArray(event.v)) { + this.analyzeContentReferences(event.v, analysis.contextReferences); + } + + // Handle variableData updates (kind: 1 with variableData update) + if (event.kind === 1 && event.k?.includes('variableData') && event.v) { + this.analyzeVariableData(event.v, analysis.contextReferences); } // Handle VS Code incremental format - count requests as interactions @@ -1594,10 +1616,8 @@ class CopilotTokenTracker implements vscode.Disposable { analysis.toolCalls.byTool[toolName] = (analysis.toolCalls.byTool[toolName] || 0) + 1; } - // Analyze contentReferences if present - if (request.contentReferences && Array.isArray(request.contentReferences)) { - this.analyzeContentReferences(request.contentReferences, analysis.contextReferences); - } + // Analyze all context references from this request + this.analyzeRequestContext(request, analysis.contextReferences); // Extract tool calls from request.response array (when full request is added) if (request.response && Array.isArray(request.response)) { @@ -1704,52 +1724,8 @@ class CopilotTokenTracker implements vscode.Disposable { analysis.modeUsage.ask++; } - // Analyze user message for context references - if (request.message) { - if (request.message.text) { - this.analyzeContextReferences(request.message.text, analysis.contextReferences); - } - if (request.message.parts) { - for (const part of request.message.parts) { - if (part.text) { - this.analyzeContextReferences(part.text, analysis.contextReferences); - } - } - } - } - - // Analyze variableData for @workspace, @terminal, @vscode references - if (request.variableData) { - // Process variables array for prompt files and other context - this.analyzeVariableData(request.variableData, analysis.contextReferences); - - // Also check for @ references in variable names/values - const varDataStr = JSON.stringify(request.variableData).toLowerCase(); - if (varDataStr.includes('workspace')) { - analysis.contextReferences.workspace++; - } - if (varDataStr.includes('terminal')) { - analysis.contextReferences.terminal++; - } - if (varDataStr.includes('vscode')) { - analysis.contextReferences.vscode++; - } - } - - // Analyze contentReferences if present - if (request.contentReferences && Array.isArray(request.contentReferences)) { - this.analyzeContentReferences(request.contentReferences, analysis.contextReferences); - } - - // Analyze variableData if present - if (request.variableData) { - this.analyzeVariableData(request.variableData, analysis.contextReferences); - } - - // Analyze variableData if present - if (request.variableData) { - this.analyzeVariableData(request.variableData, analysis.contextReferences); - } + // Analyze all context references from this request + this.analyzeRequestContext(request, analysis.contextReferences); // Analyze response for tool calls and MCP tools if (request.response && Array.isArray(request.response)) { @@ -1857,6 +1833,36 @@ class CopilotTokenTracker implements vscode.Disposable { } } + /** + * Analyze a request object for all context references. + * This is the unified method that processes text, contentReferences, and variableData. + */ + private analyzeRequestContext(request: any, refs: ContextReferenceUsage): void { + // Analyze user message text for context references + if (request.message) { + if (request.message.text) { + this.analyzeContextReferences(request.message.text, refs); + } + if (request.message.parts) { + for (const part of request.message.parts) { + if (part.text) { + this.analyzeContextReferences(part.text, refs); + } + } + } + } + + // Analyze contentReferences if present + if (request.contentReferences && Array.isArray(request.contentReferences)) { + this.analyzeContentReferences(request.contentReferences, refs); + } + + // Analyze variableData if present + if (request.variableData) { + this.analyzeVariableData(request.variableData, refs); + } + } + /** * Analyze text for context references like #file, #selection, @workspace */ @@ -1874,8 +1880,9 @@ class CopilotTokenTracker implements vscode.Disposable { } // Count #symbol and #sym references (both aliases) + // Note: #sym:symbolName format is handled via variableData, not text matching const symbolMatches = text.match(/#symbol/gi); - const symMatches = text.match(/#sym\b/gi); // \b ensures we don't match #symbol + const symMatches = text.match(/#sym(?![:\w])/gi); // Negative lookahead: don't match #symbol or #sym: if (symbolMatches) { refs.symbol += symbolMatches.length; } @@ -1947,13 +1954,18 @@ class CopilotTokenTracker implements vscode.Disposable { // Normalize path separators for pattern matching const normalizedPath = fsPath.replace(/\\/g, '/').toLowerCase(); - // Track specific patterns + // Track specific patterns - these are auto-attached, not user-explicit #file refs if (normalizedPath.endsWith('/.github/copilot-instructions.md') || normalizedPath.includes('.github/copilot-instructions.md')) { refs.copilotInstructions++; } else if (normalizedPath.endsWith('/agents.md') || normalizedPath.match(/\/agents\.md$/i)) { refs.agentsMd++; + } else if (normalizedPath.endsWith('.instructions.md') || + normalizedPath.includes('.instructions.md')) { + // Other instruction files (e.g., github-actions.instructions.md) are auto-attached + // Track as copilotInstructions since they're part of the instructions system + refs.copilotInstructions++; } else { // For other files, increment the general file counter // This makes actual file attachments show up in context ref counts @@ -1999,6 +2011,15 @@ class CopilotTokenTracker implements vscode.Disposable { refs.byKind[kind] = (refs.byKind[kind] || 0) + 1; } + // Handle symbol references (e.g., #sym:functionName) + // These appear as kind="generic" with name starting with "sym:" + if (kind === 'generic' && typeof variable.name === 'string' && variable.name.startsWith('sym:')) { + refs.symbol++; + // Track symbol by name for display + const symbolKey = `#${variable.name}`; + refs.byPath[symbolKey] = (refs.byPath[symbolKey] || 0) + 1; + } + // Process promptFile variables that contain file references if (kind === 'promptFile' && variable.value) { const value = variable.value; @@ -2610,6 +2631,8 @@ class CopilotTokenTracker implements vscode.Disposable { timestamps.push(new Date(ts).getTime()); } + // Analyze all context references from this request + this.analyzeRequestContext(request, details.contextReferences); // Analyze context references if (request.message?.text) { this.analyzeContextReferences(request.message.text, details.contextReferences); @@ -2740,17 +2763,9 @@ class CopilotTokenTracker implements vscode.Disposable { const contextRefs = this.createEmptyContextRefs(); const userMessage = request.message?.text || ''; - this.analyzeContextReferences(userMessage, contextRefs); - - // Analyze contentReferences from request - if (request.contentReferences && Array.isArray(request.contentReferences)) { - this.analyzeContentReferences(request.contentReferences, contextRefs); - } - // Analyze variableData from request - if (request.variableData) { - this.analyzeVariableData(request.variableData, contextRefs); - } + // Analyze all context references from this request + this.analyzeRequestContext(request, contextRefs); // Get model from request or fall back to session model const requestModel = request.modelId || @@ -2874,13 +2889,7 @@ class CopilotTokenTracker implements vscode.Disposable { // Analyze context references const contextRefs = this.createEmptyContextRefs(); - this.analyzeContextReferences(userMessage, contextRefs); - if (request.variableData) { - const varDataStr = JSON.stringify(request.variableData).toLowerCase(); - if (varDataStr.includes('workspace')) { contextRefs.workspace++; } - if (varDataStr.includes('terminal')) { contextRefs.terminal++; } - if (varDataStr.includes('vscode')) { contextRefs.vscode++; } - } + this.analyzeRequestContext(request, contextRefs); // Extract model const model = this.getModelFromRequest(request); diff --git a/src/webview/diagnostics/main.ts b/src/webview/diagnostics/main.ts index 3b842b0..a60a478 100644 --- a/src/webview/diagnostics/main.ts +++ b/src/webview/diagnostics/main.ts @@ -9,20 +9,7 @@ const LOADING_MESSAGE = `⏳ Loading diagnostic data... This may take a few moments depending on the number of session files. The view will automatically update when data is ready.`; -type ContextReferenceUsage = { - file: number; - selection: number; - implicitSelection: number; - symbol: number; - codebase: number; - workspace: number; - terminal: number; - vscode: number; - byKind: { [kind: string]: number }; - copilotInstructions: number; - agentsMd: number; - byPath: { [path: string]: number }; -}; +import { ContextReferenceUsage, getTotalContextRefs, getContextRefsSummary } from '../shared/contextRefUtils'; type SessionFileDetails = { file: string; @@ -92,6 +79,7 @@ const initialData = window.__INITIAL_DIAGNOSTICS__; let currentSortColumn: 'lastInteraction' = 'lastInteraction'; let currentSortDirection: 'asc' | 'desc' = 'desc'; let currentEditorFilter: string | null = null; // null = show all +let currentContextRefFilter: keyof ContextReferenceUsage | null = null; // null = show all function escapeHtml(text: string): string { return text @@ -150,26 +138,6 @@ function sanitizeNumber(value: number | undefined | null): string { return value.toString(); } -function getTotalContextRefs(refs: ContextReferenceUsage): number { - return refs.file + refs.selection + refs.implicitSelection + refs.symbol + refs.codebase + - refs.workspace + refs.terminal + refs.vscode + refs.copilotInstructions + refs.agentsMd; -} - -function getContextRefsSummary(refs: ContextReferenceUsage): string { - const parts: string[] = []; - if (refs.file > 0) { parts.push(`#file: ${refs.file}`); } - if (refs.selection > 0) { parts.push(`#sel: ${refs.selection}`); } - if (refs.implicitSelection > 0) { parts.push(`impl: ${refs.implicitSelection}`); } - if (refs.symbol > 0) { parts.push(`#sym: ${refs.symbol}`); } - if (refs.codebase > 0) { parts.push(`#cb: ${refs.codebase}`); } - if (refs.workspace > 0) { parts.push(`@ws: ${refs.workspace}`); } - if (refs.terminal > 0) { parts.push(`@term: ${refs.terminal}`); } - if (refs.vscode > 0) { parts.push(`@vsc: ${refs.vscode}`); } - if (refs.copilotInstructions > 0) { parts.push(`📋 inst: ${refs.copilotInstructions}`); } - if (refs.agentsMd > 0) { parts.push(`🤖 ag: ${refs.agentsMd}`); } - return parts.length > 0 ? parts.join(', ') : 'None'; -} - function getFileName(filePath: string): string { const parts = filePath.split(/[/\\]/); return parts[parts.length - 1]; @@ -293,14 +261,39 @@ function renderSessionTable(detailedFiles: SessionFileDetails[], isLoading: bool const editors = Object.keys(editorStats).sort(); // Apply editor filter - const filteredFiles = currentEditorFilter + let filteredFiles = currentEditorFilter ? detailedFiles.filter(sf => sf.editorSource === currentEditorFilter) : detailedFiles; + // Apply context ref filter + if (currentContextRefFilter) { + filteredFiles = filteredFiles.filter(sf => { + const refType = currentContextRefFilter!; // Assert non-null since we're inside the if block + const value = sf.contextReferences[refType]; + return typeof value === 'number' && value > 0; + }); + } + // Summary stats for filtered files const totalInteractions = filteredFiles.reduce((sum, sf) => sum + Number(sf.interactions || 0), 0); const totalContextRefs = filteredFiles.reduce((sum, sf) => sum + getTotalContextRefs(sf.contextReferences), 0); + // Aggregate context ref breakdown + const aggContextRefs = filteredFiles.reduce((agg, sf) => { + const r = sf.contextReferences; + agg.file += r.file; + agg.symbol += r.symbol; + agg.selection += r.selection; + agg.implicitSelection += r.implicitSelection; + agg.codebase += r.codebase; + agg.workspace += r.workspace; + agg.terminal += r.terminal; + agg.vscode += r.vscode; + agg.copilotInstructions += r.copilotInstructions; + agg.agentsMd += r.agentsMd; + return agg; + }, { file: 0, symbol: 0, selection: 0, implicitSelection: 0, codebase: 0, workspace: 0, terminal: 0, vscode: 0, copilotInstructions: 0, agentsMd: 0 }); + // Sort filtered files const sortedFiles = sortSessionFiles(filteredFiles); @@ -337,6 +330,16 @@ function renderSessionTable(detailedFiles: SessionFileDetails[], isLoading: bool
🔗 Context References
${safeText(totalContextRefs)}
+
+ ${totalContextRefs === 0 ? 'None' : ''} + ${aggContextRefs.file > 0 ? `
#file ${aggContextRefs.file}
` : ''} + ${aggContextRefs.symbol > 0 ? `
#sym ${aggContextRefs.symbol}
` : ''} + ${aggContextRefs.implicitSelection > 0 ? `
implicit ${aggContextRefs.implicitSelection}
` : ''} + ${aggContextRefs.copilotInstructions > 0 ? `
📋 instructions ${aggContextRefs.copilotInstructions}
` : ''} + ${aggContextRefs.agentsMd > 0 ? `
🤖 agents ${aggContextRefs.agentsMd}
` : ''} + ${aggContextRefs.workspace > 0 ? `
@workspace ${aggContextRefs.workspace}
` : ''} + ${aggContextRefs.vscode > 0 ? `
@vscode ${aggContextRefs.vscode}
` : ''} +
📅 Time Range
@@ -742,6 +745,23 @@ function renderLayout(data: DiagnosticsData): void { } .summary-label { font-size: 11px; color: #b3b3b3; margin-bottom: 4px; } .summary-value { font-size: 18px; font-weight: 600; color: #fff; } + .summary-sub { font-size: 10px; color: #94a3b8; text-align: left; margin-top: 6px; } + .context-ref-filter { + cursor: pointer; + padding: 2px 6px; + border-radius: 3px; + margin: 2px 0; + transition: all 0.2s; + } + .context-ref-filter:hover { + background: rgba(79, 195, 247, 0.2); + color: #4FC3F7; + } + .context-ref-filter.active { + background: rgba(79, 195, 247, 0.3); + color: #4FC3F7; + font-weight: 600; + } /* Table styles */ .table-container { @@ -1269,6 +1289,25 @@ function setupStorageLinkHandlers(): void { }); }); } + + // Wire up context ref filter handlers + function setupContextRefFilterHandlers(): void { + document.querySelectorAll('.context-ref-filter').forEach(filter => { + filter.addEventListener('click', () => { + const refType = (filter as HTMLElement).getAttribute('data-ref-type') as keyof ContextReferenceUsage | null; + + // Toggle: if clicking the same filter, clear it + if (currentContextRefFilter === refType) { + currentContextRefFilter = null; + } else { + currentContextRefFilter = refType; + } + + // Re-render table + reRenderTable(); + }); + }); + } // Re-render the session table with current filter/sort state function reRenderTable(): void { @@ -1278,6 +1317,7 @@ function setupStorageLinkHandlers(): void { if (!isLoading) { setupSortHandlers(); setupEditorFilterHandlers(); + setupContextRefFilterHandlers(); setupFileLinks(); } } @@ -1398,6 +1438,7 @@ function setupStorageLinkHandlers(): void { setupSortHandlers(); setupEditorFilterHandlers(); + setupContextRefFilterHandlers(); setupFileLinks(); setupStorageLinkHandlers(); diff --git a/src/webview/logviewer/main.ts b/src/webview/logviewer/main.ts index c9bffed..044b680 100644 --- a/src/webview/logviewer/main.ts +++ b/src/webview/logviewer/main.ts @@ -1,18 +1,5 @@ // Log Viewer webview - displays session file details and chat turns -type ContextReferenceUsage = { - file: number; - selection: number; - implicitSelection: number; - symbol: number; - codebase: number; - workspace: number; - terminal: number; - vscode: number; - byKind: { [kind: string]: number }; - copilotInstructions: number; - agentsMd: number; - byPath: { [path: string]: number }; -}; +import { ContextReferenceUsage, getTotalContextRefs, getImplicitContextRefs, getExplicitContextRefs, getContextRefsSummary } from '../shared/contextRefUtils'; type ChatTurn = { turnNumber: number; @@ -101,26 +88,6 @@ function formatFileSize(bytes: number): string { return `${(bytes / (1024 * 1024)).toFixed(2)} MB`; } -function getTotalContextRefs(refs: ContextReferenceUsage): number { - return refs.file + refs.selection + refs.implicitSelection + refs.symbol + refs.codebase + - refs.workspace + refs.terminal + refs.vscode + refs.copilotInstructions + refs.agentsMd; -} - -function getContextRefsSummary(refs: ContextReferenceUsage): string { - const parts: string[] = []; - if (refs.file > 0) { parts.push(`#file: ${refs.file}`); } - if (refs.selection > 0) { parts.push(`#selection: ${refs.selection}`); } - if (refs.implicitSelection > 0) { parts.push(`implicit: ${refs.implicitSelection}`); } - if (refs.symbol > 0) { parts.push(`#symbol: ${refs.symbol}`); } - if (refs.codebase > 0) { parts.push(`#codebase: ${refs.codebase}`); } - if (refs.workspace > 0) { parts.push(`@workspace: ${refs.workspace}`); } - if (refs.terminal > 0) { parts.push(`@terminal: ${refs.terminal}`); } - if (refs.vscode > 0) { parts.push(`@vscode: ${refs.vscode}`); } - if (refs.copilotInstructions > 0) { parts.push(`📋 instructions: ${refs.copilotInstructions}`); } - if (refs.agentsMd > 0) { parts.push(`🤖 agents: ${refs.agentsMd}`); } - return parts.length > 0 ? parts.join(', ') : 'None'; -} - function getContextRefBadges(refs: ContextReferenceUsage): string { const badges: string[] = []; if (refs.selection > 0) { badges.push(`#selection: ${refs.selection}`); } @@ -374,6 +341,8 @@ function renderLayout(data: SessionLogData): void { const usageTopMcpTools = usage ? getTopEntries(usage.mcpTools.byTool, 3) : []; const usageContextRefs = usage?.contextReferences || data.contextReferences; const usageContextTotal = getTotalContextRefs(usageContextRefs); + const usageContextImplicit = getImplicitContextRefs(usageContextRefs); + const usageContextExplicit = getExplicitContextRefs(usageContextRefs); const formatTopList = (entries: { key: string; value: number }[], mapper?: (k: string) => string) => { if (!entries.length) { return 'None'; } @@ -1065,13 +1034,7 @@ function renderLayout(data: SessionLogData): void {
🔗 Context Refs
${usageContextTotal}
- ${usageContextTotal === 0 ? 'None' : ''} - ${usageContextRefs.file > 0 ? `
#file ${usageContextRefs.file}
` : ''} - ${usageContextRefs.implicitSelection > 0 ? `
implicit ${usageContextRefs.implicitSelection}
` : ''} - ${usageContextRefs.copilotInstructions > 0 ? `
📋 instructions ${usageContextRefs.copilotInstructions}
` : ''} - ${usageContextRefs.agentsMd > 0 ? `
🤖 agents ${usageContextRefs.agentsMd}
` : ''} - ${usageContextRefs.workspace > 0 ? `
@workspace ${usageContextRefs.workspace}
` : ''} - ${usageContextRefs.vscode > 0 ? `
@vscode ${usageContextRefs.vscode}
` : ''} + ${usageContextTotal === 0 ? 'None' : `implicit ${usageContextImplicit}, explicit ${usageContextExplicit}`}
diff --git a/src/webview/shared/contextRefUtils.ts b/src/webview/shared/contextRefUtils.ts new file mode 100644 index 0000000..89d18bb --- /dev/null +++ b/src/webview/shared/contextRefUtils.ts @@ -0,0 +1,81 @@ +/** + * Shared utilities for working with context references across webviews + */ + +export type ContextReferenceUsage = { + file: number; + selection: number; + implicitSelection: number; + symbol: number; + codebase: number; + workspace: number; + terminal: number; + vscode: number; + byKind: { [kind: string]: number }; + copilotInstructions: number; + agentsMd: number; + byPath: { [path: string]: number }; +}; + +/** + * Calculate the total number of context references. + * This is the single source of truth for what constitutes a context reference. + */ +export function getTotalContextRefs(refs: ContextReferenceUsage): number { + return refs.file + refs.selection + refs.implicitSelection + refs.symbol + refs.codebase + + refs.workspace + refs.terminal + refs.vscode + refs.copilotInstructions + refs.agentsMd; +} + +/** + * Calculate the count of implicit (auto-attached) context references. + * Implicit refs are not user-initiated: copilotInstructions, agentsMd, implicitSelection + */ +export function getImplicitContextRefs(refs: ContextReferenceUsage): number { + return refs.copilotInstructions + refs.agentsMd + refs.implicitSelection; +} + +/** + * Calculate the count of explicit (user-initiated) context references. + * Explicit refs are user-initiated: #file, #selection, #symbol, #codebase, @workspace, @terminal, @vscode + */ +export function getExplicitContextRefs(refs: ContextReferenceUsage): number { + return refs.file + refs.selection + refs.symbol + refs.codebase + + refs.workspace + refs.terminal + refs.vscode; +} + +/** + * Generate a summary string of context references. + * @param refs - The context reference usage counts + * @param abbreviated - If true, use short labels (e.g., '#sel' instead of '#selection') + */ +export function getContextRefsSummary(refs: ContextReferenceUsage, abbreviated = false): string { + const parts: string[] = []; + + if (abbreviated) { + // Abbreviated labels for compact display (used in diagnostics) + if (refs.file > 0) { parts.push(`#file: ${refs.file}`); } + if (refs.selection > 0) { parts.push(`#sel: ${refs.selection}`); } + if (refs.implicitSelection > 0) { parts.push(`impl: ${refs.implicitSelection}`); } + if (refs.symbol > 0) { parts.push(`#sym: ${refs.symbol}`); } + if (refs.codebase > 0) { parts.push(`#cb: ${refs.codebase}`); } + if (refs.workspace > 0) { parts.push(`@ws: ${refs.workspace}`); } + if (refs.terminal > 0) { parts.push(`@term: ${refs.terminal}`); } + if (refs.vscode > 0) { parts.push(`@vsc: ${refs.vscode}`); } + if (refs.copilotInstructions > 0) { parts.push(`📋 inst: ${refs.copilotInstructions}`); } + if (refs.agentsMd > 0) { parts.push(`🤖 ag: ${refs.agentsMd}`); } + } else { + // Full labels for detailed display (used in logviewer) + if (refs.file > 0) { parts.push(`#file: ${refs.file}`); } + if (refs.selection > 0) { parts.push(`#selection: ${refs.selection}`); } + if (refs.implicitSelection > 0) { parts.push(`implicit: ${refs.implicitSelection}`); } + if (refs.symbol > 0) { parts.push(`#symbol: ${refs.symbol}`); } + if (refs.codebase > 0) { parts.push(`#codebase: ${refs.codebase}`); } + if (refs.workspace > 0) { parts.push(`@workspace: ${refs.workspace}`); } + if (refs.terminal > 0) { parts.push(`@terminal: ${refs.terminal}`); } + if (refs.vscode > 0) { parts.push(`@vscode: ${refs.vscode}`); } + if (refs.copilotInstructions > 0) { parts.push(`📋 instructions: ${refs.copilotInstructions}`); } + if (refs.agentsMd > 0) { parts.push(`🤖 agents: ${refs.agentsMd}`); } + } + + return parts.length > 0 ? parts.join(', ') : 'None'; +} diff --git a/src/webview/usage/main.ts b/src/webview/usage/main.ts index e1c2d8d..e7ddfdc 100644 --- a/src/webview/usage/main.ts +++ b/src/webview/usage/main.ts @@ -1,22 +1,9 @@ // Usage Analysis webview import { el } from '../shared/domUtils'; import { buttonHtml } from '../shared/buttonConfig'; +import { ContextReferenceUsage, getTotalContextRefs } from '../shared/contextRefUtils'; type ModeUsage = { ask: number; edit: number; agent: number }; -type ContextReferenceUsage = { - file: number; - selection: number; - implicitSelection: number; - symbol: number; - codebase: number; - workspace: number; - terminal: number; - vscode: number; - byKind: { [kind: string]: number }; - copilotInstructions: number; - agentsMd: number; - byPath: { [path: string]: number }; -}; type ToolCallUsage = { total: number; byTool: { [key: string]: number } }; type McpToolUsage = { total: number; byServer: { [key: string]: number }; byTool: { [key: string]: number } }; @@ -71,16 +58,6 @@ function escapeHtml(text: string): string { .replace(/'/g, '''); } -function getTotalContextRefs(refs: ContextReferenceUsage): number { - const basicRefs = refs.file + refs.selection + refs.implicitSelection + refs.symbol + refs.codebase + - refs.workspace + refs.terminal + refs.vscode; - - // Add contentReferences counts - const contentRefs = refs.copilotInstructions + refs.agentsMd; - - return basicRefs + contentRefs; -} - import toolNames from '../../toolNames.json'; let TOOL_NAME_MAP: { [key: string]: string } | null = toolNames || null; From bd8169761e6776876e96613f5351919f1be511b0 Mon Sep 17 00:00:00 2001 From: Rob Bos Date: Sun, 8 Feb 2026 17:17:08 +0100 Subject: [PATCH 4/8] Update context ref details --- src/webview/logviewer/main.ts | 144 +++++++++++++++++++++++++++------- 1 file changed, 115 insertions(+), 29 deletions(-) diff --git a/src/webview/logviewer/main.ts b/src/webview/logviewer/main.ts index 044b680..f4c5551 100644 --- a/src/webview/logviewer/main.ts +++ b/src/webview/logviewer/main.ts @@ -102,48 +102,95 @@ function getContextRefBadges(refs: ContextReferenceUsage): string { } function renderContextReferencesDetailed(refs: ContextReferenceUsage): string { - const sections: string[] = []; + const rows: { category: string; name: string; count: number; type: 'implicit' | 'explicit' }[] = []; - // Show instruction file references - if (refs.copilotInstructions > 0 || refs.agentsMd > 0) { - const instrRefs: string[] = []; - if (refs.copilotInstructions > 0) { instrRefs.push(`📋 copilot-instructions: ${refs.copilotInstructions}`); } - if (refs.agentsMd > 0) { instrRefs.push(`🤖 agents.md: ${refs.agentsMd}`); } - sections.push(`
Instructions: ${instrRefs.join(', ')}
`); + // Implicit selections (implicit) + if (refs.implicitSelection > 0) { + rows.push({ category: '📝 Selection', name: 'editor selection', count: refs.implicitSelection, type: 'implicit' }); } - // Show file paths if any + // File paths and symbols from byPath if (refs.byPath && Object.keys(refs.byPath).length > 0) { - // Separate symbols from files - const symbols: [string, number][] = []; - const files: [string, number][] = []; - Object.entries(refs.byPath).forEach(([path, count]) => { if (path.startsWith('#sym:')) { - symbols.push([path.substring(5), count]); // Remove '#sym:' prefix + // Symbols are explicit user references + rows.push({ category: '🔣 Symbol', name: path.substring(5), count, type: 'explicit' }); } else { - files.push([path, count]); + // Check if this is an instruction file (implicit) or regular file (explicit) + const normalizedPath = path.replace(/\\/g, '/').toLowerCase(); + const isInstructionFile = normalizedPath.includes('copilot-instructions.md') || + normalizedPath.endsWith('.instructions.md') || + normalizedPath.endsWith('/agents.md'); + if (isInstructionFile) { + rows.push({ category: '📋 Instructions', name: getFileName(path), count, type: 'implicit' }); + } else { + // Regular file references are explicit + rows.push({ category: '📁 File', name: getFileName(path), count, type: 'explicit' }); + } } }); - - // Show files - if (files.length > 0) { - const pathList = files - .map(([path, count]) => `${getFileName(path)}: ${count}`) - .join(', '); - sections.push(`
Files: ${pathList}
`); + } + + // Instruction counters that aren't in byPath (fallback) + // Only show if we haven't already added instruction files from byPath + const hasInstructionFiles = rows.some(r => r.category === '📋 Instructions'); + if (!hasInstructionFiles) { + if (refs.copilotInstructions > 0) { + rows.push({ category: '📋 Instructions', name: 'copilot-instructions', count: refs.copilotInstructions, type: 'implicit' }); } - - // Show symbols - if (symbols.length > 0) { - const symbolList = symbols - .map(([symbolName, count]) => `${symbolName}: ${count}`) - .join(', '); - sections.push(`
Symbols: ${symbolList}
`); + if (refs.agentsMd > 0) { + rows.push({ category: '🤖 Agents', name: 'agents.md', count: refs.agentsMd, type: 'implicit' }); } } - return sections.length > 0 ? sections.join('') : '
No additional details
'; + // Explicit @ references + if (refs.workspace > 0) { + rows.push({ category: '🌐 Workspace', name: '@workspace', count: refs.workspace, type: 'explicit' }); + } + if (refs.terminal > 0) { + rows.push({ category: '💻 Terminal', name: '@terminal', count: refs.terminal, type: 'explicit' }); + } + if (refs.vscode > 0) { + rows.push({ category: '⚙️ VS Code', name: '@vscode', count: refs.vscode, type: 'explicit' }); + } + if (refs.codebase > 0) { + rows.push({ category: '📚 Codebase', name: '#codebase', count: refs.codebase, type: 'explicit' }); + } + if (refs.selection > 0) { + rows.push({ category: '✂️ Selection', name: '#selection', count: refs.selection, type: 'explicit' }); + } + + if (rows.length === 0) { + return '
No context references
'; + } + + // Build table + const tableRows = rows.map(row => { + const typeClass = row.type === 'implicit' ? 'context-type-implicit' : 'context-type-explicit'; + const typeLabel = row.type === 'implicit' ? '🔒 implicit' : '👤 explicit'; + return ` + ${row.category} + ${escapeHtml(row.name)} + ${row.count} + ${typeLabel} + `; + }).join(''); + + return ` + + + + + + + + + + + ${tableRows} + +
CategoryReferenceCountType
+ `; } function getTopEntries(map: { [key: string]: number } = {}, limit = 3): { key: string; value: number }[] { @@ -869,6 +916,45 @@ function renderLayout(data: SessionLogData): void { color: #94a3b8; font-weight: 600; } + .context-refs-table { + width: 100%; + border-collapse: collapse; + font-size: 13px; + margin-top: 8px; + } + .context-refs-table th, + .context-refs-table td { + padding: 8px 12px; + text-align: left; + border-bottom: 1px solid #334155; + } + .context-refs-table th { + color: #94a3b8; + font-weight: 600; + font-size: 11px; + text-transform: uppercase; + letter-spacing: 0.5px; + background: #1e293b; + } + .context-refs-table td { + color: #cbd5e1; + } + .context-refs-table tbody tr:hover { + background: #1e293b; + } + .context-refs-table .count-cell { + text-align: center; + font-weight: 600; + color: #60a5fa; + } + .context-type-implicit { + color: #94a3b8; + font-size: 12px; + } + .context-type-explicit { + color: #4ade80; + font-size: 12px; + } .context-path { padding-left: 10px; color: #9ca3af; From 6accef89d46e12ea689022847e25e2e7db917949 Mon Sep 17 00:00:00 2001 From: Rob Bos Date: Sun, 8 Feb 2026 17:26:14 +0100 Subject: [PATCH 5/8] CSS fixes --- src/webview/logviewer/main.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/webview/logviewer/main.ts b/src/webview/logviewer/main.ts index f4c5551..0e305ba 100644 --- a/src/webview/logviewer/main.ts +++ b/src/webview/logviewer/main.ts @@ -989,11 +989,10 @@ function renderLayout(data: SessionLogData): void { ::-webkit-scrollbar-thumb:hover { background: #4a4a54; } - } /* Context References */ .turn-context-refs { - margin-bottom: 14px; + margin: 0 16px 14px 16px; background: linear-gradient(135deg, #2a2535 0%, #252530 100%); border: 1px solid #4a4a5a; border-radius: 8px; From d17e93d0e5d320603f3a3c367ebe3343ca5d2e04 Mon Sep 17 00:00:00 2001 From: Rob Bos Date: Sun, 8 Feb 2026 18:17:22 +0100 Subject: [PATCH 6/8] Add stylelint configuration and new CSS styles for webview components - Created a .stylelintrc.json file to configure stylelint with custom rules. - Added TypeScript declaration for CSS modules in src/types/css.d.ts. - Implemented new styles for various webview components including charts, details, diagnostics, log viewer, and usage. - Ensured consistent styling across components with a dark theme and responsive design. --- .stylelintrc.json | 10 + esbuild.js | 1 + package-lock.json | 862 ++++++++++++++++++++++++++++- package.json | 3 + src/types/css.d.ts | 4 + src/webview/chart/main.ts | 29 +- src/webview/chart/styles.css | 162 ++++++ src/webview/details/main.ts | 33 +- src/webview/details/styles.css | 154 ++++++ src/webview/diagnostics/main.ts | 272 +-------- src/webview/diagnostics/styles.css | 423 ++++++++++++++ src/webview/logviewer/main.ts | 676 +--------------------- src/webview/logviewer/styles.css | 746 +++++++++++++++++++++++++ src/webview/usage/main.ts | 123 +--- src/webview/usage/styles.css | 235 ++++++++ 15 files changed, 2602 insertions(+), 1131 deletions(-) create mode 100644 .stylelintrc.json create mode 100644 src/types/css.d.ts create mode 100644 src/webview/chart/styles.css create mode 100644 src/webview/details/styles.css create mode 100644 src/webview/diagnostics/styles.css create mode 100644 src/webview/logviewer/styles.css create mode 100644 src/webview/usage/styles.css diff --git a/.stylelintrc.json b/.stylelintrc.json new file mode 100644 index 0000000..1bbdb38 --- /dev/null +++ b/.stylelintrc.json @@ -0,0 +1,10 @@ +{ + "extends": ["stylelint-config-standard"], + "rules": { + "selector-class-pattern": null, + "custom-property-pattern": null, + "no-descending-specificity": null, + "color-function-notation": null, + "alpha-value-notation": null + } +} diff --git a/esbuild.js b/esbuild.js index 21d0b03..2b42477 100644 --- a/esbuild.js +++ b/esbuild.js @@ -59,6 +59,7 @@ async function main() { external: ['vscode'], logLevel: 'silent', plugins: [esbuildProblemMatcherPlugin], + loader: { '.css': 'text' }, }); if (watch) { diff --git a/package-lock.json b/package-lock.json index 149eaf5..481bb71 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32,6 +32,8 @@ "esbuild": "^0.27.2", "eslint": "^9.39.2", "npm-run-all": "^4.1.5", + "stylelint": "^17.1.1", + "stylelint-config-standard": "^40.0.0", "typescript": "^5.9.3" }, "engines": { @@ -479,6 +481,67 @@ "node": ">=18" } }, + "node_modules/@cacheable/memory": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@cacheable/memory/-/memory-2.0.7.tgz", + "integrity": "sha512-RbxnxAMf89Tp1dLhXMS7ceft/PGsDl1Ip7T20z5nZ+pwIAsQ1p2izPjVG69oCLv/jfQ7HDPHTWK0c9rcAWXN3A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cacheable/utils": "^2.3.3", + "@keyv/bigmap": "^1.3.0", + "hookified": "^1.14.0", + "keyv": "^5.5.5" + } + }, + "node_modules/@cacheable/memory/node_modules/@keyv/bigmap": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@keyv/bigmap/-/bigmap-1.3.1.tgz", + "integrity": "sha512-WbzE9sdmQtKy8vrNPa9BRnwZh5UF4s1KTmSK0KUVLo3eff5BlQNNWDnFOouNpKfPKDnms9xynJjsMYjMaT/aFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "hashery": "^1.4.0", + "hookified": "^1.15.0" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "keyv": "^5.6.0" + } + }, + "node_modules/@cacheable/memory/node_modules/keyv": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.6.0.tgz", + "integrity": "sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@keyv/serialize": "^1.1.1" + } + }, + "node_modules/@cacheable/utils": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/@cacheable/utils/-/utils-2.3.4.tgz", + "integrity": "sha512-knwKUJEYgIfwShABS1BX6JyJJTglAFcEU7EXqzTdiGCXur4voqkiJkdgZIQtWNFhynzDWERcTYv/sETMu3uJWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hashery": "^1.3.0", + "keyv": "^5.6.0" + } + }, + "node_modules/@cacheable/utils/node_modules/keyv": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.6.0.tgz", + "integrity": "sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@keyv/serialize": "^1.1.1" + } + }, "node_modules/@csstools/color-helpers": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz", @@ -571,9 +634,9 @@ } }, "node_modules/@csstools/css-syntax-patches-for-csstree": { - "version": "1.0.22", - "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.0.22.tgz", - "integrity": "sha512-qBcx6zYlhleiFfdtzkRgwNC7VVoAwfK76Vmsw5t+PbvtdknO9StgRk7ROvq9so1iqbdW4uLIDAsXRsTfUrIoOw==", + "version": "1.0.26", + "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.0.26.tgz", + "integrity": "sha512-6boXK0KkzT5u5xOgF6TKB+CLq9SOpEGmkZw0g5n9/7yg85wab3UzSxB8TxhLJ31L4SGJ6BCFRw/iftTha1CJXA==", "funding": [ { "type": "github", @@ -584,10 +647,7 @@ "url": "https://opencollective.com/csstools" } ], - "license": "MIT-0", - "engines": { - "node": ">=18" - } + "license": "MIT-0" }, "node_modules/@csstools/css-tokenizer": { "version": "3.0.4", @@ -608,6 +668,52 @@ "node": ">=18" } }, + "node_modules/@csstools/selector-resolve-nested": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/selector-resolve-nested/-/selector-resolve-nested-4.0.0.tgz", + "integrity": "sha512-9vAPxmp+Dx3wQBIUwc1v7Mdisw1kbbaGqXUM8QLTgWg7SoPGYtXBsMXvsFs/0Bn5yoFhcktzxNZGNaUt0VjgjA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "postcss-selector-parser": "^7.1.1" + } + }, + "node_modules/@csstools/selector-specificity": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-6.0.0.tgz", + "integrity": "sha512-4sSgl78OtOXEX/2d++8A83zHNTgwCJMaR24FvsYL7Uf/VS8HZk9PTwR51elTbGqMuwH3szLvvOXEaVnqn0Z3zA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "postcss-selector-parser": "^7.1.1" + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.27.2", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", @@ -1387,6 +1493,13 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@keyv/serialize": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@keyv/serialize/-/serialize-1.1.1.tgz", + "integrity": "sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA==", + "dev": true, + "license": "MIT" + }, "node_modules/@kurkle/color": { "version": "0.3.4", "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz", @@ -2969,6 +3082,30 @@ } } }, + "node_modules/cacheable": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/cacheable/-/cacheable-2.3.2.tgz", + "integrity": "sha512-w+ZuRNmex9c1TR9RcsxbfTKCjSL0rh1WA5SABbrWprIHeNBdmyQLSYonlDy9gpD+63XT8DgZ/wNh1Smvc9WnJA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cacheable/memory": "^2.0.7", + "@cacheable/utils": "^2.3.3", + "hookified": "^1.15.0", + "keyv": "^5.5.5", + "qified": "^0.6.0" + } + }, + "node_modules/cacheable/node_modules/keyv": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.6.0.tgz", + "integrity": "sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@keyv/serialize": "^1.1.1" + } + }, "node_modules/call-bind": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", @@ -3297,6 +3434,13 @@ "dev": true, "license": "MIT" }, + "node_modules/colord": { + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", + "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==", + "dev": true, + "license": "MIT" + }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -3330,6 +3474,52 @@ "dev": true, "license": "MIT" }, + "node_modules/cosmiconfig": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", + "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/cosmiconfig/node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -3345,6 +3535,16 @@ "node": ">= 8" } }, + "node_modules/css-functions-list": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/css-functions-list/-/css-functions-list-3.2.3.tgz", + "integrity": "sha512-IQOkD3hbR5KrN93MtcYuad6YPuTSUhntLHDuLEbFWE+ff2/XSZNdZG+LcbbIW5AXKg/WFIfYItIzVoHngHXZzA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12 || >=16" + } + }, "node_modules/css-select": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", @@ -3388,6 +3588,19 @@ "url": "https://github.com/sponsors/fb55" } }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/cssstyle": { "version": "5.3.5", "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-5.3.5.tgz", @@ -3807,6 +4020,16 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/environment": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", @@ -4368,6 +4591,16 @@ "fxparser": "src/cli/cli.js" } }, + "node_modules/fastest-levenshtein": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", + "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.9.1" + } + }, "node_modules/fastq": { "version": "1.19.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", @@ -4711,6 +4944,47 @@ "node": ">= 6" } }, + "node_modules/global-modules": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz", + "integrity": "sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "global-prefix": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/global-prefix": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz", + "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ini": "^1.3.5", + "kind-of": "^6.0.2", + "which": "^1.3.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/global-prefix/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, "node_modules/globals": { "version": "14.0.0", "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", @@ -4773,6 +5047,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/globjoin": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/globjoin/-/globjoin-0.1.4.tgz", + "integrity": "sha512-xYfnw62CKG8nLkZBfWbhWwDw02CHty86jfPcc2cr3ZfeuK9ysoVPPEUxf21bAD/rWAgk52SuBrLJlefNy8mvFg==", + "dev": true, + "license": "MIT" + }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -4874,6 +5155,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/hashery": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/hashery/-/hashery-1.4.0.tgz", + "integrity": "sha512-Wn2i1In6XFxl8Az55kkgnFRiAlIAushzh26PTjL2AKtQcEfXrcLa7Hn5QOWGZEf3LU057P9TwwZjFyxfS1VuvQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "hookified": "^1.14.0" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", @@ -4897,6 +5191,13 @@ "he": "bin/he" } }, + "node_modules/hookified": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/hookified/-/hookified-1.15.1.tgz", + "integrity": "sha512-MvG/clsADq1GPM2KGo2nyfaWVyn9naPiXrqIe4jYjXNZQt238kWyOGrsyc/DmRAQ+Re6yeo6yX/yoNCG5KAEVg==", + "dev": true, + "license": "MIT" + }, "node_modules/hosted-git-info": { "version": "2.8.9", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", @@ -4929,6 +5230,19 @@ "dev": true, "license": "MIT" }, + "node_modules/html-tags": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-5.1.0.tgz", + "integrity": "sha512-n6l5uca7/y5joxZ3LUePhzmBFUJ+U2YWzhMa8XUTecSeSlQiZdF5XAd/Q3/WUl0VsXgUwWi8I7CNIwdI5WN1SQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/htmlparser2": { "version": "10.0.0", "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.0.0.tgz", @@ -5057,6 +5371,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/import-meta-resolve": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.2.0.tgz", + "integrity": "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", @@ -5091,8 +5416,7 @@ "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", "dev": true, - "license": "ISC", - "optional": true + "license": "ISC" }, "node_modules/internal-slot": { "version": "1.1.0", @@ -5449,6 +5773,16 @@ "node": ">=8" } }, + "node_modules/is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-potential-custom-element-name": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", @@ -5822,6 +6156,13 @@ "dev": true, "license": "MIT" }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "license": "MIT" + }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -5943,6 +6284,23 @@ "json-buffer": "3.0.1" } }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/known-css-properties": { + "version": "0.37.0", + "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.37.0.tgz", + "integrity": "sha512-JCDrsP4Z1Sb9JwG0aJ8Eo2r7k4Ou5MwmThS/6lcIe1ICyb7UBJKGRIUUdqc2ASdE/42lgz6zFUnzAIhtXnBVrQ==", + "dev": true, + "license": "MIT" + }, "node_modules/leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", @@ -5977,6 +6335,13 @@ "immediate": "~3.0.5" } }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, "node_modules/load-json-file": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", @@ -6114,6 +6479,17 @@ "node": ">= 0.4" } }, + "node_modules/mathml-tag-names": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mathml-tag-names/-/mathml-tag-names-4.0.0.tgz", + "integrity": "sha512-aa6AU2Pcx0VP/XWnh8IGL0SYSgQHDT6Ucror2j2mXeFAlN3ahaNs8EZtG1YiticMkSLj3Gt6VPFfZogt7G5iFQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/mdn-data": { "version": "2.12.2", "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.12.2.tgz", @@ -6129,6 +6505,19 @@ "node": ">= 0.10.0" } }, + "node_modules/meow": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-14.0.0.tgz", + "integrity": "sha512-JhC3R1f6dbspVtmF3vKjAWz1EVIvwFrGGPLSdU6rK79xBwHWTuHoLnRX/t1/zHS1Ch1Y2UtIrih7DAHuH9JFJA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -6355,10 +6744,29 @@ "dev": true, "license": "ISC" }, - "node_modules/napi-build-utils": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", - "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/napi-build-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", + "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", "dev": true, "license": "MIT", "optional": true @@ -7172,6 +7580,83 @@ "node": ">= 0.4" } }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-safe-parser": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-7.0.1.tgz", + "integrity": "sha512-0AioNCJZ2DPYz5ABT6bddIqlhgwhpHZ/l65YAYo0BCIn0xiDpsnTHz0gnoTGk0OXZW0JRs+cDwL8u/teRdz+8A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss-safe-parser" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "engines": { + "node": ">=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-selector-parser": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", + "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true, + "license": "MIT" + }, "node_modules/prebuild-install": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", @@ -7247,6 +7732,19 @@ "node": ">=6" } }, + "node_modules/qified": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/qified/-/qified-0.6.0.tgz", + "integrity": "sha512-tsSGN1x3h569ZSU1u6diwhltLyfUWDp3YbFHedapTmpBl0B3P6U3+Qptg7xu+v+1io1EwhdPyyRHYbEw0KN2FA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hookified": "^1.14.0" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/qs": { "version": "6.14.1", "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", @@ -8348,6 +8846,324 @@ "boundary": "^2.0.0" } }, + "node_modules/stylelint": { + "version": "17.1.1", + "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-17.1.1.tgz", + "integrity": "sha512-SBHVcLEcRF1M9OkD3oT0hT2PayDNLw2hd+aovmzfNQ2ys4Xd3oS9ZNizILWqhQvW802AqKN/vUTMwJqQYMBlWw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/stylelint" + }, + { + "type": "github", + "url": "https://github.com/sponsors/stylelint" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-syntax-patches-for-csstree": "^1.0.25", + "@csstools/css-tokenizer": "^4.0.0", + "@csstools/media-query-list-parser": "^5.0.0", + "@csstools/selector-resolve-nested": "^4.0.0", + "@csstools/selector-specificity": "^6.0.0", + "balanced-match": "^3.0.1", + "colord": "^2.9.3", + "cosmiconfig": "^9.0.0", + "css-functions-list": "^3.2.3", + "css-tree": "^3.1.0", + "debug": "^4.4.3", + "fast-glob": "^3.3.3", + "fastest-levenshtein": "^1.0.16", + "file-entry-cache": "^11.1.2", + "global-modules": "^2.0.0", + "globby": "^16.1.0", + "globjoin": "^0.1.4", + "html-tags": "^5.1.0", + "ignore": "^7.0.5", + "import-meta-resolve": "^4.2.0", + "imurmurhash": "^0.1.4", + "is-plain-object": "^5.0.0", + "known-css-properties": "^0.37.0", + "mathml-tag-names": "^4.0.0", + "meow": "^14.0.0", + "micromatch": "^4.0.8", + "normalize-path": "^3.0.0", + "picocolors": "^1.1.1", + "postcss": "^8.5.6", + "postcss-safe-parser": "^7.0.1", + "postcss-selector-parser": "^7.1.1", + "postcss-value-parser": "^4.2.0", + "string-width": "^8.1.0", + "supports-hyperlinks": "^4.4.0", + "svg-tags": "^1.0.0", + "table": "^6.9.0", + "write-file-atomic": "^7.0.0" + }, + "bin": { + "stylelint": "bin/stylelint.mjs" + }, + "engines": { + "node": ">=20.19.0" + } + }, + "node_modules/stylelint-config-recommended": { + "version": "18.0.0", + "resolved": "https://registry.npmjs.org/stylelint-config-recommended/-/stylelint-config-recommended-18.0.0.tgz", + "integrity": "sha512-mxgT2XY6YZ3HWWe3Di8umG6aBmWmHTblTgu/f10rqFXnyWxjKWwNdjSWkgkwCtxIKnqjSJzvFmPT5yabVIRxZg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/stylelint" + }, + { + "type": "github", + "url": "https://github.com/sponsors/stylelint" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "stylelint": "^17.0.0" + } + }, + "node_modules/stylelint-config-standard": { + "version": "40.0.0", + "resolved": "https://registry.npmjs.org/stylelint-config-standard/-/stylelint-config-standard-40.0.0.tgz", + "integrity": "sha512-EznGJxOUhtWck2r6dJpbgAdPATIzvpLdK9+i5qPd4Lx70es66TkBPljSg4wN3Qnc6c4h2n+WbUrUynQ3fanjHw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/stylelint" + }, + { + "type": "github", + "url": "https://github.com/sponsors/stylelint" + } + ], + "license": "MIT", + "dependencies": { + "stylelint-config-recommended": "^18.0.0" + }, + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "stylelint": "^17.0.0" + } + }, + "node_modules/stylelint/node_modules/@csstools/css-parser-algorithms": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-4.0.0.tgz", + "integrity": "sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/stylelint/node_modules/@csstools/css-tokenizer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-4.0.0.tgz", + "integrity": "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + } + }, + "node_modules/stylelint/node_modules/@csstools/media-query-list-parser": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-5.0.0.tgz", + "integrity": "sha512-T9lXmZOfnam3eMERPsszjY5NK0jX8RmThmmm99FZ8b7z8yMaFZWKwLWGZuTwdO3ddRY5fy13GmmEYZXB4I98Eg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/stylelint/node_modules/@sindresorhus/merge-streams": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz", + "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/stylelint/node_modules/balanced-match": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-3.0.1.tgz", + "integrity": "sha512-vjtV3hiLqYDNRoiAv0zC4QaGAMPomEoq83PRmYIofPswwZurCeWR5LByXm7SyoL0Zh5+2z0+HC7jG8gSZJUh0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, + "node_modules/stylelint/node_modules/file-entry-cache": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-11.1.2.tgz", + "integrity": "sha512-N2WFfK12gmrK1c1GXOqiAJ1tc5YE+R53zvQ+t5P8S5XhnmKYVB5eZEiLNZKDSmoG8wqqbF9EXYBBW/nef19log==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^6.1.20" + } + }, + "node_modules/stylelint/node_modules/flat-cache": { + "version": "6.1.20", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-6.1.20.tgz", + "integrity": "sha512-AhHYqwvN62NVLp4lObVXGVluiABTHapoB57EyegZVmazN+hhGhLTn3uZbOofoTw4DSDvVCadzzyChXhOAvy8uQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "cacheable": "^2.3.2", + "flatted": "^3.3.3", + "hookified": "^1.15.0" + } + }, + "node_modules/stylelint/node_modules/globby": { + "version": "16.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-16.1.0.tgz", + "integrity": "sha512-+A4Hq7m7Ze592k9gZRy4gJ27DrXRNnC1vPjxTt1qQxEY8RxagBkBxivkCwg7FxSTG0iLLEMaUx13oOr0R2/qcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/merge-streams": "^4.0.0", + "fast-glob": "^3.3.3", + "ignore": "^7.0.5", + "is-path-inside": "^4.0.0", + "slash": "^5.1.0", + "unicorn-magic": "^0.4.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/stylelint/node_modules/has-flag": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-5.0.1.tgz", + "integrity": "sha512-CsNUt5x9LUdx6hnk/E2SZLsDyvfqANZSUq4+D3D8RzDJ2M+HDTIkF60ibS1vHaK55vzgiZw1bEPFG9yH7l33wA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/stylelint/node_modules/is-path-inside": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-4.0.0.tgz", + "integrity": "sha512-lJJV/5dYS+RcL8uQdBDW9c9uWFLLBNRyFhnAKXw5tVqLlKZ4RMGZKv+YQ/IA3OhD+RpbJa1LLFM1FQPGyIXvOA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/stylelint/node_modules/string-width": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.1.1.tgz", + "integrity": "sha512-KpqHIdDL9KwYk22wEOg/VIqYbrnLeSApsKT/bSj6Ez7pn3CftUiLAv2Lccpq1ALcpLV9UX1Ppn92npZWu2w/aw==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.3.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/stylelint/node_modules/supports-hyperlinks": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-4.4.0.tgz", + "integrity": "sha512-UKbpT93hN5Nr9go5UY7bopIB9YQlMz9nm/ct4IXt/irb5YRkn9WaqrOBJGZ5Pwvsd5FQzSVeYlGdXoCAPQZrPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^5.0.1", + "supports-color": "^10.2.2" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/chalk/supports-hyperlinks?sponsor=1" + } + }, + "node_modules/stylelint/node_modules/unicorn-magic": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.4.0.tgz", + "integrity": "sha512-wH590V9VNgYH9g3lH9wWjTrUoKsjLF6sGLjhR4sH1LWpLmCOH0Zf7PukhDA8BiS7KHe4oPNkcTHqYkj7SOGUOw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/supports-color": { "version": "10.2.2", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-10.2.2.tgz", @@ -8402,6 +9218,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/svg-tags": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/svg-tags/-/svg-tags-1.0.0.tgz", + "integrity": "sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==", + "dev": true + }, "node_modules/symbol-tree": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", @@ -9313,6 +10135,20 @@ "license": "ISC", "optional": true }, + "node_modules/write-file-atomic": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-7.0.0.tgz", + "integrity": "sha512-YnlPC6JqnZl6aO4uRc+dx5PHguiR9S6WeoLtpxNT9wIG+BDya7ZNE1q7KOjVgaA73hKhKLpVPgJ5QA9THQ5BRg==", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, "node_modules/ws": { "version": "8.18.3", "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", diff --git a/package.json b/package.json index 9a8a26c..4336ab5 100644 --- a/package.json +++ b/package.json @@ -233,6 +233,7 @@ "pretest": "npm run compile && npm run compile-tests && npm run lint", "check-types": "tsc --noEmit", "lint": "eslint src", + "lint:css": "stylelint \"src/webview/**/*.css\"", "test": "vscode-test", "test:node": "npm run compile-tests && node --test out/test/test-node/backend-identity.test.js", "test:coverage": "npm run compile-tests && node --require ./out/test/test-node/vscode-shim-register.js --experimental-test-coverage --test --test-coverage-lines=60 --test-coverage-functions=60 --test-coverage-branches=60 --test-coverage-include=out/test/backend/**/*.js --test-coverage-include=out/test/utils/**/*.js out/test/test-node/backend-identity.test.js out/test/test-node/utils-errors.test.js out/test/test-node/backend-settings.test.js out/test/test-node/backend-copyConfig.test.js out/test/test-node/backend-integration.test.js out/test/test-node/backend-commands.test.js out/test/test-node/backend-facade-helpers.test.js out/test/test-node/backend-facade-rollups.test.js out/test/test-node/backend-facade-query.test.js", @@ -251,6 +252,8 @@ "esbuild": "^0.27.2", "eslint": "^9.39.2", "npm-run-all": "^4.1.5", + "stylelint": "^17.1.1", + "stylelint-config-standard": "^40.0.0", "typescript": "^5.9.3" }, "dependencies": { diff --git a/src/types/css.d.ts b/src/types/css.d.ts new file mode 100644 index 0000000..31f07ea --- /dev/null +++ b/src/types/css.d.ts @@ -0,0 +1,4 @@ +declare module '*.css' { + const content: string; + export default content; +} diff --git a/src/webview/chart/main.ts b/src/webview/chart/main.ts index 6df9e07..bc6a4b4 100644 --- a/src/webview/chart/main.ts +++ b/src/webview/chart/main.ts @@ -1,6 +1,8 @@ // @ts-nocheck // Chart.js ESM bundle is loaded dynamically; skip CJS resolution noise import { el, createButton } from '../shared/domUtils'; import { BUTTONS } from '../shared/buttonConfig'; +// CSS imported as text via esbuild +import styles from './styles.css'; type ChartModule = typeof import('chart.js/auto'); type ChartConstructor = ChartModule['default']; @@ -64,32 +66,7 @@ function renderLayout(data: InitialChartData): void { root.replaceChildren(); const style = document.createElement('style'); - style.textContent = ` - :root { color: #e7e7e7; background: #0e0e0f; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; } - body { margin: 0; background: #0e0e0f; } - .container { padding: 16px; display: flex; flex-direction: column; gap: 14px; max-width: 1200px; margin: 0 auto; } - .header { display: flex; justify-content: space-between; align-items: center; gap: 12px; padding-bottom: 4px; } - .header-left { display: flex; align-items: center; gap: 8px; } - .header-icon { font-size: 20px; } - .header-title { font-size: 16px; font-weight: 700; color: #fff; text-align: left; } - .button-row { display: flex; flex-wrap: wrap; gap: 8px; } - .section { background: linear-gradient(135deg, #1b1b1e 0%, #1f1f22 100%); border: 1px solid #2e2e34; border-radius: 10px; padding: 12px; box-shadow: 0 4px 10px rgba(0, 0, 0, 0.28); text-align: center; } - .section h3 { margin: 0 0 10px 0; font-size: 14px; display: flex; align-items: center; gap: 6px; color: #ffffff; letter-spacing: 0.2px; text-align: left; } - .cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 10px; text-align: center; } - .card { background: #1b1b1e; border: 1px solid #2a2a30; border-radius: 8px; padding: 12px; box-shadow: 0 2px 6px rgba(0,0,0,0.24); text-align: center; } - .card-label { color: #b8b8b8; font-size: 11px; margin-bottom: 6px; } - .card-value { color: #f6f6f6; font-size: 18px; font-weight: 700; } - .card-sub { color: #9aa0a6; font-size: 11px; margin-top: 2px; } - .chart-shell { background: #1b1b1e; border: 1px solid #2a2a30; border-radius: 10px; padding: 12px; box-shadow: 0 2px 8px rgba(0,0,0,0.22); text-align: center; } - .chart-controls { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 8px; justify-content: center; } - .toggle { background: #202024; border: 1px solid #2d2d33; color: #e7e7e7; padding: 8px 12px; border-radius: 6px; font-size: 12px; cursor: pointer; transition: all 0.15s ease; } - .toggle.active { background: #0e639c; border-color: #1177bb; color: #fff; } - .toggle:hover { background: #2a2a30; } - .toggle.active:hover { background: #1177bb; } - .canvas-wrap { position: relative; height: 420px; } - .footer { color: #a0a0a0; font-size: 11px; margin-top: 6px; text-align: center; } - .footer em { color: #c0c0c0; } - `; + style.textContent = styles; const container = el('div', 'container'); const header = el('div', 'header'); diff --git a/src/webview/chart/styles.css b/src/webview/chart/styles.css new file mode 100644 index 0000000..6d2d759 --- /dev/null +++ b/src/webview/chart/styles.css @@ -0,0 +1,162 @@ +:root { + color: #e7e7e7; + background: #0e0e0f; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; +} + +body { + margin: 0; + background: #0e0e0f; +} + +.container { + padding: 16px; + display: flex; + flex-direction: column; + gap: 14px; + max-width: 1200px; + margin: 0 auto; +} + +.header { + display: flex; + justify-content: space-between; + align-items: center; + gap: 12px; + padding-bottom: 4px; +} + +.header-left { + display: flex; + align-items: center; + gap: 8px; +} + +.header-icon { + font-size: 20px; +} + +.header-title { + font-size: 16px; + font-weight: 700; + color: #fff; + text-align: left; +} + +.button-row { + display: flex; + flex-wrap: wrap; + gap: 8px; +} + +.section { + background: linear-gradient(135deg, #1b1b1e 0%, #1f1f22 100%); + border: 1px solid #2e2e34; + border-radius: 10px; + padding: 12px; + box-shadow: 0 4px 10px rgb(0, 0, 0, 0.28); + text-align: center; +} + +.section h3 { + margin: 0 0 10px; + font-size: 14px; + display: flex; + align-items: center; + gap: 6px; + color: #fff; + letter-spacing: 0.2px; + text-align: left; +} + +.cards { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); + gap: 10px; + text-align: center; +} + +.card { + background: #1b1b1e; + border: 1px solid #2a2a30; + border-radius: 8px; + padding: 12px; + box-shadow: 0 2px 6px rgb(0, 0, 0, 0.24); + text-align: center; +} + +.card-label { + color: #b8b8b8; + font-size: 11px; + margin-bottom: 6px; +} + +.card-value { + color: #f6f6f6; + font-size: 18px; + font-weight: 700; +} + +.card-sub { + color: #9aa0a6; + font-size: 11px; + margin-top: 2px; +} + +.chart-shell { + background: #1b1b1e; + border: 1px solid #2a2a30; + border-radius: 10px; + padding: 12px; + box-shadow: 0 2px 8px rgb(0, 0, 0, 0.22); + text-align: center; +} + +.chart-controls { + display: flex; + flex-wrap: wrap; + gap: 8px; + margin-bottom: 8px; + justify-content: center; +} + +.toggle { + background: #202024; + border: 1px solid #2d2d33; + color: #e7e7e7; + padding: 8px 12px; + border-radius: 6px; + font-size: 12px; + cursor: pointer; + transition: all 0.15s ease; +} + +.toggle.active { + background: #0e639c; + border-color: #17b; + color: #fff; +} + +.toggle:hover { + background: #2a2a30; +} + +.toggle.active:hover { + background: #17b; +} + +.canvas-wrap { + position: relative; + height: 420px; +} + +.footer { + color: #a0a0a0; + font-size: 11px; + margin-top: 6px; + text-align: center; +} + +.footer em { + color: #c0c0c0; +} diff --git a/src/webview/details/main.ts b/src/webview/details/main.ts index 19252db..bd6d94a 100644 --- a/src/webview/details/main.ts +++ b/src/webview/details/main.ts @@ -6,6 +6,8 @@ import { BUTTONS } from '../shared/buttonConfig'; // Token estimators loaded from JSON // @ts-ignore import tokenEstimatorsJson from '../../tokenEstimators.json'; +// CSS imported as text via esbuild +import styles from './styles.css'; type ModelUsage = Record; type EditorUsage = Record; @@ -100,36 +102,7 @@ function renderShell( root.replaceChildren(); const style = document.createElement('style'); - style.textContent = ` - :root { - color: #e7e7e7; - background: #1e1e1e; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; - } - body { margin: 0; background: #0e0e0f; } - .container { padding: 16px; display: flex; flex-direction: column; gap: 14px; max-width: 1200px; margin: 0 auto; } - .header { display: flex; justify-content: space-between; align-items: center; gap: 12px; padding-bottom: 4px; } - .title { display: flex; align-items: center; gap: 8px; font-size: 16px; font-weight: 700; color: #fff; } - .button-row { display: flex; flex-wrap: wrap; gap: 8px; } - .sections { display: flex; flex-direction: column; gap: 16px; } - .section { background: linear-gradient(135deg, #1b1b1e 0%, #1f1f22 100%); border: 1px solid #2e2e34; border-radius: 10px; padding: 12px; box-shadow: 0 4px 10px rgba(0, 0, 0, 0.28); } - .section h3 { margin: 0 0 10px 0; font-size: 14px; display: flex; align-items: center; gap: 6px; color: #ffffff; letter-spacing: 0.2px; } - .stats-table { width: 100%; border-collapse: collapse; table-layout: fixed; background: #1b1b1e; border: 1px solid #2a2a30; border-radius: 8px; overflow: hidden; } - .stats-table thead { background: #242429; } - .stats-table th, .stats-table td { padding: 10px 12px; border-bottom: 1px solid #2d2d33; vertical-align: middle; } - .stats-table th { text-align: left; color: #d0d0d0; font-weight: 700; font-size: 12px; letter-spacing: 0.1px; } - .stats-table td { color: #f0f0f0; font-size: 12px; } - .stats-table th.align-right, .stats-table td.align-right { text-align: right; } - .stats-table tbody tr:nth-child(even) { background: #18181b; } - .metric-label { display: inline-flex; align-items: center; gap: 6px; font-weight: 600; } - .period-header { display: flex; align-items: center; gap: 4px; color: #c8c8c8; } - .align-right .period-header { justify-content: flex-end; } - .value-right { text-align: right; } - .muted { color: #a0a0a0; font-size: 11px; margin-top: 4px; } - .notes { margin: 4px 0 0 0; padding-left: 16px; color: #c8c8c8; } - .notes li { margin: 4px 0; line-height: 1.4; } - .footer { color: #a0a0a0; font-size: 11px; margin-top: 6px; } - `; + style.textContent = styles; const container = el('div', 'container'); const header = el('div', 'header'); diff --git a/src/webview/details/styles.css b/src/webview/details/styles.css new file mode 100644 index 0000000..4fffe5d --- /dev/null +++ b/src/webview/details/styles.css @@ -0,0 +1,154 @@ +:root { + color: #e7e7e7; + background: #1e1e1e; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; +} + +body { + margin: 0; + background: #0e0e0f; +} + +.container { + padding: 16px; + display: flex; + flex-direction: column; + gap: 14px; + max-width: 1200px; + margin: 0 auto; +} + +.header { + display: flex; + justify-content: space-between; + align-items: center; + gap: 12px; + padding-bottom: 4px; +} + +.title { + display: flex; + align-items: center; + gap: 8px; + font-size: 16px; + font-weight: 700; + color: #fff; +} + +.button-row { + display: flex; + flex-wrap: wrap; + gap: 8px; +} + +.sections { + display: flex; + flex-direction: column; + gap: 16px; +} + +.section { + background: linear-gradient(135deg, #1b1b1e 0%, #1f1f22 100%); + border: 1px solid #2e2e34; + border-radius: 10px; + padding: 12px; + box-shadow: 0 4px 10px rgb(0, 0, 0, 0.28); +} + +.section h3 { + margin: 0 0 10px; + font-size: 14px; + display: flex; + align-items: center; + gap: 6px; + color: #fff; + letter-spacing: 0.2px; +} + +.stats-table { + width: 100%; + border-collapse: collapse; + table-layout: fixed; + background: #1b1b1e; + border: 1px solid #2a2a30; + border-radius: 8px; + overflow: hidden; +} + +.stats-table thead { + background: #242429; +} + +.stats-table th, +.stats-table td { + padding: 10px 12px; + border-bottom: 1px solid #2d2d33; + vertical-align: middle; +} + +.stats-table th { + text-align: left; + color: #d0d0d0; + font-weight: 700; + font-size: 12px; + letter-spacing: 0.1px; +} + +.stats-table td { + color: #f0f0f0; + font-size: 12px; +} + +.stats-table th.align-right, +.stats-table td.align-right { + text-align: right; +} + +.stats-table tbody tr:nth-child(even) { + background: #18181b; +} + +.metric-label { + display: inline-flex; + align-items: center; + gap: 6px; + font-weight: 600; +} + +.period-header { + display: flex; + align-items: center; + gap: 4px; + color: #c8c8c8; +} + +.align-right .period-header { + justify-content: flex-end; +} + +.value-right { + text-align: right; +} + +.muted { + color: #a0a0a0; + font-size: 11px; + margin-top: 4px; +} + +.notes { + margin: 4px 0 0; + padding-left: 16px; + color: #c8c8c8; +} + +.notes li { + margin: 4px 0; + line-height: 1.4; +} + +.footer { + color: #a0a0a0; + font-size: 11px; + margin-top: 6px; +} diff --git a/src/webview/diagnostics/main.ts b/src/webview/diagnostics/main.ts index a60a478..5de96d1 100644 --- a/src/webview/diagnostics/main.ts +++ b/src/webview/diagnostics/main.ts @@ -1,5 +1,7 @@ // Diagnostics Report webview with tabbed interface import { buttonHtml } from '../shared/buttonConfig'; +// CSS imported as text via esbuild +import styles from './styles.css'; // Constants const LOADING_PLACEHOLDER = 'Loading...'; @@ -607,275 +609,7 @@ function renderLayout(data: DiagnosticsData): void { const detailedFiles = data.detailedSessionFiles || []; root.innerHTML = ` - +
diff --git a/src/webview/diagnostics/styles.css b/src/webview/diagnostics/styles.css new file mode 100644 index 0000000..ada4cb6 --- /dev/null +++ b/src/webview/diagnostics/styles.css @@ -0,0 +1,423 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; + background: #0e0e0f; + color: #e7e7e7; + padding: 16px; + line-height: 1.5; + min-width: 320px; +} + +.container { + background: linear-gradient(135deg, #1b1b1e 0%, #1f1f22 100%); + border: 1px solid #2e2e34; + border-radius: 10px; + padding: 16px; + box-shadow: 0 4px 10px rgb(0, 0, 0, 0.28); + max-width: 1200px; + margin: 0 auto; +} + +.header { + display: flex; + justify-content: space-between; + align-items: center; + gap: 12px; + margin-bottom: 16px; + padding-bottom: 4px; +} + +.header-left { + display: flex; + align-items: center; + gap: 8px; +} + +.header-icon { + font-size: 20px; +} + +.header-title { + font-size: 16px; + font-weight: 700; + color: #fff; +} + +.button-row { + display: flex; + flex-wrap: wrap; + gap: 8px; +} + +/* Tab styles */ +.tabs { + display: flex; + border-bottom: 1px solid #5a5a5a; + margin-bottom: 16px; +} + +.tab { + padding: 10px 20px; + cursor: pointer; + border: none; + background: transparent; + color: #999; + font-size: 13px; + font-weight: 500; + border-bottom: 2px solid transparent; + transition: all 0.2s; +} + +.tab:hover { + color: #fff; + background: rgb(255, 255, 255, 0.05); +} + +.tab.active { + color: #4FC3F7; + border-bottom-color: #4FC3F7; +} + +.tab-content { + display: none; +} + +.tab-content.active { + display: block; +} + +/* Editor filter panels */ +.editor-filter-panels { + display: flex; + flex-wrap: wrap; + gap: 10px; + margin-bottom: 16px; +} + +.editor-panel { + background: #353535; + border: 2px solid #5a5a5a; + border-radius: 8px; + padding: 12px 16px; + cursor: pointer; + transition: all 0.2s; + min-width: 140px; + text-align: center; +} + +.editor-panel:hover { + background: #404040; + border-color: #7a7a7a; +} + +.editor-panel.active { + background: #3a4a5a; + border-color: #4FC3F7; +} + +.editor-panel-icon { + font-size: 24px; + margin-bottom: 4px; +} + +.editor-panel-name { + font-size: 13px; + font-weight: 600; + color: #fff; + margin-bottom: 2px; +} + +.editor-panel-stats { + font-size: 10px; + color: #999; +} + +/* Loading state */ +.loading-state { + text-align: center; + padding: 40px 20px; + color: #999; +} + +.loading-spinner { + font-size: 48px; + margin-bottom: 16px; + animation: pulse 1.5s ease-in-out infinite; +} + +@keyframes pulse { + 0%, + 100% { + opacity: 1; + } + + 50% { + opacity: 0.5; + } +} + +.loading-text { + font-size: 16px; + color: #fff; + margin-bottom: 8px; +} + +.loading-subtext { + font-size: 12px; + color: #888; +} + +/* Summary cards */ +.summary-cards { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); + gap: 12px; + margin-bottom: 16px; +} + +.summary-card { + background: #353535; + border: 1px solid #5a5a5a; + border-radius: 4px; + padding: 12px; + text-align: center; +} + +.summary-label { + font-size: 11px; + color: #b3b3b3; + margin-bottom: 4px; +} + +.summary-value { + font-size: 18px; + font-weight: 600; + color: #fff; +} + +.summary-sub { + font-size: 10px; + color: #94a3b8; + text-align: left; + margin-top: 6px; +} + +.context-ref-filter { + cursor: pointer; + padding: 2px 6px; + border-radius: 3px; + margin: 2px 0; + transition: all 0.2s; +} + +.context-ref-filter:hover { + background: rgb(79, 195, 247, 0.2); + color: #4FC3F7; +} + +.context-ref-filter.active { + background: rgb(79, 195, 247, 0.3); + color: #4FC3F7; + font-weight: 600; +} + +/* Table styles */ +.table-container { + overflow: auto; + max-height: 500px; +} + +.session-table { + width: 100%; + border-collapse: collapse; + font-size: 12px; +} + +.session-table th, +.session-table td { + padding: 8px 10px; + text-align: left; + border-bottom: 1px solid #5a5a5a; +} + +.session-table th { + background: #353535; + color: #fff; + font-weight: 600; + position: sticky; + top: 0; +} + +.session-table th.sortable { + cursor: pointer; + user-select: none; +} + +.session-table th.sortable:hover { + background: #454545; + color: #4FC3F7; +} + +.session-table tr:hover { + background: rgb(255, 255, 255, 0.03); +} + +.editor-badge { + background: #4a5a6a; + padding: 2px 6px; + border-radius: 3px; + font-size: 10px; + color: #fff; +} + +.session-folders-table { + margin-top: 16px; + margin-bottom: 16px; +} + +.session-folders-table h4 { + color: #fff; + font-size: 14px; + margin-bottom: 12px; +} + +.report-content { + background: #2a2a2a; + border: 1px solid #5a5a5a; + border-radius: 4px; + padding: 16px; + white-space: pre-wrap; + font-size: 13px; + overflow: auto; + max-height: 60vh; +} + +.file-subpath { + font-size: 11px; + color: #9aa0a6; + margin-top: 4px; +} + +.session-file-link, +.reveal-link, +.view-formatted-link { + color: #4FC3F7; + text-decoration: underline; + cursor: pointer; +} + +.session-file-link:hover, +.reveal-link:hover, +.view-formatted-link:hover { + color: #81D4FA; +} + +.empty-session-link { + color: #999; +} + +.empty-session-link:hover { + color: #aaa; +} + +.button-group { + display: flex; + gap: 12px; + margin-top: 16px; + flex-wrap: wrap; +} + +.button { + background: #202024; + border: 1px solid #2d2d33; + color: #e7e7e7; + padding: 8px 12px; + border-radius: 6px; + cursor: pointer; + font-size: 13px; + font-weight: 500; + transition: background-color 0.15s ease; + display: inline-flex; + align-items: center; + gap: 8px; +} + +.button:hover { + background: #2a2a30; +} + +.button:active { + background: #0a5a8a; +} + +.button:disabled { + opacity: 0.6; + cursor: not-allowed; +} + +.button.secondary { + background: #3c3c3c; + border-color: #5a5a5a; + color: #fff; +} + +.button.secondary:hover { + background: #4a4a4a; +} + +.info-box { + background: #3a4a5a; + border: 1px solid #4a5a6a; + border-radius: 4px; + padding: 12px; + margin-bottom: 16px; + font-size: 13px; +} + +.info-box-title { + font-weight: 600; + color: #fff; + margin-bottom: 6px; +} + +.cache-details { + margin-top: 16px; +} + +.cache-location { + margin-top: 20px; +} + +.cache-location h4 { + color: #fff; + font-size: 14px; + margin-bottom: 8px; +} + +.location-box { + background: #2a2a2a; + border: 1px solid #5a5a5a; + border-radius: 4px; + padding: 12px; + overflow-x: auto; +} + +.location-box code { + color: #4FC3F7; + font-size: 12px; +} + +.cache-actions { + margin-top: 20px; +} + +.cache-actions h4 { + color: #fff; + font-size: 14px; + margin-bottom: 8px; +} diff --git a/src/webview/logviewer/main.ts b/src/webview/logviewer/main.ts index 0e305ba..6d5e3d1 100644 --- a/src/webview/logviewer/main.ts +++ b/src/webview/logviewer/main.ts @@ -1,5 +1,7 @@ // Log Viewer webview - displays session file details and chat turns import { ContextReferenceUsage, getTotalContextRefs, getImplicitContextRefs, getExplicitContextRefs, getContextRefsSummary } from '../shared/contextRefUtils'; +// CSS imported as text via esbuild +import styles from './styles.css'; type ChatTurn = { turnNumber: number; @@ -419,679 +421,7 @@ function renderLayout(data: SessionLogData): void { const modelNames = Object.keys(modelUsage); root.innerHTML = ` - +
diff --git a/src/webview/logviewer/styles.css b/src/webview/logviewer/styles.css new file mode 100644 index 0000000..912903f --- /dev/null +++ b/src/webview/logviewer/styles.css @@ -0,0 +1,746 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; + background: #0e0e0f; + color: #e7e7e7; + padding: 20px; + line-height: 1.6; + min-width: 320px; +} + +.container { + max-width: 1400px; + margin: 0 auto; +} + +/* Mode/model bar improvements */ +.mode-bar-group { + background: linear-gradient(135deg, #1a1a22 0%, #1f1f28 100%); + border: 1px solid #3a3a44; + border-radius: 12px; + padding: 20px 24px; + display: flex; + align-items: center; + flex-wrap: wrap; + gap: 28px; + margin-bottom: 24px; + box-shadow: 0 4px 12px rgb(0, 0, 0, 0.3), 0 1px 3px rgb(0, 0, 0, 0.2); +} + +.mode-bar { + display: flex; + align-items: center; + gap: 10px; + font-size: 15px; + font-weight: 500; +} + +.mode-icon { + width: 36px; + height: 36px; + border-radius: 8px; + display: flex; + align-items: center; + justify-content: center; + font-size: 18px; + background: #23232a; + border: 2px solid #2a2a30; + box-shadow: 0 2px 4px rgb(0, 0, 0, 0.2); +} + +.mode-label { + color: #b8b8c0; + font-weight: 600; +} + +.mode-count { + color: #fff; + font-weight: 700; + font-size: 18px; +} + +.model-summary { + margin-left: auto; + font-size: 15px; + font-weight: 600; + color: #fff; + display: flex; + align-items: center; + gap: 12px; + flex-wrap: wrap; +} + +.model-list { + display: flex; + gap: 10px; + flex-wrap: wrap; +} + +.model-item { + background: linear-gradient(135deg, #2a2a35 0%, #25252f 100%); + border: 1px solid #3a3a44; + border-radius: 6px; + padding: 4px 12px; + color: #60a5fa; + font-weight: 600; + font-size: 13px; + box-shadow: 0 2px 6px rgb(0, 0, 0, 0.15); +} + +/* Summary cards */ +.summary-cards { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); + gap: 16px; + margin-bottom: 24px; +} + +.summary-card { + background: linear-gradient(135deg, #1a1a22 0%, #1f1f28 100%); + border: 1px solid #3a3a44; + border-radius: 12px; + padding: 24px 16px; + text-align: center; + box-shadow: 0 4px 12px rgb(0, 0, 0, 0.3), 0 1px 3px rgb(0, 0, 0, 0.2); + transition: transform 0.2s, box-shadow 0.2s; +} + +.summary-card:hover { + transform: translateY(-2px); + box-shadow: 0 6px 16px rgb(0, 0, 0, 0.4), 0 2px 4px rgb(0, 0, 0, 0.2); +} + +.filename-link { + cursor: pointer; + color: #60a5fa; + text-decoration: underline; + transition: color 0.2s; +} + +.filename-link:hover { + color: #93c5fd; +} + +.summary-label { + font-size: 14px; + color: #b8b8c0; + margin-bottom: 8px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.summary-value { + font-size: 32px; + font-weight: 700; + color: #60a5fa; + margin-bottom: 8px; +} + +.summary-sub { + font-size: 12px; + color: #94a3b8; + line-height: 1.5; +} + +/* Turns container */ +.turns-header { + font-size: 18px; + font-weight: 700; + color: #fff; + margin-bottom: 16px; + display: flex; + align-items: center; + gap: 10px; + padding: 12px 0; + border-bottom: 2px solid #3a3a44; +} + +.turns-list { + display: flex; + flex-direction: column; + gap: 16px; +} + +/* Turn card */ +.turn-card { + background: linear-gradient(135deg, #1a1a22 0%, #1f1f28 100%); + border: 1px solid #3a3a44; + border-radius: 12px; + overflow: hidden; + box-shadow: 0 4px 12px rgb(0, 0, 0, 0.3), 0 1px 3px rgb(0, 0, 0, 0.2); + transition: transform 0.2s, box-shadow 0.2s; +} + +.turn-card:hover { + transform: translateY(-2px); + box-shadow: 0 6px 16px rgb(0, 0, 0, 0.4), 0 2px 4px rgb(0, 0, 0, 0.2); +} + +.turn-header { + background: linear-gradient(135deg, #22222a 0%, #27272f 100%); + padding: 14px 16px; + display: flex; + justify-content: space-between; + align-items: center; + gap: 10px; + border-bottom: 1px solid #3a3a44; + min-height: 48px; +} + +.turn-meta { + display: flex; + align-items: center; + gap: 8px; + flex-wrap: nowrap; + flex: 1; + min-width: 0; + overflow: hidden; +} + +.turn-number { + font-weight: 700; + color: #fff; + font-size: 16px; + background: #3a3a44; + padding: 4px 10px; + border-radius: 6px; + flex-shrink: 0; +} + +.turn-mode { + padding: 4px 12px; + border-radius: 16px; + font-size: 12px; + font-weight: 700; + color: #fff; + box-shadow: 0 2px 4px rgb(0, 0, 0, 0.2); + flex-shrink: 0; + white-space: nowrap; +} + +.turn-model { + font-size: 12px; + color: #94a3b8; + background: #2a2a35; + padding: 4px 10px; + border-radius: 6px; + font-weight: 600; + border: 1px solid #3a3a44; + flex-shrink: 0; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + max-width: 200px; +} + +.turn-tokens { + font-size: 12px; + color: #94a3b8; + font-weight: 600; + flex-shrink: 0; + white-space: nowrap; +} + +.context-badge { + font-size: 12px; + color: #e0e7ff; + background: linear-gradient(135deg, #4c1d95 0%, #5b21b6 100%); + padding: 4px 10px; + border-radius: 6px; + font-weight: 600; + border: 1px solid #6d28d9; + flex-shrink: 0; + white-space: nowrap; + margin-left: 4px; + box-shadow: 0 2px 4px rgb(0, 0, 0, 0.2); +} + +/* Messages */ +.turn-content { + padding: 16px; +} + +.message { + margin-bottom: 14px; +} + +.message:last-child { + margin-bottom: 0; +} + +.message-label { + font-size: 12px; + font-weight: 700; + color: #94a3b8; + margin-bottom: 6px; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.message-text { + background: #22222a; + border-radius: 8px; + padding: 14px 16px; + font-size: 14px; + line-height: 1.6; + white-space: pre-wrap; + overflow-wrap: break-word; + max-height: 400px; + overflow-y: auto; + border: 1px solid #3a3a44; +} + +.user-message .message-text { + border-left: 4px solid #60a5fa; + background: linear-gradient(135deg, #1e293b 0%, #22222a 100%); +} + +.assistant-message .message-text { + border-left: 4px solid #7c3aed; + background: linear-gradient(135deg, #1e1e2a 0%, #22222a 100%); +} + +/* Shared collapse arrow for details/summary panels */ +.collapse-arrow { + display: inline-block; + width: 16px; + color: #94a3b8; + font-size: 10px; + transition: transform 0.2s; + flex-shrink: 0; +} + +details[open] > summary .collapse-arrow { + transform: rotate(90deg); +} + +/* Tool calls */ +.turn-tools { + margin: 0 16px 14px; + background: linear-gradient(135deg, #2a2a35 0%, #25252f 100%); + border: 1px solid #3a3a44; + border-radius: 8px; + padding: 12px 14px; + box-shadow: 0 2px 8px rgb(0, 0, 0, 0.2); +} + +.tool-calls-details { + cursor: pointer; + margin: 0; + padding: 0; +} + +.tool-calls-summary { + list-style: none; + cursor: pointer; + user-select: none; + display: flex; + align-items: center; + gap: 10px; + padding: 2px 0; + padding-inline-start: 0; + margin: 0; +} + +.tool-calls-summary::-webkit-details-marker { + display: none; +} + +.tool-calls-summary::marker { + display: none; +} + +.tool-calls-summary:hover { + color: #fff; +} + +.tools-header-inline { + font-size: 13px; + font-weight: 700; + color: #fff; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.tool-summary-text { + font-size: 12px; + font-weight: 600; + color: #c084fc; + flex: 1; + display: flex; + flex-wrap: wrap; + gap: 8px; + align-items: center; +} + +.tool-summary-item { + background: rgb(192, 132, 252, 0.1); + border: 1px solid rgb(192, 132, 252, 0.3); + padding: 2px 8px; + border-radius: 4px; + white-space: nowrap; +} + +.tool-summary-item strong { + color: #e9d5ff; + font-weight: 700; +} + +.tools-header { + font-size: 13px; + font-weight: 700; + color: #fff; + margin-bottom: 10px; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.tools-table { + width: 100%; + border-collapse: collapse; + font-size: 13px; + margin-top: 10px; +} + +.tools-table thead th { + text-align: left; + padding: 8px 12px; + background: #1a1a22; + border-bottom: 2px solid #4a4a5a; + color: #94a3b8; + font-weight: 600; + font-size: 12px; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.tools-table thead th:nth-child(2) { + text-align: right; +} + +.tools-table tbody .tool-row { + border-bottom: 1px solid #3a3a44; +} + +.tools-table tbody .tool-row:last-child { + border-bottom: none; +} + +.tool-name-cell { + padding: 10px 12px; + vertical-align: top; +} + +.tool-action-cell { + padding: 10px 12px; + text-align: right; + vertical-align: top; + width: 100px; +} + +.tool-name { + font-weight: 700; + color: #c084fc; + font-size: 13px; +} + +.tool-call-pretty { + font-weight: 700; + color: #34d399; + font-size: 12px; + text-decoration: underline; + white-space: nowrap; +} + +.tool-call-pretty:hover { + color: #6ee7b7; +} + +.tool-details { + margin-top: 8px; + font-size: 12px; +} + +.tool-details summary { + cursor: pointer; + color: #94a3b8; + font-weight: 600; +} + +.tool-details summary:hover { + color: #cbd5e1; +} + +.tool-details pre { + background: #1a1a20; + border: 1px solid #2a2a30; + padding: 10px; + border-radius: 6px; + overflow-x: auto; + max-height: 200px; + font-size: 11px; + margin-top: 6px; + line-height: 1.5; +} + +/* MCP tools */ +.turn-mcp { + margin: 0 16px 14px; + background: linear-gradient(135deg, #1e2e1e 0%, #1a261a 100%); + border: 1px solid #3a5a3a; + border-radius: 8px; + padding: 14px; + box-shadow: 0 2px 8px rgb(0, 0, 0, 0.2); +} + +.mcp-header { + font-size: 13px; + font-weight: 700; + color: #fff; + margin-bottom: 10px; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.mcp-list { + display: flex; + flex-wrap: wrap; + gap: 6px; +} + +.mcp-item { + background: rgb(34, 197, 94, 0.1); + border: 1px solid rgb(34, 197, 94, 0.3); + padding: 4px 10px; + border-radius: 4px; + font-size: 12px; + color: #cbd5e1; +} + +.mcp-server { + font-weight: 600; + color: #22c55e; +} + +/* Context References - detail panel */ +.turn-context-details { + margin-bottom: 14px; + background: linear-gradient(135deg, #2a2535 0%, #252530 100%); + border: 1px solid #4a4a5a; + border-radius: 8px; + padding: 14px; + box-shadow: 0 2px 8px rgb(0, 0, 0, 0.2); +} + +.context-header { + font-size: 13px; + font-weight: 700; + color: #fff; + margin-bottom: 10px; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.context-section { + font-size: 13px; + color: #cbd5e1; + margin-bottom: 8px; + line-height: 1.6; +} + +.context-section:last-child { + margin-bottom: 0; +} + +.context-section strong { + color: #94a3b8; + font-weight: 600; +} + +.context-refs-table { + width: 100%; + border-collapse: collapse; + font-size: 13px; + margin-top: 8px; +} + +.context-refs-table th, +.context-refs-table td { + padding: 8px 12px; + text-align: left; + border-bottom: 1px solid #334155; +} + +.context-refs-table th { + color: #94a3b8; + font-weight: 600; + font-size: 11px; + text-transform: uppercase; + letter-spacing: 0.5px; + background: #1e293b; +} + +.context-refs-table td { + color: #cbd5e1; +} + +.context-refs-table tbody tr:hover { + background: #1e293b; +} + +.context-refs-table .count-cell { + text-align: center; + font-weight: 600; + color: #60a5fa; +} + +.context-type-implicit { + color: #94a3b8; + font-size: 12px; +} + +.context-type-explicit { + color: #4ade80; + font-size: 12px; +} + +.context-path { + padding-left: 10px; + color: #9ca3af; + font-size: 12px; + margin-top: 4px; +} + +/* Empty state */ +.empty-state { + text-align: center; + padding: 60px 20px; + color: #94a3b8; + font-size: 16px; + background: linear-gradient(135deg, #1a1a22 0%, #1f1f28 100%); + border: 1px solid #3a3a44; + border-radius: 12px; + box-shadow: 0 4px 12px rgb(0, 0, 0, 0.3); +} + +/* Scrollbar styling */ +::-webkit-scrollbar { + width: 10px; + height: 10px; +} + +::-webkit-scrollbar-track { + background: #1a1a22; +} + +::-webkit-scrollbar-thumb { + background: #3a3a44; + border-radius: 5px; +} + +::-webkit-scrollbar-thumb:hover { + background: #4a4a54; +} + +/* Context References - collapsible panel */ +.turn-context-refs { + margin: 0 16px 14px; + background: linear-gradient(135deg, #2a2535 0%, #252530 100%); + border: 1px solid #4a4a5a; + border-radius: 8px; + padding: 12px 14px; + box-shadow: 0 2px 8px rgb(0, 0, 0, 0.2); +} + +.context-refs-details { + cursor: pointer; + margin: 0; + padding: 0; +} + +.context-refs-summary { + list-style: none; + cursor: pointer; + user-select: none; + display: flex; + align-items: center; + gap: 10px; + padding: 2px 0; + padding-inline-start: 0; + margin: 0; +} + +.context-refs-summary::-webkit-details-marker { + display: none; +} + +.context-refs-summary::marker { + display: none; +} + +.context-refs-summary:hover { + color: #fff; +} + +.context-refs-header-inline { + font-size: 13px; + font-weight: 700; + color: #fff; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.context-ref-summary-text { + font-size: 12px; + font-weight: 600; + color: #22d3ee; + flex: 1; + display: flex; + flex-wrap: wrap; + gap: 8px; + align-items: center; +} + +.context-ref-item { + background: rgb(34, 211, 238, 0.1); + border: 1px solid rgb(34, 211, 238, 0.3); + padding: 2px 8px; + border-radius: 4px; + white-space: nowrap; +} + +.context-ref-item strong { + color: #a5f3fc; + font-weight: 700; +} + +.context-ref-implicit { + background: rgb(148, 163, 184, 0.1); + border: 1px solid rgb(148, 163, 184, 0.3); + color: #94a3b8; +} + +.context-ref-implicit strong { + color: #cbd5e1; +} + +.context-refs-content { + margin-top: 12px; + padding-top: 12px; + border-top: 1px solid rgb(255, 255, 255, 0.1); +} + +/* Footer */ +.footer { + margin-top: 16px; + padding-top: 12px; + border-top: 1px solid #2a2a30; + font-size: 11px; + color: #666; +} diff --git a/src/webview/usage/main.ts b/src/webview/usage/main.ts index e7ddfdc..2ce194b 100644 --- a/src/webview/usage/main.ts +++ b/src/webview/usage/main.ts @@ -2,6 +2,8 @@ import { el } from '../shared/domUtils'; import { buttonHtml } from '../shared/buttonConfig'; import { ContextReferenceUsage, getTotalContextRefs } from '../shared/contextRefUtils'; +// CSS imported as text via esbuild +import styles from './styles.css'; type ModeUsage = { ask: number; edit: number; agent: number }; type ToolCallUsage = { total: number; byTool: { [key: string]: number } }; @@ -116,126 +118,7 @@ function renderLayout(stats: UsageAnalysisStats): void { const monthTotalModes = stats.month.modeUsage.ask + stats.month.modeUsage.edit + stats.month.modeUsage.agent; root.innerHTML = ` - +
diff --git a/src/webview/usage/styles.css b/src/webview/usage/styles.css new file mode 100644 index 0000000..e3401da --- /dev/null +++ b/src/webview/usage/styles.css @@ -0,0 +1,235 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; + background: #0e0e0f; + color: #e7e7e7; + padding: 16px; + line-height: 1.5; + min-width: 320px; +} + +.container { + background: linear-gradient(135deg, #1b1b1e 0%, #1f1f22 100%); + border: 1px solid #2e2e34; + border-radius: 10px; + padding: 16px; + box-shadow: 0 4px 10px rgb(0, 0, 0, 0.28); + max-width: 1200px; + margin: 0 auto; +} + +.header { + display: flex; + justify-content: space-between; + align-items: center; + gap: 12px; + margin-bottom: 14px; + padding-bottom: 4px; +} + +.header-left { + display: flex; + align-items: center; + gap: 8px; +} + +.header-icon { + font-size: 20px; +} + +.header-title { + font-size: 16px; + font-weight: 700; + color: #fff; + letter-spacing: 0.2px; +} + +.button-row { + display: flex; + flex-wrap: wrap; + gap: 8px; +} + +.nav-button { + background: #202024; + border: 1px solid #2d2d33; + color: #e7e7e7; + padding: 8px 12px; + border-radius: 6px; + font-size: 12px; + cursor: pointer; + transition: all 0.15s ease; +} + +.nav-button:hover { + background: #2a2a30; +} + +.nav-button.primary { + background: #0e639c; + border-color: #17b; + color: #fff; +} + +.nav-button.primary:hover { + background: #17b; +} + +.section { + background: #1b1b1e; + border: 1px solid #2a2a30; + border-radius: 8px; + padding: 12px; + margin-bottom: 16px; + box-shadow: 0 2px 6px rgb(0, 0, 0, 0.24); +} + +.section-title { + font-size: 14px; + font-weight: 700; + color: #fff; + margin-bottom: 10px; + display: flex; + align-items: center; + gap: 6px; + letter-spacing: 0.2px; +} + +.section-subtitle { + font-size: 12px; + color: #b8b8b8; + margin-bottom: 12px; +} + +.stats-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); + gap: 12px; + margin-bottom: 16px; +} + +.stat-card { + background: #18181b; + border: 1px solid #2a2a30; + border-radius: 6px; + padding: 12px; + box-shadow: 0 2px 4px rgb(0, 0, 0, 0.2); +} + +.stat-card[title] { + cursor: help; +} + +.stat-label { + font-size: 11px; + color: #b8b8b8; + margin-bottom: 4px; +} + +.stat-value { + font-size: 20px; + font-weight: 700; + color: #f6f6f6; +} + +.bar-chart { + background: #18181b; + border: 1px solid #2a2a30; + border-radius: 6px; + padding: 12px; + margin-bottom: 12px; +} + +.bar-item { + margin-bottom: 8px; +} + +.bar-label { + display: flex; + justify-content: space-between; + font-size: 12px; + margin-bottom: 4px; + color: #d0d0d0; +} + +.bar-track { + background: #242429; + height: 8px; + border-radius: 4px; + overflow: hidden; +} + +.bar-fill { + height: 100%; + border-radius: 4px; + transition: width 0.3s ease; +} + +.list { + background: #18181b; + border: 1px solid #2a2a30; + border-radius: 6px; + padding: 12px 16px; +} + +.list ul { + list-style: none; + padding: 0; +} + +.list li { + padding: 4px 0; + font-size: 13px; +} + +.two-column { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 16px; +} + +.three-column { + display: grid; + grid-template-columns: 1fr 1fr 1fr; + gap: 16px; +} + +.info-box { + background: #1b1b1e; + border: 1px solid #2a2a30; + border-radius: 6px; + padding: 12px; + margin-bottom: 16px; + font-size: 12px; + color: #c8c8c8; +} + +.info-box-title { + font-weight: 600; + color: #fff; + margin-bottom: 6px; +} + +.footer { + margin-top: 6px; + padding-top: 12px; + border-top: 1px solid #2a2a30; + text-align: left; + font-size: 11px; + color: #a0a0a0; +} + +@media (width <= 768px) { + .two-column { + grid-template-columns: 1fr; + } + + .three-column { + grid-template-columns: 1fr; + } +} From 88081175b6fc5a64519f1d3c4abc39ea6da489df Mon Sep 17 00:00:00 2001 From: Rob Bos Date: Sun, 8 Feb 2026 19:57:58 +0100 Subject: [PATCH 7/8] Fix regression --- src/extension.ts | 271 ++++++++++++++++++++++++++--------------------- 1 file changed, 150 insertions(+), 121 deletions(-) diff --git a/src/extension.ts b/src/extension.ts index 19ad556..665a4c4 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1546,104 +1546,97 @@ class CopilotTokenTracker implements vscode.Disposable { // Handle .jsonl files OR .json files with JSONL content (Copilot CLI format and VS Code incremental format) const isJsonlContent = sessionFile.endsWith('.jsonl') || this.isJsonlContent(fileContent); if (isJsonlContent) { - const lines = fileContent.trim().split('\n'); - let sessionMode = 'ask'; // Default mode + const lines = fileContent.trim().split('\n').filter(l => l.trim()); - for (const line of lines) { - if (!line.trim()) { continue; } + // Detect if this is delta-based format (VS Code incremental) + let isDeltaBased = false; + if (lines.length > 0) { try { - const event = JSON.parse(line); - - // Handle VS Code incremental format - detect mode from session header - if (event.kind === 0 && event.v?.inputState?.mode?.kind) { - sessionMode = event.v.inputState.mode.kind; - - // Detect implicit selections in initial state (only if there's an actual range) - if (event.v?.inputState?.selections && Array.isArray(event.v.inputState.selections)) { - for (const sel of event.v.inputState.selections) { - // Only count if it's an actual selection (not just a cursor position) - if (sel.startLineNumber !== sel.endLineNumber || sel.startColumn !== sel.endColumn) { - analysis.contextReferences.implicitSelection++; - break; // Count once per session - } - } - } + const firstLine = JSON.parse(lines[0]); + if (firstLine && typeof firstLine.kind === 'number') { + isDeltaBased = true; } - - // Handle mode changes (kind: 1 with mode update) - if (event.kind === 1 && event.k?.includes('mode') && event.v?.kind) { - sessionMode = event.v.kind; + } catch { + // Not delta format + } + } + + if (isDeltaBased) { + // Delta-based format: reconstruct full state first, then process + let sessionState: any = {}; + for (const line of lines) { + try { + const delta = JSON.parse(line); + sessionState = this.applyDelta(sessionState, delta); + } catch { + // Skip invalid lines } - - // Detect implicit selections in updates to inputState.selections - if (event.kind === 1 && event.k?.includes('selections') && Array.isArray(event.v)) { - for (const sel of event.v) { - // Only count if it's an actual selection (not just a cursor position) - if (sel && (sel.startLineNumber !== sel.endLineNumber || sel.startColumn !== sel.endColumn)) { - analysis.contextReferences.implicitSelection++; - break; // Count once per update - } + } + + // Extract session mode from reconstructed state + let sessionMode = 'ask'; + if (sessionState.inputState?.mode?.kind) { + sessionMode = sessionState.inputState.mode.kind; + } + + // Detect implicit selections + if (sessionState.inputState?.selections && Array.isArray(sessionState.inputState.selections)) { + for (const sel of sessionState.inputState.selections) { + if (sel && (sel.startLineNumber !== sel.endLineNumber || sel.startColumn !== sel.endColumn)) { + analysis.contextReferences.implicitSelection++; + break; } } + } + + // Process reconstructed requests array + const requests = sessionState.requests || []; + for (const request of requests) { + if (!request || !request.requestId) { continue; } - // Handle contentReferences updates (kind: 1 with contentReferences update) - if (event.kind === 1 && event.k?.includes('contentReferences') && Array.isArray(event.v)) { - this.analyzeContentReferences(event.v, analysis.contextReferences); + // Count by mode + if (sessionMode === 'agent') { + analysis.modeUsage.agent++; + } else if (sessionMode === 'edit') { + analysis.modeUsage.edit++; + } else { + analysis.modeUsage.ask++; } - // Handle variableData updates (kind: 1 with variableData update) - if (event.kind === 1 && event.k?.includes('variableData') && event.v) { - this.analyzeVariableData(event.v, analysis.contextReferences); + // Check for agent in request + if (request.agent?.id) { + const toolName = request.agent.id; + analysis.toolCalls.total++; + analysis.toolCalls.byTool[toolName] = (analysis.toolCalls.byTool[toolName] || 0) + 1; } - // Handle VS Code incremental format - count requests as interactions - if (event.kind === 2 && event.k?.[0] === 'requests' && Array.isArray(event.v)) { - for (const request of event.v) { - if (request.requestId) { - // Count by mode - if (sessionMode === 'agent') { - analysis.modeUsage.agent++; - } else if (sessionMode === 'edit') { - analysis.modeUsage.edit++; - } else { - analysis.modeUsage.ask++; - } - } - // Check for agent in request - if (request.agent?.id) { - const toolName = request.agent.id; + // Analyze all context references from this request + this.analyzeRequestContext(request, analysis.contextReferences); + + // Extract tool calls from request.response array + if (request.response && Array.isArray(request.response)) { + for (const responseItem of request.response) { + if (responseItem.kind === 'toolInvocationSerialized' || responseItem.kind === 'prepareToolInvocation') { analysis.toolCalls.total++; + const toolName = responseItem.toolId || responseItem.toolName || responseItem.invocationMessage?.toolName || responseItem.toolSpecificData?.kind || 'unknown'; analysis.toolCalls.byTool[toolName] = (analysis.toolCalls.byTool[toolName] || 0) + 1; } - - // Analyze all context references from this request - this.analyzeRequestContext(request, analysis.contextReferences); - - // Extract tool calls from request.response array (when full request is added) - if (request.response && Array.isArray(request.response)) { - for (const responseItem of request.response) { - if (responseItem.kind === 'toolInvocationSerialized' || responseItem.kind === 'prepareToolInvocation') { - analysis.toolCalls.total++; - const toolName = responseItem.toolId || responseItem.toolName || responseItem.invocationMessage?.toolName || responseItem.toolSpecificData?.kind || 'unknown'; - analysis.toolCalls.byTool[toolName] = (analysis.toolCalls.byTool[toolName] || 0) + 1; - } - } - } - } - } - - // Handle VS Code incremental format - tool invocations in responses - if (event.kind === 2 && event.k?.includes('response') && Array.isArray(event.v)) { - for (const responseItem of event.v) { - if (responseItem.kind === 'toolInvocationSerialized') { - analysis.toolCalls.total++; - const toolName = responseItem.toolId || responseItem.toolName || responseItem.invocationMessage?.toolName || responseItem.toolSpecificData?.kind || 'unknown'; - analysis.toolCalls.byTool[toolName] = (analysis.toolCalls.byTool[toolName] || 0) + 1; } } } - // Handle Copilot CLI format + // Calculate model switching for delta-based JSONL files + await this.calculateModelSwitching(sessionFile, analysis); + return analysis; + } + + // Non-delta JSONL (Copilot CLI format) - process line-by-line + for (const line of lines) { + if (!line.trim()) { continue; } + try { + const event = JSON.parse(line); + + // Handle Copilot CLI format // Detect mode from event type - CLI can be chat or agent mode if (event.type === 'user.message') { analysis.modeUsage.ask++; @@ -2519,10 +2512,86 @@ class CopilotTokenTracker implements vscode.Disposable { // Handle .jsonl files OR .json files with JSONL content (Copilot CLI format and VS Code incremental format) const isJsonlContent = sessionFile.endsWith('.jsonl') || this.isJsonlContent(fileContent); if (isJsonlContent) { - const lines = fileContent.trim().split('\n'); + const lines = fileContent.trim().split('\n').filter(l => l.trim()); const timestamps: number[] = []; const allContentReferences: any[] = []; // Collect for repository extraction + // Detect if this is delta-based format (VS Code incremental) + let isDeltaBased = false; + if (lines.length > 0) { + try { + const firstLine = JSON.parse(lines[0]); + if (firstLine && typeof firstLine.kind === 'number') { + isDeltaBased = true; + } + } catch { + // Not delta format + } + } + + if (isDeltaBased) { + // Delta-based format: reconstruct full state first, then extract details + let sessionState: any = {}; + for (const line of lines) { + try { + const delta = JSON.parse(line); + sessionState = this.applyDelta(sessionState, delta); + } catch { + // Skip invalid lines + } + } + + // Extract session metadata from reconstructed state + if (sessionState.creationDate) { + timestamps.push(sessionState.creationDate); + } + if (sessionState.customTitle) { + details.title = sessionState.customTitle; + } + + // Process reconstructed requests array + const requests = sessionState.requests || []; + details.interactions = requests.length; + + for (const request of requests) { + if (!request) { continue; } + + if (request.timestamp) { + timestamps.push(request.timestamp); + } + + // Analyze all context references from this request (unified method) + this.analyzeRequestContext(request, details.contextReferences); + + // Collect contentReferences for repository extraction + if (request.contentReferences && Array.isArray(request.contentReferences)) { + allContentReferences.push(...request.contentReferences); + } + } + + if (timestamps.length > 0) { + timestamps.sort((a, b) => a - b); + details.firstInteraction = new Date(timestamps[0]).toISOString(); + const lastTimestamp = new Date(timestamps[timestamps.length - 1]); + details.lastInteraction = lastTimestamp > stat.mtime + ? lastTimestamp.toISOString() + : stat.mtime.toISOString(); + } else { + details.lastInteraction = stat.mtime.toISOString(); + } + + // Extract repository from collected contentReferences + if (allContentReferences.length > 0) { + details.repository = await this.extractRepositoryFromContentReferences(allContentReferences); + } + + // Update cache with the details we just collected + await this.updateCacheWithSessionDetails(sessionFile, stat, details); + + return details; + } + + // Non-delta JSONL (Copilot CLI format) - process line-by-line for (const line of lines) { if (!line.trim()) { continue; } try { @@ -2539,46 +2608,6 @@ class CopilotTokenTracker implements vscode.Disposable { this.analyzeContextReferences(event.data.content, details.contextReferences); } } - - // Handle VS Code incremental .jsonl format (kind: 0, 1, 2) - // kind: 0 = session header with creationDate - // kind: 2 = requests array with timestamps - if (event.kind === 0 && event.v) { - // Session creation timestamp - if (event.v.creationDate) { - timestamps.push(event.v.creationDate); - } - // Session title - always update to get LAST title (matches VS Code UI) - if (event.v.customTitle) { - details.title = event.v.customTitle; - } - } - - if (event.kind === 2 && event.k?.[0] === 'requests' && Array.isArray(event.v)) { - // New requests being added - count interactions and extract timestamps - for (const request of event.v) { - if (request.requestId) { - details.interactions++; - } - if (request.timestamp) { - timestamps.push(request.timestamp); - } - // Analyze context references in request message - if (request.message?.text) { - this.analyzeContextReferences(request.message.text, details.contextReferences); - } - // Collect contentReferences for repository extraction - if (request.contentReferences && Array.isArray(request.contentReferences)) { - allContentReferences.push(...request.contentReferences); - } - - } - } - - // Check kind: 1 (value updates) for title changes - if (event.kind === 1 && event.k?.includes('customTitle') && event.v) { - details.title = event.v; - } } catch { // Skip malformed lines } From db3e6956a1183cd89e70d6e6124b6479e7f1574b Mon Sep 17 00:00:00 2001 From: Rob Bos Date: Sun, 8 Feb 2026 20:15:29 +0100 Subject: [PATCH 8/8] Update debug logging --- .github/copilot-instructions.md | 47 ++++++++++++++++++++++++--------- src/extension.ts | 1 - 2 files changed, 35 insertions(+), 13 deletions(-) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index f7fb6d2..2930044 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -38,26 +38,49 @@ The entire extension's logic is contained within the `CopilotTokenTracker` class ## Logging Best Practices -**CRITICAL**: Do NOT add debug logging statements like `log('[DEBUG] message')` for troubleshooting during development. This approach has been found to interfere with the output channel and can hide existing log messages from appearing. +**CRITICAL**: Do NOT add debug logging statements like `this.log('[DEBUG] message')` or `console.log('[DEBUG] ...')` for troubleshooting during development. This approach has been found to flood the output channel and cause messages to disappear. -**Root Cause**: The issue occurs when webview code (diagnostics panel, etc.) uses `console.log` statements with DEBUG prefixes. These logs are written to the browser console (Developer Tools) of the webview, not to the extension's output channel. When clearing the cache or performing other operations, if there are DEBUG console.log statements in the webview code, they don't affect the output channel directly. However, the pattern of using DEBUG prefixes in webviews was removed to maintain consistency with the extension's logging guidelines and avoid confusion. +### Why DEBUG Logs Are Problematic -**The Difference**: -- **Extension logging** (`src/extension.ts`): Uses `this.outputChannel.appendLine()` to write to VS Code's Output Channel, which is visible in the Output panel. -- **Webview logging** (`src/webview/*/main.ts`): Uses `console.log()` to write to the browser console, which is only visible when you open Developer Tools in the webview. +**Extension Host Flooding**: When DEBUG log statements are added to frequently-called methods in `extension.ts` (e.g., cache lookups, file processing loops, webview message handlers), they can generate hundreds of log entries per operation. VS Code's OutputChannel has a buffer limit, and excessive logging causes older messages to be pushed out and lost. This was observed when: +- Cache hit/miss logging was added to session file processing +- JSONL content reference counting was logged for each file +- Webview message handlers logged every incoming message with full JSON payloads +- Session data was logged with repository counts on each webview update -When you clear the output channel using `outputChannel.clear()` or similar operations, it only affects the extension's output channel, not the webview's browser console. The two are separate logging systems. +**Symptom**: After operations like clearing the cache, expected log messages would disappear from the Output panel because they were pushed out of the buffer by DEBUG logs. -- **Use Existing Logs**: The extension already has comprehensive logging throughout. Review existing log statements to understand what's being tracked. -- **Minimal Logging**: Only add logging if absolutely necessary for a new feature. Keep messages concise and informative. -- **Remove Debug Logs**: Any temporary debug logging added during development MUST be removed before committing code. -- **Log Methods**: Use the appropriate method for the severity: +### Extension vs Webview Logging + +These are two completely separate logging systems: + +| Context | Method | Destination | Visibility | +|---------|--------|-------------|------------| +| Extension (`src/extension.ts`) | `this.log()`, `this.warn()`, `this.error()` | VS Code Output Channel | Output panel → "Copilot Token Tracker" | +| Webview (`src/webview/*/main.ts`) | `console.log()` | Browser DevTools | Help → Toggle Developer Tools in webview | + +- Clearing the output channel (`outputChannel.clear()`) does NOT affect webview console logs +- Webview console.log statements do NOT appear in the Output panel +- DEBUG prefixes in webviews were removed to maintain consistency with extension guidelines + +### Best Practices + +- **Use Existing Logs**: The extension already has comprehensive logging. Review existing log statements before adding new ones. +- **Minimal Logging**: Only add logging if absolutely necessary for a new feature. Keep messages concise. +- **Remove Debug Logs**: Any temporary debug logging added during development MUST be removed before committing. +- **Log Methods**: Use appropriate severity: - `log(message)` - Standard informational messages - `warn(message)` - Warnings or recoverable errors - `error(message)` - Critical errors -- **No Debug Prefixes**: Avoid prefixing messages with `[DEBUG]` or similar markers. The log output is already timestamped and categorized. +- **No Debug Prefixes**: Avoid `[DEBUG]` markers. The log output is already timestamped. +- **Avoid High-Frequency Logging**: Never log inside loops that process many items (files, sessions, cache entries). + +### Debugging Without Logs -If you need to troubleshoot execution flow, prefer using VS Code's debugger with breakpoints rather than adding log statements. +Prefer VS Code's debugger with breakpoints rather than adding log statements: +1. Press `F5` to launch Extension Development Host +2. Set breakpoints in `src/extension.ts` +3. Use the Debug Console to inspect variables ## Key Files & Conventions diff --git a/src/extension.ts b/src/extension.ts index 665a4c4..fec91b6 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -4390,7 +4390,6 @@ class CopilotTokenTracker implements vscode.Disposable { // Handle messages from the webview this.diagnosticsPanel.webview.onDidReceiveMessage(async (message) => { - this.log(`DEBUG Diagnostics webview message: ${JSON.stringify(message)}`); switch (message.command) { case 'copyReport': await vscode.env.clipboard.writeText(this.lastDiagnosticReport);