@hienlh/ppm 0.13.6 → 0.13.8

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 (84) hide show
  1. package/CHANGELOG.md +15 -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-CHgpQ_OP.js +1 -0
  5. package/dist/web/assets/{api-settings-t7Leca7J.js → api-settings-D0_eiIYv.js} +1 -1
  6. package/dist/web/assets/architecture-PBZL5I3N-WMbLpD5Y.js +1 -0
  7. package/dist/web/assets/{audio-preview-DEJiSXcO.js → audio-preview-DQbX8gfL.js} +1 -1
  8. package/dist/web/assets/{chat-tab-DQBl8oq4.js → chat-tab-BJQT9kie.js} +9 -9
  9. package/dist/web/assets/code-editor-CeKTvfyz.js +8 -0
  10. package/dist/web/assets/{conflict-editor-BZ4mdQJX.js → conflict-editor-BKwJLX0D.js} +1 -1
  11. package/dist/web/assets/{csv-preview-C9qGhDlb.js → csv-preview-D5lmgVEy.js} +1 -1
  12. package/dist/web/assets/database-viewer-DixWWvjx.js +5 -0
  13. package/dist/web/assets/diff-viewer-SAtaBwNI.js +4 -0
  14. package/dist/web/assets/{esm-B3je8j5P.js → esm-nXReYVnB.js} +1 -1
  15. package/dist/web/assets/{extension-webview-DEOG3sef.js → extension-webview-PiV4bKJ1.js} +1 -1
  16. package/dist/web/assets/{file-store-BgZggznw.js → file-store-BrbCNyLm.js} +1 -1
  17. package/dist/web/assets/gitGraph-HDMCJU4V-BdPTuzO3.js +1 -0
  18. package/dist/web/assets/{image-preview-YNpn3xj7.js → image-preview-CbFFD9BS.js} +1 -1
  19. package/dist/web/assets/{index-BJ76xcQz.css → index-C1RBJe0a.css} +1 -1
  20. package/dist/web/assets/index-ZFyltHwi.js +27 -0
  21. package/dist/web/assets/info-3K5VOQVL-MHX_1JfR.js +1 -0
  22. package/dist/web/assets/{input-bGJExpJZ.js → input-BMvRUOr7.js} +1 -1
  23. package/dist/web/assets/keybindings-store-D0C-Pq2o.js +1 -0
  24. package/dist/web/assets/{markdown-renderer-B7yiCjpQ.js → markdown-renderer-CHWA0KAo.js} +3 -3
  25. package/dist/web/assets/packet-RMMSAZCW-CreFbf9A.js +1 -0
  26. package/dist/web/assets/{pdf-preview-D3u3Hr2R.js → pdf-preview-DQMdjqa2.js} +1 -1
  27. package/dist/web/assets/pie-UPGHQEXC-CnaHXUh8.js +1 -0
  28. package/dist/web/assets/port-forwarding-tab-9BpNC9_7.js +1 -0
  29. package/dist/web/assets/{postgres-viewer-Cf4sjXxY.js → postgres-viewer-Bm5T51n6.js} +3 -3
  30. package/dist/web/assets/radar-KQ55EAFF-UxsdRHvt.js +1 -0
  31. package/dist/web/assets/{scroll-area-D0EQpAH2.js → scroll-area-BEllam7_.js} +1 -1
  32. package/dist/web/assets/{settings-store-CdcSAgEZ.js → settings-store-BHBb62gq.js} +2 -2
  33. package/dist/web/assets/settings-tab-BUstSDLR.js +1 -0
  34. package/dist/web/assets/sparkles-B0mRBy_j.js +1 -0
  35. package/dist/web/assets/{sql-query-editor-vpD0I0KG.js → sql-query-editor-CMQpaOjA.js} +1 -1
  36. package/dist/web/assets/{sqlite-viewer-WAA8mf_X.js → sqlite-viewer-C7rhO4bn.js} +1 -1
  37. package/dist/web/assets/tab-store-0rGchMXr.js +1 -0
  38. package/dist/web/assets/terminal-tab-Xtj6RN0d.js +1 -0
  39. package/dist/web/assets/treemap-KZPCXAKY-CBVPi4NV.js +1 -0
  40. package/dist/web/assets/{use-blob-url-BgxxT-n_.js → use-blob-url-Hn6n1730.js} +1 -1
  41. package/dist/web/assets/{use-monaco-theme-dtPsv6sh.js → use-monaco-theme-CP-vyTF8.js} +1 -1
  42. package/dist/web/assets/{vendor-mermaid-DCxaaPi4.js → vendor-mermaid-CMiurk2b.js} +2 -2
  43. package/dist/web/assets/{video-preview-CRDjNIVX.js → video-preview-BLI_RruT.js} +1 -1
  44. package/dist/web/index.html +14 -14
  45. package/dist/web/sw.js +1 -1
  46. package/package.json +1 -1
  47. package/src/server/ws/terminal.ts +4 -0
  48. package/src/services/terminal.service.ts +4 -1
  49. package/src/web/components/database/data-grid.tsx +117 -17
  50. package/src/web/components/editor/code-editor.tsx +36 -3
  51. package/src/web/components/editor/editor-mobile-toolbar.tsx +144 -0
  52. package/src/web/components/layout/draggable-tab.tsx +1 -0
  53. package/src/web/components/terminal/terminal-tab.tsx +19 -8
  54. package/src/web/hooks/use-terminal.ts +22 -2
  55. package/src/web/stores/panel-utils.ts +1 -0
  56. package/dist/web/assets/ai-settings-section-DeW4WN43.js +0 -1
  57. package/dist/web/assets/architecture-PBZL5I3N-Dy3PgD6O.js +0 -1
  58. package/dist/web/assets/code-editor-JxVGEgWY.js +0 -8
  59. package/dist/web/assets/database-viewer-CBfw4hkk.js +0 -2
  60. package/dist/web/assets/diff-viewer-4hobWs-B.js +0 -4
  61. package/dist/web/assets/gitGraph-HDMCJU4V-Bu1SIFFq.js +0 -1
  62. package/dist/web/assets/index-C7gvr4Xo.js +0 -27
  63. package/dist/web/assets/info-3K5VOQVL-DzfAxmVd.js +0 -1
  64. package/dist/web/assets/keybindings-store-BhvgfX51.js +0 -1
  65. package/dist/web/assets/packet-RMMSAZCW-DpzHf4xp.js +0 -1
  66. package/dist/web/assets/pie-UPGHQEXC-BpzFCKJ8.js +0 -1
  67. package/dist/web/assets/port-forwarding-tab-CSGpa5Bk.js +0 -1
  68. package/dist/web/assets/radar-KQ55EAFF-DAxWKxM4.js +0 -1
  69. package/dist/web/assets/settings-tab-BLlfwRRc.js +0 -1
  70. package/dist/web/assets/tab-store-Jvy1eZGM.js +0 -1
  71. package/dist/web/assets/terminal-tab-Bn8wLy9d.js +0 -1
  72. package/dist/web/assets/treemap-KZPCXAKY-D6dgXbAe.js +0 -1
  73. /package/dist/web/assets/{api-client-r4nyVy7H.js → api-client-Dvzcc_EO.js} +0 -0
  74. /package/dist/web/assets/{csv-parser-DxVplKKB.js → csv-parser-DO0dz4x_.js} +0 -0
  75. /package/dist/web/assets/{dist-BqoEabX7.js → dist-CGvx1c8C.js} +0 -0
  76. /package/dist/web/assets/{katex-bpagxk3Z.js → katex-BFE6i_OH.js} +0 -0
  77. /package/dist/web/assets/{lib-BqkcKGFq.js → lib-D_kRA9p6.js} +0 -0
  78. /package/dist/web/assets/{react-BkWDCPD7.js → react-GqWghJ-L.js} +0 -0
  79. /package/dist/web/assets/{sql-completion-provider-EzHOQLfo.js → sql-completion-provider-tCzZfqWs.js} +0 -0
  80. /package/dist/web/assets/{table-DbSviOmw.js → table-Dq575bPF.js} +0 -0
  81. /package/dist/web/assets/{text-wrap-DzvCTq_i.js → text-wrap-Cn6BNQfq.js} +0 -0
  82. /package/dist/web/assets/{trash-2-BgDIBl6f.js → trash-2-CJYoLw7Q.js} +0 -0
  83. /package/dist/web/assets/{utils-ChWX7pZv.js → utils-CTg5uAYR.js} +0 -0
  84. /package/dist/web/assets/{vendor-xterm-D7SePDJp.js → vendor-xterm-u3AZMvTx.js} +0 -0
