@hienlh/ppm 0.8.61 → 0.8.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 (155) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/web/assets/{_basePickBy-COwDPZl_.js → _basePickBy-CZovQgWd.js} +1 -1
  3. package/dist/web/assets/{_baseUniq-DCb0mkTp.js → _baseUniq-ClnvscgW.js} +1 -1
  4. package/dist/web/assets/{api-settings-CuUkz5gb.js → api-settings--eVrUeZM.js} +1 -1
  5. package/dist/web/assets/{arc-D0bJaFyD.js → arc-C2Qaz-ch.js} +1 -1
  6. package/dist/web/assets/architecture-PBZL5I3N-ChOahOB7.js +1 -0
  7. package/dist/web/assets/{architectureDiagram-2XIMDMQ5-BVEUkQYB.js → architectureDiagram-2XIMDMQ5-Jq91S_rs.js} +1 -1
  8. package/dist/web/assets/{blockDiagram-WCTKOSBZ-CU2t4NHJ.js → blockDiagram-WCTKOSBZ-CKGufRTy.js} +1 -1
  9. package/dist/web/assets/browser-tab-CjjWgPDL.js +1 -0
  10. package/dist/web/assets/{c4Diagram-IC4MRINW-DzjR91sM.js → c4Diagram-IC4MRINW-BNP2L9r_.js} +1 -1
  11. package/dist/web/assets/channel-w7yboq56.js +1 -0
  12. package/dist/web/assets/chat-tab--hD0r5RS.js +7 -0
  13. package/dist/web/assets/{chunk-4BX2VUAB-0YMkpW2S.js → chunk-4BX2VUAB-BptTlTyl.js} +1 -1
  14. package/dist/web/assets/{chunk-55IACEB6-Dp0pTM5r.js → chunk-55IACEB6-C4mUdyio.js} +1 -1
  15. package/dist/web/assets/{chunk-7E7YKBS2-CuYKSUgJ.js → chunk-7E7YKBS2-6xAQfBwa.js} +1 -1
  16. package/dist/web/assets/{chunk-7R4GIKGN-DvbvLUIN.js → chunk-7R4GIKGN-DXaGAn_K.js} +2 -2
  17. package/dist/web/assets/{chunk-C72U2L5F-CcEW1AMZ.js → chunk-C72U2L5F-DOtEiN5f.js} +1 -1
  18. package/dist/web/assets/{chunk-EGIJ26TM-Cgt-qg75.js → chunk-EGIJ26TM-D0KJTa_T.js} +1 -1
  19. package/dist/web/assets/{chunk-FMBD7UC4-JCLgVcaC.js → chunk-FMBD7UC4-C_1aG0eb.js} +1 -1
  20. package/dist/web/assets/{chunk-GEFDOKGD-B82RP9ow.js → chunk-GEFDOKGD-DwVPiYfW.js} +1 -1
  21. package/dist/web/assets/chunk-GLR3WWYH-D9pZakqr.js +2 -0
  22. package/dist/web/assets/chunk-HHEYEP7N-Dld5BpGB.js +1 -0
  23. package/dist/web/assets/{chunk-JSJVCQXG-Pb-JMOgO.js → chunk-JSJVCQXG-BSrqCL_3.js} +1 -1
  24. package/dist/web/assets/{chunk-KX2RTZJC-BRj-ZEvL.js → chunk-KX2RTZJC-BCxGmbzy.js} +1 -1
  25. package/dist/web/assets/{chunk-KYZI473N-CBRPKraG.js → chunk-KYZI473N-BKO5gMeU.js} +1 -1
  26. package/dist/web/assets/{chunk-L3YUKLVL-DNFj84V6.js → chunk-L3YUKLVL-3wBgkSvL.js} +1 -1
  27. package/dist/web/assets/{chunk-MX3YWQON-BnPzQK-O.js → chunk-MX3YWQON-BgjSEzus.js} +1 -1
  28. package/dist/web/assets/{chunk-NQ4KR5QH-BRj25yO7.js → chunk-NQ4KR5QH-DLrZwBEm.js} +1 -1
  29. package/dist/web/assets/{chunk-O4XLMI2P-BdXwVXjJ.js → chunk-O4XLMI2P-BurQy8tt.js} +1 -1
  30. package/dist/web/assets/{chunk-OZEHJAEY-LfXT4p8B.js → chunk-OZEHJAEY-YTn24bGg.js} +1 -1
  31. package/dist/web/assets/{chunk-PQ6SQG4A-EdgQyTqa.js → chunk-PQ6SQG4A-BxtUGYhW.js} +1 -1
  32. package/dist/web/assets/{chunk-PU5JKC2W-D3thuSok.js → chunk-PU5JKC2W-B66ELkQm.js} +1 -1
  33. package/dist/web/assets/chunk-QZHKN3VN-DwSXwtjH.js +1 -0
  34. package/dist/web/assets/{chunk-R5LLSJPH-LdG7RqsM.js → chunk-R5LLSJPH-euR2RxLN.js} +1 -1
  35. package/dist/web/assets/{chunk-WL4C6EOR-BHFnnXOt.js → chunk-WL4C6EOR-_2CBOJdI.js} +1 -1
  36. package/dist/web/assets/{chunk-XIRO2GV7-DUmQrLsF.js → chunk-XIRO2GV7-kqQ0g6wW.js} +1 -1
  37. package/dist/web/assets/{chunk-XPW4576I-CsGTseUr.js → chunk-XPW4576I-CtcaMb09.js} +1 -1
  38. package/dist/web/assets/{chunk-XZSTWKYB-5W2emiq4.js → chunk-XZSTWKYB-BYxFzZwS.js} +1 -1
  39. package/dist/web/assets/{chunk-YBOYWFTD-COdZIaX4.js → chunk-YBOYWFTD-Dx_fX35n.js} +1 -1
  40. package/dist/web/assets/classDiagram-VBA2DB6C-BpJ6Oog2.js +1 -0
  41. package/dist/web/assets/classDiagram-v2-RAHNMMFH-Bj8gIhkP.js +1 -0
  42. package/dist/web/assets/clone-BSi6cgDh.js +1 -0
  43. package/dist/web/assets/{code-editor-C6umJOvn.js → code-editor-EG9sb3gL.js} +1 -1
  44. package/dist/web/assets/{cose-bilkent-S5V4N54A-C1QJ6GPW.js → cose-bilkent-S5V4N54A-CHHjH2dV.js} +1 -1
  45. package/dist/web/assets/{dagre-CWo8w9wK.js → dagre-CNtSxiE_.js} +1 -1
  46. package/dist/web/assets/{dagre-KLK3FWXG-Br4t5TRV.js → dagre-KLK3FWXG-ChenfPp1.js} +1 -1
  47. package/dist/web/assets/database-viewer-h1Zb9cFF.js +1 -0
  48. package/dist/web/assets/{diagram-E7M64L7V-CkDC2uAj.js → diagram-E7M64L7V-CzKYZM0Y.js} +1 -1
  49. package/dist/web/assets/{diagram-IFDJBPK2-NvhckwcA.js → diagram-IFDJBPK2-ChB_paPo.js} +1 -1
  50. package/dist/web/assets/{diagram-P4PSJMXO--nUaNiyB.js → diagram-P4PSJMXO-D1eW1dkL.js} +1 -1
  51. package/dist/web/assets/{diff-viewer-DApETeeX.js → diff-viewer-DrTqG6RM.js} +1 -1
  52. package/dist/web/assets/{erDiagram-INFDFZHY-DK4QEZYh.js → erDiagram-INFDFZHY-mCvUFSn6.js} +1 -1
  53. package/dist/web/assets/{flowDiagram-PKNHOUZH-B9h_Ba-v.js → flowDiagram-PKNHOUZH-14ohZ1M1.js} +1 -1
  54. package/dist/web/assets/{ganttDiagram-A5KZAMGK-BVlftqyZ.js → ganttDiagram-A5KZAMGK-DIX0pLbk.js} +1 -1
  55. package/dist/web/assets/git-graph-Bx3h7BK1.js +1 -0
  56. package/dist/web/assets/gitGraph-HDMCJU4V-CEee2FCA.js +1 -0
  57. package/dist/web/assets/{gitGraphDiagram-K3NZZRJ6-L7sj3Bs-.js → gitGraphDiagram-K3NZZRJ6-yEWZbdf_.js} +1 -1
  58. package/dist/web/assets/{graphlib-BbbiUImY.js → graphlib-DhOZxqsh.js} +1 -1
  59. package/dist/web/assets/index-Beb248lR.css +2 -0
  60. package/dist/web/assets/index-DmfRyMpE.js +37 -0
  61. package/dist/web/assets/info-3K5VOQVL-ce_pi3En.js +1 -0
  62. package/dist/web/assets/infoDiagram-LFFYTUFH-BzqyoqXw.js +2 -0
  63. package/dist/web/assets/input-Brjz2Vv-.js +41 -0
  64. package/dist/web/assets/{isEmpty-DXomfd7J.js → isEmpty-C0YYdhYj.js} +1 -1
  65. package/dist/web/assets/{ishikawaDiagram-PHBUUO56-cW7SMLa_.js → ishikawaDiagram-PHBUUO56-olazD6dZ.js} +1 -1
  66. package/dist/web/assets/{journeyDiagram-4ABVD52K-DFQXUZsc.js → journeyDiagram-4ABVD52K-CttDH9bb.js} +1 -1
  67. package/dist/web/assets/{kanban-definition-K7BYSVSG-BMUhjxqj.js → kanban-definition-K7BYSVSG-BBXbI37U.js} +1 -1
  68. package/dist/web/assets/keybindings-store-BScuugqK.js +1 -0
  69. package/dist/web/assets/{line--xyfYP3x.js → line-DBLLF7lH.js} +1 -1
  70. package/dist/web/assets/{linear-BdqW7iQu.js → linear-BLFWatDe.js} +1 -1
  71. package/dist/web/assets/{markdown-renderer-oHkpw_nC.js → markdown-renderer-DwmzGpNI.js} +5 -5
  72. package/dist/web/assets/{mermaid-parser.core-BY8JfkE_.js → mermaid-parser.core-BKiGOTjR.js} +2 -2
  73. package/dist/web/assets/{mindmap-definition-YRQLILUH-DIv-LMXG.js → mindmap-definition-YRQLILUH-DoT7m4Sz.js} +1 -1
  74. package/dist/web/assets/{ordinal-CIoJK3nc.js → ordinal-CCj7PWgZ.js} +1 -1
  75. package/dist/web/assets/packet-RMMSAZCW-CdYSLjRL.js +1 -0
  76. package/dist/web/assets/pie-UPGHQEXC-Bm5LiD-6.js +1 -0
  77. package/dist/web/assets/{pieDiagram-SKSYHLDU-seSK40d1.js → pieDiagram-SKSYHLDU-Bkh2E4zE.js} +1 -1
  78. package/dist/web/assets/postgres-viewer-Bp6mOne8.js +1 -0
  79. package/dist/web/assets/{quadrantDiagram-337W2JSQ-BaRFqlsA.js → quadrantDiagram-337W2JSQ-B7zgALOL.js} +1 -1
  80. package/dist/web/assets/radar-KQ55EAFF-C4PnyG7_.js +1 -0
  81. package/dist/web/assets/{requirementDiagram-Z7DCOOCP-1WWjMQB_.js → requirementDiagram-Z7DCOOCP-D_5GXNRo.js} +1 -1
  82. package/dist/web/assets/{sankeyDiagram-WA2Y5GQK-DEGGYsk7.js → sankeyDiagram-WA2Y5GQK-BA9EFAAe.js} +1 -1
  83. package/dist/web/assets/{sequenceDiagram-2WXFIKYE-BtRvoUTC.js → sequenceDiagram-2WXFIKYE-fyWIrHiG.js} +1 -1
  84. package/dist/web/assets/{settings-store-D3dJqGhB.js → settings-store-Bbhg_ptG.js} +2 -2
  85. package/dist/web/assets/settings-tab-8lfbaK4W.js +1 -0
  86. package/dist/web/assets/sqlite-viewer-Cenucoym.js +1 -0
  87. package/dist/web/assets/{stateDiagram-RAJIS63D-C16aO8tn.js → stateDiagram-RAJIS63D-DfRBcaBu.js} +1 -1
  88. package/dist/web/assets/stateDiagram-v2-FVOUBMTO-CMN4M2Em.js +1 -0
  89. package/dist/web/assets/{tab-store-DSz5PQI0.js → tab-store-DcIBZTD4.js} +1 -1
  90. package/dist/web/assets/{terminal-tab-CxJ3m9tD.js → terminal-tab-B0TAHXjw.js} +1 -1
  91. package/dist/web/assets/{timeline-definition-YZTLITO2-DrjxCpEM.js → timeline-definition-YZTLITO2-DYfwJ1jM.js} +1 -1
  92. package/dist/web/assets/treemap-KZPCXAKY-2_y-mhkz.js +1 -0
  93. package/dist/web/assets/{use-monaco-theme-BQzvItNE.js → use-monaco-theme-vwto-Vlf.js} +1 -1
  94. package/dist/web/assets/{vennDiagram-LZ73GAT5-DfYFnniI.js → vennDiagram-LZ73GAT5-DqbKNRD9.js} +1 -1
  95. package/dist/web/assets/{xychartDiagram-JWTSCODW-BRvXOVlG.js → xychartDiagram-JWTSCODW-DhUL86qT.js} +1 -1
  96. package/dist/web/index.html +10 -12
  97. package/dist/web/sw.js +1 -1
  98. package/docs/project-roadmap.md +41 -19
  99. package/package.json +1 -1
  100. package/src/server/index.ts +4 -1
  101. package/src/server/routes/browser-preview.ts +135 -65
  102. package/src/web/components/browser/browser-tab.tsx +105 -224
  103. package/src/web/components/chat/message-input.tsx +69 -2
  104. package/src/web/components/layout/command-palette.tsx +2 -0
  105. package/src/web/hooks/use-global-keybindings.ts +7 -0
  106. package/src/web/hooks/use-voice-input.ts +111 -0
  107. package/src/web/stores/keybindings-store.ts +1 -0
  108. package/dist/web/assets/architecture-PBZL5I3N-281eTKQ3.js +0 -1
  109. package/dist/web/assets/arrow-left-C_j9Ki73.js +0 -1
  110. package/dist/web/assets/browser-tab-CsZFFI1C.js +0 -1
  111. package/dist/web/assets/channel-CKNZAqoN.js +0 -1
  112. package/dist/web/assets/chat-tab-WYQKXiDW.js +0 -7
  113. package/dist/web/assets/chunk-GLR3WWYH-Bx2UL5jF.js +0 -2
  114. package/dist/web/assets/chunk-HHEYEP7N-BnRVfNc5.js +0 -1
  115. package/dist/web/assets/chunk-QZHKN3VN-gaBt0Rbd.js +0 -1
  116. package/dist/web/assets/classDiagram-VBA2DB6C-CqaIqYPn.js +0 -1
  117. package/dist/web/assets/classDiagram-v2-RAHNMMFH-Bo5WN2ok.js +0 -1
  118. package/dist/web/assets/clone-DNDy9Sms.js +0 -1
  119. package/dist/web/assets/database-viewer-TYwvlW4u.js +0 -1
  120. package/dist/web/assets/git-graph-mtdNxBZs.js +0 -1
  121. package/dist/web/assets/gitGraph-HDMCJU4V-D5qEPjgs.js +0 -1
  122. package/dist/web/assets/index-CYhfwlmi.js +0 -37
  123. package/dist/web/assets/index-n0Ww6i6b.css +0 -2
  124. package/dist/web/assets/info-3K5VOQVL-CbpovIYU.js +0 -1
  125. package/dist/web/assets/infoDiagram-LFFYTUFH-DFh9c-S2.js +0 -2
  126. package/dist/web/assets/input-DGlv6gt_.js +0 -41
  127. package/dist/web/assets/keybindings-store-DA8at4_B.js +0 -1
  128. package/dist/web/assets/packet-RMMSAZCW-BbzPU9BK.js +0 -1
  129. package/dist/web/assets/pie-UPGHQEXC-B0h6hM1j.js +0 -1
  130. package/dist/web/assets/postgres-viewer-XnXGFIcT.js +0 -1
  131. package/dist/web/assets/radar-KQ55EAFF-CHptMqVT.js +0 -1
  132. package/dist/web/assets/settings-tab-t--MmXOo.js +0 -1
  133. package/dist/web/assets/sqlite-viewer-Zm20Z3Ys.js +0 -1
  134. package/dist/web/assets/stateDiagram-v2-FVOUBMTO-D7qSAjnK.js +0 -1
  135. package/dist/web/assets/switch-goUjvGec.js +0 -1
  136. package/dist/web/assets/treemap-KZPCXAKY-BL9OJq3X.js +0 -1
  137. /package/dist/web/assets/{api-client-icCZ-07C.js → api-client-DpGMOZNf.js} +0 -0
  138. /package/dist/web/assets/{array-CLwNaqU1.js → array-BGFCBI0e.js} +0 -0
  139. /package/dist/web/assets/{columns-2-Bcg3QJBg.js → columns-2-ChOTgl3e.js} +0 -0
  140. /package/dist/web/assets/{cytoscape.esm-B-QQuWwK.js → cytoscape.esm-Ccan6xou.js} +0 -0
  141. /package/dist/web/assets/{defaultLocale-D_VMtRaY.js → defaultLocale-CRZydyG6.js} +0 -0
  142. /package/dist/web/assets/{dist-Ckxnw5rl.js → dist-Cce3efmT.js} +0 -0
  143. /package/dist/web/assets/{dist-CMmNEgEP.js → dist-T0Vhi0Mh.js} +0 -0
  144. /package/dist/web/assets/{init-vVpfz1D6.js → init-B8gtcn7T.js} +0 -0
  145. /package/dist/web/assets/{isArrayLikeObject-DvHDmeBe.js → isArrayLikeObject-B4pdpV8V.js} +0 -0
  146. /package/dist/web/assets/{katex-C3cZrCvP.js → katex-Bbu770d9.js} +0 -0
  147. /package/dist/web/assets/{math-a44lmFDa.js → math-DwgHI-Cu.js} +0 -0
  148. /package/dist/web/assets/{path-CuyvWNAH.js → path-DZF-JdEe.js} +0 -0
  149. /package/dist/web/assets/{preload-helper-CsoeaaUJ.js → preload-helper-qlgyTAkD.js} +0 -0
  150. /package/dist/web/assets/{react-BPIfZRKM.js → react-BGf7KNLk.js} +0 -0
  151. /package/dist/web/assets/{rough.esm-c4PR5shF.js → rough.esm-VLpapkIG.js} +0 -0
  152. /package/dist/web/assets/{src-CLWraeNW.js → src-BoSBNdA_.js} +0 -0
  153. /package/dist/web/assets/{table-C9jDaRl2.js → table-Yo02WRH-.js} +0 -0
  154. /package/dist/web/assets/{tag-CENGyt_L.js → tag-CaC1ng2E.js} +0 -0
  155. /package/dist/web/assets/{utils-Bslrbb-G.js → utils-btZ8C8-R.js} +0 -0
