@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.
Files changed (103) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/assets/skills/ppm/SKILL.md +1 -1
  3. package/assets/skills/ppm/references/http-api.md +1 -1
  4. package/dist/web/assets/{ai-settings-section-QE6nBNgN.js → ai-settings-section-DeW4WN43.js} +1 -1
  5. package/dist/web/assets/{api-settings-DAk7D-NP.js → api-settings-t7Leca7J.js} +1 -1
  6. package/dist/web/assets/architecture-PBZL5I3N-Dy3PgD6O.js +1 -0
  7. package/dist/web/assets/{audio-preview--hRMnXRZ.js → audio-preview-BSAe2WQB.js} +1 -1
  8. package/dist/web/assets/chat-tab-UFEFOnpl.js +12 -0
  9. package/dist/web/assets/code-editor-BJ1tSNWA.js +8 -0
  10. package/dist/web/assets/{conflict-editor-Dlo25nmt.js → conflict-editor-CrgrMZ2F.js} +1 -1
  11. package/dist/web/assets/{csv-preview-HMSavgBb.js → csv-preview-C9qGhDlb.js} +1 -1
  12. package/dist/web/assets/{database-viewer-DcBl6OkV.js → database-viewer-e_NAkIL_.js} +2 -2
  13. package/dist/web/assets/diff-viewer-C2eOczTs.js +4 -0
  14. package/dist/web/assets/{esm-K1XIK4vc.js → esm-B3je8j5P.js} +1 -1
  15. package/dist/web/assets/{extension-webview-D7bGVSEd.js → extension-webview-B95nOfj-.js} +2 -2
  16. package/dist/web/assets/{file-store-BrbCNyLm.js → file-store-BgZggznw.js} +1 -1
  17. package/dist/web/assets/gitGraph-HDMCJU4V-Bu1SIFFq.js +1 -0
  18. package/dist/web/assets/{image-preview-CfkqnhXJ.js → image-preview-DAuPOzYl.js} +1 -1
  19. package/dist/web/assets/index-DJOjXTcq.js +27 -0
  20. package/dist/web/assets/index-DSOP0R0s.css +2 -0
  21. package/dist/web/assets/info-3K5VOQVL-DzfAxmVd.js +1 -0
  22. package/dist/web/assets/{input-Dk49gO8E.js → input-bGJExpJZ.js} +1 -1
  23. package/dist/web/assets/keybindings-store-V12kZZHO.js +1 -0
  24. package/dist/web/assets/{markdown-renderer-DyAm7zuA.js → markdown-renderer-DwINRWo4.js} +3 -3
  25. package/dist/web/assets/packet-RMMSAZCW-DpzHf4xp.js +1 -0
  26. package/dist/web/assets/{pdf-preview-CZPcuy5c.js → pdf-preview-CqoQE09t.js} +1 -1
  27. package/dist/web/assets/pie-UPGHQEXC-BpzFCKJ8.js +1 -0
  28. package/dist/web/assets/{port-forwarding-tab-3RNozlZ5.js → port-forwarding-tab-De7qxkjp.js} +1 -1
  29. package/dist/web/assets/{postgres-viewer-CXJv4TXc.js → postgres-viewer-Dd6rLb8b.js} +3 -3
  30. package/dist/web/assets/radar-KQ55EAFF-DAxWKxM4.js +1 -0
  31. package/dist/web/assets/{scroll-area-BEllam7_.js → scroll-area-D0EQpAH2.js} +1 -1
  32. package/dist/web/assets/{settings-store-BLLR7ed8.js → settings-store-CdcSAgEZ.js} +2 -2
  33. package/dist/web/assets/settings-tab-BdTEumwU.js +1 -0
  34. package/dist/web/assets/{sql-query-editor-CVAnRFbi.js → sql-query-editor-vpD0I0KG.js} +1 -1
  35. package/dist/web/assets/sqlite-viewer-Ccz2crvN.js +1 -0
  36. package/dist/web/assets/{tab-store-B3M9hjho.js → tab-store-Jvy1eZGM.js} +1 -1
  37. package/dist/web/assets/terminal-tab-D7u7wsyb.js +1 -0
  38. package/dist/web/assets/treemap-KZPCXAKY-D6dgXbAe.js +1 -0
  39. package/dist/web/assets/{use-blob-url-e9uTXjv5.js → use-blob-url-BgxxT-n_.js} +1 -1
  40. package/dist/web/assets/{use-monaco-theme-BkZDwoVd.js → use-monaco-theme-dtPsv6sh.js} +1 -1
  41. package/dist/web/assets/{vendor-mermaid-Dx86tuVP.js → vendor-mermaid-DCxaaPi4.js} +2 -2
  42. package/dist/web/assets/{video-preview-Dfz71RGb.js → video-preview-BSDzqlzk.js} +1 -1
  43. package/dist/web/index.html +17 -19
  44. package/dist/web/sw.js +1 -1
  45. package/docs/codebase-summary.md +2 -0
  46. package/docs/journals/2026-04-22-compare-files-feature-ship.md +53 -0
  47. package/docs/project-changelog.md +9 -1
  48. package/package.json +1 -1
  49. package/src/providers/claude-agent-sdk.ts +5 -2
  50. package/src/server/routes/chat.ts +10 -1
  51. package/src/server/ws/chat.ts +29 -2
  52. package/src/services/file-filter.service.ts +17 -4
  53. package/src/services/file-list-index.service.ts +7 -3
  54. package/src/types/chat.ts +1 -1
  55. package/src/types/project.ts +2 -0
  56. package/src/web/app.tsx +4 -0
  57. package/src/web/components/chat/message-list.tsx +6 -5
  58. package/src/web/components/editor/compare-picker.tsx +245 -0
  59. package/src/web/components/explorer/file-tree.tsx +42 -1
  60. package/src/web/components/layout/command-palette.tsx +66 -13
  61. package/src/web/components/layout/draggable-tab.tsx +13 -5
  62. package/src/web/components/layout/tab-bar.tsx +101 -27
  63. package/src/web/hooks/use-chat.ts +6 -0
  64. package/src/web/hooks/use-global-keybindings.ts +20 -0
  65. package/src/web/lib/open-compare-tab.ts +76 -0
  66. package/src/web/lib/score-file-search.ts +41 -21
  67. package/src/web/stores/compare-store.ts +57 -0
  68. package/src/web/stores/keybindings-store.ts +1 -0
  69. package/dist/web/assets/architecture-PBZL5I3N-DvZbltvY.js +0 -1
  70. package/dist/web/assets/chat-tab-4kL3DNxf.js +0 -12
  71. package/dist/web/assets/code-editor-Caq5_BaF.js +0 -8
  72. package/dist/web/assets/columns-2-4fQcE4PF.js +0 -1
  73. package/dist/web/assets/diff-viewer-CCzPq1o-.js +0 -4
  74. package/dist/web/assets/extension-store-3yZYn07W.js +0 -1
  75. package/dist/web/assets/gitGraph-HDMCJU4V-BxhdxFgj.js +0 -1
  76. package/dist/web/assets/index-BGFG66Gh.js +0 -27
  77. package/dist/web/assets/index-Bce0weeW.css +0 -2
  78. package/dist/web/assets/info-3K5VOQVL-BwAZ2zd8.js +0 -1
  79. package/dist/web/assets/keybindings-store-B-zET-0o.js +0 -1
  80. package/dist/web/assets/keybindings-store-DaBV6qhz.js +0 -1
  81. package/dist/web/assets/packet-RMMSAZCW-tx2n5Qry.js +0 -1
  82. package/dist/web/assets/pie-UPGHQEXC-D6S2MqVT.js +0 -1
  83. package/dist/web/assets/radar-KQ55EAFF-BviZcL-b.js +0 -1
  84. package/dist/web/assets/settings-tab-Cnav4g2u.js +0 -1
  85. package/dist/web/assets/sqlite-viewer-C8WUEFhA.js +0 -1
  86. package/dist/web/assets/terminal-tab-CaEsMxp8.js +0 -1
  87. package/dist/web/assets/treemap-KZPCXAKY-CM54VdaB.js +0 -1
  88. /package/dist/web/assets/{api-client-Dvzcc_EO.js → api-client-r4nyVy7H.js} +0 -0
  89. /package/dist/web/assets/{csv-parser--2WJNgS7.js → csv-parser-DxVplKKB.js} +0 -0
  90. /package/dist/web/assets/{database-D4DIhgi-.js → database-DCT0OjgQ.js} +0 -0
  91. /package/dist/web/assets/{dist-im4ynINo.js → dist-BqoEabX7.js} +0 -0
  92. /package/dist/web/assets/{file-exclamation-point-BwzaQ50n.js → file-exclamation-point-Baz81y5z.js} +0 -0
  93. /package/dist/web/assets/{katex-CKoArbIw.js → katex-bpagxk3Z.js} +0 -0
  94. /package/dist/web/assets/{lib-DQHnkzGy.js → lib-BqkcKGFq.js} +0 -0
  95. /package/dist/web/assets/{react-GqWghJ-L.js → react-BkWDCPD7.js} +0 -0
  96. /package/dist/web/assets/{refresh-cw-LlbZDJpO.js → refresh-cw-CSFrDtiu.js} +0 -0
  97. /package/dist/web/assets/{sql-completion-provider-C3cq9j99.js → sql-completion-provider-EzHOQLfo.js} +0 -0
  98. /package/dist/web/assets/{table-Dq575bPF.js → table-DbSviOmw.js} +0 -0
  99. /package/dist/web/assets/{text-wrap-Cn6BNQfq.js → text-wrap-DzvCTq_i.js} +0 -0
  100. /package/dist/web/assets/{trash-2-CJYoLw7Q.js → trash-2-BgDIBl6f.js} +0 -0
  101. /package/dist/web/assets/{utils-CTg5uAYR.js → utils-ChWX7pZv.js} +0 -0
  102. /package/dist/web/assets/{vendor-xterm-CU2c3f0A.js → vendor-xterm-D7SePDJp.js} +0 -0
  103. /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-BwzaQ50n.js";import"./api-client-Dvzcc_EO.js";import{I as n}from"./index-BGFG66Gh.js";import{t as r}from"./use-blob-url-e9uTXjv5.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};
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};
@@ -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-BGFG66Gh.js"></script>
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-Dx86tuVP.js">
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-CTg5uAYR.js">
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-DlFGzN8d.js">
50
- <link rel="modulepreload" crossorigin href="/assets/input-Dk49gO8E.js">
51
- <link rel="modulepreload" crossorigin href="/assets/react-GqWghJ-L.js">
52
- <link rel="modulepreload" crossorigin href="/assets/api-client-Dvzcc_EO.js">
53
- <link rel="modulepreload" crossorigin href="/assets/settings-store-BLLR7ed8.js">
54
- <link rel="modulepreload" crossorigin href="/assets/scroll-area-BEllam7_.js">
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-LlbZDJpO.js">
58
- <link rel="modulepreload" crossorigin href="/assets/trash-2-CJYoLw7Q.js">
59
- <link rel="modulepreload" crossorigin href="/assets/api-settings-DAk7D-NP.js">
60
- <link rel="modulepreload" crossorigin href="/assets/ai-settings-section-QE6nBNgN.js">
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-D4DIhgi-.js">
63
- <link rel="modulepreload" crossorigin href="/assets/extension-store-3yZYn07W.js">
64
- <link rel="modulepreload" crossorigin href="/assets/file-store-BrbCNyLm.js">
65
- <link rel="modulepreload" crossorigin href="/assets/keybindings-store-B-zET-0o.js">
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||`/`)}))});
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hienlh/ppm",
3
- "version": "0.13.2",
3
+ "version": "0.13.4",
4
4
  "description": "Personal Project Manager — mobile-first web IDE with AI assistance",
5
5
  "author": "hienlh",
6
6
  "license": "MIT",
@@ -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
- return c.json(ok({ sessionId, jsonlPath: jsonlExists ? jsonlPath : null, jsonlDir, projectPath }));
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) */
@@ -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
- provider?.abortQuery?.(sessionId);
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
- // Pattern with no slash (e.g. *.log) → match at any depth
115
- const re = p.includes("/")
116
- ? new RegExp(`^${escaped}(/|$)`)
117
- : new RegExp(`(^|/)${escaped}(/|$)`);
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
- if (ig.ignores(checkPath) || ig.ignores(relPosix)) continue;
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;
@@ -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
- return { files: parsed.files, text: cleanText, tags, command, jsonlPath: extractJsonlPath(cleanText) };
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