@hienlh/ppm 0.11.5 → 0.11.7

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 (81) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/web/assets/{ai-settings-section-D2vqiydT.js → ai-settings-section-C6FDY8qE.js} +1 -1
  3. package/dist/web/assets/{api-settings-2eTz4SgY.js → api-settings-C__hxGX2.js} +1 -1
  4. package/dist/web/assets/architecture-PBZL5I3N-XX6_EZsC.js +1 -0
  5. package/dist/web/assets/audio-preview--VaB403K.js +1 -0
  6. package/dist/web/assets/{chat-tab-CWc9ZYVY.js → chat-tab-DS455Mwt.js} +4 -4
  7. package/dist/web/assets/code-editor-BsUsDjJz.js +8 -0
  8. package/dist/web/assets/{conflict-editor-BmuhLalI.js → conflict-editor-DTMT0ZxI.js} +1 -1
  9. package/dist/web/assets/{csv-preview-D37K2LRd.js → csv-preview-BEBJD4a_.js} +1 -1
  10. package/dist/web/assets/{database-viewer-CqW0fH7Q.js → database-viewer-B7aIs1XX.js} +1 -1
  11. package/dist/web/assets/{diff-viewer-ium8zJhL.js → diff-viewer-C9unqCxZ.js} +1 -1
  12. package/dist/web/assets/{esm-B99v94EE.js → esm-K1XIK4vc.js} +1 -1
  13. package/dist/web/assets/{extension-store-CkyOvGbF.js → extension-store-3yZYn07W.js} +1 -1
  14. package/dist/web/assets/{extension-webview-AxcQX8ML.js → extension-webview-DUs8mqO_.js} +1 -1
  15. package/dist/web/assets/file-exclamation-point-BwzaQ50n.js +1 -0
  16. package/dist/web/assets/gitGraph-HDMCJU4V-BhjTKsbg.js +1 -0
  17. package/dist/web/assets/image-preview-CoiyMXuX.js +1 -0
  18. package/dist/web/assets/{index-CyciQ-Gz.js → index-B1GZswlR.js} +5 -5
  19. package/dist/web/assets/index-C3ZstZ9f.css +2 -0
  20. package/dist/web/assets/info-3K5VOQVL-CzgVqYTx.js +1 -0
  21. package/dist/web/assets/{input-CHRMley8.js → input-ClhO__YM.js} +1 -1
  22. package/dist/web/assets/keybindings-store-BkZjvU9J.js +1 -0
  23. package/dist/web/assets/{keybindings-store-CpP5_miA.js → keybindings-store-C9KsBH7z.js} +1 -1
  24. package/dist/web/assets/{markdown-renderer-Ckjp96ej.js → markdown-renderer-4LYdtPZU.js} +3 -3
  25. package/dist/web/assets/packet-RMMSAZCW-C7agXrtd.js +1 -0
  26. package/dist/web/assets/pdf-preview-CFO6CjHG.js +1 -0
  27. package/dist/web/assets/pie-UPGHQEXC-BRZ7alnf.js +1 -0
  28. package/dist/web/assets/{port-forwarding-tab-DR5Bngg2.js → port-forwarding-tab-DpoVwtlp.js} +1 -1
  29. package/dist/web/assets/{postgres-viewer-hy2EksiB.js → postgres-viewer-Ku3X9rJQ.js} +3 -3
  30. package/dist/web/assets/{project-store-CczGNZyf.js → project-store-BYmQ0fDC.js} +1 -1
  31. package/dist/web/assets/radar-KQ55EAFF-DSn_ekR5.js +1 -0
  32. package/dist/web/assets/{scroll-area-DwWF9FpN.js → scroll-area-DW7L4Gnc.js} +1 -1
  33. package/dist/web/assets/{settings-store-CuYjM0FF.js → settings-store-B9axDbuA.js} +2 -2
  34. package/dist/web/assets/settings-tab-l-SopAs4.js +1 -0
  35. package/dist/web/assets/{sql-query-editor-CVEi0jLM.js → sql-query-editor-BnpKNGG0.js} +1 -1
  36. package/dist/web/assets/{sqlite-viewer-DDPZjt-X.js → sqlite-viewer-DzS8-nbv.js} +1 -1
  37. package/dist/web/assets/{tab-store-Jvy1eZGM.js → tab-store-B3M9hjho.js} +1 -1
  38. package/dist/web/assets/{terminal-tab-DSWdc4AO.js → terminal-tab-ERPwhU5O.js} +1 -1
  39. package/dist/web/assets/treemap-KZPCXAKY-C8puYVyN.js +1 -0
  40. package/dist/web/assets/use-blob-url-BK7zshV7.js +1 -0
  41. package/dist/web/assets/{use-monaco-theme-kjiAwvOp.js → use-monaco-theme-BERjR8IA.js} +1 -1
  42. package/dist/web/assets/{vendor-mermaid-CylkVm4U.js → vendor-mermaid-BlWh9BJO.js} +2 -2
  43. package/dist/web/assets/video-preview-BZenXc_U.js +1 -0
  44. package/dist/web/index.html +17 -17
  45. package/dist/web/sw.js +1 -1
  46. package/package.json +1 -1
  47. package/src/server/ws/chat.ts +10 -0
  48. package/src/services/file-watcher.service.ts +72 -0
  49. package/src/services/supervisor.ts +29 -9
  50. package/src/web/components/editor/audio-preview.tsx +26 -0
  51. package/src/web/components/editor/code-editor.tsx +44 -81
  52. package/src/web/components/editor/image-preview.tsx +23 -0
  53. package/src/web/components/editor/pdf-preview.tsx +32 -0
  54. package/src/web/components/editor/use-blob-url.ts +35 -0
  55. package/src/web/components/editor/video-preview.tsx +23 -0
  56. package/src/web/components/explorer/file-tree.tsx +16 -5
  57. package/src/web/hooks/use-chat.ts +6 -0
  58. package/dist/web/assets/architecture-PBZL5I3N-BRW4VwMk.js +0 -1
  59. package/dist/web/assets/code-editor-CSej2S2J.js +0 -8
  60. package/dist/web/assets/gitGraph-HDMCJU4V-Bt68dqWT.js +0 -1
  61. package/dist/web/assets/index-iZHWllzQ.css +0 -2
  62. package/dist/web/assets/info-3K5VOQVL-ySD5z855.js +0 -1
  63. package/dist/web/assets/keybindings-store-qfYScgY0.js +0 -1
  64. package/dist/web/assets/packet-RMMSAZCW-CLxaXgIf.js +0 -1
  65. package/dist/web/assets/pie-UPGHQEXC-C9wPZfkn.js +0 -1
  66. package/dist/web/assets/radar-KQ55EAFF-DxEpzVN_.js +0 -1
  67. package/dist/web/assets/settings-tab-D5SXI74b.js +0 -1
  68. package/dist/web/assets/treemap-KZPCXAKY-yelcZZqO.js +0 -1
  69. /package/dist/web/assets/{api-client-C3tXCh0r.js → api-client-Bn-Pi9k5.js} +0 -0
  70. /package/dist/web/assets/{csv-parser-BAa56Nnn.js → csv-parser--2WJNgS7.js} +0 -0
  71. /package/dist/web/assets/{dist-On3hz9_g.js → dist-im4ynINo.js} +0 -0
  72. /package/dist/web/assets/{katex-Bbu770d9.js → katex-CKoArbIw.js} +0 -0
  73. /package/dist/web/assets/{lib-BqkcKGFq.js → lib-DQHnkzGy.js} +0 -0
  74. /package/dist/web/assets/{react-BkWDCPD7.js → react-GqWghJ-L.js} +0 -0
  75. /package/dist/web/assets/{refresh-cw-CSFrDtiu.js → refresh-cw-LlbZDJpO.js} +0 -0
  76. /package/dist/web/assets/{sql-completion-provider-D3acAhav.js → sql-completion-provider-C3cq9j99.js} +0 -0
  77. /package/dist/web/assets/{table-DbSviOmw.js → table-Dq575bPF.js} +0 -0
  78. /package/dist/web/assets/{text-wrap-DzvCTq_i.js → text-wrap-Cn6BNQfq.js} +0 -0
  79. /package/dist/web/assets/{trash-2-BgDIBl6f.js → trash-2-CJYoLw7Q.js} +0 -0
  80. /package/dist/web/assets/{utils-ChWX7pZv.js → utils-CTg5uAYR.js} +0 -0
  81. /package/dist/web/assets/{vendor-xterm-B9BUAFKA.js → vendor-xterm-CU2c3f0A.js} +0 -0