@@ -39,21 +39,19 @@
39
39
  <link rel="preconnect" href="https://fonts.googleapis.com" />
40
40
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
41
41
  <link href="https://fonts.googleapis.com/css2?family=Geist+Mono:wght@400;500;600;700&family=Geist:wght@400;500;600;700&display=swap" rel="stylesheet" />
42
- <script type="module" crossorigin src="/assets/index-CYhfwlmi.js"></script>
42
+ <script type="module" crossorigin src="/assets/index-DmfRyMpE.js"></script>
43
43
  <link rel="modulepreload" crossorigin href="/assets/chunk-CFjPhJqf.js">
44
- <link rel="modulepreload" crossorigin href="/assets/preload-helper-CsoeaaUJ.js">
45
- <link rel="modulepreload" crossorigin href="/assets/utils-Bslrbb-G.js">
44
+ <link rel="modulepreload" crossorigin href="/assets/preload-helper-qlgyTAkD.js">
45
+ <link rel="modulepreload" crossorigin href="/assets/utils-btZ8C8-R.js">
46
46
  <link rel="modulepreload" crossorigin href="/assets/react-nm2Ru1Pt.js">
47
47
  <link rel="modulepreload" crossorigin href="/assets/jsx-runtime-BRW_vwa9.js">
48
- <link rel="modulepreload" crossorigin href="/assets/input-DGlv6gt_.js">
49
- <link rel="modulepreload" crossorigin href="/assets/api-client-icCZ-07C.js">
50
- <link rel="modulepreload" crossorigin href="/assets/api-settings-CuUkz5gb.js">
51
- <link rel="modulepreload" crossorigin href="/assets/switch-goUjvGec.js">
52
- <link rel="modulepreload" crossorigin href="/assets/arrow-left-C_j9Ki73.js">
53
- <link rel="modulepreload" crossorigin href="/assets/react-BPIfZRKM.js">
54
- <link rel="modulepreload" crossorigin href="/assets/tab-store-DSz5PQI0.js">
55
- <link rel="modulepreload" crossorigin href="/assets/settings-store-D3dJqGhB.js">
56
- <link rel="stylesheet" crossorigin href="/assets/index-n0Ww6i6b.css">
48
+ <link rel="modulepreload" crossorigin href="/assets/input-Brjz2Vv-.js">
49
+ <link rel="modulepreload" crossorigin href="/assets/react-BGf7KNLk.js">
50
+ <link rel="modulepreload" crossorigin href="/assets/tab-store-DcIBZTD4.js">
51
+ <link rel="modulepreload" crossorigin href="/assets/api-client-DpGMOZNf.js">
52
+ <link rel="modulepreload" crossorigin href="/assets/api-settings--eVrUeZM.js">
53
+ <link rel="modulepreload" crossorigin href="/assets/settings-store-Bbhg_ptG.js">
54
+ <link rel="stylesheet" crossorigin href="/assets/index-Beb248lR.css">
57
55
  <link rel="manifest" href="/manifest.webmanifest"><script id="vite-plugin-pwa:register-sw" src="/registerSW.js"></script></head>
58
56
  <body class="bg-[#0f1419] text-[#e5e7eb] font-sans antialiased">
59
57
  <div id="root"></div>
