@hienlh/ppm 0.13.61 → 0.13.63

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 (58) hide show
  1. package/CHANGELOG.md +11 -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/audio-preview-D8onaG_2.js +1 -0
  5. package/dist/web/assets/{chat-tab-6xXNp6td.js → chat-tab-D1n2fmne.js} +7 -7
  6. package/dist/web/assets/code-editor-D3ofpe9P.js +8 -0
  7. package/dist/web/assets/{conflict-editor-pC5U_tzr.js → conflict-editor-BaSyowCW.js} +3 -3
  8. package/dist/web/assets/database-viewer-IDG5i3mc.js +1 -0
  9. package/dist/web/assets/diff-viewer-D5SnxW8Y.js +4 -0
  10. package/dist/web/assets/{extension-webview-CVylZpkv.js → extension-webview-BUzklJiG.js} +1 -1
  11. package/dist/web/assets/git-log-panel-CkfQFd6J.js +1 -0
  12. package/dist/web/assets/{glide-data-grid-PZOzxnWV.js → glide-data-grid-BJ3Wn9to.js} +3 -3
  13. package/dist/web/assets/image-preview-CJ8Qqjgt.js +1 -0
  14. package/dist/web/assets/index-Di7q5I27.js +27 -0
  15. package/dist/web/assets/keybindings-store-D9NMYHYK.js +1 -0
  16. package/dist/web/assets/{markdown-renderer-BPuu5Czn.js → markdown-renderer-YnP2OcPh.js} +1 -1
  17. package/dist/web/assets/notification-store-CW0Alosv.js +1 -0
  18. package/dist/web/assets/pdf-preview-D3Y2rNVC.js +1 -0
  19. package/dist/web/assets/port-forwarding-tab-DvoM_j8q.js +1 -0
  20. package/dist/web/assets/{postgres-viewer-D72BwBOg.js → postgres-viewer-BN_vFAqs.js} +2 -2
  21. package/dist/web/assets/{settings-tab-BTT3CCTS.js → settings-tab-B52JczcO.js} +1 -1
  22. package/dist/web/assets/sql-query-editor-XkJZFBl5.js +3 -0
  23. package/dist/web/assets/sqlite-viewer-DR2scYmN.js +1 -0
  24. package/dist/web/assets/system-monitor-tab-QXA7p0VH.js +1 -0
  25. package/dist/web/assets/{terminal-tab-BHFM5N0l.js → terminal-tab-BI9y5tDL.js} +2 -2
  26. package/dist/web/assets/video-preview-EhN6q3Xv.js +1 -0
  27. package/dist/web/index.html +1 -1
  28. package/dist/web/sw.js +1 -1
  29. package/docs/codebase-summary.md +2 -1
  30. package/docs/project-changelog.md +10 -1
  31. package/package.json +1 -1
  32. package/src/index.ts +0 -0
  33. package/src/web/components/database/database-viewer.tsx +13 -2
  34. package/src/web/components/database/sql-query-editor.tsx +8 -10
  35. package/src/web/components/explorer/file-tree.tsx +5 -0
  36. package/src/web/components/explorer/tree-node-context-menu.tsx +3 -0
  37. package/src/web/components/layout/command-palette-filter-chips.tsx +61 -0
  38. package/src/web/components/layout/command-palette.tsx +54 -7
  39. package/src/web/components/layout/draggable-tab.tsx +3 -0
  40. package/src/web/components/layout/mobile-nav.tsx +11 -0
  41. package/src/web/components/layout/tab-bar.tsx +9 -0
  42. package/bun.lock +0 -2129
  43. package/bunfig.toml +0 -2
  44. package/dist/web/assets/audio-preview-CKyqt8Qa.js +0 -1
  45. package/dist/web/assets/code-editor-DWNVyji6.js +0 -8
  46. package/dist/web/assets/database-viewer-Qp7wc8sJ.js +0 -1
  47. package/dist/web/assets/diff-viewer-FyAyKn4J.js +0 -4
  48. package/dist/web/assets/git-log-panel-BA4xxiZR.js +0 -1
  49. package/dist/web/assets/image-preview-meDqzT6e.js +0 -1
  50. package/dist/web/assets/index-Drwzii_w.js +0 -27
  51. package/dist/web/assets/keybindings-store-D6R_grEQ.js +0 -1
  52. package/dist/web/assets/notification-store-eSbjsPHn.js +0 -1
  53. package/dist/web/assets/pdf-preview-CE6iGnyw.js +0 -1
  54. package/dist/web/assets/port-forwarding-tab-Bm2K9NO-.js +0 -1
  55. package/dist/web/assets/sql-query-editor-BiVMmcK3.js +0 -3
  56. package/dist/web/assets/sqlite-viewer-CKdZF5AA.js +0 -1
  57. package/dist/web/assets/system-monitor-tab-PIhMDYPP.js +0 -1
  58. package/dist/web/assets/video-preview-DC5PWgPo.js +0 -1
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":"21fe89139d44830c19da7e565175c361","url":"index.html"},{"revision":"a0fb34fc84eb148d51812cd62669f20d","url":"icon-512.svg"},{"revision":null,"url":"assets/react-CfveccaI.js"},{"revision":null,"url":"assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2"},{"revision":null,"url":"assets/api-settings-uQKmeGkl.js"},{"revision":null,"url":"assets/KaTeX_AMS-Regular-BQhdFMY1.woff2"},{"revision":null,"url":"assets/project-store-BnvrVKBw.js"},{"revision":null,"url":"assets/table-2wDtM4_B.js"},{"revision":null,"url":"assets/packet-RMMSAZCW-Ar00Wbhd.js"},{"revision":null,"url":"assets/arrow-down-D825m4vm.js"},{"revision":null,"url":"assets/use-blob-url-DCUIEzjB.js"},{"revision":null,"url":"assets/KaTeX_Main-Regular-B22Nviop.woff2"},{"revision":null,"url":"assets/chevron-down-BMo4cBth.js"},{"revision":null,"url":"assets/dist-DeY41KFi.js"},{"revision":null,"url":"assets/code-editor-DWNVyji6.js"},{"revision":null,"url":"assets/pie-UPGHQEXC-Q4ssDdib.js"},{"revision":null,"url":"assets/github.min-D2BCvnWf.css"},{"revision":null,"url":"assets/index-Drwzii_w.js"},{"revision":null,"url":"assets/pdf-preview-CE6iGnyw.js"},{"revision":null,"url":"assets/radar-KQ55EAFF-kq5v4OKX.js"},{"revision":null,"url":"assets/extension-webview-CVylZpkv.js"},{"revision":null,"url":"assets/settings-store-CSDOihqv.js"},{"revision":null,"url":"assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2"},{"revision":null,"url":"assets/info-3K5VOQVL-CWKw4e0V.js"},{"revision":null,"url":"assets/glide-data-grid-nthEL3fk.css"},{"revision":null,"url":"assets/esm-JPvheKDJ.js"},{"revision":null,"url":"assets/lib-DrypSCq8.js"},{"revision":null,"url":"assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2"},{"revision":null,"url":"assets/panel-store-B1pOXkyS.js"},{"revision":null,"url":"assets/vendor-markdown-0Mxgxy0L.js"},{"revision":null,"url":"assets/wifi-LJEyIdXf.js"},{"revision":null,"url":"assets/system-monitor-tab-PIhMDYPP.js"},{"revision":null,"url":"assets/notification-store-eSbjsPHn.js"},{"revision":null,"url":"assets/KaTeX_Main-Italic-NWA7e6Wa.woff2"},{"revision":null,"url":"assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2"},{"revision":null,"url":"assets/chevron-right-CD8e6Aj4.js"},{"revision":null,"url":"assets/vendor-mermaid-DCie7hiR.js"},{"revision":null,"url":"assets/trash-2-DkIfBY8d.js"},{"revision":null,"url":"assets/git-log-panel-BA4xxiZR.js"},{"revision":null,"url":"assets/text-wrap-AZErifCu.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/globe-CQ8NAYvi.js"},{"revision":null,"url":"assets/utils-E0yyGxXt.js"},{"revision":null,"url":"assets/rolldown-runtime-FhOqtrmT.js"},{"revision":null,"url":"assets/use-monaco-theme-qx6SfVRk.js"},{"revision":null,"url":"assets/input-B78ol0hV.js"},{"revision":null,"url":"assets/code-DiNmA3eR.js"},{"revision":null,"url":"assets/refresh-cw-CRD2qr4U.js"},{"revision":null,"url":"assets/search-D90WJ5fo.js"},{"revision":null,"url":"assets/vendor-ui-UXCWAcmi.js"},{"revision":null,"url":"assets/file-exclamation-point-B__2Hrd6.js"},{"revision":null,"url":"assets/KaTeX_Math-Italic-t53AETM-.woff2"},{"revision":null,"url":"assets/data-grid-types-C29KDkZJ.js"},{"revision":null,"url":"assets/katex-DUj5OG1J.js"},{"revision":null,"url":"assets/index-DwvSM9vu.css"},{"revision":null,"url":"assets/github-dark-dimmed.min-BrpRStFV.css"},{"revision":null,"url":"assets/postgres-viewer-D72BwBOg.js"},{"revision":null,"url":"assets/number-overlay-editor-DS-qf63L.js"},{"revision":null,"url":"assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2"},{"revision":null,"url":"assets/KaTeX_Script-Regular-D3wIWfF6.woff2"},{"revision":null,"url":"assets/arrow-up-Rcw6_KKu.js"},{"revision":null,"url":"assets/ai-settings-section-BH2UOQH-.js"},{"revision":null,"url":"assets/database-Dc8mr-dP.js"},{"revision":null,"url":"assets/eye-off-BacF7RVS.js"},{"revision":null,"url":"assets/KaTeX_Main-Bold-Cx986IdX.woff2"},{"revision":null,"url":"assets/database-viewer-Qp7wc8sJ.js"},{"revision":null,"url":"assets/port-forwarding-tab-Bm2K9NO-.js"},{"revision":null,"url":"assets/image-preview-meDqzT6e.js"},{"revision":null,"url":"assets/KaTeX_Size2-Regular-Dy4dx90m.woff2"},{"revision":null,"url":"assets/vendor-xterm-t3d5xZdz.js"},{"revision":null,"url":"assets/video-preview-DC5PWgPo.js"},{"revision":null,"url":"assets/chat-tab-6xXNp6td.js"},{"revision":null,"url":"assets/api-client-DiZgVOok.js"},{"revision":null,"url":"assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2"},{"revision":null,"url":"assets/KaTeX_Size1-Regular-mCD8mA8B.woff2"},{"revision":null,"url":"assets/x-DfF6D5Js.js"},{"revision":null,"url":"assets/treemap-KZPCXAKY-DChODgHt.js"},{"revision":null,"url":"assets/createLucideIcon-BjHrJDVb.js"},{"revision":null,"url":"assets/data-grid-overlay-editor-CmduzuPM.js"},{"revision":null,"url":"assets/terminal-tab-BHFM5N0l.js"},{"revision":null,"url":"assets/settings-tab-BTT3CCTS.js"},{"revision":null,"url":"assets/keybindings-store-D6R_grEQ.js"},{"revision":null,"url":"assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2"},{"revision":null,"url":"assets/markdown-renderer-BPuu5Czn.js"},{"revision":null,"url":"assets/gitGraph-HDMCJU4V-2a0r4GHr.js"},{"revision":null,"url":"assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2"},{"revision":null,"url":"assets/sparkles-KCOEy7QI.js"},{"revision":null,"url":"assets/architecture-PBZL5I3N-DLKD1Xjj.js"},{"revision":null,"url":"assets/sql-query-editor-BiVMmcK3.js"},{"revision":null,"url":"assets/diff-viewer-FyAyKn4J.js"},{"revision":null,"url":"assets/sqlite-viewer-CKdZF5AA.js"},{"revision":null,"url":"assets/csv-parser-D8VHWVA6.js"},{"revision":null,"url":"assets/csv-preview-DgArUJhd.js"},{"revision":null,"url":"assets/tab-store-DzftzxTL.js"},{"revision":null,"url":"assets/glide-data-grid-PZOzxnWV.js"},{"revision":null,"url":"assets/vendor-xterm-BrP-ENHg.css"},{"revision":null,"url":"assets/audio-preview-CKyqt8Qa.js"},{"revision":null,"url":"assets/dist-PPUhQONj.js"},{"revision":null,"url":"assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2"},{"revision":null,"url":"assets/conflict-editor-pC5U_tzr.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":"a0fb34fc84eb148d51812cd62669f20d","url":"icon-512.svg"},{"revision":"a0fb34fc84eb148d51812cd62669f20d","url":"icon-192.svg"},{"revision":"26dccd02a2ef7522892015154f5e3680","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.svg`,badge:`/icon-192.svg`,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":"9c8d76ce1ce265e16d12a82d0a7452bf","url":"index.html"},{"revision":"a0fb34fc84eb148d51812cd62669f20d","url":"icon-512.svg"},{"revision":null,"url":"assets/image-preview-CJ8Qqjgt.js"},{"revision":null,"url":"assets/react-CfveccaI.js"},{"revision":null,"url":"assets/sql-query-editor-XkJZFBl5.js"},{"revision":null,"url":"assets/code-editor-D3ofpe9P.js"},{"revision":null,"url":"assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2"},{"revision":null,"url":"assets/api-settings-uQKmeGkl.js"},{"revision":null,"url":"assets/KaTeX_AMS-Regular-BQhdFMY1.woff2"},{"revision":null,"url":"assets/project-store-BnvrVKBw.js"},{"revision":null,"url":"assets/extension-webview-BUzklJiG.js"},{"revision":null,"url":"assets/table-2wDtM4_B.js"},{"revision":null,"url":"assets/packet-RMMSAZCW-Ar00Wbhd.js"},{"revision":null,"url":"assets/arrow-down-D825m4vm.js"},{"revision":null,"url":"assets/port-forwarding-tab-DvoM_j8q.js"},{"revision":null,"url":"assets/sqlite-viewer-DR2scYmN.js"},{"revision":null,"url":"assets/use-blob-url-DCUIEzjB.js"},{"revision":null,"url":"assets/KaTeX_Main-Regular-B22Nviop.woff2"},{"revision":null,"url":"assets/chevron-down-BMo4cBth.js"},{"revision":null,"url":"assets/dist-DeY41KFi.js"},{"revision":null,"url":"assets/pie-UPGHQEXC-Q4ssDdib.js"},{"revision":null,"url":"assets/github.min-D2BCvnWf.css"},{"revision":null,"url":"assets/radar-KQ55EAFF-kq5v4OKX.js"},{"revision":null,"url":"assets/settings-store-CSDOihqv.js"},{"revision":null,"url":"assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2"},{"revision":null,"url":"assets/audio-preview-D8onaG_2.js"},{"revision":null,"url":"assets/info-3K5VOQVL-CWKw4e0V.js"},{"revision":null,"url":"assets/settings-tab-B52JczcO.js"},{"revision":null,"url":"assets/glide-data-grid-nthEL3fk.css"},{"revision":null,"url":"assets/esm-JPvheKDJ.js"},{"revision":null,"url":"assets/lib-DrypSCq8.js"},{"revision":null,"url":"assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2"},{"revision":null,"url":"assets/panel-store-B1pOXkyS.js"},{"revision":null,"url":"assets/glide-data-grid-BJ3Wn9to.js"},{"revision":null,"url":"assets/vendor-markdown-0Mxgxy0L.js"},{"revision":null,"url":"assets/wifi-LJEyIdXf.js"},{"revision":null,"url":"assets/conflict-editor-BaSyowCW.js"},{"revision":null,"url":"assets/git-log-panel-CkfQFd6J.js"},{"revision":null,"url":"assets/chat-tab-D1n2fmne.js"},{"revision":null,"url":"assets/KaTeX_Main-Italic-NWA7e6Wa.woff2"},{"revision":null,"url":"assets/diff-viewer-D5SnxW8Y.js"},{"revision":null,"url":"assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2"},{"revision":null,"url":"assets/chevron-right-CD8e6Aj4.js"},{"revision":null,"url":"assets/vendor-mermaid-DCie7hiR.js"},{"revision":null,"url":"assets/trash-2-DkIfBY8d.js"},{"revision":null,"url":"assets/keybindings-store-D9NMYHYK.js"},{"revision":null,"url":"assets/text-wrap-AZErifCu.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/pdf-preview-D3Y2rNVC.js"},{"revision":null,"url":"assets/globe-CQ8NAYvi.js"},{"revision":null,"url":"assets/utils-E0yyGxXt.js"},{"revision":null,"url":"assets/video-preview-EhN6q3Xv.js"},{"revision":null,"url":"assets/rolldown-runtime-FhOqtrmT.js"},{"revision":null,"url":"assets/use-monaco-theme-qx6SfVRk.js"},{"revision":null,"url":"assets/input-B78ol0hV.js"},{"revision":null,"url":"assets/code-DiNmA3eR.js"},{"revision":null,"url":"assets/refresh-cw-CRD2qr4U.js"},{"revision":null,"url":"assets/search-D90WJ5fo.js"},{"revision":null,"url":"assets/vendor-ui-UXCWAcmi.js"},{"revision":null,"url":"assets/file-exclamation-point-B__2Hrd6.js"},{"revision":null,"url":"assets/KaTeX_Math-Italic-t53AETM-.woff2"},{"revision":null,"url":"assets/data-grid-types-C29KDkZJ.js"},{"revision":null,"url":"assets/katex-DUj5OG1J.js"},{"revision":null,"url":"assets/index-DwvSM9vu.css"},{"revision":null,"url":"assets/github-dark-dimmed.min-BrpRStFV.css"},{"revision":null,"url":"assets/number-overlay-editor-DS-qf63L.js"},{"revision":null,"url":"assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2"},{"revision":null,"url":"assets/KaTeX_Script-Regular-D3wIWfF6.woff2"},{"revision":null,"url":"assets/arrow-up-Rcw6_KKu.js"},{"revision":null,"url":"assets/ai-settings-section-BH2UOQH-.js"},{"revision":null,"url":"assets/database-Dc8mr-dP.js"},{"revision":null,"url":"assets/eye-off-BacF7RVS.js"},{"revision":null,"url":"assets/KaTeX_Main-Bold-Cx986IdX.woff2"},{"revision":null,"url":"assets/index-Di7q5I27.js"},{"revision":null,"url":"assets/KaTeX_Size2-Regular-Dy4dx90m.woff2"},{"revision":null,"url":"assets/vendor-xterm-t3d5xZdz.js"},{"revision":null,"url":"assets/api-client-DiZgVOok.js"},{"revision":null,"url":"assets/markdown-renderer-YnP2OcPh.js"},{"revision":null,"url":"assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2"},{"revision":null,"url":"assets/KaTeX_Size1-Regular-mCD8mA8B.woff2"},{"revision":null,"url":"assets/x-DfF6D5Js.js"},{"revision":null,"url":"assets/treemap-KZPCXAKY-DChODgHt.js"},{"revision":null,"url":"assets/createLucideIcon-BjHrJDVb.js"},{"revision":null,"url":"assets/data-grid-overlay-editor-CmduzuPM.js"},{"revision":null,"url":"assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2"},{"revision":null,"url":"assets/gitGraph-HDMCJU4V-2a0r4GHr.js"},{"revision":null,"url":"assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2"},{"revision":null,"url":"assets/database-viewer-IDG5i3mc.js"},{"revision":null,"url":"assets/sparkles-KCOEy7QI.js"},{"revision":null,"url":"assets/architecture-PBZL5I3N-DLKD1Xjj.js"},{"revision":null,"url":"assets/postgres-viewer-BN_vFAqs.js"},{"revision":null,"url":"assets/csv-parser-D8VHWVA6.js"},{"revision":null,"url":"assets/csv-preview-DgArUJhd.js"},{"revision":null,"url":"assets/tab-store-DzftzxTL.js"},{"revision":null,"url":"assets/notification-store-CW0Alosv.js"},{"revision":null,"url":"assets/vendor-xterm-BrP-ENHg.css"},{"revision":null,"url":"assets/system-monitor-tab-QXA7p0VH.js"},{"revision":null,"url":"assets/dist-PPUhQONj.js"},{"revision":null,"url":"assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2"},{"revision":null,"url":"assets/terminal-tab-BI9y5tDL.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":"a0fb34fc84eb148d51812cd62669f20d","url":"icon-512.svg"},{"revision":"a0fb34fc84eb148d51812cd62669f20d","url":"icon-192.svg"},{"revision":"26dccd02a2ef7522892015154f5e3680","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.svg`,badge:`/icon-192.svg`,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||`/`)}))});
@@ -210,7 +210,8 @@ src/
210
210
  │ │ ├── draggable-tab.tsx # Draggable tab with context menu, rename, connection color