@@ -0,0 +1 @@
1
+ import{b as e}from"./vendor-markdown-0Mxgxy0L.js";import{t}from"./file-exclamation-point-BwzaQ50n.js";import"./api-client-Bn-Pi9k5.js";import{M as n}from"./index-B1GZswlR.js";import{t as r}from"./use-blob-url-BK7zshV7.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,32 @@
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-CyciQ-Gz.js"></script>
42
+ <script type="module" crossorigin src="/assets/index-B1GZswlR.js"></script>
43
43
  <link rel="modulepreload" crossorigin href="/assets/rolldown-runtime-FhOqtrmT.js">
44
- <link rel="modulepreload" crossorigin href="/assets/vendor-mermaid-CylkVm4U.js">
44
+ <link rel="modulepreload" crossorigin href="/assets/vendor-mermaid-BlWh9BJO.js">
45
45
  <link rel="modulepreload" crossorigin href="/assets/vendor-markdown-0Mxgxy0L.js">
46
46
  <link rel="modulepreload" crossorigin href="/assets/vendor-ui-B-T_damt.js">
47
- <link rel="modulepreload" crossorigin href="/assets/utils-ChWX7pZv.js">
47
+ <link rel="modulepreload" crossorigin href="/assets/utils-CTg5uAYR.js">
48
48
  <link rel="modulepreload" crossorigin href="/assets/createLucideIcon-BjHrJDVb.js">
49
49
  <link rel="modulepreload" crossorigin href="/assets/x-DlFGzN8d.js">
50
- <link rel="modulepreload" crossorigin href="/assets/input-CHRMley8.js">
51
- <link rel="modulepreload" crossorigin href="/assets/scroll-area-DwWF9FpN.js">
50
+ <link rel="modulepreload" crossorigin href="/assets/input-ClhO__YM.js">
51
+ <link rel="modulepreload" crossorigin href="/assets/scroll-area-DW7L4Gnc.js">
52
52
  <link rel="modulepreload" crossorigin href="/assets/dist-C5IgeqrV.js">
53
53
  <link rel="modulepreload" crossorigin href="/assets/plus-51UQ45rf.js">
54
- <link rel="modulepreload" crossorigin href="/assets/refresh-cw-CSFrDtiu.js">
55
- <link rel="modulepreload" crossorigin href="/assets/trash-2-BgDIBl6f.js">
56
- <link rel="modulepreload" crossorigin href="/assets/api-client-C3tXCh0r.js">
57
- <link rel="modulepreload" crossorigin href="/assets/api-settings-2eTz4SgY.js">
58
- <link rel="modulepreload" crossorigin href="/assets/ai-settings-section-D2vqiydT.js">
54
+ <link rel="modulepreload" crossorigin href="/assets/refresh-cw-LlbZDJpO.js">
55
+ <link rel="modulepreload" crossorigin href="/assets/trash-2-CJYoLw7Q.js">
56
+ <link rel="modulepreload" crossorigin href="/assets/api-client-Bn-Pi9k5.js">
57
+ <link rel="modulepreload" crossorigin href="/assets/api-settings-C__hxGX2.js">
58
+ <link rel="modulepreload" crossorigin href="/assets/ai-settings-section-C6FDY8qE.js">
59
59
  <link rel="modulepreload" crossorigin href="/assets/chevron-right-BzAdxJRG.js">
60
60
  <link rel="modulepreload" crossorigin href="/assets/database-D4DIhgi-.js">
61
- <link rel="modulepreload" crossorigin href="/assets/react-BkWDCPD7.js">
62
- <link rel="modulepreload" crossorigin href="/assets/extension-store-CkyOvGbF.js">
63
- <link rel="modulepreload" crossorigin href="/assets/keybindings-store-CpP5_miA.js">
64
- <link rel="modulepreload" crossorigin href="/assets/tab-store-Jvy1eZGM.js">
65
- <link rel="modulepreload" crossorigin href="/assets/project-store-CczGNZyf.js">
66
- <link rel="modulepreload" crossorigin href="/assets/settings-store-CuYjM0FF.js">
67
- <link rel="stylesheet" crossorigin href="/assets/index-iZHWllzQ.css">
61
+ <link rel="modulepreload" crossorigin href="/assets/react-GqWghJ-L.js">
62
+ <link rel="modulepreload" crossorigin href="/assets/extension-store-3yZYn07W.js">
63
+ <link rel="modulepreload" crossorigin href="/assets/keybindings-store-C9KsBH7z.js">
64
+ <link rel="modulepreload" crossorigin href="/assets/tab-store-B3M9hjho.js">
65
+ <link rel="modulepreload" crossorigin href="/assets/project-store-BYmQ0fDC.js">
66
+ <link rel="modulepreload" crossorigin href="/assets/settings-store-B9axDbuA.js">
67
+ <link rel="stylesheet" crossorigin href="/assets/index-C3ZstZ9f.css">
68
68
  <link rel="manifest" href="/manifest.webmanifest"><script id="vite-plugin-pwa:register-sw" src="/registerSW.js"></script></head>