package/dist/web/sw.js CHANGED
@@ -1 +1 @@
1
- try{self[`workbox:core:7.3.0`]&&_()}catch{}var e=(e,...t)=>{let n=e;return t.length>0&&(n+=` :: ${JSON.stringify(t)}`),n},t=class extends Error{constructor(t,n){let r=e(t,n);super(r),this.name=t,this.details=n}},n={googleAnalytics:`googleAnalytics`,precache:`precache-v2`,prefix:`workbox`,runtime:`runtime`,suffix:typeof registration<`u`?registration.scope:``},r=e=>[n.prefix,e,n.suffix].filter(e=>e&&e.length>0).join(`-`),i=e=>{for(let t of Object.keys(n))e(t)},a={updateDetails:e=>{i(t=>{typeof e[t]==`string`&&(n[t]=e[t])})},getGoogleAnalyticsName:e=>e||r(n.googleAnalytics),getPrecacheName:e=>e||r(n.precache),getPrefix:()=>n.prefix,getRuntimeName:e=>e||r(n.runtime),getSuffix:()=>n.suffix};function o(e,t){let n=t();return e.waitUntil(n),n}try{self[`workbox:precaching:7.3.0`]&&_()}catch{}var s=`__WB_REVISION__`;function c(e){if(!e)throw new t(`add-to-cache-list-unexpected-type`,{entry:e});if(typeof e==`string`){let t=new URL(e,location.href);return{cacheKey:t.href,url:t.href}}let{revision:n,url:r}=e;if(!r)throw new t(`add-to-cache-list-unexpected-type`,{entry:e});if(!n){let e=new URL(r,location.href);return{cacheKey:e.href,url:e.href}}let i=new URL(r,location.href),a=new URL(r,location.href);return i.searchParams.set(s,n),{cacheKey:i.href,url:a.href}}var l=class{constructor(){this.updatedURLs=[],this.notUpdatedURLs=[],this.handlerWillStart=async({request:e,state:t})=>{t&&(t.originalRequest=e)},this.cachedResponseWillBeUsed=async({event:e,state:t,cachedResponse:n})=>{if(e.type===`install`&&t&&t.originalRequest&&t.originalRequest instanceof Request){let e=t.originalRequest.url;n?this.notUpdatedURLs.push(e):this.updatedURLs.push(e)}return n}}},u=class{constructor({precacheController:e}){this.cacheKeyWillBeUsed=async({request:e,params:t})=>{let n=t?.cacheKey||this._precacheController.getCacheKeyForURL(e.url);return n?new Request(n,{headers:e.headers}):e},this._precacheController=e}},d;function f(){if(d===void 0){let e=new Response(``);if(`body`in e)try{new Response(e.body),d=!0}catch{d=!1}d=!1}return d}async function p(e,n){let r=null;if(e.url&&(r=new URL(e.url).origin),r!==self.location.origin)throw new t(`cross-origin-copy-response`,{origin:r});let i=e.clone(),a={headers:new Headers(i.headers),status:i.status,statusText:i.statusText},o=n?n(a):a,s=f()?i.body:await i.blob();return new Response(s,o)}var m=e=>new URL(String(e),location.href).href.replace(RegExp(`^${location.origin}`),``);function h(e,t){let n=new URL(e);for(let e of t)n.searchParams.delete(e);return n.href}async function g(e,t,n,r){let i=h(t.url,n);if(t.url===i)return e.match(t,r);let a=Object.assign(Object.assign({},r),{ignoreSearch:!0}),o=await e.keys(t,a);for(let t of o)if(i===h(t.url,n))return e.match(t,r)}var v=class{constructor(){this.promise=new Promise((e,t)=>{this.resolve=e,this.reject=t})}},y=new Set;async function b(){for(let e of y)await e()}function x(e){return new Promise(t=>setTimeout(t,e))}try{self[`workbox:strategies:7.3.0`]&&_()}catch{}function S(e){return typeof e==`string`?new Request(e):e}var C=class{constructor(e,t){this._cacheKeys={},Object.assign(this,t),this.event=t.event,this._strategy=e,this._handlerDeferred=new v,this._extendLifetimePromises=[],this._plugins=[...e.plugins],this._pluginStateMap=new Map;for(let e of this._plugins)this._pluginStateMap.set(e,{});this.event.waitUntil(this._handlerDeferred.promise)}async fetch(e){let{event:n}=this,r=S(e);if(r.mode===`navigate`&&n instanceof FetchEvent&&n.preloadResponse){let e=await n.preloadResponse;if(e)return e}let i=this.hasCallback(`fetchDidFail`)?r.clone():null;try{for(let e of this.iterateCallbacks(`requestWillFetch`))r=await e({request:r.clone(),event:n})}catch(e){if(e instanceof Error)throw new t(`plugin-error-request-will-fetch`,{thrownErrorMessage:e.message})}let a=r.clone();try{let e;e=await fetch(r,r.mode===`navigate`?void 0:this._strategy.fetchOptions);for(let t of this.iterateCallbacks(`fetchDidSucceed`))e=await t({event:n,request:a,response:e});return e}catch(e){throw i&&await this.runCallbacks(`fetchDidFail`,{error:e,event:n,originalRequest:i.clone(),request:a.clone()}),e}}async fetchAndCachePut(e){let t=await this.fetch(e),n=t.clone();return this.waitUntil(this.cachePut(e,n)),t}async cacheMatch(e){let t=S(e),n,{cacheName:r,matchOptions:i}=this._strategy,a=await this.getCacheKey(t,`read`),o=Object.assign(Object.assign({},i),{cacheName:r});n=await caches.match(a,o);for(let e of this.iterateCallbacks(`cachedResponseWillBeUsed`))n=await e({cacheName:r,matchOptions:i,cachedResponse:n,request:a,event:this.event})||void 0;return n}async cachePut(e,n){let r=S(e);await x(0);let i=await this.getCacheKey(r,`write`);if(!n)throw new t(`cache-put-with-no-response`,{url:m(i.url)});let a=await this._ensureResponseSafeToCache(n);if(!a)return!1;let{cacheName:o,matchOptions:s}=this._strategy,c=await self.caches.open(o),l=this.hasCallback(`cacheDidUpdate`),u=l?await g(c,i.clone(),[`__WB_REVISION__`],s):null;try{await c.put(i,l?a.clone():a)}catch(e){if(e instanceof Error)throw e.name===`QuotaExceededError`&&await b(),e}for(let e of this.iterateCallbacks(`cacheDidUpdate`))await e({cacheName:o,oldResponse:u,newResponse:a.clone(),request:i,event:this.event});return!0}async getCacheKey(e,t){let n=`${e.url} | ${t}`;if(!this._cacheKeys[n]){let r=e;for(let e of this.iterateCallbacks(`cacheKeyWillBeUsed`))r=S(await e({mode:t,request:r,event:this.event,params:this.params}));this._cacheKeys[n]=r}return this._cacheKeys[n]}hasCallback(e){for(let t of this._strategy.plugins)if(e in t)return!0;return!1}async runCallbacks(e,t){for(let n of this.iterateCallbacks(e))await n(t)}*iterateCallbacks(e){for(let t of this._strategy.plugins)if(typeof t[e]==`function`){let n=this._pluginStateMap.get(t);yield r=>{let i=Object.assign(Object.assign({},r),{state:n});return t[e](i)}}}waitUntil(e){return this._extendLifetimePromises.push(e),e}async doneWaiting(){for(;this._extendLifetimePromises.length;){let e=this._extendLifetimePromises.splice(0),t=(await Promise.allSettled(e)).find(e=>e.status===`rejected`);if(t)throw t.reason}}destroy(){this._handlerDeferred.resolve(null)}async _ensureResponseSafeToCache(e){let t=e,n=!1;for(let e of this.iterateCallbacks(`cacheWillUpdate`))if(t=await e({request:this.request,response:t,event:this.event})||void 0,n=!0,!t)break;return n||t&&t.status!==200&&(t=void 0),t}},w=class{constructor(e={}){this.cacheName=a.getRuntimeName(e.cacheName),this.plugins=e.plugins||[],this.fetchOptions=e.fetchOptions,this.matchOptions=e.matchOptions}handle(e){let[t]=this.handleAll(e);return t}handleAll(e){e instanceof FetchEvent&&(e={event:e,request:e.request});let t=e.event,n=typeof e.request==`string`?new Request(e.request):e.request,r=`params`in e?e.params:void 0,i=new C(this,{event:t,request:n,params:r}),a=this._getResponse(i,n,t);return[a,this._awaitComplete(a,i,n,t)]}async _getResponse(e,n,r){await e.runCallbacks(`handlerWillStart`,{event:r,request:n});let i;try{if(i=await this._handle(n,e),!i||i.type===`error`)throw new t(`no-response`,{url:n.url})}catch(t){if(t instanceof Error){for(let a of e.iterateCallbacks(`handlerDidError`))if(i=await a({error:t,event:r,request:n}),i)break}if(!i)throw t}for(let t of e.iterateCallbacks(`handlerWillRespond`))i=await t({event:r,request:n,response:i});return i}async _awaitComplete(e,t,n,r){let i,a;try{i=await e}catch{}try{await t.runCallbacks(`handlerDidRespond`,{event:r,request:n,response:i}),await t.doneWaiting()}catch(e){e instanceof Error&&(a=e)}if(await t.runCallbacks(`handlerDidComplete`,{event:r,request:n,response:i,error:a}),t.destroy(),a)throw a}},T=class e extends w{constructor(t={}){t.cacheName=a.getPrecacheName(t.cacheName),super(t),this._fallbackToNetwork=t.fallbackToNetwork!==!1,this.plugins.push(e.copyRedirectedCacheableResponsesPlugin)}async _handle(e,t){return await t.cacheMatch(e)||(t.event&&t.event.type===`install`?await this._handleInstall(e,t):await this._handleFetch(e,t))}async _handleFetch(e,n){let r,i=n.params||{};if(this._fallbackToNetwork){let t=i.integrity,a=e.integrity,o=!a||a===t;r=await n.fetch(new Request(e,{integrity:e.mode===`no-cors`?void 0:a||t})),t&&o&&e.mode!==`no-cors`&&(this._useDefaultCacheabilityPluginIfNeeded(),await n.cachePut(e,r.clone()))}else throw new t(`missing-precache-entry`,{cacheName:this.cacheName,url:e.url});return r}async _handleInstall(e,n){this._useDefaultCacheabilityPluginIfNeeded();let r=await n.fetch(e);if(!await n.cachePut(e,r.clone()))throw new t(`bad-precaching-response`,{url:e.url,status:r.status});return r}_useDefaultCacheabilityPluginIfNeeded(){let t=null,n=0;for(let[r,i]of this.plugins.entries())i!==e.copyRedirectedCacheableResponsesPlugin&&(i===e.defaultPrecacheCacheabilityPlugin&&(t=r),i.cacheWillUpdate&&n++);n===0?this.plugins.push(e.defaultPrecacheCacheabilityPlugin):n>1&&t!==null&&this.plugins.splice(t,1)}};T.defaultPrecacheCacheabilityPlugin={async cacheWillUpdate({response:e}){return!e||e.status>=400?null:e}},T.copyRedirectedCacheableResponsesPlugin={async cacheWillUpdate({response:e}){return e.redirected?await p(e):e}};var E=class{constructor({cacheName:e,plugins:t=[],fallbackToNetwork:n=!0}={}){this._urlsToCacheKeys=new Map,this._urlsToCacheModes=new Map,this._cacheKeysToIntegrities=new Map,this._strategy=new T({cacheName:a.getPrecacheName(e),plugins:[...t,new u({precacheController:this})],fallbackToNetwork:n}),this.install=this.install.bind(this),this.activate=this.activate.bind(this)}get strategy(){return this._strategy}precache(e){this.addToCacheList(e),this._installAndActiveListenersAdded||=(self.addEventListener(`install`,this.install),self.addEventListener(`activate`,this.activate),!0)}addToCacheList(e){let n=[];for(let r of e){typeof r==`string`?n.push(r):r&&r.revision===void 0&&n.push(r.url);let{cacheKey:e,url:i}=c(r),a=typeof r!=`string`&&r.revision?`reload`:`default`;if(this._urlsToCacheKeys.has(i)&&this._urlsToCacheKeys.get(i)!==e)throw new t(`add-to-cache-list-conflicting-entries`,{firstEntry:this._urlsToCacheKeys.get(i),secondEntry:e});if(typeof r!=`string`&&r.integrity){if(this._cacheKeysToIntegrities.has(e)&&this._cacheKeysToIntegrities.get(e)!==r.integrity)throw new t(`add-to-cache-list-conflicting-integrities`,{url:i});this._cacheKeysToIntegrities.set(e,r.integrity)}if(this._urlsToCacheKeys.set(i,e),this._urlsToCacheModes.set(i,a),n.length>0){let e=`Workbox is precaching URLs without revision info: ${n.join(`, `)}\nThis is generally NOT safe. Learn more at https://bit.ly/wb-precache`;console.warn(e)}}}install(e){return o(e,async()=>{let t=new l;this.strategy.plugins.push(t);for(let[t,n]of this._urlsToCacheKeys){let r=this._cacheKeysToIntegrities.get(n),i=this._urlsToCacheModes.get(t),a=new Request(t,{integrity:r,cache:i,credentials:`same-origin`});await Promise.all(this.strategy.handleAll({params:{cacheKey:n},request:a,event:e}))}let{updatedURLs:n,notUpdatedURLs:r}=t;return{updatedURLs:n,notUpdatedURLs:r}})}activate(e){return o(e,async()=>{let e=await self.caches.open(this.strategy.cacheName),t=await e.keys(),n=new Set(this._urlsToCacheKeys.values()),r=[];for(let i of t)n.has(i.url)||(await e.delete(i),r.push(i.url));return{deletedURLs:r}})}getURLsToCacheKeys(){return this._urlsToCacheKeys}getCachedURLs(){return[...this._urlsToCacheKeys.keys()]}getCacheKeyForURL(e){let t=new URL(e,location.href);return this._urlsToCacheKeys.get(t.href)}getIntegrityForCacheKey(e){return this._cacheKeysToIntegrities.get(e)}async matchPrecache(e){let t=e instanceof Request?e.url:e,n=this.getCacheKeyForURL(t);if(n)return(await self.caches.open(this.strategy.cacheName)).match(n)}createHandlerBoundToURL(e){let n=this.getCacheKeyForURL(e);if(!n)throw new t(`non-precached-url`,{url:e});return t=>(t.request=new Request(e),t.params=Object.assign({cacheKey:n},t.params),this.strategy.handle(t))}},D,O=()=>(D||=new E,D);try{self[`workbox:routing:7.3.0`]&&_()}catch{}var k=e=>e&&typeof e==`object`?e:{handle:e},A=class{constructor(e,t,n=`GET`){this.handler=k(t),this.match=e,this.method=n}setCatchHandler(e){this.catchHandler=k(e)}},j=class extends A{constructor(e,t,n){super(({url:t})=>{let n=e.exec(t.href);if(n&&!(t.origin!==location.origin&&n.index!==0))return n.slice(1)},t,n)}},M=class{constructor(){this._routes=new Map,this._defaultHandlerMap=new Map}get routes(){return this._routes}addFetchListener(){self.addEventListener(`fetch`,(e=>{let{request:t}=e,n=this.handleRequest({request:t,event:e});n&&e.respondWith(n)}))}addCacheListener(){self.addEventListener(`message`,(e=>{if(e.data&&e.data.type===`CACHE_URLS`){let{payload:t}=e.data,n=Promise.all(t.urlsToCache.map(t=>{typeof t==`string`&&(t=[t]);let n=new Request(...t);return this.handleRequest({request:n,event:e})}));e.waitUntil(n),e.ports&&e.ports[0]&&n.then(()=>e.ports[0].postMessage(!0))}}))}handleRequest({request:e,event:t}){let n=new URL(e.url,location.href);if(!n.protocol.startsWith(`http`))return;let r=n.origin===location.origin,{params:i,route:a}=this.findMatchingRoute({event:t,request:e,sameOrigin:r,url:n}),o=a&&a.handler,s=e.method;if(!o&&this._defaultHandlerMap.has(s)&&(o=this._defaultHandlerMap.get(s)),!o)return;let c;try{c=o.handle({url:n,request:e,event:t,params:i})}catch(e){c=Promise.reject(e)}let l=a&&a.catchHandler;return c instanceof Promise&&(this._catchHandler||l)&&(c=c.catch(async r=>{if(l)try{return await l.handle({url:n,request:e,event:t,params:i})}catch(e){e instanceof Error&&(r=e)}if(this._catchHandler)return this._catchHandler.handle({url:n,request:e,event:t});throw r})),c}findMatchingRoute({url:e,sameOrigin:t,request:n,event:r}){let i=this._routes.get(n.method)||[];for(let a of i){let i,o=a.match({url:e,sameOrigin:t,request:n,event:r});if(o)return i=o,(Array.isArray(i)&&i.length===0||o.constructor===Object&&Object.keys(o).length===0||typeof o==`boolean`)&&(i=void 0),{route:a,params:i}}return{}}setDefaultHandler(e,t=`GET`){this._defaultHandlerMap.set(t,k(e))}setCatchHandler(e){this._catchHandler=k(e)}registerRoute(e){this._routes.has(e.method)||this._routes.set(e.method,[]),this._routes.get(e.method).push(e)}unregisterRoute(e){if(!this._routes.has(e.method))throw new t(`unregister-route-but-not-found-with-method`,{method:e.method});let n=this._routes.get(e.method).indexOf(e);if(n>-1)this._routes.get(e.method).splice(n,1);else throw new t(`unregister-route-route-not-registered`)}},N,P=()=>(N||(N=new M,N.addFetchListener(),N.addCacheListener()),N);function F(e,n,r){let i;if(typeof e==`string`){let t=new URL(e,location.href);i=new A(({url:e})=>e.href===t.href,n,r)}else if(e instanceof RegExp)i=new j(e,n,r);else if(typeof e==`function`)i=new A(e,n,r);else if(e instanceof A)i=e;else throw new t(`unsupported-route-type`,{moduleName:`workbox-routing`,funcName:`registerRoute`,paramName:`capture`});return P().registerRoute(i),i}function I(e,t=[]){for(let n of[...e.searchParams.keys()])t.some(e=>e.test(n))&&e.searchParams.delete(n);return e}function*L(e,{ignoreURLParametersMatching:t=[/^utm_/,/^fbclid$/],directoryIndex:n=`index.html`,cleanURLs:r=!0,urlManipulation:i}={}){let a=new URL(e,location.href);a.hash=``,yield a.href;let o=I(a,t);if(yield o.href,n&&o.pathname.endsWith(`/`)){let e=new URL(o.href);e.pathname+=n,yield e.href}if(r){let e=new URL(o.href);e.pathname+=`.html`,yield e.href}if(i){let e=i({url:a});for(let t of e)yield t.href}}var R=class extends A{constructor(e,t){super(({request:n})=>{let r=e.getURLsToCacheKeys();for(let i of L(n.url,t)){let t=r.get(i);if(t)return{cacheKey:t,integrity:e.getIntegrityForCacheKey(t)}}},e.strategy)}};function z(e){F(new R(O(),e))}function B(e){O().precache(e)}function V(e,t){B(e),z(t)}V([{"revision":"1872c500de691dce40960bb85481de07","url":"registerSW.js"},{"revision":"2b981b018b202252559a4f14445b82bd","url":"index.html"},{"revision":"a0fb34fc84eb148d51812cd62669f20d","url":"icon-512.svg"},{"revision":"a0fb34fc84eb148d51812cd62669f20d","url":"icon-192.svg"},{"revision":"eb9818b9094675c0c5d303168f273345","url":"monacoeditorwork/ts.worker.bundle.js"},{"revision":"9af0be92dcefdc1f1290441cb5ff5d9b","url":"monacoeditorwork/json.worker.bundle.js"},{"revision":"a261b429c39dbb75ae97972d7d005e6d","url":"monacoeditorwork/html.worker.bundle.js"},{"revision":"79953d804e1bbacecfd79b85fd679016","url":"monacoeditorwork/editor.worker.bundle.js"},{"revision":"fdcba0d09aac31df7a0bc652f6e739bd","url":"monacoeditorwork/css.worker.bundle.js"},{"revision":null,"url":"assets/xychartDiagram-JWTSCODW-BRvXOVlG.js"},{"revision":null,"url":"assets/vennDiagram-LZ73GAT5-DfYFnniI.js"},{"revision":null,"url":"assets/utils-Bslrbb-G.js"},{"revision":null,"url":"assets/use-monaco-theme-BQzvItNE.js"},{"revision":null,"url":"assets/treemap-KZPCXAKY-BL9OJq3X.js"},{"revision":null,"url":"assets/timeline-definition-YZTLITO2-DrjxCpEM.js"},{"revision":null,"url":"assets/terminal-tab-CxJ3m9tD.js"},{"revision":null,"url":"assets/terminal-tab-BrP-ENHg.css"},{"revision":null,"url":"assets/tag-CENGyt_L.js"},{"revision":null,"url":"assets/table-C9jDaRl2.js"},{"revision":null,"url":"assets/tab-store-DSz5PQI0.js"},{"revision":null,"url":"assets/switch-goUjvGec.js"},{"revision":null,"url":"assets/stateDiagram-v2-FVOUBMTO-D7qSAjnK.js"},{"revision":null,"url":"assets/stateDiagram-RAJIS63D-C16aO8tn.js"},{"revision":null,"url":"assets/src-CLWraeNW.js"},{"revision":null,"url":"assets/sqlite-viewer-Zm20Z3Ys.js"},{"revision":null,"url":"assets/settings-tab-t--MmXOo.js"},{"revision":null,"url":"assets/settings-store-D3dJqGhB.js"},{"revision":null,"url":"assets/sequenceDiagram-2WXFIKYE-BtRvoUTC.js"},{"revision":null,"url":"assets/sankeyDiagram-WA2Y5GQK-DEGGYsk7.js"},{"revision":null,"url":"assets/rough.esm-c4PR5shF.js"},{"revision":null,"url":"assets/requirementDiagram-Z7DCOOCP-1WWjMQB_.js"},{"revision":null,"url":"assets/react-nm2Ru1Pt.js"},{"revision":null,"url":"assets/react-BPIfZRKM.js"},{"revision":null,"url":"assets/radar-KQ55EAFF-CHptMqVT.js"},{"revision":null,"url":"assets/quadrantDiagram-337W2JSQ-BaRFqlsA.js"},{"revision":null,"url":"assets/preload-helper-CsoeaaUJ.js"},{"revision":null,"url":"assets/postgres-viewer-XnXGFIcT.js"},{"revision":null,"url":"assets/pieDiagram-SKSYHLDU-seSK40d1.js"},{"revision":null,"url":"assets/pie-UPGHQEXC-B0h6hM1j.js"},{"revision":null,"url":"assets/path-CuyvWNAH.js"},{"revision":null,"url":"assets/packet-RMMSAZCW-BbzPU9BK.js"},{"revision":null,"url":"assets/ordinal-CIoJK3nc.js"},{"revision":null,"url":"assets/mindmap-definition-YRQLILUH-DIv-LMXG.js"},{"revision":null,"url":"assets/mermaid-parser.core-BY8JfkE_.js"},{"revision":null,"url":"assets/math-a44lmFDa.js"},{"revision":null,"url":"assets/markdown-renderer-oHkpw_nC.js"},{"revision":null,"url":"assets/linear-BdqW7iQu.js"},{"revision":null,"url":"assets/line--xyfYP3x.js"},{"revision":null,"url":"assets/keybindings-store-DA8at4_B.js"},{"revision":null,"url":"assets/katex-C3cZrCvP.js"},{"revision":null,"url":"assets/kanban-definition-K7BYSVSG-BMUhjxqj.js"},{"revision":null,"url":"assets/jsx-runtime-BRW_vwa9.js"},{"revision":null,"url":"assets/journeyDiagram-4ABVD52K-DFQXUZsc.js"},{"revision":null,"url":"assets/ishikawaDiagram-PHBUUO56-cW7SMLa_.js"},{"revision":null,"url":"assets/isEmpty-DXomfd7J.js"},{"revision":null,"url":"assets/isArrayLikeObject-DvHDmeBe.js"},{"revision":null,"url":"assets/input-DGlv6gt_.js"},{"revision":null,"url":"assets/init-vVpfz1D6.js"},{"revision":null,"url":"assets/infoDiagram-LFFYTUFH-DFh9c-S2.js"},{"revision":null,"url":"assets/info-3K5VOQVL-CbpovIYU.js"},{"revision":null,"url":"assets/index-n0Ww6i6b.css"},{"revision":null,"url":"assets/index-CYhfwlmi.js"},{"revision":null,"url":"assets/graphlib-BbbiUImY.js"},{"revision":null,"url":"assets/gitGraphDiagram-K3NZZRJ6-L7sj3Bs-.js"},{"revision":null,"url":"assets/gitGraph-HDMCJU4V-D5qEPjgs.js"},{"revision":null,"url":"assets/git-graph-mtdNxBZs.js"},{"revision":null,"url":"assets/ganttDiagram-A5KZAMGK-BVlftqyZ.js"},{"revision":null,"url":"assets/flowDiagram-PKNHOUZH-B9h_Ba-v.js"},{"revision":null,"url":"assets/erDiagram-INFDFZHY-DK4QEZYh.js"},{"revision":null,"url":"assets/dist-Ckxnw5rl.js"},{"revision":null,"url":"assets/dist-CMmNEgEP.js"},{"revision":null,"url":"assets/diff-viewer-DApETeeX.js"},{"revision":null,"url":"assets/diagram-P4PSJMXO--nUaNiyB.js"},{"revision":null,"url":"assets/diagram-IFDJBPK2-NvhckwcA.js"},{"revision":null,"url":"assets/diagram-E7M64L7V-CkDC2uAj.js"},{"revision":null,"url":"assets/defaultLocale-D_VMtRaY.js"},{"revision":null,"url":"assets/database-viewer-TYwvlW4u.js"},{"revision":null,"url":"assets/dagre-KLK3FWXG-Br4t5TRV.js"},{"revision":null,"url":"assets/dagre-CWo8w9wK.js"},{"revision":null,"url":"assets/cytoscape.esm-B-QQuWwK.js"},{"revision":null,"url":"assets/cose-bilkent-S5V4N54A-C1QJ6GPW.js"},{"revision":null,"url":"assets/columns-2-Bcg3QJBg.js"},{"revision":null,"url":"assets/code-editor-C6umJOvn.js"},{"revision":null,"url":"assets/clone-DNDy9Sms.js"},{"revision":null,"url":"assets/classDiagram-v2-RAHNMMFH-Bo5WN2ok.js"},{"revision":null,"url":"assets/classDiagram-VBA2DB6C-CqaIqYPn.js"},{"revision":null,"url":"assets/chunk-YBOYWFTD-COdZIaX4.js"},{"revision":null,"url":"assets/chunk-XZSTWKYB-5W2emiq4.js"},{"revision":null,"url":"assets/chunk-XPW4576I-CsGTseUr.js"},{"revision":null,"url":"assets/chunk-XIRO2GV7-DUmQrLsF.js"},{"revision":null,"url":"assets/chunk-WL4C6EOR-BHFnnXOt.js"},{"revision":null,"url":"assets/chunk-R5LLSJPH-LdG7RqsM.js"},{"revision":null,"url":"assets/chunk-QZHKN3VN-gaBt0Rbd.js"},{"revision":null,"url":"assets/chunk-PU5JKC2W-D3thuSok.js"},{"revision":null,"url":"assets/chunk-PQ6SQG4A-EdgQyTqa.js"},{"revision":null,"url":"assets/chunk-OZEHJAEY-LfXT4p8B.js"},{"revision":null,"url":"assets/chunk-O4XLMI2P-BdXwVXjJ.js"},{"revision":null,"url":"assets/chunk-NQ4KR5QH-BRj25yO7.js"},{"revision":null,"url":"assets/chunk-MX3YWQON-BnPzQK-O.js"},{"revision":null,"url":"assets/chunk-L3YUKLVL-DNFj84V6.js"},{"revision":null,"url":"assets/chunk-KYZI473N-CBRPKraG.js"},{"revision":null,"url":"assets/chunk-KX2RTZJC-BRj-ZEvL.js"},{"revision":null,"url":"assets/chunk-JSJVCQXG-Pb-JMOgO.js"},{"revision":null,"url":"assets/chunk-HHEYEP7N-BnRVfNc5.js"},{"revision":null,"url":"assets/chunk-GLR3WWYH-Bx2UL5jF.js"},{"revision":null,"url":"assets/chunk-GEFDOKGD-B82RP9ow.js"},{"revision":null,"url":"assets/chunk-FMBD7UC4-JCLgVcaC.js"},{"revision":null,"url":"assets/chunk-EGIJ26TM-Cgt-qg75.js"},{"revision":null,"url":"assets/chunk-CFjPhJqf.js"},{"revision":null,"url":"assets/chunk-C72U2L5F-CcEW1AMZ.js"},{"revision":null,"url":"assets/chunk-7R4GIKGN-DvbvLUIN.js"},{"revision":null,"url":"assets/chunk-7E7YKBS2-CuYKSUgJ.js"},{"revision":null,"url":"assets/chunk-55IACEB6-Dp0pTM5r.js"},{"revision":null,"url":"assets/chunk-4BX2VUAB-0YMkpW2S.js"},{"revision":null,"url":"assets/chat-tab-WYQKXiDW.js"},{"revision":null,"url":"assets/channel-CKNZAqoN.js"},{"revision":null,"url":"assets/c4Diagram-IC4MRINW-DzjR91sM.js"},{"revision":null,"url":"assets/browser-tab-CsZFFI1C.js"},{"revision":null,"url":"assets/blockDiagram-WCTKOSBZ-CU2t4NHJ.js"},{"revision":null,"url":"assets/arrow-left-C_j9Ki73.js"},{"revision":null,"url":"assets/array-CLwNaqU1.js"},{"revision":null,"url":"assets/architectureDiagram-2XIMDMQ5-BVEUkQYB.js"},{"revision":null,"url":"assets/architecture-PBZL5I3N-281eTKQ3.js"},{"revision":null,"url":"assets/arc-D0bJaFyD.js"},{"revision":null,"url":"assets/api-settings-CuUkz5gb.js"},{"revision":null,"url":"assets/api-client-icCZ-07C.js"},{"revision":null,"url":"assets/_baseUniq-DCb0mkTp.js"},{"revision":null,"url":"assets/_basePickBy-COwDPZl_.js"},{"revision":"79c8870653c8f419f2e3323085e1f4be","url":"manifest.webmanifest"}]),self.addEventListener(`push`,e=>{e.waitUntil(self.clients.matchAll({type:`window`,includeUncontrolled:!0}).then(t=>{if(t.some(e=>e.visibilityState===`visible`))return;let n=e.data?.json()??{title:`PPM`,body:`Chat completed`};return self.registration.showNotification(n.title,{body:n.body,icon:`/icon-192.png`,badge:`/icon-192.png`,tag:`ppm-chat-done`,silent:!1,data:{url:self.location.origin}})}))}),self.addEventListener(`notificationclick`,e=>{e.notification.close(),e.waitUntil(self.clients.matchAll({type:`window`,includeUncontrolled:!0}).then(t=>{for(let e of t)if(e.url.includes(self.location.origin)&&`focus`in e)return e.focus();return self.clients.openWindow(e.notification.data?.url||`/`)}))});
1
+ try{self[`workbox:core:7.3.0`]&&_()}catch{}var e=(e,...t)=>{let n=e;return t.length>0&&(n+=` :: ${JSON.stringify(t)}`),n},t=class extends Error{constructor(t,n){let r=e(t,n);super(r),this.name=t,this.details=n}},n={googleAnalytics:`googleAnalytics`,precache:`precache-v2`,prefix:`workbox`,runtime:`runtime`,suffix:typeof registration<`u`?registration.scope:``},r=e=>[n.prefix,e,n.suffix].filter(e=>e&&e.length>0).join(`-`),i=e=>{for(let t of Object.keys(n))e(t)},a={updateDetails:e=>{i(t=>{typeof e[t]==`string`&&(n[t]=e[t])})},getGoogleAnalyticsName:e=>e||r(n.googleAnalytics),getPrecacheName:e=>e||r(n.precache),getPrefix:()=>n.prefix,getRuntimeName:e=>e||r(n.runtime),getSuffix:()=>n.suffix};function o(e,t){let n=t();return e.waitUntil(n),n}try{self[`workbox:precaching:7.3.0`]&&_()}catch{}var s=`__WB_REVISION__`;function c(e){if(!e)throw new t(`add-to-cache-list-unexpected-type`,{entry:e});if(typeof e==`string`){let t=new URL(e,location.href);return{cacheKey:t.href,url:t.href}}let{revision:n,url:r}=e;if(!r)throw new t(`add-to-cache-list-unexpected-type`,{entry:e});if(!n){let e=new URL(r,location.href);return{cacheKey:e.href,url:e.href}}let i=new URL(r,location.href),a=new URL(r,location.href);return i.searchParams.set(s,n),{cacheKey:i.href,url:a.href}}var l=class{constructor(){this.updatedURLs=[],this.notUpdatedURLs=[],this.handlerWillStart=async({request:e,state:t})=>{t&&(t.originalRequest=e)},this.cachedResponseWillBeUsed=async({event:e,state:t,cachedResponse:n})=>{if(e.type===`install`&&t&&t.originalRequest&&t.originalRequest instanceof Request){let e=t.originalRequest.url;n?this.notUpdatedURLs.push(e):this.updatedURLs.push(e)}return n}}},u=class{constructor({precacheController:e}){this.cacheKeyWillBeUsed=async({request:e,params:t})=>{let n=t?.cacheKey||this._precacheController.getCacheKeyForURL(e.url);return n?new Request(n,{headers:e.headers}):e},this._precacheController=e}},d;function f(){if(d===void 0){let e=new Response(``);if(`body`in e)try{new Response(e.body),d=!0}catch{d=!1}d=!1}return d}async function p(e,n){let r=null;if(e.url&&(r=new URL(e.url).origin),r!==self.location.origin)throw new t(`cross-origin-copy-response`,{origin:r});let i=e.clone(),a={headers:new Headers(i.headers),status:i.status,statusText:i.statusText},o=n?n(a):a,s=f()?i.body:await i.blob();return new Response(s,o)}var m=e=>new URL(String(e),location.href).href.replace(RegExp(`^${location.origin}`),``);function h(e,t){let n=new URL(e);for(let e of t)n.searchParams.delete(e);return n.href}async function g(e,t,n,r){let i=h(t.url,n);if(t.url===i)return e.match(t,r);let a=Object.assign(Object.assign({},r),{ignoreSearch:!0}),o=await e.keys(t,a);for(let t of o)if(i===h(t.url,n))return e.match(t,r)}var v=class{constructor(){this.promise=new Promise((e,t)=>{this.resolve=e,this.reject=t})}},y=new Set;async function b(){for(let e of y)await e()}function x(e){return new Promise(t=>setTimeout(t,e))}try{self[`workbox:strategies:7.3.0`]&&_()}catch{}function S(e){return typeof e==`string`?new Request(e):e}var C=class{constructor(e,t){this._cacheKeys={},Object.assign(this,t),this.event=t.event,this._strategy=e,this._handlerDeferred=new v,this._extendLifetimePromises=[],this._plugins=[...e.plugins],this._pluginStateMap=new Map;for(let e of this._plugins)this._pluginStateMap.set(e,{});this.event.waitUntil(this._handlerDeferred.promise)}async fetch(e){let{event:n}=this,r=S(e);if(r.mode===`navigate`&&n instanceof FetchEvent&&n.preloadResponse){let e=await n.preloadResponse;if(e)return e}let i=this.hasCallback(`fetchDidFail`)?r.clone():null;try{for(let e of this.iterateCallbacks(`requestWillFetch`))r=await e({request:r.clone(),event:n})}catch(e){if(e instanceof Error)throw new t(`plugin-error-request-will-fetch`,{thrownErrorMessage:e.message})}let a=r.clone();try{let e;e=await fetch(r,r.mode===`navigate`?void 0:this._strategy.fetchOptions);for(let t of this.iterateCallbacks(`fetchDidSucceed`))e=await t({event:n,request:a,response:e});return e}catch(e){throw i&&await this.runCallbacks(`fetchDidFail`,{error:e,event:n,originalRequest:i.clone(),request:a.clone()}),e}}async fetchAndCachePut(e){let t=await this.fetch(e),n=t.clone();return this.waitUntil(this.cachePut(e,n)),t}async cacheMatch(e){let t=S(e),n,{cacheName:r,matchOptions:i}=this._strategy,a=await this.getCacheKey(t,`read`),o=Object.assign(Object.assign({},i),{cacheName:r});n=await caches.match(a,o);for(let e of this.iterateCallbacks(`cachedResponseWillBeUsed`))n=await e({cacheName:r,matchOptions:i,cachedResponse:n,request:a,event:this.event})||void 0;return n}async cachePut(e,n){let r=S(e);await x(0);let i=await this.getCacheKey(r,`write`);if(!n)throw new t(`cache-put-with-no-response`,{url:m(i.url)});let a=await this._ensureResponseSafeToCache(n);if(!a)return!1;let{cacheName:o,matchOptions:s}=this._strategy,c=await self.caches.open(o),l=this.hasCallback(`cacheDidUpdate`),u=l?await g(c,i.clone(),[`__WB_REVISION__`],s):null;try{await c.put(i,l?a.clone():a)}catch(e){if(e instanceof Error)throw e.name===`QuotaExceededError`&&await b(),e}for(let e of this.iterateCallbacks(`cacheDidUpdate`))await e({cacheName:o,oldResponse:u,newResponse:a.clone(),request:i,event:this.event});return!0}async getCacheKey(e,t){let n=`${e.url} | ${t}`;if(!this._cacheKeys[n]){let r=e;for(let e of this.iterateCallbacks(`cacheKeyWillBeUsed`))r=S(await e({mode:t,request:r,event:this.event,params:this.params}));this._cacheKeys[n]=r}return this._cacheKeys[n]}hasCallback(e){for(let t of this._strategy.plugins)if(e in t)return!0;return!1}async runCallbacks(e,t){for(let n of this.iterateCallbacks(e))await n(t)}*iterateCallbacks(e){for(let t of this._strategy.plugins)if(typeof t[e]==`function`){let n=this._pluginStateMap.get(t);yield r=>{let i=Object.assign(Object.assign({},r),{state:n});return t[e](i)}}}waitUntil(e){return this._extendLifetimePromises.push(e),e}async doneWaiting(){for(;this._extendLifetimePromises.length;){let e=this._extendLifetimePromises.splice(0),t=(await Promise.allSettled(e)).find(e=>e.status===`rejected`);if(t)throw t.reason}}destroy(){this._handlerDeferred.resolve(null)}async _ensureResponseSafeToCache(e){let t=e,n=!1;for(let e of this.iterateCallbacks(`cacheWillUpdate`))if(t=await e({request:this.request,response:t,event:this.event})||void 0,n=!0,!t)break;return n||t&&t.status!==200&&(t=void 0),t}},w=class{constructor(e={}){this.cacheName=a.getRuntimeName(e.cacheName),this.plugins=e.plugins||[],this.fetchOptions=e.fetchOptions,this.matchOptions=e.matchOptions}handle(e){let[t]=this.handleAll(e);return t}handleAll(e){e instanceof FetchEvent&&(e={event:e,request:e.request});let t=e.event,n=typeof e.request==`string`?new Request(e.request):e.request,r=`params`in e?e.params:void 0,i=new C(this,{event:t,request:n,params:r}),a=this._getResponse(i,n,t);return[a,this._awaitComplete(a,i,n,t)]}async _getResponse(e,n,r){await e.runCallbacks(`handlerWillStart`,{event:r,request:n});let i;try{if(i=await this._handle(n,e),!i||i.type===`error`)throw new t(`no-response`,{url:n.url})}catch(t){if(t instanceof Error){for(let a of e.iterateCallbacks(`handlerDidError`))if(i=await a({error:t,event:r,request:n}),i)break}if(!i)throw t}for(let t of e.iterateCallbacks(`handlerWillRespond`))i=await t({event:r,request:n,response:i});return i}async _awaitComplete(e,t,n,r){let i,a;try{i=await e}catch{}try{await t.runCallbacks(`handlerDidRespond`,{event:r,request:n,response:i}),await t.doneWaiting()}catch(e){e instanceof Error&&(a=e)}if(await t.runCallbacks(`handlerDidComplete`,{event:r,request:n,response:i,error:a}),t.destroy(),a)throw a}},T=class e extends w{constructor(t={}){t.cacheName=a.getPrecacheName(t.cacheName),super(t),this._fallbackToNetwork=t.fallbackToNetwork!==!1,this.plugins.push(e.copyRedirectedCacheableResponsesPlugin)}async _handle(e,t){return await t.cacheMatch(e)||(t.event&&t.event.type===`install`?await this._handleInstall(e,t):await this._handleFetch(e,t))}async _handleFetch(e,n){let r,i=n.params||{};if(this._fallbackToNetwork){let t=i.integrity,a=e.integrity,o=!a||a===t;r=await n.fetch(new Request(e,{integrity:e.mode===`no-cors`?void 0:a||t})),t&&o&&e.mode!==`no-cors`&&(this._useDefaultCacheabilityPluginIfNeeded(),await n.cachePut(e,r.clone()))}else throw new t(`missing-precache-entry`,{cacheName:this.cacheName,url:e.url});return r}async _handleInstall(e,n){this._useDefaultCacheabilityPluginIfNeeded();let r=await n.fetch(e);if(!await n.cachePut(e,r.clone()))throw new t(`bad-precaching-response`,{url:e.url,status:r.status});return r}_useDefaultCacheabilityPluginIfNeeded(){let t=null,n=0;for(let[r,i]of this.plugins.entries())i!==e.copyRedirectedCacheableResponsesPlugin&&(i===e.defaultPrecacheCacheabilityPlugin&&(t=r),i.cacheWillUpdate&&n++);n===0?this.plugins.push(e.defaultPrecacheCacheabilityPlugin):n>1&&t!==null&&this.plugins.splice(t,1)}};T.defaultPrecacheCacheabilityPlugin={async cacheWillUpdate({response:e}){return!e||e.status>=400?null:e}},T.copyRedirectedCacheableResponsesPlugin={async cacheWillUpdate({response:e}){return e.redirected?await p(e):e}};var E=class{constructor({cacheName:e,plugins:t=[],fallbackToNetwork:n=!0}={}){this._urlsToCacheKeys=new Map,this._urlsToCacheModes=new Map,this._cacheKeysToIntegrities=new Map,this._strategy=new T({cacheName:a.getPrecacheName(e),plugins:[...t,new u({precacheController:this})],fallbackToNetwork:n}),this.install=this.install.bind(this),this.activate=this.activate.bind(this)}get strategy(){return this._strategy}precache(e){this.addToCacheList(e),this._installAndActiveListenersAdded||=(self.addEventListener(`install`,this.install),self.addEventListener(`activate`,this.activate),!0)}addToCacheList(e){let n=[];for(let r of e){typeof r==`string`?n.push(r):r&&r.revision===void 0&&n.push(r.url);let{cacheKey:e,url:i}=c(r),a=typeof r!=`string`&&r.revision?`reload`:`default`;if(this._urlsToCacheKeys.has(i)&&this._urlsToCacheKeys.get(i)!==e)throw new t(`add-to-cache-list-conflicting-entries`,{firstEntry:this._urlsToCacheKeys.get(i),secondEntry:e});if(typeof r!=`string`&&r.integrity){if(this._cacheKeysToIntegrities.has(e)&&this._cacheKeysToIntegrities.get(e)!==r.integrity)throw new t(`add-to-cache-list-conflicting-integrities`,{url:i});this._cacheKeysToIntegrities.set(e,r.integrity)}if(this._urlsToCacheKeys.set(i,e),this._urlsToCacheModes.set(i,a),n.length>0){let e=`Workbox is precaching URLs without revision info: ${n.join(`, `)}\nThis is generally NOT safe. Learn more at https://bit.ly/wb-precache`;console.warn(e)}}}install(e){return o(e,async()=>{let t=new l;this.strategy.plugins.push(t);for(let[t,n]of this._urlsToCacheKeys){let r=this._cacheKeysToIntegrities.get(n),i=this._urlsToCacheModes.get(t),a=new Request(t,{integrity:r,cache:i,credentials:`same-origin`});await Promise.all(this.strategy.handleAll({params:{cacheKey:n},request:a,event:e}))}let{updatedURLs:n,notUpdatedURLs:r}=t;return{updatedURLs:n,notUpdatedURLs:r}})}activate(e){return o(e,async()=>{let e=await self.caches.open(this.strategy.cacheName),t=await e.keys(),n=new Set(this._urlsToCacheKeys.values()),r=[];for(let i of t)n.has(i.url)||(await e.delete(i),r.push(i.url));return{deletedURLs:r}})}getURLsToCacheKeys(){return this._urlsToCacheKeys}getCachedURLs(){return[...this._urlsToCacheKeys.keys()]}getCacheKeyForURL(e){let t=new URL(e,location.href);return this._urlsToCacheKeys.get(t.href)}getIntegrityForCacheKey(e){return this._cacheKeysToIntegrities.get(e)}async matchPrecache(e){let t=e instanceof Request?e.url:e,n=this.getCacheKeyForURL(t);if(n)return(await self.caches.open(this.strategy.cacheName)).match(n)}createHandlerBoundToURL(e){let n=this.getCacheKeyForURL(e);if(!n)throw new t(`non-precached-url`,{url:e});return t=>(t.request=new Request(e),t.params=Object.assign({cacheKey:n},t.params),this.strategy.handle(t))}},D,O=()=>(D||=new E,D);try{self[`workbox:routing:7.3.0`]&&_()}catch{}var k=e=>e&&typeof e==`object`?e:{handle:e},A=class{constructor(e,t,n=`GET`){this.handler=k(t),this.match=e,this.method=n}setCatchHandler(e){this.catchHandler=k(e)}},j=class extends A{constructor(e,t,n){super(({url:t})=>{let n=e.exec(t.href);if(n&&!(t.origin!==location.origin&&n.index!==0))return n.slice(1)},t,n)}},M=class{constructor(){this._routes=new Map,this._defaultHandlerMap=new Map}get routes(){return this._routes}addFetchListener(){self.addEventListener(`fetch`,(e=>{let{request:t}=e,n=this.handleRequest({request:t,event:e});n&&e.respondWith(n)}))}addCacheListener(){self.addEventListener(`message`,(e=>{if(e.data&&e.data.type===`CACHE_URLS`){let{payload:t}=e.data,n=Promise.all(t.urlsToCache.map(t=>{typeof t==`string`&&(t=[t]);let n=new Request(...t);return this.handleRequest({request:n,event:e})}));e.waitUntil(n),e.ports&&e.ports[0]&&n.then(()=>e.ports[0].postMessage(!0))}}))}handleRequest({request:e,event:t}){let n=new URL(e.url,location.href);if(!n.protocol.startsWith(`http`))return;let r=n.origin===location.origin,{params:i,route:a}=this.findMatchingRoute({event:t,request:e,sameOrigin:r,url:n}),o=a&&a.handler,s=e.method;if(!o&&this._defaultHandlerMap.has(s)&&(o=this._defaultHandlerMap.get(s)),!o)return;let c;try{c=o.handle({url:n,request:e,event:t,params:i})}catch(e){c=Promise.reject(e)}let l=a&&a.catchHandler;return c instanceof Promise&&(this._catchHandler||l)&&(c=c.catch(async r=>{if(l)try{return await l.handle({url:n,request:e,event:t,params:i})}catch(e){e instanceof Error&&(r=e)}if(this._catchHandler)return this._catchHandler.handle({url:n,request:e,event:t});throw r})),c}findMatchingRoute({url:e,sameOrigin:t,request:n,event:r}){let i=this._routes.get(n.method)||[];for(let a of i){let i,o=a.match({url:e,sameOrigin:t,request:n,event:r});if(o)return i=o,(Array.isArray(i)&&i.length===0||o.constructor===Object&&Object.keys(o).length===0||typeof o==`boolean`)&&(i=void 0),{route:a,params:i}}return{}}setDefaultHandler(e,t=`GET`){this._defaultHandlerMap.set(t,k(e))}setCatchHandler(e){this._catchHandler=k(e)}registerRoute(e){this._routes.has(e.method)||this._routes.set(e.method,[]),this._routes.get(e.method).push(e)}unregisterRoute(e){if(!this._routes.has(e.method))throw new t(`unregister-route-but-not-found-with-method`,{method:e.method});let n=this._routes.get(e.method).indexOf(e);if(n>-1)this._routes.get(e.method).splice(n,1);else throw new t(`unregister-route-route-not-registered`)}},N,P=()=>(N||(N=new M,N.addFetchListener(),N.addCacheListener()),N);function F(e,n,r){let i;if(typeof e==`string`){let t=new URL(e,location.href);i=new A(({url:e})=>e.href===t.href,n,r)}else if(e instanceof RegExp)i=new j(e,n,r);else if(typeof e==`function`)i=new A(e,n,r);else if(e instanceof A)i=e;else throw new t(`unsupported-route-type`,{moduleName:`workbox-routing`,funcName:`registerRoute`,paramName:`capture`});return P().registerRoute(i),i}function I(e,t=[]){for(let n of[...e.searchParams.keys()])t.some(e=>e.test(n))&&e.searchParams.delete(n);return e}function*L(e,{ignoreURLParametersMatching:t=[/^utm_/,/^fbclid$/],directoryIndex:n=`index.html`,cleanURLs:r=!0,urlManipulation:i}={}){let a=new URL(e,location.href);a.hash=``,yield a.href;let o=I(a,t);if(yield o.href,n&&o.pathname.endsWith(`/`)){let e=new URL(o.href);e.pathname+=n,yield e.href}if(r){let e=new URL(o.href);e.pathname+=`.html`,yield e.href}if(i){let e=i({url:a});for(let t of e)yield t.href}}var R=class extends A{constructor(e,t){super(({request:n})=>{let r=e.getURLsToCacheKeys();for(let i of L(n.url,t)){let t=r.get(i);if(t)return{cacheKey:t,integrity:e.getIntegrityForCacheKey(t)}}},e.strategy)}};function z(e){F(new R(O(),e))}function B(e){O().precache(e)}function V(e,t){B(e),z(t)}V([{"revision":"1872c500de691dce40960bb85481de07","url":"registerSW.js"},{"revision":"4c21669b41e8898795a580d086427e56","url":"index.html"},{"revision":"a0fb34fc84eb148d51812cd62669f20d","url":"icon-512.svg"},{"revision":"a0fb34fc84eb148d51812cd62669f20d","url":"icon-192.svg"},{"revision":"eb9818b9094675c0c5d303168f273345","url":"monacoeditorwork/ts.worker.bundle.js"},{"revision":"9af0be92dcefdc1f1290441cb5ff5d9b","url":"monacoeditorwork/json.worker.bundle.js"},{"revision":"a261b429c39dbb75ae97972d7d005e6d","url":"monacoeditorwork/html.worker.bundle.js"},{"revision":"79953d804e1bbacecfd79b85fd679016","url":"monacoeditorwork/editor.worker.bundle.js"},{"revision":"fdcba0d09aac31df7a0bc652f6e739bd","url":"monacoeditorwork/css.worker.bundle.js"},{"revision":null,"url":"assets/xychartDiagram-JWTSCODW-DhUL86qT.js"},{"revision":null,"url":"assets/vennDiagram-LZ73GAT5-DqbKNRD9.js"},{"revision":null,"url":"assets/utils-btZ8C8-R.js"},{"revision":null,"url":"assets/use-monaco-theme-vwto-Vlf.js"},{"revision":null,"url":"assets/treemap-KZPCXAKY-2_y-mhkz.js"},{"revision":null,"url":"assets/timeline-definition-YZTLITO2-DYfwJ1jM.js"},{"revision":null,"url":"assets/terminal-tab-BrP-ENHg.css"},{"revision":null,"url":"assets/terminal-tab-B0TAHXjw.js"},{"revision":null,"url":"assets/tag-CaC1ng2E.js"},{"revision":null,"url":"assets/table-Yo02WRH-.js"},{"revision":null,"url":"assets/tab-store-DcIBZTD4.js"},{"revision":null,"url":"assets/stateDiagram-v2-FVOUBMTO-CMN4M2Em.js"},{"revision":null,"url":"assets/stateDiagram-RAJIS63D-DfRBcaBu.js"},{"revision":null,"url":"assets/src-BoSBNdA_.js"},{"revision":null,"url":"assets/sqlite-viewer-Cenucoym.js"},{"revision":null,"url":"assets/settings-tab-8lfbaK4W.js"},{"revision":null,"url":"assets/settings-store-Bbhg_ptG.js"},{"revision":null,"url":"assets/sequenceDiagram-2WXFIKYE-fyWIrHiG.js"},{"revision":null,"url":"assets/sankeyDiagram-WA2Y5GQK-BA9EFAAe.js"},{"revision":null,"url":"assets/rough.esm-VLpapkIG.js"},{"revision":null,"url":"assets/requirementDiagram-Z7DCOOCP-D_5GXNRo.js"},{"revision":null,"url":"assets/react-nm2Ru1Pt.js"},{"revision":null,"url":"assets/react-BGf7KNLk.js"},{"revision":null,"url":"assets/radar-KQ55EAFF-C4PnyG7_.js"},{"revision":null,"url":"assets/quadrantDiagram-337W2JSQ-B7zgALOL.js"},{"revision":null,"url":"assets/preload-helper-qlgyTAkD.js"},{"revision":null,"url":"assets/postgres-viewer-Bp6mOne8.js"},{"revision":null,"url":"assets/pieDiagram-SKSYHLDU-Bkh2E4zE.js"},{"revision":null,"url":"assets/pie-UPGHQEXC-Bm5LiD-6.js"},{"revision":null,"url":"assets/path-DZF-JdEe.js"},{"revision":null,"url":"assets/packet-RMMSAZCW-CdYSLjRL.js"},{"revision":null,"url":"assets/ordinal-CCj7PWgZ.js"},{"revision":null,"url":"assets/mindmap-definition-YRQLILUH-DoT7m4Sz.js"},{"revision":null,"url":"assets/mermaid-parser.core-BKiGOTjR.js"},{"revision":null,"url":"assets/math-DwgHI-Cu.js"},{"revision":null,"url":"assets/markdown-renderer-DwmzGpNI.js"},{"revision":null,"url":"assets/linear-BLFWatDe.js"},{"revision":null,"url":"assets/line-DBLLF7lH.js"},{"revision":null,"url":"assets/keybindings-store-BScuugqK.js"},{"revision":null,"url":"assets/katex-Bbu770d9.js"},{"revision":null,"url":"assets/kanban-definition-K7BYSVSG-BBXbI37U.js"},{"revision":null,"url":"assets/jsx-runtime-BRW_vwa9.js"},{"revision":null,"url":"assets/journeyDiagram-4ABVD52K-CttDH9bb.js"},{"revision":null,"url":"assets/ishikawaDiagram-PHBUUO56-olazD6dZ.js"},{"revision":null,"url":"assets/isEmpty-C0YYdhYj.js"},{"revision":null,"url":"assets/isArrayLikeObject-B4pdpV8V.js"},{"revision":null,"url":"assets/input-Brjz2Vv-.js"},{"revision":null,"url":"assets/init-B8gtcn7T.js"},{"revision":null,"url":"assets/infoDiagram-LFFYTUFH-BzqyoqXw.js"},{"revision":null,"url":"assets/info-3K5VOQVL-ce_pi3En.js"},{"revision":null,"url":"assets/index-DmfRyMpE.js"},{"revision":null,"url":"assets/index-Beb248lR.css"},{"revision":null,"url":"assets/graphlib-DhOZxqsh.js"},{"revision":null,"url":"assets/gitGraphDiagram-K3NZZRJ6-yEWZbdf_.js"},{"revision":null,"url":"assets/gitGraph-HDMCJU4V-CEee2FCA.js"},{"revision":null,"url":"assets/git-graph-Bx3h7BK1.js"},{"revision":null,"url":"assets/ganttDiagram-A5KZAMGK-DIX0pLbk.js"},{"revision":null,"url":"assets/flowDiagram-PKNHOUZH-14ohZ1M1.js"},{"revision":null,"url":"assets/erDiagram-INFDFZHY-mCvUFSn6.js"},{"revision":null,"url":"assets/dist-T0Vhi0Mh.js"},{"revision":null,"url":"assets/dist-Cce3efmT.js"},{"revision":null,"url":"assets/diff-viewer-DrTqG6RM.js"},{"revision":null,"url":"assets/diagram-P4PSJMXO-D1eW1dkL.js"},{"revision":null,"url":"assets/diagram-IFDJBPK2-ChB_paPo.js"},{"revision":null,"url":"assets/diagram-E7M64L7V-CzKYZM0Y.js"},{"revision":null,"url":"assets/defaultLocale-CRZydyG6.js"},{"revision":null,"url":"assets/database-viewer-h1Zb9cFF.js"},{"revision":null,"url":"assets/dagre-KLK3FWXG-ChenfPp1.js"},{"revision":null,"url":"assets/dagre-CNtSxiE_.js"},{"revision":null,"url":"assets/cytoscape.esm-Ccan6xou.js"},{"revision":null,"url":"assets/cose-bilkent-S5V4N54A-CHHjH2dV.js"},{"revision":null,"url":"assets/columns-2-ChOTgl3e.js"},{"revision":null,"url":"assets/code-editor-EG9sb3gL.js"},{"revision":null,"url":"assets/clone-BSi6cgDh.js"},{"revision":null,"url":"assets/classDiagram-v2-RAHNMMFH-Bj8gIhkP.js"},{"revision":null,"url":"assets/classDiagram-VBA2DB6C-BpJ6Oog2.js"},{"revision":null,"url":"assets/chunk-YBOYWFTD-Dx_fX35n.js"},{"revision":null,"url":"assets/chunk-XZSTWKYB-BYxFzZwS.js"},{"revision":null,"url":"assets/chunk-XPW4576I-CtcaMb09.js"},{"revision":null,"url":"assets/chunk-XIRO2GV7-kqQ0g6wW.js"},{"revision":null,"url":"assets/chunk-WL4C6EOR-_2CBOJdI.js"},{"revision":null,"url":"assets/chunk-R5LLSJPH-euR2RxLN.js"},{"revision":null,"url":"assets/chunk-QZHKN3VN-DwSXwtjH.js"},{"revision":null,"url":"assets/chunk-PU5JKC2W-B66ELkQm.js"},{"revision":null,"url":"assets/chunk-PQ6SQG4A-BxtUGYhW.js"},{"revision":null,"url":"assets/chunk-OZEHJAEY-YTn24bGg.js"},{"revision":null,"url":"assets/chunk-O4XLMI2P-BurQy8tt.js"},{"revision":null,"url":"assets/chunk-NQ4KR5QH-DLrZwBEm.js"},{"revision":null,"url":"assets/chunk-MX3YWQON-BgjSEzus.js"},{"revision":null,"url":"assets/chunk-L3YUKLVL-3wBgkSvL.js"},{"revision":null,"url":"assets/chunk-KYZI473N-BKO5gMeU.js"},{"revision":null,"url":"assets/chunk-KX2RTZJC-BCxGmbzy.js"},{"revision":null,"url":"assets/chunk-JSJVCQXG-BSrqCL_3.js"},{"revision":null,"url":"assets/chunk-HHEYEP7N-Dld5BpGB.js"},{"revision":null,"url":"assets/chunk-GLR3WWYH-D9pZakqr.js"},{"revision":null,"url":"assets/chunk-GEFDOKGD-DwVPiYfW.js"},{"revision":null,"url":"assets/chunk-FMBD7UC4-C_1aG0eb.js"},{"revision":null,"url":"assets/chunk-EGIJ26TM-D0KJTa_T.js"},{"revision":null,"url":"assets/chunk-CFjPhJqf.js"},{"revision":null,"url":"assets/chunk-C72U2L5F-DOtEiN5f.js"},{"revision":null,"url":"assets/chunk-7R4GIKGN-DXaGAn_K.js"},{"revision":null,"url":"assets/chunk-7E7YKBS2-6xAQfBwa.js"},{"revision":null,"url":"assets/chunk-55IACEB6-C4mUdyio.js"},{"revision":null,"url":"assets/chunk-4BX2VUAB-BptTlTyl.js"},{"revision":null,"url":"assets/chat-tab--hD0r5RS.js"},{"revision":null,"url":"assets/channel-w7yboq56.js"},{"revision":null,"url":"assets/c4Diagram-IC4MRINW-BNP2L9r_.js"},{"revision":null,"url":"assets/browser-tab-CjjWgPDL.js"},{"revision":null,"url":"assets/blockDiagram-WCTKOSBZ-CKGufRTy.js"},{"revision":null,"url":"assets/array-BGFCBI0e.js"},{"revision":null,"url":"assets/architectureDiagram-2XIMDMQ5-Jq91S_rs.js"},{"revision":null,"url":"assets/architecture-PBZL5I3N-ChOahOB7.js"},{"revision":null,"url":"assets/arc-C2Qaz-ch.js"},{"revision":null,"url":"assets/api-settings--eVrUeZM.js"},{"revision":null,"url":"assets/api-client-DpGMOZNf.js"},{"revision":null,"url":"assets/_baseUniq-ClnvscgW.js"},{"revision":null,"url":"assets/_basePickBy-CZovQgWd.js"},{"revision":"79c8870653c8f419f2e3323085e1f4be","url":"manifest.webmanifest"}]),self.addEventListener(`push`,e=>{e.waitUntil(self.clients.matchAll({type:`window`,includeUncontrolled:!0}).then(t=>{if(t.some(e=>e.visibilityState===`visible`))return;let n=e.data?.json()??{title:`PPM`,body:`Chat completed`};return self.registration.showNotification(n.title,{body:n.body,icon:`/icon-192.png`,badge:`/icon-192.png`,tag:`ppm-chat-done`,silent:!1,data:{url:self.location.origin}})}))}),self.addEventListener(`notificationclick`,e=>{e.notification.close(),e.waitUntil(self.clients.matchAll({type:`window`,includeUncontrolled:!0}).then(t=>{for(let e of t)if(e.url.includes(self.location.origin)&&`focus`in e)return e.focus();return self.clients.openWindow(e.notification.data?.url||`/`)}))});
@@ -1,6 +1,6 @@
1
1
  # PPM Project Roadmap