@@ -1 +1 @@
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-C7gvr4Xo.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};
1
+ import{b as e}from"./vendor-markdown-0Mxgxy0L.js";import{t}from"./file-exclamation-point-Baz81y5z.js";import"./api-client-Dvzcc_EO.js";import{G as n}from"./index-ZFyltHwi.js";import{t as r}from"./use-blob-url-Hn6n1730.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,30 +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-C7gvr4Xo.js"></script>
42
+ <script type="module" crossorigin src="/assets/index-ZFyltHwi.js"></script>
43
43
  <link rel="modulepreload" crossorigin href="/assets/rolldown-runtime-FhOqtrmT.js">
44
- <link rel="modulepreload" crossorigin href="/assets/vendor-mermaid-DCxaaPi4.js">
44
+ <link rel="modulepreload" crossorigin href="/assets/vendor-mermaid-CMiurk2b.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-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-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">
50
+ <link rel="modulepreload" crossorigin href="/assets/input-BMvRUOr7.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-BHBb62gq.js">
54
+ <link rel="modulepreload" crossorigin href="/assets/scroll-area-BEllam7_.js">
55
55
  <link rel="modulepreload" crossorigin href="/assets/dist-D7KGU7Vl.js">
56
56
  <link rel="modulepreload" crossorigin href="/assets/plus-51UQ45rf.js">
57
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">
58
+ <link rel="modulepreload" crossorigin href="/assets/trash-2-CJYoLw7Q.js">
59
+ <link rel="modulepreload" crossorigin href="/assets/api-settings-D0_eiIYv.js">
60
+ <link rel="modulepreload" crossorigin href="/assets/ai-settings-section-CHgpQ_OP.js">
61
61
  <link rel="modulepreload" crossorigin href="/assets/chevron-right-BzAdxJRG.js">
62
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">
63
+ <link rel="modulepreload" crossorigin href="/assets/file-store-BrbCNyLm.js">
64
+ <link rel="modulepreload" crossorigin href="/assets/tab-store-0rGchMXr.js">
65
+ <link rel="stylesheet" crossorigin href="/assets/index-C1RBJe0a.css">
66
66
  <link rel="manifest" href="/manifest.webmanifest"><script id="vite-plugin-pwa:register-sw" src="/registerSW.js"></script></head>
67
67
  <body class="bg-[#0f1419] text-[#e5e7eb] font-sans antialiased">
