termbeam 1.22.4 → 1.23.0

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 (67) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/README.md +4 -2
  3. package/package.json +2 -2
  4. package/public/assets/{_basePickBy-Q45O29Rp.js → _basePickBy-DyywFBt7.js} +1 -1
  5. package/public/assets/{_baseUniq-DveOFRWk.js → _baseUniq-8tmjrEbB.js} +1 -1
  6. package/public/assets/{arc-BxuxWgHK.js → arc-CHQhvBsi.js} +1 -1
  7. package/public/assets/{architectureDiagram-Q4EWVU46-ByQ5j_Jw.js → architectureDiagram-Q4EWVU46-MEcrgEqw.js} +1 -1
  8. package/public/assets/{blockDiagram-DXYQGD6D-DrElm25z.js → blockDiagram-DXYQGD6D-EtZXyKmn.js} +1 -1
  9. package/public/assets/{c4Diagram-AHTNJAMY-BATf25U2.js → c4Diagram-AHTNJAMY-BuvkD-Hg.js} +1 -1
  10. package/public/assets/channel-a7jr0in8.js +1 -0
  11. package/public/assets/{chunk-4BX2VUAB-BksLrXVs.js → chunk-4BX2VUAB-m4pupC7U.js} +1 -1
  12. package/public/assets/{chunk-4TB4RGXK-D5KX6fMH.js → chunk-4TB4RGXK-C-BdMhbz.js} +1 -1
  13. package/public/assets/{chunk-55IACEB6-DLtS129C.js → chunk-55IACEB6-BhvwRyIq.js} +1 -1
  14. package/public/assets/{chunk-EDXVE4YY-CtqMCguR.js → chunk-EDXVE4YY-BqButrNC.js} +1 -1
  15. package/public/assets/{chunk-FMBD7UC4-BkeSsxqP.js → chunk-FMBD7UC4-54Zrs_SG.js} +1 -1
  16. package/public/assets/{chunk-OYMX7WX6-Dl4p17KI.js → chunk-OYMX7WX6-Zxqgp5PC.js} +1 -1
  17. package/public/assets/{chunk-QZHKN3VN-CBMUqRwi.js → chunk-QZHKN3VN-BOkHXJLa.js} +1 -1
  18. package/public/assets/{chunk-YZCP3GAM-D4sWv3YN.js → chunk-YZCP3GAM-BFo64bhH.js} +1 -1
  19. package/public/assets/classDiagram-6PBFFD2Q-DHBlyv18.js +1 -0
  20. package/public/assets/classDiagram-v2-HSJHXN6E-DHBlyv18.js +1 -0
  21. package/public/assets/clone-D-YD4Hfu.js +1 -0
  22. package/public/assets/{cose-bilkent-S5V4N54A-BW1nNLLk.js → cose-bilkent-S5V4N54A-CdRKoWQm.js} +1 -1
  23. package/public/assets/{dagre-KV5264BT-CDXKs5yo.js → dagre-KV5264BT-CqYqJqNz.js} +1 -1
  24. package/public/assets/{diagram-5BDNPKRD-DfSZsVAD.js → diagram-5BDNPKRD-KXbdPm8R.js} +1 -1
  25. package/public/assets/{diagram-G4DWMVQ6-CIKqapDb.js → diagram-G4DWMVQ6-BjcXJ3-Q.js} +1 -1
  26. package/public/assets/{diagram-MMDJMWI5-CWwOGFbE.js → diagram-MMDJMWI5-Da91D4Jv.js} +1 -1
  27. package/public/assets/{diagram-TYMM5635-CAMd8OZP.js → diagram-TYMM5635-CilekWFu.js} +1 -1
  28. package/public/assets/{erDiagram-SMLLAGMA-BCpXZJei.js → erDiagram-SMLLAGMA-COlP9qIO.js} +1 -1
  29. package/public/assets/{flowDiagram-DWJPFMVM-DpRY38iu.js → flowDiagram-DWJPFMVM-BebPntFK.js} +1 -1
  30. package/public/assets/{ganttDiagram-T4ZO3ILL-DJrwFHlO.js → ganttDiagram-T4ZO3ILL-C-JweidK.js} +1 -1
  31. package/public/assets/{gitGraphDiagram-UUTBAWPF-CpEmQcDN.js → gitGraphDiagram-UUTBAWPF-CmtVcokM.js} +1 -1
  32. package/public/assets/{graph-C-meUi4Z.js → graph-DGYmOqb_.js} +1 -1
  33. package/public/assets/index-Bap8dJMF.js +463 -0
  34. package/public/assets/index-CTMMjkRB.css +32 -0
  35. package/public/assets/{infoDiagram-42DDH7IO-sXN9f7L8.js → infoDiagram-42DDH7IO-JWuRQxiM.js} +1 -1
  36. package/public/assets/{ishikawaDiagram-UXIWVN3A-D_2gGEMI.js → ishikawaDiagram-UXIWVN3A-Buhn0bpg.js} +1 -1
  37. package/public/assets/{journeyDiagram-VCZTEJTY-CUv-hh0I.js → journeyDiagram-VCZTEJTY-CKm_djiT.js} +1 -1
  38. package/public/assets/{kanban-definition-6JOO6SKY-Cx3Plwl2.js → kanban-definition-6JOO6SKY-C_Sj08cN.js} +1 -1
  39. package/public/assets/{layout-CpGcFwHw.js → layout-BQRZ4_Il.js} +1 -1
  40. package/public/assets/{linear-Bw9XpXJW.js → linear-wWmlfvSH.js} +1 -1
  41. package/public/assets/{mindmap-definition-QFDTVHPH-Swj4jh3Z.js → mindmap-definition-QFDTVHPH-CwCTVEhc.js} +1 -1
  42. package/public/assets/{pieDiagram-DEJITSTG-Ck2h10zd.js → pieDiagram-DEJITSTG-C6ng_7H-.js} +1 -1
  43. package/public/assets/{quadrantDiagram-34T5L4WZ-B1ZrruGm.js → quadrantDiagram-34T5L4WZ-tAb9lK_2.js} +1 -1
  44. package/public/assets/{requirementDiagram-MS252O5E-B8JKwgDX.js → requirementDiagram-MS252O5E-DO7a12OA.js} +1 -1
  45. package/public/assets/{sankeyDiagram-XADWPNL6-b442XRH8.js → sankeyDiagram-XADWPNL6-DjRA6Gx_.js} +1 -1
  46. package/public/assets/{sequenceDiagram-FGHM5R23-DY9IFA-F.js → sequenceDiagram-FGHM5R23-DmTXUCCq.js} +1 -1
  47. package/public/assets/{stateDiagram-FHFEXIEX-IGjwW9Pw.js → stateDiagram-FHFEXIEX-cpBtM_eo.js} +1 -1
  48. package/public/assets/stateDiagram-v2-QKLJ7IA2-xe4-E4yV.js +1 -0
  49. package/public/assets/{timeline-definition-GMOUNBTQ-B0JwGAT7.js → timeline-definition-GMOUNBTQ-BOPg9ppv.js} +1 -1
  50. package/public/assets/{vennDiagram-DHZGUBPP-DKLiidvh.js → vennDiagram-DHZGUBPP-C1wYmABu.js} +1 -1
  51. package/public/assets/{wardley-RL74JXVD-C1HnYfXI.js → wardley-RL74JXVD-BsEwEn5-.js} +1 -1
  52. package/public/assets/{wardleyDiagram-NUSXRM2D-BhbO6L2Q.js → wardleyDiagram-NUSXRM2D-Dt0kKYbS.js} +1 -1
  53. package/public/assets/{xychartDiagram-5P7HB3ND-CSZSmQ9N.js → xychartDiagram-5P7HB3ND-CqzYOgsH.js} +1 -1
  54. package/public/index.html +2 -2
  55. package/public/sw.js +1 -1
  56. package/src/server/index.js +107 -9
  57. package/src/server/preferences.js +280 -0
  58. package/src/server/routes.js +10 -1
  59. package/src/server/sessions.js +73 -6
  60. package/src/server/websocket.js +43 -22
  61. package/public/assets/channel-RrT8pJmM.js +0 -1
  62. package/public/assets/classDiagram-6PBFFD2Q-BaJ5lA7J.js +0 -1
  63. package/public/assets/classDiagram-v2-HSJHXN6E-BaJ5lA7J.js +0 -1
  64. package/public/assets/clone-BFbWnZ1G.js +0 -1
  65. package/public/assets/index-CK9vCujh.css +0 -32
  66. package/public/assets/index-Do67tk4s.js +0 -463
  67. package/public/assets/stateDiagram-v2-QKLJ7IA2-2xQCbec6.js +0 -1