2
2
 
3
- **Last Updated:** March 22, 2026
3
+ **Last Updated:** March 27, 2026
4
4
 
5
5
  ## Vision
6
6
 
@@ -56,19 +56,26 @@ PPM is the **lightest path from phone to code** — a self-hosted, BYOK, multi-d
56
56
 
57
57
  ### v0.9.0 — "Open Platform" (Q2–Q3 2026)
58
58
 
59
- **Theme:** Multi-provider AI + extension system. Expand user base beyond Claude-only developers.
59
+ **Theme:** Multi-provider AI (Claude + Cursor) + extension system. Ship a focused release, expand providers later.
60
60
 
61
- | Feature | Priority | Description |
62
- |---------|----------|-------------|
63
- | **Multi-provider AI** | Critical | Refactor `ProviderInterface` for clean provider abstraction. Tiered support: Tier 1 (full agentic) = Claude Agent SDK; Tier 2 (chat + tools) = Gemini CLI, OpenAI Codex; Tier 3 (chat-only) = any OpenAI-compatible API. Clean base code for future Chinese providers (DeepSeek, Qwen). |
64
- | **Extension architecture** | High | Dynamic extension loading system. Extensions = npm packages exporting skills + optional UI panels. First extension: extract DB viewer from core. Extension API: register routes, UI panels, sidebar tabs, skills. Config: `"extensions": ["@ppm/ext-database", "@ppm/ext-docker"]`. |
65
- | **MCP Management** | Medium | UI to add/remove/configure MCP servers. Test connection. Per-project MCP overrides. Store in SQLite. Pass to Agent SDK via `mcpServers`. |
61
+ **Overall progress: ~40%** (1/3 features complete, merge all at 100%)
66
62
 
