@hienlh/ppm 0.13.3 → 0.13.5

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 (100) hide show
  1. package/CHANGELOG.md +28 -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-R7cq1uhJ.js → audio-preview-BdRw2cYi.js} +1 -1
  8. package/dist/web/assets/chat-tab-C2NBEXEX.js +12 -0
  9. package/dist/web/assets/code-editor-BhmUC3pD.js +8 -0
  10. package/dist/web/assets/{conflict-editor-dzofjxab.js → conflict-editor-Br_CSQdA.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-5Uf8Rrls.js → database-viewer-CbIMjroK.js} +2 -2
  13. package/dist/web/assets/diff-viewer-oq0RiOpV.js +4 -0
  14. package/dist/web/assets/{esm-K1XIK4vc.js → esm-B3je8j5P.js} +1 -1
  15. package/dist/web/assets/{extension-webview-HILvTnnn.js → extension-webview-DxP22X_y.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-0cJMnFZK.js → image-preview-yX0yZtyd.js} +1 -1
  19. package/dist/web/assets/index-BJ76xcQz.css +2 -0
  20. package/dist/web/assets/index-DJQJu6Ef.js +27 -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-zxSQXdFL.js +1 -0
  24. package/dist/web/assets/{markdown-renderer-D0MrsVJB.js → markdown-renderer-DHD3HPwK.js} +3 -3
  25. package/dist/web/assets/packet-RMMSAZCW-DpzHf4xp.js +1 -0
  26. package/dist/web/assets/{pdf-preview-BBVDS-z5.js → pdf-preview-BlRtar7G.js} +1 -1
  27. package/dist/web/assets/pie-UPGHQEXC-BpzFCKJ8.js +1 -0
  28. package/dist/web/assets/{port-forwarding-tab-ByKzBs-R.js → port-forwarding-tab-DOYZIXHo.js} +1 -1
  29. package/dist/web/assets/{postgres-viewer-BnCbdR7g.js → postgres-viewer-DM6b5mZl.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-JzeC-QC7.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-IvosQxK2.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-D4xxia2I.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-CKaht6nI.js → video-preview-ClY8ALGJ.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/project-changelog.md +9 -1
  47. package/package.json +1 -1
  48. package/src/server/routes/chat.ts +2 -1
  49. package/src/services/file-filter.service.ts +17 -4
  50. package/src/services/file-list-index.service.ts +7 -3
  51. package/src/services/jsonl-transcript-parser.ts +10 -1
  52. package/src/services/supervisor.ts +35 -35
  53. package/src/types/project.ts +2 -0
  54. package/src/web/app.tsx +4 -0
  55. package/src/web/components/chat/message-list.tsx +49 -2
  56. package/src/web/components/editor/compare-picker.tsx +245 -0
  57. package/src/web/components/explorer/file-tree.tsx +42 -1
  58. package/src/web/components/layout/command-palette.tsx +31 -1
  59. package/src/web/components/layout/draggable-tab.tsx +8 -0
  60. package/src/web/components/layout/tab-bar.tsx +101 -27
  61. package/src/web/hooks/use-chat.ts +8 -1
  62. package/src/web/hooks/use-global-keybindings.ts +20 -0
  63. package/src/web/lib/open-compare-tab.ts +76 -0
  64. package/src/web/stores/compare-store.ts +57 -0
  65. package/src/web/stores/keybindings-store.ts +1 -0
  66. package/dist/web/assets/architecture-PBZL5I3N-DvZbltvY.js +0 -1
  67. package/dist/web/assets/chat-tab-umei1UkV.js +0 -12
  68. package/dist/web/assets/code-editor-BTosKXkr.js +0 -8
  69. package/dist/web/assets/columns-2-4fQcE4PF.js +0 -1
  70. package/dist/web/assets/diff-viewer-DKLeIBkK.js +0 -4
  71. package/dist/web/assets/extension-store-3yZYn07W.js +0 -1
  72. package/dist/web/assets/gitGraph-HDMCJU4V-BxhdxFgj.js +0 -1
  73. package/dist/web/assets/index-Bce0weeW.css +0 -2
  74. package/dist/web/assets/index-DDBvHVVr.js +0 -27
  75. package/dist/web/assets/info-3K5VOQVL-BwAZ2zd8.js +0 -1
  76. package/dist/web/assets/keybindings-store-B-zET-0o.js +0 -1
  77. package/dist/web/assets/keybindings-store-DaBV6qhz.js +0 -1
  78. package/dist/web/assets/packet-RMMSAZCW-tx2n5Qry.js +0 -1
  79. package/dist/web/assets/pie-UPGHQEXC-D6S2MqVT.js +0 -1
  80. package/dist/web/assets/radar-KQ55EAFF-BviZcL-b.js +0 -1
  81. package/dist/web/assets/settings-tab-BPdzUw3v.js +0 -1
  82. package/dist/web/assets/sqlite-viewer-D6mSIIx2.js +0 -1
  83. package/dist/web/assets/terminal-tab-BLIA53mt.js +0 -1
  84. package/dist/web/assets/treemap-KZPCXAKY-CM54VdaB.js +0 -1
  85. /package/dist/web/assets/{api-client-Dvzcc_EO.js → api-client-r4nyVy7H.js} +0 -0
  86. /package/dist/web/assets/{csv-parser--2WJNgS7.js → csv-parser-DxVplKKB.js} +0 -0
  87. /package/dist/web/assets/{database-D4DIhgi-.js → database-DCT0OjgQ.js} +0 -0
  88. /package/dist/web/assets/{dist-im4ynINo.js → dist-BqoEabX7.js} +0 -0
  89. /package/dist/web/assets/{file-exclamation-point-BwzaQ50n.js → file-exclamation-point-Baz81y5z.js} +0 -0
  90. /package/dist/web/assets/{katex-CKoArbIw.js → katex-bpagxk3Z.js} +0 -0
  91. /package/dist/web/assets/{lib-DQHnkzGy.js → lib-BqkcKGFq.js} +0 -0
  92. /package/dist/web/assets/{react-GqWghJ-L.js → react-BkWDCPD7.js} +0 -0
  93. /package/dist/web/assets/{refresh-cw-LlbZDJpO.js → refresh-cw-CSFrDtiu.js} +0 -0
  94. /package/dist/web/assets/{sql-completion-provider-C3cq9j99.js → sql-completion-provider-EzHOQLfo.js} +0 -0
  95. /package/dist/web/assets/{table-Dq575bPF.js → table-DbSviOmw.js} +0 -0
  96. /package/dist/web/assets/{text-wrap-Cn6BNQfq.js → text-wrap-DzvCTq_i.js} +0 -0
  97. /package/dist/web/assets/{trash-2-CJYoLw7Q.js → trash-2-BgDIBl6f.js} +0 -0
  98. /package/dist/web/assets/{utils-CTg5uAYR.js → utils-ChWX7pZv.js} +0 -0
  99. /package/dist/web/assets/{vendor-xterm-CU2c3f0A.js → vendor-xterm-D7SePDJp.js} +0 -0
  100. /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-DDBvHVVr.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-DJQJu6Ef.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-DDBvHVVr.js"></script>
42
+ <script type="module" crossorigin src="/assets/index-DJQJu6Ef.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-BJ76xcQz.css">
68
66
  <link rel="manifest" href="/manifest.webmanifest"><script id="vite-plugin-pwa:register-sw" src="/registerSW.js"></script></head>
69
67
  <body class="bg-[#0f1419] text-[#e5e7eb] font-sans antialiased">
70
68
  <div id="root"></div>
package/dist/web/sw.js CHANGED
@@ -1 +1 @@
1
- try{self[`workbox:core:7.3.0`]&&_()}catch{}var e=(e,...t)=>{let n=e;return t.length>0&&(n+=` :: ${JSON.stringify(t)}`),n},t=class extends Error{constructor(t,n){let r=e(t,n);super(r),this.name=t,this.details=n}},n={googleAnalytics:`googleAnalytics`,precache:`precache-v2`,prefix:`workbox`,runtime:`runtime`,suffix:typeof registration<`u`?registration.scope:``},r=e=>[n.prefix,e,n.suffix].filter(e=>e&&e.length>0).join(`-`),i=e=>{for(let t of Object.keys(n))e(t)},a={updateDetails:e=>{i(t=>{typeof e[t]==`string`&&(n[t]=e[t])})},getGoogleAnalyticsName:e=>e||r(n.googleAnalytics),getPrecacheName:e=>e||r(n.precache),getPrefix:()=>n.prefix,getRuntimeName:e=>e||r(n.runtime),getSuffix:()=>n.suffix};function o(e,t){let n=t();return e.waitUntil(n),n}try{self[`workbox:precaching:7.3.0`]&&_()}catch{}var s=`__WB_REVISION__`;function c(e){if(!e)throw new t(`add-to-cache-list-unexpected-type`,{entry:e});if(typeof e==`string`){let t=new URL(e,location.href);return{cacheKey:t.href,url:t.href}}let{revision:n,url:r}=e;if(!r)throw new t(`add-to-cache-list-unexpected-type`,{entry:e});if(!n){let e=new URL(r,location.href);return{cacheKey:e.href,url:e.href}}let i=new URL(r,location.href),a=new URL(r,location.href);return i.searchParams.set(s,n),{cacheKey:i.href,url:a.href}}var l=class{constructor(){this.updatedURLs=[],this.notUpdatedURLs=[],this.handlerWillStart=async({request:e,state:t})=>{t&&(t.originalRequest=e)},this.cachedResponseWillBeUsed=async({event:e,state:t,cachedResponse:n})=>{if(e.type===`install`&&t&&t.originalRequest&&t.originalRequest instanceof Request){let e=t.originalRequest.url;n?this.notUpdatedURLs.push(e):this.updatedURLs.push(e)}return n}}},u=class{constructor({precacheController:e}){this.cacheKeyWillBeUsed=async({request:e,params:t})=>{let n=t?.cacheKey||this._precacheController.getCacheKeyForURL(e.url);return n?new Request(n,{headers:e.headers}):e},this._precacheController=e}},d;function f(){if(d===void 0){let e=new Response(``);if(`body`in e)try{new Response(e.body),d=!0}catch{d=!1}d=!1}return d}async function p(e,n){let r=null;if(e.url&&(r=new URL(e.url).origin),r!==self.location.origin)throw new t(`cross-origin-copy-response`,{origin:r});let i=e.clone(),a={headers:new Headers(i.headers),status:i.status,statusText:i.statusText},o=n?n(a):a,s=f()?i.body:await i.blob();return new Response(s,o)}var m=e=>new URL(String(e),location.href).href.replace(RegExp(`^${location.origin}`),``);function h(e,t){let n=new URL(e);for(let e of t)n.searchParams.delete(e);return n.href}async function g(e,t,n,r){let i=h(t.url,n);if(t.url===i)return e.match(t,r);let a=Object.assign(Object.assign({},r),{ignoreSearch:!0}),o=await e.keys(t,a);for(let t of o)if(i===h(t.url,n))return e.match(t,r)}var v=class{constructor(){this.promise=new Promise((e,t)=>{this.resolve=e,this.reject=t})}},y=new Set;async function b(){for(let e of y)await e()}function x(e){return new Promise(t=>setTimeout(t,e))}try{self[`workbox:strategies:7.3.0`]&&_()}catch{}function S(e){return typeof e==`string`?new Request(e):e}var C=class{constructor(e,t){this._cacheKeys={},Object.assign(this,t),this.event=t.event,this._strategy=e,this._handlerDeferred=new v,this._extendLifetimePromises=[],this._plugins=[...e.plugins],this._pluginStateMap=new Map;for(let e of this._plugins)this._pluginStateMap.set(e,{});this.event.waitUntil(this._handlerDeferred.promise)}async fetch(e){let{event:n}=this,r=S(e);if(r.mode===`navigate`&&n instanceof FetchEvent&&n.preloadResponse){let e=await n.preloadResponse;if(e)return e}let i=this.hasCallback(`fetchDidFail`)?r.clone():null;try{for(let e of this.iterateCallbacks(`requestWillFetch`))r=await e({request:r.clone(),event:n})}catch(e){if(e instanceof Error)throw new t(`plugin-error-request-will-fetch`,{thrownErrorMessage:e.message})}let a=r.clone();try{let e;e=await fetch(r,r.mode===`navigate`?void 0:this._strategy.fetchOptions);for(let t of this.iterateCallbacks(`fetchDidSucceed`))e=await t({event:n,request:a,response:e});return e}catch(e){throw i&&await this.runCallbacks(`fetchDidFail`,{error:e,event:n,originalRequest:i.clone(),request:a.clone()}),e}}async fetchAndCachePut(e){let t=await this.fetch(e),n=t.clone();return this.waitUntil(this.cachePut(e,n)),t}async cacheMatch(e){let t=S(e),n,{cacheName:r,matchOptions:i}=this._strategy,a=await this.getCacheKey(t,`read`),o=Object.assign(Object.assign({},i),{cacheName:r});n=await caches.match(a,o);for(let e of this.iterateCallbacks(`cachedResponseWillBeUsed`))n=await e({cacheName:r,matchOptions:i,cachedResponse:n,request:a,event:this.event})||void 0;return n}async cachePut(e,n){let r=S(e);await x(0);let i=await this.getCacheKey(r,`write`);if(!n)throw new t(`cache-put-with-no-response`,{url:m(i.url)});let a=await this._ensureResponseSafeToCache(n);if(!a)return!1;let{cacheName:o,matchOptions:s}=this._strategy,c=await self.caches.open(o),l=this.hasCallback(`cacheDidUpdate`),u=l?await g(c,i.clone(),[`__WB_REVISION__`],s):null;try{await c.put(i,l?a.clone():a)}catch(e){if(e instanceof Error)throw e.name===`QuotaExceededError`&&await b(),e}for(let e of this.iterateCallbacks(`cacheDidUpdate`))await e({cacheName:o,oldResponse:u,newResponse:a.clone(),request:i,event:this.event});return!0}async getCacheKey(e,t){let n=`${e.url} | ${t}`;if(!this._cacheKeys[n]){let r=e;for(let e of this.iterateCallbacks(`cacheKeyWillBeUsed`))r=S(await e({mode:t,request:r,event:this.event,params:this.params}));this._cacheKeys[n]=r}return this._cacheKeys[n]}hasCallback(e){for(let t of this._strategy.plugins)if(e in t)return!0;return!1}async runCallbacks(e,t){for(let n of this.iterateCallbacks(e))await n(t)}*iterateCallbacks(e){for(let t of this._strategy.plugins)if(typeof t[e]==`function`){let n=this._pluginStateMap.get(t);yield r=>{let i=Object.assign(Object.assign({},r),{state:n});return t[e](i)}}}waitUntil(e){return this._extendLifetimePromises.push(e),e}async doneWaiting(){for(;this._extendLifetimePromises.length;){let e=this._extendLifetimePromises.splice(0),t=(await Promise.allSettled(e)).find(e=>e.status===`rejected`);if(t)throw t.reason}}destroy(){this._handlerDeferred.resolve(null)}async _ensureResponseSafeToCache(e){let t=e,n=!1;for(let e of this.iterateCallbacks(`cacheWillUpdate`))if(t=await e({request:this.request,response:t,event:this.event})||void 0,n=!0,!t)break;return n||t&&t.status!==200&&(t=void 0),t}},w=class{constructor(e={}){this.cacheName=a.getRuntimeName(e.cacheName),this.plugins=e.plugins||[],this.fetchOptions=e.fetchOptions,this.matchOptions=e.matchOptions}handle(e){let[t]=this.handleAll(e);return t}handleAll(e){e instanceof FetchEvent&&(e={event:e,request:e.request});let t=e.event,n=typeof e.request==`string`?new Request(e.request):e.request,r=`params`in e?e.params:void 0,i=new C(this,{event:t,request:n,params:r}),a=this._getResponse(i,n,t);return[a,this._awaitComplete(a,i,n,t)]}async _getResponse(e,n,r){await e.runCallbacks(`handlerWillStart`,{event:r,request:n});let i;try{if(i=await this._handle(n,e),!i||i.type===`error`)throw new t(`no-response`,{url:n.url})}catch(t){if(t instanceof Error){for(let a of e.iterateCallbacks(`handlerDidError`))if(i=await a({error:t,event:r,request:n}),i)break}if(!i)throw t}for(let t of e.iterateCallbacks(`handlerWillRespond`))i=await t({event:r,request:n,response:i});return i}async _awaitComplete(e,t,n,r){let i,a;try{i=await e}catch{}try{await t.runCallbacks(`handlerDidRespond`,{event:r,request:n,response:i}),await t.doneWaiting()}catch(e){e instanceof Error&&(a=e)}if(await t.runCallbacks(`handlerDidComplete`,{event:r,request:n,response:i,error:a}),t.destroy(),a)throw a}},T=class e extends w{constructor(t={}){t.cacheName=a.getPrecacheName(t.cacheName),super(t),this._fallbackToNetwork=t.fallbackToNetwork!==!1,this.plugins.push(e.copyRedirectedCacheableResponsesPlugin)}async _handle(e,t){return await t.cacheMatch(e)||(t.event&&t.event.type===`install`?await this._handleInstall(e,t):await this._handleFetch(e,t))}async _handleFetch(e,n){let r,i=n.params||{};if(this._fallbackToNetwork){let t=i.integrity,a=e.integrity,o=!a||a===t;r=await n.fetch(new Request(e,{integrity:e.mode===`no-cors`?void 0:a||t})),t&&o&&e.mode!==`no-cors`&&(this._useDefaultCacheabilityPluginIfNeeded(),await n.cachePut(e,r.clone()))}else throw new t(`missing-precache-entry`,{cacheName:this.cacheName,url:e.url});return r}async _handleInstall(e,n){this._useDefaultCacheabilityPluginIfNeeded();let r=await n.fetch(e);if(!await n.cachePut(e,r.clone()))throw new t(`bad-precaching-response`,{url:e.url,status:r.status});return r}_useDefaultCacheabilityPluginIfNeeded(){let t=null,n=0;for(let[r,i]of this.plugins.entries())i!==e.copyRedirectedCacheableResponsesPlugin&&(i===e.defaultPrecacheCacheabilityPlugin&&(t=r),i.cacheWillUpdate&&n++);n===0?this.plugins.push(e.defaultPrecacheCacheabilityPlugin):n>1&&t!==null&&this.plugins.splice(t,1)}};T.defaultPrecacheCacheabilityPlugin={async cacheWillUpdate({response:e}){return!e||e.status>=400?null:e}},T.copyRedirectedCacheableResponsesPlugin={async cacheWillUpdate({response:e}){return e.redirected?await p(e):e}};var E=class{constructor({cacheName:e,plugins:t=[],fallbackToNetwork:n=!0}={}){this._urlsToCacheKeys=new Map,this._urlsToCacheModes=new Map,this._cacheKeysToIntegrities=new Map,this._strategy=new T({cacheName:a.getPrecacheName(e),plugins:[...t,new u({precacheController:this})],fallbackToNetwork:n}),this.install=this.install.bind(this),this.activate=this.activate.bind(this)}get strategy(){return this._strategy}precache(e){this.addToCacheList(e),this._installAndActiveListenersAdded||=(self.addEventListener(`install`,this.install),self.addEventListener(`activate`,this.activate),!0)}addToCacheList(e){let n=[];for(let r of e){typeof r==`string`?n.push(r):r&&r.revision===void 0&&n.push(r.url);let{cacheKey:e,url:i}=c(r),a=typeof r!=`string`&&r.revision?`reload`:`default`;if(this._urlsToCacheKeys.has(i)&&this._urlsToCacheKeys.get(i)!==e)throw new t(`add-to-cache-list-conflicting-entries`,{firstEntry:this._urlsToCacheKeys.get(i),secondEntry:e});if(typeof r!=`string`&&r.integrity){if(this._cacheKeysToIntegrities.has(e)&&this._cacheKeysToIntegrities.get(e)!==r.integrity)throw new t(`add-to-cache-list-conflicting-integrities`,{url:i});this._cacheKeysToIntegrities.set(e,r.integrity)}if(this._urlsToCacheKeys.set(i,e),this._urlsToCacheModes.set(i,a),n.length>0){let e=`Workbox is precaching URLs without revision info: ${n.join(`, `)}\nThis is generally NOT safe. Learn more at https://bit.ly/wb-precache`;console.warn(e)}}}install(e){return o(e,async()=>{let t=new l;this.strategy.plugins.push(t);for(let[t,n]of this._urlsToCacheKeys){let r=this._cacheKeysToIntegrities.get(n),i=this._urlsToCacheModes.get(t),a=new Request(t,{integrity:r,cache:i,credentials:`same-origin`});await Promise.all(this.strategy.handleAll({params:{cacheKey:n},request:a,event:e}))}let{updatedURLs:n,notUpdatedURLs:r}=t;return{updatedURLs:n,notUpdatedURLs:r}})}activate(e){return o(e,async()=>{let e=await self.caches.open(this.strategy.cacheName),t=await e.keys(),n=new Set(this._urlsToCacheKeys.values()),r=[];for(let i of t)n.has(i.url)||(await e.delete(i),r.push(i.url));return{deletedURLs:r}})}getURLsToCacheKeys(){return this._urlsToCacheKeys}getCachedURLs(){return[...this._urlsToCacheKeys.keys()]}getCacheKeyForURL(e){let t=new URL(e,location.href);return this._urlsToCacheKeys.get(t.href)}getIntegrityForCacheKey(e){return this._cacheKeysToIntegrities.get(e)}async matchPrecache(e){let t=e instanceof Request?e.url:e,n=this.getCacheKeyForURL(t);if(n)return(await self.caches.open(this.strategy.cacheName)).match(n)}createHandlerBoundToURL(e){let n=this.getCacheKeyForURL(e);if(!n)throw new t(`non-precached-url`,{url:e});return t=>(t.request=new Request(e),t.params=Object.assign({cacheKey:n},t.params),this.strategy.handle(t))}},D,O=()=>(D||=new E,D);try{self[`workbox:routing:7.3.0`]&&_()}catch{}var k=e=>e&&typeof e==`object`?e:{handle:e},A=class{constructor(e,t,n=`GET`){this.handler=k(t),this.match=e,this.method=n}setCatchHandler(e){this.catchHandler=k(e)}},j=class extends A{constructor(e,t,n){super(({url:t})=>{let n=e.exec(t.href);if(n&&!(t.origin!==location.origin&&n.index!==0))return n.slice(1)},t,n)}},M=class{constructor(){this._routes=new Map,this._defaultHandlerMap=new Map}get routes(){return this._routes}addFetchListener(){self.addEventListener(`fetch`,(e=>{let{request:t}=e,n=this.handleRequest({request:t,event:e});n&&e.respondWith(n)}))}addCacheListener(){self.addEventListener(`message`,(e=>{if(e.data&&e.data.type===`CACHE_URLS`){let{payload:t}=e.data,n=Promise.all(t.urlsToCache.map(t=>{typeof t==`string`&&(t=[t]);let n=new Request(...t);return this.handleRequest({request:n,event:e})}));e.waitUntil(n),e.ports&&e.ports[0]&&n.then(()=>e.ports[0].postMessage(!0))}}))}handleRequest({request:e,event:t}){let n=new URL(e.url,location.href);if(!n.protocol.startsWith(`http`))return;let r=n.origin===location.origin,{params:i,route:a}=this.findMatchingRoute({event:t,request:e,sameOrigin:r,url:n}),o=a&&a.handler,s=e.method;if(!o&&this._defaultHandlerMap.has(s)&&(o=this._defaultHandlerMap.get(s)),!o)return;let c;try{c=o.handle({url:n,request:e,event:t,params:i})}catch(e){c=Promise.reject(e)}let l=a&&a.catchHandler;return c instanceof Promise&&(this._catchHandler||l)&&(c=c.catch(async r=>{if(l)try{return await l.handle({url:n,request:e,event:t,params:i})}catch(e){e instanceof Error&&(r=e)}if(this._catchHandler)return this._catchHandler.handle({url:n,request:e,event:t});throw r})),c}findMatchingRoute({url:e,sameOrigin:t,request:n,event:r}){let i=this._routes.get(n.method)||[];for(let a of i){let i,o=a.match({url:e,sameOrigin:t,request:n,event:r});if(o)return i=o,(Array.isArray(i)&&i.length===0||o.constructor===Object&&Object.keys(o).length===0||typeof o==`boolean`)&&(i=void 0),{route:a,params:i}}return{}}setDefaultHandler(e,t=`GET`){this._defaultHandlerMap.set(t,k(e))}setCatchHandler(e){this._catchHandler=k(e)}registerRoute(e){this._routes.has(e.method)||this._routes.set(e.method,[]),this._routes.get(e.method).push(e)}unregisterRoute(e){if(!this._routes.has(e.method))throw new t(`unregister-route-but-not-found-with-method`,{method:e.method});let n=this._routes.get(e.method).indexOf(e);if(n>-1)this._routes.get(e.method).splice(n,1);else throw new t(`unregister-route-route-not-registered`)}},N,P=()=>(N||(N=new M,N.addFetchListener(),N.addCacheListener()),N);function F(e,n,r){let i;if(typeof e==`string`){let t=new URL(e,location.href);i=new A(({url:e})=>e.href===t.href,n,r)}else if(e instanceof RegExp)i=new j(e,n,r);else if(typeof e==`function`)i=new A(e,n,r);else if(e instanceof A)i=e;else throw new t(`unsupported-route-type`,{moduleName:`workbox-routing`,funcName:`registerRoute`,paramName:`capture`});return P().registerRoute(i),i}function I(e,t=[]){for(let n of[...e.searchParams.keys()])t.some(e=>e.test(n))&&e.searchParams.delete(n);return e}function*L(e,{ignoreURLParametersMatching:t=[/^utm_/,/^fbclid$/],directoryIndex:n=`index.html`,cleanURLs:r=!0,urlManipulation:i}={}){let a=new URL(e,location.href);a.hash=``,yield a.href;let o=I(a,t);if(yield o.href,n&&o.pathname.endsWith(`/`)){let e=new URL(o.href);e.pathname+=n,yield e.href}if(r){let e=new URL(o.href);e.pathname+=`.html`,yield e.href}if(i){let e=i({url:a});for(let t of e)yield t.href}}var R=class extends A{constructor(e,t){super(({request:n})=>{let r=e.getURLsToCacheKeys();for(let i of L(n.url,t)){let t=r.get(i);if(t)return{cacheKey:t,integrity:e.getIntegrityForCacheKey(t)}}},e.strategy)}};function z(e){F(new R(O(),e))}function B(e){O().precache(e)}function V(e,t){B(e),z(t)}V([{"revision":"1872c500de691dce40960bb85481de07","url":"registerSW.js"},{"revision":"a0fb34fc84eb148d51812cd62669f20d","url":"icon-192.svg"},{"revision":"e2e61f88ee712a28cc9a55adecc0812c","url":"index.html"},{"revision":"a0fb34fc84eb148d51812cd62669f20d","url":"icon-512.svg"},{"revision":null,"url":"assets/extension-webview-HILvTnnn.js"},{"revision":null,"url":"assets/dist-D7KGU7Vl.js"},{"revision":null,"url":"assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2"},{"revision":null,"url":"assets/pdf-preview-BBVDS-z5.js"},{"revision":null,"url":"assets/KaTeX_AMS-Regular-BQhdFMY1.woff2"},{"revision":null,"url":"assets/scroll-area-BEllam7_.js"},{"revision":null,"url":"assets/database-D4DIhgi-.js"},{"revision":null,"url":"assets/csv-parser--2WJNgS7.js"},{"revision":null,"url":"assets/x-DlFGzN8d.js"},{"revision":null,"url":"assets/keybindings-store-B-zET-0o.js"},{"revision":null,"url":"assets/use-blob-url-e9uTXjv5.js"},{"revision":null,"url":"assets/index-Bce0weeW.css"},{"revision":null,"url":"assets/terminal-tab-BLIA53mt.js"},{"revision":null,"url":"assets/radar-KQ55EAFF-BviZcL-b.js"},{"revision":null,"url":"assets/vendor-xterm-CU2c3f0A.js"},{"revision":null,"url":"assets/code-editor-BTosKXkr.js"},{"revision":null,"url":"assets/input-Dk49gO8E.js"},{"revision":null,"url":"assets/video-preview-CKaht6nI.js"},{"revision":null,"url":"assets/KaTeX_Main-Regular-B22Nviop.woff2"},{"revision":null,"url":"assets/refresh-cw-LlbZDJpO.js"},{"revision":null,"url":"assets/pie-UPGHQEXC-D6S2MqVT.js"},{"revision":null,"url":"assets/treemap-KZPCXAKY-CM54VdaB.js"},{"revision":null,"url":"assets/react-GqWghJ-L.js"},{"revision":null,"url":"assets/github.min-D2BCvnWf.css"},{"revision":null,"url":"assets/vendor-mermaid-Dx86tuVP.js"},{"revision":null,"url":"assets/ai-settings-section-QE6nBNgN.js"},{"revision":null,"url":"assets/csv-preview-HMSavgBb.js"},{"revision":null,"url":"assets/katex-CKoArbIw.js"},{"revision":null,"url":"assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2"},{"revision":null,"url":"assets/info-3K5VOQVL-BwAZ2zd8.js"},{"revision":null,"url":"assets/chevron-right-BzAdxJRG.js"},{"revision":null,"url":"assets/extension-store-3yZYn07W.js"},{"revision":null,"url":"assets/markdown-renderer-D0MrsVJB.js"},{"revision":null,"url":"assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2"},{"revision":null,"url":"assets/vendor-markdown-0Mxgxy0L.js"},{"revision":null,"url":"assets/image-preview-0cJMnFZK.js"},{"revision":null,"url":"assets/api-client-Dvzcc_EO.js"},{"revision":null,"url":"assets/dist-im4ynINo.js"},{"revision":null,"url":"assets/KaTeX_Main-Italic-NWA7e6Wa.woff2"},{"revision":null,"url":"assets/table-Dq575bPF.js"},{"revision":null,"url":"assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2"},{"revision":null,"url":"assets/sqlite-viewer-D6mSIIx2.js"},{"revision":null,"url":"assets/packet-RMMSAZCW-tx2n5Qry.js"},{"revision":null,"url":"assets/port-forwarding-tab-ByKzBs-R.js"},{"revision":null,"url":"assets/plus-51UQ45rf.js"},{"revision":null,"url":"assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2"},{"revision":null,"url":"assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2"},{"revision":null,"url":"assets/arrow-up-Dtrfv490.js"},{"revision":null,"url":"assets/api-settings-DAk7D-NP.js"},{"revision":null,"url":"assets/architecture-PBZL5I3N-DvZbltvY.js"},{"revision":null,"url":"assets/rolldown-runtime-FhOqtrmT.js"},{"revision":null,"url":"assets/settings-store-BLLR7ed8.js"},{"revision":null,"url":"assets/use-monaco-theme-BkZDwoVd.js"},{"revision":null,"url":"assets/KaTeX_Math-Italic-t53AETM-.woff2"},{"revision":null,"url":"assets/github-dark-dimmed.min-BrpRStFV.css"},{"revision":null,"url":"assets/text-wrap-Cn6BNQfq.js"},{"revision":null,"url":"assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2"},{"revision":null,"url":"assets/KaTeX_Script-Regular-D3wIWfF6.woff2"},{"revision":null,"url":"assets/trash-2-CJYoLw7Q.js"},{"revision":null,"url":"assets/audio-preview-R7cq1uhJ.js"},{"revision":null,"url":"assets/file-exclamation-point-BwzaQ50n.js"},{"revision":null,"url":"assets/file-store-BrbCNyLm.js"},{"revision":null,"url":"assets/postgres-viewer-BnCbdR7g.js"},{"revision":null,"url":"assets/utils-CTg5uAYR.js"},{"revision":null,"url":"assets/columns-2-4fQcE4PF.js"},{"revision":null,"url":"assets/KaTeX_Main-Bold-Cx986IdX.woff2"},{"revision":null,"url":"assets/diff-viewer-DKLeIBkK.js"},{"revision":null,"url":"assets/settings-tab-BPdzUw3v.js"},{"revision":null,"url":"assets/KaTeX_Size2-Regular-Dy4dx90m.woff2"},{"revision":null,"url":"assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2"},{"revision":null,"url":"assets/KaTeX_Size1-Regular-mCD8mA8B.woff2"},{"revision":null,"url":"assets/tab-store-B3M9hjho.js"},{"revision":null,"url":"assets/keybindings-store-DaBV6qhz.js"},{"revision":null,"url":"assets/createLucideIcon-BjHrJDVb.js"},{"revision":null,"url":"assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2"},{"revision":null,"url":"assets/code-CuravVys.js"},{"revision":null,"url":"assets/chat-tab-umei1UkV.js"},{"revision":null,"url":"assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2"},{"revision":null,"url":"assets/sql-completion-provider-C3cq9j99.js"},{"revision":null,"url":"assets/vendor-ui-B-89Uj8i.js"},{"revision":null,"url":"assets/esm-K1XIK4vc.js"},{"revision":null,"url":"assets/gitGraph-HDMCJU4V-BxhdxFgj.js"},{"revision":null,"url":"assets/conflict-editor-dzofjxab.js"},{"revision":null,"url":"assets/index-DDBvHVVr.js"},{"revision":null,"url":"assets/vendor-xterm-BrP-ENHg.css"},{"revision":null,"url":"assets/database-viewer-5Uf8Rrls.js"},{"revision":null,"url":"assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2"},{"revision":null,"url":"assets/lib-DQHnkzGy.js"},{"revision":null,"url":"assets/sql-query-editor-CVAnRFbi.js"},{"revision":"d0f94ce046cf8cf09605ee7664dac557","url":"monacoeditorwork/html.worker.bundle.js"},{"revision":"a424156a79b9c1b907db93aa3180585a","url":"monacoeditorwork/editor.worker.bundle.js"},{"revision":"b3a7f967560c9816492a1567b3f7f0dc","url":"monacoeditorwork/css.worker.bundle.js"},{"revision":"a5d8a1acfc29c2a4c882a54ffc93def3","url":"monacoeditorwork/json.worker.bundle.js"},{"revision":"948e060affb598c339be40d69e1f6f9c","url":"monacoeditorwork/ts.worker.bundle.js"},{"revision":"79c8870653c8f419f2e3323085e1f4be","url":"manifest.webmanifest"}]),self.addEventListener(`push`,e=>{e.waitUntil(self.clients.matchAll({type:`window`,includeUncontrolled:!0}).then(t=>{if(t.some(e=>e.visibilityState===`visible`))return;let n=e.data?.json()??{title:`PPM`,body:`Chat completed`};return self.registration.showNotification(n.title,{body:n.body,icon:`/icon-192.png`,badge:`/icon-192.png`,tag:`ppm-chat-done`,silent:!1,data:{url:self.location.origin}})}))}),self.addEventListener(`notificationclick`,e=>{e.notification.close(),e.waitUntil(self.clients.matchAll({type:`window`,includeUncontrolled:!0}).then(t=>{for(let e of t)if(e.url.includes(self.location.origin)&&`focus`in e)return e.focus();return self.clients.openWindow(e.notification.data?.url||`/`)}))});
1
+ try{self[`workbox:core:7.3.0`]&&_()}catch{}var e=(e,...t)=>{let n=e;return t.length>0&&(n+=` :: ${JSON.stringify(t)}`),n},t=class extends Error{constructor(t,n){let r=e(t,n);super(r),this.name=t,this.details=n}},n={googleAnalytics:`googleAnalytics`,precache:`precache-v2`,prefix:`workbox`,runtime:`runtime`,suffix:typeof registration<`u`?registration.scope:``},r=e=>[n.prefix,e,n.suffix].filter(e=>e&&e.length>0).join(`-`),i=e=>{for(let t of Object.keys(n))e(t)},a={updateDetails:e=>{i(t=>{typeof e[t]==`string`&&(n[t]=e[t])})},getGoogleAnalyticsName:e=>e||r(n.googleAnalytics),getPrecacheName:e=>e||r(n.precache),getPrefix:()=>n.prefix,getRuntimeName:e=>e||r(n.runtime),getSuffix:()=>n.suffix};function o(e,t){let n=t();return e.waitUntil(n),n}try{self[`workbox:precaching:7.3.0`]&&_()}catch{}var s=`__WB_REVISION__`;function c(e){if(!e)throw new t(`add-to-cache-list-unexpected-type`,{entry:e});if(typeof e==`string`){let t=new URL(e,location.href);return{cacheKey:t.href,url:t.href}}let{revision:n,url:r}=e;if(!r)throw new t(`add-to-cache-list-unexpected-type`,{entry:e});if(!n){let e=new URL(r,location.href);return{cacheKey:e.href,url:e.href}}let i=new URL(r,location.href),a=new URL(r,location.href);return i.searchParams.set(s,n),{cacheKey:i.href,url:a.href}}var l=class{constructor(){this.updatedURLs=[],this.notUpdatedURLs=[],this.handlerWillStart=async({request:e,state:t})=>{t&&(t.originalRequest=e)},this.cachedResponseWillBeUsed=async({event:e,state:t,cachedResponse:n})=>{if(e.type===`install`&&t&&t.originalRequest&&t.originalRequest instanceof Request){let e=t.originalRequest.url;n?this.notUpdatedURLs.push(e):this.updatedURLs.push(e)}return n}}},u=class{constructor({precacheController:e}){this.cacheKeyWillBeUsed=async({request:e,params:t})=>{let n=t?.cacheKey||this._precacheController.getCacheKeyForURL(e.url);return n?new Request(n,{headers:e.headers}):e},this._precacheController=e}},d;function f(){if(d===void 0){let e=new Response(``);if(`body`in e)try{new Response(e.body),d=!0}catch{d=!1}d=!1}return d}async function p(e,n){let r=null;if(e.url&&(r=new URL(e.url).origin),r!==self.location.origin)throw new t(`cross-origin-copy-response`,{origin:r});let i=e.clone(),a={headers:new Headers(i.headers),status:i.status,statusText:i.statusText},o=n?n(a):a,s=f()?i.body:await i.blob();return new Response(s,o)}var m=e=>new URL(String(e),location.href).href.replace(RegExp(`^${location.origin}`),``);function h(e,t){let n=new URL(e);for(let e of t)n.searchParams.delete(e);return n.href}async function g(e,t,n,r){let i=h(t.url,n);if(t.url===i)return e.match(t,r);let a=Object.assign(Object.assign({},r),{ignoreSearch:!0}),o=await e.keys(t,a);for(let t of o)if(i===h(t.url,n))return e.match(t,r)}var v=class{constructor(){this.promise=new Promise((e,t)=>{this.resolve=e,this.reject=t})}},y=new Set;async function b(){for(let e of y)await e()}function x(e){return new Promise(t=>setTimeout(t,e))}try{self[`workbox:strategies:7.3.0`]&&_()}catch{}function S(e){return typeof e==`string`?new Request(e):e}var C=class{constructor(e,t){this._cacheKeys={},Object.assign(this,t),this.event=t.event,this._strategy=e,this._handlerDeferred=new v,this._extendLifetimePromises=[],this._plugins=[...e.plugins],this._pluginStateMap=new Map;for(let e of this._plugins)this._pluginStateMap.set(e,{});this.event.waitUntil(this._handlerDeferred.promise)}async fetch(e){let{event:n}=this,r=S(e);if(r.mode===`navigate`&&n instanceof FetchEvent&&n.preloadResponse){let e=await n.preloadResponse;if(e)return e}let i=this.hasCallback(`fetchDidFail`)?r.clone():null;try{for(let e of this.iterateCallbacks(`requestWillFetch`))r=await e({request:r.clone(),event:n})}catch(e){if(e instanceof Error)throw new t(`plugin-error-request-will-fetch`,{thrownErrorMessage:e.message})}let a=r.clone();try{let e;e=await fetch(r,r.mode===`navigate`?void 0:this._strategy.fetchOptions);for(let t of this.iterateCallbacks(`fetchDidSucceed`))e=await t({event:n,request:a,response:e});return e}catch(e){throw i&&await this.runCallbacks(`fetchDidFail`,{error:e,event:n,originalRequest:i.clone(),request:a.clone()}),e}}async fetchAndCachePut(e){let t=await this.fetch(e),n=t.clone();return this.waitUntil(this.cachePut(e,n)),t}async cacheMatch(e){let t=S(e),n,{cacheName:r,matchOptions:i}=this._strategy,a=await this.getCacheKey(t,`read`),o=Object.assign(Object.assign({},i),{cacheName:r});n=await caches.match(a,o);for(let e of this.iterateCallbacks(`cachedResponseWillBeUsed`))n=await e({cacheName:r,matchOptions:i,cachedResponse:n,request:a,event:this.event})||void 0;return n}async cachePut(e,n){let r=S(e);await x(0);let i=await this.getCacheKey(r,`write`);if(!n)throw new t(`cache-put-with-no-response`,{url:m(i.url)});let a=await this._ensureResponseSafeToCache(n);if(!a)return!1;let{cacheName:o,matchOptions:s}=this._strategy,c=await self.caches.open(o),l=this.hasCallback(`cacheDidUpdate`),u=l?await g(c,i.clone(),[`__WB_REVISION__`],s):null;try{await c.put(i,l?a.clone():a)}catch(e){if(e instanceof Error)throw e.name===`QuotaExceededError`&&await b(),e}for(let e of this.iterateCallbacks(`cacheDidUpdate`))await e({cacheName:o,oldResponse:u,newResponse:a.clone(),request:i,event:this.event});return!0}async getCacheKey(e,t){let n=`${e.url} | ${t}`;if(!this._cacheKeys[n]){let r=e;for(let e of this.iterateCallbacks(`cacheKeyWillBeUsed`))r=S(await e({mode:t,request:r,event:this.event,params:this.params}));this._cacheKeys[n]=r}return this._cacheKeys[n]}hasCallback(e){for(let t of this._strategy.plugins)if(e in t)return!0;return!1}async runCallbacks(e,t){for(let n of this.iterateCallbacks(e))await n(t)}*iterateCallbacks(e){for(let t of this._strategy.plugins)if(typeof t[e]==`function`){let n=this._pluginStateMap.get(t);yield r=>{let i=Object.assign(Object.assign({},r),{state:n});return t[e](i)}}}waitUntil(e){return this._extendLifetimePromises.push(e),e}async doneWaiting(){for(;this._extendLifetimePromises.length;){let e=this._extendLifetimePromises.splice(0),t=(await Promise.allSettled(e)).find(e=>e.status===`rejected`);if(t)throw t.reason}}destroy(){this._handlerDeferred.resolve(null)}async _ensureResponseSafeToCache(e){let t=e,n=!1;for(let e of this.iterateCallbacks(`cacheWillUpdate`))if(t=await e({request:this.request,response:t,event:this.event})||void 0,n=!0,!t)break;return n||t&&t.status!==200&&(t=void 0),t}},w=class{constructor(e={}){this.cacheName=a.getRuntimeName(e.cacheName),this.plugins=e.plugins||[],this.fetchOptions=e.fetchOptions,this.matchOptions=e.matchOptions}handle(e){let[t]=this.handleAll(e);return t}handleAll(e){e instanceof FetchEvent&&(e={event:e,request:e.request});let t=e.event,n=typeof e.request==`string`?new Request(e.request):e.request,r=`params`in e?e.params:void 0,i=new C(this,{event:t,request:n,params:r}),a=this._getResponse(i,n,t);return[a,this._awaitComplete(a,i,n,t)]}async _getResponse(e,n,r){await e.runCallbacks(`handlerWillStart`,{event:r,request:n});let i;try{if(i=await this._handle(n,e),!i||i.type===`error`)throw new t(`no-response`,{url:n.url})}catch(t){if(t instanceof Error){for(let a of e.iterateCallbacks(`handlerDidError`))if(i=await a({error:t,event:r,request:n}),i)break}if(!i)throw t}for(let t of e.iterateCallbacks(`handlerWillRespond`))i=await t({event:r,request:n,response:i});return i}async _awaitComplete(e,t,n,r){let i,a;try{i=await e}catch{}try{await t.runCallbacks(`handlerDidRespond`,{event:r,request:n,response:i}),await t.doneWaiting()}catch(e){e instanceof Error&&(a=e)}if(await t.runCallbacks(`handlerDidComplete`,{event:r,request:n,response:i,error:a}),t.destroy(),a)throw a}},T=class e extends w{constructor(t={}){t.cacheName=a.getPrecacheName(t.cacheName),super(t),this._fallbackToNetwork=t.fallbackToNetwork!==!1,this.plugins.push(e.copyRedirectedCacheableResponsesPlugin)}async _handle(e,t){return await t.cacheMatch(e)||(t.event&&t.event.type===`install`?await this._handleInstall(e,t):await this._handleFetch(e,t))}async _handleFetch(e,n){let r,i=n.params||{};if(this._fallbackToNetwork){let t=i.integrity,a=e.integrity,o=!a||a===t;r=await n.fetch(new Request(e,{integrity:e.mode===`no-cors`?void 0:a||t})),t&&o&&e.mode!==`no-cors`&&(this._useDefaultCacheabilityPluginIfNeeded(),await n.cachePut(e,r.clone()))}else throw new t(`missing-precache-entry`,{cacheName:this.cacheName,url:e.url});return r}async _handleInstall(e,n){this._useDefaultCacheabilityPluginIfNeeded();let r=await n.fetch(e);if(!await n.cachePut(e,r.clone()))throw new t(`bad-precaching-response`,{url:e.url,status:r.status});return r}_useDefaultCacheabilityPluginIfNeeded(){let t=null,n=0;for(let[r,i]of this.plugins.entries())i!==e.copyRedirectedCacheableResponsesPlugin&&(i===e.defaultPrecacheCacheabilityPlugin&&(t=r),i.cacheWillUpdate&&n++);n===0?this.plugins.push(e.defaultPrecacheCacheabilityPlugin):n>1&&t!==null&&this.plugins.splice(t,1)}};T.defaultPrecacheCacheabilityPlugin={async cacheWillUpdate({response:e}){return!e||e.status>=400?null:e}},T.copyRedirectedCacheableResponsesPlugin={async cacheWillUpdate({response:e}){return e.redirected?await p(e):e}};var E=class{constructor({cacheName:e,plugins:t=[],fallbackToNetwork:n=!0}={}){this._urlsToCacheKeys=new Map,this._urlsToCacheModes=new Map,this._cacheKeysToIntegrities=new Map,this._strategy=new T({cacheName:a.getPrecacheName(e),plugins:[...t,new u({precacheController:this})],fallbackToNetwork:n}),this.install=this.install.bind(this),this.activate=this.activate.bind(this)}get strategy(){return this._strategy}precache(e){this.addToCacheList(e),this._installAndActiveListenersAdded||=(self.addEventListener(`install`,this.install),self.addEventListener(`activate`,this.activate),!0)}addToCacheList(e){let n=[];for(let r of e){typeof r==`string`?n.push(r):r&&r.revision===void 0&&n.push(r.url);let{cacheKey:e,url:i}=c(r),a=typeof r!=`string`&&r.revision?`reload`:`default`;if(this._urlsToCacheKeys.has(i)&&this._urlsToCacheKeys.get(i)!==e)throw new t(`add-to-cache-list-conflicting-entries`,{firstEntry:this._urlsToCacheKeys.get(i),secondEntry:e});if(typeof r!=`string`&&r.integrity){if(this._cacheKeysToIntegrities.has(e)&&this._cacheKeysToIntegrities.get(e)!==r.integrity)throw new t(`add-to-cache-list-conflicting-integrities`,{url:i});this._cacheKeysToIntegrities.set(e,r.integrity)}if(this._urlsToCacheKeys.set(i,e),this._urlsToCacheModes.set(i,a),n.length>0){let e=`Workbox is precaching URLs without revision info: ${n.join(`, `)}\nThis is generally NOT safe. Learn more at https://bit.ly/wb-precache`;console.warn(e)}}}install(e){return o(e,async()=>{let t=new l;this.strategy.plugins.push(t);for(let[t,n]of this._urlsToCacheKeys){let r=this._cacheKeysToIntegrities.get(n),i=this._urlsToCacheModes.get(t),a=new Request(t,{integrity:r,cache:i,credentials:`same-origin`});await Promise.all(this.strategy.handleAll({params:{cacheKey:n},request:a,event:e}))}let{updatedURLs:n,notUpdatedURLs:r}=t;return{updatedURLs:n,notUpdatedURLs:r}})}activate(e){return o(e,async()=>{let e=await self.caches.open(this.strategy.cacheName),t=await e.keys(),n=new Set(this._urlsToCacheKeys.values()),r=[];for(let i of t)n.has(i.url)||(await e.delete(i),r.push(i.url));return{deletedURLs:r}})}getURLsToCacheKeys(){return this._urlsToCacheKeys}getCachedURLs(){return[...this._urlsToCacheKeys.keys()]}getCacheKeyForURL(e){let t=new URL(e,location.href);return this._urlsToCacheKeys.get(t.href)}getIntegrityForCacheKey(e){return this._cacheKeysToIntegrities.get(e)}async matchPrecache(e){let t=e instanceof Request?e.url:e,n=this.getCacheKeyForURL(t);if(n)return(await self.caches.open(this.strategy.cacheName)).match(n)}createHandlerBoundToURL(e){let n=this.getCacheKeyForURL(e);if(!n)throw new t(`non-precached-url`,{url:e});return t=>(t.request=new Request(e),t.params=Object.assign({cacheKey:n},t.params),this.strategy.handle(t))}},D,O=()=>(D||=new E,D);try{self[`workbox:routing:7.3.0`]&&_()}catch{}var k=e=>e&&typeof e==`object`?e:{handle:e},A=class{constructor(e,t,n=`GET`){this.handler=k(t),this.match=e,this.method=n}setCatchHandler(e){this.catchHandler=k(e)}},j=class extends A{constructor(e,t,n){super(({url:t})=>{let n=e.exec(t.href);if(n&&!(t.origin!==location.origin&&n.index!==0))return n.slice(1)},t,n)}},M=class{constructor(){this._routes=new Map,this._defaultHandlerMap=new Map}get routes(){return this._routes}addFetchListener(){self.addEventListener(`fetch`,(e=>{let{request:t}=e,n=this.handleRequest({request:t,event:e});n&&e.respondWith(n)}))}addCacheListener(){self.addEventListener(`message`,(e=>{if(e.data&&e.data.type===`CACHE_URLS`){let{payload:t}=e.data,n=Promise.all(t.urlsToCache.map(t=>{typeof t==`string`&&(t=[t]);let n=new Request(...t);return this.handleRequest({request:n,event:e})}));e.waitUntil(n),e.ports&&e.ports[0]&&n.then(()=>e.ports[0].postMessage(!0))}}))}handleRequest({request:e,event:t}){let n=new URL(e.url,location.href);if(!n.protocol.startsWith(`http`))return;let r=n.origin===location.origin,{params:i,route:a}=this.findMatchingRoute({event:t,request:e,sameOrigin:r,url:n}),o=a&&a.handler,s=e.method;if(!o&&this._defaultHandlerMap.has(s)&&(o=this._defaultHandlerMap.get(s)),!o)return;let c;try{c=o.handle({url:n,request:e,event:t,params:i})}catch(e){c=Promise.reject(e)}let l=a&&a.catchHandler;return c instanceof Promise&&(this._catchHandler||l)&&(c=c.catch(async r=>{if(l)try{return await l.handle({url:n,request:e,event:t,params:i})}catch(e){e instanceof Error&&(r=e)}if(this._catchHandler)return this._catchHandler.handle({url:n,request:e,event:t});throw r})),c}findMatchingRoute({url:e,sameOrigin:t,request:n,event:r}){let i=this._routes.get(n.method)||[];for(let a of i){let i,o=a.match({url:e,sameOrigin:t,request:n,event:r});if(o)return i=o,(Array.isArray(i)&&i.length===0||o.constructor===Object&&Object.keys(o).length===0||typeof o==`boolean`)&&(i=void 0),{route:a,params:i}}return{}}setDefaultHandler(e,t=`GET`){this._defaultHandlerMap.set(t,k(e))}setCatchHandler(e){this._catchHandler=k(e)}registerRoute(e){this._routes.has(e.method)||this._routes.set(e.method,[]),this._routes.get(e.method).push(e)}unregisterRoute(e){if(!this._routes.has(e.method))throw new t(`unregister-route-but-not-found-with-method`,{method:e.method});let n=this._routes.get(e.method).indexOf(e);if(n>-1)this._routes.get(e.method).splice(n,1);else throw new t(`unregister-route-route-not-registered`)}},N,P=()=>(N||(N=new M,N.addFetchListener(),N.addCacheListener()),N);function F(e,n,r){let i;if(typeof e==`string`){let t=new URL(e,location.href);i=new A(({url:e})=>e.href===t.href,n,r)}else if(e instanceof RegExp)i=new j(e,n,r);else if(typeof e==`function`)i=new A(e,n,r);else if(e instanceof A)i=e;else throw new t(`unsupported-route-type`,{moduleName:`workbox-routing`,funcName:`registerRoute`,paramName:`capture`});return P().registerRoute(i),i}function I(e,t=[]){for(let n of[...e.searchParams.keys()])t.some(e=>e.test(n))&&e.searchParams.delete(n);return e}function*L(e,{ignoreURLParametersMatching:t=[/^utm_/,/^fbclid$/],directoryIndex:n=`index.html`,cleanURLs:r=!0,urlManipulation:i}={}){let a=new URL(e,location.href);a.hash=``,yield a.href;let o=I(a,t);if(yield o.href,n&&o.pathname.endsWith(`/`)){let e=new URL(o.href);e.pathname+=n,yield e.href}if(r){let e=new URL(o.href);e.pathname+=`.html`,yield e.href}if(i){let e=i({url:a});for(let t of e)yield t.href}}var R=class extends A{constructor(e,t){super(({request:n})=>{let r=e.getURLsToCacheKeys();for(let i of L(n.url,t)){let t=r.get(i);if(t)return{cacheKey:t,integrity:e.getIntegrityForCacheKey(t)}}},e.strategy)}};function z(e){F(new R(O(),e))}function B(e){O().precache(e)}function V(e,t){B(e),z(t)}V([{"revision":"1872c500de691dce40960bb85481de07","url":"registerSW.js"},{"revision":"a0fb34fc84eb148d51812cd62669f20d","url":"icon-192.svg"},{"revision":"68621e26387f540756f76eaa61a6c67f","url":"index.html"},{"revision":"a0fb34fc84eb148d51812cd62669f20d","url":"icon-512.svg"},{"revision":null,"url":"assets/video-preview-ClY8ALGJ.js"},{"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/postgres-viewer-DM6b5mZl.js"},{"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/packet-RMMSAZCW-DpzHf4xp.js"},{"revision":null,"url":"assets/extension-webview-DxP22X_y.js"},{"revision":null,"url":"assets/KaTeX_Main-Regular-B22Nviop.woff2"},{"revision":null,"url":"assets/image-preview-yX0yZtyd.js"},{"revision":null,"url":"assets/scroll-area-D0EQpAH2.js"},{"revision":null,"url":"assets/vendor-xterm-D7SePDJp.js"},{"revision":null,"url":"assets/file-exclamation-point-Baz81y5z.js"},{"revision":null,"url":"assets/github.min-D2BCvnWf.css"},{"revision":null,"url":"assets/database-viewer-CbIMjroK.js"},{"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/port-forwarding-tab-DOYZIXHo.js"},{"revision":null,"url":"assets/code-editor-BhmUC3pD.js"},{"revision":null,"url":"assets/KaTeX_Main-Italic-NWA7e6Wa.woff2"},{"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/dist-BqoEabX7.js"},{"revision":null,"url":"assets/input-bGJExpJZ.js"},{"revision":null,"url":"assets/sqlite-viewer-IvosQxK2.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/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2"},{"revision":null,"url":"assets/arrow-up-Dtrfv490.js"},{"revision":null,"url":"assets/keybindings-store-zxSQXdFL.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/diff-viewer-oq0RiOpV.js"},{"revision":null,"url":"assets/KaTeX_Math-Italic-t53AETM-.woff2"},{"revision":null,"url":"assets/github-dark-dimmed.min-BrpRStFV.css"},{"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/esm-B3je8j5P.js"},{"revision":null,"url":"assets/terminal-tab-D4xxia2I.js"},{"revision":null,"url":"assets/treemap-KZPCXAKY-D6dgXbAe.js"},{"revision":null,"url":"assets/KaTeX_Main-Bold-Cx986IdX.woff2"},{"revision":null,"url":"assets/chat-tab-C2NBEXEX.js"},{"revision":null,"url":"assets/react-BkWDCPD7.js"},{"revision":null,"url":"assets/database-DCT0OjgQ.js"},{"revision":null,"url":"assets/KaTeX_Size2-Regular-Dy4dx90m.woff2"},{"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/audio-preview-BdRw2cYi.js"},{"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/settings-tab-JzeC-QC7.js"},{"revision":null,"url":"assets/code-CuravVys.js"},{"revision":null,"url":"assets/pdf-preview-BlRtar7G.js"},{"revision":null,"url":"assets/conflict-editor-Br_CSQdA.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/index-BJ76xcQz.css"},{"revision":null,"url":"assets/utils-ChWX7pZv.js"},{"revision":null,"url":"assets/text-wrap-DzvCTq_i.js"},{"revision":null,"url":"assets/csv-parser-DxVplKKB.js"},{"revision":null,"url":"assets/markdown-renderer-DHD3HPwK.js"},{"revision":null,"url":"assets/vendor-xterm-BrP-ENHg.css"},{"revision":null,"url":"assets/use-monaco-theme-dtPsv6sh.js"},{"revision":null,"url":"assets/index-DJQJu6Ef.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
@@ -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.3",
3
+ "version": "0.13.5",
4
4
  "description": "Personal Project Manager — mobile-first web IDE with AI assistance",
5
5
  "author": "hienlh",
6
6
  "license": "MIT",
@@ -377,9 +377,10 @@ chatRoutes.get("/sessions/:id/debug", (c) => {
377
377
  chatRoutes.get("/pre-compact-messages", async (c) => {
378
378
  try {
379
379
  const jsonlPath = c.req.query("jsonlPath");
380
+ const beforeUuid = c.req.query("before");
380
381
  if (!jsonlPath) return c.json(err("jsonlPath query param required"), 400);
381
382
  const validated = validateJsonlPath(jsonlPath);
382
- const messages = await parseJsonlTranscript(validated);
383
+ const messages = await parseJsonlTranscript(validated, beforeUuid);
383
384
  return c.json(ok(messages));
384
385
  } catch (e) {
385
386
  const message = e instanceof Error ? e.message : "Unknown error";
@@ -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
  }
@@ -175,8 +175,16 @@ export function validateJsonlPath(inputPath: string): string {
175
175
  /**
176
176
  * Read a JSONL transcript file, parse entries, apply merge/nest pipeline, return ChatMessage[].
177
177
  * Applies the same logic as ClaudeAgentSdkProvider.getMessages() but reads from file directly.
178
+ *
179
+ * @param beforeUuid If provided, stop parsing at the line with this uuid (exclusive).
180
+ * Used for the expand-compact feature: Claude's compact summary references
181
+ * the CURRENT session file (pre+summary+post), so we truncate at the
182
+ * compact summary's uuid to return only pre-compact messages.
178
183
  */
179
- export async function parseJsonlTranscript(filePath: string): Promise<ChatMessage[]> {
184
+ export async function parseJsonlTranscript(
185
+ filePath: string,
186
+ beforeUuid?: string,
187
+ ): Promise<ChatMessage[]> {
180
188
  const text = await Bun.file(filePath).text();
181
189
  const parsed: ChatMessage[] = [];
182
190
  for (const line of text.split("\n")) {
@@ -188,6 +196,7 @@ export async function parseJsonlTranscript(filePath: string): Promise<ChatMessag
188
196
  } catch {
189
197
  continue; // skip malformed lines defensively
190
198
  }
199
+ if (beforeUuid && entry.uuid === beforeUuid) break; // stop at compact boundary (exclusive)
191
200
  if (entry.type !== "user" && entry.type !== "assistant") continue;
192
201
  if (!entry.uuid || !entry.message) continue;
193
202
  parsed.push(parseSessionMessage(entry));
@@ -7,7 +7,7 @@
7
7
  import type { Subprocess } from "bun";
8
8
  import { resolve } from "node:path";
9
9
  import {
10
- readFileSync, writeFileSync, existsSync, mkdirSync, openSync, appendFileSync,
10
+ readFileSync, writeFileSync, existsSync, mkdirSync, openSync, closeSync, appendFileSync,
11
11
  unlinkSync,
12
12
  } from "node:fs";
13
13
  import { getPpmDir } from "./ppm-dir.ts";
@@ -170,40 +170,28 @@ export async function spawnServer(
170
170
  }
171
171
 
172
172
  // ─── Tunnel management ─────────────────────────────────────────────────
173
- async function extractUrlFromStderr(stderr: ReadableStream<Uint8Array>): Promise<string> {
174
- const reader = stderr.getReader();
175
- const decoder = new TextDecoder();
176
- let buffer = "";
173
+ const cloudflaredLogPath = () => resolve(getPpmDir(), "cloudflared.log");
177
174
 
178
- return new Promise((resolve, reject) => {
179
- const timeout = setTimeout(() => reject(new Error("Tunnel URL timeout (30s)")), 30_000);
180
-
181
- const read = async () => {
175
+ /**
176
+ * Poll cloudflared log file for trycloudflare URL.
177
+ * Stderr is redirected to this file (not piped) so cloudflared survives
178
+ * parent supervisor exit during self-replace (no SIGPIPE on closed pipe).
179
+ */
180
+ async function extractUrlFromLogFile(child: Subprocess): Promise<string> {
181
+ const path = cloudflaredLogPath();
182
+ const deadline = Date.now() + 30_000;
183
+ while (Date.now() < deadline) {
184
+ if (existsSync(path)) {
182
185
  try {
183
- while (true) {
184
- const { done, value } = await reader.read();
185
- if (done) break;
186
- buffer += decoder.decode(value, { stream: true });
187
- const match = buffer.match(TUNNEL_URL_REGEX);
188
- if (match) {
189
- clearTimeout(timeout);
190
- // Keep draining in background to avoid SIGPIPE
191
- (async () => {
192
- try { while (!(await reader.read()).done) {} } catch {}
193
- })();
194
- resolve(match[0]);
195
- return;
196
- }
197
- }
198
- clearTimeout(timeout);
199
- reject(new Error("cloudflared exited without providing URL"));
200
- } catch (err) {
201
- clearTimeout(timeout);
202
- reject(err);
203
- }
204
- };
205
- read();
206
- });
186
+ const content = readFileSync(path, "utf8");
187
+ const match = content.match(TUNNEL_URL_REGEX);
188
+ if (match) return match[0];
189
+ } catch {}
190
+ }
191
+ if (child.exitCode !== null) throw new Error("cloudflared exited without providing URL");
192
+ await Bun.sleep(200);
193
+ }
194
+ throw new Error("Tunnel URL timeout (30s)");
207
195
  }
208
196
 
209
197
  async function syncUrlToCloud(url: string) {
@@ -243,11 +231,23 @@ export async function spawnTunnel(port: number): Promise<void> {
243
231
  ]
244
232
  : [bin, "tunnel", "--url", `http://127.0.0.1:${port}`];
245
233
 
246
- tunnelChild = Bun.spawn(tunnelCmd, { stderr: "pipe", stdout: "ignore", stdin: "ignore" });
234
+ // Redirect cloudflared stderr to a log file (not pipe). This way cloudflared
235
+ // survives parent supervisor exit during self-replace — a piped stderr would
236
+ // close when parent exits, causing SIGPIPE on next cloudflared log write and
237
+ // killing the tunnel ~10-15s later (silently breaking adoption).
238
+ const logPath = cloudflaredLogPath();
239
+ try { unlinkSync(logPath); } catch {} // truncate stale URLs from prior run
240
+ const tunnelLogFd = openSync(logPath, "a");
241
+ try {
242
+ tunnelChild = Bun.spawn(tunnelCmd, { stderr: tunnelLogFd, stdout: "ignore", stdin: "ignore" });
243
+ } finally {
244
+ // Close our handle; cloudflared keeps its own via dup2
245
+ try { closeSync(tunnelLogFd); } catch {}
246
+ }
247
247
  if (underSystemd) log("INFO", "Tunnel spawned inside transient systemd-run scope (escapes ppm.service cgroup)");
248
248
 
249
249
  try {
250
- tunnelUrl = await extractUrlFromStderr(tunnelChild.stderr as ReadableStream<Uint8Array>);
250
+ tunnelUrl = await extractUrlFromLogFile(tunnelChild);
251
251
  } catch (err) {
252
252
  log("ERROR", `Tunnel URL extraction failed: ${err}`);
253
253
  tunnelUrl = null;
@@ -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
 
@@ -109,12 +109,21 @@ export function MessageList({
109
109
  onFork?.(msgContent, msgId);
110
110
  }, [onFork]);
111
111
 
112
- // Wrap expandCompact: bump visibleCount by loaded count so expansion is immediately visible
113
- // in the paginated view (pre-compact messages land at top of flattened array, above pagination window).
112
+ // Scroll anchor bridge published from inside StickToBottom (needs the context's scrollRef).
113
+ // MessageList captures pre-expand scroll metrics and restores post-render so the compact
114
+ // message stays at the same viewport offset when history is prepended.
115
+ const scrollAnchorRef = useRef<ScrollAnchorHandle | null>(null);
116
+
117
+ // Wrap expandCompact: bump visibleCount, then restore scroll after React commits the new DOM.
118
+ // Pre-compact messages land at top of flattened array, above pagination window — bumping
119
+ // visibleCount by loaded count ensures they render immediately.
114
120
  const handleExpandCompact = useCallback(async (compactId: string, jsonlPath: string): Promise<number> => {
115
121
  if (!onExpandCompact) throw new Error("Expansion not wired");
122
+ scrollAnchorRef.current?.capture();
116
123
  const count = await onExpandCompact(compactId, jsonlPath);
117
124
  setVisibleCount((c) => c + count);
125
+ // rAF fires after React commits + layout; two rAFs to cover any async measure (lazy markdown).
126
+ requestAnimationFrame(() => requestAnimationFrame(() => scrollAnchorRef.current?.restore()));
118
127
  return count;
119
128
  }, [onExpandCompact]);
120
129
 
@@ -140,6 +149,7 @@ export function MessageList({
140
149
  <div className="relative flex-1 overflow-hidden flex flex-col min-h-0">
141
150
  <StickToBottom className="flex-1 overflow-y-auto overflow-x-hidden [contain:strict] [overflow-anchor:auto]" resize="smooth" initial="instant">
142
151
  <StickToBottom.Content className="p-4 space-y-4 select-none [&>*]:[overflow-anchor:auto]">
152
+ <ScrollAnchorBridge bridgeRef={scrollAnchorRef} />
143
153
  {hasMore && (
144
154
  <button onClick={() => setVisibleCount((c) => c + PAGE_SIZE)}
145
155
  className="w-full py-2 text-xs text-text-secondary hover:text-text-primary bg-surface-elevated/50 hover:bg-surface-elevated rounded-md border border-border/50 transition-colors">
@@ -179,6 +189,43 @@ export function MessageList({
179
189
  );
180
190
  }
181
191
 
192
+ /** Imperative handle exposed by ScrollAnchorBridge — capture & restore scroll on prepend. */
193
+ interface ScrollAnchorHandle {
194
+ /** Record current scrollTop + scrollHeight before a prepend. No-op if user is at bottom. */
195
+ capture: () => void;
196
+ /** After prepend commits, adjust scrollTop by the height delta so viewport stays locked. */
197
+ restore: () => void;
198
+ }
199
+
200
+ /**
201
+ * Consumes StickToBottom's scrollRef (only accessible inside its subtree) and publishes
202
+ * capture/restore functions to a ref owned by the parent MessageList, so prepend-history
203
+ * expansion can preserve scroll position across the re-render.
204
+ */
205
+ function ScrollAnchorBridge({ bridgeRef }: { bridgeRef: React.MutableRefObject<ScrollAnchorHandle | null> }) {
206
+ const { scrollRef, isAtBottom } = useStickToBottomContext();
207
+ const state = useRef<{ top: number; height: number } | null>(null);
208
+ useEffect(() => {
209
+ bridgeRef.current = {
210
+ capture: () => {
211
+ const el = scrollRef.current;
212
+ if (!el || isAtBottom) { state.current = null; return; } // skip if sticking to bottom
213
+ state.current = { top: el.scrollTop, height: el.scrollHeight };
214
+ },
215
+ restore: () => {
216
+ const el = scrollRef.current;
217
+ const s = state.current;
218
+ if (!el || !s) return;
219
+ const delta = el.scrollHeight - s.height;
220
+ if (delta !== 0) el.scrollTop = s.top + delta;
221
+ state.current = null;
222
+ },
223
+ };
224
+ return () => { bridgeRef.current = null; };
225
+ }, [bridgeRef, scrollRef, isAtBottom]);
226
+ return null;
227
+ }
228
+
182
229
  /** Floating button to scroll back to bottom when user has scrolled up */
183
230
  function ScrollToBottomButton() {
184
231
  const { isAtBottom, scrollToBottom } = useStickToBottomContext();