68
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":"15aac7907981c1ae87e62d1dc7acc2df","url":"index.html"},{"revision":"a0fb34fc84eb148d51812cd62669f20d","url":"icon-512.svg"},{"revision":null,"url":"assets/lib-BqkcKGFq.js"},{"revision":null,"url":"assets/dist-D7KGU7Vl.js"},{"revision":null,"url":"assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2"},{"revision":null,"url":"assets/KaTeX_AMS-Regular-BQhdFMY1.woff2"},{"revision":null,"url":"assets/tab-store-Jvy1eZGM.js"},{"revision":null,"url":"assets/architecture-PBZL5I3N-Dy3PgD6O.js"},{"revision":null,"url":"assets/sql-query-editor-vpD0I0KG.js"},{"revision":null,"url":"assets/packet-RMMSAZCW-DpzHf4xp.js"},{"revision":null,"url":"assets/database-viewer-CBfw4hkk.js"},{"revision":null,"url":"assets/KaTeX_Main-Regular-B22Nviop.woff2"},{"revision":null,"url":"assets/settings-tab-BLlfwRRc.js"},{"revision":null,"url":"assets/scroll-area-D0EQpAH2.js"},{"revision":null,"url":"assets/vendor-xterm-D7SePDJp.js"},{"revision":null,"url":"assets/extension-webview-DEOG3sef.js"},{"revision":null,"url":"assets/markdown-renderer-B7yiCjpQ.js"},{"revision":null,"url":"assets/file-exclamation-point-Baz81y5z.js"},{"revision":null,"url":"assets/github.min-D2BCvnWf.css"},{"revision":null,"url":"assets/csv-preview-C9qGhDlb.js"},{"revision":null,"url":"assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2"},{"revision":null,"url":"assets/chevron-right-BzAdxJRG.js"},{"revision":null,"url":"assets/file-store-BgZggznw.js"},{"revision":null,"url":"assets/x-BtqbfkR7.js"},{"revision":null,"url":"assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2"},{"revision":null,"url":"assets/ai-settings-section-DeW4WN43.js"},{"revision":null,"url":"assets/pie-UPGHQEXC-BpzFCKJ8.js"},{"revision":null,"url":"assets/vendor-mermaid-DCxaaPi4.js"},{"revision":null,"url":"assets/vendor-markdown-0Mxgxy0L.js"},{"revision":null,"url":"assets/keybindings-store-BhvgfX51.js"},{"revision":null,"url":"assets/image-preview-YNpn3xj7.js"},{"revision":null,"url":"assets/KaTeX_Main-Italic-NWA7e6Wa.woff2"},{"revision":null,"url":"assets/code-editor-JxVGEgWY.js"},{"revision":null,"url":"assets/pdf-preview-D3u3Hr2R.js"},{"revision":null,"url":"assets/trash-2-BgDIBl6f.js"},{"revision":null,"url":"assets/use-blob-url-BgxxT-n_.js"},{"revision":null,"url":"assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2"},{"revision":null,"url":"assets/dist-BqoEabX7.js"},{"revision":null,"url":"assets/input-bGJExpJZ.js"},{"revision":null,"url":"assets/plus-51UQ45rf.js"},{"revision":null,"url":"assets/api-settings-t7Leca7J.js"},{"revision":null,"url":"assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2"},{"revision":null,"url":"assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2"},{"revision":null,"url":"assets/arrow-up-Dtrfv490.js"},{"revision":null,"url":"assets/refresh-cw-CSFrDtiu.js"},{"revision":null,"url":"assets/rolldown-runtime-FhOqtrmT.js"},{"revision":null,"url":"assets/radar-KQ55EAFF-DAxWKxM4.js"},{"revision":null,"url":"assets/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/terminal-tab-Bn8wLy9d.js"},{"revision":null,"url":"assets/sql-completion-provider-EzHOQLfo.js"},{"revision":null,"url":"assets/audio-preview-DEJiSXcO.js"},{"revision":null,"url":"assets/conflict-editor-BZ4mdQJX.js"},{"revision":null,"url":"assets/esm-B3je8j5P.js"},{"revision":null,"url":"assets/treemap-KZPCXAKY-D6dgXbAe.js"},{"revision":null,"url":"assets/port-forwarding-tab-CSGpa5Bk.js"},{"revision":null,"url":"assets/index-C7gvr4Xo.js"},{"revision":null,"url":"assets/KaTeX_Main-Bold-Cx986IdX.woff2"},{"revision":null,"url":"assets/video-preview-CRDjNIVX.js"},{"revision":null,"url":"assets/sqlite-viewer-WAA8mf_X.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/api-client-r4nyVy7H.js"},{"revision":null,"url":"assets/diff-viewer-4hobWs-B.js"},{"revision":null,"url":"assets/createLucideIcon-BjHrJDVb.js"},{"revision":null,"url":"assets/info-3K5VOQVL-DzfAxmVd.js"},{"revision":null,"url":"assets/settings-store-CdcSAgEZ.js"},{"revision":null,"url":"assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2"},{"revision":null,"url":"assets/code-CuravVys.js"},{"revision":null,"url":"assets/katex-bpagxk3Z.js"},{"revision":null,"url":"assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2"},{"revision":null,"url":"assets/vendor-ui-B-89Uj8i.js"},{"revision":null,"url":"assets/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/chat-tab-DQBl8oq4.js"},{"revision":null,"url":"assets/postgres-viewer-Cf4sjXxY.js"},{"revision":null,"url":"assets/vendor-xterm-BrP-ENHg.css"},{"revision":null,"url":"assets/use-monaco-theme-dtPsv6sh.js"},{"revision":null,"url":"assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2"},{"revision":null,"url":"assets/table-DbSviOmw.js"},{"revision":"d0f94ce046cf8cf09605ee7664dac557","url":"monacoeditorwork/html.worker.bundle.js"},{"revision":"a424156a79b9c1b907db93aa3180585a","url":"monacoeditorwork/editor.worker.bundle.js"},{"revision":"b3a7f967560c9816492a1567b3f7f0dc","url":"monacoeditorwork/css.worker.bundle.js"},{"revision":"a5d8a1acfc29c2a4c882a54ffc93def3","url":"monacoeditorwork/json.worker.bundle.js"},{"revision":"948e060affb598c339be40d69e1f6f9c","url":"monacoeditorwork/ts.worker.bundle.js"},{"revision":"79c8870653c8f419f2e3323085e1f4be","url":"manifest.webmanifest"}]),self.addEventListener(`push`,e=>{e.waitUntil(self.clients.matchAll({type:`window`,includeUncontrolled:!0}).then(t=>{if(t.some(e=>e.visibilityState===`visible`))return;let n=e.data?.json()??{title:`PPM`,body:`Chat completed`};return self.registration.showNotification(n.title,{body:n.body,icon:`/icon-192.png`,badge:`/icon-192.png`,tag:`ppm-chat-done`,silent:!1,data:{url:self.location.origin}})}))}),self.addEventListener(`notificationclick`,e=>{e.notification.close(),e.waitUntil(self.clients.matchAll({type:`window`,includeUncontrolled:!0}).then(t=>{for(let e of t)if(e.url.includes(self.location.origin)&&`focus`in e)return e.focus();return self.clients.openWindow(e.notification.data?.url||`/`)}))});
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":"0243ed2ed58acd13176b487d17e042d9","url":"index.html"},{"revision":"a0fb34fc84eb148d51812cd62669f20d","url":"icon-512.svg"},{"revision":null,"url":"assets/dist-D7KGU7Vl.js"},{"revision":null,"url":"assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2"},{"revision":null,"url":"assets/KaTeX_AMS-Regular-BQhdFMY1.woff2"},{"revision":null,"url":"assets/scroll-area-BEllam7_.js"},{"revision":null,"url":"assets/extension-webview-PiV4bKJ1.js"},{"revision":null,"url":"assets/gitGraph-HDMCJU4V-BdPTuzO3.js"},{"revision":null,"url":"assets/pie-UPGHQEXC-CnaHXUh8.js"},{"revision":null,"url":"assets/audio-preview-DQbX8gfL.js"},{"revision":null,"url":"assets/chat-tab-BJQT9kie.js"},{"revision":null,"url":"assets/ai-settings-section-CHgpQ_OP.js"},{"revision":null,"url":"assets/KaTeX_Main-Regular-B22Nviop.woff2"},{"revision":null,"url":"assets/code-editor-CeKTvfyz.js"},{"revision":null,"url":"assets/settings-store-BHBb62gq.js"},{"revision":null,"url":"assets/react-GqWghJ-L.js"},{"revision":null,"url":"assets/file-exclamation-point-Baz81y5z.js"},{"revision":null,"url":"assets/github.min-D2BCvnWf.css"},{"revision":null,"url":"assets/diff-viewer-SAtaBwNI.js"},{"revision":null,"url":"assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2"},{"revision":null,"url":"assets/chevron-right-BzAdxJRG.js"},{"revision":null,"url":"assets/info-3K5VOQVL-MHX_1JfR.js"},{"revision":null,"url":"assets/conflict-editor-BKwJLX0D.js"},{"revision":null,"url":"assets/lib-D_kRA9p6.js"},{"revision":null,"url":"assets/x-BtqbfkR7.js"},{"revision":null,"url":"assets/dist-CGvx1c8C.js"},{"revision":null,"url":"assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2"},{"revision":null,"url":"assets/postgres-viewer-Bm5T51n6.js"},{"revision":null,"url":"assets/api-settings-D0_eiIYv.js"},{"revision":null,"url":"assets/port-forwarding-tab-9BpNC9_7.js"},{"revision":null,"url":"assets/treemap-KZPCXAKY-CBVPi4NV.js"},{"revision":null,"url":"assets/csv-preview-D5lmgVEy.js"},{"revision":null,"url":"assets/vendor-markdown-0Mxgxy0L.js"},{"revision":null,"url":"assets/index-C1RBJe0a.css"},{"revision":null,"url":"assets/radar-KQ55EAFF-UxsdRHvt.js"},{"revision":null,"url":"assets/api-client-Dvzcc_EO.js"},{"revision":null,"url":"assets/architecture-PBZL5I3N-WMbLpD5Y.js"},{"revision":null,"url":"assets/KaTeX_Main-Italic-NWA7e6Wa.woff2"},{"revision":null,"url":"assets/table-Dq575bPF.js"},{"revision":null,"url":"assets/tab-store-0rGchMXr.js"},{"revision":null,"url":"assets/settings-tab-BUstSDLR.js"},{"revision":null,"url":"assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2"},{"revision":null,"url":"assets/database-viewer-DixWWvjx.js"},{"revision":null,"url":"assets/pdf-preview-DQMdjqa2.js"},{"revision":null,"url":"assets/plus-51UQ45rf.js"},{"revision":null,"url":"assets/sql-query-editor-CMQpaOjA.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/markdown-renderer-CHWA0KAo.js"},{"revision":null,"url":"assets/sqlite-viewer-C7rhO4bn.js"},{"revision":null,"url":"assets/input-BMvRUOr7.js"},{"revision":null,"url":"assets/refresh-cw-CSFrDtiu.js"},{"revision":null,"url":"assets/rolldown-runtime-FhOqtrmT.js"},{"revision":null,"url":"assets/esm-nXReYVnB.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-BFE6i_OH.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/keybindings-store-D0C-Pq2o.js"},{"revision":null,"url":"assets/use-monaco-theme-CP-vyTF8.js"},{"revision":null,"url":"assets/file-store-BrbCNyLm.js"},{"revision":null,"url":"assets/use-blob-url-Hn6n1730.js"},{"revision":null,"url":"assets/utils-CTg5uAYR.js"},{"revision":null,"url":"assets/KaTeX_Main-Bold-Cx986IdX.woff2"},{"revision":null,"url":"assets/database-DCT0OjgQ.js"},{"revision":null,"url":"assets/vendor-mermaid-CMiurk2b.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/csv-parser-DO0dz4x_.js"},{"revision":null,"url":"assets/createLucideIcon-BjHrJDVb.js"},{"revision":null,"url":"assets/terminal-tab-Xtj6RN0d.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/sparkles-B0mRBy_j.js"},{"revision":null,"url":"assets/vendor-ui-B-89Uj8i.js"},{"revision":null,"url":"assets/index-ZFyltHwi.js"},{"revision":null,"url":"assets/vendor-xterm-u3AZMvTx.js"},{"revision":null,"url":"assets/packet-RMMSAZCW-CreFbf9A.js"},{"revision":null,"url":"assets/sql-completion-provider-tCzZfqWs.js"},{"revision":null,"url":"assets/image-preview-CbFFD9BS.js"},{"revision":null,"url":"assets/vendor-xterm-BrP-ENHg.css"},{"revision":null,"url":"assets/video-preview-BLI_RruT.js"},{"revision":null,"url":"assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2"},{"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.13.6",
3
+ "version": "0.13.8",
4
4
  "description": "Personal Project Manager — mobile-first web IDE with AI assistance",