67
- **Multi-provider tiered approach:**
63
+ | Feature | Priority | Status | Description |
64
+ |---------|----------|--------|-------------|
65
+ | **Multi-provider AI** | Critical | ✅ Done | ProviderInterface, registry, Cursor CLI, CLI provider base, UI provider/model selector, permission mode selector, system prompt customization, comprehensive tests — all on beta branch. |
66
+ | **Extension architecture** | High | 🔴 0% | Dynamic extension loading. Extensions = npm packages. First extension: extract DB viewer from core. Extension API: register routes, UI panels, sidebar tabs, skills. Config: `"extensions": ["@ppm/ext-database", "@ppm/ext-docker"]`. |
67
+ | **MCP Management** | Medium | 🔴 0% | UI to add/remove/configure MCP servers. Test connection. Per-project MCP overrides. Store in SQLite. Pass to Agent SDK via `mcpServers`. |
68
+
69
+ **Multi-provider — v0.9 scope (reduced):**
68
70
  - Tier 1 (full agentic): Claude Agent SDK — file edit, terminal, git, full autonomy
69
- - Tier 2 (chat + tools): Provider-specific CLIs (Gemini CLI, Codex) — agentic via their own tool system
70
- - Tier 3 (chat-only): Any OpenAI-compatible API conversation only, no tools
71
- - Provider interface refactor is foundation work — do it clean now, avoid painful refactor later
71
+ - Tier 2 (agentic CLI): Cursor — agentic via its own tool system
72
+ - Provider interface is clean enough to add more providers later without refactor
73
+
74
+ **Deferred to v0.9.5+:**
75
+ - Gemini CLI (Tier 2)
76
+ - OpenAI Codex (Tier 2)
77
+ - Tier 3 (chat-only): Any OpenAI-compatible API
78
+ - Chinese providers (DeepSeek, Qwen) — v1.0+
72
79
 
