@hienlh/ppm 0.9.0-beta.9 → 0.9.1

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 (262) hide show
  1. package/CHANGELOG.md +233 -0
  2. package/bun.lock +17 -0
  3. package/dist/web/assets/{_basePickBy-3Xe18azI.js → _basePickBy-5PGDJbfF.js} +1 -1
  4. package/dist/web/assets/{_baseUniq-Yy35llnn.js → _baseUniq-BT4Ow4Kk.js} +1 -1
  5. package/dist/web/assets/api-settings-BUvk6Saw.js +1 -0
  6. package/dist/web/assets/{arc-B9n1Gvb5.js → arc-BAOivWpI.js} +1 -1
  7. package/dist/web/assets/architecture-PBZL5I3N-DEO2f3VD.js +1 -0
  8. package/dist/web/assets/{architectureDiagram-2XIMDMQ5-DqAZP_F6.js → architectureDiagram-2XIMDMQ5-Z-4eN4za.js} +1 -1
  9. package/dist/web/assets/arrow-up-BYhx9ckd.js +1 -0
  10. package/dist/web/assets/{blockDiagram-WCTKOSBZ-h3cDF2vI.js → blockDiagram-WCTKOSBZ-BCLqzhuZ.js} +1 -1
  11. package/dist/web/assets/browser-tab-CrkhFCaw.js +1 -0
  12. package/dist/web/assets/{c4Diagram-IC4MRINW--pF1r5lr.js → c4Diagram-IC4MRINW-0Vp0Jeas.js} +1 -1
  13. package/dist/web/assets/channel-By7bn0Yq.js +1 -0
  14. package/dist/web/assets/chat-tab-C6jpiwh7.js +8 -0
  15. package/dist/web/assets/chevron-right-5HgK6l7K.js +1 -0
  16. package/dist/web/assets/{chunk-4BX2VUAB-C3aZvW7B.js → chunk-4BX2VUAB-D4tOov49.js} +1 -1
  17. package/dist/web/assets/{chunk-55IACEB6-D5cABeB9.js → chunk-55IACEB6-DJ6BynZ4.js} +1 -1
  18. package/dist/web/assets/{chunk-7E7YKBS2-CkFGv6Zs.js → chunk-7E7YKBS2-CiyUJxNI.js} +1 -1
  19. package/dist/web/assets/{chunk-7R4GIKGN-Dvbyu4Zw.js → chunk-7R4GIKGN-Dv-4cAYn.js} +2 -2
  20. package/dist/web/assets/{chunk-C72U2L5F-CtqKiH4q.js → chunk-C72U2L5F-D21mS_6G.js} +1 -1
  21. package/dist/web/assets/{chunk-EGIJ26TM-Cpr87sBR.js → chunk-EGIJ26TM-DzqmU2Z7.js} +1 -1
  22. package/dist/web/assets/{chunk-FMBD7UC4-D23YVTOU.js → chunk-FMBD7UC4-DXncblvW.js} +1 -1
  23. package/dist/web/assets/{chunk-GEFDOKGD-tDjHsAUs.js → chunk-GEFDOKGD-D-pKjlVd.js} +1 -1
  24. package/dist/web/assets/chunk-GLR3WWYH-DKikpoJM.js +2 -0
  25. package/dist/web/assets/chunk-HHEYEP7N-C7vxA5i9.js +1 -0
  26. package/dist/web/assets/{chunk-JSJVCQXG-BBmymCjA.js → chunk-JSJVCQXG-99JzIdPr.js} +1 -1
  27. package/dist/web/assets/{chunk-KX2RTZJC-DP36BDiU.js → chunk-KX2RTZJC-CRq1OBZv.js} +1 -1
  28. package/dist/web/assets/{chunk-KYZI473N-Djw13C-3.js → chunk-KYZI473N-Bb0MCaIO.js} +1 -1
  29. package/dist/web/assets/{chunk-L3YUKLVL-HG_eMj_C.js → chunk-L3YUKLVL-C7qGJrfV.js} +1 -1
  30. package/dist/web/assets/{chunk-MX3YWQON-C2UEioMs.js → chunk-MX3YWQON-BpS_PtKp.js} +1 -1
  31. package/dist/web/assets/{chunk-NQ4KR5QH-DXUTQ-BL.js → chunk-NQ4KR5QH-z_blpjxi.js} +1 -1
  32. package/dist/web/assets/{chunk-O4XLMI2P-BsUWb9d0.js → chunk-O4XLMI2P-nDhi_cVu.js} +1 -1
  33. package/dist/web/assets/{chunk-OZEHJAEY-rG0P22U9.js → chunk-OZEHJAEY-BXhYx3nO.js} +1 -1
  34. package/dist/web/assets/{chunk-PQ6SQG4A-DX0xW7kO.js → chunk-PQ6SQG4A-TF58UVMU.js} +1 -1
  35. package/dist/web/assets/{chunk-PU5JKC2W-C7Gry6md.js → chunk-PU5JKC2W-ek7k4QVB.js} +1 -1
  36. package/dist/web/assets/chunk-QZHKN3VN-CYaTbeZf.js +1 -0
  37. package/dist/web/assets/{chunk-R5LLSJPH-CMY0PkRK.js → chunk-R5LLSJPH-CFwSJijQ.js} +1 -1
  38. package/dist/web/assets/{chunk-WL4C6EOR-CXuQvlyu.js → chunk-WL4C6EOR-ByUrSRin.js} +1 -1
  39. package/dist/web/assets/{chunk-XIRO2GV7-DRJEb7Zb.js → chunk-XIRO2GV7-Djlmrely.js} +1 -1
  40. package/dist/web/assets/{chunk-XPW4576I-BPEX8KhL.js → chunk-XPW4576I-BPQQBakK.js} +1 -1
  41. package/dist/web/assets/{chunk-XZSTWKYB-Cb0iqycX.js → chunk-XZSTWKYB-DxAOx4hG.js} +1 -1
  42. package/dist/web/assets/{chunk-YBOYWFTD-av5aeHLq.js → chunk-YBOYWFTD-rQG3QH5s.js} +1 -1
  43. package/dist/web/assets/classDiagram-VBA2DB6C-BA8Nj-_C.js +1 -0
  44. package/dist/web/assets/classDiagram-v2-RAHNMMFH-DjYu-6mn.js +1 -0
  45. package/dist/web/assets/clone-LRxlvnMj.js +1 -0
  46. package/dist/web/assets/code-editor-CBIPzlP2.js +2 -0
  47. package/dist/web/assets/columns-2-cEVJHYd7.js +1 -0
  48. package/dist/web/assets/{cose-bilkent-S5V4N54A-qudEiMCT.js → cose-bilkent-S5V4N54A-B_AWZsOP.js} +1 -1
  49. package/dist/web/assets/createLucideIcon-PuMiQgHl.js +1 -0
  50. package/dist/web/assets/{csv-preview-DUbHtTAS.js → csv-preview-ncSOnJSC.js} +2 -2
  51. package/dist/web/assets/{dagre-BFcnKyBF.js → dagre-DHq9bhnd.js} +1 -1
  52. package/dist/web/assets/{dagre-KLK3FWXG-C3O-MTLf.js → dagre-KLK3FWXG-BdJr7Byp.js} +1 -1
  53. package/dist/web/assets/database-viewer-BqOJR_zi.js +1 -0
  54. package/dist/web/assets/{diagram-E7M64L7V-DxPjK7_c.js → diagram-E7M64L7V-_db4pBVA.js} +1 -1
  55. package/dist/web/assets/{diagram-IFDJBPK2-sqTog_XV.js → diagram-IFDJBPK2-xKoeuiJx.js} +1 -1
  56. package/dist/web/assets/{diagram-P4PSJMXO-hzmp0GHK.js → diagram-P4PSJMXO-C8tjJsev.js} +1 -1
  57. package/dist/web/assets/diff-viewer-CcLyp4eY.js +4 -0
  58. package/dist/web/assets/{dist-CALwEtco.js → dist-DIV6WgAG.js} +1 -1
  59. package/dist/web/assets/{dist-DGDPTxs1.js → dist-ovWkrgO-.js} +1 -1
  60. package/dist/web/assets/{erDiagram-INFDFZHY-DLeYhAAT.js → erDiagram-INFDFZHY-BSh2z9Df.js} +1 -1
  61. package/dist/web/assets/extension-webview-NiZ7Ybvv.js +3 -0
  62. package/dist/web/assets/{flowDiagram-PKNHOUZH-CRxlE9Sr.js → flowDiagram-PKNHOUZH-oYaovqyp.js} +1 -1
  63. package/dist/web/assets/{ganttDiagram-A5KZAMGK-BdjmoMLS.js → ganttDiagram-A5KZAMGK-DmL26q2P.js} +1 -1
  64. package/dist/web/assets/git-graph-CoTvMrIo.js +1 -0
  65. package/dist/web/assets/gitGraph-HDMCJU4V-Bwna3and.js +1 -0
  66. package/dist/web/assets/{gitGraphDiagram-K3NZZRJ6-BeHSX7kk.js → gitGraphDiagram-K3NZZRJ6-CMoukSrY.js} +1 -1
  67. package/dist/web/assets/{graphlib-Duh_bWLa.js → graphlib-BcsNnGcW.js} +1 -1
  68. package/dist/web/assets/index-C8byznLO.js +37 -0
  69. package/dist/web/assets/index-KwC2YrG4.css +2 -0
  70. package/dist/web/assets/info-3K5VOQVL-_vRxVNUm.js +1 -0
  71. package/dist/web/assets/infoDiagram-LFFYTUFH-DWwumDkq.js +2 -0
  72. package/dist/web/assets/{isEmpty-B9L-Ge-H.js → isEmpty-bnrF3Qbc.js} +1 -1
  73. package/dist/web/assets/{ishikawaDiagram-PHBUUO56-Cu0Rt1Ok.js → ishikawaDiagram-PHBUUO56-D05_LyL7.js} +1 -1
  74. package/dist/web/assets/{journeyDiagram-4ABVD52K-CgDI-UG4.js → journeyDiagram-4ABVD52K-B_L20qMe.js} +1 -1
  75. package/dist/web/assets/jsx-runtime-kMwlnEGE.js +1 -0
  76. package/dist/web/assets/{kanban-definition-K7BYSVSG-h4g10UHL.js → kanban-definition-K7BYSVSG-CZ535BbZ.js} +1 -1
  77. package/dist/web/assets/keybindings-store-DPYzBe_M.js +1 -0
  78. package/dist/web/assets/{line-B75-Rx70.js → line-CVvo3dRu.js} +1 -1
  79. package/dist/web/assets/{linear-Bcjv9FQt.js → linear-DP4mkX3m.js} +1 -1
  80. package/dist/web/assets/{markdown-renderer-VIZB1GXE.js → markdown-renderer-DPLdR9xc.js} +5 -5
  81. package/dist/web/assets/{mermaid-parser.core-8u2leTXI.js → mermaid-parser.core-C7UwoIh6.js} +2 -2
  82. package/dist/web/assets/{mindmap-definition-YRQLILUH-BaOBwb-W.js → mindmap-definition-YRQLILUH-x0MTutJp.js} +1 -1
  83. package/dist/web/assets/{ordinal-LFEjVtwQ.js → ordinal-_K3x1fkz.js} +1 -1
  84. package/dist/web/assets/packet-RMMSAZCW-DY5PNnZU.js +1 -0
  85. package/dist/web/assets/pie-UPGHQEXC-BHncZutv.js +1 -0
  86. package/dist/web/assets/{pieDiagram-SKSYHLDU-At5Kz0KK.js → pieDiagram-SKSYHLDU-C1Gjrtzy.js} +1 -1
  87. package/dist/web/assets/postgres-viewer-BeiK4lCa.js +1 -0
  88. package/dist/web/assets/{quadrantDiagram-337W2JSQ-CdjGIDfw.js → quadrantDiagram-337W2JSQ-C8bzJCjQ.js} +1 -1
  89. package/dist/web/assets/radar-KQ55EAFF-DH0AOkUy.js +1 -0
  90. package/dist/web/assets/{requirementDiagram-Z7DCOOCP-B9F_Cx_p.js → requirementDiagram-Z7DCOOCP-pQyah6WB.js} +1 -1
  91. package/dist/web/assets/{sankeyDiagram-WA2Y5GQK-RolPi8bU.js → sankeyDiagram-WA2Y5GQK-T6RgG-N8.js} +1 -1
  92. package/dist/web/assets/{sequenceDiagram-2WXFIKYE-DM-tMAhx.js → sequenceDiagram-2WXFIKYE-BQDJ4CVs.js} +1 -1
  93. package/dist/web/assets/settings-tab-D3AvU4lu.js +1 -0
  94. package/dist/web/assets/sqlite-viewer-nA2sD4Yv.js +1 -0
  95. package/dist/web/assets/{stateDiagram-RAJIS63D-C4EMl6jf.js → stateDiagram-RAJIS63D-66vhiIuk.js} +1 -1
  96. package/dist/web/assets/stateDiagram-v2-FVOUBMTO-BGVqj_g9.js +1 -0
  97. package/dist/web/assets/tab-store-BOgTrqRr.js +1 -0
  98. package/dist/web/assets/table-DFevCOMd.js +1 -0
  99. package/dist/web/assets/tag-CXMT0QB6.js +1 -0
  100. package/dist/web/assets/{terminal-tab-XhKfb4ei.js → terminal-tab-BBi0pEji.js} +1 -1
  101. package/dist/web/assets/{timeline-definition-YZTLITO2-A4PN_Efm.js → timeline-definition-YZTLITO2-DwZqB3nn.js} +1 -1
  102. package/dist/web/assets/treemap-KZPCXAKY-B2Xkyv-K.js +1 -0
  103. package/dist/web/assets/{use-monaco-theme-0p0-84jJ.js → use-monaco-theme-B5pG2d1w.js} +1 -1
  104. package/dist/web/assets/{vennDiagram-LZ73GAT5-ywK7LMaH.js → vennDiagram-LZ73GAT5-s9Z71fz-.js} +1 -1
  105. package/dist/web/assets/{xychartDiagram-JWTSCODW-DylHYNtJ.js → xychartDiagram-JWTSCODW-DRa_TH4B.js} +1 -1
  106. package/dist/web/index.html +10 -9
  107. package/dist/web/monacoeditorwork/css.worker.bundle.js +122 -122
  108. package/dist/web/monacoeditorwork/editor.worker.bundle.js +78 -78
  109. package/dist/web/monacoeditorwork/html.worker.bundle.js +110 -110
  110. package/dist/web/monacoeditorwork/json.worker.bundle.js +108 -108
  111. package/dist/web/monacoeditorwork/ts.worker.bundle.js +81 -81
  112. package/dist/web/sw.js +1 -1
  113. package/docs/code-standards.md +128 -1
  114. package/docs/codebase-summary.md +79 -12
  115. package/docs/extension-development-guide.md +532 -0
  116. package/docs/project-changelog.md +51 -1
  117. package/docs/project-roadmap.md +9 -3
  118. package/docs/streaming-input-guide.md +267 -0
  119. package/docs/system-architecture.md +432 -3
  120. package/package.json +6 -3
  121. package/packages/ext-database/package.json +41 -0
  122. package/packages/ext-database/src/connection-tree.ts +142 -0
  123. package/packages/ext-database/src/extension.ts +346 -0
  124. package/packages/ext-database/src/query-panel.ts +120 -0
  125. package/packages/ext-database/src/table-viewer-panel.ts +410 -0
  126. package/packages/ext-database/tsconfig.json +8 -0
  127. package/packages/vscode-compat/package.json +16 -0
  128. package/packages/vscode-compat/src/commands.ts +39 -0
  129. package/packages/vscode-compat/src/context.ts +65 -0
  130. package/packages/vscode-compat/src/disposable.ts +21 -0
  131. package/packages/vscode-compat/src/env.ts +20 -0
  132. package/packages/vscode-compat/src/event-emitter.ts +28 -0
  133. package/packages/vscode-compat/src/index.ts +93 -0
  134. package/packages/vscode-compat/src/not-supported.ts +15 -0
  135. package/packages/vscode-compat/src/types.ts +167 -0
  136. package/packages/vscode-compat/src/uri.ts +65 -0
  137. package/packages/vscode-compat/src/window.ts +229 -0
  138. package/packages/vscode-compat/src/workspace.ts +76 -0
  139. package/packages/vscode-compat/tsconfig.json +10 -0
  140. package/snapshot-state.md +1526 -0
  141. package/src/cli/commands/autostart.ts +1 -1
  142. package/src/cli/commands/ext-cmd.ts +121 -0
  143. package/src/cli/commands/restart.ts +9 -1
  144. package/src/cli/commands/status.ts +19 -0
  145. package/src/index.ts +5 -3
  146. package/src/providers/claude-agent-sdk.ts +221 -17
  147. package/src/providers/cli-provider-base.ts +6 -0
  148. package/src/server/index.ts +55 -155
  149. package/src/server/routes/chat.ts +81 -11
  150. package/src/server/routes/extensions.ts +81 -0
  151. package/src/server/routes/project-scoped.ts +2 -0
  152. package/src/server/routes/settings.ts +27 -0
  153. package/src/server/routes/workspace.ts +35 -0
  154. package/src/server/ws/chat.ts +9 -3
  155. package/src/server/ws/extensions.ts +175 -0
  156. package/src/services/account-selector.service.ts +14 -5
  157. package/src/services/account.service.ts +7 -7
  158. package/src/services/claude-usage.service.ts +11 -11
  159. package/src/services/cloud-ws.service.ts +228 -0
  160. package/src/services/cloud.service.ts +1 -0
  161. package/src/services/contribution-registry.ts +110 -0
  162. package/src/services/db.service.ts +181 -4
  163. package/src/services/extension-host-worker.ts +160 -0
  164. package/src/services/extension-installer.ts +112 -0
  165. package/src/services/extension-manifest.ts +65 -0
  166. package/src/services/extension-rpc-handlers.ts +235 -0
  167. package/src/services/extension-rpc.ts +105 -0
  168. package/src/services/extension.service.ts +228 -0
  169. package/src/services/mcp-config.service.ts +15 -6
  170. package/src/services/supervisor.ts +271 -25
  171. package/src/types/api.ts +1 -0
  172. package/src/types/chat.ts +4 -0
  173. package/src/types/extension-messages.ts +64 -0
  174. package/src/types/extension.ts +131 -0
  175. package/src/web/app.tsx +69 -48
  176. package/src/web/components/chat/account-rotation-settings.tsx +163 -0
  177. package/src/web/components/chat/chat-history-bar.tsx +106 -10
  178. package/src/web/components/chat/chat-tab.tsx +15 -10
  179. package/src/web/components/chat/chat-welcome.tsx +148 -0
  180. package/src/web/components/chat/message-list.tsx +19 -6
  181. package/src/web/components/chat/session-picker.tsx +80 -32
  182. package/src/web/components/chat/usage-badge.tsx +68 -8
  183. package/src/web/components/extensions/extension-inputbox.tsx +92 -0
  184. package/src/web/components/extensions/extension-quickpick.tsx +194 -0
  185. package/src/web/components/extensions/extension-tree-view.tsx +240 -0
  186. package/src/web/components/extensions/extension-webview.tsx +83 -0
  187. package/src/web/components/layout/command-palette.tsx +22 -2
  188. package/src/web/components/layout/editor-panel.tsx +163 -18
  189. package/src/web/components/layout/mobile-nav.tsx +2 -1
  190. package/src/web/components/layout/sidebar.tsx +21 -3
  191. package/src/web/components/layout/status-bar.tsx +64 -0
  192. package/src/web/components/layout/tab-bar.tsx +2 -0
  193. package/src/web/components/layout/tab-content.tsx +5 -0
  194. package/src/web/components/layout/upgrade-banner.tsx +15 -5
  195. package/src/web/components/settings/change-password-section.tsx +128 -0
  196. package/src/web/components/settings/extension-manager-section.tsx +214 -0
  197. package/src/web/components/settings/settings-tab.tsx +9 -2
  198. package/src/web/components/shared/connection-lost-overlay.tsx +89 -0
  199. package/src/web/hooks/use-chat.ts +28 -0
  200. package/src/web/hooks/use-extension-ws.ts +181 -0
  201. package/src/web/hooks/use-global-keybindings.ts +18 -2
  202. package/src/web/hooks/use-server-reload.ts +9 -0
  203. package/src/web/hooks/use-url-sync.ts +173 -21
  204. package/src/web/stores/connection-store.ts +39 -0
  205. package/src/web/stores/extension-store.ts +204 -0
  206. package/src/web/stores/panel-store.ts +63 -9
  207. package/src/web/stores/panel-utils.ts +145 -3
  208. package/src/web/stores/settings-store.ts +7 -2
  209. package/src/web/stores/tab-store.ts +2 -1
  210. package/test-session-ops.mjs +444 -0
  211. package/test-tokens.mjs +212 -0
  212. package/tsconfig.json +3 -1
  213. package/dist/web/assets/api-settings-CEMxVMCV.js +0 -1
  214. package/dist/web/assets/architecture-PBZL5I3N-CFzkFKEL.js +0 -1
  215. package/dist/web/assets/arrow-up--LjUXLEt.js +0 -1
  216. package/dist/web/assets/browser-tab-D1Zua62g.js +0 -1
  217. package/dist/web/assets/channel-C2fMafck.js +0 -1
  218. package/dist/web/assets/chat-tab-BnD27Vp9.js +0 -7
  219. package/dist/web/assets/chevron-right-CHnjJt4E.js +0 -1
  220. package/dist/web/assets/chunk-GLR3WWYH-DBdWQ3zy.js +0 -2
  221. package/dist/web/assets/chunk-HHEYEP7N-BBw_z0fW.js +0 -1
  222. package/dist/web/assets/chunk-QZHKN3VN-DFKFM_C1.js +0 -1
  223. package/dist/web/assets/classDiagram-VBA2DB6C-Dp4Kk3Yb.js +0 -1
  224. package/dist/web/assets/classDiagram-v2-RAHNMMFH-D8IvcV_B.js +0 -1
  225. package/dist/web/assets/clone-B2hUek6n.js +0 -1
  226. package/dist/web/assets/code-editor-DGRg8stf.js +0 -2
  227. package/dist/web/assets/columns-2-DbesTfa7.js +0 -1
  228. package/dist/web/assets/database-viewer-DxCXZQcE.js +0 -1
  229. package/dist/web/assets/diff-viewer-C1sDJG35.js +0 -4
  230. package/dist/web/assets/git-graph-BDn-EiGE.js +0 -1
  231. package/dist/web/assets/gitGraph-HDMCJU4V-CNlas3Rz.js +0 -1
  232. package/dist/web/assets/index-Bun94AK3.js +0 -37
  233. package/dist/web/assets/index-Db8uky1a.css +0 -2
  234. package/dist/web/assets/info-3K5VOQVL-BDzTLc11.js +0 -1
  235. package/dist/web/assets/infoDiagram-LFFYTUFH-ZZmpgc6t.js +0 -2
  236. package/dist/web/assets/jsx-runtime-BRW_vwa9.js +0 -1
  237. package/dist/web/assets/keybindings-store-COmK4Dte.js +0 -1
  238. package/dist/web/assets/packet-RMMSAZCW-IVa5F-go.js +0 -1
  239. package/dist/web/assets/pie-UPGHQEXC-CvXHKAzp.js +0 -1
  240. package/dist/web/assets/postgres-viewer-CvQZ8gkh.js +0 -1
  241. package/dist/web/assets/radar-KQ55EAFF-Z-Tr5wtS.js +0 -1
  242. package/dist/web/assets/settings-tab-RCnvZ29H.js +0 -1
  243. package/dist/web/assets/sqlite-viewer-CEEm2W4C.js +0 -1
  244. package/dist/web/assets/stateDiagram-v2-FVOUBMTO-B-UjZch3.js +0 -1
  245. package/dist/web/assets/tab-store-Bjh6bXFP.js +0 -1
  246. package/dist/web/assets/table-CQVQM2SB.js +0 -1
  247. package/dist/web/assets/tag-Q2dZiSPX.js +0 -1
  248. package/dist/web/assets/treemap-KZPCXAKY-C9TYRE0k.js +0 -1
  249. /package/dist/web/assets/{api-client-BKIT_Qeg.js → api-client-BfBM3I7n.js} +0 -0
  250. /package/dist/web/assets/{array-DqLCdDFv.js → array-B9UHiPd-.js} +0 -0
  251. /package/dist/web/assets/{cytoscape.esm-CWPXKqbJ.js → cytoscape.esm-BW-DbntU.js} +0 -0
  252. /package/dist/web/assets/{defaultLocale-CrJzLgRD.js → defaultLocale-5eAKkKJC.js} +0 -0
  253. /package/dist/web/assets/{dist-Cep75xXf.js → dist-CSJdAyA9.js} +0 -0
  254. /package/dist/web/assets/{init-C0r9Gk5G.js → init-DlZdxViB.js} +0 -0
  255. /package/dist/web/assets/{isArrayLikeObject-CGBoxvCD.js → isArrayLikeObject-B_v2FtYn.js} +0 -0
  256. /package/dist/web/assets/{katex-DzXRfQ_m.js → katex-Bqvo_ZG0.js} +0 -0
  257. /package/dist/web/assets/{lib-BeaDXEkP.js → lib-BQ34Db2e.js} +0 -0
  258. /package/dist/web/assets/{math-y9zN1W-N.js → math-069Z4SuC.js} +0 -0
  259. /package/dist/web/assets/{path-DIKpVbHL.js → path-6uRLdFF7.js} +0 -0
  260. /package/dist/web/assets/{rough.esm-nHaDi0Kw.js → rough.esm-JX0wREDd.js} +0 -0
  261. /package/dist/web/assets/{src-Dw4QhedI.js → src-BqX54PbV.js} +0 -0
  262. /package/dist/web/assets/{utils-DMiycH3O.js → utils-BNytJOb1.js} +0 -0