5
5
  "author": "hienlh",
6
6
  "license": "MIT",
@@ -83,6 +83,10 @@ export const terminalWebSocket = {
83
83
 
84
84
  close(ws: { data: { type: string; id: string } }) {
85
85
  const sessionId = ws.data.id;
86
+ const session = terminalService.get(sessionId);
87
+ // Only clean up if this WS is still the active connection.
88
+ // Prevents race: stale WS close fires after new WS open, removing the new listener.
89
+ if (!session || session.ws !== ws) return;
86
90
  terminalService.removeOutputListener(sessionId);
87
91
  terminalService.setDisconnected(sessionId);
88
92
  },
@@ -144,7 +144,10 @@ class TerminalService {
144
144
  };
145
145
  const onExit = () => {
146
146
  const listener = this.outputListeners.get(id);
147
- if (listener) listener(id, "\r\n[Process exited]\r\n");
147
+ if (listener) {
148
+ listener(id, "\r\n[Process exited]\r\n");
149
+ listener(id, JSON.stringify({ type: "exited" }));
150
+ }
148
151
  };
149
152
 
150
153
  const pty = isWindows
@@ -1,7 +1,9 @@
1
1
  import { useState, useCallback, useMemo, useRef, memo, useEffect } from "react";
2
- import { Loader2, ChevronLeft, ChevronRight, ChevronUp, ChevronDown, Trash2, Plus, Search, X, Eye, Filter, Pin, PinOff, Columns3 } from "lucide-react";
2
+ import Editor from "@monaco-editor/react";
3
+ import { Loader2, ChevronLeft, ChevronRight, ChevronUp, ChevronDown, Trash2, Plus, Search, X, Eye, ExternalLink, WrapText, Sparkles, Filter, Pin, PinOff, Columns3 } from "lucide-react";
3
4
  import { useShallow } from "zustand/react/shallow";
4
5
  import { useTabStore } from "@/stores/tab-store";
6
+ import { useMonacoTheme } from "@/lib/use-monaco-theme";
5
7
  import type { DbColumnInfo } from "./use-database";
6
8
  import { ExportButton } from "./export-button";
7
9
 
@@ -44,15 +46,14 @@ export function DataGrid({
44
46
  const [insertError, setInsertError] = useState<string | null>(null);
45
47
  const [confirmBulkDelete, setConfirmBulkDelete] = useState(false);
46
48
  const { openTab } = useTabStore(useShallow((s) => ({ openTab: s.openTab })));
47
- const openCellViewer = useCallback((cell: { col: string; value: string }) => {
48
- openTab({
49
- type: "editor",
50
- title: cell.col,
51
- projectId: null,
52
- closable: true,
53
- metadata: { inlineContent: cell.value, inlineLanguage: detectLang(cell.value) },
54
- });
55
- }, [openTab]);
49
+ const [previewData, setPreviewData] = useState<{ title: string; content: string; language: string; viewerKey: string } | null>(null);
50
+ const openCellViewer = useCallback((cell: { col: string; value: string; pkVal: string }) => {
51
+ const table = selectedTable ?? "";
52
+ const lang = detectLang(cell.value);
53
+ const key = `${connectionId ?? "local"}:${table}:${cell.col}:${cell.pkVal}`;
54
+ const title = table ? `${cell.col} #${cell.pkVal} — ${table}` : `${cell.col} #${cell.pkVal}`;
55
+ setPreviewData({ title, content: cell.value, language: lang, viewerKey: key });
56
+ }, [connectionId, selectedTable]);
56
57
  const [pinnedCols, setPinnedCols] = useState<Set<string>>(new Set());
57
58
  const [pinnedRows, setPinnedRows] = useState<Set<number>>(new Set());
58
59
  const [filterOpenCol, setFilterOpenCol] = useState<string | null>(null);
@@ -69,14 +70,21 @@ export function DataGrid({
69
70
  const openRowViewer = useCallback((row: Record<string, unknown>) => {
70
71
  const json = JSON.stringify(row, null, 2);
71
72
  const pk = pkCol ? String(row[pkCol] ?? "") : "";
73
+ const table = selectedTable ?? "";
74
+ const key = `${connectionId ?? "local"}:${table}:row:${pk || "unknown"}`;
75
+ const title = pk ? `Row #${pk}${table ? ` — ${table}` : ""}` : `Row${table ? ` — ${table}` : ""}`;
76
+ setPreviewData({ title, content: json, language: "json", viewerKey: key });
77
+ }, [pkCol, connectionId, selectedTable]);
78
+ const openPreviewInTab = useCallback(() => {
79
+ if (!previewData) return;
72
80
  openTab({
73
81
  type: "editor",
74
- title: pk ? `Row ${pk}` : "Row",
82
+ title: previewData.title,
75
83
  projectId: null,
76
84
  closable: true,
77
- metadata: { inlineContent: json, inlineLanguage: "json" },
85
+ metadata: { inlineContent: previewData.content, inlineLanguage: previewData.language, viewerKey: previewData.viewerKey },
78
86
  });
79
- }, [openTab, pkCol]);
87
+ }, [openTab, previewData]);
80
88
 
81
89
  // Refs for cell renderers — avoid column memo rebuild on every state change
82
90
  const editingRef = useRef(editingCell);
@@ -196,7 +204,7 @@ export function DataGrid({
196
204
  if (!el) return;
197
205
  const handler = (e: KeyboardEvent) => {
198
206
  // Escape closes column search from anywhere
199
- if (e.key === "Escape") { setColSearchOpen(false); return; }
207
+ if (e.key === "Escape") { setColSearchOpen(false); setPreviewData(null); return; }
200
208
 
201
209
  // Skip if focus is in an input/textarea
202
210
  const tag = (e.target as HTMLElement)?.tagName;
@@ -433,7 +441,7 @@ export function DataGrid({
433
441
  )}
434
442
 
435
443
  {/* Table */}
436
- <div ref={scrollRef} className="flex-1 overflow-auto relative">
444
+ <div ref={scrollRef} className="flex-1 overflow-auto relative min-h-0">
437
445
  {loading && (
438
446
  <div className="absolute inset-0 z-30 flex items-center justify-center bg-background/60">
439
447
  <Loader2 className="size-5 animate-spin text-primary" />
@@ -540,6 +548,11 @@ export function DataGrid({
540
548
  </table>
541
549
  </div>
542
550
 
551
+ {/* Inline preview panel */}
552
+ {previewData && (
553
+ <DataPreviewPanel data={previewData} onClose={() => setPreviewData(null)} onOpenInTab={openPreviewInTab} />
554
+ )}
555
+
543
556
  {/* Footer: row count + pagination */}
544
557
  <div className="flex items-center justify-between px-3 py-1.5 border-t border-border bg-background shrink-0 text-xs text-muted-foreground">
545
558
  <span>{tableData.total.toLocaleString()} rows</span>
@@ -596,6 +609,93 @@ function detectLang(text: string): string {
596
609
  return "plaintext";
597
610
  }
598
611
 
612
+ /** Inline preview panel for cell/row content with Monaco editor */
613
+ function DataPreviewPanel({ data, onClose, onOpenInTab }: {
614
+ data: { title: string; content: string; language: string };
615
+ onClose: () => void;
616
+ onOpenInTab: () => void;
617
+ }) {
618
+ const monacoTheme = useMonacoTheme();
619
+ const [wordWrap, setWordWrap] = useState(true);
620
+ const [displayContent, setDisplayContent] = useState(data.content);
621
+ const [beautified, setBeautified] = useState(false);
622
+ const canBeautify = data.language === "json" || data.language === "xml";
623
+
624
+ // Reset state when data changes
625
+ const prevKey = useRef(data.title);
626
+ if (prevKey.current !== data.title) {
627
+ prevKey.current = data.title;
628
+ setDisplayContent(data.content);
629
+ setBeautified(false);
630
+ }
631
+
632
+ const toggleBeautify = useCallback(() => {
633
+ if (beautified) {
634
+ setDisplayContent(data.content);
635
+ setBeautified(false);
636
+ } else {
637
+ if (data.language === "json") {
638
+ try { setDisplayContent(JSON.stringify(JSON.parse(data.content.trim()), null, 2)); setBeautified(true); } catch { /* invalid */ }
639
+ } else if (data.language === "xml") {
640
+ // Simple XML indent
641
+ let depth = 0;
642
+ const formatted = data.content.trim().replace(/>\s*</g, ">\n<").split("\n").map((line) => {
643
+ const trimmed = line.trim();
644
+ if (trimmed.startsWith("</")) depth = Math.max(0, depth - 1);
645
+ const indented = " ".repeat(depth) + trimmed;
646
+ if (trimmed.startsWith("<") && !trimmed.startsWith("</") && !trimmed.endsWith("/>") && !trimmed.startsWith("<?")) depth++;
647
+ return indented;
648
+ }).join("\n");
649
+ setDisplayContent(formatted);
650
+ setBeautified(true);
651
+ }
652
+ }
653
+ }, [beautified, data.content, data.language]);
654
+
655
+ return (
656
+ <div className="shrink-0 border-t border-border flex flex-col" style={{ height: "40%" }}>
657
+ <div className="flex items-center gap-1 px-2 py-1 bg-muted/50 border-b border-border shrink-0">
658
+ <Eye className="size-3 text-muted-foreground" />
659
+ <span className="text-xs font-medium text-foreground truncate flex-1">{data.title}</span>
660
+ {canBeautify && (
661
+ <button type="button" onClick={toggleBeautify} title={beautified ? "Raw" : "Beautify"}
662
+ className={`p-0.5 rounded transition-colors ${beautified ? "text-primary" : "text-muted-foreground hover:text-foreground"}`}>
663
+ <Sparkles className="size-3" />
664
+ </button>
665
+ )}
666
+ <button type="button" onClick={() => setWordWrap(!wordWrap)} title={wordWrap ? "No wrap" : "Word wrap"}
667
+ className={`p-0.5 rounded transition-colors ${wordWrap ? "text-primary" : "text-muted-foreground hover:text-foreground"}`}>
668
+ <WrapText className="size-3" />
669
+ </button>
670
+ <button type="button" onClick={onOpenInTab} title="Open in new tab"
671
+ className="flex items-center gap-1 px-1.5 py-0.5 rounded text-[10px] text-muted-foreground hover:text-foreground hover:bg-muted transition-colors">
672
+ <ExternalLink className="size-3" />
673
+ <span className="hidden sm:inline">Open in Tab</span>
674
+ </button>
675
+ <button type="button" onClick={onClose} title="Close preview (Esc)"
676
+ className="p-0.5 rounded text-muted-foreground hover:text-foreground transition-colors">
677
+ <X className="size-3" />
678
+ </button>
679
+ </div>
680
+ <div className="flex-1 min-h-0">
681
+ <Editor
682
+ height="100%"
683
+ language={data.language === "plaintext" ? undefined : data.language}
684
+ value={displayContent}
685
+ theme={monacoTheme}
686
+ options={{
687
+ readOnly: true, minimap: { enabled: false }, scrollBeyondLastLine: false,
688
+ wordWrap: wordWrap ? "on" : "off", lineNumbers: "on", fontSize: 12,
689
+ folding: true, bracketPairColorization: { enabled: true },
690
+ domReadOnly: true, contextmenu: false, overviewRulerLanes: 0,
691
+ }}
692
+ loading={<Loader2 className="size-4 animate-spin text-muted-foreground" />}
693
+ />
694
+ </div>
695
+ </div>
696
+ );
697
+ }
698
+
599
699
  /** Column search dropdown — owns query/index state internally to avoid re-rendering DataGrid */
600
700
  function ColumnSearchDropdown({ columns, onSelect, onClose }: {
601
701
  columns: string[]; onSelect: (col: string) => void; onClose: () => void;
@@ -651,7 +751,7 @@ const DataRow = memo(function DataRow({ row, rowIdx, columns, selected, onToggle
651
751
  onSetEditValue: (v: string) => void;
652
752
  showDelete: boolean; confirmingDelete: boolean;
653
753
  onDelete: (i: number) => void; onConfirmDelete: (i: number | null) => void;
654
- onViewCell: (cell: { col: string; value: string }) => void;
754
+ onViewCell: (cell: { col: string; value: string; pkVal: string }) => void;
655
755
  onViewRow: (row: Record<string, unknown>) => void;
656
756
  pinned: boolean; onTogglePin: (i: number) => void;
657
757
  pinnedCols: Set<string>; pinnedColOffsets: Map<string, number>;
@@ -702,7 +802,7 @@ const DataRow = memo(function DataRow({ row, rowIdx, columns, selected, onToggle
702
802
  </span>
703
803
  {showEye && (
704
804
  <button type="button" title="View full content"
705
- onClick={() => onViewCell({ col, value: formatCellValue(val) })}
805
+ onClick={() => onViewCell({ col, value: formatCellValue(val), pkVal: pkCol ? String(row[pkCol] ?? rowIdx) : String(rowIdx) })}
706
806
  className="shrink-0 p-0.5 rounded text-muted-foreground/50 hover:text-foreground transition-colors">
707
807
  <Eye className="size-3" />
708
808
  </button>
@@ -12,6 +12,7 @@ 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";
15
+ import { EditorMobileToolbar } from "./editor-mobile-toolbar";
15
16
  import { createSqlCompletionProvider, clearCompletionCache, type SchemaInfo } from "../database/sql-completion-provider";
16
17
  import { useConnections, type Connection } from "../database/use-connections";
17
18
 
@@ -192,6 +193,31 @@ export const CodeEditor = memo(function CodeEditor({ metadata, tabId }: CodeEdit
192
193
  runSqlInViewer(sqlText);
193
194
  }, [selectedSqlConn, runSqlInViewer]);
194
195
 
196
+ // Touch device detection for mobile toolbar
197
+ const isMobile = typeof window !== "undefined" && "ontouchstart" in window;
198
+
199
+ // Track visual viewport so toolbar stays above mobile keyboard
200
+ const containerRef = useRef<HTMLDivElement | null>(null);
201
+ const [mobileHeight, setMobileHeight] = useState<number | null>(null);
202
+ useEffect(() => {
203
+ if (!isMobile) return;
204
+ const vv = window.visualViewport;
205
+ if (!vv) return;
206
+ const handle = () => {
207
+ const el = containerRef.current;
208
+ if (!el) return;
209
+ // Calculate available height = viewport height - element's top offset from viewport
210
+ const top = el.getBoundingClientRect().top;
211
+ setMobileHeight(vv.height - Math.max(0, top));
212
+ };
213
+ vv.addEventListener("resize", handle);
214
+ vv.addEventListener("scroll", handle);
215
+ return () => {
216
+ vv.removeEventListener("resize", handle);
217
+ vv.removeEventListener("scroll", handle);
218
+ };
219
+ }, [isMobile]);
220
+
195
221
  // CodeLens: inline Run buttons between SQL statements
196
222
  const codeLensDisposable = useRef<MonacoType.IDisposable[]>([]);
197
223
  const runSqlRef = useRef(runSqlInViewer);
@@ -273,9 +299,9 @@ export const CodeEditor = memo(function CodeEditor({ metadata, tabId }: CodeEdit
273
299
  return () => window.removeEventListener("file:changed", handler);
274
300
  }, [filePath, projectName, isExternalFile, inlineContent, isUntitled]);
275
301
 
276
- // Update tab title unsaved indicator
302
+ // Update tab title unsaved indicator (skip for inline content — title set by caller)
277
303
  useEffect(() => {
278
- if (!ownTab) return;
304
+ if (!ownTab || inlineContent != null) return;
279
305
  const baseName = isUntitled
280
306
  ? `Untitled-${metadata?.untitledNumber ?? 1}`
281
307
  : (filePath ? basename(filePath) : "Untitled");
@@ -502,7 +528,11 @@ export const CodeEditor = memo(function CodeEditor({ metadata, tabId }: CodeEdit
502
528
  ) : null;
503
529
 
504
530
  return (
505
- <div className="flex flex-col h-full w-full overflow-hidden">
531
+ <div
532
+ ref={containerRef}
533
+ className="flex flex-col h-full w-full overflow-hidden"
534
+ style={mobileHeight ? { height: `${mobileHeight}px`, maxHeight: `${mobileHeight}px` } : undefined}
535
+ >
506
536
  {/* Inline content toolbar (cell viewer mode) */}
507
537
  {inlineContent != null && canBeautifyInline && (
508
538
  <div className="flex items-center h-7 border-b border-border bg-background shrink-0 px-2 gap-2">
@@ -578,6 +608,9 @@ export const CodeEditor = memo(function CodeEditor({ metadata, tabId }: CodeEdit
578
608
  </div>
579
609
  )}
580
610
 
611
+ {/* Mobile toolbar — bottom, like terminal */}
612
+ {isMobile && <EditorMobileToolbar editorRef={editorRef} readOnly={inlineContent != null} />}
613
+
581
614
  {/* Save As dialog for untitled tabs */}
582
615
  {showSaveAs && (
583
616
  <SaveAsDialog
@@ -0,0 +1,144 @@
1
+ import { useCallback, useRef, useState } from "react";
2
+ import { ClipboardPaste, Undo2, Redo2, X } from "lucide-react";
3
+ import type * as MonacoType from "monaco-editor";
4
+
5
+ /** Clipboard API requires secure context (HTTPS / localhost) */
6
+ const isSecureContext = typeof window !== "undefined" && window.isSecureContext;
7
+
8
+ /** Symbols commonly needed when coding on mobile — ordered by frequency */
9
+ const SYMBOL_KEYS = [
10
+ "(", ")", "{", "}", "[", "]",
11
+ "<", ">", ";", ":", "=",
12
+ '"', "'", "`", "/", "\\", "_", "#",
13
+ ];
14
+
15
+ const btnBase =
16
+ "px-2 py-1.5 rounded text-xs min-w-[36px] min-h-[32px] bg-surface-elevated text-text-primary active:bg-primary active:text-primary-foreground transition-colors select-none";
17
+ const btnSymbol =
18
+ "px-3 py-1.5 rounded text-xs font-mono min-w-[36px] min-h-[32px] bg-surface-elevated text-text-primary active:bg-primary active:text-primary-foreground transition-colors select-none";
19
+ const divider = "w-px h-5 bg-border mx-0.5 shrink-0";
20
+
21
+ interface EditorMobileToolbarProps {
22
+ editorRef: React.RefObject<MonacoType.editor.IStandaloneCodeEditor | null>;
23
+ readOnly?: boolean;
24
+ }
25
+
26
+ export function EditorMobileToolbar({ editorRef, readOnly }: EditorMobileToolbarProps) {
27
+ const getEditor = useCallback(() => editorRef.current, [editorRef]);
28
+
29
+ /** Insert text at cursor position in Monaco */
30
+ const insertText = useCallback((text: string) => {
31
+ const editor = getEditor();
32
+ if (!editor) return;
33
+ editor.focus();
34
+ const selection = editor.getSelection();
35
+ if (selection) {
36
+ editor.executeEdits("mobile-toolbar", [{ range: selection, text }]);
37
+ }
38
+ }, [getEditor]);
39
+
40
+ // --- Paste: two strategies based on secure context ---
41
+ const [pasteMode, setPasteMode] = useState(false);
42
+ const pasteRef = useRef<HTMLTextAreaElement | null>(null);
43
+
44
+ // HTTPS: use Clipboard API directly (single tap)
45
+ const handleClipboardPaste = useCallback(async () => {
46
+ try {
47
+ const text = await navigator.clipboard.readText();
48
+ if (text) insertText(text);
49
+ } catch { /* permission denied */ }
50
+ }, [insertText]);
51
+
52
+ // HTTP fallback: show textarea for native long-press paste
53
+ const openPasteMode = useCallback(() => {
54
+ setPasteMode(true);
55
+ requestAnimationFrame(() => pasteRef.current?.focus());
56
+ }, []);
57
+
58
+ const handleNativePaste = useCallback((e: React.ClipboardEvent<HTMLTextAreaElement>) => {
59
+ e.preventDefault();
60
+ const text = e.clipboardData.getData("text/plain");
61
+ if (!text) return;
62
+ setPasteMode(false);
63
+ insertText(text);
64
+ }, [insertText]);
65
+
66
+ const handleUndo = useCallback(() => {
67
+ const editor = getEditor();
68
+ if (!editor) return;
69
+ editor.focus();
70
+ editor.trigger("mobile-toolbar", "undo", null);
71
+ }, [getEditor]);
72
+
73
+ const handleRedo = useCallback(() => {
74
+ const editor = getEditor();
75
+ if (!editor) return;
76
+ editor.focus();
77
+ editor.trigger("mobile-toolbar", "redo", null);
78
+ }, [getEditor]);
79
+
80
+ const handleTab = useCallback(() => {
81
+ const editor = getEditor();
82
+ if (!editor) return;
83
+ editor.focus();
84
+ editor.trigger("mobile-toolbar", "tab", null);
85
+ }, [getEditor]);
86
+
87
+ if (readOnly) return null;
88
+
89
+ return (
90
+ <div className="shrink-0 border-t border-border bg-surface">
91
+ {/* HTTP-only: textarea for native paste via long-press */}
92
+ {!isSecureContext && pasteMode && (
93
+ <div className="flex items-center gap-2 px-2 py-1.5 border-b border-border bg-muted/50">
94
+ <textarea
95
+ ref={pasteRef}
96
+ onPaste={handleNativePaste}
97
+ placeholder="Long-press here → Paste"
98
+ className="flex-1 h-8 rounded border border-border bg-background text-foreground text-xs px-2 py-1.5 resize-none focus:outline-none focus:ring-1 focus:ring-primary"
99
+ />
100
+ <button
101
+ type="button"
102
+ onClick={() => setPasteMode(false)}
103
+ className="p-1.5 rounded text-muted-foreground active:bg-muted transition-colors"
104
+ >
105
+ <X size={14} />
106
+ </button>
107
+ </div>
108
+ )}
109
+
110
+ {/* Toolbar buttons */}
111
+ <div className="flex items-center gap-1 px-2 py-1.5 overflow-x-auto">
112
+ {/* Paste: Clipboard API on HTTPS, textarea fallback on HTTP */}
113
+ <button
114
+ type="button"
115
+ onClick={isSecureContext ? handleClipboardPaste : openPasteMode}
116
+ className={btnBase}
117
+ title="Paste"
118
+ >
119
+ <ClipboardPaste size={14} />
120
+ </button>
121
+ <button type="button" onClick={handleUndo} className={btnBase} title="Undo">
122
+ <Undo2 size={14} />
123
+ </button>
124
+ <button type="button" onClick={handleRedo} className={btnBase} title="Redo">
125
+ <Redo2 size={14} />
126
+ </button>
127
+
128
+ <div className={divider} />
129
+
130
+ <button type="button" onClick={handleTab} className={btnSymbol}>
131
+ Tab
132
+ </button>
133
+
134
+ <div className={divider} />
135
+
136
+ {SYMBOL_KEYS.map((key) => (
137
+ <button key={key} type="button" onClick={() => insertText(key)} className={btnSymbol}>
138
+ {key}
139
+ </button>
140
+ ))}
141
+ </div>
142
+ </div>
143
+ );
144
+ }
@@ -136,6 +136,7 @@ export function DraggableTab({
136
136
  ) : (
137
137
  <span
138
138
  className="max-w-[120px] truncate"
139
+ title={tab.title}
139
140
  onDoubleClick={(e) => {
140
141
  if (onRename) { e.stopPropagation(); setEditing(true); }
141
142
  }}