73
80
  **Extension architecture — design principles:**
74
81
  - Extensions are npm packages: `ppm ext install @ppm/ext-database`
@@ -79,15 +86,27 @@ PPM is the **lightest path from phone to code** — a self-hosted, BYOK, multi-d
79
86
 
80
87
  ---
81
88
 
82
- ### v0.10.0 — "Intelligence" (Q3 2026)
89
+ ### v0.10.0 — "Enhanced Workflow" (Q3 2026)
83
90
 
84
- **Theme:** PPM's own AI layer. Built-in bot + programmable skills.
91
+ **Theme:** Chat UX upgrade + git workflow. High-impact, independent features that ship fast.
85
92
 
86
93
  | Feature | Priority | Description |
87
94
  |---------|----------|-------------|
95
+ | **Chat history graph** | High | Visual branching tree of chat sessions. Fork conversations, navigate history graph. Game-changer for AI chat UX. |
96
+ | **Worktree management** | Medium | UI to create/switch/delete git worktrees. Use different providers on different branches. Integrated with project switcher. |
97
+
98
+ ---
99
+
100
+ ### v0.11.0 — "Intelligence" (Q3–Q4 2026)
101
+
102
+ **Theme:** Event system + PPM's own AI layer. Hooks → Skills API → Clawbot dependency chain.
103
+
104
+ | Feature | Priority | Description |
105
+ |---------|----------|-------------|
106
+ | **Hooks system** | High | Event hooks for PPM lifecycle (file save, git commit, chat message, etc.). Foundation for Skills API and deeper extension integration. |
88
107
  | **PPM Skills API** | High | Stable internal API for AI to control PPM: file.read/write/search, terminal.run, git.status/commit/diff, db.query, editor.open/goto, project.switch. Skills are the bridge between AI and PPM features. |