211
211
  │ │ ├── tab-content.tsx # Router for tab content (v0.9.85+: fallback guards)
212
212
  │ │ ├── split-drop-overlay.tsx # Drop zone for tab splitting
213
- │ │ ├── command-palette.tsx # Global command palette (Shift+Shift, DB table search)
213
+ │ │ ├── command-palette.tsx # Global command palette (Shift+Shift, DB table search, filter chips for Actions/Files/DB/Filesystem)
214
+ │ │ ├── command-palette-filter-chips.tsx # Presentational filter chip bar — group toggle buttons with count badges (hidden when ≤1 group)
214
215
  │ │ ├── add-project-form.tsx # Modal form to add projects
215
216
  │ │ ├── mobile-nav.tsx # Bottom navigation for mobile (v0.9.85+: fallback guards)
216
217
  │ │ └── mobile-drawer.tsx # Mobile overlay drawer
@@ -20,9 +20,18 @@ All notable changes to PPM are documented here. Format follows [Keep a Changelog
20
20
 
21
21
  ---
22
22
 
23
- ## [Unreleased] — Resource Monitor, Lazy-Load File Tree + Palette Index, Session Tagging, File Compare, Draft Messages, Jira Debug Session Redesign, Frontend Memory Optimization, Git-Graph Enhancements
23
+ ## [Unreleased] — Resource Monitor, Lazy-Load File Tree + Palette Index, Session Tagging, File Compare, Draft Messages, Jira Debug Session Redesign, Frontend Memory Optimization, Git-Graph Enhancements, Command Palette Filter Chips
24
24
 
25
25
  ### Added
26
+ - **Command Palette Filter Chips** — Filter palette results by type with toggle chips
27
+ - Chips appear above results when 2+ groups are available (Actions, Files, Database, Filesystem)
28
+ - Each chip shows group label, icon, and live result count for the current query
29
+ - Toggling a chip includes/excludes that group; multiple chips can be active simultaneously
30
+ - Chips hidden when only one group has results (no filtering value)
31
+ - Mobile-friendly: 44px touch targets, horizontally scrollable chip row
32
+ - Files: `command-palette-filter-chips.tsx` (new presentational component), `command-palette.tsx` (filter state + `availableGroups` + `groupCounts` + `displayItems` logic)
33
+
34
+
26
35
  - **System Resource Monitor** — Real-time process monitoring with SSE streaming, sidebar status bar, and dedicated System Monitor tab
27
36
  - Backend: `ResourceMonitorService` polls `ps` command every 3s, builds process tree from PPM root PID, categorizes processes (server/terminal/ai-tool/build/unknown), maintains 30-min ring buffer (600 snapshots)
28
37
  - Backend: SSE streaming route at `GET /api/system/resources/stream` with client count cap (max 5), manual reconnect with exponential backoff
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hienlh/ppm",
3
- "version": "0.13.61",
3
+ "version": "0.13.63",
4
4
  "description": "Personal Project Manager — mobile-first web IDE with AI assistance",
5
5
  "author": "hienlh",
6
6
  "license": "MIT",
package/src/index.ts CHANGED
File without changes
@@ -1,6 +1,7 @@
1
1
  import { useState, useMemo, useEffect, useRef, useCallback } from "react";
2
2
  import { Database, RefreshCw, GripHorizontal, Loader2 } from "lucide-react";
3
3
  import { api } from "@/lib/api-client";
4
+ import { useTabStore } from "@/stores/tab-store";
4
5
  import { useDatabase, type DbColumnInfo } from "./use-database";
5
6
  import { SqlQueryEditor } from "./sql-query-editor";
6
7
  import { ExportButton } from "./export-button";
@@ -21,12 +22,22 @@ function parseSqlFilters(sql: string): Record<string, string> {
21
22
  interface Props { metadata?: Record<string, unknown>; tabId?: string }
22
23
 
23
24
  /** Generic database viewer — works for any DB type via unified API */
24
- export function DatabaseViewer({ metadata }: Props) {
25
+ export function DatabaseViewer({ metadata, tabId }: Props) {
25
26
  const connectionId = metadata?.connectionId as number;
26
27
  const connectionName = metadata?.connectionName as string | undefined;
27
28
  const initialTable = metadata?.tableName as string | undefined;
28
29
  const initialSchema = (metadata?.schemaName as string) ?? "public";
29
30
  const initialSql = metadata?.initialSql as string | undefined;
31
+ const persistedSql = metadata?.currentSql as string | undefined;
32
+
33
+ // Persist SQL text to tab metadata (debounced via updateTab's built-in persist)
34
+ const updateTab = useTabStore((s) => s.updateTab);
35
+ const metadataRef = useRef(metadata);
36
+ metadataRef.current = metadata;
37
+ const handleSqlChange = useCallback((sql: string) => {
38
+ if (!tabId) return;
39
+ updateTab(tabId, { metadata: { ...metadataRef.current, currentSql: sql } });
40
+ }, [tabId, updateTab]);
30
41
 
31
42
  const db = useDatabase(connectionId);
32
43
  const [cachedTableNames, setCachedTableNames] = useState<{ name: string; schema: string }[]>([]);
@@ -183,7 +194,7 @@ export function DatabaseViewer({ metadata }: Props) {
183
194
  <SqlQueryEditor
184
195
  onExecute={handleExecuteQuery} loading={db.queryLoading}
185
196
  defaultValue={defaultQuery} schemaInfo={schemaInfo}
186
- cacheKey={connectionId ? String(connectionId) : undefined} />
197
+ onSqlChange={handleSqlChange} persistedSql={persistedSql} />
187
198
  </div>
188
199
 
189
200
  {/* Resize handle */}
@@ -9,8 +9,10 @@ interface SqlQueryEditorProps {
9
9
  loading: boolean;
10
10
  defaultValue?: string;
11
11
  schemaInfo?: SchemaInfo;
12
- /** Unique key for caching query text in sessionStorage (e.g. connectionId) */
13
- cacheKey?: string;
12
+ /** Called when the user edits the SQL text (for external persistence) */
13
+ onSqlChange?: (sql: string) => void;
14
+ /** Persisted SQL to restore on mount (takes priority over defaultValue if user hasn't edited) */
15
+ persistedSql?: string;
14
16
  }
15
17
 
16
18
  /** Find the SQL statement surrounding the cursor line (split by ;) */
@@ -43,13 +45,9 @@ export function getStatementAtCursor(text: string, cursorLine: number): string {
43
45
  }
44
46
 
45
47
  /** Shared Monaco-based SQL query editor (editor only, no results) */
46
- export function SqlQueryEditor({ onExecute, loading, defaultValue = "SELECT * FROM ", schemaInfo, cacheKey }: SqlQueryEditorProps) {
47
- const storageKey = cacheKey ? `ppm:sql-query:${cacheKey}` : null;
48
- const [query, setQuery] = useState(() => {
49
- if (storageKey) { try { return sessionStorage.getItem(storageKey) ?? defaultValue; } catch { /* */ } }
50
- return defaultValue;
51
- });
52
- const userEditedRef = useRef(false);
48
+ export function SqlQueryEditor({ onExecute, loading, defaultValue = "SELECT * FROM ", schemaInfo, onSqlChange, persistedSql }: SqlQueryEditorProps) {
49
+ const [query, setQuery] = useState(() => persistedSql ?? defaultValue);
50
+ const userEditedRef = useRef(!!persistedSql);
53
51
  const editorRef = useRef<MonacoType.editor.IStandaloneCodeEditor | null>(null);
54
52
  const monacoRef = useRef<typeof MonacoType | null>(null);
55
53
  const disposableRef = useRef<MonacoType.IDisposable | null>(null);
@@ -107,7 +105,7 @@ export function SqlQueryEditor({ onExecute, loading, defaultValue = "SELECT * FR
107
105
  language="sql"
108
106
  theme={monacoTheme}
109
107
  value={query}
110
- onChange={(v) => { const val = v ?? ""; setQuery(val); userEditedRef.current = true; if (storageKey) try { sessionStorage.setItem(storageKey, val); } catch {} }}
108
+ onChange={(v) => { const val = v ?? ""; setQuery(val); userEditedRef.current = true; onSqlChange?.(val); }}
111
109
  onMount={handleMount}
112
110
  options={{
113
111
  minimap: { enabled: false },
@@ -245,6 +245,11 @@ export function FileTree({ onFileOpen }: FileTreeProps = {}) {
245
245
  navigator.clipboard.writeText(node.path).catch(() => {});
246
246
  return;
247
247
  }
248
+ if (action === "copy-full-path") {
249
+ const root = activeProject?.path;
250
+ navigator.clipboard.writeText(root ? `${root}/${node.path}` : node.path).catch(() => {});
251
+ return;
252
+ }
248
253
  if (action === "select-for-compare") {
249
254
  useCompareStore.getState().setSelection({
250
255
  filePath: node.path,
@@ -66,6 +66,9 @@ export function TreeNodeContextMenu({
66
66
  <ContextMenuItem onClick={() => onAction("copy-path", node)}>
67
67
  Copy Path
68
68
  </ContextMenuItem>
69
+ <ContextMenuItem onClick={() => onAction("copy-full-path", node)}>
70
+ Copy Full Path
71
+ </ContextMenuItem>
69
72
  <ContextMenuSeparator />
70
73
  <ContextMenuItem onClick={() => onAction("download", node)}>
71
74
  <Download className="size-3.5 mr-2" />
@@ -0,0 +1,61 @@
1
+ import { FileCode, Database, FolderOpen, Zap } from "lucide-react";
2
+
3
+ /** Metadata for each command group — label and icon for the filter chip */
4
+ const GROUP_META: Record<string, { label: string; icon: React.ElementType }> = {
5
+ action: { label: "Actions", icon: Zap },
6
+ file: { label: "Files", icon: FileCode },
7
+ db: { label: "Database", icon: Database },
8
+ fs: { label: "Filesystem", icon: FolderOpen },
9
+ };
10
+
11
+ interface CommandPaletteFilterChipsProps {
12
+ /** Groups that have data (stable, pre-query) */
13
+ availableGroups: string[];
14
+ /** Count of filtered results per group (updates with query) */
15
+ groupCounts: Record<string, number>;
16
+ /** Currently active filter groups */
17
+ activeFilters: Set<string>;
18
+ /** Toggle a group filter on/off */
19
+ onToggle: (group: string) => void;
20
+ }
21
+
22
+ export function CommandPaletteFilterChips({
23
+ availableGroups,
24
+ groupCounts,
25
+ activeFilters,
26
+ onToggle,
27
+ }: CommandPaletteFilterChipsProps) {
28
+ if (availableGroups.length <= 1) return null;
29
+
30
+ return (
31
+ <div className="flex items-center gap-1.5 border-b border-border/50 px-3 py-1.5 overflow-x-auto">
32
+ {availableGroups.map((group) => {
33
+ const meta = GROUP_META[group];
34
+ if (!meta) return null;
35
+ const count = groupCounts[group] ?? 0;
36
+ const isActive = activeFilters.has(group);
37
+ const Icon = meta.icon;
38
+
39
+ return (
40
+ <button
41
+ key={group}
42
+ type="button"
43
+ role="switch"
44
+ aria-checked={isActive}
45
+ aria-label={`Filter by ${meta.label}`}
46
+ onClick={() => onToggle(group)}
47
+ className={`inline-flex items-center gap-1 shrink-0 rounded-full border px-2.5 min-h-[44px] text-xs font-medium transition-colors ${
48
+ isActive
49
+ ? "bg-accent/15 border-accent text-accent"
50
+ : "bg-surface border-border text-text-subtle hover:bg-surface-elevated"
51
+ } ${count === 0 ? "opacity-50" : ""}`}
52
+ >
53
+ <Icon className="size-3" />
54
+ <span>{meta.label}</span>
55
+ <span className="text-[10px] opacity-70">({count})</span>
56
+ </button>
57
+ );
58
+ })}
59
+ </div>
60
+ );
61
+ }
@@ -28,6 +28,7 @@ import { useCompareStore } from "@/stores/compare-store";
28
28
  import { api } from "@/lib/api-client";
29
29
  import { basename } from "@/lib/utils";
30
30
  import { scoreFileSearchFast, compareScores, getFilename, type FileSearchScore } from "@/lib/score-file-search";
31
+ import { CommandPaletteFilterChips } from "@/components/layout/command-palette-filter-chips";
31
32
 
32
33
  /** Max results to display — prevents rendering thousands of matches */
33
34
  const MAX_RESULTS = 100;
@@ -120,6 +121,7 @@ export function CommandPalette({ open, onClose, initialQuery = "" }: { open: boo
120
121
  const [fsFiles, setFsFiles] = useState<string[]>([]);
121
122
  const [fsLoading, setFsLoading] = useState(false);
122
123
  const [dbResults, setDbResults] = useState<DbSearchResult[]>([]);
124
+ const [activeFilters, setActiveFilters] = useState<Set<string>>(new Set());
123
125
  const inputRef = useRef<HTMLInputElement>(null);
124
126
  const listRef = useRef<HTMLDivElement>(null);
125
127
 
@@ -384,6 +386,38 @@ export function CommandPalette({ open, onClose, initialQuery = "" }: { open: boo
384
386
  return deferredQuery.trim().length >= 2 ? [...dbCommands, ...matched] : matched;
385
387
  }, [searchIndex, actionCommands, fsCommands, dbCommands, deferredQuery]);
386
388
 
389
+ // Stable set of groups that have data (pre-query) — prevents chip flashing
390
+ const availableGroups = useMemo(() => {
391
+ const groups = new Set<string>();
392
+ for (const cmd of allCommands) groups.add(cmd.group);
393
+ if (dbResults.length > 0) groups.add("db");
394
+ if (fsFiles.length > 0) groups.add("fs");
395
+ return Array.from(groups);
396
+ }, [allCommands, dbResults.length, fsFiles.length]);
397
+
398
+ // Per-group counts from search-filtered results (updates with query)
399
+ const groupCounts = useMemo(() => {
400
+ const counts: Record<string, number> = {};
401
+ for (const cmd of filtered) counts[cmd.group] = (counts[cmd.group] ?? 0) + 1;
402
+ return counts;
403
+ }, [filtered]);
404
+
405
+ // Final display list — apply group filters as post-process
406
+ const displayItems = useMemo(() => {
407
+ if (activeFilters.size === 0) return filtered;
408
+ return filtered.filter((cmd) => activeFilters.has(cmd.group));
409
+ }, [filtered, activeFilters]);
410
+
411
+ const toggleFilter = useCallback((group: string) => {
412
+ setActiveFilters((prev) => {
413
+ const next = new Set(prev);
414
+ if (next.has(group)) next.delete(group);
415
+ else next.add(group);
416
+ return next;
417
+ });
418
+ setSelectedIdx(0);
419
+ }, []);
420
+
387
421
  // Auto-load file index when palette opens and index isn't ready
388
422
  useEffect(() => {
389
423
  if (open && indexStatus === "idle" && activeProject) {
@@ -398,14 +432,15 @@ export function CommandPalette({ open, onClose, initialQuery = "" }: { open: boo
398
432
  setSelectedIdx(0);
399
433
  setFsFiles([]);
400
434
  setDbResults([]);
435
+ setActiveFilters(new Set());
401
436
  requestAnimationFrame(() => inputRef.current?.focus());
402
437
  }
403
438
  }, [open]);
404
439
 
405
- // Clamp selected index when filter changes
440
+ // Clamp selected index when display list changes
406
441
  useEffect(() => {
407
- setSelectedIdx((prev) => Math.min(prev, Math.max(filtered.length - 1, 0)));
408
- }, [filtered.length]);
442
+ setSelectedIdx((prev) => Math.min(prev, Math.max(displayItems.length - 1, 0)));
443
+ }, [displayItems.length]);
409
444
 
410
445
  // Scroll selected item into view
411
446
  useEffect(() => {
@@ -430,7 +465,7 @@ export function CommandPalette({ open, onClose, initialQuery = "" }: { open: boo
430
465
  }, [query, activeProject, openTab, onClose]);
431
466
 
432
467
  function handleKeyDown(e: React.KeyboardEvent) {
433
- const len = filtered.length;
468
+ const len = displayItems.length;
434
469
  switch (e.key) {
435
470
  case "ArrowDown":
436
471
  e.preventDefault();
@@ -443,7 +478,7 @@ export function CommandPalette({ open, onClose, initialQuery = "" }: { open: boo
443
478
  case "Enter":
444
479
  e.preventDefault();
445
480
  if (len > 0) {
446
- filtered[selectedIdx]?.action();
481
+ displayItems[selectedIdx]?.action();
447
482
  } else if (query.trim()) {
448
483
  askAi();
449
484
  }
@@ -510,11 +545,23 @@ export function CommandPalette({ open, onClose, initialQuery = "" }: { open: boo
510
545
  </div>
511
546
  )}
512
547
 
548
+ {/* Filter chips — hidden in path mode */}
549
+ {!pathMode && (
550
+ <CommandPaletteFilterChips
551
+ availableGroups={availableGroups}
552
+ groupCounts={groupCounts}
553
+ activeFilters={activeFilters}
554
+ onToggle={toggleFilter}
555
+ />
556
+ )}
557
+
513
558
  {/* Results */}
514
559
  <div ref={listRef} className="max-h-72 overflow-y-auto py-1">
515
- {filtered.length === 0 ? (
560
+ {displayItems.length === 0 ? (
516
561
  fsLoading ? (
517
562
  <p className="px-3 py-4 text-sm text-text-subtle text-center">Searching...</p>
563
+ ) : activeFilters.size > 0 && filtered.length > 0 ? (
564
+ <p className="px-3 py-4 text-sm text-text-subtle text-center">No results in selected filters</p>
518
565
  ) : query.trim() ? (
519
566
  <button
520
567
  onClick={askAi}
@@ -527,7 +574,7 @@ export function CommandPalette({ open, onClose, initialQuery = "" }: { open: boo
527
574
  <p className="px-3 py-4 text-sm text-text-subtle text-center">No results</p>
528
575
  )
529
576
  ) : (
530
- filtered.map((cmd, i) => {
577
+ displayItems.map((cmd, i) => {
531
578
  const Icon = cmd.icon;
532
579
  return (
533
580
  <button
@@ -182,6 +182,9 @@ export function DraggableTab({
182
182
  <ContextMenuItem onClick={() => onContextAction("copy-path")}>
183
183
  Copy Path
184
184
  </ContextMenuItem>
185
+ <ContextMenuItem onClick={() => onContextAction("copy-full-path")}>
186
+ Copy Full Path
187
+ </ContextMenuItem>
185
188
  <ContextMenuItem onClick={() => onContextAction("download")}>
186
189
  <Download className="size-3.5 mr-2" />
187
190
  Download
@@ -138,6 +138,13 @@ export function MobileNav({ onMenuPress, onProjectsPress }: MobileNavProps) {
138
138
  case "copy-path":
139
139
  if (filePath) navigator.clipboard.writeText(filePath).catch(() => {});
140
140
  break;
141
+ case "copy-full-path": {
142
+ if (filePath) {
143
+ const project = projectName ? useProjectStore.getState().projects.find((p) => p.name === projectName) : null;
144
+ navigator.clipboard.writeText(project ? `${project.path}/${filePath}` : filePath).catch(() => {});
145
+ }
146
+ break;
147
+ }
141
148
  case "download":
142
149
  if (filePath && projectName) downloadFile(projectName, filePath);
143
150
  break;
@@ -334,6 +341,10 @@ export function MobileNav({ onMenuPress, onProjectsPress }: MobileNavProps) {
334
341
  className="flex items-center gap-2 w-full px-3 py-2.5 text-sm text-foreground active:bg-surface-elevated">
335
342
  <Copy className="size-4" /> Copy Path
336
343
  </button>
344
+ <button onClick={() => handleFileAction(menuTab, "copy-full-path")}
345
+ className="flex items-center gap-2 w-full px-3 py-2.5 text-sm text-foreground active:bg-surface-elevated">
346
+ <Copy className="size-4" /> Copy Full Path
347
+ </button>
337
348
  <button onClick={() => handleFileAction(menuTab, "download")}
338
349
  className="flex items-center gap-2 w-full px-3 py-2.5 text-sm text-foreground active:bg-surface-elevated">
339
350
  <Download className="size-4" /> Download
@@ -231,6 +231,15 @@ export const TabBar = memo(function TabBar({ panelId }: TabBarProps) {
231
231
  if (filePath) navigator.clipboard.writeText(filePath).catch(() => {});
232
232
  break;
233
233
  }
234
+ case "copy-full-path": {
235
+ const filePath = tab.metadata?.filePath as string | undefined;
236
+ const projectName = tab.metadata?.projectName as string | undefined;
237
+ if (filePath) {
238
+ const project = projectName ? useProjectStore.getState().projects.find((p) => p.name === projectName) : null;
239
+ navigator.clipboard.writeText(project ? `${project.path}/${filePath}` : filePath).catch(() => {});
240
+ }
241
+ break;
242
+ }
234
243
  case "download": {
235
244
  const filePath = tab.metadata?.filePath as string | undefined;
236
245
  const projectName = tab.metadata?.projectName as string | undefined;