@hienlh/ppm 0.13.3 → 0.13.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +22 -0
- package/assets/skills/ppm/SKILL.md +1 -1
- package/assets/skills/ppm/references/http-api.md +1 -1
- package/dist/web/assets/{ai-settings-section-QE6nBNgN.js → ai-settings-section-DeW4WN43.js} +1 -1
- package/dist/web/assets/{api-settings-DAk7D-NP.js → api-settings-t7Leca7J.js} +1 -1
- package/dist/web/assets/architecture-PBZL5I3N-Dy3PgD6O.js +1 -0
- package/dist/web/assets/{audio-preview-R7cq1uhJ.js → audio-preview-BSAe2WQB.js} +1 -1
- package/dist/web/assets/chat-tab-UFEFOnpl.js +12 -0
- package/dist/web/assets/code-editor-BJ1tSNWA.js +8 -0
- package/dist/web/assets/{conflict-editor-dzofjxab.js → conflict-editor-CrgrMZ2F.js} +1 -1
- package/dist/web/assets/{csv-preview-HMSavgBb.js → csv-preview-C9qGhDlb.js} +1 -1
- package/dist/web/assets/{database-viewer-5Uf8Rrls.js → database-viewer-e_NAkIL_.js} +2 -2
- package/dist/web/assets/diff-viewer-C2eOczTs.js +4 -0
- package/dist/web/assets/{esm-K1XIK4vc.js → esm-B3je8j5P.js} +1 -1
- package/dist/web/assets/{extension-webview-HILvTnnn.js → extension-webview-B95nOfj-.js} +2 -2
- package/dist/web/assets/{file-store-BrbCNyLm.js → file-store-BgZggznw.js} +1 -1
- package/dist/web/assets/gitGraph-HDMCJU4V-Bu1SIFFq.js +1 -0
- package/dist/web/assets/{image-preview-0cJMnFZK.js → image-preview-DAuPOzYl.js} +1 -1
- package/dist/web/assets/index-DJOjXTcq.js +27 -0
- package/dist/web/assets/index-DSOP0R0s.css +2 -0
- package/dist/web/assets/info-3K5VOQVL-DzfAxmVd.js +1 -0
- package/dist/web/assets/{input-Dk49gO8E.js → input-bGJExpJZ.js} +1 -1
- package/dist/web/assets/keybindings-store-V12kZZHO.js +1 -0
- package/dist/web/assets/{markdown-renderer-D0MrsVJB.js → markdown-renderer-DwINRWo4.js} +3 -3
- package/dist/web/assets/packet-RMMSAZCW-DpzHf4xp.js +1 -0
- package/dist/web/assets/{pdf-preview-BBVDS-z5.js → pdf-preview-CqoQE09t.js} +1 -1
- package/dist/web/assets/pie-UPGHQEXC-BpzFCKJ8.js +1 -0
- package/dist/web/assets/{port-forwarding-tab-ByKzBs-R.js → port-forwarding-tab-De7qxkjp.js} +1 -1
- package/dist/web/assets/{postgres-viewer-BnCbdR7g.js → postgres-viewer-Dd6rLb8b.js} +3 -3
- package/dist/web/assets/radar-KQ55EAFF-DAxWKxM4.js +1 -0
- package/dist/web/assets/{scroll-area-BEllam7_.js → scroll-area-D0EQpAH2.js} +1 -1
- package/dist/web/assets/{settings-store-BLLR7ed8.js → settings-store-CdcSAgEZ.js} +2 -2
- package/dist/web/assets/settings-tab-BdTEumwU.js +1 -0
- package/dist/web/assets/{sql-query-editor-CVAnRFbi.js → sql-query-editor-vpD0I0KG.js} +1 -1
- package/dist/web/assets/sqlite-viewer-Ccz2crvN.js +1 -0
- package/dist/web/assets/{tab-store-B3M9hjho.js → tab-store-Jvy1eZGM.js} +1 -1
- package/dist/web/assets/terminal-tab-D7u7wsyb.js +1 -0
- package/dist/web/assets/treemap-KZPCXAKY-D6dgXbAe.js +1 -0
- package/dist/web/assets/{use-blob-url-e9uTXjv5.js → use-blob-url-BgxxT-n_.js} +1 -1
- package/dist/web/assets/{use-monaco-theme-BkZDwoVd.js → use-monaco-theme-dtPsv6sh.js} +1 -1
- package/dist/web/assets/{vendor-mermaid-Dx86tuVP.js → vendor-mermaid-DCxaaPi4.js} +2 -2
- package/dist/web/assets/{video-preview-CKaht6nI.js → video-preview-BSDzqlzk.js} +1 -1
- package/dist/web/index.html +17 -19
- package/dist/web/sw.js +1 -1
- package/docs/codebase-summary.md +2 -0
- package/docs/journals/2026-04-22-compare-files-feature-ship.md +53 -0
- package/docs/project-changelog.md +9 -1
- package/package.json +1 -1
- package/src/services/file-filter.service.ts +17 -4
- package/src/services/file-list-index.service.ts +7 -3
- package/src/types/project.ts +2 -0
- package/src/web/app.tsx +4 -0
- package/src/web/components/editor/compare-picker.tsx +245 -0
- package/src/web/components/explorer/file-tree.tsx +42 -1
- package/src/web/components/layout/command-palette.tsx +31 -1
- package/src/web/components/layout/draggable-tab.tsx +8 -0
- package/src/web/components/layout/tab-bar.tsx +101 -27
- package/src/web/hooks/use-global-keybindings.ts +20 -0
- package/src/web/lib/open-compare-tab.ts +76 -0
- package/src/web/stores/compare-store.ts +57 -0
- package/src/web/stores/keybindings-store.ts +1 -0
- package/dist/web/assets/architecture-PBZL5I3N-DvZbltvY.js +0 -1
- package/dist/web/assets/chat-tab-umei1UkV.js +0 -12
- package/dist/web/assets/code-editor-BTosKXkr.js +0 -8
- package/dist/web/assets/columns-2-4fQcE4PF.js +0 -1
- package/dist/web/assets/diff-viewer-DKLeIBkK.js +0 -4
- package/dist/web/assets/extension-store-3yZYn07W.js +0 -1
- package/dist/web/assets/gitGraph-HDMCJU4V-BxhdxFgj.js +0 -1
- package/dist/web/assets/index-Bce0weeW.css +0 -2
- package/dist/web/assets/index-DDBvHVVr.js +0 -27
- package/dist/web/assets/info-3K5VOQVL-BwAZ2zd8.js +0 -1
- package/dist/web/assets/keybindings-store-B-zET-0o.js +0 -1
- package/dist/web/assets/keybindings-store-DaBV6qhz.js +0 -1
- package/dist/web/assets/packet-RMMSAZCW-tx2n5Qry.js +0 -1
- package/dist/web/assets/pie-UPGHQEXC-D6S2MqVT.js +0 -1
- package/dist/web/assets/radar-KQ55EAFF-BviZcL-b.js +0 -1
- package/dist/web/assets/settings-tab-BPdzUw3v.js +0 -1
- package/dist/web/assets/sqlite-viewer-D6mSIIx2.js +0 -1
- package/dist/web/assets/terminal-tab-BLIA53mt.js +0 -1
- package/dist/web/assets/treemap-KZPCXAKY-CM54VdaB.js +0 -1
- /package/dist/web/assets/{api-client-Dvzcc_EO.js → api-client-r4nyVy7H.js} +0 -0
- /package/dist/web/assets/{csv-parser--2WJNgS7.js → csv-parser-DxVplKKB.js} +0 -0
- /package/dist/web/assets/{database-D4DIhgi-.js → database-DCT0OjgQ.js} +0 -0
- /package/dist/web/assets/{dist-im4ynINo.js → dist-BqoEabX7.js} +0 -0
- /package/dist/web/assets/{file-exclamation-point-BwzaQ50n.js → file-exclamation-point-Baz81y5z.js} +0 -0
- /package/dist/web/assets/{katex-CKoArbIw.js → katex-bpagxk3Z.js} +0 -0
- /package/dist/web/assets/{lib-DQHnkzGy.js → lib-BqkcKGFq.js} +0 -0
- /package/dist/web/assets/{react-GqWghJ-L.js → react-BkWDCPD7.js} +0 -0
- /package/dist/web/assets/{refresh-cw-LlbZDJpO.js → refresh-cw-CSFrDtiu.js} +0 -0
- /package/dist/web/assets/{sql-completion-provider-C3cq9j99.js → sql-completion-provider-EzHOQLfo.js} +0 -0
- /package/dist/web/assets/{table-Dq575bPF.js → table-DbSviOmw.js} +0 -0
- /package/dist/web/assets/{text-wrap-Cn6BNQfq.js → text-wrap-DzvCTq_i.js} +0 -0
- /package/dist/web/assets/{trash-2-CJYoLw7Q.js → trash-2-BgDIBl6f.js} +0 -0
- /package/dist/web/assets/{utils-CTg5uAYR.js → utils-ChWX7pZv.js} +0 -0
- /package/dist/web/assets/{vendor-xterm-CU2c3f0A.js → vendor-xterm-D7SePDJp.js} +0 -0
- /package/dist/web/assets/{x-DlFGzN8d.js → x-BtqbfkR7.js} +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
import{b as e}from"./vendor-markdown-0Mxgxy0L.js";import{t}from"./file-exclamation-point-
|
|
1
|
+
import{b as e}from"./vendor-markdown-0Mxgxy0L.js";import{t}from"./file-exclamation-point-Baz81y5z.js";import"./api-client-r4nyVy7H.js";import{U as n}from"./index-DJOjXTcq.js";import{t as r}from"./use-blob-url-BgxxT-n_.js";var i=e();function a({filePath:e,projectName:a}){let{blobUrl:o,error:s}=r(e,a);return s?(0,i.jsxs)(`div`,{className:`flex flex-col items-center justify-center h-full gap-3 text-text-secondary`,children:[(0,i.jsx)(t,{className:`size-10 text-text-subtle`}),(0,i.jsx)(`p`,{className:`text-sm`,children:`Failed to load video.`})]}):o?(0,i.jsx)(`div`,{className:`flex items-center justify-center h-full p-4 bg-surface overflow-auto`,children:(0,i.jsx)(`video`,{src:o,controls:!0,className:`max-w-full max-h-full`})}):(0,i.jsx)(`div`,{className:`flex items-center justify-center h-full`,children:(0,i.jsx)(n,{className:`size-5 animate-spin text-text-subtle`})})}export{a as VideoPreview};
|
package/dist/web/index.html
CHANGED
|
@@ -39,32 +39,30 @@
|
|
|
39
39
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
40
40
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
41
41
|
<link href="https://fonts.googleapis.com/css2?family=Geist+Mono:wght@400;500;600;700&family=Geist:wght@400;500;600;700&display=swap" rel="stylesheet" />
|
|
42
|
-
<script type="module" crossorigin src="/assets/index-
|
|
42
|
+
<script type="module" crossorigin src="/assets/index-DJOjXTcq.js"></script>
|
|
43
43
|
<link rel="modulepreload" crossorigin href="/assets/rolldown-runtime-FhOqtrmT.js">
|
|
44
|
-
<link rel="modulepreload" crossorigin href="/assets/vendor-mermaid-
|
|
44
|
+
<link rel="modulepreload" crossorigin href="/assets/vendor-mermaid-DCxaaPi4.js">
|
|
45
45
|
<link rel="modulepreload" crossorigin href="/assets/vendor-markdown-0Mxgxy0L.js">
|
|
46
46
|
<link rel="modulepreload" crossorigin href="/assets/vendor-ui-B-89Uj8i.js">
|
|
47
|
-
<link rel="modulepreload" crossorigin href="/assets/utils-
|
|
47
|
+
<link rel="modulepreload" crossorigin href="/assets/utils-ChWX7pZv.js">
|
|
48
48
|
<link rel="modulepreload" crossorigin href="/assets/createLucideIcon-BjHrJDVb.js">
|
|
49
|
-
<link rel="modulepreload" crossorigin href="/assets/x-
|
|
50
|
-
<link rel="modulepreload" crossorigin href="/assets/input-
|
|
51
|
-
<link rel="modulepreload" crossorigin href="/assets/react-
|
|
52
|
-
<link rel="modulepreload" crossorigin href="/assets/api-client-
|
|
53
|
-
<link rel="modulepreload" crossorigin href="/assets/settings-store-
|
|
54
|
-
<link rel="modulepreload" crossorigin href="/assets/scroll-area-
|
|
49
|
+
<link rel="modulepreload" crossorigin href="/assets/x-BtqbfkR7.js">
|
|
50
|
+
<link rel="modulepreload" crossorigin href="/assets/input-bGJExpJZ.js">
|
|
51
|
+
<link rel="modulepreload" crossorigin href="/assets/react-BkWDCPD7.js">
|
|
52
|
+
<link rel="modulepreload" crossorigin href="/assets/api-client-r4nyVy7H.js">
|
|
53
|
+
<link rel="modulepreload" crossorigin href="/assets/settings-store-CdcSAgEZ.js">
|
|
54
|
+
<link rel="modulepreload" crossorigin href="/assets/scroll-area-D0EQpAH2.js">
|
|
55
55
|
<link rel="modulepreload" crossorigin href="/assets/dist-D7KGU7Vl.js">
|
|
56
56
|
<link rel="modulepreload" crossorigin href="/assets/plus-51UQ45rf.js">
|
|
57
|
-
<link rel="modulepreload" crossorigin href="/assets/refresh-cw-
|
|
58
|
-
<link rel="modulepreload" crossorigin href="/assets/trash-2-
|
|
59
|
-
<link rel="modulepreload" crossorigin href="/assets/api-settings-
|
|
60
|
-
<link rel="modulepreload" crossorigin href="/assets/ai-settings-section-
|
|
57
|
+
<link rel="modulepreload" crossorigin href="/assets/refresh-cw-CSFrDtiu.js">
|
|
58
|
+
<link rel="modulepreload" crossorigin href="/assets/trash-2-BgDIBl6f.js">
|
|
59
|
+
<link rel="modulepreload" crossorigin href="/assets/api-settings-t7Leca7J.js">
|
|
60
|
+
<link rel="modulepreload" crossorigin href="/assets/ai-settings-section-DeW4WN43.js">
|
|
61
61
|
<link rel="modulepreload" crossorigin href="/assets/chevron-right-BzAdxJRG.js">
|
|
62
|
-
<link rel="modulepreload" crossorigin href="/assets/database-
|
|
63
|
-
<link rel="modulepreload" crossorigin href="/assets/
|
|
64
|
-
<link rel="modulepreload" crossorigin href="/assets/
|
|
65
|
-
<link rel="
|
|
66
|
-
<link rel="modulepreload" crossorigin href="/assets/tab-store-B3M9hjho.js">
|
|
67
|
-
<link rel="stylesheet" crossorigin href="/assets/index-Bce0weeW.css">
|
|
62
|
+
<link rel="modulepreload" crossorigin href="/assets/database-DCT0OjgQ.js">
|
|
63
|
+
<link rel="modulepreload" crossorigin href="/assets/file-store-BgZggznw.js">
|
|
64
|
+
<link rel="modulepreload" crossorigin href="/assets/tab-store-Jvy1eZGM.js">
|
|
65
|
+
<link rel="stylesheet" crossorigin href="/assets/index-DSOP0R0s.css">
|
|
68
66
|
<link rel="manifest" href="/manifest.webmanifest"><script id="vite-plugin-pwa:register-sw" src="/registerSW.js"></script></head>
|
|
69
67
|
<body class="bg-[#0f1419] text-[#e5e7eb] font-sans antialiased">
|
|
70
68
|
<div id="root"></div>
|
package/dist/web/sw.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
try{self[`workbox:core:7.3.0`]&&_()}catch{}var e=(e,...t)=>{let n=e;return t.length>0&&(n+=` :: ${JSON.stringify(t)}`),n},t=class extends Error{constructor(t,n){let r=e(t,n);super(r),this.name=t,this.details=n}},n={googleAnalytics:`googleAnalytics`,precache:`precache-v2`,prefix:`workbox`,runtime:`runtime`,suffix:typeof registration<`u`?registration.scope:``},r=e=>[n.prefix,e,n.suffix].filter(e=>e&&e.length>0).join(`-`),i=e=>{for(let t of Object.keys(n))e(t)},a={updateDetails:e=>{i(t=>{typeof e[t]==`string`&&(n[t]=e[t])})},getGoogleAnalyticsName:e=>e||r(n.googleAnalytics),getPrecacheName:e=>e||r(n.precache),getPrefix:()=>n.prefix,getRuntimeName:e=>e||r(n.runtime),getSuffix:()=>n.suffix};function o(e,t){let n=t();return e.waitUntil(n),n}try{self[`workbox:precaching:7.3.0`]&&_()}catch{}var s=`__WB_REVISION__`;function c(e){if(!e)throw new t(`add-to-cache-list-unexpected-type`,{entry:e});if(typeof e==`string`){let t=new URL(e,location.href);return{cacheKey:t.href,url:t.href}}let{revision:n,url:r}=e;if(!r)throw new t(`add-to-cache-list-unexpected-type`,{entry:e});if(!n){let e=new URL(r,location.href);return{cacheKey:e.href,url:e.href}}let i=new URL(r,location.href),a=new URL(r,location.href);return i.searchParams.set(s,n),{cacheKey:i.href,url:a.href}}var l=class{constructor(){this.updatedURLs=[],this.notUpdatedURLs=[],this.handlerWillStart=async({request:e,state:t})=>{t&&(t.originalRequest=e)},this.cachedResponseWillBeUsed=async({event:e,state:t,cachedResponse:n})=>{if(e.type===`install`&&t&&t.originalRequest&&t.originalRequest instanceof Request){let e=t.originalRequest.url;n?this.notUpdatedURLs.push(e):this.updatedURLs.push(e)}return n}}},u=class{constructor({precacheController:e}){this.cacheKeyWillBeUsed=async({request:e,params:t})=>{let n=t?.cacheKey||this._precacheController.getCacheKeyForURL(e.url);return n?new Request(n,{headers:e.headers}):e},this._precacheController=e}},d;function f(){if(d===void 0){let e=new Response(``);if(`body`in e)try{new Response(e.body),d=!0}catch{d=!1}d=!1}return d}async function p(e,n){let r=null;if(e.url&&(r=new URL(e.url).origin),r!==self.location.origin)throw new t(`cross-origin-copy-response`,{origin:r});let i=e.clone(),a={headers:new Headers(i.headers),status:i.status,statusText:i.statusText},o=n?n(a):a,s=f()?i.body:await i.blob();return new Response(s,o)}var m=e=>new URL(String(e),location.href).href.replace(RegExp(`^${location.origin}`),``);function h(e,t){let n=new URL(e);for(let e of t)n.searchParams.delete(e);return n.href}async function g(e,t,n,r){let i=h(t.url,n);if(t.url===i)return e.match(t,r);let a=Object.assign(Object.assign({},r),{ignoreSearch:!0}),o=await e.keys(t,a);for(let t of o)if(i===h(t.url,n))return e.match(t,r)}var v=class{constructor(){this.promise=new Promise((e,t)=>{this.resolve=e,this.reject=t})}},y=new Set;async function b(){for(let e of y)await e()}function x(e){return new Promise(t=>setTimeout(t,e))}try{self[`workbox:strategies:7.3.0`]&&_()}catch{}function S(e){return typeof e==`string`?new Request(e):e}var C=class{constructor(e,t){this._cacheKeys={},Object.assign(this,t),this.event=t.event,this._strategy=e,this._handlerDeferred=new v,this._extendLifetimePromises=[],this._plugins=[...e.plugins],this._pluginStateMap=new Map;for(let e of this._plugins)this._pluginStateMap.set(e,{});this.event.waitUntil(this._handlerDeferred.promise)}async fetch(e){let{event:n}=this,r=S(e);if(r.mode===`navigate`&&n instanceof FetchEvent&&n.preloadResponse){let e=await n.preloadResponse;if(e)return e}let i=this.hasCallback(`fetchDidFail`)?r.clone():null;try{for(let e of this.iterateCallbacks(`requestWillFetch`))r=await e({request:r.clone(),event:n})}catch(e){if(e instanceof Error)throw new t(`plugin-error-request-will-fetch`,{thrownErrorMessage:e.message})}let a=r.clone();try{let e;e=await fetch(r,r.mode===`navigate`?void 0:this._strategy.fetchOptions);for(let t of this.iterateCallbacks(`fetchDidSucceed`))e=await t({event:n,request:a,response:e});return e}catch(e){throw i&&await this.runCallbacks(`fetchDidFail`,{error:e,event:n,originalRequest:i.clone(),request:a.clone()}),e}}async fetchAndCachePut(e){let t=await this.fetch(e),n=t.clone();return this.waitUntil(this.cachePut(e,n)),t}async cacheMatch(e){let t=S(e),n,{cacheName:r,matchOptions:i}=this._strategy,a=await this.getCacheKey(t,`read`),o=Object.assign(Object.assign({},i),{cacheName:r});n=await caches.match(a,o);for(let e of this.iterateCallbacks(`cachedResponseWillBeUsed`))n=await e({cacheName:r,matchOptions:i,cachedResponse:n,request:a,event:this.event})||void 0;return n}async cachePut(e,n){let r=S(e);await x(0);let i=await this.getCacheKey(r,`write`);if(!n)throw new t(`cache-put-with-no-response`,{url:m(i.url)});let a=await this._ensureResponseSafeToCache(n);if(!a)return!1;let{cacheName:o,matchOptions:s}=this._strategy,c=await self.caches.open(o),l=this.hasCallback(`cacheDidUpdate`),u=l?await g(c,i.clone(),[`__WB_REVISION__`],s):null;try{await c.put(i,l?a.clone():a)}catch(e){if(e instanceof Error)throw e.name===`QuotaExceededError`&&await b(),e}for(let e of this.iterateCallbacks(`cacheDidUpdate`))await e({cacheName:o,oldResponse:u,newResponse:a.clone(),request:i,event:this.event});return!0}async getCacheKey(e,t){let n=`${e.url} | ${t}`;if(!this._cacheKeys[n]){let r=e;for(let e of this.iterateCallbacks(`cacheKeyWillBeUsed`))r=S(await e({mode:t,request:r,event:this.event,params:this.params}));this._cacheKeys[n]=r}return this._cacheKeys[n]}hasCallback(e){for(let t of this._strategy.plugins)if(e in t)return!0;return!1}async runCallbacks(e,t){for(let n of this.iterateCallbacks(e))await n(t)}*iterateCallbacks(e){for(let t of this._strategy.plugins)if(typeof t[e]==`function`){let n=this._pluginStateMap.get(t);yield r=>{let i=Object.assign(Object.assign({},r),{state:n});return t[e](i)}}}waitUntil(e){return this._extendLifetimePromises.push(e),e}async doneWaiting(){for(;this._extendLifetimePromises.length;){let e=this._extendLifetimePromises.splice(0),t=(await Promise.allSettled(e)).find(e=>e.status===`rejected`);if(t)throw t.reason}}destroy(){this._handlerDeferred.resolve(null)}async _ensureResponseSafeToCache(e){let t=e,n=!1;for(let e of this.iterateCallbacks(`cacheWillUpdate`))if(t=await e({request:this.request,response:t,event:this.event})||void 0,n=!0,!t)break;return n||t&&t.status!==200&&(t=void 0),t}},w=class{constructor(e={}){this.cacheName=a.getRuntimeName(e.cacheName),this.plugins=e.plugins||[],this.fetchOptions=e.fetchOptions,this.matchOptions=e.matchOptions}handle(e){let[t]=this.handleAll(e);return t}handleAll(e){e instanceof FetchEvent&&(e={event:e,request:e.request});let t=e.event,n=typeof e.request==`string`?new Request(e.request):e.request,r=`params`in e?e.params:void 0,i=new C(this,{event:t,request:n,params:r}),a=this._getResponse(i,n,t);return[a,this._awaitComplete(a,i,n,t)]}async _getResponse(e,n,r){await e.runCallbacks(`handlerWillStart`,{event:r,request:n});let i;try{if(i=await this._handle(n,e),!i||i.type===`error`)throw new t(`no-response`,{url:n.url})}catch(t){if(t instanceof Error){for(let a of e.iterateCallbacks(`handlerDidError`))if(i=await a({error:t,event:r,request:n}),i)break}if(!i)throw t}for(let t of e.iterateCallbacks(`handlerWillRespond`))i=await t({event:r,request:n,response:i});return i}async _awaitComplete(e,t,n,r){let i,a;try{i=await e}catch{}try{await t.runCallbacks(`handlerDidRespond`,{event:r,request:n,response:i}),await t.doneWaiting()}catch(e){e instanceof Error&&(a=e)}if(await t.runCallbacks(`handlerDidComplete`,{event:r,request:n,response:i,error:a}),t.destroy(),a)throw a}},T=class e extends w{constructor(t={}){t.cacheName=a.getPrecacheName(t.cacheName),super(t),this._fallbackToNetwork=t.fallbackToNetwork!==!1,this.plugins.push(e.copyRedirectedCacheableResponsesPlugin)}async _handle(e,t){return await t.cacheMatch(e)||(t.event&&t.event.type===`install`?await this._handleInstall(e,t):await this._handleFetch(e,t))}async _handleFetch(e,n){let r,i=n.params||{};if(this._fallbackToNetwork){let t=i.integrity,a=e.integrity,o=!a||a===t;r=await n.fetch(new Request(e,{integrity:e.mode===`no-cors`?void 0:a||t})),t&&o&&e.mode!==`no-cors`&&(this._useDefaultCacheabilityPluginIfNeeded(),await n.cachePut(e,r.clone()))}else throw new t(`missing-precache-entry`,{cacheName:this.cacheName,url:e.url});return r}async _handleInstall(e,n){this._useDefaultCacheabilityPluginIfNeeded();let r=await n.fetch(e);if(!await n.cachePut(e,r.clone()))throw new t(`bad-precaching-response`,{url:e.url,status:r.status});return r}_useDefaultCacheabilityPluginIfNeeded(){let t=null,n=0;for(let[r,i]of this.plugins.entries())i!==e.copyRedirectedCacheableResponsesPlugin&&(i===e.defaultPrecacheCacheabilityPlugin&&(t=r),i.cacheWillUpdate&&n++);n===0?this.plugins.push(e.defaultPrecacheCacheabilityPlugin):n>1&&t!==null&&this.plugins.splice(t,1)}};T.defaultPrecacheCacheabilityPlugin={async cacheWillUpdate({response:e}){return!e||e.status>=400?null:e}},T.copyRedirectedCacheableResponsesPlugin={async cacheWillUpdate({response:e}){return e.redirected?await p(e):e}};var E=class{constructor({cacheName:e,plugins:t=[],fallbackToNetwork:n=!0}={}){this._urlsToCacheKeys=new Map,this._urlsToCacheModes=new Map,this._cacheKeysToIntegrities=new Map,this._strategy=new T({cacheName:a.getPrecacheName(e),plugins:[...t,new u({precacheController:this})],fallbackToNetwork:n}),this.install=this.install.bind(this),this.activate=this.activate.bind(this)}get strategy(){return this._strategy}precache(e){this.addToCacheList(e),this._installAndActiveListenersAdded||=(self.addEventListener(`install`,this.install),self.addEventListener(`activate`,this.activate),!0)}addToCacheList(e){let n=[];for(let r of e){typeof r==`string`?n.push(r):r&&r.revision===void 0&&n.push(r.url);let{cacheKey:e,url:i}=c(r),a=typeof r!=`string`&&r.revision?`reload`:`default`;if(this._urlsToCacheKeys.has(i)&&this._urlsToCacheKeys.get(i)!==e)throw new t(`add-to-cache-list-conflicting-entries`,{firstEntry:this._urlsToCacheKeys.get(i),secondEntry:e});if(typeof r!=`string`&&r.integrity){if(this._cacheKeysToIntegrities.has(e)&&this._cacheKeysToIntegrities.get(e)!==r.integrity)throw new t(`add-to-cache-list-conflicting-integrities`,{url:i});this._cacheKeysToIntegrities.set(e,r.integrity)}if(this._urlsToCacheKeys.set(i,e),this._urlsToCacheModes.set(i,a),n.length>0){let e=`Workbox is precaching URLs without revision info: ${n.join(`, `)}\nThis is generally NOT safe. Learn more at https://bit.ly/wb-precache`;console.warn(e)}}}install(e){return o(e,async()=>{let t=new l;this.strategy.plugins.push(t);for(let[t,n]of this._urlsToCacheKeys){let r=this._cacheKeysToIntegrities.get(n),i=this._urlsToCacheModes.get(t),a=new Request(t,{integrity:r,cache:i,credentials:`same-origin`});await Promise.all(this.strategy.handleAll({params:{cacheKey:n},request:a,event:e}))}let{updatedURLs:n,notUpdatedURLs:r}=t;return{updatedURLs:n,notUpdatedURLs:r}})}activate(e){return o(e,async()=>{let e=await self.caches.open(this.strategy.cacheName),t=await e.keys(),n=new Set(this._urlsToCacheKeys.values()),r=[];for(let i of t)n.has(i.url)||(await e.delete(i),r.push(i.url));return{deletedURLs:r}})}getURLsToCacheKeys(){return this._urlsToCacheKeys}getCachedURLs(){return[...this._urlsToCacheKeys.keys()]}getCacheKeyForURL(e){let t=new URL(e,location.href);return this._urlsToCacheKeys.get(t.href)}getIntegrityForCacheKey(e){return this._cacheKeysToIntegrities.get(e)}async matchPrecache(e){let t=e instanceof Request?e.url:e,n=this.getCacheKeyForURL(t);if(n)return(await self.caches.open(this.strategy.cacheName)).match(n)}createHandlerBoundToURL(e){let n=this.getCacheKeyForURL(e);if(!n)throw new t(`non-precached-url`,{url:e});return t=>(t.request=new Request(e),t.params=Object.assign({cacheKey:n},t.params),this.strategy.handle(t))}},D,O=()=>(D||=new E,D);try{self[`workbox:routing:7.3.0`]&&_()}catch{}var k=e=>e&&typeof e==`object`?e:{handle:e},A=class{constructor(e,t,n=`GET`){this.handler=k(t),this.match=e,this.method=n}setCatchHandler(e){this.catchHandler=k(e)}},j=class extends A{constructor(e,t,n){super(({url:t})=>{let n=e.exec(t.href);if(n&&!(t.origin!==location.origin&&n.index!==0))return n.slice(1)},t,n)}},M=class{constructor(){this._routes=new Map,this._defaultHandlerMap=new Map}get routes(){return this._routes}addFetchListener(){self.addEventListener(`fetch`,(e=>{let{request:t}=e,n=this.handleRequest({request:t,event:e});n&&e.respondWith(n)}))}addCacheListener(){self.addEventListener(`message`,(e=>{if(e.data&&e.data.type===`CACHE_URLS`){let{payload:t}=e.data,n=Promise.all(t.urlsToCache.map(t=>{typeof t==`string`&&(t=[t]);let n=new Request(...t);return this.handleRequest({request:n,event:e})}));e.waitUntil(n),e.ports&&e.ports[0]&&n.then(()=>e.ports[0].postMessage(!0))}}))}handleRequest({request:e,event:t}){let n=new URL(e.url,location.href);if(!n.protocol.startsWith(`http`))return;let r=n.origin===location.origin,{params:i,route:a}=this.findMatchingRoute({event:t,request:e,sameOrigin:r,url:n}),o=a&&a.handler,s=e.method;if(!o&&this._defaultHandlerMap.has(s)&&(o=this._defaultHandlerMap.get(s)),!o)return;let c;try{c=o.handle({url:n,request:e,event:t,params:i})}catch(e){c=Promise.reject(e)}let l=a&&a.catchHandler;return c instanceof Promise&&(this._catchHandler||l)&&(c=c.catch(async r=>{if(l)try{return await l.handle({url:n,request:e,event:t,params:i})}catch(e){e instanceof Error&&(r=e)}if(this._catchHandler)return this._catchHandler.handle({url:n,request:e,event:t});throw r})),c}findMatchingRoute({url:e,sameOrigin:t,request:n,event:r}){let i=this._routes.get(n.method)||[];for(let a of i){let i,o=a.match({url:e,sameOrigin:t,request:n,event:r});if(o)return i=o,(Array.isArray(i)&&i.length===0||o.constructor===Object&&Object.keys(o).length===0||typeof o==`boolean`)&&(i=void 0),{route:a,params:i}}return{}}setDefaultHandler(e,t=`GET`){this._defaultHandlerMap.set(t,k(e))}setCatchHandler(e){this._catchHandler=k(e)}registerRoute(e){this._routes.has(e.method)||this._routes.set(e.method,[]),this._routes.get(e.method).push(e)}unregisterRoute(e){if(!this._routes.has(e.method))throw new t(`unregister-route-but-not-found-with-method`,{method:e.method});let n=this._routes.get(e.method).indexOf(e);if(n>-1)this._routes.get(e.method).splice(n,1);else throw new t(`unregister-route-route-not-registered`)}},N,P=()=>(N||(N=new M,N.addFetchListener(),N.addCacheListener()),N);function F(e,n,r){let i;if(typeof e==`string`){let t=new URL(e,location.href);i=new A(({url:e})=>e.href===t.href,n,r)}else if(e instanceof RegExp)i=new j(e,n,r);else if(typeof e==`function`)i=new A(e,n,r);else if(e instanceof A)i=e;else throw new t(`unsupported-route-type`,{moduleName:`workbox-routing`,funcName:`registerRoute`,paramName:`capture`});return P().registerRoute(i),i}function I(e,t=[]){for(let n of[...e.searchParams.keys()])t.some(e=>e.test(n))&&e.searchParams.delete(n);return e}function*L(e,{ignoreURLParametersMatching:t=[/^utm_/,/^fbclid$/],directoryIndex:n=`index.html`,cleanURLs:r=!0,urlManipulation:i}={}){let a=new URL(e,location.href);a.hash=``,yield a.href;let o=I(a,t);if(yield o.href,n&&o.pathname.endsWith(`/`)){let e=new URL(o.href);e.pathname+=n,yield e.href}if(r){let e=new URL(o.href);e.pathname+=`.html`,yield e.href}if(i){let e=i({url:a});for(let t of e)yield t.href}}var R=class extends A{constructor(e,t){super(({request:n})=>{let r=e.getURLsToCacheKeys();for(let i of L(n.url,t)){let t=r.get(i);if(t)return{cacheKey:t,integrity:e.getIntegrityForCacheKey(t)}}},e.strategy)}};function z(e){F(new R(O(),e))}function B(e){O().precache(e)}function V(e,t){B(e),z(t)}V([{"revision":"1872c500de691dce40960bb85481de07","url":"registerSW.js"},{"revision":"a0fb34fc84eb148d51812cd62669f20d","url":"icon-192.svg"},{"revision":"e2e61f88ee712a28cc9a55adecc0812c","url":"index.html"},{"revision":"a0fb34fc84eb148d51812cd62669f20d","url":"icon-512.svg"},{"revision":null,"url":"assets/extension-webview-HILvTnnn.js"},{"revision":null,"url":"assets/dist-D7KGU7Vl.js"},{"revision":null,"url":"assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2"},{"revision":null,"url":"assets/pdf-preview-BBVDS-z5.js"},{"revision":null,"url":"assets/KaTeX_AMS-Regular-BQhdFMY1.woff2"},{"revision":null,"url":"assets/scroll-area-BEllam7_.js"},{"revision":null,"url":"assets/database-D4DIhgi-.js"},{"revision":null,"url":"assets/csv-parser--2WJNgS7.js"},{"revision":null,"url":"assets/x-DlFGzN8d.js"},{"revision":null,"url":"assets/keybindings-store-B-zET-0o.js"},{"revision":null,"url":"assets/use-blob-url-e9uTXjv5.js"},{"revision":null,"url":"assets/index-Bce0weeW.css"},{"revision":null,"url":"assets/terminal-tab-BLIA53mt.js"},{"revision":null,"url":"assets/radar-KQ55EAFF-BviZcL-b.js"},{"revision":null,"url":"assets/vendor-xterm-CU2c3f0A.js"},{"revision":null,"url":"assets/code-editor-BTosKXkr.js"},{"revision":null,"url":"assets/input-Dk49gO8E.js"},{"revision":null,"url":"assets/video-preview-CKaht6nI.js"},{"revision":null,"url":"assets/KaTeX_Main-Regular-B22Nviop.woff2"},{"revision":null,"url":"assets/refresh-cw-LlbZDJpO.js"},{"revision":null,"url":"assets/pie-UPGHQEXC-D6S2MqVT.js"},{"revision":null,"url":"assets/treemap-KZPCXAKY-CM54VdaB.js"},{"revision":null,"url":"assets/react-GqWghJ-L.js"},{"revision":null,"url":"assets/github.min-D2BCvnWf.css"},{"revision":null,"url":"assets/vendor-mermaid-Dx86tuVP.js"},{"revision":null,"url":"assets/ai-settings-section-QE6nBNgN.js"},{"revision":null,"url":"assets/csv-preview-HMSavgBb.js"},{"revision":null,"url":"assets/katex-CKoArbIw.js"},{"revision":null,"url":"assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2"},{"revision":null,"url":"assets/info-3K5VOQVL-BwAZ2zd8.js"},{"revision":null,"url":"assets/chevron-right-BzAdxJRG.js"},{"revision":null,"url":"assets/extension-store-3yZYn07W.js"},{"revision":null,"url":"assets/markdown-renderer-D0MrsVJB.js"},{"revision":null,"url":"assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2"},{"revision":null,"url":"assets/vendor-markdown-0Mxgxy0L.js"},{"revision":null,"url":"assets/image-preview-0cJMnFZK.js"},{"revision":null,"url":"assets/api-client-Dvzcc_EO.js"},{"revision":null,"url":"assets/dist-im4ynINo.js"},{"revision":null,"url":"assets/KaTeX_Main-Italic-NWA7e6Wa.woff2"},{"revision":null,"url":"assets/table-Dq575bPF.js"},{"revision":null,"url":"assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2"},{"revision":null,"url":"assets/sqlite-viewer-D6mSIIx2.js"},{"revision":null,"url":"assets/packet-RMMSAZCW-tx2n5Qry.js"},{"revision":null,"url":"assets/port-forwarding-tab-ByKzBs-R.js"},{"revision":null,"url":"assets/plus-51UQ45rf.js"},{"revision":null,"url":"assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2"},{"revision":null,"url":"assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2"},{"revision":null,"url":"assets/arrow-up-Dtrfv490.js"},{"revision":null,"url":"assets/api-settings-DAk7D-NP.js"},{"revision":null,"url":"assets/architecture-PBZL5I3N-DvZbltvY.js"},{"revision":null,"url":"assets/rolldown-runtime-FhOqtrmT.js"},{"revision":null,"url":"assets/settings-store-BLLR7ed8.js"},{"revision":null,"url":"assets/use-monaco-theme-BkZDwoVd.js"},{"revision":null,"url":"assets/KaTeX_Math-Italic-t53AETM-.woff2"},{"revision":null,"url":"assets/github-dark-dimmed.min-BrpRStFV.css"},{"revision":null,"url":"assets/text-wrap-Cn6BNQfq.js"},{"revision":null,"url":"assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2"},{"revision":null,"url":"assets/KaTeX_Script-Regular-D3wIWfF6.woff2"},{"revision":null,"url":"assets/trash-2-CJYoLw7Q.js"},{"revision":null,"url":"assets/audio-preview-R7cq1uhJ.js"},{"revision":null,"url":"assets/file-exclamation-point-BwzaQ50n.js"},{"revision":null,"url":"assets/file-store-BrbCNyLm.js"},{"revision":null,"url":"assets/postgres-viewer-BnCbdR7g.js"},{"revision":null,"url":"assets/utils-CTg5uAYR.js"},{"revision":null,"url":"assets/columns-2-4fQcE4PF.js"},{"revision":null,"url":"assets/KaTeX_Main-Bold-Cx986IdX.woff2"},{"revision":null,"url":"assets/diff-viewer-DKLeIBkK.js"},{"revision":null,"url":"assets/settings-tab-BPdzUw3v.js"},{"revision":null,"url":"assets/KaTeX_Size2-Regular-Dy4dx90m.woff2"},{"revision":null,"url":"assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2"},{"revision":null,"url":"assets/KaTeX_Size1-Regular-mCD8mA8B.woff2"},{"revision":null,"url":"assets/tab-store-B3M9hjho.js"},{"revision":null,"url":"assets/keybindings-store-DaBV6qhz.js"},{"revision":null,"url":"assets/createLucideIcon-BjHrJDVb.js"},{"revision":null,"url":"assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2"},{"revision":null,"url":"assets/code-CuravVys.js"},{"revision":null,"url":"assets/chat-tab-umei1UkV.js"},{"revision":null,"url":"assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2"},{"revision":null,"url":"assets/sql-completion-provider-C3cq9j99.js"},{"revision":null,"url":"assets/vendor-ui-B-89Uj8i.js"},{"revision":null,"url":"assets/esm-K1XIK4vc.js"},{"revision":null,"url":"assets/gitGraph-HDMCJU4V-BxhdxFgj.js"},{"revision":null,"url":"assets/conflict-editor-dzofjxab.js"},{"revision":null,"url":"assets/index-DDBvHVVr.js"},{"revision":null,"url":"assets/vendor-xterm-BrP-ENHg.css"},{"revision":null,"url":"assets/database-viewer-5Uf8Rrls.js"},{"revision":null,"url":"assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2"},{"revision":null,"url":"assets/lib-DQHnkzGy.js"},{"revision":null,"url":"assets/sql-query-editor-CVAnRFbi.js"},{"revision":"d0f94ce046cf8cf09605ee7664dac557","url":"monacoeditorwork/html.worker.bundle.js"},{"revision":"a424156a79b9c1b907db93aa3180585a","url":"monacoeditorwork/editor.worker.bundle.js"},{"revision":"b3a7f967560c9816492a1567b3f7f0dc","url":"monacoeditorwork/css.worker.bundle.js"},{"revision":"a5d8a1acfc29c2a4c882a54ffc93def3","url":"monacoeditorwork/json.worker.bundle.js"},{"revision":"948e060affb598c339be40d69e1f6f9c","url":"monacoeditorwork/ts.worker.bundle.js"},{"revision":"79c8870653c8f419f2e3323085e1f4be","url":"manifest.webmanifest"}]),self.addEventListener(`push`,e=>{e.waitUntil(self.clients.matchAll({type:`window`,includeUncontrolled:!0}).then(t=>{if(t.some(e=>e.visibilityState===`visible`))return;let n=e.data?.json()??{title:`PPM`,body:`Chat completed`};return self.registration.showNotification(n.title,{body:n.body,icon:`/icon-192.png`,badge:`/icon-192.png`,tag:`ppm-chat-done`,silent:!1,data:{url:self.location.origin}})}))}),self.addEventListener(`notificationclick`,e=>{e.notification.close(),e.waitUntil(self.clients.matchAll({type:`window`,includeUncontrolled:!0}).then(t=>{for(let e of t)if(e.url.includes(self.location.origin)&&`focus`in e)return e.focus();return self.clients.openWindow(e.notification.data?.url||`/`)}))});
|
|
1
|
+
try{self[`workbox:core:7.3.0`]&&_()}catch{}var e=(e,...t)=>{let n=e;return t.length>0&&(n+=` :: ${JSON.stringify(t)}`),n},t=class extends Error{constructor(t,n){let r=e(t,n);super(r),this.name=t,this.details=n}},n={googleAnalytics:`googleAnalytics`,precache:`precache-v2`,prefix:`workbox`,runtime:`runtime`,suffix:typeof registration<`u`?registration.scope:``},r=e=>[n.prefix,e,n.suffix].filter(e=>e&&e.length>0).join(`-`),i=e=>{for(let t of Object.keys(n))e(t)},a={updateDetails:e=>{i(t=>{typeof e[t]==`string`&&(n[t]=e[t])})},getGoogleAnalyticsName:e=>e||r(n.googleAnalytics),getPrecacheName:e=>e||r(n.precache),getPrefix:()=>n.prefix,getRuntimeName:e=>e||r(n.runtime),getSuffix:()=>n.suffix};function o(e,t){let n=t();return e.waitUntil(n),n}try{self[`workbox:precaching:7.3.0`]&&_()}catch{}var s=`__WB_REVISION__`;function c(e){if(!e)throw new t(`add-to-cache-list-unexpected-type`,{entry:e});if(typeof e==`string`){let t=new URL(e,location.href);return{cacheKey:t.href,url:t.href}}let{revision:n,url:r}=e;if(!r)throw new t(`add-to-cache-list-unexpected-type`,{entry:e});if(!n){let e=new URL(r,location.href);return{cacheKey:e.href,url:e.href}}let i=new URL(r,location.href),a=new URL(r,location.href);return i.searchParams.set(s,n),{cacheKey:i.href,url:a.href}}var l=class{constructor(){this.updatedURLs=[],this.notUpdatedURLs=[],this.handlerWillStart=async({request:e,state:t})=>{t&&(t.originalRequest=e)},this.cachedResponseWillBeUsed=async({event:e,state:t,cachedResponse:n})=>{if(e.type===`install`&&t&&t.originalRequest&&t.originalRequest instanceof Request){let e=t.originalRequest.url;n?this.notUpdatedURLs.push(e):this.updatedURLs.push(e)}return n}}},u=class{constructor({precacheController:e}){this.cacheKeyWillBeUsed=async({request:e,params:t})=>{let n=t?.cacheKey||this._precacheController.getCacheKeyForURL(e.url);return n?new Request(n,{headers:e.headers}):e},this._precacheController=e}},d;function f(){if(d===void 0){let e=new Response(``);if(`body`in e)try{new Response(e.body),d=!0}catch{d=!1}d=!1}return d}async function p(e,n){let r=null;if(e.url&&(r=new URL(e.url).origin),r!==self.location.origin)throw new t(`cross-origin-copy-response`,{origin:r});let i=e.clone(),a={headers:new Headers(i.headers),status:i.status,statusText:i.statusText},o=n?n(a):a,s=f()?i.body:await i.blob();return new Response(s,o)}var m=e=>new URL(String(e),location.href).href.replace(RegExp(`^${location.origin}`),``);function h(e,t){let n=new URL(e);for(let e of t)n.searchParams.delete(e);return n.href}async function g(e,t,n,r){let i=h(t.url,n);if(t.url===i)return e.match(t,r);let a=Object.assign(Object.assign({},r),{ignoreSearch:!0}),o=await e.keys(t,a);for(let t of o)if(i===h(t.url,n))return e.match(t,r)}var v=class{constructor(){this.promise=new Promise((e,t)=>{this.resolve=e,this.reject=t})}},y=new Set;async function b(){for(let e of y)await e()}function x(e){return new Promise(t=>setTimeout(t,e))}try{self[`workbox:strategies:7.3.0`]&&_()}catch{}function S(e){return typeof e==`string`?new Request(e):e}var C=class{constructor(e,t){this._cacheKeys={},Object.assign(this,t),this.event=t.event,this._strategy=e,this._handlerDeferred=new v,this._extendLifetimePromises=[],this._plugins=[...e.plugins],this._pluginStateMap=new Map;for(let e of this._plugins)this._pluginStateMap.set(e,{});this.event.waitUntil(this._handlerDeferred.promise)}async fetch(e){let{event:n}=this,r=S(e);if(r.mode===`navigate`&&n instanceof FetchEvent&&n.preloadResponse){let e=await n.preloadResponse;if(e)return e}let i=this.hasCallback(`fetchDidFail`)?r.clone():null;try{for(let e of this.iterateCallbacks(`requestWillFetch`))r=await e({request:r.clone(),event:n})}catch(e){if(e instanceof Error)throw new t(`plugin-error-request-will-fetch`,{thrownErrorMessage:e.message})}let a=r.clone();try{let e;e=await fetch(r,r.mode===`navigate`?void 0:this._strategy.fetchOptions);for(let t of this.iterateCallbacks(`fetchDidSucceed`))e=await t({event:n,request:a,response:e});return e}catch(e){throw i&&await this.runCallbacks(`fetchDidFail`,{error:e,event:n,originalRequest:i.clone(),request:a.clone()}),e}}async fetchAndCachePut(e){let t=await this.fetch(e),n=t.clone();return this.waitUntil(this.cachePut(e,n)),t}async cacheMatch(e){let t=S(e),n,{cacheName:r,matchOptions:i}=this._strategy,a=await this.getCacheKey(t,`read`),o=Object.assign(Object.assign({},i),{cacheName:r});n=await caches.match(a,o);for(let e of this.iterateCallbacks(`cachedResponseWillBeUsed`))n=await e({cacheName:r,matchOptions:i,cachedResponse:n,request:a,event:this.event})||void 0;return n}async cachePut(e,n){let r=S(e);await x(0);let i=await this.getCacheKey(r,`write`);if(!n)throw new t(`cache-put-with-no-response`,{url:m(i.url)});let a=await this._ensureResponseSafeToCache(n);if(!a)return!1;let{cacheName:o,matchOptions:s}=this._strategy,c=await self.caches.open(o),l=this.hasCallback(`cacheDidUpdate`),u=l?await g(c,i.clone(),[`__WB_REVISION__`],s):null;try{await c.put(i,l?a.clone():a)}catch(e){if(e instanceof Error)throw e.name===`QuotaExceededError`&&await b(),e}for(let e of this.iterateCallbacks(`cacheDidUpdate`))await e({cacheName:o,oldResponse:u,newResponse:a.clone(),request:i,event:this.event});return!0}async getCacheKey(e,t){let n=`${e.url} | ${t}`;if(!this._cacheKeys[n]){let r=e;for(let e of this.iterateCallbacks(`cacheKeyWillBeUsed`))r=S(await e({mode:t,request:r,event:this.event,params:this.params}));this._cacheKeys[n]=r}return this._cacheKeys[n]}hasCallback(e){for(let t of this._strategy.plugins)if(e in t)return!0;return!1}async runCallbacks(e,t){for(let n of this.iterateCallbacks(e))await n(t)}*iterateCallbacks(e){for(let t of this._strategy.plugins)if(typeof t[e]==`function`){let n=this._pluginStateMap.get(t);yield r=>{let i=Object.assign(Object.assign({},r),{state:n});return t[e](i)}}}waitUntil(e){return this._extendLifetimePromises.push(e),e}async doneWaiting(){for(;this._extendLifetimePromises.length;){let e=this._extendLifetimePromises.splice(0),t=(await Promise.allSettled(e)).find(e=>e.status===`rejected`);if(t)throw t.reason}}destroy(){this._handlerDeferred.resolve(null)}async _ensureResponseSafeToCache(e){let t=e,n=!1;for(let e of this.iterateCallbacks(`cacheWillUpdate`))if(t=await e({request:this.request,response:t,event:this.event})||void 0,n=!0,!t)break;return n||t&&t.status!==200&&(t=void 0),t}},w=class{constructor(e={}){this.cacheName=a.getRuntimeName(e.cacheName),this.plugins=e.plugins||[],this.fetchOptions=e.fetchOptions,this.matchOptions=e.matchOptions}handle(e){let[t]=this.handleAll(e);return t}handleAll(e){e instanceof FetchEvent&&(e={event:e,request:e.request});let t=e.event,n=typeof e.request==`string`?new Request(e.request):e.request,r=`params`in e?e.params:void 0,i=new C(this,{event:t,request:n,params:r}),a=this._getResponse(i,n,t);return[a,this._awaitComplete(a,i,n,t)]}async _getResponse(e,n,r){await e.runCallbacks(`handlerWillStart`,{event:r,request:n});let i;try{if(i=await this._handle(n,e),!i||i.type===`error`)throw new t(`no-response`,{url:n.url})}catch(t){if(t instanceof Error){for(let a of e.iterateCallbacks(`handlerDidError`))if(i=await a({error:t,event:r,request:n}),i)break}if(!i)throw t}for(let t of e.iterateCallbacks(`handlerWillRespond`))i=await t({event:r,request:n,response:i});return i}async _awaitComplete(e,t,n,r){let i,a;try{i=await e}catch{}try{await t.runCallbacks(`handlerDidRespond`,{event:r,request:n,response:i}),await t.doneWaiting()}catch(e){e instanceof Error&&(a=e)}if(await t.runCallbacks(`handlerDidComplete`,{event:r,request:n,response:i,error:a}),t.destroy(),a)throw a}},T=class e extends w{constructor(t={}){t.cacheName=a.getPrecacheName(t.cacheName),super(t),this._fallbackToNetwork=t.fallbackToNetwork!==!1,this.plugins.push(e.copyRedirectedCacheableResponsesPlugin)}async _handle(e,t){return await t.cacheMatch(e)||(t.event&&t.event.type===`install`?await this._handleInstall(e,t):await this._handleFetch(e,t))}async _handleFetch(e,n){let r,i=n.params||{};if(this._fallbackToNetwork){let t=i.integrity,a=e.integrity,o=!a||a===t;r=await n.fetch(new Request(e,{integrity:e.mode===`no-cors`?void 0:a||t})),t&&o&&e.mode!==`no-cors`&&(this._useDefaultCacheabilityPluginIfNeeded(),await n.cachePut(e,r.clone()))}else throw new t(`missing-precache-entry`,{cacheName:this.cacheName,url:e.url});return r}async _handleInstall(e,n){this._useDefaultCacheabilityPluginIfNeeded();let r=await n.fetch(e);if(!await n.cachePut(e,r.clone()))throw new t(`bad-precaching-response`,{url:e.url,status:r.status});return r}_useDefaultCacheabilityPluginIfNeeded(){let t=null,n=0;for(let[r,i]of this.plugins.entries())i!==e.copyRedirectedCacheableResponsesPlugin&&(i===e.defaultPrecacheCacheabilityPlugin&&(t=r),i.cacheWillUpdate&&n++);n===0?this.plugins.push(e.defaultPrecacheCacheabilityPlugin):n>1&&t!==null&&this.plugins.splice(t,1)}};T.defaultPrecacheCacheabilityPlugin={async cacheWillUpdate({response:e}){return!e||e.status>=400?null:e}},T.copyRedirectedCacheableResponsesPlugin={async cacheWillUpdate({response:e}){return e.redirected?await p(e):e}};var E=class{constructor({cacheName:e,plugins:t=[],fallbackToNetwork:n=!0}={}){this._urlsToCacheKeys=new Map,this._urlsToCacheModes=new Map,this._cacheKeysToIntegrities=new Map,this._strategy=new T({cacheName:a.getPrecacheName(e),plugins:[...t,new u({precacheController:this})],fallbackToNetwork:n}),this.install=this.install.bind(this),this.activate=this.activate.bind(this)}get strategy(){return this._strategy}precache(e){this.addToCacheList(e),this._installAndActiveListenersAdded||=(self.addEventListener(`install`,this.install),self.addEventListener(`activate`,this.activate),!0)}addToCacheList(e){let n=[];for(let r of e){typeof r==`string`?n.push(r):r&&r.revision===void 0&&n.push(r.url);let{cacheKey:e,url:i}=c(r),a=typeof r!=`string`&&r.revision?`reload`:`default`;if(this._urlsToCacheKeys.has(i)&&this._urlsToCacheKeys.get(i)!==e)throw new t(`add-to-cache-list-conflicting-entries`,{firstEntry:this._urlsToCacheKeys.get(i),secondEntry:e});if(typeof r!=`string`&&r.integrity){if(this._cacheKeysToIntegrities.has(e)&&this._cacheKeysToIntegrities.get(e)!==r.integrity)throw new t(`add-to-cache-list-conflicting-integrities`,{url:i});this._cacheKeysToIntegrities.set(e,r.integrity)}if(this._urlsToCacheKeys.set(i,e),this._urlsToCacheModes.set(i,a),n.length>0){let e=`Workbox is precaching URLs without revision info: ${n.join(`, `)}\nThis is generally NOT safe. Learn more at https://bit.ly/wb-precache`;console.warn(e)}}}install(e){return o(e,async()=>{let t=new l;this.strategy.plugins.push(t);for(let[t,n]of this._urlsToCacheKeys){let r=this._cacheKeysToIntegrities.get(n),i=this._urlsToCacheModes.get(t),a=new Request(t,{integrity:r,cache:i,credentials:`same-origin`});await Promise.all(this.strategy.handleAll({params:{cacheKey:n},request:a,event:e}))}let{updatedURLs:n,notUpdatedURLs:r}=t;return{updatedURLs:n,notUpdatedURLs:r}})}activate(e){return o(e,async()=>{let e=await self.caches.open(this.strategy.cacheName),t=await e.keys(),n=new Set(this._urlsToCacheKeys.values()),r=[];for(let i of t)n.has(i.url)||(await e.delete(i),r.push(i.url));return{deletedURLs:r}})}getURLsToCacheKeys(){return this._urlsToCacheKeys}getCachedURLs(){return[...this._urlsToCacheKeys.keys()]}getCacheKeyForURL(e){let t=new URL(e,location.href);return this._urlsToCacheKeys.get(t.href)}getIntegrityForCacheKey(e){return this._cacheKeysToIntegrities.get(e)}async matchPrecache(e){let t=e instanceof Request?e.url:e,n=this.getCacheKeyForURL(t);if(n)return(await self.caches.open(this.strategy.cacheName)).match(n)}createHandlerBoundToURL(e){let n=this.getCacheKeyForURL(e);if(!n)throw new t(`non-precached-url`,{url:e});return t=>(t.request=new Request(e),t.params=Object.assign({cacheKey:n},t.params),this.strategy.handle(t))}},D,O=()=>(D||=new E,D);try{self[`workbox:routing:7.3.0`]&&_()}catch{}var k=e=>e&&typeof e==`object`?e:{handle:e},A=class{constructor(e,t,n=`GET`){this.handler=k(t),this.match=e,this.method=n}setCatchHandler(e){this.catchHandler=k(e)}},j=class extends A{constructor(e,t,n){super(({url:t})=>{let n=e.exec(t.href);if(n&&!(t.origin!==location.origin&&n.index!==0))return n.slice(1)},t,n)}},M=class{constructor(){this._routes=new Map,this._defaultHandlerMap=new Map}get routes(){return this._routes}addFetchListener(){self.addEventListener(`fetch`,(e=>{let{request:t}=e,n=this.handleRequest({request:t,event:e});n&&e.respondWith(n)}))}addCacheListener(){self.addEventListener(`message`,(e=>{if(e.data&&e.data.type===`CACHE_URLS`){let{payload:t}=e.data,n=Promise.all(t.urlsToCache.map(t=>{typeof t==`string`&&(t=[t]);let n=new Request(...t);return this.handleRequest({request:n,event:e})}));e.waitUntil(n),e.ports&&e.ports[0]&&n.then(()=>e.ports[0].postMessage(!0))}}))}handleRequest({request:e,event:t}){let n=new URL(e.url,location.href);if(!n.protocol.startsWith(`http`))return;let r=n.origin===location.origin,{params:i,route:a}=this.findMatchingRoute({event:t,request:e,sameOrigin:r,url:n}),o=a&&a.handler,s=e.method;if(!o&&this._defaultHandlerMap.has(s)&&(o=this._defaultHandlerMap.get(s)),!o)return;let c;try{c=o.handle({url:n,request:e,event:t,params:i})}catch(e){c=Promise.reject(e)}let l=a&&a.catchHandler;return c instanceof Promise&&(this._catchHandler||l)&&(c=c.catch(async r=>{if(l)try{return await l.handle({url:n,request:e,event:t,params:i})}catch(e){e instanceof Error&&(r=e)}if(this._catchHandler)return this._catchHandler.handle({url:n,request:e,event:t});throw r})),c}findMatchingRoute({url:e,sameOrigin:t,request:n,event:r}){let i=this._routes.get(n.method)||[];for(let a of i){let i,o=a.match({url:e,sameOrigin:t,request:n,event:r});if(o)return i=o,(Array.isArray(i)&&i.length===0||o.constructor===Object&&Object.keys(o).length===0||typeof o==`boolean`)&&(i=void 0),{route:a,params:i}}return{}}setDefaultHandler(e,t=`GET`){this._defaultHandlerMap.set(t,k(e))}setCatchHandler(e){this._catchHandler=k(e)}registerRoute(e){this._routes.has(e.method)||this._routes.set(e.method,[]),this._routes.get(e.method).push(e)}unregisterRoute(e){if(!this._routes.has(e.method))throw new t(`unregister-route-but-not-found-with-method`,{method:e.method});let n=this._routes.get(e.method).indexOf(e);if(n>-1)this._routes.get(e.method).splice(n,1);else throw new t(`unregister-route-route-not-registered`)}},N,P=()=>(N||(N=new M,N.addFetchListener(),N.addCacheListener()),N);function F(e,n,r){let i;if(typeof e==`string`){let t=new URL(e,location.href);i=new A(({url:e})=>e.href===t.href,n,r)}else if(e instanceof RegExp)i=new j(e,n,r);else if(typeof e==`function`)i=new A(e,n,r);else if(e instanceof A)i=e;else throw new t(`unsupported-route-type`,{moduleName:`workbox-routing`,funcName:`registerRoute`,paramName:`capture`});return P().registerRoute(i),i}function I(e,t=[]){for(let n of[...e.searchParams.keys()])t.some(e=>e.test(n))&&e.searchParams.delete(n);return e}function*L(e,{ignoreURLParametersMatching:t=[/^utm_/,/^fbclid$/],directoryIndex:n=`index.html`,cleanURLs:r=!0,urlManipulation:i}={}){let a=new URL(e,location.href);a.hash=``,yield a.href;let o=I(a,t);if(yield o.href,n&&o.pathname.endsWith(`/`)){let e=new URL(o.href);e.pathname+=n,yield e.href}if(r){let e=new URL(o.href);e.pathname+=`.html`,yield e.href}if(i){let e=i({url:a});for(let t of e)yield t.href}}var R=class extends A{constructor(e,t){super(({request:n})=>{let r=e.getURLsToCacheKeys();for(let i of L(n.url,t)){let t=r.get(i);if(t)return{cacheKey:t,integrity:e.getIntegrityForCacheKey(t)}}},e.strategy)}};function z(e){F(new R(O(),e))}function B(e){O().precache(e)}function V(e,t){B(e),z(t)}V([{"revision":"1872c500de691dce40960bb85481de07","url":"registerSW.js"},{"revision":"a0fb34fc84eb148d51812cd62669f20d","url":"icon-192.svg"},{"revision":"7bb21b98483de3a88d92d2f3edb2e8fa","url":"index.html"},{"revision":"a0fb34fc84eb148d51812cd62669f20d","url":"icon-512.svg"},{"revision":null,"url":"assets/lib-BqkcKGFq.js"},{"revision":null,"url":"assets/dist-D7KGU7Vl.js"},{"revision":null,"url":"assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2"},{"revision":null,"url":"assets/KaTeX_AMS-Regular-BQhdFMY1.woff2"},{"revision":null,"url":"assets/tab-store-Jvy1eZGM.js"},{"revision":null,"url":"assets/architecture-PBZL5I3N-Dy3PgD6O.js"},{"revision":null,"url":"assets/sql-query-editor-vpD0I0KG.js"},{"revision":null,"url":"assets/pdf-preview-CqoQE09t.js"},{"revision":null,"url":"assets/image-preview-DAuPOzYl.js"},{"revision":null,"url":"assets/packet-RMMSAZCW-DpzHf4xp.js"},{"revision":null,"url":"assets/terminal-tab-D7u7wsyb.js"},{"revision":null,"url":"assets/KaTeX_Main-Regular-B22Nviop.woff2"},{"revision":null,"url":"assets/code-editor-BJ1tSNWA.js"},{"revision":null,"url":"assets/scroll-area-D0EQpAH2.js"},{"revision":null,"url":"assets/vendor-xterm-D7SePDJp.js"},{"revision":null,"url":"assets/keybindings-store-V12kZZHO.js"},{"revision":null,"url":"assets/file-exclamation-point-Baz81y5z.js"},{"revision":null,"url":"assets/github.min-D2BCvnWf.css"},{"revision":null,"url":"assets/csv-preview-C9qGhDlb.js"},{"revision":null,"url":"assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2"},{"revision":null,"url":"assets/chevron-right-BzAdxJRG.js"},{"revision":null,"url":"assets/file-store-BgZggznw.js"},{"revision":null,"url":"assets/x-BtqbfkR7.js"},{"revision":null,"url":"assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2"},{"revision":null,"url":"assets/ai-settings-section-DeW4WN43.js"},{"revision":null,"url":"assets/pie-UPGHQEXC-BpzFCKJ8.js"},{"revision":null,"url":"assets/vendor-mermaid-DCxaaPi4.js"},{"revision":null,"url":"assets/vendor-markdown-0Mxgxy0L.js"},{"revision":null,"url":"assets/conflict-editor-CrgrMZ2F.js"},{"revision":null,"url":"assets/KaTeX_Main-Italic-NWA7e6Wa.woff2"},{"revision":null,"url":"assets/chat-tab-UFEFOnpl.js"},{"revision":null,"url":"assets/trash-2-BgDIBl6f.js"},{"revision":null,"url":"assets/use-blob-url-BgxxT-n_.js"},{"revision":null,"url":"assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2"},{"revision":null,"url":"assets/video-preview-BSDzqlzk.js"},{"revision":null,"url":"assets/dist-BqoEabX7.js"},{"revision":null,"url":"assets/port-forwarding-tab-De7qxkjp.js"},{"revision":null,"url":"assets/input-bGJExpJZ.js"},{"revision":null,"url":"assets/plus-51UQ45rf.js"},{"revision":null,"url":"assets/api-settings-t7Leca7J.js"},{"revision":null,"url":"assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2"},{"revision":null,"url":"assets/sqlite-viewer-Ccz2crvN.js"},{"revision":null,"url":"assets/extension-webview-B95nOfj-.js"},{"revision":null,"url":"assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2"},{"revision":null,"url":"assets/arrow-up-Dtrfv490.js"},{"revision":null,"url":"assets/refresh-cw-CSFrDtiu.js"},{"revision":null,"url":"assets/rolldown-runtime-FhOqtrmT.js"},{"revision":null,"url":"assets/radar-KQ55EAFF-DAxWKxM4.js"},{"revision":null,"url":"assets/markdown-renderer-DwINRWo4.js"},{"revision":null,"url":"assets/KaTeX_Math-Italic-t53AETM-.woff2"},{"revision":null,"url":"assets/github-dark-dimmed.min-BrpRStFV.css"},{"revision":null,"url":"assets/index-DJOjXTcq.js"},{"revision":null,"url":"assets/diff-viewer-C2eOczTs.js"},{"revision":null,"url":"assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2"},{"revision":null,"url":"assets/KaTeX_Script-Regular-D3wIWfF6.woff2"},{"revision":null,"url":"assets/sql-completion-provider-EzHOQLfo.js"},{"revision":null,"url":"assets/database-viewer-e_NAkIL_.js"},{"revision":null,"url":"assets/esm-B3je8j5P.js"},{"revision":null,"url":"assets/index-DSOP0R0s.css"},{"revision":null,"url":"assets/treemap-KZPCXAKY-D6dgXbAe.js"},{"revision":null,"url":"assets/KaTeX_Main-Bold-Cx986IdX.woff2"},{"revision":null,"url":"assets/react-BkWDCPD7.js"},{"revision":null,"url":"assets/database-DCT0OjgQ.js"},{"revision":null,"url":"assets/settings-tab-BdTEumwU.js"},{"revision":null,"url":"assets/KaTeX_Size2-Regular-Dy4dx90m.woff2"},{"revision":null,"url":"assets/audio-preview-BSAe2WQB.js"},{"revision":null,"url":"assets/gitGraph-HDMCJU4V-Bu1SIFFq.js"},{"revision":null,"url":"assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2"},{"revision":null,"url":"assets/KaTeX_Size1-Regular-mCD8mA8B.woff2"},{"revision":null,"url":"assets/api-client-r4nyVy7H.js"},{"revision":null,"url":"assets/createLucideIcon-BjHrJDVb.js"},{"revision":null,"url":"assets/info-3K5VOQVL-DzfAxmVd.js"},{"revision":null,"url":"assets/settings-store-CdcSAgEZ.js"},{"revision":null,"url":"assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2"},{"revision":null,"url":"assets/code-CuravVys.js"},{"revision":null,"url":"assets/katex-bpagxk3Z.js"},{"revision":null,"url":"assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2"},{"revision":null,"url":"assets/vendor-ui-B-89Uj8i.js"},{"revision":null,"url":"assets/utils-ChWX7pZv.js"},{"revision":null,"url":"assets/text-wrap-DzvCTq_i.js"},{"revision":null,"url":"assets/postgres-viewer-Dd6rLb8b.js"},{"revision":null,"url":"assets/csv-parser-DxVplKKB.js"},{"revision":null,"url":"assets/vendor-xterm-BrP-ENHg.css"},{"revision":null,"url":"assets/use-monaco-theme-dtPsv6sh.js"},{"revision":null,"url":"assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2"},{"revision":null,"url":"assets/table-DbSviOmw.js"},{"revision":"d0f94ce046cf8cf09605ee7664dac557","url":"monacoeditorwork/html.worker.bundle.js"},{"revision":"a424156a79b9c1b907db93aa3180585a","url":"monacoeditorwork/editor.worker.bundle.js"},{"revision":"b3a7f967560c9816492a1567b3f7f0dc","url":"monacoeditorwork/css.worker.bundle.js"},{"revision":"a5d8a1acfc29c2a4c882a54ffc93def3","url":"monacoeditorwork/json.worker.bundle.js"},{"revision":"948e060affb598c339be40d69e1f6f9c","url":"monacoeditorwork/ts.worker.bundle.js"},{"revision":"79c8870653c8f419f2e3323085e1f4be","url":"manifest.webmanifest"}]),self.addEventListener(`push`,e=>{e.waitUntil(self.clients.matchAll({type:`window`,includeUncontrolled:!0}).then(t=>{if(t.some(e=>e.visibilityState===`visible`))return;let n=e.data?.json()??{title:`PPM`,body:`Chat completed`};return self.registration.showNotification(n.title,{body:n.body,icon:`/icon-192.png`,badge:`/icon-192.png`,tag:`ppm-chat-done`,silent:!1,data:{url:self.location.origin}})}))}),self.addEventListener(`notificationclick`,e=>{e.notification.close(),e.waitUntil(self.clients.matchAll({type:`window`,includeUncontrolled:!0}).then(t=>{for(let e of t)if(e.url.includes(self.location.origin)&&`focus`in e)return e.focus();return self.clients.openWindow(e.notification.data?.url||`/`)}))});
|
package/docs/codebase-summary.md
CHANGED
|
@@ -347,6 +347,8 @@ src/
|
|
|
347
347
|
- **PanelStore** — Grid layout, panel creation, keep-alive snapshots
|
|
348
348
|
- **FileStore** — File cache
|
|
349
349
|
- **SettingsStore** — Theme, sidebar, git view, device name
|
|
350
|
+
- **CompareStore** — File compare selection (path, project, dirty content); persists to localStorage with >500KB guard; auto-clears on project switch
|
|
351
|
+
- **KeybindingsStore** — Custom keybinding overrides (includes `compare-files` action with default `Mod+Alt+D`)
|
|
350
352
|
- **Pattern:** Zustand for state, React.lazy() for tab content splitting
|
|
351
353
|
|
|
352
354
|
## Data Flow Diagrams
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Compare-Files Feature Ship
|
|
2
|
+
|
|
3
|
+
**Date**: 2026-04-22 14:35
|
|
4
|
+
**Severity**: Low
|
|
5
|
+
**Component**: Web IDE / Diff Viewer
|
|
6
|
+
**Status**: Resolved
|
|
7
|
+
|
|
8
|
+
## What Happened
|
|
9
|
+
|
|
10
|
+
Shipped compare-files feature (file diff picker) for PPM web IDE. Four trigger paths — tab context menu, file-tree context menu, command palette, keyboard shortcut `Mod+Alt+D`. Reused existing DiffViewer + git-diff tab type; zero backend changes. Added zustand store with persist middleware, ComparePicker singleton at App root, and in-flight guard to prevent double-invoke race.
|
|
11
|
+
|
|
12
|
+
## Technical Details
|
|
13
|
+
|
|
14
|
+
- **370 new lines of code** across 3 new files + 6 edited files
|
|
15
|
+
- **Zustand store** (`useCompareStore`) with `persist` middleware that strips dirty content >500KB
|
|
16
|
+
- **Dirty-buffer semantic**: snapshot at select-time, not compare-time (closing the tab doesn't invalidate selection)
|
|
17
|
+
- **Custom event** (`open-compare-picker`) dispatched from 4 trigger points, singleton listener mounted at App root
|
|
18
|
+
- **Keyboard shortcut**: `Mod+Alt+D` single-stroke (chord parser doesn't support `Cmd+K D` yet)
|
|
19
|
+
- **Module-scope subscription**: `useProjectStore.subscribe` auto-clears selection on project switch
|
|
20
|
+
- **In-flight guard**: ref-based tracking in `handlePick` to block concurrent invocations (reviewer comment M5)
|
|
21
|
+
- **Test coverage**: 1698 pass, 48 pre-existing unrelated failures, 0 new regressions
|
|
22
|
+
- **Code review**: 7.5/10, 0 critical, 6 major (1 addressed: M5; 3 deferred: M3/M4/M6)
|
|
23
|
+
|
|
24
|
+
## Why This Matters
|
|
25
|
+
|
|
26
|
+
Snapshot-at-select semantics felt risky initially but aligns with user mental model: "I'm picking a version to diff." Closing the tab doesn't invalidate the selection — this is more intuitive than invalidating on every interaction. Deferring byte-accurate persist limit (M4), clear-on-error (M3), and legacy multi-select-compare unification (M6) kept scope tight; all are low-risk YAGNI.
|
|
27
|
+
|
|
28
|
+
## Key Decisions
|
|
29
|
+
|
|
30
|
+
1. **Same-project only**: Cross-project selection auto-cleared rather than supported. Reduces state complexity; users can switch projects and re-pick.
|
|
31
|
+
2. **Single-stroke `Mod+Alt+D`**: Chord parser limitation means `Cmd+K D` unsupported. Single-stroke is good enough; avoiding yak-shave on chord parsing.
|
|
32
|
+
3. **Snapshot timing**: Capturing dirty buffer at select-time (not compare-time) prevents stale diffs after file edits. User closes tab; selection still valid.
|
|
33
|
+
4. **Defer judgment calls**: M3/M4/M6 are correctness edge-cases, not showstoppers. Shipping first; addressing in follow-up if telemetry justifies.
|
|
34
|
+
|
|
35
|
+
## Lessons Learned
|
|
36
|
+
|
|
37
|
+
- **Persist middleware footprint**: 500KB strip limit is conservative but safe; no observed bloat in dev.
|
|
38
|
+
- **Custom events + singleton**: Avoids prop-drilling 4 trigger points through component tree. Event-driven pattern cleaner than callback props.
|
|
39
|
+
- **In-flight guard as ref**: Simpler than promise-based debounce; prevents race without async complexity.
|
|
40
|
+
- **Deferred ≠ broken**: 6 major review comments sound high, but only 1 was blocking (M5). Others are judgment-calls; shipping with known trade-offs is valid.
|
|
41
|
+
|
|
42
|
+
## Next Steps
|
|
43
|
+
|
|
44
|
+
- Monitor telemetry for M3 (clear-on-error) if users report stale diffs
|
|
45
|
+
- Consider byte-accurate persist limit (M4) if DB bloat observed
|
|
46
|
+
- Unify legacy multi-select-compare path (M6) in v0.14 if feature gains adoption
|
|
47
|
+
|
|
48
|
+
Commit: `cc09bb5b9e652a2feefde4e533934a6f53301895`
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
**Status:** Resolved
|
|
53
|
+
**Summary:** Shipped compare-files feature with 4 trigger paths, zustand persist store, and event-driven ComparePicker. 370 new LOC, 0 new test regressions, 1 review blocker addressed (M5), 3 judgment-calls deferred (M3/M4/M6). Snapshot-at-select semantics and same-project-only scope kept complexity tight.
|
|
@@ -20,9 +20,17 @@ All notable changes to PPM are documented here. Format follows [Keep a Changelog
|
|
|
20
20
|
|
|
21
21
|
---
|
|
22
22
|
|
|
23
|
-
## [Unreleased] — Lazy-Load File Tree + Palette Index, Session Tagging, Jira Debug Session Redesign, Frontend Memory Optimization, Git-Graph Enhancements
|
|
23
|
+
## [Unreleased] — Lazy-Load File Tree + Palette Index, Session Tagging, File Compare, Jira Debug Session Redesign, Frontend Memory Optimization, Git-Graph Enhancements
|
|
24
24
|
|
|
25
25
|
### Added
|
|
26
|
+
- **File Compare** — Side-by-side diff viewer for comparing two files or file versions
|
|
27
|
+
- Four triggers: (1) tab right-click "Select for Compare" / "Compare with Selected", (2) file tree right-click (same), (3) command palette "Compare Files...", (4) keyboard shortcut `Mod+Alt+D`
|
|
28
|
+
- Reuses existing `DiffViewer` component + `git-diff` tab type + `/files/compare` API — no new backend endpoints
|
|
29
|
+
- Supports dirty buffer content: unsaved editor changes captured at select-time
|
|
30
|
+
- New zustand store `useCompareStore` persists selection across reload (strips dirty content >500KB to keep localStorage fast)
|
|
31
|
+
- Auto-clears selection on project switch via store subscription
|
|
32
|
+
- New keybinding action `compare-files` with customizable default `Mod+Alt+D` in Settings > Keybindings
|
|
33
|
+
|
|
26
34
|
- **Lazy-Load File Tree + Palette Index** — Instant project opening on large codebases
|
|
27
35
|
- Backend: `GET /api/project/:name/files/list?path=<rel>` for 1-level directory listing with gitignore decoration
|
|
28
36
|
- Backend: `GET /api/project/:name/files/index` for flat full-project index (cached, watcher-invalidated)
|
package/package.json
CHANGED
|
@@ -104,6 +104,12 @@ function globPatternToRegex(pattern: string): RegExp {
|
|
|
104
104
|
// Strip leading ./
|
|
105
105
|
if (p.startsWith("./")) p = p.slice(2);
|
|
106
106
|
|
|
107
|
+
// Leading `**/` should match zero-or-more path segments, INCLUDING root.
|
|
108
|
+
// So `**/.git` matches both `.git` (at root) and `src/.git` (nested).
|
|
109
|
+
// Strip `**/` prefix here so it doesn't force a leading path; we handle it via optional group below.
|
|
110
|
+
const hasStarstarPrefix = p.startsWith("**/");
|
|
111
|
+
if (hasStarstarPrefix) p = p.slice(3);
|
|
112
|
+
|
|
107
113
|
const escaped = p
|
|
108
114
|
.replace(/[.+^${}()|[\]]/g, "\\$&") // escape regex special chars (not * ?)
|
|
109
115
|
.replace(/\*\*/g, "\x00") // temp: ** placeholder
|
|
@@ -111,10 +117,17 @@ function globPatternToRegex(pattern: string): RegExp {
|
|
|
111
117
|
.replace(/\x00/g, ".*") // ** = any path
|
|
112
118
|
.replace(/\?/g, "[^/]"); // ? = single non-slash char
|
|
113
119
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
120
|
+
let re: RegExp;
|
|
121
|
+
if (hasStarstarPrefix) {
|
|
122
|
+
// `**/X` → match X at any depth including root: `(^|.*/)X(/|$)`
|
|
123
|
+
re = new RegExp(`(^|.*/)${escaped}(/|$)`);
|
|
124
|
+
} else if (p.includes("/")) {
|
|
125
|
+
// Anchored pattern with explicit path
|
|
126
|
+
re = new RegExp(`^${escaped}(/|$)`);
|
|
127
|
+
} else {
|
|
128
|
+
// Pattern with no slash (e.g. *.log) → match at any depth
|
|
129
|
+
re = new RegExp(`(^|/)${escaped}(/|$)`);
|
|
130
|
+
}
|
|
118
131
|
|
|
119
132
|
regexCache.set(pattern, re);
|
|
120
133
|
return re;
|
|
@@ -151,20 +151,24 @@ function walkForIndex(
|
|
|
151
151
|
const relPosix = relPath.split("\\").join("/");
|
|
152
152
|
|
|
153
153
|
// Apply glob exclusion (check full relative path and bare entry name)
|
|
154
|
+
// These are HARD excludes — .git, node_modules, dist, etc.
|
|
154
155
|
if (matchesGlob(relPosix, allExclude)) continue;
|
|
155
156
|
if (matchesGlob(entry.name, allExclude)) continue;
|
|
156
157
|
|
|
157
|
-
// Apply gitignore rules
|
|
158
|
+
// Apply gitignore rules — SOFT exclude for files (include with isIgnored flag),
|
|
159
|
+
// HARD exclude for directories (skip recursion to avoid walking huge gitignored dirs).
|
|
160
|
+
let isIgnored = false;
|
|
158
161
|
if (ig) {
|
|
159
162
|
const checkPath = entry.isDirectory() ? `${relPosix}/` : relPosix;
|
|
160
|
-
|
|
163
|
+
isIgnored = ig.ignores(checkPath) || ig.ignores(relPosix);
|
|
164
|
+
if (isIgnored && entry.isDirectory()) continue;
|
|
161
165
|
}
|
|
162
166
|
|
|
163
167
|
if (entry.isDirectory()) {
|
|
164
168
|
results.push({ path: relPosix, name: entry.name, type: "directory" });
|
|
165
169
|
walkForIndex(rootPath, fullPath, allExclude, ig, results);
|
|
166
170
|
} else {
|
|
167
|
-
results.push({ path: relPosix, name: entry.name, type: "file" });
|
|
171
|
+
results.push({ path: relPosix, name: entry.name, type: "file", ...(isIgnored && { isIgnored: true }) });
|
|
168
172
|
}
|
|
169
173
|
}
|
|
170
174
|
}
|
package/src/types/project.ts
CHANGED
|
@@ -25,6 +25,8 @@ export interface FileEntry {
|
|
|
25
25
|
path: string;
|
|
26
26
|
name: string;
|
|
27
27
|
type: "file" | "directory";
|
|
28
|
+
/** True if file is excluded by .gitignore but still surfaced in palette for discoverability (e.g. .env) */
|
|
29
|
+
isIgnored?: boolean;
|
|
28
30
|
}
|
|
29
31
|
|
|
30
32
|
/** Entry returned by /files/list (single directory level) */
|
package/src/web/app.tsx
CHANGED
|
@@ -24,6 +24,7 @@ import { useGlobalKeybindings } from "@/hooks/use-global-keybindings";
|
|
|
24
24
|
import { useNotificationBadge } from "@/hooks/use-notification-badge";
|
|
25
25
|
import { useServerReload } from "@/hooks/use-server-reload";
|
|
26
26
|
import { CommandPalette } from "@/components/layout/command-palette";
|
|
27
|
+
import { ComparePicker } from "@/components/editor/compare-picker";
|
|
27
28
|
import { BugReportPopup } from "@/components/shared/bug-report-popup";
|
|
28
29
|
import { UpgradeBanner } from "@/components/layout/upgrade-banner";
|
|
29
30
|
import { ImageOverlay } from "@/components/shared/image-overlay";
|
|
@@ -299,6 +300,9 @@ export function App() {
|
|
|
299
300
|
{/* Command palette (Shift+Shift) */}
|
|
300
301
|
<CommandPalette open={paletteOpen} onClose={closePalette} initialQuery={paletteInitialQuery} />
|
|
301
302
|
|
|
303
|
+
{/* Compare Files picker (Mod+Alt+D, palette, context menus) — singleton */}
|
|
304
|
+
<ComparePicker />
|
|
305
|
+
|
|
302
306
|
{/* Global bug report popup */}
|
|
303
307
|
<BugReportPopup />
|
|
304
308
|
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import { useState, useMemo, useEffect, useRef } from "react";
|
|
2
|
+
import { Columns2, FileCode, X } from "lucide-react";
|
|
3
|
+
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
|
4
|
+
import { useTabStore } from "@/stores/tab-store";
|
|
5
|
+
import { useFileStore } from "@/stores/file-store";
|
|
6
|
+
import { useProjectStore } from "@/stores/project-store";
|
|
7
|
+
import { useCompareStore, type CompareSelection } from "@/stores/compare-store";
|
|
8
|
+
import { openCompareTab } from "@/lib/open-compare-tab";
|
|
9
|
+
import { basename, cn } from "@/lib/utils";
|
|
10
|
+
import { scoreFileSearch, compareScores } from "@/lib/score-file-search";
|
|
11
|
+
|
|
12
|
+
interface Candidate {
|
|
13
|
+
id: string;
|
|
14
|
+
path: string;
|
|
15
|
+
label: string;
|
|
16
|
+
source: "tab" | "file";
|
|
17
|
+
dirtyContent?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface ComparePickerProps {
|
|
21
|
+
/** Controlled mode: parent manages open state. Omit both to use singleton/event mode. */
|
|
22
|
+
open?: boolean;
|
|
23
|
+
onOpenChange?: (o: boolean) => void;
|
|
24
|
+
/** If provided, dialog pre-seeds Side A. Ignored in singleton mode (reads from store on open). */
|
|
25
|
+
initialA?: CompareSelection | null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const MAX_RESULTS = 50;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* File-compare picker.
|
|
32
|
+
*
|
|
33
|
+
* Two modes:
|
|
34
|
+
* - Controlled: pass `open`+`onOpenChange` (for tests / programmatic callers).
|
|
35
|
+
* - Singleton: mount once at app root with no props — listens for
|
|
36
|
+
* `window` event `open-compare-picker` and seeds Side A from `useCompareStore`.
|
|
37
|
+
*/
|
|
38
|
+
export function ComparePicker({ open: openProp, onOpenChange, initialA }: ComparePickerProps = {}) {
|
|
39
|
+
const controlled = openProp !== undefined;
|
|
40
|
+
const [internalOpen, setInternalOpen] = useState(false);
|
|
41
|
+
const open = controlled ? openProp : internalOpen;
|
|
42
|
+
const setOpen = (o: boolean) => {
|
|
43
|
+
if (controlled) onOpenChange?.(o);
|
|
44
|
+
else setInternalOpen(o);
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const [localA, setLocalA] = useState<CompareSelection | null>(initialA ?? null);
|
|
48
|
+
|
|
49
|
+
// Singleton mode: listen for global event, seed A from store
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
if (controlled) return;
|
|
52
|
+
function onEvent() {
|
|
53
|
+
setLocalA(useCompareStore.getState().selection);
|
|
54
|
+
setInternalOpen(true);
|
|
55
|
+
}
|
|
56
|
+
window.addEventListener("open-compare-picker", onEvent);
|
|
57
|
+
return () => window.removeEventListener("open-compare-picker", onEvent);
|
|
58
|
+
}, [controlled]);
|
|
59
|
+
const [query, setQuery] = useState("");
|
|
60
|
+
const [activeIndex, setActiveIndex] = useState(0);
|
|
61
|
+
const [error, setError] = useState<string | null>(null);
|
|
62
|
+
const inputRef = useRef<HTMLInputElement>(null);
|
|
63
|
+
|
|
64
|
+
const tabs = useTabStore((s) => s.tabs);
|
|
65
|
+
const fileIndex = useFileStore((s) => s.fileIndex);
|
|
66
|
+
const activeProject = useProjectStore((s) => s.activeProject);
|
|
67
|
+
|
|
68
|
+
useEffect(() => {
|
|
69
|
+
if (!open) return;
|
|
70
|
+
// In controlled mode, sync A from prop. In singleton mode, event handler
|
|
71
|
+
// already populated localA — don't clobber it here.
|
|
72
|
+
if (controlled) setLocalA(initialA ?? null);
|
|
73
|
+
setQuery("");
|
|
74
|
+
setActiveIndex(0);
|
|
75
|
+
setError(null);
|
|
76
|
+
// Focus input after dialog mounts
|
|
77
|
+
setTimeout(() => inputRef.current?.focus(), 50);
|
|
78
|
+
}, [open, initialA, controlled]);
|
|
79
|
+
|
|
80
|
+
const candidates = useMemo<Candidate[]>(() => {
|
|
81
|
+
const tabCands: Candidate[] = tabs
|
|
82
|
+
.filter((t) => t.type === "editor" && t.metadata?.filePath)
|
|
83
|
+
.map((t) => ({
|
|
84
|
+
id: `tab:${t.id}`,
|
|
85
|
+
path: t.metadata!.filePath as string,
|
|
86
|
+
label: basename(t.metadata!.filePath as string),
|
|
87
|
+
source: "tab",
|
|
88
|
+
dirtyContent: t.metadata!.unsavedContent as string | undefined,
|
|
89
|
+
}));
|
|
90
|
+
const seenPaths = new Set(tabCands.map((c) => c.path));
|
|
91
|
+
const fileCands: Candidate[] = fileIndex
|
|
92
|
+
.filter((f) => f.type === "file" && !seenPaths.has(f.path))
|
|
93
|
+
.map((f) => ({
|
|
94
|
+
id: `file:${f.path}`,
|
|
95
|
+
path: f.path,
|
|
96
|
+
label: f.name,
|
|
97
|
+
source: "file",
|
|
98
|
+
}));
|
|
99
|
+
return [...tabCands, ...fileCands];
|
|
100
|
+
}, [tabs, fileIndex]);
|
|
101
|
+
|
|
102
|
+
const filtered = useMemo<Candidate[]>(() => {
|
|
103
|
+
if (!query.trim()) return candidates.slice(0, MAX_RESULTS);
|
|
104
|
+
const scored = candidates
|
|
105
|
+
.map((c) => {
|
|
106
|
+
const score = scoreFileSearch(query, c.label, c.path);
|
|
107
|
+
return score ? { c, score } : null;
|
|
108
|
+
})
|
|
109
|
+
.filter((x): x is { c: Candidate; score: ReturnType<typeof scoreFileSearch> & {} } => x !== null)
|
|
110
|
+
.sort((a, b) => compareScores(a.score, b.score))
|
|
111
|
+
.slice(0, MAX_RESULTS)
|
|
112
|
+
.map((x) => x.c);
|
|
113
|
+
return scored;
|
|
114
|
+
}, [candidates, query]);
|
|
115
|
+
|
|
116
|
+
useEffect(() => {
|
|
117
|
+
if (activeIndex >= filtered.length) setActiveIndex(Math.max(0, filtered.length - 1));
|
|
118
|
+
}, [filtered, activeIndex]);
|
|
119
|
+
|
|
120
|
+
// Guards against rapid double-invoke (Enter spam, double-click) while the
|
|
121
|
+
// openCompareTab promise is in flight — ref so a second sync call sees it.
|
|
122
|
+
const pickingRef = useRef(false);
|
|
123
|
+
|
|
124
|
+
async function handlePick(c: Candidate) {
|
|
125
|
+
if (!activeProject) return;
|
|
126
|
+
if (!localA) {
|
|
127
|
+
setLocalA({
|
|
128
|
+
filePath: c.path,
|
|
129
|
+
projectName: activeProject.name,
|
|
130
|
+
dirtyContent: c.dirtyContent,
|
|
131
|
+
label: c.label,
|
|
132
|
+
});
|
|
133
|
+
setQuery("");
|
|
134
|
+
setActiveIndex(0);
|
|
135
|
+
inputRef.current?.focus();
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
if (pickingRef.current) return;
|
|
139
|
+
pickingRef.current = true;
|
|
140
|
+
try {
|
|
141
|
+
await openCompareTab(
|
|
142
|
+
{ path: localA.filePath, dirtyContent: localA.dirtyContent },
|
|
143
|
+
{ path: c.path, dirtyContent: c.dirtyContent },
|
|
144
|
+
activeProject.name,
|
|
145
|
+
);
|
|
146
|
+
useCompareStore.getState().clearSelection();
|
|
147
|
+
setOpen(false);
|
|
148
|
+
} catch (err) {
|
|
149
|
+
setError(err instanceof Error ? err.message : "Compare failed");
|
|
150
|
+
} finally {
|
|
151
|
+
pickingRef.current = false;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function handleKeyDown(e: React.KeyboardEvent) {
|
|
156
|
+
if (e.key === "ArrowDown") {
|
|
157
|
+
e.preventDefault();
|
|
158
|
+
setActiveIndex((i) => Math.min(filtered.length - 1, i + 1));
|
|
159
|
+
} else if (e.key === "ArrowUp") {
|
|
160
|
+
e.preventDefault();
|
|
161
|
+
setActiveIndex((i) => Math.max(0, i - 1));
|
|
162
|
+
} else if (e.key === "Enter") {
|
|
163
|
+
e.preventDefault();
|
|
164
|
+
const pick = filtered[activeIndex];
|
|
165
|
+
if (pick) handlePick(pick);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return (
|
|
170
|
+
<Dialog open={open} onOpenChange={setOpen}>
|
|
171
|
+
<DialogContent className="max-w-lg p-0 gap-0 overflow-hidden">
|
|
172
|
+
<DialogHeader className="px-4 pt-4 pb-2">
|
|
173
|
+
<DialogTitle className="flex items-center gap-2 text-sm">
|
|
174
|
+
<Columns2 className="size-4" />
|
|
175
|
+
Compare Files
|
|
176
|
+
</DialogTitle>
|
|
177
|
+
</DialogHeader>
|
|
178
|
+
|
|
179
|
+
{/* Side A chip */}
|
|
180
|
+
<div className="px-4 pb-2">
|
|
181
|
+
{localA ? (
|
|
182
|
+
<div className="flex items-center gap-2 text-xs bg-muted rounded px-2 py-1 w-fit max-w-full">
|
|
183
|
+
<FileCode className="size-3.5 shrink-0" />
|
|
184
|
+
<span className="truncate" title={localA.filePath}>{localA.label}</span>
|
|
185
|
+
<button
|
|
186
|
+
type="button"
|
|
187
|
+
onClick={() => setLocalA(null)}
|
|
188
|
+
className="hover:bg-surface-elevated rounded p-0.5"
|
|
189
|
+
aria-label="Clear first file"
|
|
190
|
+
>
|
|
191
|
+
<X className="size-3" />
|
|
192
|
+
</button>
|
|
193
|
+
</div>
|
|
194
|
+
) : (
|
|
195
|
+
<p className="text-xs text-muted-foreground">Pick first file, then second.</p>
|
|
196
|
+
)}
|
|
197
|
+
</div>
|
|
198
|
+
|
|
199
|
+
{/* Search input */}
|
|
200
|
+
<input
|
|
201
|
+
ref={inputRef}
|
|
202
|
+
type="text"
|
|
203
|
+
value={query}
|
|
204
|
+
onChange={(e) => { setQuery(e.target.value); setActiveIndex(0); }}
|
|
205
|
+
onKeyDown={handleKeyDown}
|
|
206
|
+
placeholder={localA ? "Search for file B..." : "Search for file A..."}
|
|
207
|
+
className="w-full px-4 py-2 bg-transparent border-y border-border text-sm outline-none"
|
|
208
|
+
/>
|
|
209
|
+
|
|
210
|
+
{error && (
|
|
211
|
+
<div className="px-4 py-2 text-xs text-destructive border-b border-border">{error}</div>
|
|
212
|
+
)}
|
|
213
|
+
|
|
214
|
+
{/* Results list */}
|
|
215
|
+
<div className="max-h-[50vh] md:max-h-80 overflow-y-auto">
|
|
216
|
+
{filtered.length === 0 ? (
|
|
217
|
+
<div className="px-4 py-6 text-center text-xs text-muted-foreground">
|
|
218
|
+
{candidates.length === 0 ? "No files available" : "No matches"}
|
|
219
|
+
</div>
|
|
220
|
+
) : (
|
|
221
|
+
filtered.map((c, i) => (
|
|
222
|
+
<button
|
|
223
|
+
key={c.id}
|
|
224
|
+
type="button"
|
|
225
|
+
onClick={() => handlePick(c)}
|
|
226
|
+
onMouseEnter={() => setActiveIndex(i)}
|
|
227
|
+
className={cn(
|
|
228
|
+
"w-full flex items-center gap-2 px-4 py-1.5 text-left text-sm",
|
|
229
|
+
"hover:bg-surface-elevated transition-colors",
|
|
230
|
+
i === activeIndex && "bg-surface-elevated",
|
|
231
|
+
)}
|
|
232
|
+
>
|
|
233
|
+
<FileCode className="size-3.5 shrink-0 text-text-secondary" />
|
|
234
|
+
<span className="truncate">{c.label}</span>
|
|
235
|
+
<span className="text-xs text-muted-foreground truncate ml-auto" title={c.path}>
|
|
236
|
+
{c.source === "tab" ? "open" : c.path}
|
|
237
|
+
</span>
|
|
238
|
+
</button>
|
|
239
|
+
))
|
|
240
|
+
)}
|
|
241
|
+
</div>
|
|
242
|
+
</DialogContent>
|
|
243
|
+
</Dialog>
|
|
244
|
+
);
|
|
245
|
+
}
|