89
108
  | **Built-in Clawbot** | High | Lightweight AI agent built into PPM using Anthropic Messages API (not Agent SDK). Uses Skills API + MCP tools. Instant response, no external CLI deps. For quick tasks: file search, code explanation, simple refactors. |
90
- | **Inline SQL** | Medium | Select text in Monaco run as SQL. Connection picker in editor context menu. Results panel below editor. Leverages existing DB service. |
109
+ | **More providers** | Medium | Gemini CLI (Tier 2), OpenAI Codex (Tier 2), Tier 3 chat-only (any OpenAI-compatible API). Provider interface already clean from v0.9. |
91
110
 
92
111
  **Built-in Clawbot — why it matters:**
93
112
  - Claude Agent SDK spawns subprocess — heavy, slow startup, requires CLI installed
@@ -106,6 +125,7 @@ PPM is the **lightest path from phone to code** — a self-hosted, BYOK, multi-d
106
125
  | **Self-hosted PPM Cloud** | High | Docker image of PPM Cloud for enterprise/team. Same codebase, self-hosted config flag. `docker-compose up` and it works. LDAP/SSO integration. |
107
126
  | **PPM Marketplace** | High | Publish/install/update extensions. Browse community extensions. Revenue sharing for paid extensions. Clawbot can create extension → test → publish in minutes. |
108
127
  | **Stability & hardening** | Critical | Security audit, performance optimization, comprehensive test coverage (>80%), documentation for contributors, CI/CD pipeline. |
128
+ | **Inline SQL** | Medium | Select text in Monaco → run as SQL. Connection picker in editor context menu. Results panel below editor. Leverages existing DB service. |
109
129
 
110
130
  ---
111
131
 
@@ -130,6 +150,7 @@ Features to pick from after v1.0. Will be reviewed and scheduled based on user f
130
150
  | **Cross-platform binaries** | Distribution | Compile macOS/Linux/Windows binaries via `bun build --compile`. `npx ppm` without Bun. |
131
151
  | **OLED dark mode** | UX | True black background for OLED screens. |
132
152
  | **Collaborative editing** | Social | Real-time multi-user file editing with CRDT (yjs/automerge). |
153
+ | **Custom domain** | Cloud | Map custom domain to PPM Cloud tunnel URL. DNS CNAME + SSL via Let's Encrypt or Cloudflare. Access PPM at `code.yourdomain.com`. |
133
154
 
134
155
  ---
135
156
 
@@ -139,9 +160,10 @@ Features to pick from after v1.0. Will be reviewed and scheduled based on user f
139
160
  |---------|-------|-------------|--------|
140
161
  | **v0.7** | Multi-Account & Mobile | Account management, usage tracking, mobile UX | ✅ Current |
141
162
  | **v0.8** | Always On | PPM Cloud, auto-start, AI chat enhancements | Q2 2026 |
