@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,8 +1,10 @@
1
- import { Suspense, lazy } from "react";
2
- import { Loader2, Terminal, MessageSquare, GitBranch } from "lucide-react";
1
+ import { Suspense, lazy, useEffect, useState, useCallback } from "react";
2
+ import { ChevronDown, ChevronUp, Loader2, Terminal, MessageSquare, GitBranch, Pin, PinOff } from "lucide-react";
3
3
  import { usePanelStore } from "@/stores/panel-store";
4
4
  import { useProjectStore } from "@/stores/project-store";
5
5
  import type { TabType } from "@/stores/tab-store";
6
+ import { api, projectUrl } from "@/lib/api-client";
7
+ import type { SessionInfo } from "../../../types/chat";
6
8
  import { TabBar } from "./tab-bar";
7
9
  import { SplitDropOverlay } from "./split-drop-overlay";
8
10
  import { cn } from "@/lib/utils";
@@ -24,6 +26,7 @@ const TAB_COMPONENTS: Record<TabType, React.LazyExoticComponent<React.ComponentT
24
26
  "git-diff": lazy(() => import("@/components/editor/diff-viewer").then((m) => ({ default: m.DiffViewer }))),
25
27
  settings: lazy(() => import("@/components/settings/settings-tab").then((m) => ({ default: m.SettingsTab }))),
26
28
  browser: lazy(() => import("@/components/browser/browser-tab").then((m) => ({ default: m.BrowserTab }))),
29
+ "extension-webview": lazy(() => import("@/components/extensions/extension-webview").then((m) => ({ default: m.ExtensionWebview }))),
27
30
  };
28
31
 
29
32
  interface EditorPanelProps {
@@ -74,8 +77,70 @@ export function EditorPanel({ panelId, projectName }: EditorPanelProps) {
74
77
  );
75
78
  }
76
79
 