package/public/sw.js CHANGED
@@ -1,2 +1,2 @@
1
1
  try{self["workbox:core:7.3.0"]&&_()}catch{}const G=(s,...e)=>{let t=s;return e.length>0&&(t+=` :: ${JSON.stringify(e)}`),t},Q=G;class h extends Error{constructor(e,t){const n=Q(e,t);super(n),this.name=e,this.details=t}}const d={googleAnalytics:"googleAnalytics",precache:"precache-v2",prefix:"workbox",runtime:"runtime",suffix:typeof registration<"u"?registration.scope:""},E=s=>[d.prefix,s,d.suffix].filter(e=>e&&e.length>0).join("-"),z=s=>{for(const e of Object.keys(d))s(e)},b={updateDetails:s=>{z(e=>{typeof s[e]=="string"&&(d[e]=s[e])})},getGoogleAnalyticsName:s=>s||E(d.googleAnalytics),getPrecacheName:s=>s||E(d.precache),getPrefix:()=>d.prefix,getRuntimeName:s=>s||E(d.runtime),getSuffix:()=>d.suffix};function v(s,e){const t=e();return s.waitUntil(t),t}try{self["workbox:precaching:7.3.0"]&&_()}catch{}const J="__WB_REVISION__";function X(s){if(!s)throw new h("add-to-cache-list-unexpected-type",{entry:s});if(typeof s=="string"){const i=new URL(s,location.href);return{cacheKey:i.href,url:i.href}}const{revision:e,url:t}=s;if(!t)throw new h("add-to-cache-list-unexpected-type",{entry:s});if(!e){const i=new URL(t,location.href);return{cacheKey:i.href,url:i.href}}const n=new URL(t,location.href),a=new URL(t,location.href);return n.searchParams.set(J,e),{cacheKey:n.href,url:a.href}}class Y{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){const a=t.originalRequest.url;n?this.notUpdatedURLs.push(a):this.updatedURLs.push(a)}return n}}}class Z{constructor({precacheController:e}){this.cacheKeyWillBeUsed=async({request:t,params:n})=>{const a=(n==null?void 0:n.cacheKey)||this._precacheController.getCacheKeyForURL(t.url);return a?new Request(a,{headers:t.headers}):t},this._precacheController=e}}let m;function ee(){if(m===void 0){const s=new Response("");if("body"in s)try{new Response(s.body),m=!0}catch{m=!1}m=!1}return m}async function te(s,e){let t=null;if(s.url&&(t=new URL(s.url).origin),t!==self.location.origin)throw new h("cross-origin-copy-response",{origin:t});const n=s.clone(),i={headers:new Headers(n.headers),status:n.status,statusText:n.statusText},r=ee()?n.body:await n.blob();return new Response(r,i)}const se=s=>new URL(String(s),location.href).href.replace(new RegExp(`^${location.origin}`),"");function M(s,e){const t=new URL(s);for(const n of e)t.searchParams.delete(n);return t.href}async function ne(s,e,t,n){const a=M(e.url,t);if(e.url===a)return s.match(e,n);const i=Object.assign(Object.assign({},n),{ignoreSearch:!0}),r=await s.keys(e,i);for(const c of r){const o=M(c.url,t);if(a===o)return s.match(c,n)}}class ae{constructor(){this.promise=new Promise((e,t)=>{this.resolve=e,this.reject=t})}}const B=new Set;async function ie(){for(const s of B)await s()}function re(s){return new Promise(e=>setTimeout(e,s))}try{self["workbox:strategies:7.3.0"]&&_()}catch{}function C(s){return typeof s=="string"?new Request(s):s}class ce{constructor(e,t){this._cacheKeys={},Object.assign(this,t),this.event=t.event,this._strategy=e,this._handlerDeferred=new ae,this._extendLifetimePromises=[],this._plugins=[...e.plugins],this._pluginStateMap=new Map;for(const n of this._plugins)this._pluginStateMap.set(n,{});this.event.waitUntil(this._handlerDeferred.promise)}async fetch(e){const{event:t}=this;let n=C(e);if(n.mode==="navigate"&&t instanceof FetchEvent&&t.preloadResponse){const r=await t.preloadResponse;if(r)return r}const a=this.hasCallback("fetchDidFail")?n.clone():null;try{for(const r of this.iterateCallbacks("requestWillFetch"))n=await r({request:n.clone(),event:t})}catch(r){if(r instanceof Error)throw new h("plugin-error-request-will-fetch",{thrownErrorMessage:r.message})}const i=n.clone();try{let r;r=await fetch(n,n.mode==="navigate"?void 0:this._strategy.fetchOptions);for(const c of this.iterateCallbacks("fetchDidSucceed"))r=await c({event:t,request:i,response:r});return r}catch(r){throw a&&await this.runCallbacks("fetchDidFail",{error:r,event:t,originalRequest:a.clone(),request:i.clone()}),r}}async fetchAndCachePut(e){const t=await this.fetch(e),n=t.clone();return this.waitUntil(this.cachePut(e,n)),t}async cacheMatch(e){const t=C(e);let n;const{cacheName:a,matchOptions:i}=this._strategy,r=await this.getCacheKey(t,"read"),c=Object.assign(Object.assign({},i),{cacheName:a});n=await caches.match(r,c);for(const o of this.iterateCallbacks("cachedResponseWillBeUsed"))n=await o({cacheName:a,matchOptions:i,cachedResponse:n,request:r,event:this.event})||void 0;return n}async cachePut(e,t){const n=C(e);await re(0);const a=await this.getCacheKey(n,"write");if(!t)throw new h("cache-put-with-no-response",{url:se(a.url)});const i=await this._ensureResponseSafeToCache(t);if(!i)return!1;const{cacheName:r,matchOptions:c}=this._strategy,o=await self.caches.open(r),l=this.hasCallback("cacheDidUpdate"),g=l?await ne(o,a.clone(),["__WB_REVISION__"],c):null;try{await o.put(a,l?i.clone():i)}catch(u){if(u instanceof Error)throw u.name==="QuotaExceededError"&&await ie(),u}for(const u of this.iterateCallbacks("cacheDidUpdate"))await u({cacheName:r,oldResponse:g,newResponse:i.clone(),request:a,event:this.event});return!0}async getCacheKey(e,t){const n=`${e.url} | ${t}`;if(!this._cacheKeys[n]){let a=e;for(const i of this.iterateCallbacks("cacheKeyWillBeUsed"))a=C(await i({mode:t,request:a,event:this.event,params:this.params}));this._cacheKeys[n]=a}return this._cacheKeys[n]}hasCallback(e){for(const t of this._strategy.plugins)if(e in t)return!0;return!1}async runCallbacks(e,t){for(const n of this.iterateCallbacks(e))await n(t)}*iterateCallbacks(e){for(const t of this._strategy.plugins)if(typeof t[e]=="function"){const n=this._pluginStateMap.get(t);yield i=>{const r=Object.assign(Object.assign({},i),{state:n});return t[e](r)}}}waitUntil(e){return this._extendLifetimePromises.push(e),e}async doneWaiting(){for(;this._extendLifetimePromises.length;){const e=this._extendLifetimePromises.splice(0),n=(await Promise.allSettled(e)).find(a=>a.status==="rejected");if(n)throw n.reason}}destroy(){this._handlerDeferred.resolve(null)}async _ensureResponseSafeToCache(e){let t=e,n=!1;for(const a of this.iterateCallbacks("cacheWillUpdate"))if(t=await a({request:this.request,response:t,event:this.event})||void 0,n=!0,!t)break;return n||t&&t.status!==200&&(t=void 0),t}}class W{constructor(e={}){this.cacheName=b.getRuntimeName(e.cacheName),this.plugins=e.plugins||[],this.fetchOptions=e.fetchOptions,this.matchOptions=e.matchOptions}handle(e){const[t]=this.handleAll(e);return t}handleAll(e){e instanceof FetchEvent&&(e={event:e,request:e.request});const t=e.event,n=typeof e.request=="string"?new Request(e.request):e.request,a="params"in e?e.params:void 0,i=new ce(this,{event:t,request:n,params:a}),r=this._getResponse(i,n,t),c=this._awaitComplete(r,i,n,t);return[r,c]}async _getResponse(e,t,n){await e.runCallbacks("handlerWillStart",{event:n,request:t});let a;try{if(a=await this._handle(t,e),!a||a.type==="error")throw new h("no-response",{url:t.url})}catch(i){if(i instanceof Error){for(const r of e.iterateCallbacks("handlerDidError"))if(a=await r({error:i,event:n,request:t}),a)break}if(!a)throw i}for(const i of e.iterateCallbacks("handlerWillRespond"))a=await i({event:n,request:t,response:a});return a}async _awaitComplete(e,t,n,a){let i,r;try{i=await e}catch{}try{await t.runCallbacks("handlerDidRespond",{event:a,request:n,response:i}),await t.doneWaiting()}catch(c){c instanceof Error&&(r=c)}if(await t.runCallbacks("handlerDidComplete",{event:a,request:n,response:i,error:r}),t.destroy(),r)throw r}}class p extends W{constructor(e={}){e.cacheName=b.getPrecacheName(e.cacheName),super(e),this._fallbackToNetwork=e.fallbackToNetwork!==!1,this.plugins.push(p.copyRedirectedCacheableResponsesPlugin)}async _handle(e,t){const n=await t.cacheMatch(e);return n||(t.event&&t.event.type==="install"?await this._handleInstall(e,t):await this._handleFetch(e,t))}async _handleFetch(e,t){let n;const a=t.params||{};if(this._fallbackToNetwork){const i=a.integrity,r=e.integrity,c=!r||r===i;n=await t.fetch(new Request(e,{integrity:e.mode!=="no-cors"?r||i:void 0})),i&&c&&e.mode!=="no-cors"&&(this._useDefaultCacheabilityPluginIfNeeded(),await t.cachePut(e,n.clone()))}else throw new h("missing-precache-entry",{cacheName:this.cacheName,url:e.url});return n}async _handleInstall(e,t){this._useDefaultCacheabilityPluginIfNeeded();const n=await t.fetch(e);if(!await t.cachePut(e,n.clone()))throw new h("bad-precaching-response",{url:e.url,status:n.status});return n}_useDefaultCacheabilityPluginIfNeeded(){let e=null,t=0;for(const[n,a]of this.plugins.entries())a!==p.copyRedirectedCacheableResponsesPlugin&&(a===p.defaultPrecacheCacheabilityPlugin&&(e=n),a.cacheWillUpdate&&t++);t===0?this.plugins.push(p.defaultPrecacheCacheabilityPlugin):t>1&&e!==null&&this.plugins.splice(e,1)}}p.defaultPrecacheCacheabilityPlugin={async cacheWillUpdate({response:s}){return!s||s.status>=400?null:s}};p.copyRedirectedCacheableResponsesPlugin={async cacheWillUpdate({response:s}){return s.redirected?await te(s):s}};class oe{constructor({cacheName:e,plugins:t=[],fallbackToNetwork:n=!0}={}){this._urlsToCacheKeys=new Map,this._urlsToCacheModes=new Map,this._cacheKeysToIntegrities=new Map,this._strategy=new p({cacheName:b.getPrecacheName(e),plugins:[...t,new Z({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),this._installAndActiveListenersAdded=!0)}addToCacheList(e){const t=[];for(const n of e){typeof n=="string"?t.push(n):n&&n.revision===void 0&&t.push(n.url);const{cacheKey:a,url:i}=X(n),r=typeof n!="string"&&n.revision?"reload":"default";if(this._urlsToCacheKeys.has(i)&&this._urlsToCacheKeys.get(i)!==a)throw new h("add-to-cache-list-conflicting-entries",{firstEntry:this._urlsToCacheKeys.get(i),secondEntry:a});if(typeof n!="string"&&n.integrity){if(this._cacheKeysToIntegrities.has(a)&&this._cacheKeysToIntegrities.get(a)!==n.integrity)throw new h("add-to-cache-list-conflicting-integrities",{url:i});this._cacheKeysToIntegrities.set(a,n.integrity)}if(this._urlsToCacheKeys.set(i,a),this._urlsToCacheModes.set(i,r),t.length>0){const c=`Workbox is precaching URLs without revision info: ${t.join(", ")}
2
- This is generally NOT safe. Learn more at https://bit.ly/wb-precache`;console.warn(c)}}}install(e){return v(e,async()=>{const t=new Y;this.strategy.plugins.push(t);for(const[i,r]of this._urlsToCacheKeys){const c=this._cacheKeysToIntegrities.get(r),o=this._urlsToCacheModes.get(i),l=new Request(i,{integrity:c,cache:o,credentials:"same-origin"});await Promise.all(this.strategy.handleAll({params:{cacheKey:r},request:l,event:e}))}const{updatedURLs:n,notUpdatedURLs:a}=t;return{updatedURLs:n,notUpdatedURLs:a}})}activate(e){return v(e,async()=>{const t=await self.caches.open(this.strategy.cacheName),n=await t.keys(),a=new Set(this._urlsToCacheKeys.values()),i=[];for(const r of n)a.has(r.url)||(await t.delete(r),i.push(r.url));return{deletedURLs:i}})}getURLsToCacheKeys(){return this._urlsToCacheKeys}getCachedURLs(){return[...this._urlsToCacheKeys.keys()]}getCacheKeyForURL(e){const t=new URL(e,location.href);return this._urlsToCacheKeys.get(t.href)}getIntegrityForCacheKey(e){return this._cacheKeysToIntegrities.get(e)}async matchPrecache(e){const 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){const t=this.getCacheKeyForURL(e);if(!t)throw new h("non-precached-url",{url:e});return n=>(n.request=new Request(e),n.params=Object.assign({cacheKey:t},n.params),this.strategy.handle(n))}}let D;const j=()=>(D||(D=new oe),D);try{self["workbox:routing:7.3.0"]&&_()}catch{}const F="GET",x=s=>s&&typeof s=="object"?s:{handle:s};class R{constructor(e,t,n=F){this.handler=x(t),this.match=e,this.method=n}setCatchHandler(e){this.catchHandler=x(e)}}class le extends R{constructor(e,t,n){const a=({url:i})=>{const r=e.exec(i.href);if(r&&!(i.origin!==location.origin&&r.index!==0))return r.slice(1)};super(a,t,n)}}class he{constructor(){this._routes=new Map,this._defaultHandlerMap=new Map}get routes(){return this._routes}addFetchListener(){self.addEventListener("fetch",(e=>{const{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"){const{payload:t}=e.data,n=Promise.all(t.urlsToCache.map(a=>{typeof a=="string"&&(a=[a]);const i=new Request(...a);return this.handleRequest({request:i,event:e})}));e.waitUntil(n),e.ports&&e.ports[0]&&n.then(()=>e.ports[0].postMessage(!0))}}))}handleRequest({request:e,event:t}){const n=new URL(e.url,location.href);if(!n.protocol.startsWith("http"))return;const a=n.origin===location.origin,{params:i,route:r}=this.findMatchingRoute({event:t,request:e,sameOrigin:a,url:n});let c=r&&r.handler;const o=e.method;if(!c&&this._defaultHandlerMap.has(o)&&(c=this._defaultHandlerMap.get(o)),!c)return;let l;try{l=c.handle({url:n,request:e,event:t,params:i})}catch(u){l=Promise.reject(u)}const g=r&&r.catchHandler;return l instanceof Promise&&(this._catchHandler||g)&&(l=l.catch(async u=>{if(g)try{return await g.handle({url:n,request:e,event:t,params:i})}catch(N){N instanceof Error&&(u=N)}if(this._catchHandler)return this._catchHandler.handle({url:n,request:e,event:t});throw u})),l}findMatchingRoute({url:e,sameOrigin:t,request:n,event:a}){const i=this._routes.get(n.method)||[];for(const r of i){let c;const o=r.match({url:e,sameOrigin:t,request:n,event:a});if(o)return c=o,(Array.isArray(c)&&c.length===0||o.constructor===Object&&Object.keys(o).length===0||typeof o=="boolean")&&(c=void 0),{route:r,params:c}}return{}}setDefaultHandler(e,t=F){this._defaultHandlerMap.set(t,x(e))}setCatchHandler(e){this._catchHandler=x(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 h("unregister-route-but-not-found-with-method",{method:e.method});const t=this._routes.get(e.method).indexOf(e);if(t>-1)this._routes.get(e.method).splice(t,1);else throw new h("unregister-route-route-not-registered")}}let y;const ue=()=>(y||(y=new he,y.addFetchListener(),y.addCacheListener()),y);function q(s,e,t){let n;if(typeof s=="string"){const i=new URL(s,location.href),r=({url:c})=>c.href===i.href;n=new R(r,e,t)}else if(s instanceof RegExp)n=new le(s,e,t);else if(typeof s=="function")n=new R(s,e,t);else if(s instanceof R)n=s;else throw new h("unsupported-route-type",{moduleName:"workbox-routing",funcName:"registerRoute",paramName:"capture"});return ue().registerRoute(n),n}function de(s,e=[]){for(const t of[...s.searchParams.keys()])e.some(n=>n.test(t))&&s.searchParams.delete(t);return s}function*fe(s,{ignoreURLParametersMatching:e=[/^utm_/,/^fbclid$/],directoryIndex:t="index.html",cleanURLs:n=!0,urlManipulation:a}={}){const i=new URL(s,location.href);i.hash="",yield i.href;const r=de(i,e);if(yield r.href,t&&r.pathname.endsWith("/")){const c=new URL(r.href);c.pathname+=t,yield c.href}if(n){const c=new URL(r.href);c.pathname+=".html",yield c.href}if(a){const c=a({url:i});for(const o of c)yield o.href}}class pe extends R{constructor(e,t){const n=({request:a})=>{const i=e.getURLsToCacheKeys();for(const r of fe(a.url,t)){const c=i.get(r);if(c){const o=e.getIntegrityForCacheKey(c);return{cacheKey:c,integrity:o}}}};super(n,e.strategy)}}function ge(s){const e=j(),t=new pe(e,s);q(t)}const me="-precache-",ye=async(s,e=me)=>{const n=(await self.caches.keys()).filter(a=>a.includes(e)&&a.includes(self.registration.scope)&&a!==s);return await Promise.all(n.map(a=>self.caches.delete(a))),n};function we(){self.addEventListener("activate",(s=>{const e=b.getPrecacheName();s.waitUntil(ye(e).then(t=>{}))}))}function _e(s){j().precache(s)}function Re(s,e){_e(s),ge(e)}class be extends W{async _handle(e,t){let n=await t.cacheMatch(e),a;if(!n)try{n=await t.fetchAndCachePut(e)}catch(i){i instanceof Error&&(a=i)}if(!n)throw new h("no-response",{url:e.url,error:a});return n}}function H(s){s.then(()=>{})}const Ce=(s,e)=>e.some(t=>s instanceof t);let A,K;function xe(){return A||(A=[IDBDatabase,IDBObjectStore,IDBIndex,IDBCursor,IDBTransaction])}function Ee(){return K||(K=[IDBCursor.prototype.advance,IDBCursor.prototype.continue,IDBCursor.prototype.continuePrimaryKey])}const V=new WeakMap,k=new WeakMap,$=new WeakMap,L=new WeakMap,P=new WeakMap;function De(s){const e=new Promise((t,n)=>{const a=()=>{s.removeEventListener("success",i),s.removeEventListener("error",r)},i=()=>{t(f(s.result)),a()},r=()=>{n(s.error),a()};s.addEventListener("success",i),s.addEventListener("error",r)});return e.then(t=>{t instanceof IDBCursor&&V.set(t,s)}).catch(()=>{}),P.set(e,s),e}function Le(s){if(k.has(s))return;const e=new Promise((t,n)=>{const a=()=>{s.removeEventListener("complete",i),s.removeEventListener("error",r),s.removeEventListener("abort",r)},i=()=>{t(),a()},r=()=>{n(s.error||new DOMException("AbortError","AbortError")),a()};s.addEventListener("complete",i),s.addEventListener("error",r),s.addEventListener("abort",r)});k.set(s,e)}let I={get(s,e,t){if(s instanceof IDBTransaction){if(e==="done")return k.get(s);if(e==="objectStoreNames")return s.objectStoreNames||$.get(s);if(e==="store")return t.objectStoreNames[1]?void 0:t.objectStore(t.objectStoreNames[0])}return f(s[e])},set(s,e,t){return s[e]=t,!0},has(s,e){return s instanceof IDBTransaction&&(e==="done"||e==="store")?!0:e in s}};function Ue(s){I=s(I)}function Te(s){return s===IDBDatabase.prototype.transaction&&!("objectStoreNames"in IDBTransaction.prototype)?function(e,...t){const n=s.call(U(this),e,...t);return $.set(n,e.sort?e.sort():[e]),f(n)}:Ee().includes(s)?function(...e){return s.apply(U(this),e),f(V.get(this))}:function(...e){return f(s.apply(U(this),e))}}function ke(s){return typeof s=="function"?Te(s):(s instanceof IDBTransaction&&Le(s),Ce(s,xe())?new Proxy(s,I):s)}function f(s){if(s instanceof IDBRequest)return De(s);if(L.has(s))return L.get(s);const e=ke(s);return e!==s&&(L.set(s,e),P.set(e,s)),e}const U=s=>P.get(s);function Ie(s,e,{blocked:t,upgrade:n,blocking:a,terminated:i}={}){const r=indexedDB.open(s,e),c=f(r);return n&&r.addEventListener("upgradeneeded",o=>{n(f(r.result),o.oldVersion,o.newVersion,f(r.transaction),o)}),t&&r.addEventListener("blocked",o=>t(o.oldVersion,o.newVersion,o)),c.then(o=>{i&&o.addEventListener("close",()=>i()),a&&o.addEventListener("versionchange",l=>a(l.oldVersion,l.newVersion,l))}).catch(()=>{}),c}function Pe(s,{blocked:e}={}){const t=indexedDB.deleteDatabase(s);return e&&t.addEventListener("blocked",n=>e(n.oldVersion,n)),f(t).then(()=>{})}const Ne=["get","getKey","getAll","getAllKeys","count"],ve=["put","add","delete","clear"],T=new Map;function O(s,e){if(!(s instanceof IDBDatabase&&!(e in s)&&typeof e=="string"))return;if(T.get(e))return T.get(e);const t=e.replace(/FromIndex$/,""),n=e!==t,a=ve.includes(t);if(!(t in(n?IDBIndex:IDBObjectStore).prototype)||!(a||Ne.includes(t)))return;const i=async function(r,...c){const o=this.transaction(r,a?"readwrite":"readonly");let l=o.store;return n&&(l=l.index(c.shift())),(await Promise.all([l[t](...c),a&&o.done]))[0]};return T.set(e,i),i}Ue(s=>({...s,get:(e,t,n)=>O(e,t)||s.get(e,t,n),has:(e,t)=>!!O(e,t)||s.has(e,t)}));try{self["workbox:expiration:7.3.0"]&&_()}catch{}const Me="workbox-expiration",w="cache-entries",S=s=>{const e=new URL(s,location.href);return e.hash="",e.href};class Ae{constructor(e){this._db=null,this._cacheName=e}_upgradeDb(e){const t=e.createObjectStore(w,{keyPath:"id"});t.createIndex("cacheName","cacheName",{unique:!1}),t.createIndex("timestamp","timestamp",{unique:!1})}_upgradeDbAndDeleteOldDbs(e){this._upgradeDb(e),this._cacheName&&Pe(this._cacheName)}async setTimestamp(e,t){e=S(e);const n={url:e,timestamp:t,cacheName:this._cacheName,id:this._getId(e)},i=(await this.getDb()).transaction(w,"readwrite",{durability:"relaxed"});await i.store.put(n),await i.done}async getTimestamp(e){const n=await(await this.getDb()).get(w,this._getId(e));return n==null?void 0:n.timestamp}async expireEntries(e,t){const n=await this.getDb();let a=await n.transaction(w).store.index("timestamp").openCursor(null,"prev");const i=[];let r=0;for(;a;){const o=a.value;o.cacheName===this._cacheName&&(e&&o.timestamp<e||t&&r>=t?i.push(a.value):r++),a=await a.continue()}const c=[];for(const o of i)await n.delete(w,o.id),c.push(o.url);return c}_getId(e){return this._cacheName+"|"+S(e)}async getDb(){return this._db||(this._db=await Ie(Me,1,{upgrade:this._upgradeDbAndDeleteOldDbs.bind(this)})),this._db}}class Ke{constructor(e,t={}){this._isRunning=!1,this._rerunRequested=!1,this._maxEntries=t.maxEntries,this._maxAgeSeconds=t.maxAgeSeconds,this._matchOptions=t.matchOptions,this._cacheName=e,this._timestampModel=new Ae(e)}async expireEntries(){if(this._isRunning){this._rerunRequested=!0;return}this._isRunning=!0;const e=this._maxAgeSeconds?Date.now()-this._maxAgeSeconds*1e3:0,t=await this._timestampModel.expireEntries(e,this._maxEntries),n=await self.caches.open(this._cacheName);for(const a of t)await n.delete(a,this._matchOptions);this._isRunning=!1,this._rerunRequested&&(this._rerunRequested=!1,H(this.expireEntries()))}async updateTimestamp(e){await this._timestampModel.setTimestamp(e,Date.now())}async isURLExpired(e){if(this._maxAgeSeconds){const t=await this._timestampModel.getTimestamp(e),n=Date.now()-this._maxAgeSeconds*1e3;return t!==void 0?t<n:!0}else return!1}async delete(){this._rerunRequested=!1,await this._timestampModel.expireEntries(1/0)}}function Oe(s){B.add(s)}class Se{constructor(e={}){this.cachedResponseWillBeUsed=async({event:t,request:n,cacheName:a,cachedResponse:i})=>{if(!i)return null;const r=this._isResponseDateFresh(i),c=this._getCacheExpiration(a);H(c.expireEntries());const o=c.updateTimestamp(n.url);if(t)try{t.waitUntil(o)}catch{}return r?i:null},this.cacheDidUpdate=async({cacheName:t,request:n})=>{const a=this._getCacheExpiration(t);await a.updateTimestamp(n.url),await a.expireEntries()},this._config=e,this._maxAgeSeconds=e.maxAgeSeconds,this._cacheExpirations=new Map,e.purgeOnQuotaError&&Oe(()=>this.deleteCacheAndMetadata())}_getCacheExpiration(e){if(e===b.getRuntimeName())throw new h("expire-custom-caches-only");let t=this._cacheExpirations.get(e);return t||(t=new Ke(e,this._config),this._cacheExpirations.set(e,t)),t}_isResponseDateFresh(e){if(!this._maxAgeSeconds)return!0;const t=this._getDateHeaderTimestamp(e);if(t===null)return!0;const n=Date.now();return t>=n-this._maxAgeSeconds*1e3}_getDateHeaderTimestamp(e){if(!e.headers.has("date"))return null;const t=e.headers.get("date"),a=new Date(t).getTime();return isNaN(a)?null:a}async deleteCacheAndMetadata(){for(const[e,t]of this._cacheExpirations)await self.caches.delete(e),await t.delete();this._cacheExpirations=new Map}}Re([{"revision":"1872c500de691dce40960bb85481de07","url":"registerSW.js"},{"revision":"0f93805414c783a30b72e0ce88f2c7c4","url":"icons/icon.svg"},{"revision":"5dee5ddcd8e8533edb348118334d9756","url":"icons/icon-512.png"},{"revision":"75734e6b38a556148f51b9e10eeb01c8","url":"icons/icon-192.png"},{"revision":"0362e0eb00358646fd4c97674945adba","url":"icons/icon-180.png"},{"revision":null,"url":"assets/xychartDiagram-5P7HB3ND-CSZSmQ9N.js"},{"revision":null,"url":"assets/wardleyDiagram-NUSXRM2D-BhbO6L2Q.js"},{"revision":null,"url":"assets/wardley-RL74JXVD-C1HnYfXI.js"},{"revision":null,"url":"assets/vennDiagram-DHZGUBPP-DKLiidvh.js"},{"revision":null,"url":"assets/timeline-definition-GMOUNBTQ-B0JwGAT7.js"},{"revision":null,"url":"assets/stateDiagram-v2-QKLJ7IA2-2xQCbec6.js"},{"revision":null,"url":"assets/stateDiagram-FHFEXIEX-IGjwW9Pw.js"},{"revision":null,"url":"assets/sequenceDiagram-FGHM5R23-DY9IFA-F.js"},{"revision":null,"url":"assets/sankeyDiagram-XADWPNL6-b442XRH8.js"},{"revision":null,"url":"assets/requirementDiagram-MS252O5E-B8JKwgDX.js"},{"revision":null,"url":"assets/quadrantDiagram-34T5L4WZ-B1ZrruGm.js"},{"revision":null,"url":"assets/pieDiagram-DEJITSTG-Ck2h10zd.js"},{"revision":null,"url":"assets/ordinal-Cboi1Yqb.js"},{"revision":null,"url":"assets/mindmap-definition-QFDTVHPH-Swj4jh3Z.js"},{"revision":null,"url":"assets/linear-Bw9XpXJW.js"},{"revision":null,"url":"assets/layout-CpGcFwHw.js"},{"revision":null,"url":"assets/katex-B1X10hvy.js"},{"revision":null,"url":"assets/kanban-definition-6JOO6SKY-Cx3Plwl2.js"},{"revision":null,"url":"assets/journeyDiagram-VCZTEJTY-CUv-hh0I.js"},{"revision":null,"url":"assets/ishikawaDiagram-UXIWVN3A-D_2gGEMI.js"},{"revision":null,"url":"assets/init-Gi6I4Gst.js"},{"revision":null,"url":"assets/infoDiagram-42DDH7IO-sXN9f7L8.js"},{"revision":null,"url":"assets/index-Do67tk4s.js"},{"revision":null,"url":"assets/index-CK9vCujh.css"},{"revision":null,"url":"assets/graph-C-meUi4Z.js"},{"revision":null,"url":"assets/gitGraphDiagram-UUTBAWPF-CpEmQcDN.js"},{"revision":null,"url":"assets/ganttDiagram-T4ZO3ILL-DJrwFHlO.js"},{"revision":null,"url":"assets/flowDiagram-DWJPFMVM-DpRY38iu.js"},{"revision":null,"url":"assets/erDiagram-SMLLAGMA-BCpXZJei.js"},{"revision":null,"url":"assets/diagram-TYMM5635-CAMd8OZP.js"},{"revision":null,"url":"assets/diagram-MMDJMWI5-CWwOGFbE.js"},{"revision":null,"url":"assets/diagram-G4DWMVQ6-CIKqapDb.js"},{"revision":null,"url":"assets/diagram-5BDNPKRD-DfSZsVAD.js"},{"revision":null,"url":"assets/defaultLocale-DX6XiGOO.js"},{"revision":null,"url":"assets/dagre-KV5264BT-CDXKs5yo.js"},{"revision":null,"url":"assets/cytoscape.esm-BQaXIfA_.js"},{"revision":null,"url":"assets/cose-bilkent-S5V4N54A-BW1nNLLk.js"},{"revision":null,"url":"assets/clone-BFbWnZ1G.js"},{"revision":null,"url":"assets/classDiagram-v2-HSJHXN6E-BaJ5lA7J.js"},{"revision":null,"url":"assets/classDiagram-6PBFFD2Q-BaJ5lA7J.js"},{"revision":null,"url":"assets/chunk-YZCP3GAM-D4sWv3YN.js"},{"revision":null,"url":"assets/chunk-QZHKN3VN-CBMUqRwi.js"},{"revision":null,"url":"assets/chunk-OYMX7WX6-Dl4p17KI.js"},{"revision":null,"url":"assets/chunk-FMBD7UC4-BkeSsxqP.js"},{"revision":null,"url":"assets/chunk-EDXVE4YY-CtqMCguR.js"},{"revision":null,"url":"assets/chunk-55IACEB6-DLtS129C.js"},{"revision":null,"url":"assets/chunk-4TB4RGXK-D5KX6fMH.js"},{"revision":null,"url":"assets/chunk-4BX2VUAB-BksLrXVs.js"},{"revision":null,"url":"assets/channel-RrT8pJmM.js"},{"revision":null,"url":"assets/c4Diagram-AHTNJAMY-BATf25U2.js"},{"revision":null,"url":"assets/blockDiagram-DXYQGD6D-DrElm25z.js"},{"revision":null,"url":"assets/architectureDiagram-Q4EWVU46-ByQ5j_Jw.js"},{"revision":null,"url":"assets/arc-BxuxWgHK.js"},{"revision":null,"url":"assets/_baseUniq-DveOFRWk.js"},{"revision":null,"url":"assets/_basePickBy-Q45O29Rp.js"},{"revision":"0362e0eb00358646fd4c97674945adba","url":"icons/icon-180.png"},{"revision":"75734e6b38a556148f51b9e10eeb01c8","url":"icons/icon-192.png"},{"revision":"5dee5ddcd8e8533edb348118334d9756","url":"icons/icon-512.png"},{"revision":"b0747e8d6cde0d45e05be8e5c731729e","url":"manifest.webmanifest"}]);we();q(({url:s})=>s.hostname==="cdn.jsdelivr.net"&&s.pathname.endsWith(".ttf"),new be({cacheName:"termbeam-fonts",plugins:[new Se({maxEntries:5,maxAgeSeconds:365*24*60*60})]}));self.addEventListener("message",s=>{var e;((e=s.data)==null?void 0:e.type)==="CLEAR_CACHES"&&caches.keys().then(t=>{for(const n of t)n!=="workbox-precache-v2"&&caches.delete(n)})});self.addEventListener("install",()=>{self.skipWaiting()});self.addEventListener("activate",s=>{s.waitUntil(caches.delete("termbeam-navigation").then(()=>self.clients.claim()))});self.addEventListener("push",s=>{let e={title:"Command finished",body:"TermBeam"};if(s.data)try{e=s.data.json()}catch{}s.waitUntil(self.clients.matchAll({type:"window",includeUncontrolled:!0}).then(async t=>{if(t.some(i=>i.url.includes(self.location.origin)&&i.focused))return;const a={body:e.body||"A command has completed",icon:"/icons/icon-192.png",badge:"/icons/icon-192.png",tag:e.tag||"termbeam-cmd",renotify:!0,data:{url:e.url||"/",type:e.type||"command-complete",sessionId:e.sessionId},vibrate:[200,100,200]};try{await self.navigator.setAppBadge(1)}catch{}return self.registration.showNotification(e.title||"Command finished",a)}))});self.addEventListener("notificationclick",s=>{s.notification.close();try{self.navigator.clearAppBadge()}catch{}const e=s.notification.data||{},t=e.url||"/",n=e.type||"";s.waitUntil(self.clients.matchAll({type:"window",includeUncontrolled:!0}).then(a=>{for(const i of a)if(i.url.includes(self.location.origin)&&"focus"in i)return n&&i.postMessage({type:"NOTIFICATION_CLICKED",notificationType:n,sessionId:e.sessionId}),i.focus();return self.clients.openWindow(t)}))});
2
+ This is generally NOT safe. Learn more at https://bit.ly/wb-precache`;console.warn(c)}}}install(e){return v(e,async()=>{const t=new Y;this.strategy.plugins.push(t);for(const[i,r]of this._urlsToCacheKeys){const c=this._cacheKeysToIntegrities.get(r),o=this._urlsToCacheModes.get(i),l=new Request(i,{integrity:c,cache:o,credentials:"same-origin"});await Promise.all(this.strategy.handleAll({params:{cacheKey:r},request:l,event:e}))}const{updatedURLs:n,notUpdatedURLs:a}=t;return{updatedURLs:n,notUpdatedURLs:a}})}activate(e){return v(e,async()=>{const t=await self.caches.open(this.strategy.cacheName),n=await t.keys(),a=new Set(this._urlsToCacheKeys.values()),i=[];for(const r of n)a.has(r.url)||(await t.delete(r),i.push(r.url));return{deletedURLs:i}})}getURLsToCacheKeys(){return this._urlsToCacheKeys}getCachedURLs(){return[...this._urlsToCacheKeys.keys()]}getCacheKeyForURL(e){const t=new URL(e,location.href);return this._urlsToCacheKeys.get(t.href)}getIntegrityForCacheKey(e){return this._cacheKeysToIntegrities.get(e)}async matchPrecache(e){const 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){const t=this.getCacheKeyForURL(e);if(!t)throw new h("non-precached-url",{url:e});return n=>(n.request=new Request(e),n.params=Object.assign({cacheKey:t},n.params),this.strategy.handle(n))}}let D;const j=()=>(D||(D=new oe),D);try{self["workbox:routing:7.3.0"]&&_()}catch{}const F="GET",x=s=>s&&typeof s=="object"?s:{handle:s};class R{constructor(e,t,n=F){this.handler=x(t),this.match=e,this.method=n}setCatchHandler(e){this.catchHandler=x(e)}}class le extends R{constructor(e,t,n){const a=({url:i})=>{const r=e.exec(i.href);if(r&&!(i.origin!==location.origin&&r.index!==0))return r.slice(1)};super(a,t,n)}}class he{constructor(){this._routes=new Map,this._defaultHandlerMap=new Map}get routes(){return this._routes}addFetchListener(){self.addEventListener("fetch",(e=>{const{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"){const{payload:t}=e.data,n=Promise.all(t.urlsToCache.map(a=>{typeof a=="string"&&(a=[a]);const i=new Request(...a);return this.handleRequest({request:i,event:e})}));e.waitUntil(n),e.ports&&e.ports[0]&&n.then(()=>e.ports[0].postMessage(!0))}}))}handleRequest({request:e,event:t}){const n=new URL(e.url,location.href);if(!n.protocol.startsWith("http"))return;const a=n.origin===location.origin,{params:i,route:r}=this.findMatchingRoute({event:t,request:e,sameOrigin:a,url:n});let c=r&&r.handler;const o=e.method;if(!c&&this._defaultHandlerMap.has(o)&&(c=this._defaultHandlerMap.get(o)),!c)return;let l;try{l=c.handle({url:n,request:e,event:t,params:i})}catch(u){l=Promise.reject(u)}const g=r&&r.catchHandler;return l instanceof Promise&&(this._catchHandler||g)&&(l=l.catch(async u=>{if(g)try{return await g.handle({url:n,request:e,event:t,params:i})}catch(N){N instanceof Error&&(u=N)}if(this._catchHandler)return this._catchHandler.handle({url:n,request:e,event:t});throw u})),l}findMatchingRoute({url:e,sameOrigin:t,request:n,event:a}){const i=this._routes.get(n.method)||[];for(const r of i){let c;const o=r.match({url:e,sameOrigin:t,request:n,event:a});if(o)return c=o,(Array.isArray(c)&&c.length===0||o.constructor===Object&&Object.keys(o).length===0||typeof o=="boolean")&&(c=void 0),{route:r,params:c}}return{}}setDefaultHandler(e,t=F){this._defaultHandlerMap.set(t,x(e))}setCatchHandler(e){this._catchHandler=x(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 h("unregister-route-but-not-found-with-method",{method:e.method});const t=this._routes.get(e.method).indexOf(e);if(t>-1)this._routes.get(e.method).splice(t,1);else throw new h("unregister-route-route-not-registered")}}let y;const ue=()=>(y||(y=new he,y.addFetchListener(),y.addCacheListener()),y);function q(s,e,t){let n;if(typeof s=="string"){const i=new URL(s,location.href),r=({url:c})=>c.href===i.href;n=new R(r,e,t)}else if(s instanceof RegExp)n=new le(s,e,t);else if(typeof s=="function")n=new R(s,e,t);else if(s instanceof R)n=s;else throw new h("unsupported-route-type",{moduleName:"workbox-routing",funcName:"registerRoute",paramName:"capture"});return ue().registerRoute(n),n}function de(s,e=[]){for(const t of[...s.searchParams.keys()])e.some(n=>n.test(t))&&s.searchParams.delete(t);return s}function*fe(s,{ignoreURLParametersMatching:e=[/^utm_/,/^fbclid$/],directoryIndex:t="index.html",cleanURLs:n=!0,urlManipulation:a}={}){const i=new URL(s,location.href);i.hash="",yield i.href;const r=de(i,e);if(yield r.href,t&&r.pathname.endsWith("/")){const c=new URL(r.href);c.pathname+=t,yield c.href}if(n){const c=new URL(r.href);c.pathname+=".html",yield c.href}if(a){const c=a({url:i});for(const o of c)yield o.href}}class pe extends R{constructor(e,t){const n=({request:a})=>{const i=e.getURLsToCacheKeys();for(const r of fe(a.url,t)){const c=i.get(r);if(c){const o=e.getIntegrityForCacheKey(c);return{cacheKey:c,integrity:o}}}};super(n,e.strategy)}}function ge(s){const e=j(),t=new pe(e,s);q(t)}const me="-precache-",ye=async(s,e=me)=>{const n=(await self.caches.keys()).filter(a=>a.includes(e)&&a.includes(self.registration.scope)&&a!==s);return await Promise.all(n.map(a=>self.caches.delete(a))),n};function we(){self.addEventListener("activate",(s=>{const e=b.getPrecacheName();s.waitUntil(ye(e).then(t=>{}))}))}function _e(s){j().precache(s)}function Re(s,e){_e(s),ge(e)}class be extends W{async _handle(e,t){let n=await t.cacheMatch(e),a;if(!n)try{n=await t.fetchAndCachePut(e)}catch(i){i instanceof Error&&(a=i)}if(!n)throw new h("no-response",{url:e.url,error:a});return n}}function H(s){s.then(()=>{})}const Ce=(s,e)=>e.some(t=>s instanceof t);let A,K;function xe(){return A||(A=[IDBDatabase,IDBObjectStore,IDBIndex,IDBCursor,IDBTransaction])}function Ee(){return K||(K=[IDBCursor.prototype.advance,IDBCursor.prototype.continue,IDBCursor.prototype.continuePrimaryKey])}const V=new WeakMap,k=new WeakMap,$=new WeakMap,L=new WeakMap,P=new WeakMap;function De(s){const e=new Promise((t,n)=>{const a=()=>{s.removeEventListener("success",i),s.removeEventListener("error",r)},i=()=>{t(f(s.result)),a()},r=()=>{n(s.error),a()};s.addEventListener("success",i),s.addEventListener("error",r)});return e.then(t=>{t instanceof IDBCursor&&V.set(t,s)}).catch(()=>{}),P.set(e,s),e}function Le(s){if(k.has(s))return;const e=new Promise((t,n)=>{const a=()=>{s.removeEventListener("complete",i),s.removeEventListener("error",r),s.removeEventListener("abort",r)},i=()=>{t(),a()},r=()=>{n(s.error||new DOMException("AbortError","AbortError")),a()};s.addEventListener("complete",i),s.addEventListener("error",r),s.addEventListener("abort",r)});k.set(s,e)}let I={get(s,e,t){if(s instanceof IDBTransaction){if(e==="done")return k.get(s);if(e==="objectStoreNames")return s.objectStoreNames||$.get(s);if(e==="store")return t.objectStoreNames[1]?void 0:t.objectStore(t.objectStoreNames[0])}return f(s[e])},set(s,e,t){return s[e]=t,!0},has(s,e){return s instanceof IDBTransaction&&(e==="done"||e==="store")?!0:e in s}};function Ue(s){I=s(I)}function Te(s){return s===IDBDatabase.prototype.transaction&&!("objectStoreNames"in IDBTransaction.prototype)?function(e,...t){const n=s.call(U(this),e,...t);return $.set(n,e.sort?e.sort():[e]),f(n)}:Ee().includes(s)?function(...e){return s.apply(U(this),e),f(V.get(this))}:function(...e){return f(s.apply(U(this),e))}}function ke(s){return typeof s=="function"?Te(s):(s instanceof IDBTransaction&&Le(s),Ce(s,xe())?new Proxy(s,I):s)}function f(s){if(s instanceof IDBRequest)return De(s);if(L.has(s))return L.get(s);const e=ke(s);return e!==s&&(L.set(s,e),P.set(e,s)),e}const U=s=>P.get(s);function Ie(s,e,{blocked:t,upgrade:n,blocking:a,terminated:i}={}){const r=indexedDB.open(s,e),c=f(r);return n&&r.addEventListener("upgradeneeded",o=>{n(f(r.result),o.oldVersion,o.newVersion,f(r.transaction),o)}),t&&r.addEventListener("blocked",o=>t(o.oldVersion,o.newVersion,o)),c.then(o=>{i&&o.addEventListener("close",()=>i()),a&&o.addEventListener("versionchange",l=>a(l.oldVersion,l.newVersion,l))}).catch(()=>{}),c}function Pe(s,{blocked:e}={}){const t=indexedDB.deleteDatabase(s);return e&&t.addEventListener("blocked",n=>e(n.oldVersion,n)),f(t).then(()=>{})}const Ne=["get","getKey","getAll","getAllKeys","count"],ve=["put","add","delete","clear"],T=new Map;function O(s,e){if(!(s instanceof IDBDatabase&&!(e in s)&&typeof e=="string"))return;if(T.get(e))return T.get(e);const t=e.replace(/FromIndex$/,""),n=e!==t,a=ve.includes(t);if(!(t in(n?IDBIndex:IDBObjectStore).prototype)||!(a||Ne.includes(t)))return;const i=async function(r,...c){const o=this.transaction(r,a?"readwrite":"readonly");let l=o.store;return n&&(l=l.index(c.shift())),(await Promise.all([l[t](...c),a&&o.done]))[0]};return T.set(e,i),i}Ue(s=>({...s,get:(e,t,n)=>O(e,t)||s.get(e,t,n),has:(e,t)=>!!O(e,t)||s.has(e,t)}));try{self["workbox:expiration:7.3.0"]&&_()}catch{}const Me="workbox-expiration",w="cache-entries",S=s=>{const e=new URL(s,location.href);return e.hash="",e.href};class Ae{constructor(e){this._db=null,this._cacheName=e}_upgradeDb(e){const t=e.createObjectStore(w,{keyPath:"id"});t.createIndex("cacheName","cacheName",{unique:!1}),t.createIndex("timestamp","timestamp",{unique:!1})}_upgradeDbAndDeleteOldDbs(e){this._upgradeDb(e),this._cacheName&&Pe(this._cacheName)}async setTimestamp(e,t){e=S(e);const n={url:e,timestamp:t,cacheName:this._cacheName,id:this._getId(e)},i=(await this.getDb()).transaction(w,"readwrite",{durability:"relaxed"});await i.store.put(n),await i.done}async getTimestamp(e){const n=await(await this.getDb()).get(w,this._getId(e));return n==null?void 0:n.timestamp}async expireEntries(e,t){const n=await this.getDb();let a=await n.transaction(w).store.index("timestamp").openCursor(null,"prev");const i=[];let r=0;for(;a;){const o=a.value;o.cacheName===this._cacheName&&(e&&o.timestamp<e||t&&r>=t?i.push(a.value):r++),a=await a.continue()}const c=[];for(const o of i)await n.delete(w,o.id),c.push(o.url);return c}_getId(e){return this._cacheName+"|"+S(e)}async getDb(){return this._db||(this._db=await Ie(Me,1,{upgrade:this._upgradeDbAndDeleteOldDbs.bind(this)})),this._db}}class Ke{constructor(e,t={}){this._isRunning=!1,this._rerunRequested=!1,this._maxEntries=t.maxEntries,this._maxAgeSeconds=t.maxAgeSeconds,this._matchOptions=t.matchOptions,this._cacheName=e,this._timestampModel=new Ae(e)}async expireEntries(){if(this._isRunning){this._rerunRequested=!0;return}this._isRunning=!0;const e=this._maxAgeSeconds?Date.now()-this._maxAgeSeconds*1e3:0,t=await this._timestampModel.expireEntries(e,this._maxEntries),n=await self.caches.open(this._cacheName);for(const a of t)await n.delete(a,this._matchOptions);this._isRunning=!1,this._rerunRequested&&(this._rerunRequested=!1,H(this.expireEntries()))}async updateTimestamp(e){await this._timestampModel.setTimestamp(e,Date.now())}async isURLExpired(e){if(this._maxAgeSeconds){const t=await this._timestampModel.getTimestamp(e),n=Date.now()-this._maxAgeSeconds*1e3;return t!==void 0?t<n:!0}else return!1}async delete(){this._rerunRequested=!1,await this._timestampModel.expireEntries(1/0)}}function Oe(s){B.add(s)}class Se{constructor(e={}){this.cachedResponseWillBeUsed=async({event:t,request:n,cacheName:a,cachedResponse:i})=>{if(!i)return null;const r=this._isResponseDateFresh(i),c=this._getCacheExpiration(a);H(c.expireEntries());const o=c.updateTimestamp(n.url);if(t)try{t.waitUntil(o)}catch{}return r?i:null},this.cacheDidUpdate=async({cacheName:t,request:n})=>{const a=this._getCacheExpiration(t);await a.updateTimestamp(n.url),await a.expireEntries()},this._config=e,this._maxAgeSeconds=e.maxAgeSeconds,this._cacheExpirations=new Map,e.purgeOnQuotaError&&Oe(()=>this.deleteCacheAndMetadata())}_getCacheExpiration(e){if(e===b.getRuntimeName())throw new h("expire-custom-caches-only");let t=this._cacheExpirations.get(e);return t||(t=new Ke(e,this._config),this._cacheExpirations.set(e,t)),t}_isResponseDateFresh(e){if(!this._maxAgeSeconds)return!0;const t=this._getDateHeaderTimestamp(e);if(t===null)return!0;const n=Date.now();return t>=n-this._maxAgeSeconds*1e3}_getDateHeaderTimestamp(e){if(!e.headers.has("date"))return null;const t=e.headers.get("date"),a=new Date(t).getTime();return isNaN(a)?null:a}async deleteCacheAndMetadata(){for(const[e,t]of this._cacheExpirations)await self.caches.delete(e),await t.delete();this._cacheExpirations=new Map}}Re([{"revision":"1872c500de691dce40960bb85481de07","url":"registerSW.js"},{"revision":"0f93805414c783a30b72e0ce88f2c7c4","url":"icons/icon.svg"},{"revision":"5dee5ddcd8e8533edb348118334d9756","url":"icons/icon-512.png"},{"revision":"75734e6b38a556148f51b9e10eeb01c8","url":"icons/icon-192.png"},{"revision":"0362e0eb00358646fd4c97674945adba","url":"icons/icon-180.png"},{"revision":null,"url":"assets/xychartDiagram-5P7HB3ND-CqzYOgsH.js"},{"revision":null,"url":"assets/wardleyDiagram-NUSXRM2D-Dt0kKYbS.js"},{"revision":null,"url":"assets/wardley-RL74JXVD-BsEwEn5-.js"},{"revision":null,"url":"assets/vennDiagram-DHZGUBPP-C1wYmABu.js"},{"revision":null,"url":"assets/timeline-definition-GMOUNBTQ-BOPg9ppv.js"},{"revision":null,"url":"assets/stateDiagram-v2-QKLJ7IA2-xe4-E4yV.js"},{"revision":null,"url":"assets/stateDiagram-FHFEXIEX-cpBtM_eo.js"},{"revision":null,"url":"assets/sequenceDiagram-FGHM5R23-DmTXUCCq.js"},{"revision":null,"url":"assets/sankeyDiagram-XADWPNL6-DjRA6Gx_.js"},{"revision":null,"url":"assets/requirementDiagram-MS252O5E-DO7a12OA.js"},{"revision":null,"url":"assets/quadrantDiagram-34T5L4WZ-tAb9lK_2.js"},{"revision":null,"url":"assets/pieDiagram-DEJITSTG-C6ng_7H-.js"},{"revision":null,"url":"assets/ordinal-Cboi1Yqb.js"},{"revision":null,"url":"assets/mindmap-definition-QFDTVHPH-CwCTVEhc.js"},{"revision":null,"url":"assets/linear-wWmlfvSH.js"},{"revision":null,"url":"assets/layout-BQRZ4_Il.js"},{"revision":null,"url":"assets/katex-B1X10hvy.js"},{"revision":null,"url":"assets/kanban-definition-6JOO6SKY-C_Sj08cN.js"},{"revision":null,"url":"assets/journeyDiagram-VCZTEJTY-CKm_djiT.js"},{"revision":null,"url":"assets/ishikawaDiagram-UXIWVN3A-Buhn0bpg.js"},{"revision":null,"url":"assets/init-Gi6I4Gst.js"},{"revision":null,"url":"assets/infoDiagram-42DDH7IO-JWuRQxiM.js"},{"revision":null,"url":"assets/index-CTMMjkRB.css"},{"revision":null,"url":"assets/index-Bap8dJMF.js"},{"revision":null,"url":"assets/graph-DGYmOqb_.js"},{"revision":null,"url":"assets/gitGraphDiagram-UUTBAWPF-CmtVcokM.js"},{"revision":null,"url":"assets/ganttDiagram-T4ZO3ILL-C-JweidK.js"},{"revision":null,"url":"assets/flowDiagram-DWJPFMVM-BebPntFK.js"},{"revision":null,"url":"assets/erDiagram-SMLLAGMA-COlP9qIO.js"},{"revision":null,"url":"assets/diagram-TYMM5635-CilekWFu.js"},{"revision":null,"url":"assets/diagram-MMDJMWI5-Da91D4Jv.js"},{"revision":null,"url":"assets/diagram-G4DWMVQ6-BjcXJ3-Q.js"},{"revision":null,"url":"assets/diagram-5BDNPKRD-KXbdPm8R.js"},{"revision":null,"url":"assets/defaultLocale-DX6XiGOO.js"},{"revision":null,"url":"assets/dagre-KV5264BT-CqYqJqNz.js"},{"revision":null,"url":"assets/cytoscape.esm-BQaXIfA_.js"},{"revision":null,"url":"assets/cose-bilkent-S5V4N54A-CdRKoWQm.js"},{"revision":null,"url":"assets/clone-D-YD4Hfu.js"},{"revision":null,"url":"assets/classDiagram-v2-HSJHXN6E-DHBlyv18.js"},{"revision":null,"url":"assets/classDiagram-6PBFFD2Q-DHBlyv18.js"},{"revision":null,"url":"assets/chunk-YZCP3GAM-BFo64bhH.js"},{"revision":null,"url":"assets/chunk-QZHKN3VN-BOkHXJLa.js"},{"revision":null,"url":"assets/chunk-OYMX7WX6-Zxqgp5PC.js"},{"revision":null,"url":"assets/chunk-FMBD7UC4-54Zrs_SG.js"},{"revision":null,"url":"assets/chunk-EDXVE4YY-BqButrNC.js"},{"revision":null,"url":"assets/chunk-55IACEB6-BhvwRyIq.js"},{"revision":null,"url":"assets/chunk-4TB4RGXK-C-BdMhbz.js"},{"revision":null,"url":"assets/chunk-4BX2VUAB-m4pupC7U.js"},{"revision":null,"url":"assets/channel-a7jr0in8.js"},{"revision":null,"url":"assets/c4Diagram-AHTNJAMY-BuvkD-Hg.js"},{"revision":null,"url":"assets/blockDiagram-DXYQGD6D-EtZXyKmn.js"},{"revision":null,"url":"assets/architectureDiagram-Q4EWVU46-MEcrgEqw.js"},{"revision":null,"url":"assets/arc-CHQhvBsi.js"},{"revision":null,"url":"assets/_baseUniq-8tmjrEbB.js"},{"revision":null,"url":"assets/_basePickBy-DyywFBt7.js"},{"revision":"0362e0eb00358646fd4c97674945adba","url":"icons/icon-180.png"},{"revision":"75734e6b38a556148f51b9e10eeb01c8","url":"icons/icon-192.png"},{"revision":"5dee5ddcd8e8533edb348118334d9756","url":"icons/icon-512.png"},{"revision":"b0747e8d6cde0d45e05be8e5c731729e","url":"manifest.webmanifest"}]);we();q(({url:s})=>s.hostname==="cdn.jsdelivr.net"&&s.pathname.endsWith(".ttf"),new be({cacheName:"termbeam-fonts",plugins:[new Se({maxEntries:5,maxAgeSeconds:365*24*60*60})]}));self.addEventListener("message",s=>{var e;((e=s.data)==null?void 0:e.type)==="CLEAR_CACHES"&&caches.keys().then(t=>{for(const n of t)n!=="workbox-precache-v2"&&caches.delete(n)})});self.addEventListener("install",()=>{self.skipWaiting()});self.addEventListener("activate",s=>{s.waitUntil(caches.delete("termbeam-navigation").then(()=>self.clients.claim()))});self.addEventListener("push",s=>{let e={title:"Command finished",body:"TermBeam"};if(s.data)try{e=s.data.json()}catch{}s.waitUntil(self.clients.matchAll({type:"window",includeUncontrolled:!0}).then(async t=>{if(t.some(i=>i.url.includes(self.location.origin)&&i.focused))return;const a={body:e.body||"A command has completed",icon:"/icons/icon-192.png",badge:"/icons/icon-192.png",tag:e.tag||"termbeam-cmd",renotify:!0,data:{url:e.url||"/",type:e.type||"command-complete",sessionId:e.sessionId},vibrate:[200,100,200]};try{await self.navigator.setAppBadge(1)}catch{}return self.registration.showNotification(e.title||"Command finished",a)}))});self.addEventListener("notificationclick",s=>{s.notification.close();try{self.navigator.clearAppBadge()}catch{}const e=s.notification.data||{},t=e.url||"/",n=e.type||"";s.waitUntil(self.clients.matchAll({type:"window",includeUncontrolled:!0}).then(a=>{for(const i of a)if(i.url.includes(self.location.origin)&&"focus"in i)return n&&i.postMessage({type:"NOTIFICATION_CLICKED",notificationType:n,sessionId:e.sessionId}),i.focus();return self.clients.openWindow(t)}))});
@@ -24,6 +24,7 @@ const { createPreviewProxy } = require('./preview');
24
24
  const { writeConnectionConfig, removeConnectionConfig } = require('../cli/resume');
25
25
  const { checkForUpdate, detectInstallMethod } = require('../utils/update-check');
26
26
  const { PushManager } = require('./push');
27
+ const { readPreferences } = require('./preferences');
27
28
 
28
29
  // --- Helpers ---
29
30
  function getLocalIP() {
@@ -60,7 +61,8 @@ function createTermBeamServer(overrides = {}) {
60
61
  const sessions = new SessionManager();
61
62
 
62
63
  // Push notification manager
63
- const configDir = process.env.TERMBEAM_CONFIG_DIR || path.join(os.homedir(), '.termbeam');
64
+ const configDir =
65
+ config.configDir || process.env.TERMBEAM_CONFIG_DIR || path.join(os.homedir(), '.termbeam');
64
66
  const pushManager = new PushManager(configDir);
65
67
  pushManager.init().catch((err) => {
66
68
  log.warn(`Push notification init failed: ${err.message}`);
@@ -115,7 +117,7 @@ function createTermBeamServer(overrides = {}) {
115
117
 
116
118
  const state = { shareBaseUrl: null, updateInfo: null, wss, tunnelStatus: null, getLoginInfo };
117
119
  app.use('/preview', auth.middleware, createPreviewProxy());
118
- setupRoutes(app, { auth, sessions, config, state, pushManager, copilotService });
120
+ setupRoutes(app, { auth, sessions, config, state, pushManager, copilotService, configDir });
119
121
  setupWebSocket(wss, { auth, sessions, copilotService });
120
122
 
121
123
  // --- Lifecycle ---
@@ -224,12 +226,104 @@ function createTermBeamServer(overrides = {}) {
224
226
  /* non-critical — resume will fall back to defaults */
225
227
  }
226
228
 
227
- const defaultId = sessions.create({
228
- name: path.basename(config.cwd),
229
- shell: config.shell,
230
- args: config.shellArgs,
231
- cwd: config.cwd,
232
- });
229
+ // Decide which sessions to spawn at startup. Server-side autoboot
230
+ // means: deleting sessions client-side stays sticky (refreshing the
231
+ // page won't re-spawn them — only a fresh server start does).
232
+ //
233
+ // Source-of-truth precedence:
234
+ // 1. A named workspace flagged default:true with sessions
235
+ // 2. The single named workspace (implicit default) with sessions
236
+ // 3. Legacy startupWorkspace.enabled with sessions
237
+ // 4. Otherwise: a single default session in config.cwd
238
+ let workspaceSessions = null;
239
+ try {
240
+ const { prefs } = readPreferences(configDir);
241
+ const named = prefs.workspaces || [];
242
+ const explicitDefault = named.find(
243
+ (w) => w.default && Array.isArray(w.sessions) && w.sessions.length > 0,
244
+ );
245
+ const onlyNamed =
246
+ named.length === 1 && Array.isArray(named[0].sessions) && named[0].sessions.length > 0
247
+ ? named[0]
248
+ : null;
249
+ const legacy = prefs.startupWorkspace;
250
+ if (explicitDefault) {
251
+ workspaceSessions = explicitDefault.sessions;
252
+ } else if (onlyNamed) {
253
+ workspaceSessions = onlyNamed.sessions;
254
+ } else if (
255
+ legacy &&
256
+ legacy.enabled &&
257
+ Array.isArray(legacy.sessions) &&
258
+ legacy.sessions.length > 0
259
+ ) {
260
+ workspaceSessions = legacy.sessions;
261
+ }
262
+ } catch {
263
+ // best-effort: if prefs read fails, fall back to single default
264
+ }
265
+
266
+ let defaultId;
267
+ const detectedShells = (() => {
268
+ try {
269
+ return require('../utils/shells').detectShells();
270
+ } catch {
271
+ return [];
272
+ }
273
+ })();
274
+ const validateShell = (shell) => {
275
+ if (!shell) return undefined;
276
+ if (detectedShells.some((s) => s.path === shell || s.cmd === shell)) return shell;
277
+ return undefined;
278
+ };
279
+ const spawnDefault = () => {
280
+ // Wrapped in try/catch so a bad shell/cwd doesn't crash the whole
281
+ // server start — it'll come up with no sessions and the user can
282
+ // create one themselves once they connect.
283
+ try {
284
+ return sessions.create({
285
+ name: path.basename(config.cwd),
286
+ shell: config.shell,
287
+ args: config.shellArgs,
288
+ cwd: config.cwd,
289
+ });
290
+ } catch (err) {
291
+ log.warn(`Default session failed to spawn: ${err.message}`);
292
+ return undefined;
293
+ }
294
+ };
295
+ if (workspaceSessions) {
296
+ let spawned = 0;
297
+ for (const s of workspaceSessions) {
298
+ try {
299
+ const cwd = s.cwd && path.isAbsolute(s.cwd) ? s.cwd : config.cwd;
300
+ const id = sessions.create({
301
+ name: s.name || path.basename(cwd),
302
+ shell: validateShell(s.shell) || config.shell,
303
+ args: config.shellArgs,
304
+ cwd,
305
+ initialCommand: (s.initialCommand || '').trim() || null,
306
+ color: s.color || null,
307
+ });
308
+ if (!defaultId) defaultId = id;
309
+ spawned += 1;
310
+ } catch (err) {
311
+ log.warn(`Workspace session "${s.name}" failed to spawn: ${err.message}`);
312
+ }
313
+ }
314
+ log.info(
315
+ `Spawned ${spawned}/${workspaceSessions.length} workspace session(s) at startup`,
316
+ );
317
+ // If every workspace session failed (e.g. all configured shells
318
+ // are gone after a host migration), fall back to the safe default
319
+ // so the user lands on something rather than an empty hub.
320
+ if (spawned === 0) {
321
+ log.warn('All workspace sessions failed; falling back to default session');
322
+ defaultId = spawnDefault();
323
+ }
324
+ } else {
325
+ defaultId = spawnDefault();
326
+ }
233
327
 
234
328
  const lp = '\x1b[38;5;141m'; // light purple
235
329
  const rs = '\x1b[0m'; // reset
@@ -314,7 +408,11 @@ function createTermBeamServer(overrides = {}) {
314
408
  }
315
409
 
316
410
  console.log(` Shell: ${config.shell}`);
317
- console.log(` Session: ${defaultId}`);
411
+ if (defaultId) {
412
+ console.log(` Session: ${defaultId}`);
413
+ } else {
414
+ console.log(` Session: (workspace auto-launches on connect)`);
415
+ }
318
416
  console.log(` Auth: ${config.password ? `${gn}🔒 password${rs}` : '🔓 none'}`);
319
417
  if (isLanReachable) {
320
418
  console.log(` Bind: ${config.host} (LAN accessible)`);
@@ -0,0 +1,280 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const log = require('../utils/logger');
4
+
5
+ const PREFS_FILENAME = 'prefs.json';
6
+
7
+ // Maximum allowed JSON body size in bytes for a PUT. Body parsing happens
8
+ // globally in src/server/index.js with a default ~100 KB limit, which is well
9
+ // above what any reasonable prefs payload should be.
10
+
11
+ // Defaults reflect the previous-implicit defaults from individual stores.
12
+ // Keep this list narrow; only add fields here that we actually round-trip
13
+ // through the API. Unknown keys from the client are dropped.
14
+ const DEFAULTS = Object.freeze({
15
+ themeId: 'dark',
16
+ fontSize: 14,
17
+ notifications: false,
18
+ haptics: true,
19
+ defaultFolder: '',
20
+ defaultInitialCommand: '',
21
+ touchBarCollapsed: false,
22
+ touchBarKeys: null, // null = use built-in defaults
23
+ startupWorkspace: { enabled: false, sessions: [] },
24
+ workspaces: [],
25
+ });
26
+
27
+ const FONT_MIN = 2;
28
+ const FONT_MAX = 32;
29
+ const MAX_STARTUP_SESSIONS = 16;
30
+ const MAX_TOUCHBAR_KEYS = 32;
31
+ const MAX_WORKSPACES = 16;
32
+ const MAX_STRING_LEN = 4096;
33
+ const MAX_SEND_LEN = 64;
34
+
35
+ const VALID_KEY_ACTIONS = new Set(['mic', 'copy', 'paste', 'cancel', 'newline']);
36
+ // Accept BOTH the new vocabulary (plain/accent/danger/custom) and the legacy
37
+ // values from older clients. The client-side normalize() migrates legacy
38
+ // values to the new ones on read, so persisted prefs converge over time.
39
+ const VALID_KEY_LOOKS = new Set([
40
+ 'plain',
41
+ 'accent',
42
+ 'danger',
43
+ 'custom',
44
+ // Legacy — accepted but client will rewrite to the new vocab on next PUT
45
+ 'default',
46
+ 'special',
47
+ 'modifier',
48
+ 'icon',
49
+ 'enter',
50
+ ]);
51
+ const VALID_KEY_MODIFIERS = new Set(['ctrl', 'alt', 'shift', 'meta']);
52
+ const VALID_KEY_SIZES = new Set([1, 2, 3, 4, 5, 6, 7, 8]);
53
+
54
+ function clampNumber(n, min, max, fallback) {
55
+ if (typeof n !== 'number' || !Number.isFinite(n)) return fallback;
56
+ return Math.max(min, Math.min(max, Math.round(n)));
57
+ }
58
+
59
+ function asString(v, fallback, maxLen = MAX_STRING_LEN) {
60
+ if (typeof v !== 'string') return fallback;
61
+ return v.length > maxLen ? v.slice(0, maxLen) : v;
62
+ }
63
+
64
+ function asBool(v, fallback) {
65
+ return typeof v === 'boolean' ? v : fallback;
66
+ }
67
+
68
+ function sanitizeTouchBarKeys(input) {
69
+ if (input === null) return null;
70
+ if (!Array.isArray(input)) return null;
71
+ const out = [];
72
+ for (const entry of input.slice(0, MAX_TOUCHBAR_KEYS)) {
73
+ if (!entry || typeof entry !== 'object') continue;
74
+ const id = asString(entry.id, '', 64);
75
+ const label = asString(entry.label, '', 16);
76
+ const send = asString(entry.send, '', MAX_SEND_LEN);
77
+ // Only `id` is required — empty label is allowed so the user can
78
+ // clear the field while editing without losing the key.
79
+ if (!id) continue;
80
+ const key = { id, label, send };
81
+ if (typeof entry.modifier === 'string') {
82
+ const mod = entry.modifier.toLowerCase();
83
+ if (VALID_KEY_MODIFIERS.has(mod)) key.modifier = mod;
84
+ }
85
+ if (typeof entry.action === 'string' && VALID_KEY_ACTIONS.has(entry.action)) {
86
+ key.action = entry.action;
87
+ }
88
+ if (VALID_KEY_SIZES.has(entry.size)) {
89
+ key.size = entry.size;
90
+ }
91
+ if (
92
+ typeof entry.row === 'number' &&
93
+ Number.isInteger(entry.row) &&
94
+ entry.row >= 1 &&
95
+ entry.row <= 3
96
+ ) {
97
+ key.row = entry.row;
98
+ }
99
+ if (
100
+ typeof entry.col === 'number' &&
101
+ Number.isInteger(entry.col) &&
102
+ entry.col >= 1 &&
103
+ entry.col <= 8
104
+ ) {
105
+ key.col = entry.col;
106
+ }
107
+ if (typeof entry.bg === 'string' && /^#[0-9a-fA-F]{3,8}$/.test(entry.bg)) {
108
+ key.bg = entry.bg;
109
+ }
110
+ if (typeof entry.color === 'string' && /^#[0-9a-fA-F]{3,8}$/.test(entry.color)) {
111
+ key.color = entry.color;
112
+ }
113
+ if (typeof entry.style === 'string' && VALID_KEY_LOOKS.has(entry.style)) {
114
+ key.style = entry.style;
115
+ }
116
+ out.push(key);
117
+ }
118
+ return out;
119
+ }
120
+
121
+ function sanitizeStartupSession(s) {
122
+ if (!s || typeof s !== 'object') return null;
123
+ const id = asString(s.id, '', 64);
124
+ const name = asString(s.name, '', 128);
125
+ const kindRaw = asString(s.kind, 'shell', 16);
126
+ const kind = kindRaw === 'agent' ? 'agent' : 'shell';
127
+ const cwd = asString(s.cwd, '', 1024);
128
+ const initialCommand = asString(s.initialCommand, '', 1024);
129
+ if (!id || !name) return null;
130
+ const entry = { id, name, kind, cwd, initialCommand };
131
+ if (kind === 'agent' && typeof s.agentId === 'string') {
132
+ entry.agentId = asString(s.agentId, '', 128);
133
+ }
134
+ if (typeof s.shell === 'string' && s.shell) {
135
+ entry.shell = asString(s.shell, '', 256);
136
+ }
137
+ if (typeof s.color === 'string' && /^#[0-9a-fA-F]{3,8}$/.test(s.color)) {
138
+ entry.color = s.color;
139
+ }
140
+ return entry;
141
+ }
142
+
143
+ function sanitizeStartupWorkspace(input) {
144
+ if (!input || typeof input !== 'object') return { ...DEFAULTS.startupWorkspace };
145
+ const enabled = asBool(input.enabled, false);
146
+ const rawSessions = Array.isArray(input.sessions) ? input.sessions : [];
147
+ const sessions = [];
148
+ for (const s of rawSessions.slice(0, MAX_STARTUP_SESSIONS)) {
149
+ const entry = sanitizeStartupSession(s);
150
+ if (entry) sessions.push(entry);
151
+ }
152
+ return { enabled, sessions };
153
+ }
154
+
155
+ function sanitizeWorkspaces(input) {
156
+ if (!Array.isArray(input)) return [];
157
+ const out = [];
158
+ let defaultsSeen = 0;
159
+ for (const w of input.slice(0, MAX_WORKSPACES)) {
160
+ if (!w || typeof w !== 'object') continue;
161
+ const id = asString(w.id, '', 64);
162
+ const name = asString(w.name, '', 128);
163
+ if (!id || !name) continue;
164
+ const sessions = [];
165
+ const rawSessions = Array.isArray(w.sessions) ? w.sessions : [];
166
+ for (const s of rawSessions.slice(0, MAX_STARTUP_SESSIONS)) {
167
+ const entry = sanitizeStartupSession(s);
168
+ if (entry) sessions.push(entry);
169
+ }
170
+ const entry = { id, name, sessions };
171
+ if (asBool(w.default, false) && defaultsSeen === 0) {
172
+ entry.default = true;
173
+ defaultsSeen += 1;
174
+ }
175
+ out.push(entry);
176
+ }
177
+ return out;
178
+ }
179
+
180
+ /**
181
+ * Coerce arbitrary client input into a known-good preferences object.
182
+ * Unknown keys are dropped; bad types fall back to defaults.
183
+ */
184
+ function sanitize(prefs) {
185
+ const src = prefs && typeof prefs === 'object' ? prefs : {};
186
+ return {
187
+ themeId: asString(src.themeId, DEFAULTS.themeId, 64),
188
+ fontSize: clampNumber(src.fontSize, FONT_MIN, FONT_MAX, DEFAULTS.fontSize),
189
+ notifications: asBool(src.notifications, DEFAULTS.notifications),
190
+ haptics: asBool(src.haptics, DEFAULTS.haptics),
191
+ defaultFolder: asString(src.defaultFolder, DEFAULTS.defaultFolder, 1024),
192
+ defaultInitialCommand: asString(
193
+ src.defaultInitialCommand,
194
+ DEFAULTS.defaultInitialCommand,
195
+ 1024,
196
+ ),
197
+ touchBarCollapsed: asBool(src.touchBarCollapsed, DEFAULTS.touchBarCollapsed),
198
+ touchBarKeys: sanitizeTouchBarKeys(src.touchBarKeys),
199
+ startupWorkspace: sanitizeStartupWorkspace(src.startupWorkspace),
200
+ workspaces: sanitizeWorkspaces(src.workspaces),
201
+ };
202
+ }
203
+
204
+ function getDefaults() {
205
+ return sanitize({});
206
+ }
207
+
208
+ function prefsPath(configDir) {
209
+ return path.join(configDir, PREFS_FILENAME);
210
+ }
211
+
212
+ function readPreferences(configDir) {
213
+ const file = prefsPath(configDir);
214
+ try {
215
+ const raw = fs.readFileSync(file, 'utf8');
216
+ const parsed = JSON.parse(raw);
217
+ const prefs = sanitize(parsed && parsed.prefs);
218
+ const version = Number.isInteger(parsed && parsed.version) ? parsed.version : 0;
219
+ return { prefs, version };
220
+ } catch (err) {
221
+ if (err.code !== 'ENOENT') {
222
+ log.warn(`prefs: failed to read ${file}: ${err.message}`);
223
+ }
224
+ return { prefs: getDefaults(), version: 0 };
225
+ }
226
+ }
227
+
228
+ function writePreferences(configDir, prefs) {
229
+ const sanitized = sanitize(prefs);
230
+ const current = readPreferences(configDir);
231
+ const next = { prefs: sanitized, version: current.version + 1 };
232
+ const file = prefsPath(configDir);
233
+ try {
234
+ fs.mkdirSync(configDir, { recursive: true });
235
+ fs.writeFileSync(file, JSON.stringify(next, null, 2), { mode: 0o600 });
236
+ // Ensure mode is correct even when the file already existed with different
237
+ // permissions (writeFileSync mode is only honored on create on some FS).
238
+ try {
239
+ fs.chmodSync(file, 0o600);
240
+ } catch {
241
+ // best-effort
242
+ }
243
+ } catch (err) {
244
+ log.error(`prefs: failed to write ${file}: ${err.message}`);
245
+ throw err;
246
+ }
247
+ return next;
248
+ }
249
+
250
+ function setupPreferenceRoutes(app, { auth, configDir, apiRateLimit }) {
251
+ app.get('/api/preferences', apiRateLimit, auth.middleware, (_req, res) => {
252
+ const state = readPreferences(configDir);
253
+ res.json(state);
254
+ });
255
+
256
+ app.put('/api/preferences', apiRateLimit, auth.middleware, (req, res) => {
257
+ const body = req.body;
258
+ if (!body || typeof body !== 'object' || !body.prefs || typeof body.prefs !== 'object') {
259
+ return res.status(400).json({ error: 'Body must be { prefs: object }' });
260
+ }
261
+ try {
262
+ const next = writePreferences(configDir, body.prefs);
263
+ res.json(next);
264
+ } catch {
265
+ res.status(500).json({ error: 'Failed to persist preferences' });
266
+ }
267
+ });
268
+ }
269
+
270
+ // `express.json` body parser is applied globally in index.js; this module
271
+ // just defines route handlers.
272
+
273
+ module.exports = {
274
+ setupPreferenceRoutes,
275
+ readPreferences,
276
+ writePreferences,
277
+ sanitize,
278
+ getDefaults,
279
+ PREFS_FILENAME,
280
+ };
@@ -9,6 +9,7 @@ const { getAgentSessions, getResumeCommand } = require('../utils/agent-sessions'
9
9
  const log = require('../utils/logger');
10
10
  const { getGitInfo } = require('../utils/git');
11
11
  const rateLimit = require('express-rate-limit');
12
+ const { setupPreferenceRoutes } = require('./preferences');
12
13
 
13
14
  const PUBLIC_DIR = path.join(__dirname, '..', '..', 'public');
14
15
 
@@ -97,7 +98,10 @@ function validateMagicBytes(buffer, contentType) {
97
98
  return true;
98
99
  }
99
100
 
100
- function setupRoutes(app, { auth, sessions, config, state, pushManager, copilotService }) {
101
+ function setupRoutes(
102
+ app,
103
+ { auth, sessions, config, state, pushManager, copilotService, configDir },
104
+ ) {
101
105
  const noopLimit = (_req, _res, next) => next();
102
106
  const pageRateLimit = config.disableRateLimit
103
107
  ? noopLimit
@@ -129,6 +133,11 @@ function setupRoutes(app, { auth, sessions, config, state, pushManager, copilotS
129
133
  });
130
134
  app.use(express.static(PUBLIC_DIR, { index: false }));
131
135
 
136
+ // Preferences API (mounted after rate limiters so it picks up apiRateLimit).
137
+ if (configDir) {
138
+ setupPreferenceRoutes(app, { auth, configDir, apiRateLimit });
139
+ }
140
+
132
141
  // Login page
133
142
  app.get('/login', (_req, res) => {
134
143
  if (!auth.password) return res.redirect('/');