142
- | **v0.9** | Open Platform | Multi-provider AI, extension architecture, MCP | Q2–Q3 2026 |
143
- | **v0.10** | Intelligence | Skills API, built-in Clawbot, inline SQL | Q3 2026 |
144
- | **v1.0** | Production Ready | Self-hosted Cloud, Marketplace, stability | Q4 2026 |
163
+ | **v0.9** | Open Platform | Multi-provider (Claude + Cursor), extension architecture, MCP | Q2–Q3 2026 |
164
+ | **v0.10** | Enhanced Workflow | Chat history graph, worktree management | Q3 2026 |
165
+ | **v0.11** | Intelligence | Hooks, Skills API, Clawbot, more providers (Gemini/Codex/Tier 3) | Q3–Q4 2026 |
166
+ | **v1.0** | Production Ready | Self-hosted Cloud, Marketplace, stability, inline SQL | Q4 2026 |
145
167
 
146
168
  ---
147
169
 
@@ -149,7 +171,7 @@ Features to pick from after v1.0. Will be reviewed and scheduled based on user f
149
171
 
150
172
  1. **Own "phone to code"** — PPM wins on multi-device access. Don't chase Cursor/Windsurf feature parity.
151
173
  2. **PPM Cloud stays razor-thin** — Device registry + tunnel URLs only. No code storage. No cloud execution.
152
- 3. **Multi-provider is tiered** — Full agentic = Claude SDK. Other providers get appropriate tier. Clean interface for future providers.
174
+ 3. **Multi-provider is tiered** — v0.9: Claude SDK (Tier 1) + Cursor (Tier 2). v0.11: Gemini, Codex, Tier 3. Clean interface for future providers.
153
175
  4. **Extensions keep core lightweight** — Features are opt-in. DB viewer, future tools = extensions. Core stays fast.
154
176
  5. **Clawbot enables the ecosystem** — Users create extensions with AI, publish to Marketplace. Zero-friction.
155
177
  6. **Self-hosted first, always** — Cloud is optional convenience. PPM works 100% offline/local.
@@ -160,7 +182,7 @@ Features to pick from after v1.0. Will be reviewed and scheduled based on user f
160
182
 
161
183
  | Item | Priority | Notes |
162
184
  |------|----------|-------|
163
- | Refactor ProviderInterface for multi-provider | High | Foundation for v0.9 |
185
+ | ~~Refactor ProviderInterface for multi-provider~~ | ~~High~~ | Done on beta branch (v0.9.0-beta.5) |
164
186
  | Simplify ChatService streaming | Medium | Reduce async generator complexity |
165
187
  | Extract WebSocket common logic | Low | DRY for chat/terminal WS |
166
188
  | Round-robin cursor bug in AccountSelector | Medium | Positional cursor not advancing correctly |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hienlh/ppm",
3
- "version": "0.8.61",
3
+ "version": "0.8.63",
4
4
  "description": "Personal Project Manager — mobile-first web IDE with AI assistance",
5
5
  "author": "hienlh",
6
6
  "license": "MIT",
@@ -451,12 +451,15 @@ export async function startServer(options: {
451
451
  }
452
452
  console.log();
453
453
 
454
- // Graceful shutdown — stop server + tunnel + DB on exit
454
+ // Graceful shutdown — stop server + tunnel + preview tunnels + DB on exit
455
455
  const shutdown = () => {
456
456
  try { server.stop(true); } catch {}
457
457
  try {
458
458
  import("../services/tunnel.service.ts").then(({ tunnelService }) => tunnelService.stopTunnel()).catch(() => {});
459
459
  } catch {}
460
+ try {
461
+ import("./routes/browser-preview.ts").then(({ stopAllPreviewTunnels }) => stopAllPreviewTunnels()).catch(() => {});
462
+ } catch {}
460
463
  try {
461
464
  import("../services/db.service.ts").then(({ closeDb }) => closeDb()).catch(() => {});
462
465
  } catch {}
@@ -1,89 +1,159 @@
1
1
  import { Hono } from "hono";
2
+ import { ok, err } from "../../types/api.ts";
3
+ import { ensureCloudflared } from "../../services/cloudflared.service.ts";
2
4
 
3
5
  /**
4
- * Browser preview reverse proxy forwards requests to localhost:<port>.
5
- * Mounted at /api/preview/:port/* so the frontend iframe can load
6
- * any localhost dev server through PPM's own origin (avoiding CORS/framing issues).
6
+ * Browser preview APIstarts per-port Cloudflare Quick Tunnels so the
7
+ * frontend can iframe any localhost dev server without CORS/path issues.
8
+ *
9
+ * POST /api/preview/tunnel { port: 3000 } → { url: "https://xxx.trycloudflare.com" }
10
+ * DELETE /api/preview/tunnel/:port → stops tunnel for that port
11
+ * GET /api/preview/tunnels → list active tunnels
7
12
  */
8
13
  export const browserPreviewRoutes = new Hono();
9
14
 
10
- /** Only allow proxying to localhost ports (security: prevent SSRF) */
11
- function isValidPort(port: string): boolean {
12
- const n = parseInt(port, 10);
13
- return !isNaN(n) && n >= 1 && n <= 65535;
15
+ const TUNNEL_URL_REGEX = /https:\/\/[a-z0-9-]+\.trycloudflare\.com/;
16
+
17
+ interface ActiveTunnel {
18
+ port: number;
19
+ url: string;
20
+ process: import("bun").Subprocess;
21
+ startedAt: number;
14
22
  }
15
23
 
16
- browserPreviewRoutes.all("/:port{[0-9]+}/*", async (c) => {
17
- const port = c.req.param("port");
18
- if (!isValidPort(port)) {
19
- return c.text("Invalid port", 400);
24
+ /** Active tunnels keyed by port */
25
+ const activeTunnels = new Map<number, ActiveTunnel>();
26
+
27
+ /** Start a tunnel for a localhost port */
28
+ browserPreviewRoutes.post("/tunnel", async (c) => {
29
+ const body = await c.req.json<{ port: number }>().catch(() => null);
30
+ const port = body?.port;
31
+ if (!port || port < 1 || port > 65535) {
32
+ return c.json(err("Invalid port (1-65535)"), 400);
20
33
  }
21
34
 
22
- // Build target URL strip the /api/preview/:port prefix
23
- const url = new URL(c.req.url);
24
- const prefix = `/api/preview/${port}`;
25
- const targetPath = url.pathname.slice(prefix.length) || "/";
26
- const targetUrl = `http://localhost:${port}${targetPath}${url.search}`;
35
+ // Return existing tunnel if already running
36
+ const existing = activeTunnels.get(port);
37
+ if (existing) {
38
+ return c.json(ok({ port, url: existing.url }));
39
+ }
27
40
 
28
41
  try {
29
- // Forward the request with original method, headers, and body
30
- const headers = new Headers(c.req.raw.headers);
31
- // Remove host header so target server sees localhost
32
- headers.delete("host");
33
-
34
- const resp = await fetch(targetUrl, {
35
- method: c.req.method,
36
- headers,
37
- body: ["GET", "HEAD"].includes(c.req.method) ? undefined : c.req.raw.body,
38
- redirect: "manual",
39
- });
42
+ const bin = await ensureCloudflared();
43
+ const proc = Bun.spawn(
44
+ [bin, "tunnel", "--url", `http://127.0.0.1:${port}`],
45
+ { stderr: "pipe", stdout: "ignore", stdin: "ignore" },
46
+ );
40
47
 
41
- // Clone response headers, remove framing restrictions so iframe works
42
- const respHeaders = new Headers(resp.headers);
43
- respHeaders.delete("x-frame-options");
44
- respHeaders.delete("content-security-policy");
48
+ // Read stderr to find tunnel URL
49
+ const reader = proc.stderr.getReader();
50
+ const decoder = new TextDecoder();
51
+ const url = await new Promise<string>((resolve, reject) => {
52
+ const timeout = setTimeout(() => {
53
+ try { proc.kill(); } catch {}
54
+ reject(new Error("Tunnel timed out after 30s"));
55
+ }, 30_000);
45
56
 
46
- return new Response(resp.body, {
47
- status: resp.status,
48
- statusText: resp.statusText,
49
- headers: respHeaders,
57
+ let buffer = "";
58
+ let found = false;
59
+ const read = async () => {
60
+ try {
61
+ while (true) {
62
+ const { done, value } = await reader.read();
63
+ if (done) break;
64
+ if (found) continue;
65
+ buffer += decoder.decode(value, { stream: true });
66
+ const match = buffer.match(TUNNEL_URL_REGEX);
67
+ if (match) {
68
+ found = true;
69
+ buffer = "";
70
+ clearTimeout(timeout);
71
+ resolve(match[0]);
72
+ }
73
+ }
74
+ if (!found) {
75
+ clearTimeout(timeout);
76
+ reject(new Error("cloudflared exited without tunnel URL"));
77
+ }
78
+ } catch (e) {
79
+ if (!found) { clearTimeout(timeout); reject(e); }
80
+ }
81
+ };
82
+ read();
50
83
  });
51
- } catch {
52
- return c.text(`Cannot connect to localhost:${port}`, 502);
84
+
85
+ activeTunnels.set(port, { port, url, process: proc, startedAt: Date.now() });
86
+
87
+ // Auto-cleanup when process exits
88
+ proc.exited.then(() => activeTunnels.delete(port)).catch(() => activeTunnels.delete(port));
89
+
90
+ console.log(`[preview] tunnel started for port ${port} → ${url}`);
91
+ return c.json(ok({ port, url }));
92
+ } catch (e: any) {
93
+ return c.json(err(e.message || "Failed to start tunnel"), 500);
53
94
  }
54
95
  });
55
96
 
56
- // Handle root path (no trailing slash)
57
- browserPreviewRoutes.all("/:port{[0-9]+}", async (c) => {
58
- const port = c.req.param("port");
59
- if (!isValidPort(port)) {
60
- return c.text("Invalid port", 400);
97
+ /** Stop a tunnel */
98
+ browserPreviewRoutes.delete("/tunnel/:port{[0-9]+}", (c) => {
99
+ const port = parseInt(c.req.param("port"), 10);
100
+ const tunnel = activeTunnels.get(port);
101
+ if (!tunnel) {
102
+ return c.json(err("No tunnel running for this port"), 404);
61
103
  }
62
104
 
63
- const url = new URL(c.req.url);
64
- const targetUrl = `http://localhost:${port}/${url.search}`;
105
+ try { tunnel.process.kill(); } catch {}
106
+ activeTunnels.delete(port);
107
+ console.log(`[preview] tunnel stopped for port ${port}`);
108
+ return c.json(ok({ port }));
109
+ });
65
110
 
66
- try {
67
- const headers = new Headers(c.req.raw.headers);
68
- headers.delete("host");
69
-
70
- const resp = await fetch(targetUrl, {
71
- method: c.req.method,
72
- headers,
73
- body: ["GET", "HEAD"].includes(c.req.method) ? undefined : c.req.raw.body,
74
- redirect: "manual",
75
- });
111
+ /** List active tunnels */
112
+ browserPreviewRoutes.get("/tunnels", (c) => {
113
+ const list = Array.from(activeTunnels.values()).map((t) => ({
114
+ port: t.port,
115
+ url: t.url,
116
+ startedAt: t.startedAt,
117
+ }));
118
+ return c.json(ok(list));
119
+ });
76
120
 
77
- const respHeaders = new Headers(resp.headers);
78
- respHeaders.delete("x-frame-options");
79
- respHeaders.delete("content-security-policy");
121
+ /** Check if a cloudflared process is still alive */
122
+ function isProcessAlive(proc: import("bun").Subprocess): boolean {
123
+ try { process.kill(proc.pid, 0); return true; } catch { return false; }
124
+ }
80
125
 
81
- return new Response(resp.body, {
82
- status: resp.status,
83
- statusText: resp.statusText,
84
- headers: respHeaders,
85
- });
86
- } catch {
87
- return c.text(`Cannot connect to localhost:${port}`, 502);
126
+ /** Remove ghost tunnels (process died or target port no longer listening) */
127
+ async function cleanupGhostTunnels() {
128
+ for (const [port, tunnel] of activeTunnels) {
129
+ // Check if cloudflared process is still running
130
+ if (!isProcessAlive(tunnel.process)) {
131
+ console.log(`[preview] ghost cleanup: tunnel for port ${port} process dead`);
132
+ activeTunnels.delete(port);
133
+ continue;
134
+ }
135
+ // Check if target port is still listening
136
+ try {
137
+ const conn = await Bun.connect({ hostname: "127.0.0.1", port, socket: {
138
+ data() {}, open(s) { s.end(); }, error() {}, close() {},
139
+ }});
140
+ conn.end();
141
+ } catch {
142
+ // Port not listening — kill tunnel
143
+ console.log(`[preview] ghost cleanup: tunnel for port ${port} — port not listening`);
144
+ try { tunnel.process.kill(); } catch {}
145
+ activeTunnels.delete(port);
146
+ }
88
147
  }
89
- });
148
+ }
149
+
150
+ // Run ghost cleanup every 30s
151
+ setInterval(cleanupGhostTunnels, 30_000);
152
+
153
+ /** Cleanup all tunnels on server shutdown */
154
+ export function stopAllPreviewTunnels() {
155
+ for (const [port, tunnel] of activeTunnels) {
156
+ try { tunnel.process.kill(); } catch {}
157
+ activeTunnels.delete(port);
158
+ }
159
+ }