From b50d066fbb35775b44d355d2d79e246b184cbb82 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 30 Jan 2026 19:54:59 +0000 Subject: [PATCH 1/5] Initial plan From 0e7230f35caac170654b47e7e5000108c394cf83 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 30 Jan 2026 19:58:03 +0000 Subject: [PATCH 2/5] Add tab state persistence to diagnostics view Co-authored-by: rajbos <6085745+rajbos@users.noreply.github.com> --- src/webview/diagnostics/main.ts | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/src/webview/diagnostics/main.ts b/src/webview/diagnostics/main.ts index f97e77e..3cab36b 100644 --- a/src/webview/diagnostics/main.ts +++ b/src/webview/diagnostics/main.ts @@ -39,7 +39,11 @@ type DiagnosticsData = { cacheInfo?: CacheInfo; }; -declare function acquireVsCodeApi(): { +type DiagnosticsViewState = { + activeTab?: string; +}; + +declare function acquireVsCodeApi(): { postMessage: (message: unknown) => void; setState: (newState: TState) => void; getState: () => TState | undefined; @@ -49,7 +53,7 @@ declare global { interface Window { __INITIAL_DIAGNOSTICS__?: DiagnosticsData; } } -const vscode = acquireVsCodeApi(); +const vscode = acquireVsCodeApi(); const initialData = window.__INITIAL_DIAGNOSTICS__; // Sorting and filtering state @@ -783,6 +787,11 @@ function renderLayout(data: DiagnosticsData): void { document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active')); const content = document.getElementById(`tab-${tabId}`); if (content) { content.classList.add('active'); } + + // Save the active tab state + if (tabId) { + vscode.setState({ activeTab: tabId }); + } }); }); @@ -934,6 +943,23 @@ function renderLayout(data: DiagnosticsData): void { setupSortHandlers(); setupEditorFilterHandlers(); setupFileLinks(); + + // Restore active tab from saved state + const savedState = vscode.getState(); + if (savedState?.activeTab) { + const tabToActivate = document.querySelector(`.tab[data-tab="${savedState.activeTab}"]`); + const contentToActivate = document.getElementById(`tab-${savedState.activeTab}`); + + if (tabToActivate && contentToActivate) { + // Remove active class from all tabs and contents + document.querySelectorAll('.tab').forEach(t => t.classList.remove('active')); + document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active')); + + // Activate the saved tab + tabToActivate.classList.add('active'); + contentToActivate.classList.add('active'); + } + } } async function bootstrap(): Promise { From bef17a8da391a0765ca3fae69573bb014041a2e9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 30 Jan 2026 20:02:25 +0000 Subject: [PATCH 3/5] Refactor: Extract tab activation into helper function Co-authored-by: rajbos <6085745+rajbos@users.noreply.github.com> --- src/webview/diagnostics/main.ts | 44 ++++++++++++++++----------------- 1 file changed, 21 insertions(+), 23 deletions(-) diff --git a/src/webview/diagnostics/main.ts b/src/webview/diagnostics/main.ts index 3cab36b..db2f524 100644 --- a/src/webview/diagnostics/main.ts +++ b/src/webview/diagnostics/main.ts @@ -774,22 +774,31 @@ function renderLayout(data: DiagnosticsData): void { } }); + // Helper function to activate a tab by its ID + function activateTab(tabId: string): boolean { + const tabButton = document.querySelector(`.tab[data-tab="${tabId}"]`); + const tabContent = document.getElementById(`tab-${tabId}`); + + if (tabButton && tabContent) { + // Remove active class from all tabs and contents + document.querySelectorAll('.tab').forEach(t => t.classList.remove('active')); + document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active')); + + // Activate the specified tab + tabButton.classList.add('active'); + tabContent.classList.add('active'); + return true; + } + return false; + } + // Wire up tab switching document.querySelectorAll('.tab').forEach(tab => { tab.addEventListener('click', () => { const tabId = (tab as HTMLElement).getAttribute('data-tab'); - // Update active tab - document.querySelectorAll('.tab').forEach(t => t.classList.remove('active')); - tab.classList.add('active'); - - // Update active content - document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active')); - const content = document.getElementById(`tab-${tabId}`); - if (content) { content.classList.add('active'); } - - // Save the active tab state - if (tabId) { + if (tabId && activateTab(tabId)) { + // Save the active tab state vscode.setState({ activeTab: tabId }); } }); @@ -947,18 +956,7 @@ function renderLayout(data: DiagnosticsData): void { // Restore active tab from saved state const savedState = vscode.getState(); if (savedState?.activeTab) { - const tabToActivate = document.querySelector(`.tab[data-tab="${savedState.activeTab}"]`); - const contentToActivate = document.getElementById(`tab-${savedState.activeTab}`); - - if (tabToActivate && contentToActivate) { - // Remove active class from all tabs and contents - document.querySelectorAll('.tab').forEach(t => t.classList.remove('active')); - document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active')); - - // Activate the saved tab - tabToActivate.classList.add('active'); - contentToActivate.classList.add('active'); - } + activateTab(savedState.activeTab); } } From cb3b5025a9b58951e672c8da1ab3c758ab80bf2f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 30 Jan 2026 20:04:02 +0000 Subject: [PATCH 4/5] Add fallback to default tab if saved tab doesn't exist Co-authored-by: rajbos <6085745+rajbos@users.noreply.github.com> --- src/webview/diagnostics/main.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/webview/diagnostics/main.ts b/src/webview/diagnostics/main.ts index db2f524..eb8e2f1 100644 --- a/src/webview/diagnostics/main.ts +++ b/src/webview/diagnostics/main.ts @@ -953,10 +953,11 @@ function renderLayout(data: DiagnosticsData): void { setupEditorFilterHandlers(); setupFileLinks(); - // Restore active tab from saved state + // Restore active tab from saved state, with fallback to default const savedState = vscode.getState(); - if (savedState?.activeTab) { - activateTab(savedState.activeTab); + if (savedState?.activeTab && !activateTab(savedState.activeTab)) { + // If saved tab doesn't exist (e.g., structure changed), activate default "report" tab + activateTab('report'); } } From 2af268d15e1a45fc257f16d27239183ba3699865 Mon Sep 17 00:00:00 2001 From: Rob Bos Date: Sat, 31 Jan 2026 21:11:14 +0100 Subject: [PATCH 5/5] prevent constantly reloading the sessions file tab content on navigation --- .../azure-storage-loader/package-lock.json | 617 ++++++++++++++++++ src/extension.ts | 93 ++- src/webview/diagnostics/main.ts | 16 + 3 files changed, 721 insertions(+), 5 deletions(-) create mode 100644 .github/skills/azure-storage-loader/package-lock.json diff --git a/.github/skills/azure-storage-loader/package-lock.json b/.github/skills/azure-storage-loader/package-lock.json new file mode 100644 index 0000000..ed1b0b4 --- /dev/null +++ b/.github/skills/azure-storage-loader/package-lock.json @@ -0,0 +1,617 @@ +{ + "name": "azure-storage-loader", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "azure-storage-loader", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "@azure/data-tables": "^13.3.2", + "@azure/identity": "^4.13.0" + } + }, + "node_modules/@azure/abort-controller": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.2.tgz", + "integrity": "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@azure/core-auth": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/@azure/core-auth/-/core-auth-1.10.1.tgz", + "integrity": "sha512-ykRMW8PjVAn+RS6ww5cmK9U2CyH9p4Q88YJwvUslfuMmN98w/2rdGRLPqJYObapBCdzBVeDgYWdJnFPFb7qzpg==", + "license": "MIT", + "dependencies": { + "@azure/abort-controller": "^2.1.2", + "@azure/core-util": "^1.13.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@azure/core-client": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/@azure/core-client/-/core-client-1.10.1.tgz", + "integrity": "sha512-Nh5PhEOeY6PrnxNPsEHRr9eimxLwgLlpmguQaHKBinFYA/RU9+kOYVOQqOrTsCL+KSxrLLl1gD8Dk5BFW/7l/w==", + "license": "MIT", + "dependencies": { + "@azure/abort-controller": "^2.1.2", + "@azure/core-auth": "^1.10.0", + "@azure/core-rest-pipeline": "^1.22.0", + "@azure/core-tracing": "^1.3.0", + "@azure/core-util": "^1.13.0", + "@azure/logger": "^1.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@azure/core-paging": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/@azure/core-paging/-/core-paging-1.6.2.tgz", + "integrity": "sha512-YKWi9YuCU04B55h25cnOYZHxXYtEvQEbKST5vqRga7hWY9ydd3FZHdeQF8pyh+acWZvppw13M/LMGx0LABUVMA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@azure/core-rest-pipeline": { + "version": "1.22.2", + "resolved": "https://registry.npmjs.org/@azure/core-rest-pipeline/-/core-rest-pipeline-1.22.2.tgz", + "integrity": "sha512-MzHym+wOi8CLUlKCQu12de0nwcq9k9Kuv43j4Wa++CsCpJwps2eeBQwD2Bu8snkxTtDKDx4GwjuR9E8yC8LNrg==", + "license": "MIT", + "dependencies": { + "@azure/abort-controller": "^2.1.2", + "@azure/core-auth": "^1.10.0", + "@azure/core-tracing": "^1.3.0", + "@azure/core-util": "^1.13.0", + "@azure/logger": "^1.3.0", + "@typespec/ts-http-runtime": "^0.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@azure/core-tracing": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@azure/core-tracing/-/core-tracing-1.3.1.tgz", + "integrity": "sha512-9MWKevR7Hz8kNzzPLfX4EAtGM2b8mr50HPDBvio96bURP/9C+HjdH3sBlLSNNrvRAr5/k/svoH457gB5IKpmwQ==", + "license": "MIT", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@azure/core-util": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/@azure/core-util/-/core-util-1.13.1.tgz", + "integrity": "sha512-XPArKLzsvl0Hf0CaGyKHUyVgF7oDnhKoP85Xv6M4StF/1AhfORhZudHtOyf2s+FcbuQ9dPRAjB8J2KvRRMUK2A==", + "license": "MIT", + "dependencies": { + "@azure/abort-controller": "^2.1.2", + "@typespec/ts-http-runtime": "^0.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@azure/core-xml": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@azure/core-xml/-/core-xml-1.5.0.tgz", + "integrity": "sha512-D/sdlJBMJfx7gqoj66PKVmhDDaU6TKA49ptcolxdas29X7AfvLTmfAGLjAcIMBK7UZ2o4lygHIqVckOlQU3xWw==", + "license": "MIT", + "dependencies": { + "fast-xml-parser": "^5.0.7", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@azure/data-tables": { + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/@azure/data-tables/-/data-tables-13.3.2.tgz", + "integrity": "sha512-PZ8e4SnCpTQEbQ1P+CK6NR7Vhb86Jw1S1qJi2IcF1ij4qiPX2b4vIemwNPkYg/gZGMqKbxkPvGRpRmEbBYdXuA==", + "license": "MIT", + "dependencies": { + "@azure/core-auth": "^1.9.0", + "@azure/core-client": "^1.9.2", + "@azure/core-paging": "^1.6.2", + "@azure/core-rest-pipeline": "^1.19.0", + "@azure/core-tracing": "^1.2.0", + "@azure/core-util": "^1.11.0", + "@azure/core-xml": "^1.4.4", + "@azure/logger": "^1.1.4", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@azure/identity": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@azure/identity/-/identity-4.13.0.tgz", + "integrity": "sha512-uWC0fssc+hs1TGGVkkghiaFkkS7NkTxfnCH+Hdg+yTehTpMcehpok4PgUKKdyCH+9ldu6FhiHRv84Ntqj1vVcw==", + "license": "MIT", + "dependencies": { + "@azure/abort-controller": "^2.0.0", + "@azure/core-auth": "^1.9.0", + "@azure/core-client": "^1.9.2", + "@azure/core-rest-pipeline": "^1.17.0", + "@azure/core-tracing": "^1.0.0", + "@azure/core-util": "^1.11.0", + "@azure/logger": "^1.0.0", + "@azure/msal-browser": "^4.2.0", + "@azure/msal-node": "^3.5.0", + "open": "^10.1.0", + "tslib": "^2.2.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@azure/logger": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@azure/logger/-/logger-1.3.0.tgz", + "integrity": "sha512-fCqPIfOcLE+CGqGPd66c8bZpwAji98tZ4JI9i/mlTNTlsIWslCfpg48s/ypyLxZTump5sypjrKn2/kY7q8oAbA==", + "license": "MIT", + "dependencies": { + "@typespec/ts-http-runtime": "^0.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@azure/msal-browser": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@azure/msal-browser/-/msal-browser-4.28.1.tgz", + "integrity": "sha512-al2u2fTchbClq3L4C1NlqLm+vwKfhYCPtZN2LR/9xJVaQ4Mnrwf5vANvuyPSJHcGvw50UBmhuVmYUAhTEetTpA==", + "license": "MIT", + "dependencies": { + "@azure/msal-common": "15.14.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@azure/msal-common": { + "version": "15.14.1", + "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-15.14.1.tgz", + "integrity": "sha512-IkzF7Pywt6QKTS0kwdCv/XV8x8JXknZDvSjj/IccooxnP373T5jaadO3FnOrbWo3S0UqkfIDyZNTaQ/oAgRdXw==", + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@azure/msal-node": { + "version": "3.8.6", + "resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-3.8.6.tgz", + "integrity": "sha512-XTmhdItcBckcVVTy65Xp+42xG4LX5GK+9AqAsXPXk4IqUNv+LyQo5TMwNjuFYBfAB2GTG9iSQGk+QLc03vhf3w==", + "license": "MIT", + "dependencies": { + "@azure/msal-common": "15.14.1", + "jsonwebtoken": "^9.0.0", + "uuid": "^8.3.0" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@typespec/ts-http-runtime": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@typespec/ts-http-runtime/-/ts-http-runtime-0.3.2.tgz", + "integrity": "sha512-IlqQ/Gv22xUC1r/WQm4StLkYQmaaTsXAhUVsNE0+xiyf0yRFiH5++q78U3bw6bLKDCTmh0uqKB9eG9+Bt75Dkg==", + "license": "MIT", + "dependencies": { + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, + "node_modules/bundle-name": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", + "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", + "license": "MIT", + "dependencies": { + "run-applescript": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/default-browser": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.4.0.tgz", + "integrity": "sha512-XDuvSq38Hr1MdN47EDvYtx3U0MTqpCEn+F6ft8z2vYDzMrvQhVp0ui9oQdqW3MvK3vqUETglt1tVGgjLuJ5izg==", + "license": "MIT", + "dependencies": { + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.1.tgz", + "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/fast-xml-parser": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.3.4.tgz", + "integrity": "sha512-EFd6afGmXlCx8H8WTZHhAoDaWaGyuIBoZJ2mknrNxug+aZKjkp0a0dlars9Izl+jF+7Gu1/5f/2h68cQpe0IiA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "strnum": "^2.1.0" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "license": "MIT", + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-wsl": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", + "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", + "license": "MIT", + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jsonwebtoken": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz", + "integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==", + "license": "MIT", + "dependencies": { + "jws": "^4.0.1", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", + "license": "MIT", + "dependencies": { + "jwa": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "license": "MIT" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "license": "MIT" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "license": "MIT" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "license": "MIT" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "license": "MIT" + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "license": "MIT" + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/open": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/open/-/open-10.2.0.tgz", + "integrity": "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==", + "license": "MIT", + "dependencies": { + "default-browser": "^5.2.1", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "wsl-utils": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/run-applescript": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz", + "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/strnum": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.1.2.tgz", + "integrity": "sha512-l63NF9y/cLROq/yqKXSLtcMeeyOfnSQlfMSlzFt/K73oIaD8DGaQWd7Z34X9GPiKqP5rbSh84Hl4bOlLcjiSrQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/wsl-utils": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.1.0.tgz", + "integrity": "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==", + "license": "MIT", + "dependencies": { + "is-wsl": "^3.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/src/extension.ts b/src/extension.ts index 62558c6..bffac67 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -176,6 +176,10 @@ interface SessionLogData { class CopilotTokenTracker implements vscode.Disposable { private diagnosticsPanel?: vscode.WebviewPanel; + // Tracks whether the diagnostics panel has already received its session files + private diagnosticsHasLoadedFiles: boolean = false; + // Cache of the last loaded detailed session files for diagnostics view + private diagnosticsCachedFiles: SessionFileDetails[] = []; private logViewerPanel?: vscode.WebviewPanel; private statusBarItem: vscode.StatusBarItem; private readonly extensionUri: vscode.Uri; @@ -370,7 +374,10 @@ class CopilotTokenTracker implements vscode.Disposable { const cacheSize = this.sessionFileCache.size; this.sessionFileCache.clear(); await this.context.globalState.update('sessionFileCache', undefined); - + // Reset diagnostics loaded flag so the diagnostics view will reload files + this.diagnosticsHasLoadedFiles = false; + this.diagnosticsCachedFiles = []; + this.log(`Cache cleared successfully. Removed ${cacheSize} entries.`); vscode.window.showInformationMessage('Cache cleared successfully. Reloading statistics...'); @@ -3121,8 +3128,38 @@ class CopilotTokenTracker implements vscode.Disposable { dirCounts.set(editorRoot, (dirCounts.get(editorRoot) || 0) + 1); } const sessionFolders = Array.from(dirCounts.entries()).map(([dir, count]) => ({ dir, count, editorName: this.getEditorTypeFromPath(dir) })); - this.diagnosticsPanel.webview.html = this.getDiagnosticReportHtml(this.diagnosticsPanel.webview, report, sessionFileData, [], sessionFolders); - this.loadSessionFilesInBackground(this.diagnosticsPanel, sessionFiles); + // Update the existing diagnostics webview without replacing its HTML + try { + await this.diagnosticsPanel.webview.postMessage({ + command: 'updateDiagnosticsReport', + report, + sessionFileData, + sessionFolders + }); + this.log('Diagnostics webview updated via postMessage'); + } catch (err) { + this.log('Could not post update to diagnostics webview, falling back to reset'); + this.log(err instanceof Error ? err.message : String(err)); + this.diagnosticsPanel.webview.html = this.getDiagnosticReportHtml(this.diagnosticsPanel.webview, report, sessionFileData, [], sessionFolders); + } + // If we have previously loaded session files, re-send them to the webview + if (this.diagnosticsHasLoadedFiles && this.diagnosticsCachedFiles.length > 0) { + try { + await this.diagnosticsPanel.webview.postMessage({ + command: 'sessionFilesLoaded', + detailedSessionFiles: this.diagnosticsCachedFiles + }); + this.log('Re-sent cached diagnostics session files to webview'); + } catch (err) { + this.log('Could not re-send cached session files to diagnostics webview'); + this.log(err instanceof Error ? err.message : String(err)); + } + } else { + // Only reload session files in background if we haven't already loaded them + if (!this.diagnosticsHasLoadedFiles) { + this.loadSessionFilesInBackground(this.diagnosticsPanel, sessionFiles); + } + } return; } @@ -3169,7 +3206,7 @@ class CopilotTokenTracker implements vscode.Disposable { }, { enableScripts: true, - retainContextWhenHidden: false, // Match other panels to avoid output channel issues + retainContextWhenHidden: true, // Keep webview context to avoid reloading session files localResourceRoots: [vscode.Uri.joinPath(this.extensionUri, 'dist', 'webview')] } ); @@ -3319,11 +3356,19 @@ class CopilotTokenTracker implements vscode.Disposable { // Send the loaded data to the webview try { + // Cache the loaded session files so we can re-send if the webview is recreated + if (panel === this.diagnosticsPanel) { + this.diagnosticsCachedFiles = detailedSessionFiles; + } await panel.webview.postMessage({ command: 'sessionFilesLoaded', detailedSessionFiles }); this.log(`Loaded ${detailedSessionFiles.length} session files in background`); + // Mark diagnostics as loaded so we don't reload unnecessarily + if (panel === this.diagnosticsPanel) { + this.diagnosticsHasLoadedFiles = true; + } } catch (err) { // Panel may have been disposed this.log('Could not send session files to panel (may be closed)'); @@ -3359,11 +3404,49 @@ class CopilotTokenTracker implements vscode.Disposable { cacheSizeInMB = 0; } + // Try to read the persisted cache from VS Code global state to show its actual storage status + let persistedCacheSummary = 'Not found in VS Code Global State'; + try { + const persisted = this.context.globalState.get>('sessionFileCache'); + if (persisted && typeof persisted === 'object') { + const count = Object.keys(persisted).length; + persistedCacheSummary = `VS Code Global State - sessionFileCache (${count} entr${count === 1 ? 'y' : 'ies'})`; + } + } catch (e) { + persistedCacheSummary = 'Error reading VS Code Global State'; + } + + // Try to locate the actual storage file (state DB) for the extension global state + let storageFilePath: string | null = null; + try { + const extensionId = 'RobBos.copilot-token-tracker'; + const userPaths = this.getVSCodeUserPaths(); + for (const userPath of userPaths) { + try { + const candidate = path.join(userPath, 'globalStorage', extensionId); + if (fs.existsSync(candidate)) { + const files = fs.readdirSync(candidate); + // Look for likely state files + const match = files.find(f => f.includes('state') || f.endsWith('.vscdb') || f.endsWith('.json')); + if (match) { + storageFilePath = path.join(candidate, match); + break; + } + } + } catch (e) { + // ignore path access errors + } + } + } catch (e) { + // ignore + } + const cacheInfo = { size: this.sessionFileCache.size, sizeInMB: cacheSizeInMB, lastUpdated: this.sessionFileCache.size > 0 ? new Date().toISOString() : null, - location: 'VS Code Global State - extension.globalState.get sessionFileCache' + location: persistedCacheSummary, + storagePath: storageFilePath }; const initialData = JSON.stringify({ report, sessionFiles, detailedSessionFiles, sessionFolders, cacheInfo }).replace(/Storage Location
${escapeHtml(data.cacheInfo?.location || 'VS Code Global State')} + ${data.cacheInfo?.storagePath ? ` Open storage location` : ''}

Cache is stored in VS Code's global state (extension storage) and includes: @@ -774,6 +776,19 @@ function renderLayout(data: DiagnosticsData): void { } }); +// Handle open storage link clicks +function setupStorageLinkHandlers(): void { + document.querySelectorAll('.open-storage-link').forEach(link => { + link.addEventListener('click', (e) => { + e.preventDefault(); + const path = decodeURIComponent((link as HTMLElement).getAttribute('data-path') || ''); + if (path) { + vscode.postMessage({ command: 'revealPath', path }); + } + }); + }); +} + // Helper function to activate a tab by its ID function activateTab(tabId: string): boolean { const tabButton = document.querySelector(`.tab[data-tab="${tabId}"]`); @@ -952,6 +967,7 @@ function renderLayout(data: DiagnosticsData): void { setupSortHandlers(); setupEditorFilterHandlers(); setupFileLinks(); + setupStorageLinkHandlers(); // Restore active tab from saved state, with fallback to default const savedState = vscode.getState();