80
+ function formatRelativeDate(iso: string): string {
81
+ try {
82
+ const date = new Date(iso);
83
+ const now = new Date();
84
+ const diffMs = now.getTime() - date.getTime();
85
+ const diffMin = Math.floor(diffMs / 60_000);
86
+ if (diffMin < 1) return "Just now";
87
+ if (diffMin < 60) return `${diffMin}m ago`;
88
+ const diffHr = Math.floor(diffMin / 60);
89
+ if (diffHr < 24) return `${diffHr}h ago`;
90
+ const diffDay = Math.floor(diffHr / 24);
91
+ if (diffDay < 7) return `${diffDay}d ago`;
92
+ return date.toLocaleDateString(undefined, { month: "short", day: "numeric" });
93
+ } catch {
94
+ return "";
95
+ }
96
+ }
97
+
98
+ const MAX_RECENT_SESSIONS = 5;
99
+ const FETCH_SESSIONS_LIMIT = 20;
100
+
77
101
  function EmptyPanel({ panelId }: { panelId: string }) {
78
102
  const activeProject = useProjectStore((s) => s.activeProject);
103
+ const [sessions, setSessions] = useState<SessionInfo[]>([]);
104
+ const [loadingSessions, setLoadingSessions] = useState(false);
105
+ const [showAll, setShowAll] = useState(false);
106
+
107
+ const loadSessions = useCallback(async () => {
108
+ if (!activeProject?.name) return;
109
+ setLoadingSessions(true);
110
+ try {
111
+ const data = await api.get<SessionInfo[]>(`${projectUrl(activeProject.name)}/chat/sessions`);
112
+ setSessions(data.slice(0, FETCH_SESSIONS_LIMIT));
113
+ } catch {
114
+ // silently ignore — empty state still functional without sessions
115
+ } finally {
116
+ setLoadingSessions(false);
117
+ }
118
+ }, [activeProject?.name]);
119
+
120
+ useEffect(() => { loadSessions(); }, [loadSessions]);
121
+
122
+ const togglePin = useCallback(async (e: React.MouseEvent, session: SessionInfo) => {
123
+ e.stopPropagation();
124
+ if (!activeProject?.name) return;
125
+ const url = `${projectUrl(activeProject.name)}/chat/sessions/${session.id}/pin`;
126
+ try {
127
+ if (session.pinned) {
128
+ await api.del(url);
129
+ } else {
130
+ await api.put(url);
131
+ }
132
+ setSessions((prev) => {
133
+ const updated = prev.map((s) => s.id === session.id ? { ...s, pinned: !s.pinned } : s);
134
+ return updated.sort((a, b) => {
135
+ if (a.pinned && !b.pinned) return -1;
136
+ if (!a.pinned && b.pinned) return 1;
137
+ return new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime();
138
+ });
139
+ });
140
+ } catch {
141
+ // silently ignore
142
+ }
143
+ }, [activeProject?.name]);
79
144
 
80
145
  function openTab(type: TabType) {
81
146
  const needsProject = type !== "settings";
@@ -86,23 +151,103 @@ function EmptyPanel({ panelId }: { panelId: string }) {
86
151
  );
87
152
  }
88
153
 
154
+ function openSession(session: SessionInfo) {
155
+ usePanelStore.getState().openTab(
156
+ {
157
+ type: "chat",
158
+ title: session.title || "Chat",
159
+ projectId: activeProject?.name ?? null,
160
+ metadata: { projectName: activeProject?.name, sessionId: session.id, providerId: session.providerId },
161
+ closable: true,
162
+ },
163
+ panelId,
164
+ );
165
+ }
166
+
167
+ const pinnedSessions = sessions.filter((s) => s.pinned);
168
+ const allRecentSessions = sessions.filter((s) => !s.pinned);
169
+ const recentSessions = showAll ? allRecentSessions : allRecentSessions.slice(0, MAX_RECENT_SESSIONS);
170
+ const hasMore = allRecentSessions.length > MAX_RECENT_SESSIONS;
171
+
172
+ function renderSessionRow(session: SessionInfo) {
173
+ return (
174
+ <button
175
+ key={session.id}
176
+ onClick={() => openSession(session)}
177
+ className="group flex items-center gap-2.5 w-full px-3 py-2.5 text-left hover:bg-surface-elevated active:bg-surface-elevated transition-colors border-b border-border/50 last:border-0"
178
+ >
179
+ <MessageSquare className="size-3.5 shrink-0 text-text-subtle" />
180
+ <span className="flex-1 min-w-0 text-xs font-medium truncate text-text-primary">
181
+ {session.title || "Untitled"}
182
+ </span>
183
+ {session.updatedAt && (
184
+ <span className="text-[10px] text-text-subtle shrink-0">
185
+ {formatRelativeDate(session.updatedAt)}
186
+ </span>
187
+ )}
188
+ <span
189
+ role="button"
190
+ tabIndex={0}
191
+ onClick={(e) => togglePin(e, session)}
192
+ className={`p-1 rounded transition-colors shrink-0 ${
193
+ session.pinned
194
+ ? "text-primary hover:text-primary/70"
195
+ : "text-text-subtle md:opacity-0 md:group-hover:opacity-100 hover:text-text-primary"
196
+ }`}
197
+ aria-label={session.pinned ? "Unpin session" : "Pin session"}
198
+ >
199
+ {session.pinned ? <PinOff className="size-3" /> : <Pin className="size-3" />}
200
+ </span>
201
+ </button>
202
+ );
203
+ }
204
+
89
205
  return (
90
- <div className="flex flex-col items-center justify-center h-full gap-4 text-text-secondary">
91
- <p className="text-sm">Open a tab to get started</p>
92
- <div className="flex flex-col md:flex-row flex-wrap justify-center gap-2">
93
- {QUICK_OPEN_TABS.map((opt) => {
94
- const Icon = opt.icon;
95
- return (
96
- <button
97
- key={opt.type}
98
- onClick={() => openTab(opt.type)}
99
- className="flex items-center gap-2 px-4 py-2 rounded-md border border-border bg-surface hover:bg-surface-elevated text-sm text-foreground transition-colors"
100
- >
101
- <Icon className="size-4" />
102
- {opt.label}
103
- </button>
104
- );
105
- })}
206
+ <div className="flex flex-col h-full overflow-y-auto text-text-secondary">
207
+ <div className="flex flex-col items-center justify-center gap-6 px-4 flex-1">
208
+ <p className="text-sm">Open a tab to get started</p>
209
+ <div className="grid grid-cols-3 gap-2 w-full max-w-sm">
210
+ {QUICK_OPEN_TABS.map((opt) => {
211
+ const Icon = opt.icon;
212
+ return (
213
+ <button
214
+ key={opt.type}
215
+ onClick={() => openTab(opt.type)}
216
+ className="flex flex-col items-center justify-center gap-1.5 px-2 py-3 rounded-md border border-border bg-surface hover:bg-surface-elevated active:bg-surface-elevated text-xs text-foreground transition-colors"
217
+ >
218
+ <Icon className="size-5" />
219
+ {opt.label}
220
+ </button>
221
+ );
222
+ })}
223
+ </div>
224
+
225
+ {activeProject && !loadingSessions && pinnedSessions.length > 0 && (
226
+ <div className="flex flex-col gap-2 w-full max-w-sm">
227
+ <p className="text-xs text-text-subtle text-center">Pinned</p>
228
+ <div className="w-full rounded-md border border-border bg-surface overflow-hidden">
229
+ {pinnedSessions.map(renderSessionRow)}
230
+ </div>
231
+ </div>
232
+ )}
233
+
234
+ {activeProject && !loadingSessions && recentSessions.length > 0 && (
235
+ <div className="flex flex-col gap-2 w-full max-w-sm">
236
+ <p className="text-xs text-text-subtle text-center">Recent chats</p>
237
+ <div className="w-full rounded-md border border-border bg-surface overflow-hidden">
238
+ {recentSessions.map(renderSessionRow)}
239
+ </div>
240
+ {hasMore && (
241
+ <button
242
+ onClick={() => setShowAll(!showAll)}
243
+ className="flex items-center justify-center gap-1 text-[11px] text-text-subtle hover:text-text-primary transition-colors py-1"
244
+ >
245
+ {showAll ? <ChevronUp className="size-3" /> : <ChevronDown className="size-3" />}
246
+ {showAll ? "Show less" : `Show more (${allRecentSessions.length - MAX_RECENT_SESSIONS})`}
247
+ </button>
248
+ )}
249
+ </div>
250
+ )}
106
251
  </div>
107
252
  </div>
108
253
  );
@@ -2,7 +2,7 @@ import { useState, useEffect, useRef, useCallback } from "react";
2
2
  import {
3
3
  Terminal, MessageSquare, GitBranch, Database,
4
4
  FileDiff, FileCode, Settings, Menu, X, ArrowLeft, ArrowRight, SplitSquareVertical, MoveVertical, Layers, Plus,
5
- ChevronLeft, ChevronRight, Globe,
5
+ ChevronLeft, ChevronRight, Globe, Puzzle,
6
6
  } from "lucide-react";
7
7
  import { usePanelStore } from "@/stores/panel-store";
8
8
  import { useProjectStore, resolveOrder } from "@/stores/project-store";
@@ -26,6 +26,7 @@ const NEW_TAB_LABELS: Partial<Record<TabType, string>> = Object.fromEntries(NEW_
26
26
  const TAB_ICONS: Record<TabType, React.ElementType> = {
27
27
  terminal: Terminal, chat: MessageSquare, editor: FileCode, database: Database, sqlite: Database, postgres: Database,
28
28
  "git-graph": GitBranch, "git-diff": FileDiff, settings: Settings, browser: Globe,
29
+ "extension-webview": Puzzle,
29
30
  };
30
31
 
31
32
  interface MobileNavProps { onMenuPress: () => void; onProjectsPress: () => void; }
@@ -1,15 +1,17 @@
1
- import { useCallback, useRef } from "react";
2
- import { PanelLeftClose, PanelLeftOpen, FolderOpen, GitBranch, Settings, Database, Search } from "lucide-react";
1
+ import { useCallback, useRef, useMemo } from "react";
2
+ import { PanelLeftClose, PanelLeftOpen, FolderOpen, GitBranch, Settings, Database, Search, Puzzle } from "lucide-react";
3
3
  import { useProjectStore } from "@/stores/project-store";
4
4
  import { useSettingsStore, type SidebarActiveTab } from "@/stores/settings-store";
5
+ import { useExtensionStore } from "@/stores/extension-store";
5
6
  import { FileTree } from "@/components/explorer/file-tree";
6
7
  import { GitStatusPanel } from "@/components/git/git-status-panel";
7
8
  import { SettingsTab } from "@/components/settings/settings-tab";
8
9
  import { DatabaseSidebar } from "@/components/database/database-sidebar";
9
10
  import { SearchPanel } from "@/components/explorer/search-panel";
11
+ import { ExtensionTreeView } from "@/components/extensions/extension-tree-view";
10
12
  import { cn } from "@/lib/utils";
11
13
 
12
- const TABS: { id: SidebarActiveTab; label: string; icon: React.ElementType }[] = [
14
+ const BUILTIN_TABS: { id: SidebarActiveTab; label: string; icon: React.ElementType }[] = [
13
15
  { id: "explorer", label: "Explorer", icon: FolderOpen },
14
16
  { id: "search", label: "Search", icon: Search },
15
17
  { id: "git", label: "Git", icon: GitBranch },
@@ -63,6 +65,19 @@ export function Sidebar() {
63
65
  const setSidebarWidth = useSettingsStore((s) => s.setSidebarWidth);
64
66
  const sidebarActiveTab = useSettingsStore((s) => s.sidebarActiveTab);
65
67
  const setSidebarActiveTab = useSettingsStore((s) => s.setSidebarActiveTab);
68
+ const contributions = useExtensionStore((s) => s.contributions);
69
+
70
+ // Build tabs list: built-in + extension-contributed sidebar views
71
+ const TABS = useMemo(() => {
72
+ const tabs: { id: SidebarActiveTab; label: string; icon: React.ElementType }[] = [...BUILTIN_TABS];
73
+ if (contributions?.views) {
74
+ const sidebarViews = contributions.views["sidebar"] ?? contributions.views["explorer"] ?? [];
75
+ for (const view of sidebarViews) {
76
+ tabs.push({ id: `ext:${view.id}` as SidebarActiveTab, label: view.name, icon: Puzzle });
77
+ }
78
+ }
79
+ return tabs;
80
+ }, [contributions]);
66
81
 
67
82
  if (sidebarCollapsed) {
68
83
  return (
@@ -135,6 +150,9 @@ export function Sidebar() {
135
150
  {sidebarActiveTab === "settings" && (
136
151
  <SettingsTab />
137
152
  )}
153
+ {typeof sidebarActiveTab === "string" && sidebarActiveTab.startsWith("ext:") && (
154
+ <ExtensionTreeView viewId={sidebarActiveTab.slice(4)} className="h-full" />
155
+ )}
138
156
  </div>
139
157
 
140
158
  {/* Resize handle */}
@@ -0,0 +1,64 @@
1
+ import { useExtensionStore, type StatusBarItemUI } from "@/stores/extension-store";
2
+ import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
3
+
4
+ /** Fixed status bar at the bottom of the editor area (hidden on mobile) */
5
+ export function StatusBar() {
6
+ const items = useExtensionStore((s) => s.statusBarItems);
7
+
8
+ const left = items
9
+ .filter((i) => i.alignment === "left")
10
+ .sort((a, b) => b.priority - a.priority);
11
+
12
+ const right = items
13
+ .filter((i) => i.alignment === "right")
14
+ .sort((a, b) => b.priority - a.priority);
15
+
16
+ return (
17
+ <div className="hidden md:flex items-center justify-between h-[22px] px-2 bg-surface border-t border-border text-[11px] text-text-subtle select-none shrink-0">
18
+ <div className="flex items-center gap-2 min-w-0">
19
+ {left.map((item) => (
20
+ <StatusBarEntry key={item.id} item={item} />
21
+ ))}
22
+ </div>
23
+ <div className="flex items-center gap-2 min-w-0">
24
+ {right.map((item) => (
25
+ <StatusBarEntry key={item.id} item={item} />
26
+ ))}
27
+ </div>
28
+ </div>
29
+ );
30
+ }
31
+
32
+ function StatusBarEntry({ item }: { item: StatusBarItemUI }) {
33
+ const content = (
34
+ <button
35
+ className={`truncate px-1 rounded-sm transition-colors ${
36
+ item.command
37
+ ? "hover:bg-accent/15 hover:text-text-primary cursor-pointer"
38
+ : "cursor-default"
39
+ }`}
40
+ onClick={() => {
41
+ if (item.command) {
42
+ window.dispatchEvent(new CustomEvent("ext:command:execute", {
43
+ detail: { command: item.command },
44
+ }));
45
+ }
46
+ }}
47
+ >
48
+ {item.text}
49
+ </button>
50
+ );
51
+
52
+ if (item.tooltip) {
53
+ return (
54
+ <Tooltip>
55
+ <TooltipTrigger asChild>{content}</TooltipTrigger>
56
+ <TooltipContent side="top" className="text-xs">
57
+ {item.tooltip}
58
+ </TooltipContent>
59
+ </Tooltip>
60
+ );
61
+ }
62
+
63
+ return content;
64
+ }
@@ -11,6 +11,7 @@ import {
11
11
  ChevronLeft,
12
12
  ChevronRight,
13
13
  Globe,
14
+ Puzzle,
14
15
  } from "lucide-react";
15
16
  import { useTabStore, type TabType } from "@/stores/tab-store";
16
17
  import { usePanelStore } from "@/stores/panel-store";
@@ -35,6 +36,7 @@ const TAB_ICONS: Record<TabType, React.ElementType> = {
35
36
  "git-diff": FileDiff,
36
37
  settings: Settings,
37
38
  browser: Globe,
39
+ "extension-webview": Puzzle,
38
40
  };
39
41
 
40
42
  interface TabBarProps {
@@ -53,6 +53,11 @@ const TAB_COMPONENTS: Record<TabType, React.LazyExoticComponent<React.ComponentT
53
53
  default: m.BrowserTab,
54
54
  })),
55
55
  ),
56
+ "extension-webview": lazy(() =>
57
+ import("@/components/extensions/extension-webview").then((m) => ({
58
+ default: m.ExtensionWebview,
59
+ })),
60
+ ),
56
61
  };
57
62
 
58
63
  function LoadingFallback() {
@@ -12,7 +12,11 @@ interface UpgradeStatus {
12
12
  installMethod: string;
13
13
  }
14
14
 
15
- export function UpgradeBanner() {
15
+ interface UpgradeBannerProps {
16
+ onVisibilityChange?: (visible: boolean) => void;
17
+ }
18
+
19
+ export function UpgradeBanner({ onVisibilityChange }: UpgradeBannerProps) {
16
20
  const [availableVersion, setAvailableVersion] = useState<string | null>(null);
17
21
  const [upgrading, setUpgrading] = useState(false);
18
22
  const [dismissed, setDismissed] = useState(false);
@@ -61,10 +65,16 @@ export function UpgradeBanner() {
61
65
  setDismissed(true);
62
66
  }, [availableVersion]);
63
67
 
64
- if (!availableVersion || dismissed) return null;
68
+ const visible = !!availableVersion && !dismissed;
69
+
70
+ useEffect(() => {
71
+ onVisibilityChange?.(visible);
72
+ }, [visible, onVisibilityChange]);
73
+
74
+ if (!visible) return null;
65
75
 
66
76
  return (
67
- <div className="w-full bg-blue-600 dark:bg-blue-700 text-white px-3 py-2 flex items-center justify-between gap-2 z-50 text-sm shrink-0">
77
+ <div className="w-full bg-blue-600 dark:bg-blue-700 text-white px-3 py-1 flex items-center justify-between gap-2 z-50 text-sm shrink-0">
68
78
  {upgrading ? (
69
79
  <div className="flex items-center gap-2 flex-1 min-w-0">
70
80
  <Loader2 className="size-4 animate-spin shrink-0" />
@@ -83,13 +93,13 @@ export function UpgradeBanner() {
83
93
  <div className="flex items-center gap-1 shrink-0">
84
94
  <button
85
95
  onClick={handleUpgrade}
86
- className="bg-white text-blue-600 font-medium rounded-full px-3 min-h-[44px] min-w-[44px] flex items-center justify-center hover:bg-blue-50 active:bg-blue-100 transition-colors"
96
+ className="bg-white text-blue-600 font-medium rounded-full px-3 py-0.5 text-xs min-h-[28px] min-w-[28px] flex items-center justify-center hover:bg-blue-50 active:bg-blue-100 transition-colors"
87
97
  >
88
98
  Upgrade
89
99
  </button>
90
100
  <button
91
101
  onClick={handleDismiss}
92
- className="min-h-[44px] min-w-[44px] flex items-center justify-center rounded-full hover:bg-blue-500 active:bg-blue-800 transition-colors"
102
+ className="min-h-[28px] min-w-[28px] flex items-center justify-center rounded-full hover:bg-blue-500 active:bg-blue-800 transition-colors"
93
103
  aria-label="Dismiss upgrade notification"
94
104
  >
95
105
  <X className="size-4" />
@@ -0,0 +1,128 @@
1
+ import { useState, useCallback } from "react";
2
+ import { KeyRound, Check, Eye, EyeOff } from "lucide-react";
3
+ import { Button } from "@/components/ui/button";
4
+ import { Input } from "@/components/ui/input";
5
+ import { api } from "@/lib/api-client";
6
+ import { setAuthToken } from "@/lib/api-client";
7
+
8
+ export function ChangePasswordSection() {
9
+ const [open, setOpen] = useState(false);
10
+ const [password, setPassword] = useState("");
11
+ const [confirm, setConfirm] = useState("");
12
+ const [showPw, setShowPw] = useState(false);
13
+ const [saving, setSaving] = useState(false);
14
+ const [saved, setSaved] = useState(false);
15
+ const [error, setError] = useState<string | null>(null);
16
+
17
+ const mismatch = confirm.length > 0 && password !== confirm;
18
+ const canSubmit = password.trim().length >= 4 && password === confirm && !saving;
19
+
20
+ const handleSubmit = useCallback(async () => {
21
+ if (!canSubmit) return;
22
+ setSaving(true);
23
+ setError(null);
24
+ try {
25
+ const { token } = await api.put<{ token: string }>("/api/settings/auth/password", {
26
+ password: password.trim(),
27
+ confirm: confirm.trim(),
28
+ });
29
+ // Update localStorage so current session stays authenticated
30
+ setAuthToken(token);
31
+ setSaved(true);
32
+ setPassword("");
33
+ setConfirm("");
34
+ setTimeout(() => {
35
+ setSaved(false);
36
+ setOpen(false);
37
+ }, 1500);
38
+ } catch (e) {
39
+ setError(e instanceof Error ? e.message : "Failed to change password");
40
+ } finally {
41
+ setSaving(false);
42
+ }
43
+ }, [canSubmit, password, confirm]);
44
+
45
+ if (!open) {
46
+ return (
47
+ <section className="space-y-2">
48
+ <h3 className="text-xs font-medium text-muted-foreground">Security</h3>
49
+ <Button
50
+ variant="outline"
51
+ size="sm"
52
+ className="h-8 text-xs gap-1.5 cursor-pointer"
53
+ onClick={() => setOpen(true)}
54
+ >
55
+ <KeyRound className="size-3.5" />
56
+ Change Password
57
+ </Button>
58
+ </section>
59
+ );
60
+ }
61
+
62
+ return (
63
+ <section className="space-y-2">
64
+ <h3 className="text-xs font-medium text-muted-foreground">Change Password</h3>
65
+ <div className="space-y-2">
66
+ <div className="relative">
67
+ <Input
68
+ type={showPw ? "text" : "password"}
69
+ placeholder="New password"
70
+ value={password}
71
+ onChange={(e) => setPassword(e.target.value)}
72
+ className="h-8 text-xs pr-8"
73
+ autoFocus
74
+ />
75
+ <button
76
+ type="button"
77
+ className="absolute right-2 top-1/2 -translate-y-1/2 text-muted-foreground cursor-pointer"
78
+ onClick={() => setShowPw(!showPw)}
79
+ tabIndex={-1}
80
+ >
81
+ {showPw ? <EyeOff className="size-3.5" /> : <Eye className="size-3.5" />}
82
+ </button>
83
+ </div>
84
+ <Input
85
+ type={showPw ? "text" : "password"}
86
+ placeholder="Confirm password"
87
+ value={confirm}
88
+ onChange={(e) => setConfirm(e.target.value)}
89
+ onKeyDown={(e) => { if (e.key === "Enter") handleSubmit(); }}
90
+ className="h-8 text-xs"
91
+ />
92
+ {mismatch && (
93
+ <p className="text-[11px] text-destructive">Passwords do not match</p>
94
+ )}
95
+ {error && (
96
+ <p className="text-[11px] text-destructive">{error}</p>
97
+ )}
98
+ <div className="flex gap-1.5">
99
+ <Button
100
+ variant="outline"
101
+ size="sm"
102
+ className="h-8 text-xs flex-1 cursor-pointer"
103
+ onClick={() => {
104
+ setOpen(false);
105
+ setPassword("");
106
+ setConfirm("");
107
+ setError(null);
108
+ }}
109
+ >
110
+ Cancel
111
+ </Button>
112
+ <Button
113
+ variant={saved ? "default" : "outline"}
114
+ size="sm"
115
+ className="h-8 text-xs flex-1 cursor-pointer"
116
+ disabled={!canSubmit}
117
+ onClick={handleSubmit}
118
+ >
119
+ {saving ? "..." : saved ? <Check className="size-3.5" /> : "Save"}
120
+ </Button>
121
+ </div>
122
+ <p className="text-[11px] text-muted-foreground">
123
+ Min 4 characters. You'll stay logged in on this device.
124
+ </p>
125
+ </div>
126
+ </section>
127
+ );
128
+ }