69
69
  <body class="bg-[#0f1419] text-[#e5e7eb] font-sans antialiased">
70
70
  <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":"3e55d147f4b877c27fb3df0baed9051d","url":"index.html"},{"revision":"a0fb34fc84eb148d51812cd62669f20d","url":"icon-512.svg"},{"revision":null,"url":"assets/lib-BqkcKGFq.js"},{"revision":null,"url":"assets/input-CHRMley8.js"},{"revision":null,"url":"assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2"},{"revision":null,"url":"assets/KaTeX_AMS-Regular-BQhdFMY1.woff2"},{"revision":null,"url":"assets/terminal-tab-DSWdc4AO.js"},{"revision":null,"url":"assets/tab-store-Jvy1eZGM.js"},{"revision":null,"url":"assets/database-D4DIhgi-.js"},{"revision":null,"url":"assets/x-DlFGzN8d.js"},{"revision":null,"url":"assets/gitGraph-HDMCJU4V-Bt68dqWT.js"},{"revision":null,"url":"assets/postgres-viewer-hy2EksiB.js"},{"revision":null,"url":"assets/markdown-renderer-Ckjp96ej.js"},{"revision":null,"url":"assets/KaTeX_Main-Regular-B22Nviop.woff2"},{"revision":null,"url":"assets/index-CyciQ-Gz.js"},{"revision":null,"url":"assets/settings-tab-D5SXI74b.js"},{"revision":null,"url":"assets/ai-settings-section-D2vqiydT.js"},{"revision":null,"url":"assets/vendor-ui-B-T_damt.js"},{"revision":null,"url":"assets/database-viewer-CqW0fH7Q.js"},{"revision":null,"url":"assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2"},{"revision":null,"url":"assets/settings-store-CuYjM0FF.js"},{"revision":null,"url":"assets/pie-UPGHQEXC-C9wPZfkn.js"},{"revision":null,"url":"assets/chevron-right-BzAdxJRG.js"},{"revision":null,"url":"assets/sql-query-editor-CVEi0jLM.js"},{"revision":null,"url":"assets/packet-RMMSAZCW-CLxaXgIf.js"},{"revision":null,"url":"assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2"},{"revision":null,"url":"assets/vendor-markdown-0Mxgxy0L.js"},{"revision":null,"url":"assets/KaTeX_Main-Italic-NWA7e6Wa.woff2"},{"revision":null,"url":"assets/trash-2-BgDIBl6f.js"},{"revision":null,"url":"assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2"},{"revision":null,"url":"assets/dist-C5IgeqrV.js"},{"revision":null,"url":"assets/extension-webview-AxcQX8ML.js"},{"revision":null,"url":"assets/plus-51UQ45rf.js"},{"revision":null,"url":"assets/chat-tab-CWc9ZYVY.js"},{"revision":null,"url":"assets/esm-B99v94EE.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/api-settings-2eTz4SgY.js"},{"revision":null,"url":"assets/arrow-up-Dtrfv490.js"},{"revision":null,"url":"assets/radar-KQ55EAFF-DxEpzVN_.js"},{"revision":null,"url":"assets/refresh-cw-CSFrDtiu.js"},{"revision":null,"url":"assets/vendor-xterm-B9BUAFKA.js"},{"revision":null,"url":"assets/sqlite-viewer-DDPZjt-X.js"},{"revision":null,"url":"assets/conflict-editor-BmuhLalI.js"},{"revision":null,"url":"assets/rolldown-runtime-FhOqtrmT.js"},{"revision":null,"url":"assets/api-client-C3tXCh0r.js"},{"revision":null,"url":"assets/KaTeX_Math-Italic-t53AETM-.woff2"},{"revision":null,"url":"assets/use-monaco-theme-kjiAwvOp.js"},{"revision":null,"url":"assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2"},{"revision":null,"url":"assets/KaTeX_Script-Regular-D3wIWfF6.woff2"},{"revision":null,"url":"assets/extension-store-CkyOvGbF.js"},{"revision":null,"url":"assets/info-3K5VOQVL-ySD5z855.js"},{"revision":null,"url":"assets/columns-2-4fQcE4PF.js"},{"revision":null,"url":"assets/KaTeX_Main-Bold-Cx986IdX.woff2"},{"revision":null,"url":"assets/sql-completion-provider-D3acAhav.js"},{"revision":null,"url":"assets/react-BkWDCPD7.js"},{"revision":null,"url":"assets/KaTeX_Size2-Regular-Dy4dx90m.woff2"},{"revision":null,"url":"assets/code-editor-CSej2S2J.js"},{"revision":null,"url":"assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2"},{"revision":null,"url":"assets/KaTeX_Size1-Regular-mCD8mA8B.woff2"},{"revision":null,"url":"assets/architecture-PBZL5I3N-BRW4VwMk.js"},{"revision":null,"url":"assets/treemap-KZPCXAKY-yelcZZqO.js"},{"revision":null,"url":"assets/createLucideIcon-BjHrJDVb.js"},{"revision":null,"url":"assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2"},{"revision":null,"url":"assets/port-forwarding-tab-DR5Bngg2.js"},{"revision":null,"url":"assets/code-CuravVys.js"},{"revision":null,"url":"assets/csv-parser-BAa56Nnn.js"},{"revision":null,"url":"assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2"},{"revision":null,"url":"assets/project-store-CczGNZyf.js"},{"revision":null,"url":"assets/katex-Bbu770d9.js"},{"revision":null,"url":"assets/keybindings-store-qfYScgY0.js"},{"revision":null,"url":"assets/utils-ChWX7pZv.js"},{"revision":null,"url":"assets/text-wrap-DzvCTq_i.js"},{"revision":null,"url":"assets/vendor-xterm-BrP-ENHg.css"},{"revision":null,"url":"assets/scroll-area-DwWF9FpN.js"},{"revision":null,"url":"assets/index-iZHWllzQ.css"},{"revision":null,"url":"assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2"},{"revision":null,"url":"assets/keybindings-store-CpP5_miA.js"},{"revision":null,"url":"assets/csv-preview-D37K2LRd.js"},{"revision":null,"url":"assets/vendor-mermaid-CylkVm4U.js"},{"revision":null,"url":"assets/diff-viewer-ium8zJhL.js"},{"revision":null,"url":"assets/table-DbSviOmw.js"},{"revision":null,"url":"assets/dist-On3hz9_g.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":"ff071aa161c8548ee3f427470d37187e","url":"index.html"},{"revision":"a0fb34fc84eb148d51812cd62669f20d","url":"icon-512.svg"},{"revision":null,"url":"assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2"},{"revision":null,"url":"assets/input-ClhO__YM.js"},{"revision":null,"url":"assets/KaTeX_AMS-Regular-BQhdFMY1.woff2"},{"revision":null,"url":"assets/diff-viewer-C9unqCxZ.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/video-preview-BZenXc_U.js"},{"revision":null,"url":"assets/conflict-editor-DTMT0ZxI.js"},{"revision":null,"url":"assets/csv-preview-BEBJD4a_.js"},{"revision":null,"url":"assets/vendor-xterm-CU2c3f0A.js"},{"revision":null,"url":"assets/keybindings-store-C9KsBH7z.js"},{"revision":null,"url":"assets/KaTeX_Main-Regular-B22Nviop.woff2"},{"revision":null,"url":"assets/gitGraph-HDMCJU4V-BhjTKsbg.js"},{"revision":null,"url":"assets/refresh-cw-LlbZDJpO.js"},{"revision":null,"url":"assets/sql-query-editor-BnpKNGG0.js"},{"revision":null,"url":"assets/architecture-PBZL5I3N-XX6_EZsC.js"},{"revision":null,"url":"assets/react-GqWghJ-L.js"},{"revision":null,"url":"assets/pdf-preview-CFO6CjHG.js"},{"revision":null,"url":"assets/vendor-ui-B-T_damt.js"},{"revision":null,"url":"assets/markdown-renderer-4LYdtPZU.js"},{"revision":null,"url":"assets/katex-CKoArbIw.js"},{"revision":null,"url":"assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2"},{"revision":null,"url":"assets/chevron-right-BzAdxJRG.js"},{"revision":null,"url":"assets/chat-tab-DS455Mwt.js"},{"revision":null,"url":"assets/index-B1GZswlR.js"},{"revision":null,"url":"assets/extension-store-3yZYn07W.js"},{"revision":null,"url":"assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2"},{"revision":null,"url":"assets/project-store-BYmQ0fDC.js"},{"revision":null,"url":"assets/scroll-area-DW7L4Gnc.js"},{"revision":null,"url":"assets/vendor-markdown-0Mxgxy0L.js"},{"revision":null,"url":"assets/postgres-viewer-Ku3X9rJQ.js"},{"revision":null,"url":"assets/vendor-mermaid-BlWh9BJO.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/packet-RMMSAZCW-C7agXrtd.js"},{"revision":null,"url":"assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2"},{"revision":null,"url":"assets/dist-C5IgeqrV.js"},{"revision":null,"url":"assets/keybindings-store-BkZjvU9J.js"},{"revision":null,"url":"assets/plus-51UQ45rf.js"},{"revision":null,"url":"assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2"},{"revision":null,"url":"assets/use-monaco-theme-BERjR8IA.js"},{"revision":null,"url":"assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2"},{"revision":null,"url":"assets/arrow-up-Dtrfv490.js"},{"revision":null,"url":"assets/radar-KQ55EAFF-DSn_ekR5.js"},{"revision":null,"url":"assets/rolldown-runtime-FhOqtrmT.js"},{"revision":null,"url":"assets/ai-settings-section-C6FDY8qE.js"},{"revision":null,"url":"assets/info-3K5VOQVL-CzgVqYTx.js"},{"revision":null,"url":"assets/KaTeX_Math-Italic-t53AETM-.woff2"},{"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/code-editor-BsUsDjJz.js"},{"revision":null,"url":"assets/extension-webview-DUs8mqO_.js"},{"revision":null,"url":"assets/file-exclamation-point-BwzaQ50n.js"},{"revision":null,"url":"assets/terminal-tab-ERPwhU5O.js"},{"revision":null,"url":"assets/api-settings-C__hxGX2.js"},{"revision":null,"url":"assets/index-C3ZstZ9f.css"},{"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/sqlite-viewer-DzS8-nbv.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/audio-preview--VaB403K.js"},{"revision":null,"url":"assets/image-preview-CoiyMXuX.js"},{"revision":null,"url":"assets/tab-store-B3M9hjho.js"},{"revision":null,"url":"assets/treemap-KZPCXAKY-C8puYVyN.js"},{"revision":null,"url":"assets/api-client-Bn-Pi9k5.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/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2"},{"revision":null,"url":"assets/sql-completion-provider-C3cq9j99.js"},{"revision":null,"url":"assets/settings-store-B9axDbuA.js"},{"revision":null,"url":"assets/use-blob-url-BK7zshV7.js"},{"revision":null,"url":"assets/esm-K1XIK4vc.js"},{"revision":null,"url":"assets/settings-tab-l-SopAs4.js"},{"revision":null,"url":"assets/vendor-xterm-BrP-ENHg.css"},{"revision":null,"url":"assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2"},{"revision":null,"url":"assets/port-forwarding-tab-DpoVwtlp.js"},{"revision":null,"url":"assets/lib-DQHnkzGy.js"},{"revision":null,"url":"assets/database-viewer-B7aIs1XX.js"},{"revision":null,"url":"assets/pie-UPGHQEXC-BRZ7alnf.js"},{"revision":"d0f94ce046cf8cf09605ee7664dac557","url":"monacoeditorwork/html.worker.bundle.js"},{"revision":"a424156a79b9c1b907db93aa3180585a","url":"monacoeditorwork/editor.worker.bundle.js"},{"revision":"b3a7f967560c9816492a1567b3f7f0dc","url":"monacoeditorwork/css.worker.bundle.js"},{"revision":"a5d8a1acfc29c2a4c882a54ffc93def3","url":"monacoeditorwork/json.worker.bundle.js"},{"revision":"948e060affb598c339be40d69e1f6f9c","url":"monacoeditorwork/ts.worker.bundle.js"},{"revision":"79c8870653c8f419f2e3323085e1f4be","url":"manifest.webmanifest"}]),self.addEventListener(`push`,e=>{e.waitUntil(self.clients.matchAll({type:`window`,includeUncontrolled:!0}).then(t=>{if(t.some(e=>e.visibilityState===`visible`))return;let n=e.data?.json()??{title:`PPM`,body:`Chat completed`};return self.registration.showNotification(n.title,{body:n.body,icon:`/icon-192.png`,badge:`/icon-192.png`,tag:`ppm-chat-done`,silent:!1,data:{url:self.location.origin}})}))}),self.addEventListener(`notificationclick`,e=>{e.notification.close(),e.waitUntil(self.clients.matchAll({type:`window`,includeUncontrolled:!0}).then(t=>{for(let e of t)if(e.url.includes(self.location.origin)&&`focus`in e)return e.focus();return self.clients.openWindow(e.notification.data?.url||`/`)}))});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hienlh/ppm",