@@ -1,37 +1,181 @@
1
1
  import { useEffect, useRef } from "react";
2
- import { useTabStore } from "@/stores/tab-store";
2
+ import { useTabStore, type TabType } from "@/stores/tab-store";
3
+
4
+ // ---------------------------------------------------------------------------
5
+ // URL state types
6
+ // ---------------------------------------------------------------------------
7
+
8
+ export interface UrlState {
9
+ projectName: string | null;
10
+ tabType: TabType | null;
11
+ tabIdentifier: string | null;
12
+ openChat: string | null;
13
+ }
14
+
15
+ const VALID_TAB_TYPES: TabType[] = [
16
+ "terminal", "chat", "editor", "database", "sqlite",
17
+ "postgres", "git-graph", "git-diff", "settings", "browser",
18
+ ];
19
+
20
+ // ---------------------------------------------------------------------------
21
+ // Parse URL → state
22
+ // ---------------------------------------------------------------------------
3
23
 
4
24
  /**
5
- * Parse the current URL to extract project name and tab ID.
6
- * Expected format: /project/:projectName/tab/:tabId
25
+ * Parse the current URL to extract project name and tab info.
26
+ * Format: /project/{name}/{tabType}/{...identifier}
7
27
  */
8
- export function parseUrlState(): { projectName: string | null; tabId: string | null; openChat: string | null } {
28
+ export function parseUrlState(): UrlState {
9
29
  const path = window.location.pathname;
10
- const match = path.match(/^\/project\/([^/]+)(?:\/tab\/([^/]+))?/);
11
30
  const params = new URLSearchParams(window.location.search);
12
31
  const openChat = params.get("openChat");
13
- if (!match) return { projectName: null, tabId: null, openChat };
14
- return {
15
- projectName: match[1] ? decodeURIComponent(match[1]) : null,
16
- tabId: match[2] ? decodeURIComponent(match[2]) : null,
17
- openChat,
18
- };
32
+
33
+ const match = path.match(/^\/project\/([^/]+)(?:\/([^/]+)(\/.*)?)?/);
34
+ if (!match) return { projectName: null, tabType: null, tabIdentifier: null, openChat };
35
+
36
+ const projectName = decodeURIComponent(match[1]!);
37
+ const rawType = match[2] ?? null;
38
+ const rawIdentifier = match[3] ? match[3].slice(1) : null; // strip leading /
39
+
40
+ // Legacy fallback: /project/{name}/tab/{tabId}
41
+ if (rawType === "tab") {
42
+ return { projectName, tabType: null, tabIdentifier: null, openChat };
43
+ }
44
+
45
+ const tabType = VALID_TAB_TYPES.includes(rawType as TabType) ? (rawType as TabType) : null;
46
+
47
+ return { projectName, tabType, tabIdentifier: rawIdentifier, openChat };
19
48
  }
20
49
 
50
+ // ---------------------------------------------------------------------------
51
+ // Build URL from state
52
+ // ---------------------------------------------------------------------------
53
+
21
54
  /**
22
- * Build URL path from project name and tab ID.
55
+ * Build URL path from project name and deterministic tab ID.
23
56
  */
24
- function buildUrl(projectName: string | null, tabId: string | null): string {
57
+ export function buildUrl(projectName: string | null, tabId: string | null): string {
25
58
  if (!projectName || projectName === "__global__") return "/";
59
+
26
60
  let url = `/project/${encodeURIComponent(projectName)}`;
27
- if (tabId) url += `/tab/${encodeURIComponent(tabId)}`;
61
+ if (!tabId) return url;
62
+
63
+ // Strip panel suffix (@panel-xxx) — not meaningful in URLs
64
+ const atIdx = tabId.indexOf("@");
65
+ const cleanId = atIdx !== -1 ? tabId.slice(0, atIdx) : tabId;
66
+
67
+ // tabId format: "type:identifier" or "type" (singletons)
68
+ const colonIdx = cleanId.indexOf(":");
69
+ if (colonIdx === -1) {
70
+ // Singleton: git-graph, settings
71
+ url += `/${cleanId}`;
72
+ } else {
73
+ const type = cleanId.slice(0, colonIdx);
74
+ const identifier = cleanId.slice(colonIdx + 1);
75
+ // Real slashes — no encoding for paths. Only encode special URL chars.
76
+ url += `/${type}/${identifier.replace(/[?#]/g, encodeURIComponent)}`;
77
+ }
28
78
  return url;
29
79
  }
30
80
 
81
+ // ---------------------------------------------------------------------------
82
+ // Tab ID reconstruction from URL
83
+ // ---------------------------------------------------------------------------
84
+
85
+ /** Reconstruct deterministic tab ID from parsed URL */
86
+ export function tabIdFromUrl(tabType: TabType, tabIdentifier: string | null): string {
87
+ if (!tabIdentifier) return tabType; // singleton
88
+ return `${tabType}:${tabIdentifier}`;
89
+ }
90
+
91
+ // ---------------------------------------------------------------------------
92
+ // Auto-open tab from URL
93
+ // ---------------------------------------------------------------------------
94
+
95
+ function buildMetadataFromUrl(
96
+ type: TabType, identifier: string | null, projectName: string,
97
+ ): Record<string, unknown> | null {
98
+ switch (type) {
99
+ case "editor": return identifier ? { filePath: identifier, projectName } : null;
100
+ case "chat": {
101
+ if (!identifier) return null;
102
+ const slashIdx = identifier.indexOf("/");
103
+ if (slashIdx === -1) return { sessionId: identifier, projectName };
104
+ const providerId = identifier.slice(0, slashIdx);
105
+ const sessionId = identifier.slice(slashIdx + 1);
106
+ return sessionId ? { sessionId, providerId, projectName } : null;
107
+ }
108
+ case "terminal": return { terminalIndex: parseInt(identifier ?? "1", 10), projectName };
109
+ case "git-graph": return { projectName };
110
+ case "git-diff": return identifier ? { filePath: identifier, projectName } : null;
111
+ case "settings": return {};
112
+ case "database": {
113
+ const [connId, tableName] = (identifier ?? "").split(":");
114
+ return connId ? { connectionId: connId, tableName: tableName ?? "" } : null;
115
+ }
116
+ case "sqlite": return identifier ? { filePath: identifier, projectName } : null;
117
+ case "postgres": {
118
+ const [connId, tableName] = (identifier ?? "").split(":");
119
+ return connId ? { connectionId: connId, tableName: tableName ?? "" } : null;
120
+ }
121
+ case "browser": return identifier ? { url: identifier } : null;
122
+ default: return null;
123
+ }
124
+ }
125
+
126
+ function buildTitleFromUrl(type: TabType, identifier: string | null): string {
127
+ switch (type) {
128
+ case "editor": return identifier?.split("/").pop() ?? "File";
129
+ case "chat": return "Chat";
130
+ case "terminal": return `Terminal ${identifier ?? "1"}`;
131
+ case "git-graph": return "Git Graph";
132
+ case "git-diff": return identifier?.split("/").pop() ?? "Diff";
133
+ case "settings": return "Settings";
134
+ case "database": return identifier ?? "Database";
135
+ case "sqlite": return identifier?.split("/").pop() ?? "SQLite";
136
+ case "postgres": return identifier ?? "PostgreSQL";
137
+ case "browser": return "Browser";
138
+ default: return type;
139
+ }
140
+ }
141
+
142
+ /** Auto-open or focus a tab based on URL state */
143
+ export function autoOpenFromUrl(
144
+ tabType: TabType,
145
+ tabIdentifier: string | null,
146
+ projectName: string,
147
+ ): void {
148
+ const { tabs, setActiveTab, openTab } = useTabStore.getState();
149
+ const expectedId = tabIdFromUrl(tabType, tabIdentifier);
150
+
151
+ // Check if tab already exists
152
+ const existing = tabs.find((t) => t.id === expectedId);
153
+ if (existing) {
154
+ setActiveTab(existing.id);
155
+ return;
156
+ }
157
+
158
+ // Auto-create tab from URL
159
+ const metadata = buildMetadataFromUrl(tabType, tabIdentifier, projectName);
160
+ if (!metadata) return;
161
+
162
+ openTab({
163
+ type: tabType,
164
+ title: buildTitleFromUrl(tabType, tabIdentifier),
165
+ projectId: projectName,
166
+ closable: true,
167
+ metadata,
168
+ });
169
+ }
170
+
171
+ // ---------------------------------------------------------------------------
172
+ // Hook: sync URL ↔ tab state
173
+ // ---------------------------------------------------------------------------
174
+
31
175
  /**
32
176
  * Sync tab/project state with browser URL.
33
- * - On tab/project change → pushState (enables back/forward navigation)
34
- * - On popstate (back/forward) → restore tab from URL
177
+ * - On tab/project change → pushState with type-based URL
178
+ * - On popstate (back/forward) → restore/create tab from URL
35
179
  */
36
180
  export function useUrlSync() {
37
181
  const activeTabId = useTabStore((s) => s.activeTabId);
@@ -40,7 +184,6 @@ export function useUrlSync() {
40
184
 
41
185
  // Push URL when active tab or project changes
42
186
  useEffect(() => {
43
- // Skip push if this change was triggered by popstate (back/forward)
44
187
  if (isPopState.current) {
45
188
  isPopState.current = false;
46
189
  return;
@@ -55,11 +198,20 @@ export function useUrlSync() {
55
198
  // Listen for back/forward navigation
56
199
  useEffect(() => {
57
200
  function handlePopState() {
58
- const { tabId } = parseUrlState();
201
+ const { tabType, tabIdentifier } = parseUrlState();
202
+ if (!tabType) return;
203
+
204
+ isPopState.current = true;
59
205
  const { tabs, setActiveTab } = useTabStore.getState();
60
- if (tabId && tabs.some((t) => t.id === tabId)) {
61
- isPopState.current = true;
62
- setActiveTab(tabId);
206
+ const expectedId = tabIdFromUrl(tabType, tabIdentifier);
207
+ const existing = tabs.find((t) => t.id === expectedId);
208
+
209
+ if (existing) {
210
+ setActiveTab(existing.id);
211
+ } else {
212
+ // Auto-open tab on back/forward if it was closed
213
+ const project = useTabStore.getState().currentProject;
214
+ if (project) autoOpenFromUrl(tabType, tabIdentifier, project);
63
215
  }
64
216
  }
65
217
 
@@ -0,0 +1,39 @@
1
+ import { create } from "zustand";
2
+
3
+ interface ConnectionState {
4
+ /** Whether the server is currently unreachable */
5
+ isDown: boolean;
6
+ /** Timestamp when the server first went down */
7
+ downSince: number | null;
8
+ /** Whether the overlay should be shown (down for > threshold) */
9
+ showOverlay: boolean;
10
+
11
+ markDown: () => void;
12
+ markUp: () => void;
13
+ }
14
+
15
+ /** How long the server must be unreachable before showing the overlay */
16
+ const OVERLAY_THRESHOLD_MS = 15_000;
17
+
18
+ export const useConnectionStore = create<ConnectionState>((set, get) => ({
19
+ isDown: false,
20
+ downSince: null,
21
+ showOverlay: false,
22
+
23
+ markDown: () => {
24
+ const { downSince } = get();
25
+ const now = Date.now();
26
+ const since = downSince ?? now;
27
+ const elapsed = now - since;
28
+
29
+ set({
30
+ isDown: true,
31
+ downSince: since,
32
+ showOverlay: elapsed >= OVERLAY_THRESHOLD_MS,
33
+ });
34
+ },
35
+
36
+ markUp: () => {
37
+ set({ isDown: false, downSince: null, showOverlay: false });
38
+ },
39
+ }));
@@ -0,0 +1,204 @@
1
+ import { create } from "zustand";
2
+ import type { ExtensionContributes, ContributedCommand } from "../../types/extension.ts";
3
+
4
+ // --- UI types for extension components ---
5
+
6
+ export interface StatusBarItemUI {
7
+ id: string;
8
+ text: string;
9
+ tooltip?: string;
10
+ command?: string;
11
+ alignment: "left" | "right";
12
+ priority: number;
13
+ extensionId?: string;
14
+ }
15
+
16
+ export interface TreeItemAction {
17
+ icon: "refresh" | "edit" | "trash" | "plus" | "search";
18
+ tooltip: string;
19
+ command: string;
20
+ commandArgs?: unknown[];
21
+ }
22
+
23
+ export interface TreeItemUI {
24
+ id: string;
25
+ label: string;
26
+ description?: string;
27
+ tooltip?: string;
28
+ icon?: string;
29
+ color?: string;
30
+ badge?: string;
31
+ actions?: TreeItemAction[];
32
+ collapsibleState: "none" | "collapsed" | "expanded";
33
+ command?: string;
34
+ commandArgs?: unknown[];
35
+ children?: TreeItemUI[];
36
+ contextValue?: string;
37
+ }
38
+
39
+ export interface WebviewPanelUI {
40
+ id: string;
41
+ extensionId: string;
42
+ viewType: string;
43
+ title: string;
44
+ html: string;
45
+ }
46
+
47
+ export interface QuickPickState {
48
+ items: QuickPickItemUI[];
49
+ options: { placeholder?: string; canPickMany?: boolean };
50
+ resolve: (selected: QuickPickItemUI[] | undefined) => void;
51
+ }
52
+
53
+ export interface QuickPickItemUI {
54
+ label: string;
55
+ description?: string;
56
+ detail?: string;
57
+ picked?: boolean;
58
+ }
59
+
60
+ export interface InputBoxState {
61
+ options: { prompt?: string; value?: string; placeholder?: string; password?: boolean };
62
+ resolve: (value: string | undefined) => void;
63
+ }
64
+
65
+ // --- Store ---
66
+
67
+ interface ExtensionStore {
68
+ // Status bar
69
+ statusBarItems: StatusBarItemUI[];
70
+ addStatusBarItem: (item: StatusBarItemUI) => void;
71
+ removeStatusBarItem: (id: string) => void;
72
+ updateStatusBarItem: (id: string, updates: Partial<StatusBarItemUI>) => void;
73
+
74
+ // Tree views
75
+ treeViews: Record<string, TreeItemUI[]>;
76
+ updateTree: (viewId: string, items: TreeItemUI[]) => void;
77
+ updateTreeChildren: (viewId: string, parentId: string, children: TreeItemUI[]) => void;
78
+ removeTree: (viewId: string) => void;
79
+
80
+ // Webview panels
81
+ webviewPanels: Record<string, WebviewPanelUI>;
82
+ addWebviewPanel: (panel: WebviewPanelUI) => void;
83
+ removeWebviewPanel: (id: string) => void;
84
+ updateWebviewPanel: (id: string, updates: Partial<WebviewPanelUI>) => void;
85
+
86
+ // Contributions (fetched from API)
87
+ contributions: ExtensionContributes | null;
88
+ setContributions: (c: ExtensionContributes) => void;
89
+
90
+ // QuickPick modal
91
+ quickPick: QuickPickState | null;
92
+ showQuickPick: (items: QuickPickItemUI[], options?: QuickPickState["options"]) => Promise<QuickPickItemUI[] | undefined>;
93
+ resolveQuickPick: (selected: QuickPickItemUI[] | undefined) => void;
94
+
95
+ // InputBox modal
96
+ inputBox: InputBoxState | null;
97
+ showInputBox: (options?: InputBoxState["options"]) => Promise<string | undefined>;
98
+ resolveInputBox: (value: string | undefined) => void;
99
+
100
+ // Cleanup
101
+ clearExtension: (extensionId: string) => void;
102
+ }
103
+
104
+ export const useExtensionStore = create<ExtensionStore>((set, get) => ({
105
+ // --- Status bar ---
106
+ statusBarItems: [],
107
+ addStatusBarItem: (item) => set((s) => ({
108
+ statusBarItems: [...s.statusBarItems.filter((i) => i.id !== item.id), item],
109
+ })),
110
+ removeStatusBarItem: (id) => set((s) => ({
111
+ statusBarItems: s.statusBarItems.filter((i) => i.id !== id),
112
+ })),
113
+ updateStatusBarItem: (id, updates) => set((s) => ({
114
+ statusBarItems: s.statusBarItems.map((i) => i.id === id ? { ...i, ...updates } : i),
115
+ })),
116
+
117
+ // --- Tree views ---
118
+ treeViews: {},
119
+ updateTree: (viewId, items) => set((s) => ({
120
+ treeViews: { ...s.treeViews, [viewId]: items },
121
+ })),
122
+ updateTreeChildren: (viewId, parentId, children) => set((s) => {
123
+ const items = s.treeViews[viewId];
124
+ if (!items) return s;
125
+ const merge = (nodes: TreeItemUI[]): TreeItemUI[] =>
126
+ nodes.map((n) => {
127
+ if (n.id === parentId) return { ...n, children, collapsibleState: "expanded" as const };
128
+ if (n.children) return { ...n, children: merge(n.children) };
129
+ return n;
130
+ });
131
+ return { treeViews: { ...s.treeViews, [viewId]: merge(items) } };
132
+ }),
133
+ removeTree: (viewId) => set((s) => {
134
+ const { [viewId]: _, ...rest } = s.treeViews;
135
+ return { treeViews: rest };
136
+ }),
137
+
138
+ // --- Webview panels ---
139
+ webviewPanels: {},
140
+ addWebviewPanel: (panel) => set((s) => ({
141
+ webviewPanels: { ...s.webviewPanels, [panel.id]: panel },
142
+ })),
143
+ removeWebviewPanel: (id) => set((s) => {
144
+ const { [id]: _, ...rest } = s.webviewPanels;
145
+ return { webviewPanels: rest };
146
+ }),
147
+ updateWebviewPanel: (id, updates) => set((s) => {
148
+ const existing = s.webviewPanels[id];
149
+ if (!existing) return s;
150
+ return { webviewPanels: { ...s.webviewPanels, [id]: { ...existing, ...updates } } };
151
+ }),
152
+
153
+ // --- Contributions ---
154
+ contributions: null,
155
+ setContributions: (c) => set({ contributions: c }),
156
+
157
+ // --- QuickPick ---
158
+ quickPick: null,
159
+ showQuickPick: (items, options = {}) => {
160
+ // Resolve any existing quickpick first (prevents promise leak)
161
+ const existing = get().quickPick;
162
+ if (existing) existing.resolve(undefined);
163
+ return new Promise((resolve) => {
164
+ set({ quickPick: { items, options, resolve } });
165
+ });
166
+ },
167
+ resolveQuickPick: (selected) => {
168
+ const qp = get().quickPick;
169
+ if (qp) {
170
+ qp.resolve(selected);
171
+ set({ quickPick: null });
172
+ }
173
+ },
174
+
175
+ // --- InputBox ---
176
+ inputBox: null,
177
+ showInputBox: (options = {}) => {
178
+ // Resolve any existing inputbox first (prevents promise leak)
179
+ const existing = get().inputBox;
180
+ if (existing) existing.resolve(undefined);
181
+ return new Promise((resolve) => {
182
+ set({ inputBox: { options, resolve } });
183
+ });
184
+ },
185
+ resolveInputBox: (value) => {
186
+ const ib = get().inputBox;
187
+ if (ib) {
188
+ ib.resolve(value);
189
+ set({ inputBox: null });
190
+ }
191
+ },
192
+
193
+ // --- Cleanup ---
194
+ clearExtension: (extensionId) => set((s) => {
195
+ const webviewPanels = { ...s.webviewPanels };
196
+ for (const [id, panel] of Object.entries(webviewPanels)) {
197
+ if (panel.extensionId === extensionId) delete webviewPanels[id];
198
+ }
199
+ return {
200
+ statusBarItems: s.statusBarItems.filter((i) => i.extensionId !== extensionId),
201
+ webviewPanels,
202
+ };
203
+ }),
204
+ }));
@@ -1,5 +1,4 @@
1
1
  import { create } from "zustand";
2
- import { randomId } from "@/lib/utils";
3
2
  import type { Tab, TabType } from "./tab-store";
4
3
  import {
5
4
  type Panel,
@@ -13,18 +12,15 @@ import {
13
12
  MAX_ROWS,
14
13
  savePanelLayout,
15
14
  loadPanelLayout,
15
+ deriveTabId,
16
16
  } from "./panel-utils";
17
17
 
18
18
  /** Tab types that can only have 1 instance per project */
19
- const SINGLETON_TYPES = new Set<TabType>(["git-graph"]);
19
+ const SINGLETON_TYPES = new Set<TabType>(["git-graph", "settings"]);
20
20
 
21
21
  /** Tab types removed in a prior version — filter them out when loading persisted state */
22
22
  const OBSOLETE_TAB_TYPES = new Set(["projects", "git-status"]);
23
23
 
24
- function generateTabId(): string {
25
- return `tab-${randomId()}`;
26
- }
27
-
28
24
  function pushHistory(history: string[], id: string): string[] {
29
25
  const filtered = history.filter((h) => h !== id);
30
26
  filtered.push(id);
@@ -47,6 +43,7 @@ export interface PanelStore {
47
43
 
48
44
  // Project lifecycle
49
45
  switchProject: (projectName: string) => void;
46
+ reloadProject: (projectName: string) => void;
50
47
 
51
48
  // Panel focus
52
49
  setFocusedPanel: (panelId: string) => void;
@@ -188,6 +185,25 @@ export const usePanelStore = create<PanelStore>()((set, get) => {
188
185
  }
189
186
  },
190
187
 
188
+ reloadProject: (projectName) => {
189
+ const { projectGrids, projectFocused, panels } = get();
190
+ // Clear in-memory cache so switchProject re-reads from localStorage
191
+ const newGrids = { ...projectGrids };
192
+ const newFocused = { ...projectFocused };
193
+ delete newGrids[projectName];
194
+ delete newFocused[projectName];
195
+
196
+ // Remove old panels belonging to this project from flat map
197
+ const oldGrid = projectGrids[projectName];
198
+ const oldPanelIds = oldGrid ? new Set(oldGrid.flat()) : new Set<string>();
199
+ const cleanedPanels = { ...panels };
200
+ for (const id of oldPanelIds) delete cleanedPanels[id];
201
+
202
+ set({ projectGrids: newGrids, projectFocused: newFocused, panels: cleanedPanels, currentProject: null });
203
+ // Re-trigger full load from localStorage
204
+ get().switchProject(projectName);
205
+ },
206
+
191
207
  setFocusedPanel: (panelId) => {
192
208
  if (get().panels[panelId]) set({ focusedPanelId: panelId });
193
209
  },
@@ -197,10 +213,25 @@ export const usePanelStore = create<PanelStore>()((set, get) => {
197
213
  const panel = get().panels[pid];
198
214
  if (!panel) return "";
199
215
 
200
- // Singleton check across ALL panels
216
+ // Terminal: compute next available index if not provided
217
+ if (tabDef.type === "terminal" && !tabDef.metadata?.terminalIndex) {
218
+ const allTabs = Object.values(get().panels).flatMap((p) => p.tabs);
219
+ const terminalNums = allTabs
220
+ .filter((t) => t.type === "terminal")
221
+ .map((t) => {
222
+ const match = t.id.match(/^terminal:(\d+)/);
223
+ return match ? parseInt(match[1]!, 10) : 0;
224
+ });
225
+ const nextIndex = terminalNums.length > 0 ? Math.max(...terminalNums) + 1 : 1;
226
+ tabDef = { ...tabDef, metadata: { ...tabDef.metadata, terminalIndex: nextIndex } };
227
+ }
228
+
229
+ const baseId = deriveTabId(tabDef.type, tabDef.metadata);
230
+
231
+ // Singleton check — focus existing across ALL panels
201
232
  if (SINGLETON_TYPES.has(tabDef.type)) {
202
233
  for (const p of Object.values(get().panels)) {
203
- const existing = p.tabs.find((t) => t.type === tabDef.type && t.projectId === tabDef.projectId);
234
+ const existing = p.tabs.find((t) => t.id === baseId);
204
235
  if (existing) {
205
236
  set((s) => ({
206
237
  focusedPanelId: p.id,
@@ -219,7 +250,30 @@ export const usePanelStore = create<PanelStore>()((set, get) => {
219
250
  }
220
251
  }
221
252
 
222
- const id = generateTabId();
253
+ // Non-singleton: dedup within SAME panel only
254
+ const currentPanel = get().panels[pid]!;
255
+ const existingInPanel = currentPanel.tabs.find((t) => t.id === baseId);
256
+ if (existingInPanel) {
257
+ set((s) => ({
258
+ panels: {
259
+ ...s.panels,
260
+ [pid]: {
261
+ ...currentPanel,
262
+ activeTabId: existingInPanel.id,
263
+ tabHistory: pushHistory(currentPanel.tabHistory, existingInPanel.id),
264
+ },
265
+ },
266
+ }));
267
+ persist();
268
+ return existingInPanel.id;
269
+ }
270
+
271
+ // Check if same base ID exists in OTHER panels (split case)
272
+ const existsElsewhere = Object.values(get().panels).some(
273
+ (p) => p.id !== pid && p.tabs.some((t) => t.id === baseId),
274
+ );
275
+ const id = existsElsewhere ? `${baseId}@${pid}` : baseId;
276
+
223
277
  const tab: Tab = { ...tabDef, id };
224
278
  set((s) => {
225
279
  const p = s.panels[pid]!;