@hienlh/ppm 0.13.2 → 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 +31 -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--hRMnXRZ.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-Dlo25nmt.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-DcBl6OkV.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-D7bGVSEd.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-CfkqnhXJ.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-DyAm7zuA.js → markdown-renderer-DwINRWo4.js} +3 -3
- package/dist/web/assets/packet-RMMSAZCW-DpzHf4xp.js +1 -0
- package/dist/web/assets/{pdf-preview-CZPcuy5c.js → pdf-preview-CqoQE09t.js} +1 -1
- package/dist/web/assets/pie-UPGHQEXC-BpzFCKJ8.js +1 -0
- package/dist/web/assets/{port-forwarding-tab-3RNozlZ5.js → port-forwarding-tab-De7qxkjp.js} +1 -1
- package/dist/web/assets/{postgres-viewer-CXJv4TXc.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-Dfz71RGb.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/providers/claude-agent-sdk.ts +5 -2
- package/src/server/routes/chat.ts +10 -1
- package/src/server/ws/chat.ts +29 -2
- package/src/services/file-filter.service.ts +17 -4
- package/src/services/file-list-index.service.ts +7 -3
- package/src/types/chat.ts +1 -1
- package/src/types/project.ts +2 -0
- package/src/web/app.tsx +4 -0
- package/src/web/components/chat/message-list.tsx +6 -5
- 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 +66 -13
- package/src/web/components/layout/draggable-tab.tsx +13 -5
- package/src/web/components/layout/tab-bar.tsx +101 -27
- package/src/web/hooks/use-chat.ts +6 -0
- package/src/web/hooks/use-global-keybindings.ts +20 -0
- package/src/web/lib/open-compare-tab.ts +76 -0
- package/src/web/lib/score-file-search.ts +41 -21
- 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-4kL3DNxf.js +0 -12
- package/dist/web/assets/code-editor-Caq5_BaF.js +0 -8
- package/dist/web/assets/columns-2-4fQcE4PF.js +0 -1
- package/dist/web/assets/diff-viewer-CCzPq1o-.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-BGFG66Gh.js +0 -27
- package/dist/web/assets/index-Bce0weeW.css +0 -2
- 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-Cnav4g2u.js +0 -1
- package/dist/web/assets/sqlite-viewer-C8WUEFhA.js +0 -1
- package/dist/web/assets/terminal-tab-CaEsMxp8.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":"0d69cc15d70d51bbdea08a77bc5db646","url":"index.html"},{"revision":"a0fb34fc84eb148d51812cd62669f20d","url":"icon-512.svg"},{"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/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/audio-preview--hRMnXRZ.js"},{"revision":null,"url":"assets/keybindings-store-B-zET-0o.js"},{"revision":null,"url":"assets/port-forwarding-tab-3RNozlZ5.js"},{"revision":null,"url":"assets/use-blob-url-e9uTXjv5.js"},{"revision":null,"url":"assets/index-Bce0weeW.css"},{"revision":null,"url":"assets/radar-KQ55EAFF-BviZcL-b.js"},{"revision":null,"url":"assets/vendor-xterm-CU2c3f0A.js"},{"revision":null,"url":"assets/input-Dk49gO8E.js"},{"revision":null,"url":"assets/sqlite-viewer-C8WUEFhA.js"},{"revision":null,"url":"assets/KaTeX_Main-Regular-B22Nviop.woff2"},{"revision":null,"url":"assets/refresh-cw-LlbZDJpO.js"},{"revision":null,"url":"assets/diff-viewer-CCzPq1o-.js"},{"revision":null,"url":"assets/pie-UPGHQEXC-D6S2MqVT.js"},{"revision":null,"url":"assets/chat-tab-4kL3DNxf.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/code-editor-Caq5_BaF.js"},{"revision":null,"url":"assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2"},{"revision":null,"url":"assets/vendor-markdown-0Mxgxy0L.js"},{"revision":null,"url":"assets/api-client-Dvzcc_EO.js"},{"revision":null,"url":"assets/database-viewer-DcBl6OkV.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/packet-RMMSAZCW-tx2n5Qry.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/markdown-renderer-DyAm7zuA.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/file-exclamation-point-BwzaQ50n.js"},{"revision":null,"url":"assets/file-store-BrbCNyLm.js"},{"revision":null,"url":"assets/extension-webview-D7bGVSEd.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/postgres-viewer-CXJv4TXc.js"},{"revision":null,"url":"assets/KaTeX_Size2-Regular-Dy4dx90m.woff2"},{"revision":null,"url":"assets/pdf-preview-CZPcuy5c.js"},{"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/index-BGFG66Gh.js"},{"revision":null,"url":"assets/settings-tab-Cnav4g2u.js"},{"revision":null,"url":"assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2"},{"revision":null,"url":"assets/code-CuravVys.js"},{"revision":null,"url":"assets/terminal-tab-CaEsMxp8.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/image-preview-CfkqnhXJ.js"},{"revision":null,"url":"assets/conflict-editor-Dlo25nmt.js"},{"revision":null,"url":"assets/esm-K1XIK4vc.js"},{"revision":null,"url":"assets/gitGraph-HDMCJU4V-BxhdxFgj.js"},{"revision":null,"url":"assets/vendor-xterm-BrP-ENHg.css"},{"revision":null,"url":"assets/video-preview-Dfz71RGb.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
|
@@ -1535,7 +1535,9 @@ export class ClaudeAgentSdkProvider implements AIProvider {
|
|
|
1535
1535
|
|
|
1536
1536
|
|
|
1537
1537
|
/** Abort and fully teardown the streaming session — user must resume to continue */
|
|
1538
|
-
abortQuery(sessionId: string): void {
|
|
1538
|
+
abortQuery(sessionId: string, source = "unknown"): void {
|
|
1539
|
+
// Capture stack to identify caller during debugging intermittent abort bugs
|
|
1540
|
+
const stack = new Error().stack?.split("\n").slice(2, 5).join(" | ").replace(/\s+/g, " ") ?? "no-stack";
|
|
1539
1541
|
const ss = this.streamingSessions.get(sessionId);
|
|
1540
1542
|
if (ss) {
|
|
1541
1543
|
// Signal generator to end, then close the query (kills bun subprocess)
|
|
@@ -1543,7 +1545,7 @@ export class ClaudeAgentSdkProvider implements AIProvider {
|
|
|
1543
1545
|
ss.query.close();
|
|
1544
1546
|
this.streamingSessions.delete(sessionId);
|
|
1545
1547
|
this.activeQueries.delete(sessionId);
|
|
1546
|
-
console.log(`[sdk] abortQuery: closed streaming session=${sessionId}`);
|
|
1548
|
+
console.log(`[sdk] abortQuery: closed streaming session=${sessionId} source=${source} stack=${stack}`);
|
|
1547
1549
|
return;
|
|
1548
1550
|
}
|
|
1549
1551
|
// Non-streaming fallback
|
|
@@ -1551,6 +1553,7 @@ export class ClaudeAgentSdkProvider implements AIProvider {
|
|
|
1551
1553
|
if (q) {
|
|
1552
1554
|
q.close();
|
|
1553
1555
|
this.activeQueries.delete(sessionId);
|
|
1556
|
+
console.log(`[sdk] abortQuery: closed non-streaming session=${sessionId} source=${source}`);
|
|
1554
1557
|
}
|
|
1555
1558
|
}
|
|
1556
1559
|
|
|
@@ -361,7 +361,16 @@ chatRoutes.get("/sessions/:id/debug", (c) => {
|
|
|
361
361
|
const jsonlDir = encodedCwd ? resolve(homedir, ".claude", "projects", encodedCwd) : "";
|
|
362
362
|
const jsonlPath = jsonlDir ? resolve(jsonlDir, `${sessionId}.jsonl`) : "";
|
|
363
363
|
const jsonlExists = jsonlPath ? existsSync(jsonlPath) : false;
|
|
364
|
-
|
|
364
|
+
// PPM session ID == SDK session ID (canonical — see claude-agent-sdk.ts:728).
|
|
365
|
+
// Return both fields so FE debug UI shows them clearly; they are the same value.
|
|
366
|
+
return c.json(ok({
|
|
367
|
+
ppmSessionId: sessionId,
|
|
368
|
+
sdkSessionId: sessionId,
|
|
369
|
+
sessionId,
|
|
370
|
+
jsonlPath: jsonlExists ? jsonlPath : null,
|
|
371
|
+
jsonlDir,
|
|
372
|
+
projectPath,
|
|
373
|
+
}));
|
|
365
374
|
});
|
|
366
375
|
|
|
367
376
|
/** GET /chat/pre-compact-messages — read and parse a JSONL transcript file (for expand-compact feature) */
|
package/src/server/ws/chat.ts
CHANGED
|
@@ -50,6 +50,8 @@ interface SessionEntry {
|
|
|
50
50
|
teamNames: Set<string>;
|
|
51
51
|
/** toolUseId of a pending TeamCreate call */
|
|
52
52
|
pendingTeamCreate?: string;
|
|
53
|
+
/** Compact indicator state — sticky until turn ends or boundary received, synced on reconnect */
|
|
54
|
+
compactStatus?: "compacting" | null;
|
|
53
55
|
}
|
|
54
56
|
|
|
55
57
|
/** Tracks active sessions — persists even when FE disconnects */
|
|
@@ -262,8 +264,12 @@ async function startSessionConsumer(sessionId: string, providerId: string, conte
|
|
|
262
264
|
if (evType === "system") {
|
|
263
265
|
const sub = (ev as any).subtype;
|
|
264
266
|
if (sub === "compacting") {
|
|
267
|
+
entry.compactStatus = "compacting";
|
|
268
|
+
console.log(`[chat] session=${sessionId} compact_status=compacting (persisted on entry)`);
|
|
265
269
|
broadcast(sessionId, { type: "compact_status", status: "compacting" });
|
|
266
270
|
} else if (sub === "compact_done") {
|
|
271
|
+
entry.compactStatus = null;
|
|
272
|
+
console.log(`[chat] session=${sessionId} compact_status=done (via compact_boundary)`);
|
|
267
273
|
broadcast(sessionId, { type: "compact_status", status: "done" });
|
|
268
274
|
}
|
|
269
275
|
if (!firstEventReceived) {
|
|
@@ -415,6 +421,14 @@ async function startSessionConsumer(sessionId: string, providerId: string, conte
|
|
|
415
421
|
if (evType === "done") {
|
|
416
422
|
entry.turnEvents = [];
|
|
417
423
|
entry.pendingApprovalEvent = undefined;
|
|
424
|
+
// Clear stale compact status if turn ended without compact_boundary.
|
|
425
|
+
// SDK may emit `status: compacting` without a matching boundary (deferred,
|
|
426
|
+
// resolved, or errored); without this clear, UI shows stuck "Compacting…".
|
|
427
|
+
if (entry.compactStatus === "compacting") {
|
|
428
|
+
entry.compactStatus = null;
|
|
429
|
+
console.log(`[chat] session=${sessionId} compact_status=done (cleared on turn done without boundary)`);
|
|
430
|
+
broadcast(sessionId, { type: "compact_status", status: "done" });
|
|
431
|
+
}
|
|
418
432
|
setPhase(sessionId, "idle");
|
|
419
433
|
// Reset heartbeat tracking for next turn
|
|
420
434
|
firstEventReceived = false;
|
|
@@ -432,6 +446,12 @@ async function startSessionConsumer(sessionId: string, providerId: string, conte
|
|
|
432
446
|
if (heartbeat) clearInterval(heartbeat);
|
|
433
447
|
entry.isStreamingActive = false;
|
|
434
448
|
entry.turnEvents = [];
|
|
449
|
+
// Force-clear compact status on stream teardown (error, close, etc.)
|
|
450
|
+
if (entry.compactStatus === "compacting") {
|
|
451
|
+
entry.compactStatus = null;
|
|
452
|
+
console.log(`[chat] session=${sessionId} compact_status=done (cleared on stream teardown)`);
|
|
453
|
+
broadcast(sessionId, { type: "compact_status", status: "done" });
|
|
454
|
+
}
|
|
435
455
|
setPhase(sessionId, "idle");
|
|
436
456
|
entry.pendingApprovalEvent = undefined;
|
|
437
457
|
// Cleanup bash output spies
|
|
@@ -488,6 +508,7 @@ export const chatWebSocket = {
|
|
|
488
508
|
phase: existing.phase,
|
|
489
509
|
pendingApproval: existing.pendingApprovalEvent ?? null,
|
|
490
510
|
sessionTitle: session?.title || null,
|
|
511
|
+
compactStatus: existing.compactStatus ?? null,
|
|
491
512
|
}));
|
|
492
513
|
|
|
493
514
|
// If actively streaming, send buffered turn events for reconnect sync
|
|
@@ -528,6 +549,7 @@ export const chatWebSocket = {
|
|
|
528
549
|
isStreamingActive: false,
|
|
529
550
|
teamWatchers: new Map(),
|
|
530
551
|
teamNames: new Set(),
|
|
552
|
+
compactStatus: null,
|
|
531
553
|
};
|
|
532
554
|
activeSessions.set(sessionId, newEntry);
|
|
533
555
|
setupClientPing(newEntry, ws);
|
|
@@ -539,6 +561,7 @@ export const chatWebSocket = {
|
|
|
539
561
|
phase: "idle",
|
|
540
562
|
pendingApproval: null,
|
|
541
563
|
sessionTitle: session?.title || null,
|
|
564
|
+
compactStatus: null,
|
|
542
565
|
}));
|
|
543
566
|
|
|
544
567
|
// Async: resolve title from SDK if in-memory title is generic (DB title takes priority)
|
|
@@ -580,7 +603,7 @@ export const chatWebSocket = {
|
|
|
580
603
|
const newEntry: SessionEntry = {
|
|
581
604
|
providerId: pid, clients: new Set([ws]), projectPath: pp, projectName: pn,
|
|
582
605
|
pingIntervals: new Map(), phase: "idle", turnEvents: [], isStreamingActive: false,
|
|
583
|
-
teamWatchers: new Map(), teamNames: new Set(),
|
|
606
|
+
teamWatchers: new Map(), teamNames: new Set(), compactStatus: null,
|
|
584
607
|
};
|
|
585
608
|
activeSessions.set(sessionId, newEntry);
|
|
586
609
|
setupClientPing(newEntry, ws);
|
|
@@ -605,6 +628,7 @@ export const chatWebSocket = {
|
|
|
605
628
|
phase: entry.phase,
|
|
606
629
|
pendingApproval: entry.pendingApprovalEvent ?? null,
|
|
607
630
|
sessionTitle: chatService.getSession(sessionId)?.title || null,
|
|
631
|
+
compactStatus: entry.compactStatus ?? null,
|
|
608
632
|
}));
|
|
609
633
|
if (entry.phase !== "idle") {
|
|
610
634
|
sendTurnEvents(sessionId, ws);
|
|
@@ -705,7 +729,10 @@ export const chatWebSocket = {
|
|
|
705
729
|
} else if (parsed.type === "cancel") {
|
|
706
730
|
// Fully teardown streaming session — user must resume to continue
|
|
707
731
|
const provider = providerRegistry.get(providerId);
|
|
708
|
-
|
|
732
|
+
const phase = entry?.phase ?? "unknown";
|
|
733
|
+
console.log(`[chat] session=${sessionId} WS cancel received from FE (phase=${phase})`);
|
|
734
|
+
logSessionEvent(sessionId, "CANCEL", `WS cancel from FE (phase=${phase})`);
|
|
735
|
+
provider?.abortQuery?.(sessionId, "ws_cancel");
|
|
709
736
|
} else if (parsed.type === "approval_response") {
|
|
710
737
|
const provider = providerRegistry.get(providerId);
|
|
711
738
|
if (provider && typeof provider.resolveApproval === "function") {
|
|
@@ -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/chat.ts
CHANGED
|
@@ -24,7 +24,7 @@ export interface AIProvider {
|
|
|
24
24
|
// Optional capabilities — providers implement what they support
|
|
25
25
|
resolveApproval?(requestId: string, approved: boolean, data?: unknown): void;
|
|
26
26
|
onToolApproval?: (callback: ToolApprovalHandler) => void;
|
|
27
|
-
abortQuery?(sessionId: string): void;
|
|
27
|
+
abortQuery?(sessionId: string, source?: string): void;
|
|
28
28
|
getMessages?(sessionId: string): Promise<ChatMessage[]>;
|
|
29
29
|
listSessionsByDir?(dir: string, opts?: { limit?: number; offset?: number }): Promise<SessionInfo[]>;
|
|
30
30
|
ensureProjectPath?(sessionId: string, path: string): void;
|
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
|
|
|
@@ -348,7 +348,11 @@ function UserBubble({ content, messageId, projectName, onFork, onExpandCompact,
|
|
|
348
348
|
const parsed = parseUserAttachments(content);
|
|
349
349
|
const { cleanText: noSysTags, tags } = extractSystemTags(parsed.text);
|
|
350
350
|
const { command, cleanText } = parseCommandTags(noSysTags);
|
|
351
|
-
|
|
351
|
+
// Merge command args into body text so line-clamp + Show more applies uniformly
|
|
352
|
+
const bodyText = command?.args
|
|
353
|
+
? (cleanText ? `${command.args}\n\n${cleanText}` : command.args)
|
|
354
|
+
: cleanText;
|
|
355
|
+
return { files: parsed.files, text: bodyText, tags, command, jsonlPath: extractJsonlPath(cleanText) };
|
|
352
356
|
}, [content]);
|
|
353
357
|
|
|
354
358
|
// Pre-compact expansion state — local per button instance
|
|
@@ -394,16 +398,13 @@ function UserBubble({ content, messageId, projectName, onFork, onExpandCompact,
|
|
|
394
398
|
{/* System tags as badges */}
|
|
395
399
|
{tags.length > 0 && <SystemTagBadges tags={tags} />}
|
|
396
400
|
|
|
397
|
-
{/* Slash command chip */}
|
|
401
|
+
{/* Slash command chip — args rendered in body for expand/collapse support */}
|
|
398
402
|
{command && (
|
|
399
403
|
<div className="flex items-center gap-1.5 mb-0.5">
|
|
400
404
|
<span className="inline-flex items-center gap-1 rounded-md bg-primary/15 border border-primary/20 px-2 py-0.5 text-xs font-medium text-primary">
|
|
401
405
|
<Slash className="size-3 shrink-0" />
|
|
402
406
|
{command.name}
|
|
403
407
|
</span>
|
|
404
|
-
{command.args && (
|
|
405
|
-
<span className="text-xs text-text-secondary truncate max-w-80">{command.args}</span>
|
|
406
|
-
)}
|
|
407
408
|
</div>
|
|
408
409
|
)}
|
|
409
410
|
|