3
- "version": "0.11.5",
3
+ "version": "0.11.7",
4
4
  "description": "Personal Project Manager — mobile-first web IDE with AI assistance",
5
5
  "author": "hienlh",
6
6
  "license": "MIT",
@@ -5,6 +5,12 @@ import { logSessionEvent } from "../../services/session-log.service.ts";
5
5
  import { listSessions as sdkListSessions } from "@anthropic-ai/claude-agent-sdk";
6
6
  import { getSessionTitle } from "../../services/db.service.ts";
7
7
  import type { ChatWsClientMessage, SessionPhase } from "../../types/api.ts";
8
+ import { startWatching, stopWatching, onFileChange } from "../../services/file-watcher.service.ts";
9
+
10
+ // Broadcast file changes to all WS clients for real-time editor reload
11
+ onFileChange((projectName, path) => {
12
+ broadcastGlobalEvent({ type: "file:changed", projectName, path });
13
+ });
8
14
 
9
15
  const PING_INTERVAL_MS = 15_000; // 15s keepalive
10
16
  const CLEANUP_TIMEOUT_MS = 5 * 60_000; // 5min after Claude done + no FE
@@ -184,6 +190,7 @@ function startCleanupTimer(sessionId: string): void {
184
190
  entry.pingIntervals.clear();
185
191
  for (const w of entry.teamWatchers.values()) w.cleanup();
186
192
  entry.teamWatchers.clear();
193
+ if (entry.projectName) stopWatching(entry.projectName);
187
194
  activeSessions.delete(sessionId);
188
195
  }, CLEANUP_TIMEOUT_MS);
189
196
  }
@@ -481,6 +488,7 @@ export const chatWebSocket = {
481
488
  }
482
489
  }).catch(() => {});
483
490
  }
491
+ if (projectName && projectPath) startWatching(projectName, projectPath);
484
492
  console.log(`[chat] session=${sessionId} FE reconnected (phase=${existing.phase}, clients=${existing.clients.size})`);
485
493
  return;
486
494
  }
@@ -500,6 +508,7 @@ export const chatWebSocket = {
500
508
  };
501
509
  activeSessions.set(sessionId, newEntry);
502
510
  setupClientPing(newEntry, ws);
511
+ if (projectName && projectPath) startWatching(projectName, projectPath);
503
512
 
504
513
  ws.send(JSON.stringify({
505
514
  type: "session_state",
@@ -707,6 +716,7 @@ export const chatWebSocket = {
707
716
 
708
717
  // Remove from clients Set + clear per-client ping
709
718
  evictClient(entry, ws);
719
+ if (entry.projectName) stopWatching(entry.projectName);
710
720
  console.log(`[chat] session=${sessionId} FE disconnected (phase=${entry.phase}, clients=${entry.clients.size})`);
711
721
 
712
722
  if (entry.clients.size === 0) {
@@ -0,0 +1,72 @@
1
+ import { watch, type FSWatcher } from "node:fs";
2
+
3
+ const IGNORE = new Set([
4
+ ".git", "node_modules", "dist", "build", ".next",
5
+ ".turbo", "coverage", "__pycache__", "bun.lock",
6
+ ]);
7
+ const DEBOUNCE_MS = 500;
8
+
9
+ type ChangeCallback = (projectName: string, path: string) => void;
10
+
11
+ interface WatchEntry {
12
+ watcher: FSWatcher;
13
+ refCount: number;
14
+ timer?: ReturnType<typeof setTimeout>;
15
+ pending: Set<string>;
16
+ }
17
+
18
+ const watchers = new Map<string, WatchEntry>();
19
+ let changeCallback: ChangeCallback | null = null;
20
+
21
+ /** Register callback for file change events */
22
+ export function onFileChange(cb: ChangeCallback): void {
23
+ changeCallback = cb;
24
+ }
25
+
26
+ function shouldIgnore(filePath: string): boolean {
27
+ const parts = filePath.split(/[/\\]/);
28
+ return parts.some((p) => IGNORE.has(p));
29
+ }
30
+
31
+ /** Start watching a project directory (ref-counted — safe to call multiple times) */
32
+ export function startWatching(projectName: string, projectPath: string): void {
33
+ const existing = watchers.get(projectName);
34
+ if (existing) {
35
+ existing.refCount++;
36
+ return;
37
+ }
38
+
39
+ try {
40
+ const watcher = watch(projectPath, { recursive: true }, (_event, filename) => {
41
+ if (!filename || shouldIgnore(filename)) return;
42
+ const entry = watchers.get(projectName);
43
+ if (!entry) return;
44
+
45
+ entry.pending.add(filename);
46
+ if (entry.timer) clearTimeout(entry.timer);
47
+ entry.timer = setTimeout(() => {
48
+ const paths = [...entry.pending];
49
+ entry.pending.clear();
50
+ for (const p of paths) changeCallback?.(projectName, p.replaceAll("\\", "/"));
51
+ }, DEBOUNCE_MS);
52
+ });
53
+
54
+ watchers.set(projectName, { watcher, refCount: 1, pending: new Set() });
55
+ console.log(`[file-watcher] Started watching: ${projectName}`);
56
+ } catch (e) {
57
+ console.warn(`[file-watcher] Failed to watch ${projectPath}: ${(e as Error).message}`);
58
+ }
59
+ }
60
+
61
+ /** Decrement ref count — stops watcher when no clients remain */
62
+ export function stopWatching(projectName: string): void {
63
+ const entry = watchers.get(projectName);
64
+ if (!entry) return;
65
+ entry.refCount--;
66
+ if (entry.refCount <= 0) {
67
+ if (entry.timer) clearTimeout(entry.timer);
68
+ entry.watcher.close();
69
+ watchers.delete(projectName);
70
+ console.log(`[file-watcher] Stopped watching: ${projectName}`);
71
+ }
72
+ }
@@ -75,9 +75,8 @@ function log(level: string, msg: string) {
75
75
  const ts = new Date().toISOString();
76
76
  const line = `[${ts}] [${level}] [supervisor] ${msg}\n`;
77
77
  try { appendFileSync(logFile(), line); } catch {}
78
- if (level === "ERROR" || level === "FATAL") {
79
- process.stderr.write(line);
80
- }
78
+ // Always write supervisor logs to stderr so journalctl captures them
79
+ try { process.stderr.write(line); } catch {}
81
80
  }
82
81
 
83
82
  // ─── Backoff calc ──────────────────────────────────────────────────────
@@ -734,9 +733,20 @@ export function shutdown() {
734
733
  if (upgradeDelayTimer) clearTimeout(upgradeDelayTimer);
735
734
  if (cloudMonitorTimer) clearInterval(cloudMonitorTimer);
736
735
 
737
- if (serverChild) { try { serverChild.kill(); } catch {} }
738
- if (tunnelChild) { try { tunnelChild.kill(); } catch {} }
739
- if (adoptedTunnelPid) { try { process.kill(adoptedTunnelPid, "SIGTERM"); } catch {} }
736
+ // Use SIGKILL for children SIGTERM leaves grandchildren (Claude SDK, etc.)
737
+ // alive, causing systemd to wait 90s then SIGKILL the entire cgroup
738
+ if (serverChild) {
739
+ log("INFO", `Killing server child (PID: ${serverChild.pid})`);
740
+ try { serverChild.kill("SIGKILL"); } catch {}
741
+ }
742
+ if (tunnelChild) {
743
+ log("INFO", `Killing tunnel child (PID: ${tunnelChild.pid})`);
744
+ try { tunnelChild.kill("SIGKILL"); } catch {}
745
+ }
746
+ if (adoptedTunnelPid) {
747
+ log("INFO", `Killing adopted tunnel (PID: ${adoptedTunnelPid})`);
748
+ try { process.kill(adoptedTunnelPid, "SIGKILL"); } catch {}
749
+ }
740
750
  }
741
751
 
742
752
  // ─── Main entry ────────────────────────────────────────────────────────
@@ -788,9 +798,19 @@ export async function runSupervisor(opts: {
788
798
  _logFd = logFd;
789
799
  _opts = { port: opts.port, host: opts.host, share: opts.share };
790
800
 
791
- // Signal handlers
792
- process.on("SIGTERM", () => { shutdown(); process.exit(0); });
793
- process.on("SIGINT", () => { shutdown(); process.exit(0); });
801
+ // Signal handlers — force exit after 5s if process.exit doesn't work
802
+ const forceShutdown = (signal: string) => {
803
+ log("INFO", `${signal} received`);
804
+ shutdown();
805
+ // Safety net: force kill self if process.exit(0) doesn't terminate
806
+ setTimeout(() => {
807
+ log("WARN", `Force exit after ${signal} — process.exit(0) did not terminate`);
808
+ try { process.kill(process.pid, "SIGKILL"); } catch {}
809
+ }, 5000).unref();
810
+ process.exit(0);
811
+ };
812
+ process.on("SIGTERM", () => forceShutdown("SIGTERM"));
813
+ process.on("SIGINT", () => forceShutdown("SIGINT"));
794
814
 
795
815
  // SIGUSR2 = command file dispatch OR graceful server restart
796
816
  process.on("SIGUSR2", () => {
@@ -0,0 +1,26 @@
1
+ import { Loader2, FileWarning, Music } from "lucide-react";
2
+ import { useBlobUrl } from "./use-blob-url";
3
+ import { basename } from "@/lib/utils";
4
+
5
+ export function AudioPreview({ filePath, projectName }: { filePath: string; projectName: string }) {
6
+ const { blobUrl, error } = useBlobUrl(filePath, projectName);
7
+
8
+ if (error) {
9
+ return (
10
+ <div className="flex flex-col items-center justify-center h-full gap-3 text-text-secondary">
11
+ <FileWarning className="size-10 text-text-subtle" />
12
+ <p className="text-sm">Failed to load audio.</p>
13
+ </div>
14
+ );
15
+ }
16
+ if (!blobUrl) {
17
+ return <div className="flex items-center justify-center h-full"><Loader2 className="size-5 animate-spin text-text-subtle" /></div>;
18
+ }
19
+ return (
20
+ <div className="flex flex-col items-center justify-center h-full gap-4 p-4 bg-surface">
21
+ <Music className="size-16 text-text-subtle" />
22
+ <p className="text-sm text-text-secondary truncate max-w-xs">{basename(filePath)}</p>
23
+ <audio src={blobUrl} controls className="w-full max-w-md" />
24
+ </div>
25
+ );
26
+ }
@@ -1,14 +1,14 @@
1
1
  import { useEffect, useState, useCallback, useRef, useMemo, memo, lazy, Suspense } from "react";
2
2
  import Editor, { type OnMount } from "@monaco-editor/react";
3
3
  import type * as MonacoType from "monaco-editor";
4
- import { api, projectUrl, getAuthToken } from "@/lib/api-client";
4
+ import { api, projectUrl } from "@/lib/api-client";
5
5
  import { useShallow } from "zustand/react/shallow";
6
6
  import { useTabStore } from "@/stores/tab-store";
7
7
  import { usePanelStore } from "@/stores/panel-store";
8
8
  import { useSettingsStore } from "@/stores/settings-store";
9
9
  import { basename } from "@/lib/utils";
10
10
  import { useMonacoTheme } from "@/lib/use-monaco-theme";
11
- import { Loader2, FileWarning, ExternalLink, Play, Database } from "lucide-react";
11
+ import { Loader2, FileWarning, Play, Database } from "lucide-react";
12
12
  import { EditorBreadcrumb } from "./editor-breadcrumb";
13
13
  import { EditorToolbar } from "./editor-toolbar";
14
14
  import { SaveAsDialog } from "./save-as-dialog";
@@ -19,9 +19,17 @@ const MarkdownRenderer = lazy(() =>
19
19
  import("@/components/shared/markdown-renderer").then((m) => ({ default: m.MarkdownRenderer }))
20
20
  );
21
21
  const CsvPreview = lazy(() => import("./csv-preview").then((m) => ({ default: m.CsvPreview })));
22
+ const ImagePreview = lazy(() => import("./image-preview").then((m) => ({ default: m.ImagePreview })));
23
+ const PdfPreview = lazy(() => import("./pdf-preview").then((m) => ({ default: m.PdfPreview })));
24
+ const VideoPreview = lazy(() => import("./video-preview").then((m) => ({ default: m.VideoPreview })));
25
+ const AudioPreview = lazy(() => import("./audio-preview").then((m) => ({ default: m.AudioPreview })));
22
26
 
23
27
  /** Image extensions renderable inline */
24
28
  const IMAGE_EXTS = new Set(["png", "jpg", "jpeg", "gif", "webp", "svg", "ico"]);
29
+ /** Video extensions playable inline */
30
+ const VIDEO_EXTS = new Set(["mp4", "webm", "mov", "ogg", "avi", "mkv"]);
31
+ /** Audio extensions playable inline */
32
+ const AUDIO_EXTS = new Set(["mp3", "wav", "flac", "aac", "m4a", "wma"]);
25
33
  /** SQLite extensions — redirect to sqlite viewer */
26
34
  const SQLITE_EXTS = new Set(["db", "sqlite", "sqlite3"]);
27
35
 
@@ -75,6 +83,8 @@ export const CodeEditor = memo(function CodeEditor({ metadata, tabId }: CodeEdit
75
83
  const ext = filePath ? getFileExt(filePath) : "";
76
84
  const isImage = IMAGE_EXTS.has(ext);
77
85
  const isPdf = ext === "pdf";
86
+ const isVideo = VIDEO_EXTS.has(ext);
87
+ const isAudio = AUDIO_EXTS.has(ext);
78
88
  const isSqlite = SQLITE_EXTS.has(ext);
79
89
  const isMarkdown = ext === "md" || ext === "mdx";
80
90
  const isCsv = ext === "csv";
@@ -215,7 +225,7 @@ export const CodeEditor = memo(function CodeEditor({ metadata, tabId }: CodeEdit
215
225
  }
216
226
  if (!filePath) return;
217
227
  if (!isExternalFile && !projectName) return;
218
- if (isImage || isPdf) { setLoading(false); return; }
228
+ if (isImage || isPdf || isVideo || isAudio) { setLoading(false); return; }
219
229
 
220
230
  setLoading(true);
221
231
  setError(null);
@@ -240,6 +250,29 @@ export const CodeEditor = memo(function CodeEditor({ metadata, tabId }: CodeEdit
240
250
  return () => { if (saveTimerRef.current) clearTimeout(saveTimerRef.current); };
241
251
  }, [filePath, projectName, isImage, isPdf, isExternalFile, isUntitled]);
242
252
 
253
+ // Real-time reload: listen for file:changed WS events, re-fetch if editor is clean
254
+ const unsavedRef = useRef(unsaved);
255
+ unsavedRef.current = unsaved;
256
+ useEffect(() => {
257
+ if (!filePath || !projectName || inlineContent != null || isUntitled) return;
258
+ const handler = (e: Event) => {
259
+ const detail = (e as CustomEvent).detail;
260
+ if (detail.projectName !== projectName || detail.path !== filePath) return;
261
+ if (unsavedRef.current) return; // don't overwrite unsaved changes
262
+ const readUrl = isExternalFile
263
+ ? `/api/fs/read?path=${encodeURIComponent(filePath)}`
264
+ : `${projectUrl(projectName)}/files/read?path=${encodeURIComponent(filePath)}`;
265
+ api.get<{ content: string; encoding?: string }>(readUrl).then((data) => {
266
+ if (data.content === latestContentRef.current) return; // skip if unchanged (e.g. self-save)
267
+ setContent(data.content);
268
+ latestContentRef.current = data.content;
269
+ if (data.encoding) setEncoding(data.encoding);
270
+ }).catch(() => {});
271
+ };
272
+ window.addEventListener("file:changed", handler);
273
+ return () => window.removeEventListener("file:changed", handler);
274
+ }, [filePath, projectName, isExternalFile, inlineContent, isUntitled]);
275
+
243
276
  // Update tab title unsaved indicator
244
277
  useEffect(() => {
245
278
  if (!ownTab) return;
@@ -428,8 +461,10 @@ export const CodeEditor = memo(function CodeEditor({ metadata, tabId }: CodeEdit
428
461
  );
429
462
  }
430
463
 
431
- if (isImage) return <ImagePreview filePath={filePath!} projectName={projectName!} />;
432
- if (isPdf) return <PdfPreview filePath={filePath!} projectName={projectName!} />;
464
+ if (isImage) return <Suspense fallback={<LoadingSpinner />}><ImagePreview filePath={filePath!} projectName={projectName!} /></Suspense>;
465
+ if (isPdf) return <Suspense fallback={<LoadingSpinner />}><PdfPreview filePath={filePath!} projectName={projectName!} /></Suspense>;
466
+ if (isVideo) return <Suspense fallback={<LoadingSpinner />}><VideoPreview filePath={filePath!} projectName={projectName!} /></Suspense>;
467
+ if (isAudio) return <Suspense fallback={<LoadingSpinner />}><AudioPreview filePath={filePath!} projectName={projectName!} /></Suspense>;
433
468
 
434
469
  if (encoding === "base64") {
435
470
  return (
@@ -557,6 +592,10 @@ export const CodeEditor = memo(function CodeEditor({ metadata, tabId }: CodeEdit
557
592
  );
558
593
  });
559
594
 
595
+ function LoadingSpinner() {
596
+ return <div className="flex items-center justify-center h-full"><Loader2 className="size-5 animate-spin text-text-subtle" /></div>;
597
+ }
598
+
560
599
  function MarkdownPreview({ content }: { content: string }) {
561
600
  return (
562
601
  <Suspense fallback={<div className="animate-pulse h-4 bg-muted rounded m-4" />}>
@@ -565,79 +604,3 @@ function MarkdownPreview({ content }: { content: string }) {
565
604
  );
566
605
  }
567
606
 
568
- function ImagePreview({ filePath, projectName }: { filePath: string; projectName: string }) {
569
- const [blobUrl, setBlobUrl] = useState<string | null>(null);
570
- const [error, setError] = useState(false);
571
-
572
- useEffect(() => {
573
- let revoke: string | undefined;
574
- const url = `${projectUrl(projectName)}/files/raw?path=${encodeURIComponent(filePath)}`;
575
- const token = getAuthToken();
576
- fetch(url, { headers: token ? { Authorization: `Bearer ${token}` } : {} })
577
- .then((r) => { if (!r.ok) throw new Error("Failed"); return r.blob(); })
578
- .then((blob) => { const u = URL.createObjectURL(blob); revoke = u; setBlobUrl(u); })
579
- .catch(() => setError(true));
580
- return () => { if (revoke) URL.revokeObjectURL(revoke); };
581
- }, [filePath, projectName]);
582
-
583
- if (error) {
584
- return (
585
- <div className="flex flex-col items-center justify-center h-full gap-3 text-text-secondary">
586
- <FileWarning className="size-10 text-text-subtle" />
587
- <p className="text-sm">Failed to load image.</p>
588
- </div>
589
- );
590
- }
591
- if (!blobUrl) {
592
- return <div className="flex items-center justify-center h-full"><Loader2 className="size-5 animate-spin text-text-subtle" /></div>;
593
- }
594
- return (
595
- <div className="flex items-center justify-center h-full p-4 bg-surface overflow-auto">
596
- <img src={blobUrl} alt={filePath} className="max-w-full max-h-full object-contain" />
597
- </div>
598
- );
599
- }
600
-
601
- function PdfPreview({ filePath, projectName }: { filePath: string; projectName: string }) {
602
- const [blobUrl, setBlobUrl] = useState<string | null>(null);
603
- const [error, setError] = useState(false);
604
-
605
- useEffect(() => {
606
- let revoke: string | undefined;
607
- const url = `${projectUrl(projectName)}/files/raw?path=${encodeURIComponent(filePath)}`;
608
- const token = getAuthToken();
609
- fetch(url, { headers: token ? { Authorization: `Bearer ${token}` } : {} })
610
- .then((r) => { if (!r.ok) throw new Error("Failed"); return r.blob(); })
611
- .then((blob) => {
612
- const u = URL.createObjectURL(new Blob([blob], { type: "application/pdf" }));
613
- revoke = u; setBlobUrl(u);
614
- })
615
- .catch(() => setError(true));
616
- return () => { if (revoke) URL.revokeObjectURL(revoke); };
617
- }, [filePath, projectName]);
618
-
619
- const openInNewTab = useCallback(() => { if (blobUrl) window.open(blobUrl, "_blank"); }, [blobUrl]);
620
-
621
- if (error) {
622
- return (
623
- <div className="flex flex-col items-center justify-center h-full gap-3 text-text-secondary">
624
- <FileWarning className="size-10 text-text-subtle" />
625
- <p className="text-sm">Failed to load PDF.</p>
626
- </div>
627
- );
628
- }
629
- if (!blobUrl) {
630
- return <div className="flex items-center justify-center h-full"><Loader2 className="size-5 animate-spin text-text-subtle" /></div>;
631
- }
632
- return (
633
- <div className="flex flex-col h-full">
634
- <div className="flex items-center justify-between px-3 py-1.5 border-b border-border bg-background shrink-0">
635
- <span className="text-xs text-text-secondary truncate">{filePath}</span>
636
- <button onClick={openInNewTab} className="flex items-center gap-1 text-xs text-text-secondary hover:text-text-primary transition-colors">
637
- <ExternalLink className="size-3" /> Open in new tab
638
- </button>
639
- </div>
640
- <iframe src={blobUrl} title={filePath} className="flex-1 w-full border-none" />
641
- </div>
642
- );
643
- }
@@ -0,0 +1,23 @@
1
+ import { Loader2, FileWarning } from "lucide-react";
2
+ import { useBlobUrl } from "./use-blob-url";
3
+
4
+ export function ImagePreview({ filePath, projectName }: { filePath: string; projectName: string }) {
5
+ const { blobUrl, error } = useBlobUrl(filePath, projectName);
6
+
7
+ if (error) {
8
+ return (
9
+ <div className="flex flex-col items-center justify-center h-full gap-3 text-text-secondary">
10
+ <FileWarning className="size-10 text-text-subtle" />
11
+ <p className="text-sm">Failed to load image.</p>
12
+ </div>
13
+ );
14
+ }
15
+ if (!blobUrl) {
16
+ return <div className="flex items-center justify-center h-full"><Loader2 className="size-5 animate-spin text-text-subtle" /></div>;
17
+ }
18
+ return (
19
+ <div className="flex items-center justify-center h-full p-4 bg-surface overflow-auto">
20
+ <img src={blobUrl} alt={filePath} className="max-w-full max-h-full object-contain" />
21
+ </div>
22
+ );
23
+ }
@@ -0,0 +1,32 @@
1
+ import { useCallback } from "react";
2
+ import { Loader2, FileWarning, ExternalLink } from "lucide-react";
3
+ import { useBlobUrl } from "./use-blob-url";
4
+
5
+ export function PdfPreview({ filePath, projectName }: { filePath: string; projectName: string }) {
6
+ const { blobUrl, error } = useBlobUrl(filePath, projectName, "application/pdf");
7
+
8
+ const openInNewTab = useCallback(() => { if (blobUrl) window.open(blobUrl, "_blank"); }, [blobUrl]);
9
+
10
+ if (error) {
11
+ return (
12
+ <div className="flex flex-col items-center justify-center h-full gap-3 text-text-secondary">
13
+ <FileWarning className="size-10 text-text-subtle" />
14
+ <p className="text-sm">Failed to load PDF.</p>
15
+ </div>
16
+ );
17
+ }
18
+ if (!blobUrl) {
19
+ return <div className="flex items-center justify-center h-full"><Loader2 className="size-5 animate-spin text-text-subtle" /></div>;
20
+ }
21
+ return (
22
+ <div className="flex flex-col h-full">
23
+ <div className="flex items-center justify-between px-3 py-1.5 border-b border-border bg-background shrink-0">
24
+ <span className="text-xs text-text-secondary truncate">{filePath}</span>
25
+ <button onClick={openInNewTab} className="flex items-center gap-1 text-xs text-text-secondary hover:text-text-primary transition-colors">
26
+ <ExternalLink className="size-3" /> Open in new tab
27
+ </button>
28
+ </div>
29
+ <iframe src={blobUrl} title={filePath} className="flex-1 w-full border-none" />
30
+ </div>
31
+ );
32
+ }