@hienlh/ppm 0.13.49 → 0.13.50

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 (52) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/assets/skills/ppm/SKILL.md +1 -1
  3. package/assets/skills/ppm/references/http-api.md +1 -1
  4. package/dist/web/assets/{audio-preview-CQ-l5P8I.js → audio-preview-CILFIsuu.js} +2 -2
  5. package/dist/web/assets/{audio-preview-CQ-l5P8I.js.map → audio-preview-CILFIsuu.js.map} +1 -1
  6. package/dist/web/assets/{chat-tab-DbuBr2ax.js → chat-tab-DBYwH_Aa.js} +4 -4
  7. package/dist/web/assets/{chat-tab-DbuBr2ax.js.map → chat-tab-DBYwH_Aa.js.map} +1 -1
  8. package/dist/web/assets/{code-editor-DEa0t62y.js → code-editor-MXnkYRLp.js} +3 -3
  9. package/dist/web/assets/{code-editor-DEa0t62y.js.map → code-editor-MXnkYRLp.js.map} +1 -1
  10. package/dist/web/assets/{conflict-editor-D5H9urYy.js → conflict-editor-C6wH5wV6.js} +2 -2
  11. package/dist/web/assets/{conflict-editor-D5H9urYy.js.map → conflict-editor-C6wH5wV6.js.map} +1 -1
  12. package/dist/web/assets/{database-viewer-CW60ytCl.js → database-viewer-BjUruZLv.js} +2 -2
  13. package/dist/web/assets/{database-viewer-CW60ytCl.js.map → database-viewer-BjUruZLv.js.map} +1 -1
  14. package/dist/web/assets/{diff-viewer-BfatMgWw.js → diff-viewer-B_nU7bQi.js} +2 -2
  15. package/dist/web/assets/{diff-viewer-BfatMgWw.js.map → diff-viewer-B_nU7bQi.js.map} +1 -1
  16. package/dist/web/assets/{extension-webview-DKSDoW_g.js → extension-webview-B56ZfvoD.js} +2 -2
  17. package/dist/web/assets/{extension-webview-DKSDoW_g.js.map → extension-webview-B56ZfvoD.js.map} +1 -1
  18. package/dist/web/assets/{glide-data-grid-Bx48618B.js → glide-data-grid-D-qQqqp7.js} +2 -2
  19. package/dist/web/assets/{glide-data-grid-Bx48618B.js.map → glide-data-grid-D-qQqqp7.js.map} +1 -1
  20. package/dist/web/assets/{image-preview-ClY2xl1B.js → image-preview-Dc6AiqYX.js} +2 -2
  21. package/dist/web/assets/{image-preview-ClY2xl1B.js.map → image-preview-Dc6AiqYX.js.map} +1 -1
  22. package/dist/web/assets/{index-DkQ6jVSH.js → index-8_rE2Q1-.js} +5 -5
  23. package/dist/web/assets/{index-DkQ6jVSH.js.map → index-8_rE2Q1-.js.map} +1 -1
  24. package/dist/web/assets/keybindings-store-COJD5O6M.js +1 -0
  25. package/dist/web/assets/{markdown-renderer-BmMmo0F-.js → markdown-renderer-CNQ8I0Dk.js} +2 -2
  26. package/dist/web/assets/{markdown-renderer-BmMmo0F-.js.map → markdown-renderer-CNQ8I0Dk.js.map} +1 -1
  27. package/dist/web/assets/notification-store-BiZaLXop.js +1 -0
  28. package/dist/web/assets/{panel-store-Dy8-7E_g.js → panel-store-C8wwxBpn.js} +2 -2
  29. package/dist/web/assets/{panel-store-Dy8-7E_g.js.map → panel-store-C8wwxBpn.js.map} +1 -1
  30. package/dist/web/assets/{pdf-preview-YylEP_Su.js → pdf-preview-zs9QdgDp.js} +2 -2
  31. package/dist/web/assets/{pdf-preview-YylEP_Su.js.map → pdf-preview-zs9QdgDp.js.map} +1 -1
  32. package/dist/web/assets/{port-forwarding-tab-COdo70kU.js → port-forwarding-tab-sArYx1nt.js} +2 -2
  33. package/dist/web/assets/{port-forwarding-tab-COdo70kU.js.map → port-forwarding-tab-sArYx1nt.js.map} +1 -1
  34. package/dist/web/assets/{postgres-viewer-3y9VshZZ.js → postgres-viewer-khk7N7cd.js} +2 -2
  35. package/dist/web/assets/{postgres-viewer-3y9VshZZ.js.map → postgres-viewer-khk7N7cd.js.map} +1 -1
  36. package/dist/web/assets/{settings-tab-sYavdJk-.js → settings-tab-CGWhVzQm.js} +1 -1
  37. package/dist/web/assets/{sql-query-editor-DlBYx1Ye.js → sql-query-editor-B5Ndypxp.js} +2 -2
  38. package/dist/web/assets/{sql-query-editor-DlBYx1Ye.js.map → sql-query-editor-B5Ndypxp.js.map} +1 -1
  39. package/dist/web/assets/{sqlite-viewer-Bj8oPYho.js → sqlite-viewer-BkpONSGa.js} +2 -2
  40. package/dist/web/assets/{sqlite-viewer-Bj8oPYho.js.map → sqlite-viewer-BkpONSGa.js.map} +1 -1
  41. package/dist/web/assets/{tab-store-Dtg1_qL0.js → tab-store-CNas5Ny8.js} +2 -2
  42. package/dist/web/assets/{tab-store-Dtg1_qL0.js.map → tab-store-CNas5Ny8.js.map} +1 -1
  43. package/dist/web/assets/{terminal-tab-B7ECmf95.js → terminal-tab-BgMCsdeN.js} +2 -2
  44. package/dist/web/assets/{terminal-tab-B7ECmf95.js.map → terminal-tab-BgMCsdeN.js.map} +1 -1
  45. package/dist/web/assets/{video-preview-CT78iZBo.js → video-preview-w8ZAy8av.js} +2 -2
  46. package/dist/web/assets/{video-preview-CT78iZBo.js.map → video-preview-w8ZAy8av.js.map} +1 -1
  47. package/dist/web/index.html +3 -3
  48. package/dist/web/sw.js +1 -1
  49. package/package.json +1 -1
  50. package/src/web/stores/panel-store.ts +5 -0
  51. package/dist/web/assets/keybindings-store-DrAeg6Gw.js +0 -1
  52. package/dist/web/assets/notification-store-Bukl8bKo.js +0 -1
@@ -1 +1 @@
1
- {"version":3,"mappings":"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","names":["useLayoutEffect","useEffect"],"ignoreList":[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,26,27],"sources":["../../../node_modules/.bun/lucide-react@0.577.0+b1ab299f0a400331/node_modules/lucide-react/dist/esm/icons/activity.js","../../../node_modules/.bun/lucide-react@0.577.0+b1ab299f0a400331/node_modules/lucide-react/dist/esm/icons/calendar-x-2.js","../../../node_modules/.bun/lucide-react@0.577.0+b1ab299f0a400331/node_modules/lucide-react/dist/esm/icons/circle-x.js","../../../node_modules/.bun/lucide-react@0.577.0+b1ab299f0a400331/node_modules/lucide-react/dist/esm/icons/clipboard-check.js","../../../node_modules/.bun/lucide-react@0.577.0+b1ab299f0a400331/node_modules/lucide-react/dist/esm/icons/clipboard-list.js","../../../node_modules/.bun/lucide-react@0.577.0+b1ab299f0a400331/node_modules/lucide-react/dist/esm/icons/hand.js","../../../node_modules/.bun/lucide-react@0.577.0+b1ab299f0a400331/node_modules/lucide-react/dist/esm/icons/history.js","../../../node_modules/.bun/lucide-react@0.577.0+b1ab299f0a400331/node_modules/lucide-react/dist/esm/icons/image.js","../../../node_modules/.bun/lucide-react@0.577.0+b1ab299f0a400331/node_modules/lucide-react/dist/esm/icons/list-ordered.js","../../../node_modules/.bun/lucide-react@0.577.0+b1ab299f0a400331/node_modules/lucide-react/dist/esm/icons/list-todo.js","../../../node_modules/.bun/lucide-react@0.577.0+b1ab299f0a400331/node_modules/lucide-react/dist/esm/icons/maximize-2.js","../../../node_modules/.bun/lucide-react@0.577.0+b1ab299f0a400331/node_modules/lucide-react/dist/esm/icons/mic-off.js","../../../node_modules/.bun/lucide-react@0.577.0+b1ab299f0a400331/node_modules/lucide-react/dist/esm/icons/minimize-2.js","../../../node_modules/.bun/lucide-react@0.577.0+b1ab299f0a400331/node_modules/lucide-react/dist/esm/icons/paperclip.js","../../../node_modules/.bun/lucide-react@0.577.0+b1ab299f0a400331/node_modules/lucide-react/dist/esm/icons/shield-alert.js","../../../node_modules/.bun/lucide-react@0.577.0+b1ab299f0a400331/node_modules/lucide-react/dist/esm/icons/shield-off.js","../../../node_modules/.bun/lucide-react@0.577.0+b1ab299f0a400331/node_modules/lucide-react/dist/esm/icons/slash.js","../../../node_modules/.bun/lucide-react@0.577.0+b1ab299f0a400331/node_modules/lucide-react/dist/esm/icons/square-terminal.js","../../../node_modules/.bun/lucide-react@0.577.0+b1ab299f0a400331/node_modules/lucide-react/dist/esm/icons/tags.js","../../../node_modules/.bun/lucide-react@0.577.0+b1ab299f0a400331/node_modules/lucide-react/dist/esm/icons/users.js","../../../node_modules/.bun/lucide-react@0.577.0+b1ab299f0a400331/node_modules/lucide-react/dist/esm/icons/zap.js","../../../src/web/hooks/use-websocket.ts","../../../src/web/lib/flatten-expansions.ts","../../../src/web/lib/notification-sounds.ts","../../../src/web/hooks/use-chat.ts","../../../src/web/hooks/use-usage.ts","../../../node_modules/.bun/use-stick-to-bottom@1.1.3+b1ab299f0a400331/node_modules/use-stick-to-bottom/dist/useStickToBottom.js","../../../node_modules/.bun/use-stick-to-bottom@1.1.3+b1ab299f0a400331/node_modules/use-stick-to-bottom/dist/StickToBottom.js","../../../src/web/components/chat/tool-cards.tsx","../../../src/web/components/chat/pre-compact-button.tsx","../../../src/web/components/shared/markdown-error-boundary.tsx","../../../src/web/components/chat/chat-welcome.tsx","../../../src/web/components/chat/question-card.tsx","../../../src/web/components/chat/message-list.tsx","../../../src/web/hooks/use-voice-input.ts","../../../src/web/lib/file-support.ts","../../../src/web/components/chat/attachment-chips.tsx","../../../src/web/components/chat/mode-selector.tsx","../../../src/web/components/chat/message-input.tsx","../../../src/shared/fuzzy-search.ts","../../../src/web/components/chat/slash-command-picker.tsx","../../../src/web/components/chat/file-picker.tsx","../../../src/web/components/settings/tag-settings-section.tsx","../../../src/web/components/chat/account-dialogs.tsx","../../../src/web/components/chat/account-rotation-settings.tsx","../../../src/web/components/chat/usage-pattern-chart.tsx","../../../src/web/components/chat/usage-badge.tsx","../../../src/web/components/chat/team-activity-panel.tsx","../../../src/web/components/chat/chat-history-bar.tsx","../../../src/web/components/chat/chat-tab.tsx"],"sourcesContent":["/**\n * @license lucide-react v0.577.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.js';\n\nconst __iconNode = [\n [\n \"path\",\n {\n d: \"M22 12h-2.48a2 2 0 0 0-1.93 1.46l-2.35 8.36a.25.25 0 0 1-.48 0L9.24 2.18a.25.25 0 0 0-.48 0l-2.35 8.36A2 2 0 0 1 4.49 12H2\",\n key: \"169zse\"\n }\n ]\n];\nconst Activity = createLucideIcon(\"activity\", __iconNode);\n\nexport { __iconNode, Activity as default };\n//# sourceMappingURL=activity.js.map\n","/**\n * @license lucide-react v0.577.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.js';\n\nconst __iconNode = [\n [\"path\", { d: \"M8 2v4\", key: \"1cmpym\" }],\n [\"path\", { d: \"M16 2v4\", key: \"4m81vk\" }],\n [\"path\", { d: \"M21 13V6a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h8\", key: \"3spt84\" }],\n [\"path\", { d: \"M3 10h18\", key: \"8toen8\" }],\n [\"path\", { d: \"m17 22 5-5\", key: \"1k6ppv\" }],\n [\"path\", { d: \"m17 17 5 5\", key: \"p7ous7\" }]\n];\nconst CalendarX2 = createLucideIcon(\"calendar-x-2\", __iconNode);\n\nexport { __iconNode, CalendarX2 as default };\n//# sourceMappingURL=calendar-x-2.js.map\n","/**\n * @license lucide-react v0.577.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.js';\n\nconst __iconNode = [\n [\"circle\", { cx: \"12\", cy: \"12\", r: \"10\", key: \"1mglay\" }],\n [\"path\", { d: \"m15 9-6 6\", key: \"1uzhvr\" }],\n [\"path\", { d: \"m9 9 6 6\", key: \"z0biqf\" }]\n];\nconst CircleX = createLucideIcon(\"circle-x\", __iconNode);\n\nexport { __iconNode, CircleX as default };\n//# sourceMappingURL=circle-x.js.map\n","/**\n * @license lucide-react v0.577.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.js';\n\nconst __iconNode = [\n [\"rect\", { width: \"8\", height: \"4\", x: \"8\", y: \"2\", rx: \"1\", ry: \"1\", key: \"tgr4d6\" }],\n [\n \"path\",\n {\n d: \"M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2\",\n key: \"116196\"\n }\n ],\n [\"path\", { d: \"m9 14 2 2 4-4\", key: \"df797q\" }]\n];\nconst ClipboardCheck = createLucideIcon(\"clipboard-check\", __iconNode);\n\nexport { __iconNode, ClipboardCheck as default };\n//# sourceMappingURL=clipboard-check.js.map\n","/**\n * @license lucide-react v0.577.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.js';\n\nconst __iconNode = [\n [\"rect\", { width: \"8\", height: \"4\", x: \"8\", y: \"2\", rx: \"1\", ry: \"1\", key: \"tgr4d6\" }],\n [\n \"path\",\n {\n d: \"M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2\",\n key: \"116196\"\n }\n ],\n [\"path\", { d: \"M12 11h4\", key: \"1jrz19\" }],\n [\"path\", { d: \"M12 16h4\", key: \"n85exb\" }],\n [\"path\", { d: \"M8 11h.01\", key: \"1dfujw\" }],\n [\"path\", { d: \"M8 16h.01\", key: \"18s6g9\" }]\n];\nconst ClipboardList = createLucideIcon(\"clipboard-list\", __iconNode);\n\nexport { __iconNode, ClipboardList as default };\n//# sourceMappingURL=clipboard-list.js.map\n","/**\n * @license lucide-react v0.577.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.js';\n\nconst __iconNode = [\n [\"path\", { d: \"M18 11V6a2 2 0 0 0-2-2a2 2 0 0 0-2 2\", key: \"1fvzgz\" }],\n [\"path\", { d: \"M14 10V4a2 2 0 0 0-2-2a2 2 0 0 0-2 2v2\", key: \"1kc0my\" }],\n [\"path\", { d: \"M10 10.5V6a2 2 0 0 0-2-2a2 2 0 0 0-2 2v8\", key: \"10h0bg\" }],\n [\n \"path\",\n {\n d: \"M18 8a2 2 0 1 1 4 0v6a8 8 0 0 1-8 8h-2c-2.8 0-4.5-.86-5.99-2.34l-3.6-3.6a2 2 0 0 1 2.83-2.82L7 15\",\n key: \"1s1gnw\"\n }\n ]\n];\nconst Hand = createLucideIcon(\"hand\", __iconNode);\n\nexport { __iconNode, Hand as default };\n//# sourceMappingURL=hand.js.map\n","/**\n * @license lucide-react v0.577.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.js';\n\nconst __iconNode = [\n [\"path\", { d: \"M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8\", key: \"1357e3\" }],\n [\"path\", { d: \"M3 3v5h5\", key: \"1xhq8a\" }],\n [\"path\", { d: \"M12 7v5l4 2\", key: \"1fdv2h\" }]\n];\nconst History = createLucideIcon(\"history\", __iconNode);\n\nexport { __iconNode, History as default };\n//# sourceMappingURL=history.js.map\n","/**\n * @license lucide-react v0.577.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.js';\n\nconst __iconNode = [\n [\"rect\", { width: \"18\", height: \"18\", x: \"3\", y: \"3\", rx: \"2\", ry: \"2\", key: \"1m3agn\" }],\n [\"circle\", { cx: \"9\", cy: \"9\", r: \"2\", key: \"af1f0g\" }],\n [\"path\", { d: \"m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21\", key: \"1xmnt7\" }]\n];\nconst Image = createLucideIcon(\"image\", __iconNode);\n\nexport { __iconNode, Image as default };\n//# sourceMappingURL=image.js.map\n","/**\n * @license lucide-react v0.577.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.js';\n\nconst __iconNode = [\n [\"path\", { d: \"M11 5h10\", key: \"1cz7ny\" }],\n [\"path\", { d: \"M11 12h10\", key: \"1438ji\" }],\n [\"path\", { d: \"M11 19h10\", key: \"11t30w\" }],\n [\"path\", { d: \"M4 4h1v5\", key: \"10yrso\" }],\n [\"path\", { d: \"M4 9h2\", key: \"r1h2o0\" }],\n [\"path\", { d: \"M6.5 20H3.4c0-1 2.6-1.925 2.6-3.5a1.5 1.5 0 0 0-2.6-1.02\", key: \"xtkcd5\" }]\n];\nconst ListOrdered = createLucideIcon(\"list-ordered\", __iconNode);\n\nexport { __iconNode, ListOrdered as default };\n//# sourceMappingURL=list-ordered.js.map\n","/**\n * @license lucide-react v0.577.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.js';\n\nconst __iconNode = [\n [\"path\", { d: \"M13 5h8\", key: \"a7qcls\" }],\n [\"path\", { d: \"M13 12h8\", key: \"h98zly\" }],\n [\"path\", { d: \"M13 19h8\", key: \"c3s6r1\" }],\n [\"path\", { d: \"m3 17 2 2 4-4\", key: \"1jhpwq\" }],\n [\"rect\", { x: \"3\", y: \"4\", width: \"6\", height: \"6\", rx: \"1\", key: \"cif1o7\" }]\n];\nconst ListTodo = createLucideIcon(\"list-todo\", __iconNode);\n\nexport { __iconNode, ListTodo as default };\n//# sourceMappingURL=list-todo.js.map\n","/**\n * @license lucide-react v0.577.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.js';\n\nconst __iconNode = [\n [\"path\", { d: \"M15 3h6v6\", key: \"1q9fwt\" }],\n [\"path\", { d: \"m21 3-7 7\", key: \"1l2asr\" }],\n [\"path\", { d: \"m3 21 7-7\", key: \"tjx5ai\" }],\n [\"path\", { d: \"M9 21H3v-6\", key: \"wtvkvv\" }]\n];\nconst Maximize2 = createLucideIcon(\"maximize-2\", __iconNode);\n\nexport { __iconNode, Maximize2 as default };\n//# sourceMappingURL=maximize-2.js.map\n","/**\n * @license lucide-react v0.577.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.js';\n\nconst __iconNode = [\n [\"path\", { d: \"M12 19v3\", key: \"npa21l\" }],\n [\"path\", { d: \"M15 9.34V5a3 3 0 0 0-5.68-1.33\", key: \"1gzdoj\" }],\n [\"path\", { d: \"M16.95 16.95A7 7 0 0 1 5 12v-2\", key: \"cqa7eg\" }],\n [\"path\", { d: \"M18.89 13.23A7 7 0 0 0 19 12v-2\", key: \"16hl24\" }],\n [\"path\", { d: \"m2 2 20 20\", key: \"1ooewy\" }],\n [\"path\", { d: \"M9 9v3a3 3 0 0 0 5.12 2.12\", key: \"r2i35w\" }]\n];\nconst MicOff = createLucideIcon(\"mic-off\", __iconNode);\n\nexport { __iconNode, MicOff as default };\n//# sourceMappingURL=mic-off.js.map\n","/**\n * @license lucide-react v0.577.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.js';\n\nconst __iconNode = [\n [\"path\", { d: \"m14 10 7-7\", key: \"oa77jy\" }],\n [\"path\", { d: \"M20 10h-6V4\", key: \"mjg0md\" }],\n [\"path\", { d: \"m3 21 7-7\", key: \"tjx5ai\" }],\n [\"path\", { d: \"M4 14h6v6\", key: \"rmj7iw\" }]\n];\nconst Minimize2 = createLucideIcon(\"minimize-2\", __iconNode);\n\nexport { __iconNode, Minimize2 as default };\n//# sourceMappingURL=minimize-2.js.map\n","/**\n * @license lucide-react v0.577.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.js';\n\nconst __iconNode = [\n [\n \"path\",\n {\n d: \"m16 6-8.414 8.586a2 2 0 0 0 2.829 2.829l8.414-8.586a4 4 0 1 0-5.657-5.657l-8.379 8.551a6 6 0 1 0 8.485 8.485l8.379-8.551\",\n key: \"1miecu\"\n }\n ]\n];\nconst Paperclip = createLucideIcon(\"paperclip\", __iconNode);\n\nexport { __iconNode, Paperclip as default };\n//# sourceMappingURL=paperclip.js.map\n","/**\n * @license lucide-react v0.577.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.js';\n\nconst __iconNode = [\n [\n \"path\",\n {\n d: \"M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z\",\n key: \"oel41y\"\n }\n ],\n [\"path\", { d: \"M12 8v4\", key: \"1got3b\" }],\n [\"path\", { d: \"M12 16h.01\", key: \"1drbdi\" }]\n];\nconst ShieldAlert = createLucideIcon(\"shield-alert\", __iconNode);\n\nexport { __iconNode, ShieldAlert as default };\n//# sourceMappingURL=shield-alert.js.map\n","/**\n * @license lucide-react v0.577.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.js';\n\nconst __iconNode = [\n [\"path\", { d: \"m2 2 20 20\", key: \"1ooewy\" }],\n [\n \"path\",\n {\n d: \"M5 5a1 1 0 0 0-1 1v7c0 5 3.5 7.5 7.67 8.94a1 1 0 0 0 .67.01c2.35-.82 4.48-1.97 5.9-3.71\",\n key: \"1jlk70\"\n }\n ],\n [\n \"path\",\n {\n d: \"M9.309 3.652A12.252 12.252 0 0 0 11.24 2.28a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1v7a9.784 9.784 0 0 1-.08 1.264\",\n key: \"18rp1v\"\n }\n ]\n];\nconst ShieldOff = createLucideIcon(\"shield-off\", __iconNode);\n\nexport { __iconNode, ShieldOff as default };\n//# sourceMappingURL=shield-off.js.map\n","/**\n * @license lucide-react v0.577.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.js';\n\nconst __iconNode = [[\"path\", { d: \"M22 2 2 22\", key: \"y4kqgn\" }]];\nconst Slash = createLucideIcon(\"slash\", __iconNode);\n\nexport { __iconNode, Slash as default };\n//# sourceMappingURL=slash.js.map\n","/**\n * @license lucide-react v0.577.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.js';\n\nconst __iconNode = [\n [\"path\", { d: \"m7 11 2-2-2-2\", key: \"1lz0vl\" }],\n [\"path\", { d: \"M11 13h4\", key: \"1p7l4v\" }],\n [\"rect\", { width: \"18\", height: \"18\", x: \"3\", y: \"3\", rx: \"2\", ry: \"2\", key: \"1m3agn\" }]\n];\nconst SquareTerminal = createLucideIcon(\"square-terminal\", __iconNode);\n\nexport { __iconNode, SquareTerminal as default };\n//# sourceMappingURL=square-terminal.js.map\n","/**\n * @license lucide-react v0.577.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.js';\n\nconst __iconNode = [\n [\n \"path\",\n {\n d: \"M13.172 2a2 2 0 0 1 1.414.586l6.71 6.71a2.4 2.4 0 0 1 0 3.408l-4.592 4.592a2.4 2.4 0 0 1-3.408 0l-6.71-6.71A2 2 0 0 1 6 9.172V3a1 1 0 0 1 1-1z\",\n key: \"16rjxf\"\n }\n ],\n [\n \"path\",\n { d: \"M2 7v6.172a2 2 0 0 0 .586 1.414l6.71 6.71a2.4 2.4 0 0 0 3.191.193\", key: \"178nd4\" }\n ],\n [\"circle\", { cx: \"10.5\", cy: \"6.5\", r: \".5\", fill: \"currentColor\", key: \"12ikhr\" }]\n];\nconst Tags = createLucideIcon(\"tags\", __iconNode);\n\nexport { __iconNode, Tags as default };\n//# sourceMappingURL=tags.js.map\n","/**\n * @license lucide-react v0.577.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.js';\n\nconst __iconNode = [\n [\"path\", { d: \"M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2\", key: \"1yyitq\" }],\n [\"path\", { d: \"M16 3.128a4 4 0 0 1 0 7.744\", key: \"16gr8j\" }],\n [\"path\", { d: \"M22 21v-2a4 4 0 0 0-3-3.87\", key: \"kshegd\" }],\n [\"circle\", { cx: \"9\", cy: \"7\", r: \"4\", key: \"nufk8\" }]\n];\nconst Users = createLucideIcon(\"users\", __iconNode);\n\nexport { __iconNode, Users as default };\n//# sourceMappingURL=users.js.map\n","/**\n * @license lucide-react v0.577.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.js';\n\nconst __iconNode = [\n [\n \"path\",\n {\n d: \"M4 14a1 1 0 0 1-.78-1.63l9.9-10.2a.5.5 0 0 1 .86.46l-1.92 6.02A1 1 0 0 0 13 10h7a1 1 0 0 1 .78 1.63l-9.9 10.2a.5.5 0 0 1-.86-.46l1.92-6.02A1 1 0 0 0 11 14z\",\n key: \"1xq2db\"\n }\n ]\n];\nconst Zap = createLucideIcon(\"zap\", __iconNode);\n\nexport { __iconNode, Zap as default };\n//# sourceMappingURL=zap.js.map\n","import { useEffect, useRef, useCallback } from \"react\";\nimport { WsClient } from \"@/lib/ws-client\";\n\ninterface UseWebSocketOptions {\n url: string;\n onMessage?: (event: MessageEvent) => void;\n autoConnect?: boolean;\n}\n\nexport function useWebSocket({\n url,\n onMessage,\n autoConnect = true,\n}: UseWebSocketOptions) {\n const clientRef = useRef<WsClient | null>(null);\n\n useEffect(() => {\n const client = new WsClient(url);\n clientRef.current = client;\n\n if (onMessage) {\n client.onMessage(onMessage);\n }\n\n if (autoConnect) {\n client.connect();\n }\n\n return () => {\n client.disconnect();\n clientRef.current = null;\n };\n }, [url, autoConnect]); // eslint-disable-line react-hooks/exhaustive-deps\n\n const send = useCallback((data: string | ArrayBuffer) => {\n clientRef.current?.send(data);\n }, []);\n\n const connect = useCallback(() => {\n clientRef.current?.connect();\n }, []);\n\n const disconnect = useCallback(() => {\n clientRef.current?.disconnect();\n }, []);\n\n return { send, connect, disconnect };\n}\n","import type { ChatMessage } from \"../../types/chat\";\n\n/** Simple deterministic hash of a jsonlPath → short prefix (non-cryptographic). */\nfunction hashPath(path: string): string {\n let h = 0;\n for (let i = 0; i < path.length; i++) h = ((h << 5) - h + path.charCodeAt(i)) | 0;\n return Math.abs(h).toString(36);\n}\n\n/**\n * Prefix pre-compact message IDs so they don't collide with current-session IDs.\n * React keys rely on `id`; collisions cause render corruption.\n */\nexport function prefixPreCompactIds(msgs: ChatMessage[], jsonlPath: string): ChatMessage[] {\n const prefix = `pc-${hashPath(jsonlPath)}-`;\n return msgs.map((m) => (m.id ? { ...m, id: `${prefix}${m.id}` } : m));\n}\n\n/**\n * Flatten messages with expansions prepended before their corresponding compact message.\n * Key of `expansions` = compact message id. Values = already-prefixed ChatMessage[].\n * Preserves reference equality when expansions is empty (zero-cost for non-compact chats).\n */\nexport function flattenWithExpansions(\n messages: ChatMessage[],\n expansions: Map<string, ChatMessage[]>,\n): ChatMessage[] {\n if (expansions.size === 0) return messages;\n const out: ChatMessage[] = [];\n for (const m of messages) {\n const pre = m.id ? expansions.get(m.id) : undefined;\n if (pre && pre.length > 0) out.push(...pre);\n out.push(m);\n }\n return out;\n}\n","let audioCtx: AudioContext | null = null;\n\nfunction getAudioContext(): AudioContext {\n if (!audioCtx) audioCtx = new AudioContext();\n return audioCtx;\n}\n\nfunction playTone(freq: number, duration: number, startTime: number, gain: number, type: OscillatorType = \"sine\") {\n const ctx = getAudioContext();\n const osc = ctx.createOscillator();\n const vol = ctx.createGain();\n osc.type = type;\n osc.frequency.value = freq;\n vol.gain.setValueAtTime(gain, startTime);\n vol.gain.exponentialRampToValueAtTime(0.001, startTime + duration);\n osc.connect(vol);\n vol.connect(ctx.destination);\n osc.start(startTime);\n osc.stop(startTime + duration);\n}\n\n/** Chat completed — pleasant double chime ascending */\nfunction playDone() {\n const ctx = getAudioContext();\n const t = ctx.currentTime;\n playTone(523, 0.15, t, 0.15); // C5\n playTone(659, 0.2, t + 0.12, 0.15); // E5\n}\n\n/** Approval request — urgent two-tone alert */\nfunction playApproval() {\n const ctx = getAudioContext();\n const t = ctx.currentTime;\n playTone(880, 0.12, t, 0.18, \"square\"); // A5\n playTone(698, 0.12, t + 0.15, 0.18, \"square\"); // F5\n playTone(880, 0.15, t + 0.3, 0.15, \"square\"); // A5\n}\n\n/** Question — soft ascending triplet */\nfunction playQuestion() {\n const ctx = getAudioContext();\n const t = ctx.currentTime;\n playTone(440, 0.12, t, 0.12); // A4\n playTone(523, 0.12, t + 0.1, 0.12); // C5\n playTone(659, 0.18, t + 0.2, 0.12); // E5\n}\n\nconst SOUND_MAP: Record<string, () => void> = {\n done: playDone,\n approval_request: playApproval,\n question: playQuestion,\n};\n\n/** Play notification sound by type. No-op if type unknown or AudioContext unavailable. */\nexport function playNotificationSound(type: string): void {\n try {\n SOUND_MAP[type]?.();\n } catch {\n // AudioContext may be blocked if no user gesture yet — silently ignore\n }\n}\n","import { useState, useCallback, useRef, useEffect, useMemo, startTransition } from \"react\";\nimport { useWebSocket } from \"./use-websocket\";\nimport { api, getAuthToken, projectUrl } from \"@/lib/api-client\";\nimport { flattenWithExpansions, prefixPreCompactIds } from \"@/lib/flatten-expansions\";\nimport { useNotificationStore } from \"@/stores/notification-store\";\nimport { useStreamingStore } from \"@/stores/streaming-store\";\nimport { usePanelStore } from \"@/stores/panel-store\";\nimport { playNotificationSound } from \"@/lib/notification-sounds\";\nimport { toast } from \"sonner\";\nimport type { ChatMessage, ChatEvent } from \"../../types/chat\";\nimport type { ChatWsServerMessage, SessionPhase } from \"../../types/api\";\n\ninterface ApprovalRequest {\n requestId: string;\n tool: string;\n input: unknown;\n}\n\nexport interface TeamMessageItem {\n from: string;\n to: string;\n text: string;\n timestamp: string;\n summary?: string;\n parsedType?: string;\n color?: string;\n}\n\ninterface TeamActivityState {\n hasTeams: boolean;\n teamNames: string[];\n messageCount: number;\n unreadCount: number;\n}\n\nconst EMPTY_TEAM_ACTIVITY: TeamActivityState = { hasTeams: false, teamNames: [], messageCount: 0, unreadCount: 0 };\n\nexport interface BashPartialEntry {\n content: string;\n lineCount: number;\n}\n\ninterface UseChatReturn {\n messages: ChatMessage[];\n /** Messages flattened with pre-compact expansions prepended before their compact cards. */\n renderedMessages: ChatMessage[];\n /** Fetch pre-compact transcript and store expansion. Returns loaded message count. */\n expandCompact: (compactMessageId: string, jsonlPath: string) => Promise<number>;\n /** Whether a given compactMessageId has been expanded. */\n isCompactExpanded: (compactMessageId: string) => boolean;\n messagesLoading: boolean;\n isStreaming: boolean;\n phase: SessionPhase;\n isReconnecting: boolean;\n connectingElapsed: number;\n pendingApproval: ApprovalRequest | null;\n contextWindowPct: number | null;\n compactStatus: \"compacting\" | null;\n statusMessage: string | null;\n sessionTitle: string | null;\n /** Team activity state from WS events */\n teamActivity: TeamActivityState;\n /** All team messages (ref-backed, updated live) */\n teamMessages: TeamMessageItem[];\n /** Mark team messages as read (reset unread counter) */\n markTeamRead: () => void;\n /** Partial bash output keyed by toolUseId (ref-backed for perf) */\n bashPartialOutput: React.RefObject<Map<string, BashPartialEntry>>;\n sendMessage: (content: string, opts?: { permissionMode?: string; priority?: 'now' | 'next' | 'later'; images?: Array<{ data: string; mediaType: string }> }) => void;\n respondToApproval: (requestId: string, approved: boolean, data?: unknown) => void;\n cancelStreaming: () => void;\n reconnect: () => void;\n refetchMessages: () => void;\n isConnected: boolean;\n}\n\n/** Check if the chat tab for this session is the active foreground tab */\nfunction isSessionTabActive(sid: string): boolean {\n if (document.hidden) return false;\n const { panels, focusedPanelId } = usePanelStore.getState();\n const panel = panels[focusedPanelId];\n if (!panel) return false;\n const activeTab = panel.tabs.find((t) => t.id === panel.activeTabId);\n return activeTab?.type === \"chat\" && activeTab.metadata?.sessionId === sid;\n}\n\nexport function useChat(sessionId: string | null, providerId = \"claude\", projectName = \"\"): UseChatReturn {\n const [messages, setMessages] = useState<ChatMessage[]>([]);\n /** Map of compactMessageId → pre-compact messages (already ID-prefixed). Ephemeral. */\n const [expansions, setExpansions] = useState<Map<string, ChatMessage[]>>(new Map());\n const [messagesLoading, setMessagesLoading] = useState(false);\n const [phase, setPhase] = useState<SessionPhase>(\"idle\");\n const [isReconnecting, setIsReconnecting] = useState(false);\n const [connectingElapsed, setConnectingElapsed] = useState(0);\n const [pendingApproval, setPendingApproval] = useState<ApprovalRequest | null>(null);\n const [contextWindowPct, setContextWindowPct] = useState<number | null>(null);\n const [compactStatus, setCompactStatus] = useState<\"compacting\" | null>(null);\n const [statusMessage, setStatusMessage] = useState<string | null>(null);\n const [sessionTitle, setSessionTitle] = useState<string | null>(null);\n const [isConnected, setIsConnected] = useState(false);\n const streamingContentRef = useRef(\"\");\n const streamingEventsRef = useRef<ChatEvent[]>([]);\n const bashOutputRef = useRef<Map<string, BashPartialEntry>>(new Map());\n const streamingAccountRef = useRef<{ accountId: string; accountLabel: string } | null>(null);\n const phaseRef = useRef<SessionPhase>(\"idle\");\n const pendingMessageRef = useRef<string | null>(null);\n const sendRef = useRef<(data: string) => void>(() => {});\n const refetchRef = useRef<(() => void) | null>(null);\n /** True while replaying turn_events — suppresses setPendingApproval */\n const isReplayingRef = useRef(false);\n const sessionIdRef = useRef(sessionId);\n sessionIdRef.current = sessionId;\n const projectNameRef = useRef(projectName);\n projectNameRef.current = projectName;\n /** Toast ID for the current pending approval notification */\n const approvalToastRef = useRef<string | number | null>(null);\n /** RAF handle for throttled syncMessages */\n const syncRafRef = useRef<number>(0);\n\n // Team activity tracking\n const teamActivityRef = useRef<{\n teamNames: Set<string>;\n messages: TeamMessageItem[];\n }>({ teamNames: new Set(), messages: [] });\n const teamUnreadRef = useRef(0);\n const [teamActivity, setTeamActivity] = useState<TeamActivityState>(EMPTY_TEAM_ACTIVITY);\n const [teamMessages, setTeamMessages] = useState<TeamMessageItem[]>([]);\n\n const updateTeamActivity = useCallback(() => {\n const ref = teamActivityRef.current;\n setTeamActivity({\n hasTeams: ref.teamNames.size > 0,\n teamNames: Array.from(ref.teamNames),\n messageCount: ref.messages.length,\n unreadCount: teamUnreadRef.current,\n });\n // Snapshot messages array so React detects changes\n setTeamMessages([...ref.messages]);\n }, []);\n\n const markTeamRead = useCallback(() => {\n teamUnreadRef.current = 0;\n updateTeamActivity();\n }, [updateTeamActivity]);\n\n // Derived state\n const isStreaming = phase !== \"idle\";\n\n // Sync streaming state to global store (for favicon + tab icon indicators)\n useEffect(() => {\n if (!sessionId) return;\n useStreamingStore.getState().setStreaming(sessionId, phase !== \"idle\");\n return () => { useStreamingStore.getState().setStreaming(sessionId, false); };\n }, [sessionId, phase]);\n\n /**\n * Route a child event to its parent Agent/Task tool_use's children array.\n * Creates a new parent object to ensure React detects the change on re-render.\n * Returns true if routed (caller should skip flat append), false if no parent found.\n */\n const routeToParent = useCallback((childEvent: ChatEvent, parentToolUseId: string): boolean => {\n const idx = streamingEventsRef.current.findIndex(\n (e) => e.type === \"tool_use\"\n && (e.tool === \"Agent\" || e.tool === \"Task\")\n && (e as any).toolUseId === parentToolUseId,\n );\n if (idx === -1) return false;\n const parent = streamingEventsRef.current[idx]!;\n if (parent.type !== \"tool_use\") return false;\n const newChildren = [...(parent.children ?? []), childEvent];\n streamingEventsRef.current[idx] = { ...parent, children: newChildren };\n return true;\n }, []);\n\n /** Flush refs into React state (called from throttled timer or directly) */\n const flushMessages = useCallback(() => {\n syncRafRef.current = 0;\n const content = streamingContentRef.current;\n const events = [...streamingEventsRef.current];\n const account = streamingAccountRef.current;\n // startTransition marks streaming updates as low-priority so React\n // yields to user interactions (text selection, copy, scroll) first.\n startTransition(() => {\n setMessages((prev) => {\n const last = prev[prev.length - 1];\n if (last?.role === \"assistant\" && !last.id.startsWith(\"final-\")) {\n return [...prev.slice(0, -1), { ...last, content, events, ...account }];\n }\n return [...prev, {\n id: `streaming-${Date.now()}`,\n role: \"assistant\" as const,\n content,\n events,\n timestamp: new Date().toISOString(),\n ...account,\n }];\n });\n });\n }, []);\n\n /** Throttled sync — batches rapid WS events into one render per ~100ms.\n * Previously used rAF (~16ms / 60fps) which blocked the main thread with\n * frequent ReactMarkdown re-parses, causing lag during text selection/copy.\n * 100ms (10fps) keeps streaming smooth while leaving idle time for interactions. */\n const syncMessages = useCallback(() => {\n if (!syncRafRef.current) {\n syncRafRef.current = window.setTimeout(flushMessages, 100) as unknown as number;\n }\n }, [flushMessages]);\n\n /** Process a single stream event — reused by live events and turn_events replay */\n const processStreamEvent = useCallback((data: unknown) => {\n const ev = data as any;\n const evType = ev?.type;\n if (!evType) return;\n\n switch (evType) {\n case \"account_info\": {\n streamingAccountRef.current = { accountId: ev.accountId, accountLabel: ev.accountLabel };\n setStatusMessage(null);\n break;\n }\n\n case \"account_retry\": {\n // Update streaming account to the new one being tried\n if (ev.accountId && ev.accountLabel) {\n streamingAccountRef.current = { accountId: ev.accountId, accountLabel: ev.accountLabel };\n }\n // Clear previous streaming events (error text from failed attempt)\n // and start fresh with only the retry notification\n streamingEventsRef.current = [ev as ChatEvent];\n syncMessages();\n break;\n }\n\n case \"status_update\": {\n const label = ev.accountLabel ? ` (${ev.accountLabel})` : \"\";\n setStatusMessage(`${ev.message}${label}`);\n break;\n }\n\n case \"text\": {\n const pid = ev.parentToolUseId as string | undefined;\n if (pid && routeToParent(ev as ChatEvent, pid)) {\n syncMessages();\n break;\n }\n streamingContentRef.current += ev.content;\n streamingEventsRef.current.push(ev as ChatEvent);\n syncMessages();\n break;\n }\n\n case \"thinking\": {\n const pid = ev.parentToolUseId as string | undefined;\n if (pid && routeToParent(ev as ChatEvent, pid)) {\n syncMessages();\n break;\n }\n streamingEventsRef.current.push(ev as ChatEvent);\n syncMessages();\n break;\n }\n\n case \"tool_use\": {\n const pid = ev.parentToolUseId as string | undefined;\n if (pid && routeToParent(ev as ChatEvent, pid)) {\n syncMessages();\n break;\n }\n streamingEventsRef.current.push(ev as ChatEvent);\n syncMessages();\n break;\n }\n\n case \"tool_result\": {\n // Clear bash partial output for this tool\n const trId = ev.toolUseId as string;\n if (trId) bashOutputRef.current.delete(trId);\n\n const pid = ev.parentToolUseId as string | undefined;\n if (pid && routeToParent(ev as ChatEvent, pid)) {\n syncMessages();\n break;\n }\n streamingEventsRef.current.push(ev as ChatEvent);\n syncMessages();\n break;\n }\n\n case \"approval_request\": {\n streamingEventsRef.current.push(ev as ChatEvent);\n // During turn_events replay, session_state already set the correct\n // pendingApproval — skip re-setting it for historical (already-answered) events\n if (isReplayingRef.current) break;\n setPendingApproval({\n requestId: ev.requestId,\n tool: ev.tool,\n input: ev.input,\n });\n if (sessionIdRef.current && !isSessionTabActive(sessionIdRef.current)) {\n const nType = ev.tool === \"AskUserQuestion\" ? \"question\" : \"approval_request\";\n // Unread state added via server-side session:unread_changed broadcast — only play sound + toast here\n playNotificationSound(nType);\n // Persistent toast with action to navigate to the waiting session\n const sid = sessionIdRef.current;\n const isQuestion = ev.tool === \"AskUserQuestion\";\n approvalToastRef.current = toast[isQuestion ? \"info\" : \"warning\"](\n isQuestion ? \"AI has a question\" : `${ev.tool} needs permission`,\n {\n description: projectNameRef.current || `Session ${sid.slice(0, 8)}`,\n duration: Infinity,\n action: {\n label: \"Go to session\",\n onClick: () => {\n const { panels } = usePanelStore.getState();\n for (const [panelId, panel] of Object.entries(panels)) {\n const tab = panel.tabs.find((t) => t.metadata?.sessionId === sid);\n if (tab) {\n usePanelStore.getState().setActiveTab(tab.id, panelId);\n break;\n }\n }\n },\n },\n },\n );\n }\n break;\n }\n\n case \"error\": {\n streamingEventsRef.current.push(ev as ChatEvent);\n const errEvents = [...streamingEventsRef.current];\n setMessages((prev) => {\n const last = prev[prev.length - 1];\n if (last?.role === \"assistant\") {\n return [...prev.slice(0, -1), { ...last, events: errEvents }];\n }\n return [...prev, {\n id: `error-${Date.now()}`,\n role: \"system\" as const,\n content: ev.message,\n events: [ev as ChatEvent],\n timestamp: new Date().toISOString(),\n }];\n });\n // Phase reset comes from BE via phase_changed\n break;\n }\n\n case \"team_detected\": {\n const teamName = ev.teamName as string;\n if (teamName) {\n teamActivityRef.current.teamNames.add(teamName);\n // Fetch full team data from REST\n api.get<any>(`/api/teams/${encodeURIComponent(teamName)}`).then((res: any) => {\n if (res?.messages) {\n const existing = teamActivityRef.current.messages;\n const newMsgs = (res.messages as any[]).filter(\n (m: any) => !existing.some((e) => e.timestamp === m.timestamp && e.from === m.from)\n );\n existing.push(...newMsgs);\n existing.sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());\n if (existing.length > 500) existing.splice(0, existing.length - 500);\n }\n updateTeamActivity();\n }).catch(() => {});\n updateTeamActivity();\n }\n break;\n }\n\n case \"team_inbox\": {\n const msgs = (ev as any).messages as any[];\n if (Array.isArray(msgs)) {\n const existing = teamActivityRef.current.messages;\n existing.push(...msgs);\n if (existing.length > 500) existing.splice(0, existing.length - 500);\n teamUnreadRef.current += msgs.length;\n updateTeamActivity();\n }\n break;\n }\n\n case \"team_updated\": {\n updateTeamActivity();\n break;\n }\n\n case \"bash_output\": {\n const tuId = ev.toolUseId as string;\n if (tuId) {\n const existing = bashOutputRef.current.get(tuId);\n if (existing) {\n existing.content += ev.content;\n // Cap at ~500KB to prevent browser OOM on long-running commands\n if (existing.content.length > 500_000) {\n existing.content = existing.content.slice(-500_000);\n }\n existing.lineCount = ev.lineCount as number;\n } else {\n bashOutputRef.current.set(tuId, {\n content: ev.content as string,\n lineCount: ev.lineCount as number,\n });\n }\n syncMessages();\n }\n break;\n }\n\n case \"done\": {\n // Idempotent: may receive duplicate done (provider + stream loop finally)\n if (phaseRef.current === \"idle\") break;\n if (ev.contextWindowPct != null) {\n setContextWindowPct(ev.contextWindowPct);\n }\n if (sessionIdRef.current && !isSessionTabActive(sessionIdRef.current)) {\n // Unread state added via server-side session:unread_changed broadcast — only play sound here\n playNotificationSound(\"done\");\n }\n // Cancel any pending throttled sync — done handler writes final state directly\n if (syncRafRef.current) { clearTimeout(syncRafRef.current); syncRafRef.current = 0; }\n // Finalize the streaming message — preserve SDK UUID for fork/rewind\n const finalContent = streamingContentRef.current;\n const finalEvents = [...streamingEventsRef.current];\n const finalAccount = streamingAccountRef.current;\n const doneUuid = ev.lastMessageUuid as string | undefined;\n setMessages((prev) => {\n const last = prev[prev.length - 1];\n if (last?.role === \"assistant\") {\n return [...prev.slice(0, -1), {\n ...last,\n id: `final-${Date.now()}`,\n content: finalContent || last.content,\n events: finalEvents.length > 0 ? finalEvents : last.events,\n ...(doneUuid && { sdkUuid: doneUuid }),\n }];\n }\n // No assistant message flushed yet (rAF was still pending when cancelled).\n // Create one from accumulated refs so the response isn't silently lost.\n if (finalContent || finalEvents.length > 0) {\n return [...prev, {\n id: `final-${Date.now()}`,\n role: \"assistant\" as const,\n content: finalContent,\n events: finalEvents,\n timestamp: new Date().toISOString(),\n ...(doneUuid && { sdkUuid: doneUuid }),\n ...finalAccount,\n }];\n }\n return prev;\n });\n streamingContentRef.current = \"\";\n streamingEventsRef.current = [];\n streamingAccountRef.current = null;\n bashOutputRef.current.clear();\n setStatusMessage(null);\n // Phase transition to idle comes from BE via phase_changed\n break;\n }\n }\n }, [routeToParent, syncMessages]);\n\n const handleMessage = useCallback((event: MessageEvent) => {\n let data: ChatWsServerMessage;\n try {\n data = JSON.parse(event.data as string) as ChatWsServerMessage;\n } catch {\n return;\n }\n\n // Ignore keepalive pings\n if ((data as any).type === \"ping\") return;\n\n // Dispatch file change events for real-time editor reload\n if ((data as any).type === \"file:changed\") {\n window.dispatchEvent(new CustomEvent(\"file:changed\", { detail: data }));\n return;\n }\n\n // Cross-tab/device unread sync — server broadcasts when unread state changes\n if ((data as any).type === \"session:unread_changed\") {\n const { sessionId: sid, unreadCount, unreadType, projectName: pn, sessionTitle: sTitle } = data as any;\n useNotificationStore.getState().handleUnreadChanged(sid, unreadCount, unreadType, pn, sTitle);\n return;\n }\n\n // Dispatch global Jira events so components can listen via window events\n if (typeof (data as any).type === \"string\" && (data as any).type.startsWith(\"jira:\")) {\n window.dispatchEvent(new CustomEvent((data as any).type, { detail: data }));\n return;\n }\n\n // Handle title updates from SDK summary\n if ((data as any).type === \"title_updated\") {\n setSessionTitle((data as any).title ?? null);\n return;\n }\n\n // Handle compact status events\n if ((data as any).type === \"compact_status\") {\n const status = (data as any).status;\n if (status === \"compacting\") {\n setCompactStatus(\"compacting\");\n } else if (status === \"done\") {\n setCompactStatus(null);\n // Do NOT refetch here — compact_done arrives mid-stream while the SDK\n // continues processing. Calling refetchMessages() would: (1) replace\n // all messages with REST history (killing the in-progress streaming\n // assistant message), (2) reset streamingContentRef/streamingEventsRef,\n // and (3) let the next flushMessages overwrite the last REST message\n // with empty streaming content — making the UI appear frozen.\n // The turn-end idle transition already calls refetchRef (phase→idle\n // handler) which safely loads compacted history after streaming stops.\n }\n return;\n }\n\n // Handle phase transitions from BE\n if ((data as any).type === \"phase_changed\") {\n const p = (data as any).phase as SessionPhase;\n setPhase(p);\n phaseRef.current = p;\n setConnectingElapsed(p === \"connecting\" ? ((data as any).elapsed ?? 0) : 0);\n // Safety: idle phase means no turn running — ensure compact indicator does not linger.\n // BE should broadcast compact_status=done too, but this is a belt-and-braces clear.\n if (p === \"idle\") setCompactStatus(null);\n return;\n }\n\n // Handle session state (replaces connected + status)\n if ((data as any).type === \"session_state\") {\n setIsConnected(true);\n const state = data as any;\n const p = state.phase as SessionPhase;\n setPhase(p);\n phaseRef.current = p;\n if (state.sessionTitle) setSessionTitle(state.sessionTitle);\n if (state.pendingApproval) {\n setPendingApproval({\n requestId: state.pendingApproval.requestId,\n tool: state.pendingApproval.tool,\n input: state.pendingApproval.input,\n });\n }\n // Sync compact indicator from authoritative server state (covers reconnect).\n // state.compactStatus is \"compacting\" | null — treat undefined as null for back-compat.\n setCompactStatus(state.compactStatus === \"compacting\" ? \"compacting\" : null);\n // If idle, refetch history (completed turns) and hide overlay\n if (p === \"idle\") {\n refetchRef.current?.();\n setIsReconnecting(false);\n }\n // If streaming, turn_events message will follow\n return;\n }\n\n // Handle turn_events (reconnect sync with rAF chunking)\n if ((data as any).type === \"turn_events\") {\n const events = (data as any).events as unknown[];\n const userMessage = (data as any).userMessage as string | null;\n if (!events?.length && !userMessage) { setIsReconnecting(false); return; }\n\n // Remove stale streaming assistant message + inject current turn's user message\n setMessages(prev => {\n let updated = prev;\n // Only remove in-progress streaming assistant (not finalized or REST-loaded)\n const last = updated[updated.length - 1];\n if (last?.role === \"assistant\" && last.id.startsWith(\"streaming-\")) {\n updated = updated.slice(0, -1);\n }\n // Add the current turn's user message if not already present\n if (userMessage) {\n const lastAfter = updated[updated.length - 1];\n if (lastAfter?.role !== \"user\" || lastAfter.content !== userMessage) {\n updated = [...updated, {\n id: `user-replay-${Date.now()}`,\n role: \"user\" as const,\n content: userMessage,\n timestamp: new Date().toISOString(),\n }];\n }\n }\n return updated;\n });\n\n // Reset streaming refs\n streamingContentRef.current = \"\";\n streamingEventsRef.current = [];\n streamingAccountRef.current = null;\n\n // Process events in chunks via requestAnimationFrame to avoid blocking main thread\n isReplayingRef.current = true;\n const CHUNK_SIZE = 100;\n let offset = 0;\n const processChunk = () => {\n const end = Math.min(offset + CHUNK_SIZE, events.length);\n for (let i = offset; i < end; i++) {\n processStreamEvent(events[i]);\n }\n offset = end;\n if (offset < events.length) {\n requestAnimationFrame(processChunk);\n } else {\n isReplayingRef.current = false;\n setIsReconnecting(false);\n }\n };\n requestAnimationFrame(processChunk);\n return;\n }\n\n // Route content events through processStreamEvent\n processStreamEvent(data);\n }, [processStreamEvent]);\n\n const wsUrl = sessionId && projectName\n ? `/ws/project/${encodeURIComponent(projectName)}/chat/${sessionId}`\n : \"\";\n\n const { send, connect: wsReconnect } = useWebSocket({\n url: wsUrl,\n onMessage: handleMessage,\n autoConnect: !!sessionId && !!projectName,\n });\n\n // Keep sendRef in sync so handleMessage can flush queued messages\n sendRef.current = send;\n\n // Load history and reset state when session changes\n useEffect(() => {\n let cancelled = false;\n\n setPhase(\"idle\");\n phaseRef.current = \"idle\";\n setPendingApproval(null);\n if (approvalToastRef.current != null) { toast.dismiss(approvalToastRef.current); approvalToastRef.current = null; }\n setCompactStatus(null);\n // Clear ephemeral pre-compact expansions on session change\n setExpansions(new Map());\n streamingContentRef.current = \"\";\n streamingEventsRef.current = [];\n bashOutputRef.current.clear();\n if (syncRafRef.current) { clearTimeout(syncRafRef.current); syncRafRef.current = 0; }\n setIsConnected(false);\n // Reset team state\n teamActivityRef.current = { teamNames: new Set(), messages: [] };\n teamUnreadRef.current = 0;\n setTeamActivity(EMPTY_TEAM_ACTIVITY);\n setTeamMessages([]);\n\n if (sessionId && projectName) {\n setMessagesLoading(true);\n fetch(`${projectUrl(projectName)}/chat/sessions/${sessionId}/messages?providerId=${providerId}`, {\n headers: { Authorization: `Bearer ${getAuthToken()}` },\n })\n .then((r) => r.json())\n .then((json: any) => {\n if (cancelled || phaseRef.current !== \"idle\") return;\n if (json.ok && Array.isArray(json.data) && json.data.length > 0) {\n setMessages(json.data);\n } else {\n setMessages([]);\n }\n })\n .catch(() => {\n if (!cancelled && phaseRef.current === \"idle\") setMessages([]);\n })\n .finally(() => {\n if (!cancelled) setMessagesLoading(false);\n });\n } else {\n setMessages([]);\n }\n\n return () => {\n cancelled = true;\n };\n }, [sessionId, providerId, projectName]);\n\n const sendMessage = useCallback(\n (content: string, opts?: { permissionMode?: string; priority?: 'now' | 'next' | 'later'; images?: Array<{ data: string; mediaType: string }> }) => {\n if (!content.trim()) return;\n\n const isFollowUp = phaseRef.current !== \"idle\";\n\n if (isFollowUp) {\n // Cancel pending throttled sync before finalizing\n if (syncRafRef.current) { clearTimeout(syncRafRef.current); syncRafRef.current = 0; }\n // Streaming follow-up: finalize current assistant message, then send\n const finalContent = streamingContentRef.current;\n const finalEvents = [...streamingEventsRef.current];\n setMessages((prev) => {\n const last = prev[prev.length - 1];\n if (last?.role === \"assistant\") {\n return [\n ...prev.slice(0, -1),\n { ...last, id: `final-${Date.now()}`, content: finalContent || last.content, events: finalEvents.length > 0 ? finalEvents : last.events },\n ];\n }\n return prev;\n });\n }\n\n // Add user message\n setMessages((prev) => [\n ...prev,\n {\n id: `user-${Date.now()}`,\n role: \"user\" as const,\n content,\n timestamp: new Date().toISOString(),\n },\n ]);\n\n // Reset streaming state for new turn\n streamingContentRef.current = \"\";\n streamingEventsRef.current = [];\n pendingMessageRef.current = null;\n if (!isFollowUp) {\n setPhase(\"initializing\");\n phaseRef.current = \"initializing\";\n } else {\n setPhase(\"thinking\");\n phaseRef.current = \"thinking\";\n }\n setPendingApproval(null);\n if (approvalToastRef.current != null) { toast.dismiss(approvalToastRef.current); approvalToastRef.current = null; }\n\n send(JSON.stringify({\n type: \"message\",\n content,\n permissionMode: opts?.permissionMode,\n priority: opts?.priority,\n images: opts?.images,\n }));\n },\n [send],\n );\n\n const respondToApproval = useCallback(\n (requestId: string, approved: boolean, data?: unknown) => {\n send(\n JSON.stringify({\n type: \"approval_response\",\n requestId,\n approved,\n data,\n }),\n );\n\n // Merge answers into the AskUserQuestion tool_use event so FE shows selected answers\n if (approved && data) {\n const evts = streamingEventsRef.current;\n const askEvt = evts.find(\n (e: ChatEvent) =>\n e.type === \"approval_request\" &&\n (e as any).requestId === requestId &&\n (e as any).tool === \"AskUserQuestion\",\n );\n if (askEvt) {\n const inp = (askEvt as any).input;\n if (inp && typeof inp === \"object\") {\n (inp as Record<string, unknown>).answers = data;\n }\n }\n setMessages((prev) => [...prev]);\n }\n\n setPendingApproval(null);\n if (approvalToastRef.current != null) { toast.dismiss(approvalToastRef.current); approvalToastRef.current = null; }\n },\n [send],\n );\n\n const cancelStreaming = useCallback(() => {\n if (phaseRef.current === \"idle\") return;\n send(JSON.stringify({ type: \"cancel\" }));\n const finalContent = streamingContentRef.current;\n const finalEvents = [...streamingEventsRef.current];\n setMessages((prev) => {\n const last = prev[prev.length - 1];\n if (last?.role === \"assistant\") {\n return [\n ...prev.slice(0, -1),\n {\n ...last,\n id: `final-${Date.now()}`,\n content: finalContent || last.content,\n events: finalEvents.length > 0 ? finalEvents : last.events,\n },\n ];\n }\n return prev;\n });\n streamingContentRef.current = \"\";\n streamingEventsRef.current = [];\n bashOutputRef.current.clear();\n pendingMessageRef.current = null;\n setPhase(\"idle\");\n phaseRef.current = \"idle\";\n setPendingApproval(null);\n if (approvalToastRef.current != null) { toast.dismiss(approvalToastRef.current); approvalToastRef.current = null; }\n }, [send]);\n\n const reconnect = useCallback(() => {\n setIsConnected(false);\n setIsReconnecting(true);\n wsReconnect();\n }, [wsReconnect]);\n\n const refetchMessages = useCallback(() => {\n if (!sessionId || !projectName) return;\n // No setMessagesLoading(true) here — keep current messages visible while\n // fetching in the background (stale-while-revalidate). The initial load\n // in the session-change useEffect already handles the first-time loading screen.\n fetch(`${projectUrl(projectName)}/chat/sessions/${sessionId}/messages?providerId=${providerId}`, {\n headers: { Authorization: `Bearer ${getAuthToken()}` },\n })\n .then((r) => r.json())\n .then((json: any) => {\n if (json.ok && Array.isArray(json.data) && json.data.length > 0) {\n setMessages(json.data);\n streamingContentRef.current = \"\";\n streamingEventsRef.current = [];\n }\n })\n .catch(() => {});\n }, [sessionId, providerId, projectName]);\n\n // Keep refetchRef in sync\n refetchRef.current = refetchMessages;\n\n /** Fetch pre-compact transcript. Idempotent: re-expanding same id replaces entry. */\n const expandCompact = useCallback(async (compactMessageId: string, jsonlPath: string): Promise<number> => {\n if (!projectName) throw new Error(\"No project context available\");\n // Claude's compact summary references the CURRENT session file (pre+summary+post).\n // Strip the `pc-{hash}-` prefix added by prefixPreCompactIds for nested expansions\n // so BE receives the raw session uuid and truncates at the correct boundary.\n const rawUuid = compactMessageId.replace(/^pc-[^-]+-/, \"\");\n const url =\n `${projectUrl(projectName)}/chat/pre-compact-messages` +\n `?jsonlPath=${encodeURIComponent(jsonlPath)}` +\n `&before=${encodeURIComponent(rawUuid)}`;\n const loaded = await api.get<ChatMessage[]>(url);\n const prefixed = prefixPreCompactIds(loaded, jsonlPath);\n setExpansions((prev) => {\n const next = new Map(prev);\n next.set(compactMessageId, prefixed);\n return next;\n });\n return prefixed.length;\n }, [projectName]);\n\n const isCompactExpanded = useCallback((id: string) => expansions.has(id), [expansions]);\n\n /** Flattened view: expansions prepended before their compact cards. */\n const renderedMessages = useMemo(\n () => flattenWithExpansions(messages, expansions),\n [messages, expansions],\n );\n\n return {\n messages,\n renderedMessages,\n expandCompact,\n isCompactExpanded,\n messagesLoading,\n isStreaming,\n phase,\n isReconnecting,\n connectingElapsed,\n pendingApproval,\n contextWindowPct,\n compactStatus,\n statusMessage,\n sessionTitle,\n teamActivity,\n teamMessages,\n markTeamRead,\n bashPartialOutput: bashOutputRef,\n sendMessage,\n respondToApproval,\n cancelStreaming,\n reconnect,\n refetchMessages,\n isConnected,\n };\n}\n","import { useState, useCallback, useEffect, useRef } from \"react\";\nimport { getAuthToken, projectUrl } from \"@/lib/api-client\";\nimport type { UsageInfo } from \"../../types/chat\";\n\nconst POLL_INTERVAL = 120_000; // read cache every 2min\n\ninterface UseUsageReturn {\n usageInfo: UsageInfo;\n usageLoading: boolean;\n /** ISO timestamp from BE — when usage was actually fetched from Anthropic API */\n lastFetchedAt: string | null;\n refreshUsage: () => void;\n}\n\nexport function useUsage(projectName: string, providerId = \"claude\"): UseUsageReturn {\n const [usageInfo, setUsageInfo] = useState<UsageInfo>({});\n const [usageLoading, setUsageLoading] = useState(false);\n const [lastFetchedAt, setLastFetchedAt] = useState<string | null>(null);\n const timerRef = useRef<ReturnType<typeof setInterval> | null>(null);\n\n const doFetch = useCallback((forceRefresh = false) => {\n if (!projectName) return;\n setUsageLoading(true);\n const qs = forceRefresh ? \"&refresh=1\" : \"\";\n fetch(`${projectUrl(projectName)}/chat/usage?providerId=${providerId}${qs}`, {\n headers: { Authorization: `Bearer ${getAuthToken()}` },\n })\n .then((r) => r.json())\n .then((json: any) => {\n if (json.ok && json.data) {\n setUsageInfo((prev) => ({ ...prev, ...json.data }));\n if (json.data.lastFetchedAt) setLastFetchedAt(json.data.lastFetchedAt);\n }\n })\n .catch(() => {})\n .finally(() => setUsageLoading(false));\n }, [projectName, providerId]);\n\n // Read cache on mount + auto-read every POLL_INTERVAL\n useEffect(() => {\n doFetch();\n timerRef.current = setInterval(() => doFetch(), POLL_INTERVAL);\n return () => { if (timerRef.current) clearInterval(timerRef.current); };\n }, [doFetch]);\n\n /** Manual refresh — tells BE to fetch fresh from Anthropic API */\n const refreshUsage = useCallback(() => doFetch(true), [doFetch]);\n\n return { usageInfo, usageLoading, lastFetchedAt, refreshUsage };\n}\n","/*!---------------------------------------------------------------------------------------------\n * Copyright (c) StackBlitz. All rights reserved.\n * Licensed under the MIT License. See License.txt in the project root for license information.\n *--------------------------------------------------------------------------------------------*/\nimport { useCallback, useMemo, useRef, useState, } from \"react\";\nconst DEFAULT_SPRING_ANIMATION = {\n /**\n * A value from 0 to 1, on how much to damp the animation.\n * 0 means no damping, 1 means full damping.\n *\n * @default 0.7\n */\n damping: 0.7,\n /**\n * The stiffness of how fast/slow the animation gets up to speed.\n *\n * @default 0.05\n */\n stiffness: 0.05,\n /**\n * The inertial mass associated with the animation.\n * Higher numbers make the animation slower.\n *\n * @default 1.25\n */\n mass: 1.25,\n};\nconst STICK_TO_BOTTOM_OFFSET_PX = 70;\nconst SIXTY_FPS_INTERVAL_MS = 1000 / 60;\nconst RETAIN_ANIMATION_DURATION_MS = 350;\nlet mouseDown = false;\nglobalThis.document?.addEventListener(\"mousedown\", () => {\n mouseDown = true;\n});\nglobalThis.document?.addEventListener(\"mouseup\", () => {\n mouseDown = false;\n});\nglobalThis.document?.addEventListener(\"click\", () => {\n mouseDown = false;\n});\nexport const useStickToBottom = (options = {}) => {\n const [escapedFromLock, updateEscapedFromLock] = useState(false);\n const [isAtBottom, updateIsAtBottom] = useState(options.initial !== false);\n const [isNearBottom, setIsNearBottom] = useState(false);\n const optionsRef = useRef(null);\n optionsRef.current = options;\n const isSelecting = useCallback(() => {\n if (!mouseDown) {\n return false;\n }\n const selection = window.getSelection();\n if (!selection || !selection.rangeCount) {\n return false;\n }\n const range = selection.getRangeAt(0);\n return (range.commonAncestorContainer.contains(scrollRef.current) ||\n scrollRef.current?.contains(range.commonAncestorContainer));\n }, []);\n const setIsAtBottom = useCallback((isAtBottom) => {\n state.isAtBottom = isAtBottom;\n updateIsAtBottom(isAtBottom);\n }, []);\n const setEscapedFromLock = useCallback((escapedFromLock) => {\n state.escapedFromLock = escapedFromLock;\n updateEscapedFromLock(escapedFromLock);\n }, []);\n // biome-ignore lint/correctness/useExhaustiveDependencies: not needed\n const state = useMemo(() => {\n let lastCalculation;\n return {\n escapedFromLock,\n isAtBottom,\n resizeDifference: 0,\n accumulated: 0,\n velocity: 0,\n listeners: new Set(),\n get scrollTop() {\n return scrollRef.current?.scrollTop ?? 0;\n },\n set scrollTop(scrollTop) {\n if (scrollRef.current) {\n scrollRef.current.scrollTop = scrollTop;\n state.ignoreScrollToTop = scrollRef.current.scrollTop;\n }\n },\n get targetScrollTop() {\n if (!scrollRef.current || !contentRef.current) {\n return 0;\n }\n return (scrollRef.current.scrollHeight - 1 - scrollRef.current.clientHeight);\n },\n get calculatedTargetScrollTop() {\n if (!scrollRef.current || !contentRef.current) {\n return 0;\n }\n const { targetScrollTop } = this;\n if (!options.targetScrollTop) {\n return targetScrollTop;\n }\n if (lastCalculation?.targetScrollTop === targetScrollTop) {\n return lastCalculation.calculatedScrollTop;\n }\n const calculatedScrollTop = Math.max(Math.min(options.targetScrollTop(targetScrollTop, {\n scrollElement: scrollRef.current,\n contentElement: contentRef.current,\n }), targetScrollTop), 0);\n lastCalculation = { targetScrollTop, calculatedScrollTop };\n requestAnimationFrame(() => {\n lastCalculation = undefined;\n });\n return calculatedScrollTop;\n },\n get scrollDifference() {\n return this.calculatedTargetScrollTop - this.scrollTop;\n },\n get isNearBottom() {\n return this.scrollDifference <= STICK_TO_BOTTOM_OFFSET_PX;\n },\n };\n }, []);\n const scrollToBottom = useCallback((scrollOptions = {}) => {\n if (typeof scrollOptions === \"string\") {\n scrollOptions = { animation: scrollOptions };\n }\n if (!scrollOptions.preserveScrollPosition) {\n setIsAtBottom(true);\n }\n const waitElapsed = Date.now() + (Number(scrollOptions.wait) || 0);\n const behavior = mergeAnimations(optionsRef.current, scrollOptions.animation);\n const { ignoreEscapes = false } = scrollOptions;\n let durationElapsed;\n let startTarget = state.calculatedTargetScrollTop;\n if (scrollOptions.duration instanceof Promise) {\n scrollOptions.duration.finally(() => {\n durationElapsed = Date.now();\n });\n }\n else {\n durationElapsed = waitElapsed + (scrollOptions.duration ?? 0);\n }\n const next = async () => {\n const promise = new Promise(requestAnimationFrame).then(() => {\n if (!state.isAtBottom) {\n state.animation = undefined;\n return false;\n }\n const { scrollTop } = state;\n const tick = performance.now();\n const tickDelta = (tick - (state.lastTick ?? tick)) / SIXTY_FPS_INTERVAL_MS;\n state.animation || (state.animation = { behavior, promise, ignoreEscapes });\n if (state.animation.behavior === behavior) {\n state.lastTick = tick;\n }\n if (isSelecting()) {\n return next();\n }\n if (waitElapsed > Date.now()) {\n return next();\n }\n if (scrollTop < Math.min(startTarget, state.calculatedTargetScrollTop)) {\n if (state.animation?.behavior === behavior) {\n if (behavior === \"instant\") {\n state.scrollTop = state.calculatedTargetScrollTop;\n return next();\n }\n state.velocity =\n (behavior.damping * state.velocity +\n behavior.stiffness * state.scrollDifference) /\n behavior.mass;\n state.accumulated += state.velocity * tickDelta;\n state.scrollTop += state.accumulated;\n if (state.scrollTop !== scrollTop) {\n state.accumulated = 0;\n }\n }\n return next();\n }\n if (durationElapsed > Date.now()) {\n startTarget = state.calculatedTargetScrollTop;\n return next();\n }\n state.animation = undefined;\n /**\n * If we're still below the target, then queue\n * up another scroll to the bottom with the last\n * requested animatino.\n */\n if (state.scrollTop < state.calculatedTargetScrollTop) {\n return scrollToBottom({\n animation: mergeAnimations(optionsRef.current, optionsRef.current.resize),\n ignoreEscapes,\n duration: Math.max(0, durationElapsed - Date.now()) || undefined,\n });\n }\n return state.isAtBottom;\n });\n return promise.then((isAtBottom) => {\n requestAnimationFrame(() => {\n if (!state.animation) {\n state.lastTick = undefined;\n state.velocity = 0;\n }\n });\n return isAtBottom;\n });\n };\n if (scrollOptions.wait !== true) {\n state.animation = undefined;\n }\n if (state.animation?.behavior === behavior) {\n return state.animation.promise;\n }\n return next();\n }, [setIsAtBottom, isSelecting, state]);\n const stopScroll = useCallback(() => {\n setEscapedFromLock(true);\n setIsAtBottom(false);\n }, [setEscapedFromLock, setIsAtBottom]);\n const handleScroll = useCallback(({ target }) => {\n if (target !== scrollRef.current) {\n return;\n }\n const { scrollTop, ignoreScrollToTop } = state;\n let { lastScrollTop = scrollTop } = state;\n state.lastScrollTop = scrollTop;\n state.ignoreScrollToTop = undefined;\n if (ignoreScrollToTop && ignoreScrollToTop > scrollTop) {\n /**\n * When the user scrolls up while the animation plays, the `scrollTop` may\n * not come in separate events; if this happens, to make sure `isScrollingUp`\n * is correct, set the lastScrollTop to the ignored event.\n */\n lastScrollTop = ignoreScrollToTop;\n }\n setIsNearBottom(state.isNearBottom);\n /**\n * Scroll events may come before a ResizeObserver event,\n * so in order to ignore resize events correctly we use a\n * timeout.\n *\n * @see https://github.com/WICG/resize-observer/issues/25#issuecomment-248757228\n */\n setTimeout(() => {\n /**\n * When theres a resize difference ignore the resize event.\n */\n if (state.resizeDifference || scrollTop === ignoreScrollToTop) {\n return;\n }\n if (isSelecting()) {\n setEscapedFromLock(true);\n setIsAtBottom(false);\n return;\n }\n const isScrollingDown = scrollTop > lastScrollTop;\n const isScrollingUp = scrollTop < lastScrollTop;\n if (state.animation?.ignoreEscapes) {\n state.scrollTop = lastScrollTop;\n return;\n }\n if (isScrollingUp) {\n setEscapedFromLock(true);\n setIsAtBottom(false);\n }\n if (isScrollingDown) {\n setEscapedFromLock(false);\n }\n if (!state.escapedFromLock && state.isNearBottom) {\n setIsAtBottom(true);\n }\n }, 1);\n }, [setEscapedFromLock, setIsAtBottom, isSelecting, state]);\n const handleWheel = useCallback(({ target, deltaY }) => {\n let element = target;\n while (![\"scroll\", \"auto\"].includes(getComputedStyle(element).overflow)) {\n if (!element.parentElement) {\n return;\n }\n element = element.parentElement;\n }\n /**\n * The browser may cancel the scrolling from the mouse wheel\n * if we update it from the animation in meantime.\n * To prevent this, always escape when the wheel is scrolled up.\n */\n if (element === scrollRef.current &&\n deltaY < 0 &&\n scrollRef.current.scrollHeight > scrollRef.current.clientHeight &&\n !state.animation?.ignoreEscapes) {\n setEscapedFromLock(true);\n setIsAtBottom(false);\n }\n }, [setEscapedFromLock, setIsAtBottom, state]);\n const scrollRef = useRefCallback((scroll) => {\n scrollRef.current?.removeEventListener(\"scroll\", handleScroll);\n scrollRef.current?.removeEventListener(\"wheel\", handleWheel);\n scroll?.addEventListener(\"scroll\", handleScroll, { passive: true });\n scroll?.addEventListener(\"wheel\", handleWheel, { passive: true });\n }, []);\n const contentRef = useRefCallback((content) => {\n state.resizeObserver?.disconnect();\n if (!content) {\n return;\n }\n let previousHeight;\n state.resizeObserver = new ResizeObserver(([entry]) => {\n const { height } = entry.contentRect;\n const difference = height - (previousHeight ?? height);\n state.resizeDifference = difference;\n /**\n * Sometimes the browser can overscroll past the target,\n * so check for this and adjust appropriately.\n */\n if (state.scrollTop > state.targetScrollTop) {\n state.scrollTop = state.targetScrollTop;\n }\n setIsNearBottom(state.isNearBottom);\n if (difference >= 0) {\n /**\n * If it's a positive resize, scroll to the bottom when\n * we're already at the bottom.\n */\n const animation = mergeAnimations(optionsRef.current, previousHeight\n ? optionsRef.current.resize\n : optionsRef.current.initial);\n scrollToBottom({\n animation,\n wait: true,\n preserveScrollPosition: true,\n duration: animation === \"instant\" ? undefined : RETAIN_ANIMATION_DURATION_MS,\n });\n }\n else {\n /**\n * Else if it's a negative resize, check if we're near the bottom\n * if we are want to un-escape from the lock, because the resize\n * could have caused the container to be at the bottom.\n */\n if (state.isNearBottom) {\n setEscapedFromLock(false);\n setIsAtBottom(true);\n }\n }\n previousHeight = height;\n /**\n * Reset the resize difference after the scroll event\n * has fired. Requires a rAF to wait for the scroll event,\n * and a setTimeout to wait for the other timeout we have in\n * resizeObserver in case the scroll event happens after the\n * resize event.\n */\n requestAnimationFrame(() => {\n setTimeout(() => {\n if (state.resizeDifference === difference) {\n state.resizeDifference = 0;\n }\n }, 1);\n });\n });\n state.resizeObserver?.observe(content);\n }, []);\n return {\n contentRef,\n scrollRef,\n scrollToBottom,\n stopScroll,\n isAtBottom: isAtBottom || isNearBottom,\n isNearBottom,\n escapedFromLock,\n state,\n };\n};\nfunction useRefCallback(callback, deps) {\n // biome-ignore lint/correctness/useExhaustiveDependencies: not needed\n const result = useCallback((ref) => {\n result.current = ref;\n return callback(ref);\n }, deps);\n return result;\n}\nconst animationCache = new Map();\nfunction mergeAnimations(...animations) {\n const result = { ...DEFAULT_SPRING_ANIMATION };\n let instant = false;\n for (const animation of animations) {\n if (animation === \"instant\") {\n instant = true;\n continue;\n }\n if (typeof animation !== \"object\") {\n continue;\n }\n instant = false;\n result.damping = animation.damping ?? result.damping;\n result.stiffness = animation.stiffness ?? result.stiffness;\n result.mass = animation.mass ?? result.mass;\n }\n const key = JSON.stringify(result);\n if (!animationCache.has(key)) {\n animationCache.set(key, Object.freeze(result));\n }\n return instant ? \"instant\" : animationCache.get(key);\n}\n","/*!---------------------------------------------------------------------------------------------\n * Copyright (c) StackBlitz. All rights reserved.\n * Licensed under the MIT License. See License.txt in the project root for license information.\n *--------------------------------------------------------------------------------------------*/\nimport * as React from \"react\";\nimport { createContext, useContext, useEffect, useImperativeHandle, useLayoutEffect, useMemo, useRef, } from \"react\";\nimport { useStickToBottom, } from \"./useStickToBottom.js\";\nconst StickToBottomContext = createContext(null);\nconst useIsomorphicLayoutEffect = typeof window !== \"undefined\" ? useLayoutEffect : useEffect;\nexport function StickToBottom({ instance, children, resize, initial, mass, damping, stiffness, targetScrollTop: currentTargetScrollTop, contextRef, ...props }) {\n const customTargetScrollTop = useRef(null);\n const targetScrollTop = React.useCallback((target, elements) => {\n const get = context?.targetScrollTop ?? currentTargetScrollTop;\n return get?.(target, elements) ?? target;\n }, [currentTargetScrollTop]);\n const defaultInstance = useStickToBottom({\n mass,\n damping,\n stiffness,\n resize,\n initial,\n targetScrollTop,\n });\n const { scrollRef, contentRef, scrollToBottom, stopScroll, isAtBottom, escapedFromLock, state, } = instance ?? defaultInstance;\n const context = useMemo(() => ({\n scrollToBottom,\n stopScroll,\n scrollRef,\n isAtBottom,\n escapedFromLock,\n contentRef,\n state,\n get targetScrollTop() {\n return customTargetScrollTop.current;\n },\n set targetScrollTop(targetScrollTop) {\n customTargetScrollTop.current = targetScrollTop;\n },\n }), [\n scrollToBottom,\n isAtBottom,\n contentRef,\n scrollRef,\n stopScroll,\n escapedFromLock,\n state,\n ]);\n useImperativeHandle(contextRef, () => context, [context]);\n useIsomorphicLayoutEffect(() => {\n if (!scrollRef.current) {\n return;\n }\n if (getComputedStyle(scrollRef.current).overflow === \"visible\") {\n scrollRef.current.style.overflow = \"auto\";\n }\n }, []);\n return (React.createElement(StickToBottomContext.Provider, { value: context },\n React.createElement(\"div\", { ...props }, typeof children === \"function\" ? children(context) : children)));\n}\n(function (StickToBottom) {\n function Content({ children, scrollClassName, ...props }) {\n const context = useStickToBottomContext();\n return (React.createElement(\"div\", { ref: context.scrollRef, style: {\n height: \"100%\",\n width: \"100%\",\n scrollbarGutter: \"stable both-edges\",\n }, className: scrollClassName },\n React.createElement(\"div\", { ...props, ref: context.contentRef }, typeof children === \"function\" ? children(context) : children)));\n }\n StickToBottom.Content = Content;\n})(StickToBottom || (StickToBottom = {}));\n/**\n * Use this hook inside a <StickToBottom> component to gain access to whether the component is at the bottom of the scrollable area.\n */\nexport function useStickToBottomContext() {\n const context = useContext(StickToBottomContext);\n if (!context) {\n throw new Error(\"use-stick-to-bottom component context must be used within a StickToBottom component\");\n }\n return context;\n}\n","/**\n * Tool card components for chat message rendering.\n * Handles summary + details for all SDK tool types.\n */\nimport { useState, useEffect, useRef, useMemo, lazy, Suspense } from \"react\";\nimport type { BashPartialEntry } from \"../../hooks/use-chat\";\nconst MarkdownRenderer = lazy(() =>\n import(\"@/components/shared/markdown-renderer\").then((m) => ({ default: m.MarkdownRenderer }))\n);\nimport {\n ChevronDown,\n ChevronRight,\n AlertCircle,\n Loader2,\n CheckCircle2,\n XCircle,\n ExternalLink,\n ListTodo,\n Search,\n Bot,\n Globe,\n Code,\n Columns2,\n} from \"lucide-react\";\nimport type { ChatEvent } from \"../../../types/chat\";\nimport { useShallow } from \"zustand/react/shallow\";\nimport { useTabStore } from \"@/stores/tab-store\";\nimport { basename } from \"@/lib/utils\";\n\n/** Extract tool name and input from a ChatEvent */\nfunction extractToolInfo(tool: ChatEvent): { toolName: string; input: Record<string, unknown> } {\n const isApproval = tool.type === \"approval_request\";\n const toolName = tool.type === \"tool_use\"\n ? tool.tool\n : isApproval\n ? (tool as any).tool ?? \"Tool\"\n : \"Tool\";\n const input = tool.type === \"tool_use\"\n ? (tool.input as Record<string, unknown>)\n : isApproval\n ? ((tool as any).input as Record<string, unknown>) ?? {}\n : {};\n return { toolName, input };\n}\n\n/** Unified tool card: shows tool-specific summary + expandable details */\nexport function ToolCard({\n tool,\n result,\n completed,\n projectName,\n bashPartialOutput,\n}: {\n tool: ChatEvent;\n result?: ChatEvent;\n completed?: boolean;\n projectName?: string;\n bashPartialOutput?: React.RefObject<Map<string, BashPartialEntry>>;\n}) {\n const [expanded, setExpanded] = useState(false);\n\n if (tool.type === \"error\") {\n return (\n <div className=\"flex items-center gap-2 rounded bg-red-500/10 border border-red-500/20 px-2 py-1.5 text-xs text-red-400\">\n <AlertCircle className=\"size-3\" />\n <span>{tool.message}</span>\n </div>\n );\n }\n\n const { toolName, input } = extractToolInfo(tool);\n const hasResult = result?.type === \"tool_result\";\n const isError = hasResult && !!(result as any).isError;\n const hasAnswers = toolName === \"AskUserQuestion\" && !!(input as any)?.answers;\n const wasApproved = tool.type === \"approval_request\" && (tool as any).approved != null;\n const isSubagent = (toolName === \"Agent\" || toolName === \"Task\") && tool.type === \"tool_use\";\n const children = isSubagent ? (tool as any).children as ChatEvent[] | undefined : undefined;\n const hasChildren = children && children.length > 0;\n const isDone = hasResult || hasAnswers || wasApproved || completed;\n\n // Read partial bash output for streaming Bash tools\n const toolUseId = tool.type === \"tool_use\" ? (tool as any).toolUseId as string | undefined : undefined;\n const partial = toolName === \"Bash\" && !hasResult && toolUseId\n ? bashPartialOutput?.current?.get(toolUseId)\n : undefined;\n const isStreamingBash = !!partial;\n\n // Auto-expand ToolCard when streaming bash output\n useEffect(() => {\n if (isStreamingBash && !expanded) setExpanded(true);\n }, [isStreamingBash]); // eslint-disable-line react-hooks/exhaustive-deps\n\n return (\n <div className={`rounded border text-xs ${isSubagent ? \"border-accent/30 bg-accent/5\" : \"border-border bg-background\"}`}>\n <button\n onClick={() => setExpanded(!expanded)}\n className=\"flex items-center gap-2 px-2 py-1.5 w-full text-left hover:bg-surface transition-colors min-w-0\"\n >\n {expanded ? <ChevronDown className=\"size-3 shrink-0\" /> : <ChevronRight className=\"size-3 shrink-0\" />}\n {isError\n ? <XCircle className=\"size-3 text-red-400 shrink-0\" />\n : isDone\n ? <CheckCircle2 className=\"size-3 text-green-400 shrink-0\" />\n : <Loader2 className=\"size-3 text-yellow-400 shrink-0 animate-spin\" />}\n <span className=\"truncate text-text-primary\">\n <ToolSummary name={toolName} input={input} />\n </span>\n {isStreamingBash && (\n <span className=\"ml-auto text-[10px] text-yellow-400 shrink-0\">{partial!.lineCount} line{partial!.lineCount !== 1 ? \"s\" : \"\"} streaming...</span>\n )}\n {hasChildren && !isStreamingBash && (\n <span className=\"ml-auto text-[10px] text-text-subtle shrink-0\">{children!.length} steps</span>\n )}\n </button>\n {expanded && (\n <div className=\"px-2 pb-2 space-y-1.5 select-text\">\n {(tool.type === \"tool_use\" || tool.type === \"approval_request\") && (\n <ToolDetails name={toolName} input={input} projectName={projectName} />\n )}\n {/* Streaming bash output */}\n {partial && <StreamingBashOutput content={partial.content} lineCount={partial.lineCount} />}\n {/* Subagent children: render nested tool events */}\n {hasChildren && (\n <SubagentChildren events={children!} projectName={projectName} />\n )}\n {hasResult && (\n <ToolResultView toolName={toolName} output={(result as any).output} />\n )}\n </div>\n )}\n </div>\n );\n}\n\n/** Render one-line summary per tool type */\nfunction ToolSummary({ name, input }: { name: string; input: Record<string, unknown> }) {\n const s = (v: unknown) => String(v ?? \"\");\n switch (name) {\n case \"Read\":\n case \"Write\":\n case \"Edit\":\n case \"MultiEdit\":\n case \"NotebookEdit\":\n return <>{name} <span className=\"text-text-subtle\">{basename(s(input.file_path))}</span></>;\n case \"Bash\": {\n const preview = input.description ? s(input.description) : s(input.command);\n return <>{name} <span className={`text-text-subtle${input.description ? \"\" : \" font-mono\"}`}>{truncate(preview, 60)}</span></>;\n }\n case \"Glob\":\n return <>{name} <span className=\"font-mono text-text-subtle\">{s(input.pattern)}</span></>;\n case \"Grep\":\n return <>{name} <span className=\"font-mono text-text-subtle\">{truncate(s(input.pattern), 40)}</span></>;\n case \"WebSearch\":\n return <><Search className=\"size-3 inline\" /> {name} <span className=\"text-text-subtle\">{truncate(s(input.query), 50)}</span></>;\n case \"WebFetch\":\n return <><Globe className=\"size-3 inline\" /> {name} <span className=\"text-text-subtle\">{truncate(s(input.url), 50)}</span></>;\n case \"ToolSearch\":\n return <><Search className=\"size-3 inline\" /> {name} <span className=\"text-text-subtle\">{truncate(s(input.query), 50)}</span></>;\n case \"Agent\":\n case \"Task\":\n return <><Bot className=\"size-3 inline\" /> {name} <span className=\"text-text-subtle\">{truncate(s(input.description || input.prompt), 60)}</span></>;\n case \"TodoWrite\": {\n const todos = Array.isArray(input.todos) ? input.todos as Array<{ content: string; status: string }> : [];\n const done = todos.filter((t) => t.status === \"completed\").length;\n return <><ListTodo className=\"size-3 inline\" /> {name} <span className=\"text-text-subtle\">{done}/{todos.length} done</span></>;\n }\n case \"AskUserQuestion\": {\n const qs = Array.isArray(input.questions) ? input.questions as Array<{ question: string }> : [];\n const hasAns = !!(input.answers);\n return <>{name} <span className=\"text-text-subtle\">{qs.length} question{qs.length !== 1 ? \"s\" : \"\"}{hasAns ? \" ✓\" : \"\"}</span></>;\n }\n default:\n return <>{name}</>;\n }\n}\n\n/** Render expanded details per tool type */\nfunction ToolDetails({\n name,\n input,\n projectName,\n}: {\n name: string;\n input: Record<string, unknown>;\n projectName?: string;\n}) {\n const s = (v: unknown) => String(v ?? \"\");\n const { openTab } = useTabStore(useShallow((state) => ({ openTab: state.openTab })));\n\n /** Open a file in a new editor tab */\n const openFile = (filePath: string) => {\n if (!projectName) return;\n openTab({\n type: \"editor\",\n title: basename(filePath),\n metadata: { filePath, projectName },\n projectId: projectName,\n closable: true,\n });\n };\n\n /** Open inline diff tab for Edit tool changes */\n const openEditDiff = (filePath: string, oldStr: string, newStr: string) => {\n openTab({\n type: \"git-diff\",\n title: `Diff ${basename(filePath)}`,\n metadata: { filePath, projectName, original: oldStr, modified: newStr },\n projectId: projectName ?? null,\n closable: true,\n });\n };\n\n switch (name) {\n case \"Bash\":\n return (\n <div className=\"space-y-1\">\n {!!input.description && <p className=\"text-text-subtle italic\">{s(input.description)}</p>}\n <pre className=\"font-mono text-text-secondary overflow-x-auto whitespace-pre-wrap break-all\">{s(input.command)}</pre>\n </div>\n );\n case \"Read\":\n case \"Write\":\n case \"Edit\":\n case \"MultiEdit\":\n case \"NotebookEdit\": {\n const filePath = s(input.file_path);\n return (\n <div className=\"space-y-1\">\n <button\n type=\"button\"\n className=\"font-mono text-text-secondary break-all hover:text-primary hover:underline text-left flex items-center gap-1\"\n onClick={() => openFile(filePath)}\n title=\"Open file in editor\"\n >\n <ExternalLink className=\"size-3 shrink-0\" />\n {filePath}\n </button>\n {name === \"Edit\" && (!!input.old_string || !!input.new_string) && (\n <button\n type=\"button\"\n className=\"text-text-subtle hover:text-primary hover:underline text-left flex items-center gap-1\"\n onClick={() => openEditDiff(filePath, s(input.old_string), s(input.new_string))}\n title=\"View diff in new tab\"\n >\n <Columns2 className=\"size-3 shrink-0\" />\n View Diff\n </button>\n )}\n {name === \"Write\" && !!input.content && (\n <pre className=\"font-mono text-text-subtle overflow-x-auto max-h-32 whitespace-pre-wrap\">{truncate(s(input.content), 300)}</pre>\n )}\n </div>\n );\n }\n case \"Glob\":\n return <p className=\"font-mono text-text-secondary\">{s(input.pattern)}{input.path ? ` in ${s(input.path)}` : \"\"}</p>;\n case \"Grep\":\n return (\n <div className=\"space-y-0.5\">\n <p className=\"font-mono text-text-secondary\">/{s(input.pattern)}/</p>\n {!!input.path && <p className=\"text-text-subtle\">in {s(input.path)}</p>}\n </div>\n );\n case \"TodoWrite\":\n return <TodoDetails todos={(input.todos as Array<{ content: string; status: string }>) ?? []} />;\n case \"Agent\":\n case \"Task\":\n return (\n <div className=\"space-y-1\">\n {!!input.description && <p className=\"text-text-secondary font-medium\">{s(input.description)}</p>}\n {!!input.subagent_type && <p className=\"text-text-subtle\">Type: {s(input.subagent_type)}</p>}\n {!!input.prompt && <MiniMarkdown content={s(input.prompt)} maxHeight=\"max-h-48\" />}\n </div>\n );\n case \"ToolSearch\":\n return (\n <div className=\"space-y-0.5\">\n <p className=\"font-mono text-text-secondary\">{s(input.query)}</p>\n {!!input.max_results && <p className=\"text-text-subtle\">Max results: {s(input.max_results)}</p>}\n </div>\n );\n case \"WebFetch\":\n return (\n <div className=\"space-y-0.5\">\n <a href={s(input.url)} target=\"_blank\" rel=\"noopener noreferrer\" className=\"font-mono text-primary hover:underline break-all flex items-center gap-1\">\n <Globe className=\"size-3 shrink-0\" />\n {s(input.url)}\n </a>\n {!!input.prompt && <p className=\"text-text-subtle\">{truncate(s(input.prompt), 100)}</p>}\n </div>\n );\n case \"AskUserQuestion\": {\n const qs = (input.questions as Array<{ question: string; header?: string; options: Array<{ label: string; description?: string }>; multiSelect?: boolean }>) ?? [];\n const answers = (input.answers as Record<string, string>) ?? {};\n return (\n <div className=\"space-y-2\">\n {qs.map((q, i) => (\n <div key={i} className=\"space-y-0.5\">\n <p className=\"text-text-primary font-medium\">{q.header ? `${q.header}: ` : \"\"}{q.question}</p>\n <div className=\"flex flex-wrap gap-1\">\n {q.options.map((opt, oi) => {\n const answer = answers[q.question] ?? \"\";\n const isSelected = answer.split(\", \").includes(opt.label);\n return (\n <span key={oi} className={`inline-block rounded px-1.5 py-0.5 text-xs border ${\n isSelected ? \"border-accent bg-accent/20 text-text-primary\" : \"border-border text-text-subtle\"\n }`}>\n {opt.label}\n </span>\n );\n })}\n </div>\n {answers[q.question] && (\n <p className=\"text-foreground text-xs\">Answer: {answers[q.question]}</p>\n )}\n </div>\n ))}\n </div>\n );\n }\n default:\n return (\n <pre className=\"overflow-x-auto text-text-secondary font-mono whitespace-pre-wrap break-all\">\n {JSON.stringify(input, null, 2)}\n </pre>\n );\n }\n}\n\n/** Todo list display with checkboxes */\nfunction TodoDetails({ todos }: { todos: Array<{ content: string; status: string }> }) {\n return (\n <div className=\"space-y-0.5\">\n {todos.map((todo, i) => (\n <div key={i} className=\"flex items-start gap-1.5\">\n <span className={`shrink-0 mt-0.5 ${\n todo.status === \"completed\"\n ? \"text-green-400\"\n : todo.status === \"in_progress\"\n ? \"text-yellow-400\"\n : \"text-text-subtle\"\n }`}>\n {todo.status === \"completed\" ? \"✓\" : todo.status === \"in_progress\" ? \"▶\" : \"○\"}\n </span>\n <span className={todo.status === \"completed\" ? \"line-through text-text-subtle\" : \"text-text-secondary\"}>\n {todo.content}\n </span>\n </div>\n ))}\n </div>\n );\n}\n\n/** Render tool result with smart formatting — markdown for Agent, collapsible JSON for others */\nfunction ToolResultView({ toolName, output }: { toolName: string; output: string }) {\n const [showRaw, setShowRaw] = useState(false);\n\n // For Agent/Task results: try to extract text content from JSON array result\n const agentContent = useMemo(() => {\n if (toolName !== \"Agent\" && toolName !== \"Task\") return null;\n try {\n const parsed = JSON.parse(output);\n if (Array.isArray(parsed)) {\n // SDK returns [{type:\"text\", text:\"...\"}, ...] — extract text blocks\n const texts = parsed\n .filter((item: any) => item.type === \"text\" && item.text)\n .map((item: any) => item.text)\n .join(\"\\n\\n\");\n if (texts) return texts;\n }\n if (typeof parsed === \"string\") return parsed;\n } catch {\n // Not JSON — might be plain text\n if (output && !output.startsWith(\"[{\")) return output;\n }\n return null;\n }, [toolName, output]);\n\n // Agent with extracted markdown content\n if (agentContent) {\n return (\n <div className=\"border-t border-border pt-1.5 space-y-1\">\n <MiniMarkdown content={agentContent} maxHeight=\"max-h-60\" />\n {/* Toggle to show raw JSON */}\n <button\n type=\"button\"\n onClick={() => setShowRaw(!showRaw)}\n className=\"flex items-center gap-1 text-[10px] text-text-subtle hover:text-text-secondary transition-colors\"\n >\n <Code className=\"size-3\" />\n {showRaw ? \"Hide\" : \"Show\"} raw\n </button>\n {showRaw && (\n <pre className=\"overflow-x-auto text-text-subtle font-mono max-h-40 whitespace-pre-wrap break-all text-[10px]\">\n {output}\n </pre>\n )}\n </div>\n );\n }\n\n // Default: collapsible raw output\n return (\n <CollapsibleOutput output={output} />\n );\n}\n\n/** Collapsible raw output — collapsed by default if > 3 lines */\nfunction CollapsibleOutput({ output }: { output: string }) {\n const lineCount = output.split(\"\\n\").length;\n const isLong = lineCount > 3 || output.length > 200;\n const [collapsed, setCollapsed] = useState(isLong);\n\n return (\n <div className=\"border-t border-border pt-1.5\">\n {isLong && (\n <button\n type=\"button\"\n onClick={() => setCollapsed(!collapsed)}\n className=\"flex items-center gap-1 text-[10px] text-text-subtle hover:text-text-secondary transition-colors mb-1\"\n >\n {collapsed ? <ChevronRight className=\"size-3\" /> : <ChevronDown className=\"size-3\" />}\n Output ({lineCount} lines)\n </button>\n )}\n <pre className={`overflow-x-auto text-text-subtle font-mono whitespace-pre-wrap break-all ${\n collapsed ? \"max-h-16 overflow-hidden\" : \"max-h-60\"\n }`}>\n {output}\n </pre>\n </div>\n );\n}\n\n/** Render subagent child events — nested tool_use/tool_result + text */\nfunction SubagentChildren({ events, projectName }: { events: ChatEvent[]; projectName?: string }) {\n // Group children similar to InterleavedEvents: pair tool_use + tool_result, merge text\n type ChildGroup =\n | { kind: \"text\"; content: string }\n | { kind: \"tool\"; tool: ChatEvent; result?: ChatEvent };\n\n const groups: ChildGroup[] = [];\n let textBuffer = \"\";\n\n for (const ev of events) {\n if (ev.type === \"text\") {\n textBuffer += ev.content;\n } else if (ev.type === \"tool_use\") {\n if (textBuffer) { groups.push({ kind: \"text\", content: textBuffer }); textBuffer = \"\"; }\n groups.push({ kind: \"tool\", tool: ev });\n } else if (ev.type === \"tool_result\") {\n // Match to last unmatched tool_use by toolUseId\n const trId = (ev as any).toolUseId;\n const match = trId\n ? groups.find((g) => g.kind === \"tool\" && g.tool.type === \"tool_use\" && (g.tool as any).toolUseId === trId && !g.result) as (ChildGroup & { kind: \"tool\" }) | undefined\n : groups.findLast((g) => g.kind === \"tool\" && !g.result) as (ChildGroup & { kind: \"tool\" }) | undefined;\n if (match) match.result = ev;\n }\n }\n if (textBuffer) groups.push({ kind: \"text\", content: textBuffer });\n\n return (\n <div className=\"border-l-2 border-accent/20 pl-2 space-y-1 mt-1\">\n {groups.map((g, i) => {\n if (g.kind === \"text\") {\n return (\n <div key={`st-${i}`} className=\"text-text-secondary text-[11px]\">\n <MiniMarkdown content={g.content} maxHeight=\"max-h-24\" />\n </div>\n );\n }\n return <ToolCard key={`sc-${i}`} tool={g.tool} result={g.result} completed={!!(g.result)} projectName={projectName} />;\n })}\n </div>\n );\n}\n\n/** Inline markdown renderer for tool details (prompt, result) */\nfunction MiniMarkdown({ content, maxHeight = \"max-h-48\" }: { content: string; maxHeight?: string }) {\n return (\n <Suspense fallback={<div className=\"animate-pulse h-4 bg-muted rounded\" />}>\n <MarkdownRenderer content={content} className={`text-text-secondary overflow-auto ${maxHeight}`} />\n </Suspense>\n );\n}\n\n\n/** Real-time streaming bash output with auto-scroll */\nfunction StreamingBashOutput({ content, lineCount }: { content: string; lineCount: number }) {\n const preRef = useRef<HTMLPreElement>(null);\n const userScrolledRef = useRef(false);\n\n useEffect(() => {\n if (preRef.current && !userScrolledRef.current) {\n preRef.current.scrollTop = preRef.current.scrollHeight;\n }\n }, [content]);\n\n return (\n <div className=\"border-t border-border pt-1.5\">\n <div className=\"flex items-center gap-1 text-[10px] text-yellow-400 mb-1\">\n <Loader2 className=\"size-3 animate-spin\" />\n <span>Output ({lineCount} line{lineCount !== 1 ? \"s\" : \"\"}, streaming...)</span>\n </div>\n <pre\n ref={preRef}\n onScroll={(e) => {\n const el = e.currentTarget;\n userScrolledRef.current = el.scrollTop + el.clientHeight < el.scrollHeight - 20;\n }}\n className=\"overflow-x-auto overflow-y-auto max-h-60 text-text-subtle font-mono whitespace-pre-wrap break-all text-[11px]\"\n >\n {content.split(\"\\n\").slice(-200).join(\"\\n\")}\n </pre>\n </div>\n );\n}\n\nfunction truncate(str?: string, max = 50): string {\n if (!str) return \"\";\n return str.length > max ? str.slice(0, max) + \"…\" : str;\n}\n","import { AlertCircle, ChevronUp, History, Loader2 } from \"lucide-react\";\n\n/** Detects a JSONL transcript path in Claude's compact summary message text. */\nconst JSONL_PATH_RE = /read the full transcript at:\\s*(\\S+\\.jsonl)/i;\n\nexport function extractJsonlPath(text: string): string | null {\n const match = text.match(JSONL_PATH_RE);\n return match?.[1]?.trim() ?? null;\n}\n\nexport type PreCompactStatus = \"idle\" | \"loading\" | \"loaded\" | \"error\";\n\ninterface PreCompactButtonProps {\n status: PreCompactStatus;\n onLoad?: () => void;\n count?: number;\n}\n\n/**\n * Button shown when Claude's compact summary is detected.\n * Clicking triggers the pre-compact-messages fetch. Shows loading/loaded/error states.\n * Responsive: full-width on mobile, inline on desktop. Min 44px touch target.\n */\nexport function PreCompactButton({ status, onLoad, count }: PreCompactButtonProps) {\n const isBusy = status === \"loading\";\n const isLoaded = status === \"loaded\";\n const isError = status === \"error\";\n\n const label = isBusy\n ? \"Loading previous conversation...\"\n : isLoaded\n ? `Previous conversation loaded${count != null ? ` (${count})` : \"\"}`\n : isError\n ? \"Failed to load — retry\"\n : \"Load previous conversation\";\n\n const Icon = isBusy ? Loader2 : isLoaded ? ChevronUp : isError ? AlertCircle : History;\n\n return (\n <button\n type=\"button\"\n onClick={onLoad}\n disabled={isBusy || isLoaded}\n className=\"mt-2 inline-flex items-center justify-center gap-2 rounded-md border border-border bg-surface/50 px-4 py-2.5 text-sm text-text-primary hover:bg-surface transition-colors disabled:opacity-70 disabled:cursor-default w-full md:w-auto min-h-[44px]\"\n >\n <Icon className={`size-4 shrink-0 ${isBusy ? \"animate-spin\" : \"\"} ${isError ? \"text-red-400\" : \"\"}`} />\n <span>{label}</span>\n </button>\n );\n}\n","import { Component, type ReactNode } from \"react\";\n\ninterface Props {\n /** Plain text fallback when fallback ReactNode is not provided */\n fallbackContent?: string;\n /** Custom fallback ReactNode — takes precedence over fallbackContent */\n fallback?: ReactNode;\n children: ReactNode;\n}\n\ninterface State {\n hasError: boolean;\n}\n\n/**\n * Error boundary that catches React DOM reconciliation errors\n * (e.g. \"removeChild\" failures from rehype-raw or browser extensions).\n * Falls back to provided content instead of crashing the whole app.\n */\nexport class RenderErrorBoundary extends Component<Props, State> {\n override state: State = { hasError: false };\n\n static getDerivedStateFromError(): State {\n return { hasError: true };\n }\n\n override render() {\n if (this.state.hasError) {\n if (this.props.fallback) return this.props.fallback;\n return this.props.fallbackContent ? (\n <div className=\"text-sm whitespace-pre-wrap break-words text-text-primary opacity-80\">\n {this.props.fallbackContent}\n </div>\n ) : null;\n }\n return this.props.children;\n }\n}\n","import { Bot } from \"lucide-react\";\nimport { SessionListPanel } from \"./session-list-panel\";\nimport type { SessionInfo } from \"../../../types/chat\";\n\ninterface ChatWelcomeProps {\n projectName: string;\n onSelectSession: (session: SessionInfo) => void;\n}\n\nexport function ChatWelcome({ projectName, onSelectSession }: ChatWelcomeProps) {\n return (\n <div className=\"flex flex-col items-center justify-center h-full gap-6 text-text-secondary overflow-y-auto\">\n <div className=\"flex flex-col items-center gap-3\">\n <Bot className=\"size-10 text-text-subtle\" />\n <p className=\"text-sm\">Send a message to start a new conversation</p>\n </div>\n\n <SessionListPanel\n projectName={projectName}\n onSelectSession={onSelectSession}\n className=\"w-full px-4\"\n />\n </div>\n );\n}\n","import { useState, useCallback, useMemo, useEffect, useRef } from \"react\";\n\n/* ── Types ── */\nexport interface QuestionOption {\n label: string;\n description?: string;\n}\n\nexport interface Question {\n question: string;\n header?: string;\n options: QuestionOption[];\n multiSelect?: boolean;\n}\n\ninterface QuestionCardProps {\n questions: Question[];\n onSubmit: (answers: Record<string, string>) => void;\n onSkip: () => void;\n}\n\n/* ── Hook: form state ── */\nfunction useQuestionForm(questions: Question[]) {\n const [answers, setAnswers] = useState<Record<number, string[]>>({});\n const [customInputs, setCustomInputs] = useState<Record<number, string>>({});\n const [activeTab, setActiveTab] = useState(0);\n\n const handleSingleSelect = useCallback((qi: number, label: string) => {\n setAnswers((p) => ({ ...p, [qi]: [label] }));\n setCustomInputs((p) => ({ ...p, [qi]: \"\" }));\n }, []);\n\n const handleMultiSelect = useCallback((qi: number, label: string) => {\n setAnswers((p) => {\n const cur = p[qi] || [];\n return { ...p, [qi]: cur.includes(label) ? cur.filter((l) => l !== label) : [...cur, label] };\n });\n }, []);\n\n const handleCustomInput = useCallback((qi: number, value: string) => {\n setCustomInputs((p) => ({ ...p, [qi]: value }));\n if (value) setAnswers((p) => ({ ...p, [qi]: [] }));\n }, []);\n\n const hasAnswer = useCallback(\n (qi: number) => (answers[qi]?.length ?? 0) > 0 || (customInputs[qi]?.trim().length ?? 0) > 0,\n [answers, customInputs],\n );\n\n const allAnswered = useMemo(() => questions.every((_, i) => hasAnswer(i)), [questions, hasAnswer]);\n\n const getFinalAnswer = useCallback(\n (qi: number) => {\n const custom = customInputs[qi]?.trim();\n if (custom) return custom;\n return (answers[qi] ?? []).join(\", \");\n },\n [answers, customInputs],\n );\n\n const goToNextTab = useCallback(() => setActiveTab((p) => Math.min(p + 1, questions.length - 1)), [questions.length]);\n const goToPrevTab = useCallback(() => setActiveTab((p) => Math.max(p - 1, 0)), []);\n\n return {\n answers, customInputs, activeTab, setActiveTab,\n handleSingleSelect, handleMultiSelect, handleCustomInput,\n hasAnswer, allAnswered, getFinalAnswer, goToNextTab, goToPrevTab,\n };\n}\n\n/* ── Hook: keyboard navigation ── */\nfunction useQuestionKeyboard(config: {\n questions: Question[];\n activeTab: number;\n totalOptions: number;\n allAnswered: boolean;\n hasAnswer: (i: number) => boolean;\n onSelectOption: (i: number) => void;\n goToNextTab: () => void;\n goToPrevTab: () => void;\n onSubmit: () => void;\n customInputRef: React.RefObject<HTMLInputElement | null>;\n enabled: boolean;\n}) {\n const [focusedOption, setFocusedOption] = useState(0);\n const containerRef = useRef<HTMLDivElement>(null);\n\n useEffect(() => setFocusedOption(0), [config.activeTab]);\n\n useEffect(() => {\n if (!config.enabled) return;\n const handleKeyDown = (e: KeyboardEvent) => {\n const isTyping = document.activeElement === config.customInputRef.current;\n\n // Number keys 1-9\n if (!isTyping && e.key >= \"1\" && e.key <= \"9\") {\n e.preventDefault();\n const idx = parseInt(e.key) - 1;\n if (idx < config.totalOptions - 1) { setFocusedOption(idx); config.onSelectOption(idx); }\n return;\n }\n // O/0 → focus custom input\n if (!isTyping && (e.key === \"o\" || e.key === \"O\" || e.key === \"0\")) {\n e.preventDefault();\n config.customInputRef.current?.focus();\n setFocusedOption(config.totalOptions - 1);\n return;\n }\n // Tab between questions\n if (e.key === \"Tab\" && config.questions.length > 1) {\n e.preventDefault();\n e.shiftKey ? config.goToPrevTab() : config.goToNextTab();\n return;\n }\n if (!isTyping) {\n if (e.key === \"ArrowLeft\") { e.preventDefault(); config.goToPrevTab(); return; }\n if (e.key === \"ArrowRight\") { e.preventDefault(); config.goToNextTab(); return; }\n if (e.key === \"ArrowUp\") { e.preventDefault(); setFocusedOption((p) => Math.max(0, p - 1)); return; }\n if (e.key === \"ArrowDown\") { e.preventDefault(); setFocusedOption((p) => Math.min(config.totalOptions - 1, p + 1)); return; }\n if (e.key === \" \") { e.preventDefault(); config.onSelectOption(focusedOption); return; }\n }\n if (e.key === \"Enter\") {\n e.preventDefault();\n if (config.allAnswered) config.onSubmit();\n else if (config.hasAnswer(config.activeTab)) config.goToNextTab();\n return;\n }\n if (e.key === \"Escape\" && isTyping) { config.customInputRef.current?.blur(); }\n };\n\n const el = containerRef.current;\n if (el) {\n el.addEventListener(\"keydown\", handleKeyDown);\n el.setAttribute(\"tabindex\", \"0\");\n if (!el.contains(document.activeElement)) el.focus();\n }\n return () => { el?.removeEventListener(\"keydown\", handleKeyDown); };\n }, [config, focusedOption]);\n\n return { focusedOption, setFocusedOption, containerRef };\n}\n\n/* ── Component ── */\nexport function QuestionCard({ questions, onSubmit, onSkip }: QuestionCardProps) {\n const customInputRef = useRef<HTMLInputElement>(null);\n const form = useQuestionForm(questions);\n const currentQ = questions[form.activeTab];\n const totalOptions = currentQ ? currentQ.options.length + 1 : 0;\n const hasMultiple = questions.length > 1;\n\n const handleSubmit = useCallback(() => {\n if (!form.allAnswered) return;\n const result: Record<string, string> = {};\n questions.forEach((q, i) => { result[q.question] = form.getFinalAnswer(i); });\n onSubmit(result);\n }, [form.allAnswered, form.getFinalAnswer, questions, onSubmit]);\n\n const handleSelectOption = useCallback(\n (index: number) => {\n if (!currentQ || index < 0) return;\n if (index < currentQ.options.length) {\n const label = currentQ.options[index]?.label;\n if (!label) return;\n if (currentQ.multiSelect) form.handleMultiSelect(form.activeTab, label);\n else form.handleSingleSelect(form.activeTab, label);\n } else if (index === currentQ.options.length) {\n customInputRef.current?.focus();\n }\n },\n [currentQ, form],\n );\n\n const kb = useQuestionKeyboard({\n questions, activeTab: form.activeTab, totalOptions,\n allAnswered: form.allAnswered, hasAnswer: form.hasAnswer,\n onSelectOption: handleSelectOption,\n goToNextTab: form.goToNextTab, goToPrevTab: form.goToPrevTab,\n onSubmit: handleSubmit, customInputRef, enabled: true,\n });\n\n const selectWithFocus = useCallback(\n (index: number) => { handleSelectOption(index); kb.setFocusedOption(index); },\n [handleSelectOption, kb.setFocusedOption],\n );\n\n return (\n <div\n ref={kb.containerRef}\n className=\"rounded-lg border-2 border-primary/30 bg-primary/5 p-3 space-y-3 outline-none animate-in slide-in-from-bottom-2\"\n >\n {/* Header */}\n <div className=\"flex items-center justify-between text-sm font-medium text-text-primary\">\n <span>\n AI has {hasMultiple ? `${questions.length} questions` : \"a question\"}\n </span>\n <span className=\"text-[10px] text-text-secondary font-normal\">\n {hasMultiple ? \"←→ tabs · \" : \"\"}↑↓ options · 1-{Math.min(totalOptions - 1, 9)} select · Enter submit\n </span>\n </div>\n\n {/* Tabs */}\n {hasMultiple && (\n <div className=\"flex gap-1 p-1 bg-background rounded-md overflow-x-auto border border-border\">\n {questions.map((q, i) => (\n <button\n key={i}\n className={`flex items-center gap-1.5 px-3 py-1.5 rounded text-xs whitespace-nowrap transition-all ${\n form.activeTab === i\n ? \"bg-primary text-primary-foreground\"\n : form.hasAnswer(i)\n ? \"text-primary bg-transparent\"\n : \"text-text-secondary hover:bg-surface-elevated\"\n }`}\n onClick={() => { form.setActiveTab(i); kb.setFocusedOption(0); }}\n tabIndex={-1}\n >\n <span\n className={`flex items-center justify-center w-4 h-4 rounded-full text-[10px] font-semibold ${\n form.activeTab === i\n ? \"bg-white/20\"\n : form.hasAnswer(i)\n ? \"bg-primary/20 text-primary\"\n : \"bg-surface-elevated text-text-secondary\"\n }`}\n >\n {form.hasAnswer(i) ? \"✓\" : i + 1}\n </span>\n <span className=\"max-w-[100px] overflow-hidden text-ellipsis\">{q.header || `Q${i + 1}`}</span>\n </button>\n ))}\n </div>\n )}\n\n {/* Current question */}\n {currentQ && (\n <div className=\"space-y-2\">\n {!hasMultiple && currentQ.header && (\n <div className=\"text-[11px] font-semibold uppercase tracking-wide text-text-secondary\">{currentQ.header}</div>\n )}\n <div className=\"text-sm text-text-primary\">{currentQ.question}</div>\n {currentQ.multiSelect && <div className=\"text-[11px] text-text-secondary\">Select multiple</div>}\n\n {/* Options */}\n <div className=\"flex flex-col gap-1.5\">\n {currentQ.options.map((opt, oi) => {\n const isSelected = (form.answers[form.activeTab] || []).includes(opt.label);\n const isFocused = kb.focusedOption === oi;\n return (\n <button\n key={oi}\n onClick={() => selectWithFocus(oi)}\n className={`text-left flex items-start gap-2.5 rounded px-2.5 py-2 text-xs border transition-all ${\n isSelected\n ? \"border-primary bg-primary/10 text-text-primary\"\n : \"border-border bg-background text-text-secondary hover:border-primary/40 hover:bg-primary/5\"\n } ${isFocused ? \"ring-2 ring-primary/40 ring-offset-1 ring-offset-background\" : \"\"}`}\n >\n <span className={`flex items-center justify-center w-4.5 h-4.5 rounded text-[10px] font-semibold shrink-0 mt-px ${\n isSelected ? \"bg-primary/20 text-primary\" : \"bg-surface-elevated text-text-secondary\"\n }`}>\n {oi + 1}\n </span>\n <div className=\"flex flex-col gap-0.5 flex-1\">\n <span className=\"font-medium text-text-primary\">{opt.label}</span>\n {opt.description && <span className=\"text-[11px] text-text-secondary\">{opt.description}</span>}\n </div>\n </button>\n );\n })}\n\n {/* Other / custom input */}\n <div\n className={`flex items-start gap-2.5 rounded px-2.5 py-2 text-xs border border-dashed transition-all border-border bg-transparent ${\n kb.focusedOption === totalOptions - 1 ? \"ring-2 ring-primary/40 ring-offset-1 ring-offset-background\" : \"\"\n }`}\n >\n <span className=\"flex items-center justify-center w-4.5 h-4.5 rounded bg-surface-elevated text-text-secondary text-[10px] font-semibold shrink-0 mt-px\">\n O\n </span>\n <input\n ref={customInputRef}\n type=\"text\"\n className=\"flex-1 px-2 py-1 text-xs bg-surface border border-border rounded text-text-primary outline-none placeholder:text-text-subtle focus:border-primary\"\n placeholder=\"Other (press O to type)...\"\n value={form.customInputs[form.activeTab] || \"\"}\n onChange={(e) => form.handleCustomInput(form.activeTab, e.target.value)}\n onFocus={() => kb.setFocusedOption(totalOptions - 1)}\n />\n </div>\n </div>\n </div>\n )}\n\n {/* Buttons */}\n <div className=\"flex gap-2 justify-end pt-1\">\n {hasMultiple && (\n <>\n <button\n className=\"px-3 py-1.5 text-xs rounded border border-border bg-background text-text-primary hover:bg-surface-elevated disabled:opacity-40 disabled:cursor-not-allowed transition-colors\"\n onClick={form.goToPrevTab}\n disabled={form.activeTab === 0}\n tabIndex={-1}\n >\n ← Prev\n </button>\n <button\n className=\"px-3 py-1.5 text-xs rounded border border-border bg-background text-text-primary hover:bg-surface-elevated disabled:opacity-40 disabled:cursor-not-allowed transition-colors\"\n onClick={form.goToNextTab}\n disabled={form.activeTab === questions.length - 1}\n tabIndex={-1}\n >\n Next →\n </button>\n </>\n )}\n <button\n onClick={onSkip}\n className=\"px-4 py-1.5 rounded border border-border bg-background text-text-secondary text-xs hover:bg-surface-elevated transition-colors\"\n tabIndex={-1}\n >\n Skip\n </button>\n <button\n onClick={handleSubmit}\n disabled={!form.allAnswered}\n className=\"px-4 py-1.5 rounded bg-primary text-primary-foreground text-xs font-medium hover:bg-primary/80 transition-colors disabled:opacity-40 disabled:cursor-not-allowed\"\n tabIndex={-1}\n >\n Submit {form.allAnswered ? \"✓\" : `(${questions.filter((_, i) => form.hasAnswer(i)).length}/${questions.length})`}\n </button>\n </div>\n </div>\n );\n}\n","import { useEffect, useRef, useState, useMemo, useCallback, memo, lazy, Suspense } from \"react\";\nimport { StickToBottom, useStickToBottomContext } from \"use-stick-to-bottom\";\nimport { getAuthToken } from \"@/lib/api-client\";\nimport type { ChatMessage, ChatEvent } from \"../../../types/chat\";\nimport type { SessionPhase } from \"../../../types/api\";\nimport type { BashPartialEntry } from \"../../hooks/use-chat\";\nimport { ToolCard } from \"./tool-cards\";\nimport { extractJsonlPath } from \"./pre-compact-button\";\nconst MarkdownRenderer = lazy(() =>\n import(\"@/components/shared/markdown-renderer\").then((m) => ({ default: m.MarkdownRenderer }))\n);\nimport { cn, basename } from \"@/lib/utils\";\nimport { RenderErrorBoundary } from \"@/components/shared/markdown-error-boundary\";\n\nimport {\n AlertCircle,\n ShieldAlert,\n Bot,\n ChevronDown,\n ChevronRight,\n FileText,\n Image as ImageIcon,\n Copy,\n Check,\n CheckCircle2,\n Loader2,\n RotateCcw,\n TerminalSquare,\n ChevronUp,\n Tag,\n XCircle,\n ExternalLink,\n Slash,\n} from \"lucide-react\";\nimport { ChatWelcome } from \"./chat-welcome\";\nimport { QuestionCard } from \"./question-card\";\nimport type { Question } from \"./question-card\";\nimport { useTabStore } from \"@/stores/tab-store\";\nimport { api } from \"@/lib/api-client\";\nimport { useProjectStore } from \"@/stores/project-store\";\nimport { useImageOverlay } from \"@/stores/image-overlay-store\";\n\ninterface MessageListProps {\n messages: ChatMessage[];\n messagesLoading?: boolean;\n pendingApproval: { requestId: string; tool: string; input: unknown } | null;\n onApprovalResponse: (requestId: string, approved: boolean, data?: unknown) => void;\n isStreaming: boolean;\n phase?: SessionPhase;\n connectingElapsed?: number;\n statusMessage?: string | null;\n compactStatus?: \"compacting\" | null;\n projectName?: string;\n /** Called when user clicks Fork/Rewind — opens new forked chat tab */\n onFork?: (userMessage: string, messageId?: string) => void;\n /** Called when user selects a recent session from the welcome screen */\n onSelectSession?: (session: import(\"../../../types/chat\").SessionInfo) => void;\n /** Partial bash output ref from useChat for real-time streaming */\n bashPartialOutput?: React.RefObject<Map<string, BashPartialEntry>>;\n /** Fetches pre-compact transcript and prepends messages. Returns loaded count. */\n onExpandCompact?: (compactMessageId: string, jsonlPath: string) => Promise<number>;\n /** Whether a given compact message has already been expanded. */\n isCompactExpanded?: (compactMessageId: string) => boolean;\n}\n\nexport function MessageList({\n messages,\n messagesLoading,\n pendingApproval,\n onApprovalResponse,\n isStreaming,\n phase,\n onSelectSession,\n connectingElapsed,\n statusMessage,\n compactStatus,\n projectName,\n onFork,\n bashPartialOutput,\n onExpandCompact,\n isCompactExpanded,\n}: MessageListProps) {\n // Scroll handled by StickToBottom wrapper — no manual scroll logic needed\n\n const PAGE_SIZE = 50;\n const [visibleCount, setVisibleCount] = useState(PAGE_SIZE);\n\n // Reset visible count when conversation identity changes (not on every streaming tick)\n const conversationId = messages[0]?.id;\n useEffect(() => { setVisibleCount(PAGE_SIZE); }, [conversationId]);\n\n const filtered = useMemo(() => messages.filter((msg) => {\n const hasContent = msg.content && msg.content.trim().length > 0;\n const hasEvents = msg.events && msg.events.length > 0;\n // User bubbles only render text — hide SDK tool-result user messages\n // that have no text content (their events are merged into assistant)\n if (msg.role === \"user\") return hasContent;\n return hasContent || hasEvents;\n }), [messages]);\n\n const displayed = useMemo(() => {\n const start = Math.max(0, filtered.length - visibleCount);\n return filtered.slice(start);\n }, [filtered, visibleCount]);\n\n const hasMoreInMemory = visibleCount < filtered.length;\n\n // Stable fork handler — avoids new closure per message (preserves MessageBubble memo)\n const handleFork = useCallback((msgContent: string, msgId: string | undefined) => {\n onFork?.(msgContent, msgId);\n }, [onFork]);\n\n // Scroll anchor bridge published from inside StickToBottom (needs the context's scrollRef).\n const scrollAnchorRef = useRef<ScrollAnchorHandle | null>(null);\n\n // Find the topmost displayed message that has an unexpanded compact JSONL path.\n const findTopUnexpandedCompact = useCallback((): { id: string; jsonlPath: string } | null => {\n if (!onExpandCompact || !isCompactExpanded) return null;\n for (const msg of displayed) {\n if (isCompactExpanded(msg.id)) continue;\n // Check user message content for JSONL path\n const path = extractJsonlPath(msg.content || \"\");\n if (path) return { id: msg.id, jsonlPath: path };\n // Check assistant events for JSONL path\n if (msg.events) {\n for (const ev of msg.events) {\n if (ev.type === \"text\") {\n const evPath = extractJsonlPath(ev.content || \"\");\n if (evPath) return { id: msg.id, jsonlPath: evPath };\n }\n }\n }\n }\n return null;\n }, [displayed, onExpandCompact, isCompactExpanded]);\n\n const topUnexpandedCompact = findTopUnexpandedCompact();\n const hasMore = hasMoreInMemory || !!topUnexpandedCompact;\n\n // Unified load-more: first show more in-memory messages, then auto-expand compact history\n const [autoLoadingCompact, setAutoLoadingCompact] = useState(false);\n const loadMore = useCallback(async () => {\n if (hasMoreInMemory) {\n scrollAnchorRef.current?.capture();\n setVisibleCount((c) => c + PAGE_SIZE);\n requestAnimationFrame(() => requestAnimationFrame(() => scrollAnchorRef.current?.restore()));\n return;\n }\n // All in-memory messages visible — try expanding topmost compact\n if (!topUnexpandedCompact || !onExpandCompact || autoLoadingCompact) return;\n setAutoLoadingCompact(true);\n try {\n scrollAnchorRef.current?.capture();\n const count = await onExpandCompact(topUnexpandedCompact.id, topUnexpandedCompact.jsonlPath);\n setVisibleCount((c) => c + count);\n requestAnimationFrame(() => requestAnimationFrame(() => scrollAnchorRef.current?.restore()));\n } finally {\n setAutoLoadingCompact(false);\n }\n }, [hasMoreInMemory, topUnexpandedCompact, onExpandCompact, autoLoadingCompact]);\n\n if (messagesLoading) {\n return (\n <div className=\"flex flex-col items-center justify-center h-full gap-3 text-text-secondary\">\n <Bot className=\"size-10 text-text-subtle animate-pulse\" />\n <p className=\"text-sm\">Loading messages...</p>\n </div>\n );\n }\n\n if (messages.length === 0 && !isStreaming) {\n return (\n <ChatWelcome\n projectName={projectName || \"\"}\n onSelectSession={onSelectSession || (() => {})}\n />\n );\n }\n\n return (\n <div className=\"relative flex-1 overflow-hidden flex flex-col min-h-0\">\n <StickToBottom className=\"flex-1 overflow-y-auto overflow-x-hidden [contain:strict] [overflow-anchor:auto]\" resize=\"smooth\" initial=\"instant\">\n <StickToBottom.Content className=\"p-4 space-y-4 select-none [&>*]:[overflow-anchor:auto]\">\n <ScrollAnchorBridge bridgeRef={scrollAnchorRef} />\n {hasMore && (\n <LoadMoreSentinel onLoadMore={loadMore} loading={autoLoadingCompact} />\n )}\n {displayed.map((msg, idx) => {\n const globalIdx = filtered.length - displayed.length + idx;\n const prevMsg = globalIdx > 0 ? filtered[globalIdx - 1] : undefined;\n return (\n <RenderErrorBoundary key={msg.id} fallbackContent={msg.content}>\n <MessageBubble\n message={msg}\n isStreaming={isStreaming && msg.id.startsWith(\"streaming-\")}\n projectName={projectName}\n onFork={msg.role === \"user\" && onFork ? handleFork : undefined}\n prevMsgId={prevMsg?.sdkUuid ?? prevMsg?.id}\n bashPartialOutput={bashPartialOutput}\n />\n </RenderErrorBoundary>\n );\n })}\n\n {pendingApproval && (\n pendingApproval.tool === \"AskUserQuestion\"\n ? <AskUserQuestionCard approval={pendingApproval} onRespond={onApprovalResponse} />\n : <ApprovalCard approval={pendingApproval} onRespond={onApprovalResponse} />\n )}\n\n {isStreaming && <ThinkingIndicator lastMessage={messages[messages.length - 1]} phase={phase} elapsed={connectingElapsed} statusMessage={compactStatus === \"compacting\" ? \"Compacting messages...\" : statusMessage} />}\n </StickToBottom.Content>\n <ScrollToBottomButton />\n </StickToBottom>\n </div>\n );\n}\n\n/** Imperative handle exposed by ScrollAnchorBridge — capture & restore scroll on prepend. */\ninterface ScrollAnchorHandle {\n /** Record current scrollTop + scrollHeight before a prepend. No-op if user is at bottom. */\n capture: () => void;\n /** After prepend commits, adjust scrollTop by the height delta so viewport stays locked. */\n restore: () => void;\n}\n\n/**\n * Consumes StickToBottom's scrollRef (only accessible inside its subtree) and publishes\n * capture/restore functions to a ref owned by the parent MessageList, so prepend-history\n * expansion can preserve scroll position across the re-render.\n */\nfunction ScrollAnchorBridge({ bridgeRef }: { bridgeRef: React.MutableRefObject<ScrollAnchorHandle | null> }) {\n const { scrollRef, isAtBottom } = useStickToBottomContext();\n const state = useRef<{ top: number; height: number } | null>(null);\n useEffect(() => {\n bridgeRef.current = {\n capture: () => {\n const el = scrollRef.current;\n if (!el || isAtBottom) { state.current = null; return; } // skip if sticking to bottom\n state.current = { top: el.scrollTop, height: el.scrollHeight };\n },\n restore: () => {\n const el = scrollRef.current;\n const s = state.current;\n if (!el || !s) return;\n const delta = el.scrollHeight - s.height;\n if (delta !== 0) el.scrollTop = s.top + delta;\n state.current = null;\n },\n };\n return () => { bridgeRef.current = null; };\n }, [bridgeRef, scrollRef, isAtBottom]);\n return null;\n}\n\n/** Floating button to scroll back to bottom when user has scrolled up */\nfunction ScrollToBottomButton() {\n const { isAtBottom, scrollToBottom } = useStickToBottomContext();\n if (isAtBottom) return null;\n return (\n <button\n onClick={() => scrollToBottom()}\n className=\"absolute bottom-4 left-1/2 -translate-x-1/2 z-10 flex items-center gap-1 px-3 py-1 rounded-full bg-surface-elevated border border-border text-xs text-text-secondary hover:text-foreground shadow-lg transition-all\"\n >\n <ChevronDown className=\"size-3\" />\n Scroll to bottom\n </button>\n );\n}\n\n/** IntersectionObserver sentinel — auto-triggers loadMore when scrolled near top.\n * Debounced to prevent cascade: after each trigger, waits 150ms before re-arming. */\nfunction LoadMoreSentinel({ onLoadMore, loading }: { onLoadMore: () => void; loading: boolean }) {\n const sentinelRef = useRef<HTMLDivElement>(null);\n const onLoadMoreRef = useRef(onLoadMore);\n onLoadMoreRef.current = onLoadMore;\n const cooldownRef = useRef(false);\n\n useEffect(() => {\n const el = sentinelRef.current;\n if (!el) return;\n const observer = new IntersectionObserver(\n ([entry]) => {\n if (!entry?.isIntersecting || cooldownRef.current) return;\n cooldownRef.current = true;\n onLoadMoreRef.current();\n setTimeout(() => { cooldownRef.current = false; }, 150);\n },\n { rootMargin: \"200px 0px 0px 0px\" },\n );\n observer.observe(el);\n return () => observer.disconnect();\n }, []);\n\n return (\n <div ref={sentinelRef} className=\"flex items-center justify-center py-2 text-xs text-text-secondary\">\n {loading && <><Loader2 className=\"size-3 animate-spin mr-1.5\" />Loading previous conversation...</>}\n </div>\n );\n}\n\nconst MessageBubble = memo(function MessageBubble({ message, isStreaming, projectName, onFork, prevMsgId, bashPartialOutput }: {\n message: ChatMessage; isStreaming: boolean; projectName?: string;\n onFork?: (content: string, messageId: string | undefined) => void;\n prevMsgId?: string;\n bashPartialOutput?: React.RefObject<Map<string, BashPartialEntry>>;\n}) {\n if (message.role === \"user\") {\n const handleFork = onFork ? () => onFork(message.content, prevMsgId) : undefined;\n return (\n <UserBubble\n content={message.content}\n messageId={message.id}\n projectName={projectName}\n onFork={handleFork}\n />\n );\n }\n\n if (message.role === \"system\") {\n return (\n <div className=\"flex items-center gap-2 rounded-lg bg-red-500/10 border border-red-500/20 px-3 py-2 text-sm text-red-400\">\n <AlertCircle className=\"size-4 shrink-0\" />\n <p>{message.content}</p>\n </div>\n );\n }\n\n // Assistant message — render events in order (text interleaved with tool calls)\n return (\n <div className=\"flex flex-col gap-2\">\n {message.events && message.events.length > 0\n ? <InterleavedEvents events={message.events} isStreaming={isStreaming} projectName={projectName} bashPartialOutput={bashPartialOutput} />\n : message.content && (\n <div className=\"text-sm text-text-primary select-text\">\n <MarkdownContent content={message.content} projectName={projectName} />\n </div>\n )}\n {message.accountLabel && (\n <p className=\"text-[10px] select-none\" style={{ color: \"var(--color-text-subtle)\" }}>\n via {message.accountLabel}\n </p>\n )}\n </div>\n );\n});\n\n/** Image extensions that can be previewed inline */\nconst IMAGE_EXTS = new Set([\".png\", \".jpg\", \".jpeg\", \".gif\", \".webp\"]);\n\ninterface SystemTag {\n name: string;\n label: string;\n content: string;\n}\n\nconst TAG_LABELS: Record<string, string> = {\n \"system-reminder\": \"Context\",\n \"claudeMd\": \"CLAUDE.md\",\n \"gitStatus\": \"Git Status\",\n \"currentDate\": \"Date\",\n \"fast_mode_info\": \"Fast Mode\",\n \"available-deferred-tools\": \"Tools\",\n \"task-notification\": \"Task Result\",\n \"environment_details\": \"Environment\",\n};\n\n/** Extract system-injected XML tags into structured objects + clean text */\nfunction extractSystemTags(text: string): { cleanText: string; tags: SystemTag[] } {\n const tags: SystemTag[] = [];\n const tagPattern = /<(system-reminder|available-deferred-tools|antml:[\\w-]+|fast_mode_info|claudeMd|gitStatus|currentDate|task-notification|environment_details)[^>]*>([\\s\\S]*?)<\\/\\1>/g;\n let match;\n while ((match = tagPattern.exec(text)) !== null) {\n const name = match[1]!;\n tags.push({\n name,\n label: TAG_LABELS[name] ?? name.replace(/^antml:/, \"\").replace(/-/g, \" \"),\n content: match[2]!.trim(),\n });\n }\n const cleanText = text.replace(tagPattern, \"\").trim();\n return { cleanText, tags };\n}\n\n/** Extract slash command tags from user message content */\ninterface SlashCommand {\n name: string;\n args?: string;\n}\nconst COMMAND_TAG_RE = /<command-message>[\\s\\S]*?<\\/command-message>\\s*<command-name>([\\s\\S]*?)<\\/command-name>(?:\\s*<command-args>([\\s\\S]*?)<\\/command-args>)?/;\n\nfunction parseCommandTags(text: string): { command: SlashCommand | null; cleanText: string } {\n const match = text.match(COMMAND_TAG_RE);\n if (!match) return { command: null, cleanText: text };\n const name = match[1]!.trim();\n const args = match[2]?.trim() || undefined;\n const cleanText = text.replace(COMMAND_TAG_RE, \"\").trim();\n return { command: { name, args }, cleanText };\n}\n\n/** Parse user message content, extracting attached file paths and the actual text */\nfunction parseUserAttachments(content: string): { files: string[]; text: string } {\n // Match: [Attached file: /path] or [Attached files:\\n/path1\\n/path2\\n]\n const singleMatch = content.match(/^\\[Attached file: (.+?)\\]\\n\\n?/);\n if (singleMatch) {\n return { files: [singleMatch[1]!], text: content.slice(singleMatch[0].length) };\n }\n\n const multiMatch = content.match(/^\\[Attached files:\\n([\\s\\S]+?)\\]\\n\\n?/);\n if (multiMatch) {\n const files = multiMatch[1]!.split(\"\\n\").map((l) => l.trim()).filter(Boolean);\n return { files, text: content.slice(multiMatch[0].length) };\n }\n\n return { files: [], text: content };\n}\n\n/** Build a preview URL for an uploaded file (served from /chat/uploads/:filename) */\nfunction uploadPreviewUrl(filePath: string, projectName?: string): string {\n const filename = basename(filePath);\n // Use a generic project name — the upload route is project-scoped but files are global\n return `/api/project/${encodeURIComponent(projectName ?? \"_\")}/chat/uploads/${encodeURIComponent(filename)}`;\n}\n\n/** Check if a file path is an image based on extension */\nfunction isImagePath(path: string): boolean {\n const dot = path.lastIndexOf(\".\");\n if (dot === -1) return false;\n return IMAGE_EXTS.has(path.slice(dot).toLowerCase());\n}\n\nfunction isPdfPath(path: string): boolean {\n return path.toLowerCase().endsWith(\".pdf\");\n}\n\n/** Detect if tags contain system-injected content (not real user input) */\nconst SYSTEM_TAG_NAMES = new Set([\"task-notification\", \"environment_details\"]);\n\n/** User message bubble — full width, collapsible, with system tag badges */\nfunction UserBubble({ content, messageId, projectName, onFork }: {\n content: string;\n messageId?: string;\n projectName?: string;\n onFork?: () => void;\n}) {\n const { files, text, tags, command } = useMemo(() => {\n const parsed = parseUserAttachments(content);\n const { cleanText: noSysTags, tags } = extractSystemTags(parsed.text);\n const { command, cleanText } = parseCommandTags(noSysTags);\n const bodyText = command?.args\n ? (cleanText ? `${command.args}\\n\\n${cleanText}` : command.args)\n : cleanText;\n return { files: parsed.files, text: bodyText, tags, command };\n }, [content]);\n\n const isSystemContext = tags.some((t) => SYSTEM_TAG_NAMES.has(t.name));\n\n const [expanded, setExpanded] = useState(false);\n const [isOverflowing, setIsOverflowing] = useState(false);\n const contentRef = useRef<HTMLDivElement>(null);\n\n useEffect(() => {\n const el = contentRef.current;\n if (!el) return;\n const check = () => setIsOverflowing(el.scrollHeight > el.clientHeight + 2);\n check();\n const ro = new ResizeObserver(check);\n ro.observe(el);\n return () => ro.disconnect();\n }, [text]);\n\n return (\n <div className={cn(\n \"group/user relative rounded-lg px-3 py-2 text-sm border shadow-sm\",\n isSystemContext\n ? \"bg-surface/40 border-border/40 text-text-secondary\"\n : \"bg-primary/10 border-primary/15 text-text-primary\",\n )}>\n {/* System tags as badges */}\n {tags.length > 0 && <SystemTagBadges tags={tags} />}\n\n {/* Slash command chip — args rendered in body for expand/collapse support */}\n {command && (\n <div className=\"flex items-center gap-1.5 mb-0.5\">\n <span className=\"inline-flex items-center gap-1 rounded-md bg-primary/15 border border-primary/20 px-2 py-0.5 text-xs font-medium text-primary\">\n <Slash className=\"size-3 shrink-0\" />\n {command.name}\n </span>\n </div>\n )}\n\n {/* Attached files — image thumbnails + file chips */}\n {files.length > 0 && (\n <div className=\"flex flex-wrap gap-1.5\">\n {files.map((filePath, i) =>\n isImagePath(filePath) ? (\n <AuthImageThumbnail key={i} filePath={filePath} projectName={projectName} />\n ) : (\n <div\n key={i}\n className=\"flex items-center gap-1 rounded-md border border-border/60 bg-background/40 px-1.5 py-0.5 text-[11px] text-text-secondary\"\n >\n <FileText className=\"size-3 shrink-0\" />\n <span className=\"truncate max-w-32\">{basename(filePath)}</span>\n </div>\n ),\n )}\n </div>\n )}\n\n {/* Text content — 2-line clamp by default, expandable */}\n {text && (\n <div\n ref={contentRef}\n className={cn(\n \"whitespace-pre-wrap break-words transition-all duration-200 select-text\",\n !expanded && \"line-clamp-2\",\n expanded && \"max-h-[50vh] overflow-y-auto\",\n )}\n >\n {isSystemContext ? <TextWithFilePaths text={text} projectName={projectName} /> : text}\n </div>\n )}\n {(isOverflowing || expanded) && (\n <button\n onClick={() => setExpanded(!expanded)}\n className={cn(\n \"flex items-center gap-1 text-xs mt-1 transition-colors\",\n isSystemContext ? \"text-text-subtle hover:text-text-secondary\" : \"text-primary/70 hover:text-primary\",\n )}\n >\n {expanded ? <><ChevronUp className=\"size-3\" />Show less</> : <><ChevronDown className=\"size-3\" />Show more</>}\n </button>\n )}\n {/* Fork/Rewind button — only for real user messages */}\n {!isSystemContext && onFork && (\n <button\n onClick={onFork}\n title=\"Retry from this message (fork session)\"\n className=\"absolute top-1.5 right-1.5 can-hover:opacity-0 can-hover:group-hover/user:opacity-100 transition-opacity size-5 flex items-center justify-center rounded text-text-subtle hover:text-text-primary\"\n >\n <RotateCcw className=\"size-3\" />\n </button>\n )}\n </div>\n );\n}\n\n/** Render system tags as collapsible badges */\nfunction SystemTagBadges({ tags }: { tags: SystemTag[] }) {\n return (\n <div className=\"flex flex-wrap gap-1.5\">\n {tags.map((tag, i) => (\n <SystemTagBadge key={i} tag={tag} />\n ))}\n </div>\n );\n}\n\nfunction SystemTagBadge({ tag }: { tag: SystemTag }) {\n const [open, setOpen] = useState(false);\n\n // Task notification: render formatted instead of raw XML\n if (tag.name === \"task-notification\") {\n return <TaskNotificationBadge content={tag.content} />;\n }\n\n return (\n <div className=\"text-xs\">\n <button\n onClick={() => setOpen(!open)}\n className=\"flex items-center gap-1 rounded-full border border-border/60 bg-surface/50 px-2 py-0.5 text-text-subtle hover:text-text-secondary hover:bg-surface transition-colors\"\n >\n <Tag className=\"size-2.5\" />\n <span>{tag.label}</span>\n <ChevronRight className={cn(\"size-2.5 transition-transform\", open && \"rotate-90\")} />\n </button>\n {open && (\n <div className=\"mt-1 rounded border border-border/40 bg-surface/30 px-2 py-1.5 text-[11px] text-text-subtle/80 whitespace-pre-wrap max-h-40 overflow-y-auto leading-relaxed\">\n {tag.content}\n </div>\n )}\n </div>\n );\n}\n\n/** Extract a sub-tag value from XML-like content */\nfunction xmlTag(content: string, tag: string): string | undefined {\n const m = content.match(new RegExp(`<${tag}>([\\\\s\\\\S]*?)</${tag}>`));\n return m?.[1]?.trim() || undefined;\n}\n\n/** Formatted badge for <task-notification> — shows status, summary, output file, result */\nfunction TaskNotificationBadge({ content }: { content: string }) {\n const [open, setOpen] = useState(false);\n const status = xmlTag(content, \"status\");\n const summary = xmlTag(content, \"summary\");\n const outputFile = xmlTag(content, \"output-file\");\n const result = xmlTag(content, \"result\");\n const isOk = status === \"completed\";\n\n return (\n <div className=\"text-xs\">\n <button\n onClick={() => setOpen(!open)}\n className=\"flex items-center gap-1.5 rounded-full border border-border/60 bg-surface/50 px-2 py-0.5 text-text-subtle hover:text-text-secondary hover:bg-surface transition-colors\"\n >\n {isOk ? <CheckCircle2 className=\"size-2.5 text-green-500\" /> : <XCircle className=\"size-2.5 text-yellow-500\" />}\n <span className=\"truncate max-w-80\">{summary ?? \"Task notification\"}</span>\n <ChevronRight className={cn(\"size-2.5 transition-transform shrink-0\", open && \"rotate-90\")} />\n </button>\n {open && (\n <div className=\"mt-1 rounded border border-border/40 bg-surface/30 px-2 py-1.5 space-y-1.5\">\n {/* Full summary (button truncates it) */}\n {summary && <p className=\"text-[11px] text-text-secondary\">{summary}</p>}\n {outputFile && <FilePathChip path={outputFile} />}\n {result && (\n <div className=\"text-[11px] text-text-subtle/80 max-h-60 overflow-y-auto leading-relaxed\">\n <MarkdownContent content={result} />\n </div>\n )}\n </div>\n )}\n </div>\n );\n}\n\n/** Clickable file path chip — opens file in editor tab */\nfunction FilePathChip({ path, projectName }: { path: string; projectName?: string }) {\n const handleClick = useCallback(() => {\n const openTab = useTabStore.getState().openTab;\n const pName = projectName ?? useProjectStore.getState().activeProject?.name;\n const fileName = basename(path);\n const meta: Record<string, unknown> = { filePath: path };\n if (pName) meta.projectName = pName;\n // Try to verify file exists, then open; fallback: open directly\n api.get(`/api/fs/read?path=${encodeURIComponent(path)}`).then(() => {\n openTab({ type: \"editor\", title: fileName, metadata: meta, projectId: null, closable: true });\n }).catch(() => {\n openTab({ type: \"editor\", title: fileName, metadata: meta, projectId: null, closable: true });\n });\n }, [path, projectName]);\n\n return (\n <button\n type=\"button\"\n onClick={handleClick}\n className=\"inline-flex items-center gap-1 rounded border border-border/50 bg-surface/50 px-1.5 py-0.5 font-mono text-[10px] text-text-secondary hover:text-text-primary hover:bg-surface transition-colors cursor-pointer\"\n >\n <FileText className=\"size-2.5 shrink-0\" />\n <span className=\"truncate max-w-60\">{basename(path)}</span>\n <ExternalLink className=\"size-2 shrink-0 opacity-50\" />\n </button>\n );\n}\n\n/** Render text with absolute file paths detected and turned into clickable chips */\nfunction TextWithFilePaths({ text, projectName }: { text: string; projectName?: string }) {\n const parts = useMemo(() => {\n // Match absolute file paths (at least 2 segments)\n const re = /(\\/(?:[\\w.\\-]+\\/)+[\\w.\\-]+)/g;\n const result: { kind: \"text\" | \"path\"; value: string }[] = [];\n let last = 0;\n let m;\n while ((m = re.exec(text)) !== null) {\n if (m.index > last) result.push({ kind: \"text\", value: text.slice(last, m.index) });\n result.push({ kind: \"path\", value: m[1]! });\n last = m.index + m[0].length;\n }\n if (last < text.length) result.push({ kind: \"text\", value: text.slice(last) });\n return result;\n }, [text]);\n\n return (\n <>\n {parts.map((p, i) =>\n p.kind === \"path\"\n ? <FilePathChip key={i} path={p.value} projectName={projectName} />\n : <span key={i}>{p.value}</span>,\n )}\n </>\n );\n}\n\n/** Hook: fetch an image via auth header, return blob URL */\nfunction useAuthBlob(src: string): { blobUrl: string | null; error: boolean } {\n const [blobUrl, setBlobUrl] = useState<string | null>(null);\n const [error, setError] = useState(false);\n\n useEffect(() => {\n let revoked = false;\n let url: string | undefined;\n const token = getAuthToken();\n fetch(src, { headers: token ? { Authorization: `Bearer ${token}` } : {} })\n .then((r) => { if (!r.ok) throw new Error(\"Failed\"); return r.blob(); })\n .then((blob) => {\n if (revoked) return;\n url = URL.createObjectURL(blob);\n setBlobUrl(url);\n })\n .catch(() => { if (!revoked) setError(true); });\n return () => { revoked = true; if (url) URL.revokeObjectURL(url); };\n }, [src]);\n\n return { blobUrl, error };\n}\n\n/** Fetches image with auth header, renders as blob URL — click opens lightbox */\nfunction AuthImage({ src, alt }: { src: string; alt: string }) {\n const { blobUrl, error } = useAuthBlob(src);\n const openOverlay = useImageOverlay((s) => s.open);\n\n if (error) {\n return (\n <div className=\"flex items-center gap-1.5 rounded-md border border-border bg-background/50 px-2 py-1 text-xs text-text-secondary\">\n <ImageIcon className=\"size-3.5 shrink-0\" />\n <span className=\"truncate max-w-40\">{alt}</span>\n </div>\n );\n }\n\n if (!blobUrl) {\n return <div className=\"rounded-md bg-surface border border-border h-24 w-32 animate-pulse\" />;\n }\n\n return (\n <button type=\"button\" onClick={() => openOverlay(blobUrl, alt)} className=\"block text-left\">\n <img\n src={blobUrl}\n alt={alt}\n className=\"rounded-md max-h-48 max-w-full object-contain border border-border cursor-pointer hover:opacity-90 transition-opacity\"\n />\n </button>\n );\n}\n\n/** Chip for attached images in user bubble — tiny preview replaces icon, click opens lightbox */\nfunction AuthImageThumbnail({ filePath, projectName }: { filePath: string; projectName?: string }) {\n const src = uploadPreviewUrl(filePath, projectName);\n const { blobUrl, error } = useAuthBlob(src);\n const openOverlay = useImageOverlay((s) => s.open);\n const name = basename(filePath);\n\n return (\n <button\n type=\"button\"\n onClick={() => blobUrl && openOverlay(blobUrl, name)}\n className=\"flex items-center gap-1 rounded-md border border-border/60 bg-background/40 px-1.5 py-0.5 text-[11px] text-text-secondary hover:bg-surface transition-colors cursor-pointer\"\n >\n {blobUrl ? (\n <img src={blobUrl} alt={name} className=\"size-4 rounded-sm object-cover shrink-0\" />\n ) : error ? (\n <ImageIcon className=\"size-3 shrink-0\" />\n ) : (\n <div className=\"size-4 rounded-sm bg-surface animate-pulse shrink-0\" />\n )}\n <span className=\"truncate max-w-32\">{name}</span>\n </button>\n );\n}\n\n/** Fetches file with auth, opens in new browser tab (for PDFs, etc.) */\nfunction AuthFileLink({ src, filename, mimeType }: { src: string; filename: string; mimeType: string }) {\n const [loading, setLoading] = useState(false);\n\n const handleClick = useCallback(async () => {\n setLoading(true);\n try {\n const token = getAuthToken();\n const res = await fetch(src, { headers: token ? { Authorization: `Bearer ${token}` } : {} });\n if (!res.ok) throw new Error(\"Failed to load\");\n const blob = await res.blob();\n const url = URL.createObjectURL(new Blob([blob], { type: mimeType }));\n window.open(url, \"_blank\");\n // Revoke after a delay to let the new tab load\n setTimeout(() => URL.revokeObjectURL(url), 60_000);\n } catch {\n // Fallback: try direct link\n window.open(src, \"_blank\");\n } finally {\n setLoading(false);\n }\n }, [src, mimeType]);\n\n return (\n <button\n type=\"button\"\n onClick={handleClick}\n disabled={loading}\n className=\"flex items-center gap-1.5 rounded-md border border-border bg-background/50 px-2 py-1 text-xs text-text-secondary hover:bg-surface hover:text-text-primary transition-colors cursor-pointer disabled:opacity-50\"\n >\n <FileText className=\"size-3.5 shrink-0 text-red-400\" />\n <span className=\"truncate max-w-40\">{filename}</span>\n {loading && <span className=\"animate-spin text-[10px]\">...</span>}\n </button>\n );\n}\n\n/**\n * Renders events in order — consecutive text events merged into one bubble,\n * tool_use/tool_result render as cards between text sections.\n * Last text group shows streaming cursor when actively streaming.\n */\ntype EventGroup =\n | { kind: \"text\"; content: string }\n | { kind: \"thinking\"; content: string }\n | { kind: \"tool\"; tool: ChatEvent; result?: ChatEvent; completed?: boolean };\n\nfunction InterleavedEvents({ events, isStreaming, projectName, bashPartialOutput }: {\n events: ChatEvent[];\n isStreaming: boolean;\n projectName?: string;\n bashPartialOutput?: React.RefObject<Map<string, BashPartialEntry>>;\n}) {\n // Group: consecutive text → merged text block; tool_use + tool_result paired by toolUseId\n const groups: EventGroup[] = [];\n let textBuffer = \"\";\n\n // First pass: create groups for text, thinking, and tool_use events\n let thinkingBuffer = \"\";\n for (let i = 0; i < events.length; i++) {\n const event = events[i]!;\n if (event.type === \"thinking\") {\n // Flush text buffer first if any\n if (textBuffer) { groups.push({ kind: \"text\", content: textBuffer }); textBuffer = \"\"; }\n thinkingBuffer += event.content;\n continue;\n }\n // Flush thinking buffer when non-thinking event arrives\n if (thinkingBuffer) {\n groups.push({ kind: \"thinking\", content: thinkingBuffer });\n thinkingBuffer = \"\";\n }\n if (event.type === \"account_retry\") {\n if (textBuffer) { groups.push({ kind: \"text\", content: textBuffer }); textBuffer = \"\"; }\n const label = (event as any).accountLabel ?? \"another account\";\n const reason = (event as any).reason ?? \"Auth failed\";\n groups.push({ kind: \"text\", content: `\\n\\n> ↻ ${reason} — retrying with **${label}**...\\n\\n` });\n continue;\n }\n if (event.type === \"text\") {\n textBuffer += event.content;\n } else if (event.type === \"tool_use\") {\n if (textBuffer) {\n groups.push({ kind: \"text\", content: textBuffer });\n textBuffer = \"\";\n }\n groups.push({ kind: \"tool\", tool: event });\n } else if (event.type === \"tool_result\") {\n // Skip tool_results in first pass — matched below\n } else {\n if (textBuffer) {\n groups.push({ kind: \"text\", content: textBuffer });\n textBuffer = \"\";\n }\n groups.push({ kind: \"tool\", tool: event });\n }\n }\n if (thinkingBuffer) {\n groups.push({ kind: \"thinking\", content: thinkingBuffer });\n }\n if (textBuffer) {\n groups.push({ kind: \"text\", content: textBuffer });\n }\n\n // Second pass: match tool_result events to their tool_use by toolUseId\n const toolResults = events.filter((e) => e.type === \"tool_result\");\n for (const tr of toolResults) {\n const trId = (tr as any).toolUseId;\n // Match by ID if available\n if (trId) {\n const match = groups.find(\n (g) => g.kind === \"tool\" && g.tool.type === \"tool_use\" && (g.tool as any).toolUseId === trId,\n ) as (EventGroup & { kind: \"tool\" }) | undefined;\n if (match) {\n match.result = tr;\n continue;\n }\n }\n // Fallback: attach to first tool group without a result\n const unmatched = groups.find(\n (g) => g.kind === \"tool\" && !g.result,\n ) as (EventGroup & { kind: \"tool\" }) | undefined;\n if (unmatched) {\n unmatched.result = tr;\n }\n }\n\n // Third pass: fallback to embedded result from buffer enrichment (reconnect).\n // When BE buffers tool_result, it also attaches result onto the matching tool_use event.\n for (const g of groups) {\n if (g.kind === \"tool\" && !g.result && g.tool.type === \"tool_use\") {\n const embedded = (g.tool as any).result;\n if (embedded) {\n g.result = { type: \"tool_result\", output: embedded.output, isError: embedded.isError } as ChatEvent;\n }\n }\n }\n\n // Mark tool groups without explicit tool_result as completed when:\n // 1. It's a Read and a later Edit on the same file has a result (Edit implies Read finished)\n // 2. Streaming is fully finished\n for (let gi = 0; gi < groups.length; gi++) {\n const g = groups[gi]!;\n if (g.kind === \"tool\" && !g.result) {\n let impliedDone = false;\n if (g.tool.type === \"tool_use\" && g.tool.tool === \"Read\") {\n const readPath = (g.tool.input as any)?.file_path;\n if (readPath) {\n impliedDone = groups.slice(gi + 1).some(\n (later) => later.kind === \"tool\" && later.result\n && later.tool.type === \"tool_use\" && later.tool.tool === \"Edit\"\n && (later.tool.input as any)?.file_path === readPath,\n );\n }\n }\n g.completed = impliedDone || !isStreaming;\n }\n }\n\n return (\n <>\n {groups.map((group, i) => {\n if (group.kind === \"thinking\") {\n return <ThinkingBlock key={`think-${i}`} content={group.content} isStreaming={isStreaming && i === groups.length - 1} />;\n }\n if (group.kind === \"text\") {\n const isLast = isStreaming && i === groups.length - 1;\n return (\n <div key={`text-${i}`} className=\"text-sm text-text-primary select-text\">\n <StreamingText content={group.content} animate={isLast} projectName={projectName} />\n </div>\n );\n }\n return <ToolCard key={`tool-${i}`} tool={group.tool} result={group.result} completed={group.completed} projectName={projectName} bashPartialOutput={bashPartialOutput} />;\n })}\n </>\n );\n}\n\n/** Collapsible thinking block — shows Claude's reasoning, collapsed by default when done */\nfunction ThinkingBlock({ content, isStreaming }: { content: string; isStreaming: boolean }) {\n const [expanded, setExpanded] = useState(isStreaming);\n const scrollRef = useRef<HTMLDivElement>(null);\n\n // Auto-collapse when streaming finishes\n useEffect(() => {\n if (!isStreaming && content.length > 0) setExpanded(false);\n }, [isStreaming, content.length]);\n\n // Auto-scroll to bottom during streaming\n useEffect(() => {\n if (isStreaming && expanded && scrollRef.current) {\n scrollRef.current.scrollTop = scrollRef.current.scrollHeight;\n }\n }, [content, isStreaming, expanded]);\n\n return (\n <div className=\"rounded border border-border/50 bg-surface/30 text-xs\">\n <button\n onClick={() => setExpanded(!expanded)}\n className=\"flex items-center gap-2 px-2 py-1.5 w-full text-left hover:bg-surface transition-colors text-text-subtle\"\n >\n {isStreaming ? <Loader2 className=\"size-3 animate-spin\" /> : <ChevronRight className={`size-3 transition-transform ${expanded ? \"rotate-90\" : \"\"}`} />}\n <span>Thinking{isStreaming ? \"...\" : \"\"}</span>\n {!isStreaming && <span className=\"text-text-subtle/50 ml-auto\">{content.length > 100 ? `${Math.round(content.length / 4)} tokens` : \"\"}</span>}\n </button>\n {expanded && (\n <div ref={scrollRef} className=\"max-h-60 overflow-y-auto\">\n <div className=\"px-2 pb-2 text-text-subtle/80 whitespace-pre-wrap text-[11px] leading-relaxed\">\n {content}\n </div>\n </div>\n )}\n </div>\n );\n}\n\n/**\n * Text component that renders streamed content directly.\n * WebSocket already delivers tokens incrementally — no fake animation needed.\n * When `isStreaming=true`, shows a blinking cursor at the end.\n */\nfunction StreamingText({ content, animate: isStreaming, projectName }: { content: string; animate: boolean; projectName?: string }) {\n return (\n <>\n <MarkdownContent content={content} projectName={projectName} isStreaming={isStreaming} />\n {isStreaming && (\n <span className=\"text-text-subtle text-sm animate-pulse\">Thinking...</span>\n )}\n </>\n );\n}\n\n/**\n * Shows streaming status with elapsed time and warnings:\n * - No assistant message: \"Connecting to Claude...\" with elapsed timer\n * - After tool: \"Processing...\"\n * - Text streaming: hidden\n */\nfunction ThinkingIndicator({ lastMessage, phase, elapsed, statusMessage }: { lastMessage?: ChatMessage; phase?: SessionPhase; elapsed?: number; statusMessage?: string | null }) {\n // Show indicator when:\n // 1. No assistant message yet (waiting for first response)\n // 2. Last event is tool_result (Claude thinking after tool execution)\n // 3. statusMessage is active (account routing/refreshing)\n // Hide when text is actively streaming (text itself is the indicator)\n\n const isWaiting = !lastMessage || lastMessage.role !== \"assistant\";\n const isAfterTool = (() => {\n if (!lastMessage?.events?.length) return false;\n const last = lastMessage.events[lastMessage.events.length - 1]!;\n return last.type === \"tool_result\";\n })();\n\n if (!statusMessage && !isWaiting && !isAfterTool) return null;\n\n const label = statusMessage\n ? statusMessage\n : phase === \"initializing\" ? \"Initializing\"\n : phase === \"connecting\" ? \"Connecting\"\n : phase === \"thinking\" ? \"Thinking\"\n : \"Processing\";\n\n const isLong = phase === \"connecting\" && (elapsed ?? 0) >= 30;\n\n return (\n <div className=\"flex flex-col gap-1 text-sm\">\n <div className=\"flex items-center gap-2 text-text-subtle\">\n <Loader2 className=\"size-3 animate-spin\" />\n <span>\n {label}\n {isWaiting && (elapsed ?? 0) > 0 && <span className=\"text-text-subtle/60\">... ({elapsed}s)</span>}\n </span>\n </div>\n {isLong && (\n <p className=\"text-xs text-yellow-500/80 ml-5\">\n Taking longer than usual — may be rate-limited or API slow. Try sending a new message to retry.\n </p>\n )}\n </div>\n );\n}\n\n/** Strip SDK teammate-message XML tags from text — team popover shows these */\nconst TEAMMATE_MSG_RE = /<teammate-message[^>]*>[\\s\\S]*?<\\/teammate-message>/g;\nfunction stripTeammateMessages(text: string): string {\n return text.replace(TEAMMATE_MSG_RE, \"\").replace(/\\n{3,}/g, \"\\n\\n\").trim();\n}\n\n/** Wrapper: delegates to shared MarkdownRenderer with code actions enabled */\nfunction MarkdownContent({ content, projectName, isStreaming }: { content: string; projectName?: string; isStreaming?: boolean }) {\n const cleaned = stripTeammateMessages(content);\n if (!cleaned) return null;\n return (\n <RenderErrorBoundary fallbackContent={cleaned}>\n <Suspense fallback={<div className=\"animate-pulse h-4 bg-muted rounded\" />}>\n <MarkdownRenderer content={cleaned} projectName={projectName} codeActions isStreaming={isStreaming} />\n </Suspense>\n </RenderErrorBoundary>\n );\n}\n\n/* ToolCard, ToolSummary, ToolDetails extracted to ./tool-cards.tsx */\n\nfunction ApprovalCard({\n approval,\n onRespond,\n}: {\n approval: { requestId: string; tool: string; input: unknown };\n onRespond: (requestId: string, approved: boolean, data?: unknown) => void;\n}) {\n return (\n <div className=\"rounded-lg border-2 border-yellow-500/40 bg-yellow-500/10 p-3 space-y-2\">\n <div className=\"flex items-center gap-2 text-yellow-400 text-sm font-medium\">\n <ShieldAlert className=\"size-4\" />\n <span>Tool Approval Required</span>\n </div>\n <div className=\"text-xs text-text-primary\">\n <span className=\"font-medium\">{approval.tool}</span>\n </div>\n <pre className=\"text-xs font-mono text-text-secondary overflow-x-auto bg-background rounded p-2 border border-border\">\n {JSON.stringify(approval.input, null, 2)}\n </pre>\n <div className=\"flex gap-2\">\n <button\n onClick={() => onRespond(approval.requestId, true)}\n className=\"px-4 py-1.5 rounded bg-green-600 text-white text-xs font-medium hover:bg-green-500 transition-colors\"\n >\n Allow\n </button>\n <button\n onClick={() => onRespond(approval.requestId, false)}\n className=\"px-4 py-1.5 rounded bg-red-600 text-white text-xs font-medium hover:bg-red-500 transition-colors\"\n >\n Deny\n </button>\n </div>\n </div>\n );\n}\n\n/** Interactive quiz form for AskUserQuestion — renders questions with selectable options + Other */\nfunction AskUserQuestionCard({\n approval,\n onRespond,\n}: {\n approval: { requestId: string; tool: string; input: unknown };\n onRespond: (requestId: string, approved: boolean, data?: unknown) => void;\n}) {\n const input = approval.input as { questions?: Question[] };\n const questions = input.questions ?? [];\n\n return (\n <QuestionCard\n questions={questions}\n onSubmit={(answers) => onRespond(approval.requestId, true, answers)}\n onSkip={() => onRespond(approval.requestId, false)}\n />\n );\n}\n","import { useState, useRef, useCallback } from \"react\";\n\n// Extend Window for webkit prefix\ninterface SpeechRecognitionEvent extends Event {\n results: SpeechRecognitionResultList;\n resultIndex: number;\n}\n\ntype SpeechRecognitionInstance = {\n lang: string;\n continuous: boolean;\n interimResults: boolean;\n start(): void;\n stop(): void;\n abort(): void;\n onresult: ((event: SpeechRecognitionEvent) => void) | null;\n onend: (() => void) | null;\n onerror: ((event: Event & { error: string }) => void) | null;\n};\n\ntype SpeechRecognitionConstructor = new () => SpeechRecognitionInstance;\n\nfunction getSpeechRecognition(): SpeechRecognitionConstructor | null {\n const w = window as unknown as {\n SpeechRecognition?: SpeechRecognitionConstructor;\n webkitSpeechRecognition?: SpeechRecognitionConstructor;\n };\n return w.SpeechRecognition ?? w.webkitSpeechRecognition ?? null;\n}\n\nexport function useVoiceInput(options?: { lang?: string }) {\n const [isListening, setIsListening] = useState(false);\n const [interimText, setInterimText] = useState(\"\");\n const recognitionRef = useRef<SpeechRecognitionInstance | null>(null);\n // Accumulate finalized text across multiple result events\n const finalizedRef = useRef(\"\");\n\n const supported = typeof window !== \"undefined\" && getSpeechRecognition() !== null;\n\n const start = useCallback(\n (onResult: (text: string, isFinal: boolean) => void) => {\n const SR = getSpeechRecognition();\n if (!SR) return;\n\n // Stop any existing session\n recognitionRef.current?.abort();\n\n const recognition = new SR();\n recognition.lang = options?.lang ?? \"vi-VN\";\n recognition.continuous = true;\n recognition.interimResults = true;\n\n finalizedRef.current = \"\";\n\n recognition.onresult = (event: SpeechRecognitionEvent) => {\n let interim = \"\";\n let newFinalized = \"\";\n\n for (let i = 0; i < event.results.length; i++) {\n const result = event.results[i]!;\n if (result.isFinal) {\n newFinalized += result[0]!.transcript;\n } else {\n interim += result[0]!.transcript;\n }\n }\n\n // Update finalized accumulator\n if (newFinalized) {\n finalizedRef.current = newFinalized;\n }\n\n const fullText = (finalizedRef.current + \" \" + interim).trim();\n setInterimText(interim);\n onResult(fullText, interim.length === 0 && finalizedRef.current.length > 0);\n };\n\n recognition.onend = () => {\n setIsListening(false);\n setInterimText(\"\");\n // Deliver final text if any\n if (finalizedRef.current) {\n onResult(finalizedRef.current.trim(), true);\n }\n };\n\n recognition.onerror = (event) => {\n // \"no-speech\" and \"aborted\" are expected, not real errors\n if (event.error !== \"no-speech\" && event.error !== \"aborted\") {\n console.warn(\"[voice-input] error:\", event.error);\n }\n setIsListening(false);\n setInterimText(\"\");\n };\n\n recognitionRef.current = recognition;\n recognition.start();\n setIsListening(true);\n },\n [options?.lang],\n );\n\n const stop = useCallback(() => {\n recognitionRef.current?.stop();\n recognitionRef.current = null;\n setIsListening(false);\n setInterimText(\"\");\n }, []);\n\n return { isListening, interimText, start, stop, supported };\n}\n","/**\n * Claude Code supported file types for chat attachments.\n * Supported files are uploaded and referenced; unsupported files get path inserted as text.\n */\n\n/** Image MIME types Claude Code can read via the Read tool (multimodal) */\nconst SUPPORTED_IMAGE_TYPES = new Set([\n \"image/png\",\n \"image/jpeg\",\n \"image/gif\",\n \"image/webp\",\n]);\n\n/** Document MIME types Claude Code can read */\nconst SUPPORTED_DOC_TYPES = new Set([\n \"application/pdf\",\n]);\n\n/** Text/code MIME prefixes Claude Code can read */\nconst TEXT_MIME_PREFIXES = [\n \"text/\",\n \"application/json\",\n \"application/xml\",\n \"application/javascript\",\n \"application/typescript\",\n \"application/x-yaml\",\n \"application/toml\",\n \"application/x-sh\",\n];\n\n/** File extensions considered text/code even if MIME is application/octet-stream */\nconst TEXT_EXTENSIONS = new Set([\n \".ts\", \".tsx\", \".js\", \".jsx\", \".mjs\", \".cjs\",\n \".py\", \".rb\", \".go\", \".rs\", \".java\", \".kt\", \".swift\",\n \".c\", \".cpp\", \".h\", \".hpp\", \".cs\",\n \".json\", \".yaml\", \".yml\", \".toml\", \".xml\",\n \".md\", \".mdx\", \".txt\", \".csv\", \".tsv\",\n \".html\", \".css\", \".scss\", \".less\", \".sass\",\n \".sh\", \".bash\", \".zsh\", \".fish\",\n \".sql\", \".graphql\", \".gql\",\n \".env\", \".ini\", \".cfg\", \".conf\",\n \".dockerfile\", \".makefile\",\n \".vue\", \".svelte\", \".astro\",\n \".ipynb\",\n]);\n\nexport function isImageFile(file: File): boolean {\n return SUPPORTED_IMAGE_TYPES.has(file.type);\n}\n\nexport function isSupportedFile(file: File): boolean {\n // Images\n if (SUPPORTED_IMAGE_TYPES.has(file.type)) return true;\n // Documents\n if (SUPPORTED_DOC_TYPES.has(file.type)) return true;\n // Text MIME types\n if (TEXT_MIME_PREFIXES.some((p) => file.type.startsWith(p))) return true;\n // Fallback: check extension\n const ext = getExtension(file.name);\n if (ext && TEXT_EXTENSIONS.has(ext)) return true;\n return false;\n}\n\nfunction getExtension(name: string): string {\n const dot = name.lastIndexOf(\".\");\n if (dot === -1) return \"\";\n return name.slice(dot).toLowerCase();\n}\n","import { useState } from \"react\";\nimport { X, FileText, Image as ImageIcon, Loader2, TerminalSquare, ChevronDown } from \"lucide-react\";\nimport { cn } from \"@/lib/utils\";\nimport type { ChatAttachment } from \"./message-input\";\n\ninterface AttachmentChipsProps {\n attachments: ChatAttachment[];\n onRemove: (id: string) => void;\n}\n\nexport function AttachmentChips({ attachments, onRemove }: AttachmentChipsProps) {\n const [expandedId, setExpandedId] = useState<string | null>(null);\n\n if (attachments.length === 0) return null;\n\n const expanded = expandedId ? attachments.find((a) => a.id === expandedId) : null;\n\n return (\n <div className=\"px-2 md:px-4 pt-2\">\n <div className=\"flex flex-wrap gap-1.5\">\n {attachments.map((att) => (\n <div\n key={att.id}\n className={cn(\n \"flex items-center gap-1.5 rounded-md border border-border bg-surface px-2 py-1 text-xs text-text-secondary max-w-48\",\n att.textContent && \"cursor-pointer hover:border-primary/50\",\n expandedId === att.id && \"border-primary/50 bg-surface-elevated\",\n )}\n onClick={() => {\n if (att.textContent) setExpandedId(expandedId === att.id ? null : att.id);\n }}\n >\n {/* Thumbnail or icon */}\n {att.previewUrl ? (\n <img src={att.previewUrl} alt={att.name} className=\"size-5 rounded object-cover shrink-0\" />\n ) : att.textContent ? (\n <TerminalSquare className=\"size-3.5 shrink-0 text-text-subtle\" />\n ) : att.isImage ? (\n <ImageIcon className=\"size-3.5 shrink-0 text-text-subtle\" />\n ) : (\n <FileText className=\"size-3.5 shrink-0 text-text-subtle\" />\n )}\n\n <span className=\"truncate\">{att.name}</span>\n\n {/* Expand indicator for text attachments */}\n {att.textContent && (\n <ChevronDown className={cn(\"size-3 shrink-0 text-text-subtle transition-transform\", expandedId === att.id && \"rotate-180\")} />\n )}\n\n {att.status === \"uploading\" ? (\n <Loader2 className=\"size-3 shrink-0 animate-spin text-text-subtle\" />\n ) : att.status === \"error\" ? (\n <span className=\"text-red-500 shrink-0\" title=\"Upload failed\">!</span>\n ) : null}\n\n {/* Remove button */}\n <button\n type=\"button\"\n onClick={(e) => { e.stopPropagation(); onRemove(att.id); if (expandedId === att.id) setExpandedId(null); }}\n className=\"shrink-0 rounded-sm p-0.5 hover:bg-border/50 transition-colors\"\n aria-label={`Remove ${att.name}`}\n >\n <X className=\"size-3\" />\n </button>\n </div>\n ))}\n </div>\n\n {/* Expanded preview for text attachment */}\n {expanded?.textContent && (\n <pre className=\"mt-1.5 max-h-40 overflow-auto rounded-md border border-border bg-background p-2 text-xs text-text-primary font-mono whitespace-pre-wrap break-words\">\n {stripCodeFence(expanded.textContent)}\n </pre>\n )}\n </div>\n );\n}\n\n/** Strip markdown code fence wrapper for preview display */\nfunction stripCodeFence(text: string): string {\n const trimmed = text.trim();\n const match = trimmed.match(/^```\\w*\\n([\\s\\S]*?)\\n```$/);\n return match ? match[1]! : trimmed;\n}\n","import { useRef, useEffect, useCallback, type KeyboardEvent } from \"react\";\nimport { Hand, Code, ClipboardList, ShieldOff, Check } from \"lucide-react\";\n\nconst MODES = [\n { id: \"default\", label: \"Ask before edits\", icon: Hand, description: \"Claude will ask for approval before making each edit\" },\n { id: \"acceptEdits\", label: \"Edit automatically\", icon: Code, description: \"Claude will edit files without asking first\" },\n { id: \"plan\", label: \"Plan mode\", icon: ClipboardList, description: \"Claude will present a plan before editing\" },\n { id: \"bypassPermissions\", label: \"Bypass permissions\", icon: ShieldOff, description: \"Claude will not ask before running commands\" },\n] as const;\n\nexport type ModeId = typeof MODES[number][\"id\"];\n\n/** Short label for the mode chip */\nexport function getModeLabel(id: string): string {\n return MODES.find((m) => m.id === id)?.label ?? \"Unknown\";\n}\n\n/** Icon component for the mode chip */\nexport function getModeIcon(id: string) {\n return MODES.find((m) => m.id === id)?.icon ?? Hand;\n}\n\ninterface ModeSelectorProps {\n value: string;\n onChange: (mode: string) => void;\n open: boolean;\n onOpenChange: (open: boolean) => void;\n}\n\nexport function ModeSelector({ value, onChange, open, onOpenChange }: ModeSelectorProps) {\n const panelRef = useRef<HTMLDivElement>(null);\n const focusedRef = useRef(0);\n\n // Close on click outside\n useEffect(() => {\n if (!open) return;\n const handler = (e: MouseEvent) => {\n if (panelRef.current && !panelRef.current.contains(e.target as Node)) {\n onOpenChange(false);\n }\n };\n document.addEventListener(\"mousedown\", handler);\n return () => document.removeEventListener(\"mousedown\", handler);\n }, [open, onOpenChange]);\n\n // Focus current mode on open\n useEffect(() => {\n if (open) {\n focusedRef.current = MODES.findIndex((m) => m.id === value);\n if (focusedRef.current < 0) focusedRef.current = 0;\n }\n }, [open, value]);\n\n const handleKeyDown = useCallback((e: KeyboardEvent) => {\n if (e.key === \"Escape\") {\n onOpenChange(false);\n return;\n }\n if (e.key === \"ArrowDown\" || e.key === \"ArrowUp\") {\n e.preventDefault();\n const dir = e.key === \"ArrowDown\" ? 1 : -1;\n focusedRef.current = (focusedRef.current + dir + MODES.length) % MODES.length;\n const el = panelRef.current?.querySelector(`[data-idx=\"${focusedRef.current}\"]`) as HTMLElement;\n el?.focus();\n }\n if (e.key === \"Enter\") {\n e.preventDefault();\n const mode = MODES[focusedRef.current];\n if (mode) { onChange(mode.id); onOpenChange(false); }\n }\n }, [onChange, onOpenChange]);\n\n if (!open) return null;\n\n return (\n <div\n ref={panelRef}\n role=\"listbox\"\n aria-label=\"Permission modes\"\n onKeyDown={handleKeyDown}\n onMouseDown={(e) => e.stopPropagation()}\n onClick={(e) => e.stopPropagation()}\n className=\"absolute bottom-full left-0 mb-1 z-50 w-72 md:w-80 rounded-lg border border-border bg-surface shadow-lg\"\n >\n <div className=\"flex items-center justify-between px-3 py-2 border-b border-border\">\n <span className=\"text-xs font-medium text-text-secondary\">Modes</span>\n <kbd className=\"text-[10px] px-1.5 py-0.5 rounded bg-surface-elevated text-text-subtle border border-border\">\n Shift + Tab\n </kbd>\n </div>\n <div className=\"py-1\">\n {MODES.map((mode, idx) => {\n const Icon = mode.icon;\n const isActive = mode.id === value;\n return (\n <button\n key={mode.id}\n data-idx={idx}\n role=\"option\"\n aria-selected={isActive}\n tabIndex={0}\n onClick={() => { onChange(mode.id); onOpenChange(false); }}\n className={`w-full flex items-start gap-3 px-3 py-2.5 text-left transition-colors hover:bg-surface-elevated focus:bg-surface-elevated focus:outline-none ${isActive ? \"bg-surface-elevated\" : \"\"}`}\n >\n <Icon className=\"size-4 mt-0.5 shrink-0 text-text-secondary\" />\n <div className=\"flex-1 min-w-0\">\n <div className=\"text-sm font-medium text-text-primary\">{mode.label}</div>\n <div className=\"text-xs text-text-subtle leading-snug\">{mode.description}</div>\n </div>\n {isActive && <Check className=\"size-4 mt-0.5 shrink-0 text-primary\" />}\n </button>\n );\n })}\n </div>\n </div>\n );\n}\n","import { useState, useRef, useCallback, useEffect, memo, type KeyboardEvent, type DragEvent, type ClipboardEvent } from \"react\";\nimport { ArrowUp, Square, Paperclip, Loader2, Mic, MicOff, Zap, ListOrdered, Clock } from \"lucide-react\";\nimport { useVoiceInput } from \"@/hooks/use-voice-input\";\nimport { api, projectUrl, getAuthToken } from \"@/lib/api-client\";\nimport { randomId } from \"@/lib/utils\";\nimport { isSupportedFile, isImageFile } from \"@/lib/file-support\";\nimport { AttachmentChips } from \"./attachment-chips\";\nimport { ModeSelector, getModeLabel, getModeIcon } from \"./mode-selector\";\nimport { ProviderSelector } from \"./provider-selector\";\nimport type { SlashItem } from \"./slash-command-picker\";\nimport type { FileNode } from \"../../../types/project\";\nimport { useFileStore } from \"@/stores/file-store\";\n\nexport interface ChatAttachment {\n id: string;\n name: string;\n file: File;\n isImage: boolean;\n previewUrl?: string;\n /** Server-side path after upload */\n serverPath?: string;\n /** Inline text content (e.g. terminal output) — no upload needed */\n textContent?: string;\n status: \"uploading\" | \"ready\" | \"error\";\n}\n\nexport type MessagePriority = 'now' | 'next' | 'later';\n\ninterface MessageInputProps {\n onSend: (content: string, attachments: ChatAttachment[], priority?: MessagePriority) => void;\n isStreaming?: boolean;\n onCancel?: () => void;\n disabled?: boolean;\n projectName?: string;\n /** Slash picker state change */\n onSlashStateChange?: (visible: boolean, filter: string) => void;\n onSlashItemsLoaded?: (items: SlashItem[], recentNames?: string[]) => void;\n slashSelected?: SlashItem | null;\n /** File picker state change */\n onFileStateChange?: (visible: boolean, filter: string) => void;\n onFileItemsLoaded?: (items: FileNode[]) => void;\n fileSelected?: FileNode | null;\n /** External files added via drag-drop on parent */\n externalFiles?: File[] | null;\n /** External paths from file tree drag or disambiguation */\n externalPaths?: string[] | null;\n /** Callback when external paths have been consumed (inserted into textarea) */\n onExternalPathsConsumed?: () => void;\n /** Callback when OS-dropped files resolve to multiple matches (disambiguation needed) */\n onDisambiguate?: (matches: FileNode[]) => void;\n /** Pre-fill input value (e.g. from command palette \"Ask AI\") */\n initialValue?: string;\n /** Auto-focus textarea on mount */\n autoFocus?: boolean;\n /** Current permission mode */\n permissionMode?: string;\n /** Permission mode change handler */\n onModeChange?: (mode: string) => void;\n /** Current provider ID */\n providerId?: string;\n /** Provider change handler — undefined when session is active (locked) */\n onProviderChange?: (providerId: string) => void;\n}\n\nexport const MessageInput = memo(function MessageInput({\n onSend,\n isStreaming,\n onCancel,\n disabled,\n projectName,\n onSlashStateChange,\n onSlashItemsLoaded,\n slashSelected,\n onFileStateChange,\n onFileItemsLoaded,\n fileSelected,\n externalFiles,\n externalPaths,\n onExternalPathsConsumed,\n onDisambiguate,\n initialValue,\n autoFocus,\n permissionMode,\n onModeChange,\n providerId,\n onProviderChange,\n}: MessageInputProps) {\n // Uncontrolled textarea: value lives in DOM + ref, not React state.\n // Only `hasText` state triggers re-renders (empty↔non-empty for send button).\n // This eliminates React re-render on every keystroke — critical for Chromium on iPad.\n const valueRef = useRef(initialValue ?? \"\");\n const [hasText, setHasText] = useState(() => (initialValue ?? \"\").trim().length > 0);\n const [attachments, setAttachments] = useState<ChatAttachment[]>([]);\n const [modeSelectorOpen, setModeSelectorOpen] = useState(false);\n const [pendingSend, setPendingSend] = useState(false);\n const [priority, setPriority] = useState<MessagePriority>('next');\n const textareaRef = useRef<HTMLTextAreaElement>(null);\n const mobileTextareaRef = useRef<HTMLTextAreaElement>(null);\n const fileInputRef = useRef<HTMLInputElement>(null);\n const slashItemsRef = useRef<SlashItem[]>([]);\n const fileItemsRef = useRef<FileNode[]>([]);\n const resizeRafRef = useRef(0);\n // Track picker open state to avoid unnecessary parent callbacks per keystroke\n const slashPickerOpenRef = useRef(false);\n const filePickerOpenRef = useRef(false);\n // CSS field-sizing: content handles auto-resize natively (Safari 18.2+, Chrome 123+).\n // Only fall back to JS scrollHeight resize when unsupported.\n const needsJsResize = useRef(\n typeof CSS === \"undefined\" || !CSS.supports(\"field-sizing\", \"content\"),\n );\n\n // File index: subscribe imperatively to avoid re-renders on every file store update.\n // The component only needs fileIndex for the effect below (populating fileItemsRef),\n // not for rendering — so we use Zustand's subscribe() instead of selector hooks.\n\n /** Write value to both textareas + ref + update hasText state */\n const writeTextareas = useCallback((newValue: string) => {\n valueRef.current = newValue;\n if (textareaRef.current) textareaRef.current.value = newValue;\n if (mobileTextareaRef.current) mobileTextareaRef.current.value = newValue;\n setHasText(newValue.trim().length > 0);\n }, []);\n\n /** Get the currently visible textarea */\n const getVisibleTextarea = useCallback(() => {\n return window.matchMedia(\"(min-width: 768px)\").matches\n ? textareaRef.current\n : mobileTextareaRef.current;\n }, []);\n\n // Voice input (Web Speech API)\n const voice = useVoiceInput();\n // Store pre-voice text so voice appends to existing input\n const preVoiceTextRef = useRef(\"\");\n const voiceResultCb = useCallback((text: string) => {\n const prefix = preVoiceTextRef.current;\n const newValue = prefix ? prefix + \" \" + text : text;\n writeTextareas(newValue);\n // Auto-resize textarea (only when CSS field-sizing is unsupported)\n if (needsJsResize.current) {\n requestAnimationFrame(() => {\n const ta = getVisibleTextarea();\n if (ta) {\n ta.style.height = \"auto\";\n ta.style.height = Math.min(ta.scrollHeight, 160) + \"px\";\n }\n });\n }\n }, [writeTextareas, getVisibleTextarea]);\n const handleVoiceToggle = useCallback(() => {\n if (voice.isListening) {\n voice.stop();\n } else {\n preVoiceTextRef.current = valueRef.current.trim();\n voice.start(voiceResultCb);\n }\n }, [voice.isListening, voice.start, voice.stop, voiceResultCb]);\n\n // Listen for global keyboard shortcut (Cmd+Shift+V) to toggle voice\n useEffect(() => {\n const handler = () => { if (voice.supported) handleVoiceToggle(); };\n window.addEventListener(\"toggle-voice-input\", handler);\n return () => window.removeEventListener(\"toggle-voice-input\", handler);\n }, [voice.supported, handleVoiceToggle]);\n\n // Listen for \"Send to Chat\" from terminal or other tabs — add as attachment chip\n useEffect(() => {\n const handler = (e: Event) => {\n const { text, label } = (e as CustomEvent).detail ?? {};\n if (!text) return;\n window.dispatchEvent(new Event(\"ppm:send-to-chat:ack\"));\n const att: ChatAttachment = {\n id: randomId(),\n name: label ?? \"Terminal output\",\n file: new File([], \"terminal-output.txt\"),\n isImage: false,\n textContent: text,\n status: \"ready\",\n };\n setAttachments((prev) => [...prev, att]);\n getVisibleTextarea()?.focus();\n };\n window.addEventListener(\"ppm:send-to-chat\", handler);\n return () => window.removeEventListener(\"ppm:send-to-chat\", handler);\n }, [getVisibleTextarea]);\n\n // Apply initialValue when it changes (e.g. \"Ask AI\" from command palette)\n useEffect(() => {\n if (initialValue) {\n writeTextareas(initialValue);\n // Focus and move cursor to end\n setTimeout(() => {\n const ta = textareaRef.current;\n if (ta) { ta.focus(); ta.selectionStart = ta.selectionEnd = ta.value.length; }\n }, 50);\n }\n }, [initialValue]); // eslint-disable-line react-hooks/exhaustive-deps\n\n // Auto-focus on mount when requested\n useEffect(() => {\n if (!autoFocus) return;\n setTimeout(() => { getVisibleTextarea()?.focus(); }, 100);\n }, []); // eslint-disable-line react-hooks/exhaustive-deps\n\n // Fetch slash items from server\n const fetchSlashItems = useCallback(() => {\n if (!projectName) {\n slashItemsRef.current = [];\n onSlashItemsLoaded?.([], []);\n return;\n }\n api\n .get<{ items: SlashItem[]; recentNames: string[] }>(`${projectUrl(projectName)}/chat/slash-items`)\n .then((data) => {\n slashItemsRef.current = data.items;\n onSlashItemsLoaded?.(data.items, data.recentNames);\n })\n .catch(() => {\n slashItemsRef.current = [];\n onSlashItemsLoaded?.([], []);\n });\n }, [projectName, onSlashItemsLoaded]);\n\n // Fetch slash items when projectName changes\n useEffect(() => { fetchSlashItems(); }, [fetchSlashItems]);\n\n // Re-fetch when cache is invalidated via refresh button\n useEffect(() => {\n const handler = () => fetchSlashItems();\n window.addEventListener(\"ppm:slash-items-refresh\", handler);\n return () => window.removeEventListener(\"ppm:slash-items-refresh\", handler);\n }, [fetchSlashItems]);\n\n // Sync file picker items from store index — subscribe imperatively to avoid re-renders.\n // Reads fileIndex on mount + whenever fileIndex/indexStatus changes in the store.\n useEffect(() => {\n const syncFromStore = () => {\n if (!projectName) {\n fileItemsRef.current = [];\n onFileItemsLoaded?.([]);\n return;\n }\n const { fileIndex } = useFileStore.getState();\n const nodes: FileNode[] = fileIndex.map((e) => ({ name: e.name, path: e.path, type: e.type }));\n fileItemsRef.current = nodes;\n onFileItemsLoaded?.(nodes);\n };\n syncFromStore();\n // Track previous values to only sync on relevant changes\n let prevIdx = useFileStore.getState().fileIndex;\n let prevStatus = useFileStore.getState().indexStatus;\n return useFileStore.subscribe((state) => {\n if (state.fileIndex !== prevIdx || state.indexStatus !== prevStatus) {\n prevIdx = state.fileIndex;\n prevStatus = state.indexStatus;\n syncFromStore();\n }\n });\n }, [projectName]); // eslint-disable-line react-hooks/exhaustive-deps\n\n // Handle parent selecting a slash item\n useEffect(() => {\n if (!slashSelected) return;\n const el = getVisibleTextarea();\n if (!el) return;\n const text = el.value;\n const cursorPos = el.selectionStart;\n const textBefore = text.slice(0, cursorPos);\n const textAfter = text.slice(cursorPos);\n // Find the /query pattern before cursor and replace it\n const replaced = textBefore.replace(/(?:^|\\s)\\/\\S*$/, (match) => {\n const prefix = match.startsWith(\"/\") ? \"\" : match[0]; // preserve whitespace\n return `${prefix}/${slashSelected.name} `;\n });\n writeTextareas(replaced + textAfter);\n onSlashStateChange?.(false, \"\");\n slashPickerOpenRef.current = false;\n onFileStateChange?.(false, \"\");\n filePickerOpenRef.current = false;\n el.focus();\n setTimeout(() => {\n el.selectionStart = el.selectionEnd = replaced.length;\n }, 0);\n }, [slashSelected]); // eslint-disable-line react-hooks/exhaustive-deps\n\n // Handle parent selecting a file\n useEffect(() => {\n if (!fileSelected) return;\n const el = getVisibleTextarea();\n if (!el) return;\n\n const text = el.value;\n const cursorPos = el.selectionStart;\n const textBefore = text.slice(0, cursorPos);\n const textAfter = text.slice(cursorPos);\n // Find the @ trigger before cursor\n const atMatch = textBefore.match(/@(\\S*)$/);\n if (atMatch) {\n const start = textBefore.length - atMatch[0].length;\n const newText = textBefore.slice(0, start) + `@${fileSelected.path} ` + textAfter;\n writeTextareas(newText);\n const newCursorPos = start + fileSelected.path.length + 2; // +2 for @ and space\n setTimeout(() => {\n el.selectionStart = el.selectionEnd = newCursorPos;\n el.focus();\n }, 0);\n } else {\n // Fallback: append at end\n const newText = text + `@${fileSelected.path} `;\n writeTextareas(newText);\n setTimeout(() => {\n el.selectionStart = el.selectionEnd = newText.length;\n el.focus();\n }, 0);\n }\n onFileStateChange?.(false, \"\");\n filePickerOpenRef.current = false;\n }, [fileSelected]); // eslint-disable-line react-hooks/exhaustive-deps\n\n // Handle external files dropped on parent (ChatTab)\n useEffect(() => {\n if (!externalFiles || externalFiles.length === 0) return;\n processFiles(externalFiles);\n }, [externalFiles]); // eslint-disable-line react-hooks/exhaustive-deps\n\n // Handle external paths from file tree drag or disambiguation\n useEffect(() => {\n if (!externalPaths || externalPaths.length === 0) return;\n const pathRefs = externalPaths.map((p) => `@${p}`).join(\" \");\n const cur = valueRef.current;\n const sep = cur.length > 0 && !cur.endsWith(\" \") ? \" \" : \"\";\n writeTextareas(cur + sep + pathRefs + \" \");\n getVisibleTextarea()?.focus();\n onExternalPathsConsumed?.();\n }, [externalPaths]); // eslint-disable-line react-hooks/exhaustive-deps\n\n /** Upload a single file to the server, return server path */\n const uploadFile = useCallback(\n async (file: File): Promise<string | null> => {\n if (!projectName) return null;\n try {\n const form = new FormData();\n form.append(\"files\", file);\n const headers: HeadersInit = {};\n const token = getAuthToken();\n if (token) headers[\"Authorization\"] = `Bearer ${token}`;\n const res = await fetch(`${projectUrl(projectName)}/chat/upload`, {\n method: \"POST\",\n headers,\n body: form,\n });\n const json = await res.json();\n if (json.ok && Array.isArray(json.data) && json.data.length > 0) {\n return json.data[0].path as string;\n }\n return null;\n } catch {\n return null;\n }\n },\n [projectName],\n );\n\n /** Process dropped/pasted/selected files — resolves paths via server when possible */\n const processFiles = useCallback(\n async (files: File[]) => {\n for (const file of files) {\n // Step 1: Try server-side filename resolution for all files\n if (projectName) {\n try {\n const data = await api.get<{ matches: FileNode[] }>(\n `${projectUrl(projectName)}/files/resolve?name=${encodeURIComponent(file.name)}`,\n );\n if (data.matches.length === 1) {\n const cur = valueRef.current;\n const sep = cur.length > 0 && !cur.endsWith(\" \") ? \" \" : \"\";\n writeTextareas(cur + sep + `@${data.matches[0]!.path} `);\n continue;\n }\n if (data.matches.length > 1) {\n onDisambiguate?.(data.matches);\n continue;\n }\n // 0 matches → fall through to existing behavior\n } catch {\n // Resolve failed → fall through\n }\n }\n\n // Step 2: Fallback — upload supported files, insert name for unsupported\n if (!isSupportedFile(file)) {\n const cur = valueRef.current;\n const sep = cur.length > 0 && !cur.endsWith(\" \") ? \" \" : \"\";\n writeTextareas(cur + sep + file.name);\n continue;\n }\n\n const id = randomId();\n const isImg = isImageFile(file);\n const previewUrl = isImg ? URL.createObjectURL(file) : undefined;\n\n const att: ChatAttachment = {\n id,\n name: file.name,\n file,\n isImage: isImg,\n previewUrl,\n status: \"uploading\",\n };\n\n setAttachments((prev) => [...prev, att]);\n\n // Upload in background\n uploadFile(file).then((serverPath) => {\n setAttachments((prev) =>\n prev.map((a) =>\n a.id === id\n ? { ...a, serverPath: serverPath ?? undefined, status: serverPath ? \"ready\" : \"error\" }\n : a,\n ),\n );\n });\n }\n (mobileTextareaRef.current ?? textareaRef.current)?.focus();\n },\n [uploadFile, writeTextareas, projectName, onDisambiguate],\n );\n\n const removeAttachment = useCallback((id: string) => {\n setAttachments((prev) => {\n const att = prev.find((a) => a.id === id);\n if (att?.previewUrl) URL.revokeObjectURL(att.previewUrl);\n return prev.filter((a) => a.id !== id);\n });\n }, []);\n\n /** Execute the actual send (called directly or after uploads complete) */\n const executeSend = useCallback(() => {\n const trimmed = valueRef.current.trim();\n const readyAttachments = attachments.filter((a) => a.status === \"ready\");\n if (!trimmed && readyAttachments.length === 0) {\n setPendingSend(false);\n return;\n }\n\n onSlashStateChange?.(false, \"\");\n slashPickerOpenRef.current = false;\n onFileStateChange?.(false, \"\");\n filePickerOpenRef.current = false;\n if (voice.isListening) voice.stop();\n onSend(trimmed, readyAttachments, isStreaming ? priority : undefined);\n writeTextareas(\"\");\n // Revoke preview URLs\n for (const att of attachments) {\n if (att.previewUrl) URL.revokeObjectURL(att.previewUrl);\n }\n setAttachments([]);\n setPendingSend(false);\n setPriority('next');\n if (needsJsResize.current) {\n if (textareaRef.current) textareaRef.current.style.height = \"auto\";\n if (mobileTextareaRef.current) mobileTextareaRef.current.style.height = \"auto\";\n }\n }, [attachments, onSend, onSlashStateChange, onFileStateChange, isStreaming, priority, writeTextareas]);\n\n const handleSend = useCallback(() => {\n if (disabled) return;\n\n // If files are still uploading, queue the send for when they finish\n if (attachments.some((a) => a.status === \"uploading\")) {\n const trimmed = valueRef.current.trim();\n if (trimmed || attachments.some((a) => a.status !== \"error\")) {\n setPendingSend(true);\n }\n return;\n }\n\n executeSend();\n }, [attachments, disabled, executeSend]);\n\n // Auto-send when queued and all uploads complete\n useEffect(() => {\n if (!pendingSend) return;\n if (attachments.some((a) => a.status === \"uploading\")) return;\n executeSend();\n }, [pendingSend, attachments, executeSend]);\n\n const handleKeyDown = useCallback(\n (e: KeyboardEvent<HTMLTextAreaElement>) => {\n if (e.key === \"Enter\" && !e.shiftKey) {\n e.preventDefault();\n handleSend();\n return;\n }\n // Shift+Tab: cycle permission mode\n if (e.shiftKey && e.key === \"Tab\") {\n e.preventDefault();\n const modeIds = [\"default\", \"acceptEdits\", \"plan\", \"bypassPermissions\"];\n const idx = modeIds.indexOf(permissionMode ?? \"bypassPermissions\");\n const next = modeIds[(idx + 1) % modeIds.length]!;\n onModeChange?.(next);\n }\n },\n [handleSend, permissionMode, onModeChange],\n );\n\n const updatePickerState = useCallback(\n (text: string, cursorPos: number) => {\n const textBefore = text.slice(0, cursorPos);\n\n // Fast path: if no trigger chars exist at all, skip regex + callbacks\n const hasSlash = textBefore.includes(\"/\");\n const hasAt = textBefore.includes(\"@\");\n if (!hasSlash && !hasAt) {\n // Close pickers only if they were actually open (avoid unnecessary parent setState)\n if (slashPickerOpenRef.current) { onSlashStateChange?.(false, \"\"); slashPickerOpenRef.current = false; }\n if (filePickerOpenRef.current) { onFileStateChange?.(false, \"\"); filePickerOpenRef.current = false; }\n return;\n }\n\n // Check for slash anywhere in text (after whitespace or at start)\n if (hasSlash) {\n const slashMatch = textBefore.match(/(?:^|\\s)\\/(\\S*)$/);\n if (slashMatch && slashItemsRef.current.length > 0) {\n const filter = slashMatch[1] ?? \"\";\n onSlashStateChange?.(true, filter);\n slashPickerOpenRef.current = true;\n if (filePickerOpenRef.current) { onFileStateChange?.(false, \"\"); filePickerOpenRef.current = false; }\n return;\n }\n }\n\n // Check for @ anywhere in text (after whitespace or at start)\n if (hasAt) {\n const atMatch = textBefore.match(/@(\\S*)$/);\n if (atMatch && fileItemsRef.current.length > 0) {\n onFileStateChange?.(true, atMatch[1] ?? \"\");\n filePickerOpenRef.current = true;\n if (slashPickerOpenRef.current) { onSlashStateChange?.(false, \"\"); slashPickerOpenRef.current = false; }\n return;\n }\n }\n\n // Nothing matched — close both pickers (only if open)\n if (slashPickerOpenRef.current) { onSlashStateChange?.(false, \"\"); slashPickerOpenRef.current = false; }\n if (filePickerOpenRef.current) { onFileStateChange?.(false, \"\"); filePickerOpenRef.current = false; }\n },\n [onSlashStateChange, onFileStateChange],\n );\n\n /** Unified onChange for both textareas — updates ref, syncs other textarea, triggers picker */\n const handleTextareaChange = useCallback(\n (e: React.ChangeEvent<HTMLTextAreaElement>) => {\n const el = e.target;\n const text = el.value;\n valueRef.current = text;\n // Sync the other textarea (handles viewport rotation edge case)\n const other = el === textareaRef.current ? mobileTextareaRef.current : textareaRef.current;\n if (other) other.value = text;\n // Only trigger re-render on empty↔non-empty transition (for send button state)\n setHasText(text.trim().length > 0);\n // Update picker state (slash/file autocomplete)\n updatePickerState(text, el.selectionStart);\n // JS auto-resize fallback — only when CSS field-sizing: content is unsupported\n if (needsJsResize.current) {\n if (resizeRafRef.current) cancelAnimationFrame(resizeRafRef.current);\n resizeRafRef.current = requestAnimationFrame(() => {\n resizeRafRef.current = 0;\n el.style.height = \"auto\";\n el.style.height = Math.min(el.scrollHeight, el === mobileTextareaRef.current ? 80 : 160) + \"px\";\n });\n }\n },\n [updatePickerState],\n );\n\n /** Handle paste — intercept images from clipboard */\n const handlePaste = useCallback(\n (e: ClipboardEvent<HTMLTextAreaElement>) => {\n const items = e.clipboardData?.items;\n if (!items) return;\n\n const files: File[] = [];\n for (const item of items) {\n if (item.kind === \"file\") {\n const file = item.getAsFile();\n if (file) files.push(file);\n }\n }\n if (files.length > 0) {\n e.preventDefault();\n processFiles(files);\n }\n },\n [processFiles],\n );\n\n /** Handle drop directly on textarea */\n const handleDrop = useCallback(\n (e: DragEvent<HTMLTextAreaElement>) => {\n e.preventDefault();\n // Check for internal file tree drag first\n const ppmPath = e.dataTransfer.getData(\"application/x-ppm-path\");\n if (ppmPath) {\n const cur = valueRef.current;\n const sep = cur.length > 0 && !cur.endsWith(\" \") ? \" \" : \"\";\n writeTextareas(cur + sep + `@${ppmPath} `);\n getVisibleTextarea()?.focus();\n return;\n }\n const files = Array.from(e.dataTransfer.files);\n if (files.length > 0) processFiles(files);\n },\n [processFiles, writeTextareas, getVisibleTextarea],\n );\n\n const handleDragOver = useCallback((e: DragEvent<HTMLTextAreaElement>) => {\n e.preventDefault();\n }, []);\n\n /** Open native file picker */\n const handleAttachClick = useCallback(() => {\n fileInputRef.current?.click();\n }, []);\n\n const handleFileInputChange = useCallback(\n (e: React.ChangeEvent<HTMLInputElement>) => {\n const files = Array.from(e.target.files ?? []);\n if (files.length > 0) processFiles(files);\n // Reset so same file can be selected again\n e.target.value = \"\";\n },\n [processFiles],\n );\n\n const hasContent = hasText || attachments.some((a) => a.status !== \"error\");\n const showCancel = isStreaming && !hasContent;\n\n return (\n <div className=\"p-2 md:p-3 bg-background\">\n {/* Rounded input container */}\n <div\n className=\"border border-border rounded-xl md:rounded-2xl bg-surface shadow-sm cursor-text\"\n onClick={(e) => {\n if (disabled) return;\n // Only focus when clicking outside the textarea (e.g. padding area)\n if (e.target instanceof HTMLTextAreaElement) return;\n getVisibleTextarea()?.focus();\n }}\n >\n {/* Attachment chips (inside container, aligned with input) */}\n <AttachmentChips attachments={attachments} onRemove={removeAttachment} />\n {/* Mobile: mode chip + provider selector row */}\n <div className=\"flex items-center gap-1 px-2 pt-2 md:hidden relative\">\n <ModeChip\n mode={permissionMode ?? \"bypassPermissions\"}\n onClick={() => setModeSelectorOpen((v) => !v)}\n />\n <ModeSelector\n value={permissionMode ?? \"bypassPermissions\"}\n onChange={(m) => onModeChange?.(m)}\n open={modeSelectorOpen}\n onOpenChange={setModeSelectorOpen}\n />\n {onProviderChange && projectName && (\n <ProviderSelector\n value={providerId ?? \"claude\"}\n onChange={onProviderChange}\n projectName={projectName}\n />\n )}\n {isStreaming && <PriorityToggle value={priority} onChange={setPriority} />}\n </div>\n {/* Mobile: single row — attach + textarea + mic + send */}\n <div className=\"flex items-end gap-1 md:hidden px-2 py-2\">\n <button\n type=\"button\"\n onClick={(e) => { e.stopPropagation(); handleAttachClick(); }}\n disabled={disabled}\n className=\"flex items-center justify-center size-7 shrink-0 rounded-full text-text-subtle hover:text-text-primary transition-colors disabled:opacity-50\"\n aria-label=\"Attach file\"\n >\n <Paperclip className=\"size-4\" />\n </button>\n <textarea\n ref={mobileTextareaRef}\n defaultValue={initialValue ?? \"\"}\n onChange={handleTextareaChange}\n onKeyDown={handleKeyDown}\n onPaste={handlePaste}\n onDrop={handleDrop}\n onDragOver={handleDragOver}\n placeholder={isStreaming ? \"Follow-up...\" : \"Ask anything...\"}\n disabled={disabled}\n rows={1}\n className=\"flex-1 resize-none bg-transparent py-1.5 text-sm text-foreground placeholder:text-text-subtle focus:outline-none disabled:opacity-50 max-h-20 [field-sizing:content]\"\n />\n {voice.supported && (\n <button\n type=\"button\"\n onClick={(e) => { e.stopPropagation(); handleVoiceToggle(); }}\n disabled={disabled}\n className={`flex items-center justify-center size-7 shrink-0 rounded-full transition-colors disabled:opacity-50 ${\n voice.isListening\n ? \"bg-red-600 text-white animate-pulse\"\n : \"text-text-subtle hover:text-text-primary\"\n }`}\n aria-label={voice.isListening ? \"Stop voice input\" : \"Start voice input\"}\n >\n {voice.isListening ? <MicOff className=\"size-4\" /> : <Mic className=\"size-4\" />}\n </button>\n )}\n {showCancel ? (\n <button\n onClick={(e) => { e.stopPropagation(); onCancel?.(); }}\n className=\"flex items-center justify-center size-7 shrink-0 rounded-full bg-red-600 text-white hover:bg-red-500 transition-colors\"\n aria-label=\"Stop\"\n >\n <Square className=\"size-3\" />\n </button>\n ) : (\n <button\n onClick={(e) => { e.stopPropagation(); pendingSend ? setPendingSend(false) : handleSend(); }}\n disabled={disabled || !hasContent}\n className=\"flex items-center justify-center size-7 shrink-0 rounded-full bg-primary text-primary-foreground hover:bg-primary/90 disabled:opacity-30 transition-colors\"\n aria-label={pendingSend ? \"Cancel queued send\" : \"Send\"}\n >\n {pendingSend ? <Loader2 className=\"size-3.5 animate-spin\" /> : <ArrowUp className=\"size-3.5\" />}\n </button>\n )}\n </div>\n\n {/* Desktop: textarea + action bar below */}\n <div className=\"hidden md:block\">\n <textarea\n ref={textareaRef}\n defaultValue={initialValue ?? \"\"}\n onChange={handleTextareaChange}\n onKeyDown={handleKeyDown}\n onPaste={handlePaste}\n onDrop={handleDrop}\n onDragOver={handleDragOver}\n placeholder={isStreaming ? \"Follow-up or Stop...\" : \"Ask anything...\"}\n disabled={disabled}\n rows={1}\n className=\"w-full resize-none bg-transparent px-4 pt-3 pb-1 text-sm text-foreground placeholder:text-text-subtle focus:outline-none disabled:opacity-50 max-h-40 [field-sizing:content]\"\n />\n <div className=\"flex items-center justify-between px-3 pb-2\">\n <div className=\"flex items-center gap-1\">\n <button\n type=\"button\"\n onClick={(e) => { e.stopPropagation(); handleAttachClick(); }}\n disabled={disabled}\n className=\"flex items-center justify-center size-8 rounded-full text-text-subtle hover:text-text-primary hover:bg-surface-elevated transition-colors disabled:opacity-50\"\n aria-label=\"Attach file\"\n >\n <Paperclip className=\"size-4\" />\n </button>\n {/* Mode indicator chip */}\n <div className=\"relative\">\n <ModeChip\n mode={permissionMode ?? \"bypassPermissions\"}\n onClick={() => setModeSelectorOpen((v) => !v)}\n />\n <ModeSelector\n value={permissionMode ?? \"bypassPermissions\"}\n onChange={(m) => onModeChange?.(m)}\n open={modeSelectorOpen}\n onOpenChange={setModeSelectorOpen}\n />\n </div>\n {/* Provider selector — only when no active session */}\n {onProviderChange && projectName && (\n <ProviderSelector\n value={providerId ?? \"claude\"}\n onChange={onProviderChange}\n projectName={projectName}\n />\n )}\n {isStreaming && <PriorityToggle value={priority} onChange={setPriority} />}\n </div>\n <div className=\"flex items-center gap-1\">\n {voice.supported && (\n <button\n type=\"button\"\n onClick={(e) => { e.stopPropagation(); handleVoiceToggle(); }}\n disabled={disabled}\n className={`flex items-center justify-center size-8 rounded-full transition-colors disabled:opacity-50 ${\n voice.isListening\n ? \"bg-red-600 text-white animate-pulse\"\n : \"text-text-subtle hover:text-text-primary hover:bg-surface-elevated\"\n }`}\n aria-label={voice.isListening ? \"Stop voice input\" : \"Start voice input\"}\n >\n {voice.isListening ? <MicOff className=\"size-4\" /> : <Mic className=\"size-4\" />}\n </button>\n )}\n {showCancel ? (\n <button\n onClick={(e) => { e.stopPropagation(); onCancel?.(); }}\n className=\"flex items-center justify-center size-8 rounded-full bg-red-600 text-white hover:bg-red-500 transition-colors\"\n aria-label=\"Stop response\"\n >\n <Square className=\"size-3.5\" />\n </button>\n ) : (\n <button\n onClick={(e) => { e.stopPropagation(); pendingSend ? setPendingSend(false) : handleSend(); }}\n disabled={disabled || !hasContent}\n className=\"flex items-center justify-center size-8 rounded-full bg-primary text-primary-foreground hover:bg-primary/90 disabled:opacity-30 disabled:cursor-not-allowed transition-colors\"\n aria-label={pendingSend ? \"Cancel queued send\" : \"Send message\"}\n >\n {pendingSend ? <Loader2 className=\"size-4 animate-spin\" /> : <ArrowUp className=\"size-4\" />}\n </button>\n )}\n </div>\n </div>\n </div>\n </div>\n\n <input ref={fileInputRef} type=\"file\" multiple className=\"hidden\" onChange={handleFileInputChange} />\n </div>\n );\n});\n\n/** Small chip showing current permission mode */\nfunction ModeChip({ mode, onClick }: { mode: string; onClick: () => void }) {\n const Icon = getModeIcon(mode);\n const label = getModeLabel(mode);\n return (\n <button\n type=\"button\"\n onClick={(e) => { e.stopPropagation(); onClick(); }}\n className=\"inline-flex items-center gap-1 px-2 py-1 rounded-md text-[11px] text-text-subtle hover:text-text-primary hover:bg-surface-elevated transition-colors border border-transparent hover:border-border\"\n aria-label={`Permission mode: ${label}`}\n >\n <Icon className=\"size-3\" />\n <span className=\"max-w-[100px] truncate\">{label}</span>\n </button>\n );\n}\n\nconst PRIORITY_OPTIONS: { value: MessagePriority; label: string; Icon: typeof Zap }[] = [\n { value: 'now', label: 'Interrupt', Icon: Zap },\n { value: 'next', label: 'Queue', Icon: ListOrdered },\n { value: 'later', label: 'Later', Icon: Clock },\n];\n\n/** Compact priority toggle — visible only during streaming */\nfunction PriorityToggle({ value, onChange }: { value: MessagePriority; onChange: (v: MessagePriority) => void }) {\n const cycle = useCallback(() => {\n const order: MessagePriority[] = ['next', 'later', 'now'];\n const idx = order.indexOf(value);\n onChange(order[(idx + 1) % order.length]!);\n }, [value, onChange]);\n\n const current = PRIORITY_OPTIONS.find((o) => o.value === value) ?? PRIORITY_OPTIONS[1]!;\n const Icon = current.Icon;\n\n return (\n <button\n type=\"button\"\n onClick={(e) => { e.stopPropagation(); cycle(); }}\n className=\"inline-flex items-center gap-1 px-2 py-1 rounded-md text-[11px] text-text-subtle hover:text-text-primary hover:bg-surface-elevated transition-colors border border-transparent hover:border-border\"\n aria-label={`Message priority: ${current.label}`}\n title={`Priority: ${current.label} (click to cycle)`}\n >\n <Icon className=\"size-3\" />\n <span>{current.label}</span>\n </button>\n );\n}\n","/** Minimal interface — any object with name + description is searchable */\nexport interface FuzzySearchable {\n name: string;\n description: string;\n}\n\n/** Iterative Levenshtein distance (single-row DP) */\nexport function levenshtein(a: string, b: string): number {\n if (a === b) return 0;\n if (a.length === 0) return b.length;\n if (b.length === 0) return a.length;\n\n let prev = Array.from({ length: b.length + 1 }, (_, i) => i);\n let curr = new Array<number>(b.length + 1);\n\n for (let i = 1; i <= a.length; i++) {\n curr[0] = i;\n for (let j = 1; j <= b.length; j++) {\n const cost = a[i - 1] === b[j - 1] ? 0 : 1;\n curr[j] = Math.min(\n curr[j - 1]! + 1, // insertion\n prev[j]! + 1, // deletion\n prev[j - 1]! + cost, // substitution\n );\n }\n [prev, curr] = [curr, prev];\n }\n return prev[b.length]!;\n}\n\ninterface FuzzyScore { rank: number; distance: number }\n\n/**\n * Score a query against a candidate string.\n * Returns null if no reasonable match. Rank: 0=prefix, 1=contains, 2=fuzzy.\n */\nexport function scoreFuzzy(query: string, candidate: string): FuzzyScore | null {\n const lq = query.toLowerCase();\n const lc = candidate.toLowerCase();\n\n if (lc.startsWith(lq)) return { rank: 0, distance: 0 };\n if (lc.includes(lq)) return { rank: 1, distance: lc.indexOf(lq) };\n\n const maxDist = Math.max(Math.floor(lq.length * 0.4), 2);\n const dist = levenshtein(lq, lc.slice(0, lq.length + maxDist));\n if (dist <= maxDist) return { rank: 2, distance: dist };\n\n return null;\n}\n\n/**\n * Search items by query with fuzzy matching.\n * Recently used items get a rank boost (sorted earlier within same rank tier).\n * Returns ranked results (best match first), truncated to limit.\n */\nexport function searchFuzzy<T extends FuzzySearchable>(\n items: T[],\n query: string,\n limit = 20,\n recentNames: string[] = [],\n): T[] {\n if (!query) return items;\n // Cap query length to prevent quadratic blowup in Levenshtein\n query = query.slice(0, 50);\n\n const recentSet = new Set(recentNames);\n const scored: Array<{ item: T; rank: number; distance: number; recent: boolean }> = [];\n\n for (const item of items) {\n const nameScore = scoreFuzzy(query, item.name);\n const descScore = scoreFuzzy(query, item.description);\n const best = [nameScore, descScore]\n .filter((s): s is FuzzyScore => s !== null)\n .sort((a, b) => a.rank - b.rank || a.distance - b.distance)[0];\n\n if (best) scored.push({ item, rank: best.rank, distance: best.distance, recent: recentSet.has(item.name) });\n }\n\n scored.sort((a, b) =>\n a.rank - b.rank\n || a.distance - b.distance\n || (a.recent === b.recent ? 0 : a.recent ? -1 : 1)\n || a.item.name.localeCompare(b.item.name),\n );\n\n return scored.slice(0, limit).map((s) => s.item);\n}\n","import { useState, useEffect, useRef, useCallback, useMemo, type KeyboardEvent } from \"react\";\nimport { Sparkles, Terminal, Zap, RefreshCw, Clock } from \"lucide-react\";\nimport { api, projectUrl } from \"@/lib/api-client\";\nimport { searchFuzzy } from \"../../../shared/fuzzy-search\";\n\nexport interface SlashItem {\n type: \"skill\" | \"command\" | \"builtin\";\n name: string;\n description: string;\n argumentHint?: string;\n scope?: \"project\" | \"user\" | \"bundled\";\n category?: string;\n aliases?: string[];\n}\n\ninterface SlashCommandPickerProps {\n items: SlashItem[];\n filter: string;\n onSelect: (item: SlashItem) => void;\n onClose: () => void;\n visible: boolean;\n /** Recently used item names (most recent first) */\n recentNames?: string[];\n /** Project name for cache invalidation */\n projectName?: string;\n}\n\nexport function SlashCommandPicker({\n items,\n filter,\n onSelect,\n onClose,\n visible,\n recentNames = [],\n projectName,\n}: SlashCommandPickerProps) {\n const [selectedIndex, setSelectedIndex] = useState(0);\n const [refreshing, setRefreshing] = useState(false);\n const listRef = useRef<HTMLDivElement>(null);\n\n const recentSet = useMemo(() => new Set(recentNames), [recentNames]);\n\n // Build display list: fuzzy search when filter is set, recents-first when idle\n const displayItems = useMemo(() => {\n if (filter) {\n // Client-side fuzzy search (Levenshtein) — replaces old server-side search\n return { items: searchFuzzy(items, filter, 20, recentNames), recentCount: 0 };\n }\n\n // No filter — show all items with recents first\n if (recentNames.length > 0) {\n const recents: SlashItem[] = [];\n const rest: SlashItem[] = [];\n for (const item of items) {\n if (recentSet.has(item.name)) recents.push(item);\n else rest.push(item);\n }\n recents.sort((a, b) => recentNames.indexOf(a.name) - recentNames.indexOf(b.name));\n return { items: [...recents, ...rest], recentCount: recents.length };\n }\n return { items, recentCount: 0 };\n }, [items, filter, recentNames, recentSet]);\n\n const filtered = displayItems.items;\n const recentCount = displayItems.recentCount;\n\n // Reset selection when filter changes\n useEffect(() => {\n setSelectedIndex(0);\n }, [filter]);\n\n // Scroll selected item into view\n useEffect(() => {\n const list = listRef.current;\n if (!list) return;\n const selected = list.children[selectedIndex] as HTMLElement | undefined;\n selected?.scrollIntoView({ block: \"nearest\" });\n }, [selectedIndex]);\n\n const handleKeyDown = useCallback(\n (e: KeyboardEvent | globalThis.KeyboardEvent) => {\n if (!visible || filtered.length === 0) return false;\n\n switch (e.key) {\n case \"ArrowUp\":\n e.preventDefault();\n setSelectedIndex((i) => (i > 0 ? i - 1 : filtered.length - 1));\n return true;\n case \"ArrowDown\":\n e.preventDefault();\n setSelectedIndex((i) => (i < filtered.length - 1 ? i + 1 : 0));\n return true;\n case \"Enter\":\n case \"Tab\":\n e.preventDefault();\n if (filtered[selectedIndex]) {\n onSelect(filtered[selectedIndex]);\n }\n return true;\n case \"Escape\":\n e.preventDefault();\n onClose();\n return true;\n }\n return false;\n },\n [visible, filtered, selectedIndex, onSelect, onClose],\n );\n\n // Global keyboard handler (captures before textarea)\n useEffect(() => {\n if (!visible) return;\n const handler = (e: globalThis.KeyboardEvent) => {\n if (handleKeyDown(e)) e.stopPropagation();\n };\n document.addEventListener(\"keydown\", handler, true);\n return () => document.removeEventListener(\"keydown\", handler, true);\n }, [visible, handleKeyDown]);\n\n const handleRefresh = useCallback(() => {\n if (!projectName || refreshing) return;\n setRefreshing(true);\n api.del(`${projectUrl(projectName)}/chat/slash-items/cache`)\n .then(() => {\n // Trigger re-fetch by dispatching custom event (MessageInput listens on projectName)\n window.dispatchEvent(new CustomEvent(\"ppm:slash-items-refresh\"));\n })\n .finally(() => setRefreshing(false));\n }, [projectName, refreshing]);\n\n if (!visible || filtered.length === 0) return null;\n\n return (\n <div className=\"max-h-52 overflow-y-auto border-b border-border bg-surface\">\n <div ref={listRef} className=\"py-1\">\n {filtered.map((item, i) => {\n // Show \"Recent\" separator before first item, \"All\" before first non-recent\n const showRecentLabel = recentCount > 0 && i === 0;\n const showAllLabel = recentCount > 0 && i === recentCount;\n\n return (\n <div key={`${item.type}-${item.name}`}>\n {showRecentLabel && (\n <div className=\"flex items-center justify-between px-3 pt-1 pb-0.5\">\n <span className=\"text-[10px] font-medium text-text-subtle uppercase tracking-wider flex items-center gap-1\">\n <Clock className=\"size-3\" />\n Recent\n </span>\n {projectName && (\n <button\n type=\"button\"\n onClick={(e) => { e.stopPropagation(); handleRefresh(); }}\n className=\"text-text-subtle hover:text-text-primary transition-colors p-0.5 rounded\"\n title=\"Refresh skill list\"\n aria-label=\"Refresh skill list\"\n >\n <RefreshCw className={`size-3 ${refreshing ? \"animate-spin\" : \"\"}`} />\n </button>\n )}\n </div>\n )}\n {showAllLabel && (\n <div className=\"px-3 pt-1.5 pb-0.5\">\n <span className=\"text-[10px] font-medium text-text-subtle uppercase tracking-wider\">All</span>\n </div>\n )}\n <button\n className={`flex items-start gap-3 w-full px-3 py-2 text-left transition-colors ${\n i === selectedIndex\n ? \"bg-primary/10 text-primary\"\n : \"hover:bg-surface-hover text-text-primary\"\n }`}\n onMouseEnter={() => setSelectedIndex(i)}\n onClick={() => onSelect(item)}\n >\n <span className=\"shrink-0 mt-0.5\">\n {item.type === \"builtin\" ? (\n <Zap className=\"size-4 text-emerald-500\" />\n ) : item.type === \"skill\" ? (\n <Sparkles className=\"size-4 text-amber-500\" />\n ) : (\n <Terminal className=\"size-4 text-blue-500\" />\n )}\n </span>\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex items-baseline gap-2\">\n <span className=\"font-medium text-sm\">/{item.name}</span>\n {item.argumentHint && (\n <span className=\"text-xs text-text-subtle\">{item.argumentHint}</span>\n )}\n <span className=\"text-xs text-text-subtle capitalize ml-auto\">\n {item.scope === \"bundled\" ? \"PPM\" : item.scope === \"user\" ? \"global\" : item.type}\n </span>\n </div>\n {item.description && (\n <p className=\"text-xs text-text-subtle mt-0.5 line-clamp-2\">\n {item.description}\n </p>\n )}\n </div>\n </button>\n </div>\n );\n })}\n </div>\n </div>\n );\n}\n","import { useState, useEffect, useRef, useCallback, type KeyboardEvent } from \"react\";\nimport { File, Folder } from \"lucide-react\";\nimport type { FileNode } from \"../../../types/project\";\n\ninterface FilePickerProps {\n items: FileNode[];\n filter: string;\n onSelect: (item: FileNode) => void;\n onClose: () => void;\n visible: boolean;\n}\n\nexport function FilePicker({\n items,\n filter,\n onSelect,\n onClose,\n visible,\n}: FilePickerProps) {\n const [selectedIndex, setSelectedIndex] = useState(0);\n const listRef = useRef<HTMLDivElement>(null);\n\n const filtered = (() => {\n if (!filter) return items.slice(0, 50);\n const q = filter.toLowerCase();\n return items\n .filter((node) => node.path.toLowerCase().includes(q) || node.name.toLowerCase().includes(q))\n .slice(0, 50);\n })();\n\n // Reset selection when filter changes\n useEffect(() => {\n setSelectedIndex(0);\n }, [filter]);\n\n // Scroll selected item into view\n useEffect(() => {\n const list = listRef.current;\n if (!list) return;\n const selected = list.children[selectedIndex] as HTMLElement | undefined;\n selected?.scrollIntoView({ block: \"nearest\" });\n }, [selectedIndex]);\n\n const handleKeyDown = useCallback(\n (e: KeyboardEvent | globalThis.KeyboardEvent) => {\n if (!visible || filtered.length === 0) return false;\n\n switch (e.key) {\n case \"ArrowUp\":\n e.preventDefault();\n setSelectedIndex((i) => (i > 0 ? i - 1 : filtered.length - 1));\n return true;\n case \"ArrowDown\":\n e.preventDefault();\n setSelectedIndex((i) => (i < filtered.length - 1 ? i + 1 : 0));\n return true;\n case \"Enter\":\n case \"Tab\":\n e.preventDefault();\n if (filtered[selectedIndex]) {\n onSelect(filtered[selectedIndex]);\n }\n return true;\n case \"Escape\":\n e.preventDefault();\n onClose();\n return true;\n }\n return false;\n },\n [visible, filtered, selectedIndex, onSelect, onClose],\n );\n\n // Global keyboard handler (captures before textarea)\n useEffect(() => {\n if (!visible) return;\n const handler = (e: globalThis.KeyboardEvent) => {\n if (handleKeyDown(e)) e.stopPropagation();\n };\n document.addEventListener(\"keydown\", handler, true);\n return () => document.removeEventListener(\"keydown\", handler, true);\n }, [visible, handleKeyDown]);\n\n if (!visible || filtered.length === 0) return null;\n\n return (\n <div className=\"max-h-52 overflow-y-auto border-b border-border bg-surface\">\n <div ref={listRef} className=\"py-1\">\n {filtered.map((item, i) => (\n <button\n key={item.path}\n className={`flex items-center gap-2 w-full px-3 py-1.5 text-left transition-colors ${\n i === selectedIndex\n ? \"bg-primary/10 text-primary\"\n : \"hover:bg-surface-hover text-text-primary\"\n }`}\n onMouseEnter={() => setSelectedIndex(i)}\n onClick={() => onSelect(item)}\n >\n <span className=\"shrink-0\">\n {item.type === \"directory\" ? (\n <Folder className=\"size-4 text-amber-500\" />\n ) : (\n <File className=\"size-4 text-blue-400\" />\n )}\n </span>\n <span className=\"text-sm truncate\">{item.path}</span>\n </button>\n ))}\n </div>\n </div>\n );\n}\n","import { useState, useEffect, useCallback } from \"react\";\nimport { Plus, Trash2, Pencil, Check, X, RotateCcw } from \"lucide-react\";\nimport { api, projectUrl } from \"@/lib/api-client\";\nimport type { ProjectTag } from \"../../../types/chat\";\n\ninterface TagSettingsSectionProps {\n projectName: string;\n onTagsChanged?: () => void;\n}\n\nexport function TagSettingsSection({ projectName, onTagsChanged }: TagSettingsSectionProps) {\n const [tags, setTags] = useState<ProjectTag[]>([]);\n const [defaultTagId, setDefaultTagId] = useState<number | null>(null);\n const [loading, setLoading] = useState(true);\n const [editingId, setEditingId] = useState<number | null>(null);\n const [editName, setEditName] = useState(\"\");\n const [editColor, setEditColor] = useState(\"\");\n const [newName, setNewName] = useState(\"\");\n const [newColor, setNewColor] = useState(\"#22c55e\");\n const [showAdd, setShowAdd] = useState(false);\n\n const baseUrl = `${projectUrl(projectName)}/tags`;\n\n const loadTags = useCallback(async () => {\n try {\n const data = await api.get<{ tags: ProjectTag[]; defaultTagId: number | null }>(baseUrl);\n setTags(data.tags);\n setDefaultTagId(data.defaultTagId);\n } catch { /* silent */ }\n setLoading(false);\n }, [baseUrl]);\n\n useEffect(() => { loadTags(); }, [loadTags]);\n\n const handleCreate = async () => {\n if (!newName.trim()) return;\n try {\n await api.post(baseUrl, { name: newName.trim(), color: newColor });\n setNewName(\"\");\n setShowAdd(false);\n loadTags();\n onTagsChanged?.();\n } catch { /* silent */ }\n };\n\n const handleUpdate = async (id: number) => {\n try {\n await api.patch(`${baseUrl}/${id}`, { name: editName.trim() || undefined, color: editColor || undefined });\n setEditingId(null);\n loadTags();\n onTagsChanged?.();\n } catch { /* silent */ }\n };\n\n const handleDelete = async (id: number, name: string) => {\n if (!window.confirm(`Delete tag \"${name}\"? Sessions with this tag will become untagged.`)) return;\n try {\n await api.del(`${baseUrl}/${id}`);\n loadTags();\n onTagsChanged?.();\n } catch { /* silent */ }\n };\n\n const handleSetDefault = async (tagId: number) => {\n const newId = tagId === defaultTagId ? null : tagId;\n try {\n await api.patch(`${baseUrl}/default-tag`, { tagId: newId });\n setDefaultTagId(newId);\n } catch { /* silent */ }\n };\n\n const handleReset = async () => {\n try {\n await api.post(`${baseUrl}/reset`, {});\n loadTags();\n onTagsChanged?.();\n } catch { /* silent */ }\n };\n\n if (loading) return <p className=\"text-[11px] text-muted-foreground animate-pulse\">Loading tags...</p>;\n\n return (\n <div className=\"space-y-2\">\n <div className=\"flex items-center justify-between\">\n <h3 className=\"text-xs font-medium text-muted-foreground\">Session Tags</h3>\n <div className=\"flex items-center gap-1\">\n <button onClick={handleReset} className=\"p-1 rounded text-text-subtle hover:text-text-secondary\" title=\"Reset to defaults\">\n <RotateCcw className=\"size-3\" />\n </button>\n <button onClick={() => setShowAdd(!showAdd)} className=\"p-1 rounded text-primary hover:bg-primary/10\" title=\"Add tag\">\n <Plus className=\"size-3.5\" />\n </button>\n </div>\n </div>\n\n {/* Add form */}\n {showAdd && (\n <div className=\"flex items-center gap-1.5 px-1\">\n <input type=\"color\" value={newColor} onChange={(e) => setNewColor(e.target.value)} className=\"size-6 rounded cursor-pointer border-0 p-0\" />\n <input\n value={newName}\n onChange={(e) => setNewName(e.target.value)}\n onKeyDown={(e) => { if (e.key === \"Enter\") handleCreate(); if (e.key === \"Escape\") setShowAdd(false); }}\n placeholder=\"Tag name\"\n className=\"flex-1 min-w-0 bg-surface-elevated text-[11px] text-text-primary px-2 py-1 rounded border border-border outline-none focus:border-primary\"\n autoFocus\n />\n <button onClick={handleCreate} className=\"p-1 text-green-500 hover:text-green-400\"><Check className=\"size-3.5\" /></button>\n <button onClick={() => setShowAdd(false)} className=\"p-1 text-text-subtle hover:text-text-secondary\"><X className=\"size-3.5\" /></button>\n </div>\n )}\n\n {/* Tag list */}\n <div className=\"space-y-0.5\">\n {tags.map((tag) => (\n <div key={tag.id} className=\"flex items-center gap-1.5 px-1 py-1 rounded hover:bg-surface-elevated group\">\n {editingId === tag.id ? (\n <>\n <input type=\"color\" value={editColor} onChange={(e) => setEditColor(e.target.value)} className=\"size-5 rounded cursor-pointer border-0 p-0\" />\n <input\n value={editName}\n onChange={(e) => setEditName(e.target.value)}\n onKeyDown={(e) => { if (e.key === \"Enter\") handleUpdate(tag.id); if (e.key === \"Escape\") setEditingId(null); }}\n className=\"flex-1 min-w-0 bg-surface-elevated text-[11px] px-1.5 py-0.5 rounded border border-border outline-none focus:border-primary\"\n autoFocus\n />\n <button onClick={() => handleUpdate(tag.id)} className=\"p-0.5 text-green-500\"><Check className=\"size-3\" /></button>\n <button onClick={() => setEditingId(null)} className=\"p-0.5 text-text-subtle\"><X className=\"size-3\" /></button>\n </>\n ) : (\n <>\n <span className=\"size-3 rounded-full shrink-0\" style={{ backgroundColor: tag.color }} />\n <span className=\"flex-1 text-[11px] text-text-primary truncate\">{tag.name}</span>\n <button\n onClick={() => handleSetDefault(tag.id)}\n className={`px-1.5 py-0.5 rounded text-[9px] font-medium transition-colors ${\n tag.id === defaultTagId\n ? \"bg-primary/15 text-primary border border-primary/30\"\n : \"text-text-subtle border border-transparent can-hover:opacity-0 can-hover:group-hover:opacity-100 hover:bg-surface-elevated hover:border-border\"\n }`}\n title={tag.id === defaultTagId ? \"Default tag (click to unset)\" : \"Set as default for new sessions\"}\n >\n {tag.id === defaultTagId ? \"Default\" : \"Set default\"}\n </button>\n <button\n onClick={() => { setEditingId(tag.id); setEditName(tag.name); setEditColor(tag.color); }}\n className=\"p-0.5 rounded text-text-subtle hover:text-text-secondary can-hover:opacity-0 can-hover:group-hover:opacity-100\"\n >\n <Pencil className=\"size-3\" />\n </button>\n <button\n onClick={() => handleDelete(tag.id, tag.name)}\n className=\"p-0.5 rounded text-text-subtle hover:text-red-400 can-hover:opacity-0 can-hover:group-hover:opacity-100\"\n >\n <Trash2 className=\"size-3\" />\n </button>\n </>\n )}\n </div>\n ))}\n {tags.length === 0 && (\n <p className=\"text-[11px] text-muted-foreground py-2 text-center\">No tags. Click + to create one.</p>\n )}\n </div>\n </div>\n );\n}\n","import { useState } from \"react\";\nimport { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from \"@/components/ui/dialog\";\nimport { Button } from \"@/components/ui/button\";\nimport { Input } from \"@/components/ui/input\";\nimport { Label } from \"@/components/ui/label\";\nimport { Loader2, Download, Copy, Lock } from \"lucide-react\";\nimport { getAuthToken } from \"../../lib/api-client\";\nimport {\n addAccount,\n getOAuthUrl,\n exchangeOAuthCode,\n importAccounts,\n type AccountInfo,\n} from \"../../lib/api-settings\";\n\nconst DEFAULT_PASSWORD = \"ppm-hienlh\";\n\n// ── Add Account Dialog ─────────────────────────────────────────────\n\ninterface AddAccountDialogProps {\n open: boolean;\n onOpenChange: (open: boolean) => void;\n onSuccess: (msg?: string) => void;\n}\n\nexport function AddAccountDialog({ open, onOpenChange, onSuccess }: AddAccountDialogProps) {\n const [newToken, setNewToken] = useState(\"\");\n const [newLabel, setNewLabel] = useState(\"\");\n const [adding, setAdding] = useState(false);\n const [addError, setAddError] = useState<string | null>(null);\n const [oauthState, setOauthState] = useState<string | null>(null);\n const [oauthCode, setOauthCode] = useState(\"\");\n const [oauthLoading, setOauthLoading] = useState(false);\n const [oauthStep, setOauthStep] = useState<\"idle\" | \"waiting\">(\"idle\");\n\n function resetOAuth() {\n setOauthState(null);\n setOauthCode(\"\");\n setOauthStep(\"idle\");\n setAddError(null);\n }\n\n function handleClose() {\n onOpenChange(false);\n resetOAuth();\n setNewToken(\"\");\n setNewLabel(\"\");\n setAddError(null);\n }\n\n async function handleOAuthLogin() {\n setOauthLoading(true);\n setAddError(null);\n try {\n const { url, state } = await getOAuthUrl();\n setOauthState(state);\n setOauthStep(\"waiting\");\n window.open(url, \"_blank\");\n } catch (e) {\n setAddError((e as Error).message);\n }\n setOauthLoading(false);\n }\n\n async function handleOAuthExchange() {\n if (!oauthCode.trim() || !oauthState) return;\n setOauthLoading(true);\n setAddError(null);\n try {\n let code = oauthCode.trim();\n if (code.includes(\"#\")) code = code.split(\"#\")[0] ?? code;\n await exchangeOAuthCode(code, oauthState);\n handleClose();\n onSuccess(\"Account connected via OAuth!\");\n } catch (e) {\n setAddError((e as Error).message);\n }\n setOauthLoading(false);\n }\n\n async function handleAddToken() {\n if (!newToken.trim()) return;\n setAdding(true);\n setAddError(null);\n try {\n await addAccount({ apiKey: newToken.trim(), label: newLabel.trim() || undefined });\n handleClose();\n onSuccess(\"Account added!\");\n } catch (e) {\n setAddError((e as Error).message);\n }\n setAdding(false);\n }\n\n const tokenHint = newToken.trim()\n ? newToken.trim().startsWith(\"sk-ant-oat\") ? \"OAuth token (Claude Max/Pro)\"\n : newToken.trim().startsWith(\"sk-ant-api\") ? \"API key\"\n : \"Unknown format\"\n : \"\";\n\n return (\n <Dialog open={open} onOpenChange={(v) => { if (!v) handleClose(); }}>\n <DialogContent className=\"sm:max-w-md\">\n <DialogHeader>\n <DialogTitle className=\"text-sm\">Add Claude Account</DialogTitle>\n <DialogDescription className=\"text-xs leading-relaxed\">\n Connect via OAuth (recommended) or paste a token manually.\n </DialogDescription>\n </DialogHeader>\n <div className=\"space-y-3\">\n {/* OAuth login */}\n <div className=\"rounded-md border p-3 space-y-2\">\n <p className=\"text-[11px] font-medium\">Recommended: Login with Claude</p>\n {oauthStep === \"idle\" ? (\n <Button size=\"sm\" className=\"w-full h-8 text-xs\" onClick={handleOAuthLogin} disabled={oauthLoading}>\n {oauthLoading ? <><Loader2 className=\"size-3 animate-spin mr-1\" /> Opening...</> : \"Login with Claude\"}\n </Button>\n ) : (\n <div className=\"space-y-2\">\n <p className=\"text-[10px] text-muted-foreground\">Authorize in the opened tab, then paste the code:</p>\n <Input placeholder=\"Paste code here...\" value={oauthCode} onChange={(e) => setOauthCode(e.target.value)} className=\"text-xs h-8 font-mono\" autoFocus />\n <div className=\"flex gap-1.5\">\n <Button size=\"sm\" className=\"flex-1 h-7 text-xs\" onClick={handleOAuthExchange} disabled={!oauthCode.trim() || oauthLoading}>\n {oauthLoading ? <><Loader2 className=\"size-3 animate-spin mr-1\" /> Connecting...</> : \"Connect\"}\n </Button>\n <Button size=\"sm\" variant=\"ghost\" className=\"h-7 text-xs\" onClick={resetOAuth}>Cancel</Button>\n </div>\n </div>\n )}\n </div>\n <div className=\"flex items-center gap-2\">\n <div className=\"flex-1 border-t\" />\n <span className=\"text-[10px] text-muted-foreground\">or paste token</span>\n <div className=\"flex-1 border-t\" />\n </div>\n {/* Manual token */}\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"add-token\" className=\"text-xs\">Token</Label>\n <Input id=\"add-token\" type=\"password\" placeholder=\"sk-ant-...\" value={newToken} onChange={(e) => setNewToken(e.target.value)} className=\"text-xs h-8 font-mono\" />\n {tokenHint && <p className=\"text-[10px] text-muted-foreground\">Detected: {tokenHint}</p>}\n </div>\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"add-label\" className=\"text-xs\">Label (optional)</Label>\n <Input id=\"add-label\" placeholder=\"e.g. Personal, Work\" value={newLabel} onChange={(e) => setNewLabel(e.target.value)} className=\"text-xs h-8\" />\n </div>\n </div>\n {addError && <div className=\"text-[11px] p-2 rounded bg-red-500/10 text-red-600\">{addError}</div>}\n <DialogFooter>\n <Button size=\"sm\" variant=\"outline\" className=\"text-xs h-7\" onClick={handleClose}>Cancel</Button>\n <Button size=\"sm\" className=\"text-xs h-7\" onClick={handleAddToken} disabled={!newToken.trim() || adding}>\n {adding ? \"Adding...\" : \"Add Token\"}\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n );\n}\n\n// ── Export Accounts Dialog ──────────────────────────────────────────\n\ninterface ExportAccountsDialogProps {\n open: boolean;\n onOpenChange: (open: boolean) => void;\n accounts: AccountInfo[];\n preselectId?: string | null;\n onMessage?: (msg: string) => void;\n}\n\nexport function ExportAccountsDialog({ open, onOpenChange, accounts, preselectId, onMessage }: ExportAccountsDialogProps) {\n const exportable = accounts.filter((a) => a.hasRefreshToken);\n const [selected, setSelected] = useState<Set<string>>(new Set());\n const [password, setPassword] = useState(\"\");\n const [fullTransfer, setFullTransfer] = useState(false);\n const [refreshBefore, setRefreshBefore] = useState(false);\n const [exporting, setExporting] = useState(false);\n const [initialized, setInitialized] = useState(false);\n\n // Initialize selection when dialog opens\n if (open && !initialized) {\n setSelected(preselectId ? new Set([preselectId]) : new Set(exportable.map((a) => a.id)));\n setInitialized(true);\n }\n if (!open && initialized) {\n setInitialized(false);\n }\n\n function handleClose() {\n onOpenChange(false);\n setPassword(\"\");\n setFullTransfer(false);\n setRefreshBefore(false);\n }\n\n async function doExport(toClipboard: boolean) {\n if (selected.size === 0) return;\n setExporting(true);\n const effectivePassword = password.trim() || DEFAULT_PASSWORD;\n try {\n const headers: HeadersInit = { \"Content-Type\": \"application/json\" };\n const token = getAuthToken();\n if (token) headers[\"Authorization\"] = `Bearer ${token}`;\n const res = await fetch(\"/api/accounts/export\", {\n method: \"POST\",\n headers,\n body: JSON.stringify({ password: effectivePassword, accountIds: [...selected], includeRefreshToken: fullTransfer, refreshBeforeExport: refreshBefore }),\n });\n if (!res.ok) { const j = await res.json() as any; throw new Error(j.error ?? `Export failed: ${res.status}`); }\n const text = await res.text();\n if (toClipboard) {\n try {\n await navigator.clipboard.writeText(text);\n onMessage?.(\"Backup copied to clipboard!\");\n } catch {\n downloadBlob(text);\n onMessage?.(\"Backup downloaded.\");\n }\n } else {\n downloadBlob(text);\n onMessage?.(\"Backup downloaded.\");\n }\n handleClose();\n } catch { /* silent */ }\n setExporting(false);\n }\n\n const valid = selected.size > 0 && !exporting;\n\n return (\n <Dialog open={open} onOpenChange={(v) => { if (!v) handleClose(); }}>\n <DialogContent className=\"sm:max-w-md\">\n <DialogHeader>\n <DialogTitle className=\"text-sm flex items-center gap-1.5\"><Lock className=\"size-3.5\" /> Export Accounts</DialogTitle>\n <DialogDescription className=\"text-xs\">Select accounts and set a password to protect the backup.</DialogDescription>\n </DialogHeader>\n <div className=\"space-y-3\">\n {/* Account selection */}\n <div className=\"space-y-1\">\n <div className=\"flex items-center justify-between mb-1\">\n <p className=\"text-[11px] font-medium text-muted-foreground\">Accounts to export</p>\n <button className=\"text-[10px] text-primary hover:underline cursor-pointer\" onClick={() => setSelected(selected.size === exportable.length ? new Set() : new Set(exportable.map((a) => a.id)))}>\n {selected.size === exportable.length ? \"Deselect all\" : \"Select all\"}\n </button>\n </div>\n {exportable.length === 0 ? (\n <p className=\"text-[10px] text-muted-foreground p-2 border rounded\">No exportable accounts.</p>\n ) : (\n <div className=\"max-h-36 overflow-y-auto space-y-1 border rounded p-2\">\n {exportable.map((acc) => (\n <div key={acc.id} className=\"flex items-center gap-2\">\n <input type=\"checkbox\" id={`exp-${acc.id}`} checked={selected.has(acc.id)} onChange={(e) => { const s = new Set(selected); e.target.checked ? s.add(acc.id) : s.delete(acc.id); setSelected(s); }} className=\"size-3.5 accent-primary cursor-pointer\" />\n <label htmlFor={`exp-${acc.id}`} className=\"text-xs cursor-pointer truncate\">\n {acc.label ?? acc.email ?? acc.id.slice(0, 8)}\n </label>\n </div>\n ))}\n </div>\n )}\n </div>\n {/* Password (optional) */}\n <div className=\"space-y-1.5\">\n <Label className=\"text-xs\">Password <span className=\"text-muted-foreground font-normal\">(optional)</span></Label>\n <Input type=\"password\" placeholder=\"Leave empty for default\" value={password} onChange={(e) => setPassword(e.target.value)} className=\"text-xs h-8\" autoComplete=\"new-password\" />\n </div>\n {/* Options */}\n <div className=\"flex items-center gap-2\">\n <input type=\"checkbox\" id=\"exp-full\" checked={fullTransfer} onChange={(e) => setFullTransfer(e.target.checked)} className=\"size-3.5 accent-primary cursor-pointer\" />\n <label htmlFor=\"exp-full\" className=\"text-[11px] cursor-pointer\">Include refresh tokens (full transfer)</label>\n </div>\n <div className=\"flex items-center gap-2\">\n <input type=\"checkbox\" id=\"exp-refresh\" checked={refreshBefore} onChange={(e) => setRefreshBefore(e.target.checked)} className=\"size-3.5 accent-primary cursor-pointer\" />\n <label htmlFor=\"exp-refresh\" className=\"text-[11px] cursor-pointer\">Refresh tokens before export</label>\n </div>\n {/* Warning */}\n {fullTransfer ? (\n <div className=\"rounded-md border border-red-500/30 bg-red-500/5 p-2.5\">\n <p className=\"text-[10px] font-medium text-red-600\">Full transfer — source accounts will expire</p>\n <p className=\"text-[10px] text-muted-foreground\">Refresh tokens included. Source machine expires in ~1h after target refreshes.</p>\n </div>\n ) : refreshBefore ? (\n <div className=\"rounded-md border border-amber-500/30 bg-amber-500/5 p-2.5\">\n <p className=\"text-[10px] font-medium text-amber-600\">Refresh before export — invalidates previous shares</p>\n </div>\n ) : (\n <div className=\"rounded-md border border-green-500/30 bg-green-500/5 p-2.5\">\n <p className=\"text-[10px] font-medium text-green-600\">Share current token (safe)</p>\n </div>\n )}\n <p className=\"text-[10px] text-muted-foreground\">Encrypted with AES-256-GCM + scrypt.</p>\n </div>\n <DialogFooter className=\"gap-1.5 flex-col sm:flex-row\">\n <Button size=\"sm\" variant=\"outline\" className=\"text-xs h-7 cursor-pointer\" onClick={handleClose}>Cancel</Button>\n <Button size=\"sm\" variant=\"outline\" className=\"text-xs h-7 cursor-pointer\" disabled={!valid} onClick={() => doExport(true)}>\n <Copy className=\"size-3 mr-1\" /> Copy\n </Button>\n <Button size=\"sm\" className=\"text-xs h-7 cursor-pointer\" disabled={!valid} onClick={() => doExport(false)}>\n {exporting ? <><Loader2 className=\"size-3 animate-spin mr-1\" /> Exporting...</> : <><Download className=\"size-3 mr-1\" /> Download</>}\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n );\n}\n\n// ── Import Accounts Dialog ─────────────────────────────────────────\n\ninterface ImportAccountsDialogProps {\n open: boolean;\n onOpenChange: (open: boolean) => void;\n onSuccess: (msg?: string) => void;\n}\n\nexport function ImportAccountsDialog({ open, onOpenChange, onSuccess }: ImportAccountsDialogProps) {\n const [data, setData] = useState(\"\");\n const [password, setPassword] = useState(\"\");\n const [importing, setImporting] = useState(false);\n const [error, setError] = useState<string | null>(null);\n\n function handleClose() {\n onOpenChange(false);\n setData(\"\");\n setPassword(\"\");\n setError(null);\n }\n\n async function doImport() {\n if (!data.trim()) return;\n setImporting(true);\n setError(null);\n try {\n const result = await importAccounts({ data: data.trim(), password: password.trim() || DEFAULT_PASSWORD });\n handleClose();\n onSuccess(`Imported ${result.imported} account(s)`);\n } catch (e) {\n setError((e as Error).message || \"Import failed\");\n }\n setImporting(false);\n }\n\n return (\n <Dialog open={open} onOpenChange={(v) => { if (!v) handleClose(); }}>\n <DialogContent className=\"sm:max-w-md\">\n <DialogHeader>\n <DialogTitle className=\"text-sm flex items-center gap-1.5\"><Lock className=\"size-3.5\" /> Import Accounts</DialogTitle>\n <DialogDescription className=\"text-xs\">Paste backup data and enter the export password. Imported accounts are temporary (~1h).</DialogDescription>\n </DialogHeader>\n <div className=\"space-y-3\">\n <div className=\"space-y-1.5\">\n <Label className=\"text-xs\">Backup data</Label>\n <textarea value={data} onChange={(e) => setData(e.target.value)} placeholder=\"Paste backup JSON here...\" rows={4} className=\"w-full text-xs p-2 rounded border border-border bg-background font-mono resize-none focus:outline-none focus:ring-1 focus:ring-primary\" />\n </div>\n <div className=\"space-y-1.5\">\n <Label className=\"text-xs\">Password <span className=\"text-muted-foreground font-normal\">(optional)</span></Label>\n <Input type=\"password\" placeholder=\"Leave empty for default\" value={password} onChange={(e) => setPassword(e.target.value)} className=\"text-xs h-8\" autoComplete=\"current-password\" />\n </div>\n </div>\n {error && <div className=\"text-[11px] p-2 rounded bg-red-500/10 text-red-600\">{error}</div>}\n <DialogFooter>\n <Button size=\"sm\" variant=\"outline\" className=\"text-xs h-7 cursor-pointer\" onClick={handleClose}>Cancel</Button>\n <Button size=\"sm\" className=\"text-xs h-7 cursor-pointer\" disabled={!data.trim() || importing} onClick={doImport}>\n {importing ? <><Loader2 className=\"size-3 animate-spin mr-1\" /> Importing...</> : \"Import\"}\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n );\n}\n\n// ── Helpers ─────────────────────────────────────────────────────────\n\nfunction downloadBlob(text: string) {\n const blob = new Blob([text], { type: \"application/json\" });\n const a = document.createElement(\"a\");\n a.href = URL.createObjectURL(blob);\n a.download = \"ppm-accounts-backup.json\";\n a.click();\n URL.revokeObjectURL(a.href);\n}\n","import { useState, useEffect, useSyncExternalStore } from \"react\";\nimport { Settings, X } from \"lucide-react\";\nimport { Dialog, DialogContent, DialogHeader, DialogTitle } from \"@/components/ui/dialog\";\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from \"@/components/ui/select\";\nimport { cn } from \"@/lib/utils\";\nimport {\n getAccountSettings,\n updateAccountSettings,\n type AccountSettings,\n} from \"../../lib/api-settings\";\n\ninterface AccountRotationSettingsProps {\n open: boolean;\n onOpenChange: (open: boolean) => void;\n}\n\nconst mdQuery = typeof window !== \"undefined\" ? window.matchMedia(\"(min-width: 768px)\") : null;\nfunction subscribeMedia(cb: () => void) {\n mdQuery?.addEventListener(\"change\", cb);\n return () => mdQuery?.removeEventListener(\"change\", cb);\n}\nfunction getIsDesktop() {\n return mdQuery?.matches ?? true;\n}\n\nfunction SettingsContent() {\n const [settings, setSettings] = useState<AccountSettings | null>(null);\n const [loading, setLoading] = useState(true);\n\n useEffect(() => {\n setLoading(true);\n getAccountSettings()\n .then(setSettings)\n .finally(() => setLoading(false));\n }, []);\n\n if (loading) {\n return <p className=\"text-xs text-text-subtle py-4 text-center\">Loading...</p>;\n }\n if (!settings) {\n return <p className=\"text-xs text-text-subtle py-4 text-center\">Failed to load settings</p>;\n }\n\n return (\n <div className=\"space-y-4\">\n {/* Strategy */}\n <div className=\"space-y-1.5\">\n <label className=\"text-xs font-medium text-text-primary\">Rotation Strategy</label>\n <Select\n value={settings.strategy}\n onValueChange={async (v) => {\n const updated = await updateAccountSettings({ strategy: v as AccountSettings[\"strategy\"] });\n setSettings(updated);\n }}\n >\n <SelectTrigger className=\"w-full h-9 text-xs\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"round-robin\">Round-robin</SelectItem>\n <SelectItem value=\"fill-first\">Fill-first</SelectItem>\n <SelectItem value=\"lowest-usage\">Lowest usage</SelectItem>\n </SelectContent>\n </Select>\n <p className=\"text-[10px] text-text-subtle\">\n {settings.strategy === \"round-robin\" && \"Cycles through accounts evenly\"}\n {settings.strategy === \"fill-first\" && \"Uses one account until its limit, then moves on\"}\n {settings.strategy === \"lowest-usage\" && \"Picks the account with the lowest current usage\"}\n </p>\n </div>\n\n {/* Max Retry */}\n <div className=\"space-y-1.5\">\n <label className=\"text-xs font-medium text-text-primary\">Max Retry</label>\n <input\n type=\"number\"\n min={0}\n value={settings.maxRetry}\n className=\"w-full h-9 text-xs border rounded-md px-3 bg-background\"\n onChange={async (e) => {\n const v = parseInt(e.target.value, 10);\n if (!isNaN(v) && v >= 0) {\n const updated = await updateAccountSettings({ maxRetry: v });\n setSettings(updated);\n }\n }}\n />\n <p className=\"text-[10px] text-text-subtle\">\n How many accounts to try on failure. 0 = try all available accounts.\n </p>\n </div>\n\n {/* Active accounts */}\n <div className=\"flex items-center justify-between text-xs border-t border-border pt-3\">\n <span className=\"text-text-subtle\">Active accounts</span>\n <span className=\"font-medium text-text-primary\">{settings.activeCount}</span>\n </div>\n </div>\n );\n}\n\nexport function AccountRotationSettings({ open, onOpenChange }: AccountRotationSettingsProps) {\n const isDesktop = useSyncExternalStore(subscribeMedia, getIsDesktop);\n\n if (!open) return null;\n\n // Desktop: Dialog\n if (isDesktop) {\n return (\n <Dialog open={open} onOpenChange={onOpenChange}>\n <DialogContent className=\"sm:max-w-sm\">\n <DialogHeader>\n <DialogTitle className=\"text-sm flex items-center gap-2\">\n <Settings className=\"size-4\" /> Rotation & Retry\n </DialogTitle>\n </DialogHeader>\n <SettingsContent />\n </DialogContent>\n </Dialog>\n );\n }\n\n // Mobile: Bottom sheet\n return (\n <>\n <div\n className=\"fixed inset-0 z-50 transition-opacity duration-200 opacity-100\"\n onClick={() => onOpenChange(false)}\n style={{ backgroundColor: \"rgba(0,0,0,0.5)\" }}\n />\n <div\n className={cn(\n \"fixed bottom-0 left-0 right-0 z-50 bg-background rounded-t-2xl border-t border-border shadow-2xl\",\n \"transition-transform duration-300 ease-out max-h-[85vh] overflow-y-auto\",\n \"translate-y-0\",\n )}\n >\n {/* Drag handle */}\n <div className=\"flex justify-center pt-3 pb-1\">\n <div className=\"w-10 h-1 rounded-full bg-border\" />\n </div>\n\n {/* Header */}\n <div className=\"flex items-center justify-between px-4 py-2 border-b border-border\">\n <span className=\"text-sm font-semibold flex items-center gap-2\">\n <Settings className=\"size-4\" /> Rotation & Retry\n </span>\n <button\n onClick={() => onOpenChange(false)}\n className=\"flex items-center justify-center size-7 rounded-md hover:bg-surface-elevated transition-colors\"\n >\n <X className=\"size-4\" />\n </button>\n </div>\n\n {/* Content */}\n <div className=\"px-4 py-4 pb-8\">\n <SettingsContent />\n </div>\n </div>\n </>\n );\n}\n","import { useState, useEffect, useMemo } from \"react\";\nimport { Loader2 } from \"lucide-react\";\nimport { getUsageHistory, type UsageSnapshot } from \"../../lib/api-settings\";\n\nconst DAY_LABELS = [\"Mon\", \"Tue\", \"Wed\", \"Thu\", \"Fri\", \"Sat\", \"Sun\"];\nconst HOUR_LABELS = Array.from({ length: 24 }, (_, i) => i);\n\ntype ViewMode = \"5h\" | \"weekly\";\n\ninterface AggregatedCell {\n sum: number;\n count: number;\n avg: number;\n}\n\n/** Aggregate snapshots into a 7×24 grid (day-of-week × hour-of-day) */\nfunction buildHeatmap(snapshots: UsageSnapshot[], mode: ViewMode): AggregatedCell[][] {\n // grid[dayOfWeek 0-6][hour 0-23]\n const grid: AggregatedCell[][] = Array.from({ length: 7 }, () =>\n Array.from({ length: 24 }, () => ({ sum: 0, count: 0, avg: 0 })),\n );\n\n for (const snap of snapshots) {\n const val = mode === \"5h\" ? snap.five_hour_util : snap.weekly_util;\n if (val == null) continue;\n const d = new Date(snap.recorded_at + (snap.recorded_at.endsWith(\"Z\") ? \"\" : \"Z\"));\n const dow = (d.getDay() + 6) % 7; // Monday=0\n const hour = d.getHours();\n grid[dow]![hour]!.sum += val;\n grid[dow]![hour]!.count += 1;\n }\n\n // Compute averages\n for (const row of grid) {\n for (const cell of row) {\n cell.avg = cell.count > 0 ? cell.sum / cell.count : 0;\n }\n }\n return grid;\n}\n\n/** Aggregate snapshots by day-of-week (average utilization) */\nfunction buildDayAvg(grid: AggregatedCell[][]): number[] {\n return grid.map((row) => {\n const totalSum = row.reduce((s, c) => s + c.sum, 0);\n const totalCount = row.reduce((s, c) => s + c.count, 0);\n return totalCount > 0 ? totalSum / totalCount : 0;\n });\n}\n\n/** Aggregate snapshots by hour-of-day (average utilization) */\nfunction buildHourAvg(grid: AggregatedCell[][]): number[] {\n return HOUR_LABELS.map((h) => {\n let sum = 0, count = 0;\n for (const row of grid) {\n sum += row[h]!.sum;\n count += row[h]!.count;\n }\n return count > 0 ? sum / count : 0;\n });\n}\n\nfunction cellColor(val: number): string {\n if (val === 0) return \"bg-surface-elevated\";\n if (val < 0.3) return \"bg-green-500/30\";\n if (val < 0.5) return \"bg-green-500/60\";\n if (val < 0.7) return \"bg-amber-500/50\";\n if (val < 0.9) return \"bg-amber-500/80\";\n return \"bg-red-500/80\";\n}\n\nfunction barColor(val: number): string {\n if (val < 0.3) return \"bg-green-500\";\n if (val < 0.7) return \"bg-amber-500\";\n return \"bg-red-500\";\n}\n\nexport function UsagePatternChart({ accountId }: { accountId: string }) {\n const [snapshots, setSnapshots] = useState<UsageSnapshot[] | null>(null);\n const [loading, setLoading] = useState(true);\n const [mode, setMode] = useState<ViewMode>(\"5h\");\n\n useEffect(() => {\n setLoading(true);\n getUsageHistory(accountId)\n .then(setSnapshots)\n .catch(() => setSnapshots([]))\n .finally(() => setLoading(false));\n }, [accountId]);\n\n const grid = useMemo(() => snapshots ? buildHeatmap(snapshots, mode) : null, [snapshots, mode]);\n const dayAvg = useMemo(() => grid ? buildDayAvg(grid) : [], [grid]);\n const hourAvg = useMemo(() => grid ? buildHourAvg(grid) : [], [grid]);\n const maxDay = Math.max(...dayAvg, 0.01);\n const maxHour = Math.max(...hourAvg, 0.01);\n\n if (loading) {\n return (\n <div className=\"flex items-center justify-center py-3\">\n <Loader2 className=\"size-3 animate-spin text-text-subtle\" />\n </div>\n );\n }\n\n if (!snapshots || snapshots.length === 0) {\n return (\n <div className=\"text-[10px] text-text-subtle py-2 text-center\">\n No usage history yet\n </div>\n );\n }\n\n const dataPoints = snapshots.length;\n const daysWithData = new Set(snapshots.map((s) => new Date(s.recorded_at + (s.recorded_at.endsWith(\"Z\") ? \"\" : \"Z\")).toDateString())).size;\n\n return (\n <div className=\"mt-2 space-y-2\">\n <div className=\"flex items-center justify-between\">\n <span className=\"text-[10px] font-medium text-text-subtle\">Usage Pattern (7d)</span>\n <div className=\"flex gap-0.5 text-[9px]\">\n <button\n onClick={() => setMode(\"5h\")}\n className={`px-1.5 py-0.5 rounded cursor-pointer transition-colors ${mode === \"5h\" ? \"bg-primary/15 text-primary\" : \"text-text-subtle hover:text-text-secondary\"}`}\n title=\"5-hour rolling window limit — resets every 5 hours\"\n >\n 5h\n </button>\n <button\n onClick={() => setMode(\"weekly\")}\n className={`px-1.5 py-0.5 rounded cursor-pointer transition-colors ${mode === \"weekly\" ? \"bg-primary/15 text-primary\" : \"text-text-subtle hover:text-text-secondary\"}`}\n title=\"Weekly limit — resets every 7 days\"\n >\n Wk\n </button>\n </div>\n </div>\n\n {/* Explanation */}\n <p className=\"text-[9px] text-text-subtle leading-tight\">\n Avg {mode === \"5h\" ? \"5-hour\" : \"weekly\"} limit usage over {daysWithData}d ({dataPoints} samples). Higher % = closer to rate limit. Hover cells for details.\n </p>\n\n {/* Day of week bars */}\n <div>\n <span className=\"text-[9px] text-text-subtle\">Avg usage by day of week</span>\n <div className=\"flex flex-col gap-[2px] mt-0.5\">\n {DAY_LABELS.map((label, i) => {\n const val = dayAvg[i] ?? 0;\n return (\n <div key={label} className=\"flex items-center gap-1\">\n <span className=\"text-[8px] text-text-subtle w-5 shrink-0 text-right tabular-nums\">{label}</span>\n <div className=\"flex-1 h-2.5 bg-surface-elevated rounded-sm overflow-hidden\">\n <div\n className={`h-full rounded-sm transition-all ${barColor(val)}`}\n style={{ width: `${Math.round((val / maxDay) * 100)}%` }}\n />\n </div>\n <span className=\"text-[8px] text-text-subtle w-6 shrink-0 text-right tabular-nums\">\n {Math.round(val * 100)}%\n </span>\n </div>\n );\n })}\n </div>\n </div>\n\n {/* Hour of day heatmap */}\n <div>\n <span className=\"text-[9px] text-text-subtle\">Avg usage by hour (0h-23h)</span>\n <div className=\"flex gap-[1px] mt-0.5\">\n {HOUR_LABELS.map((h) => {\n const val = hourAvg[h] ?? 0;\n return (\n <div key={h} className=\"flex-1 flex flex-col items-center gap-[1px]\">\n <div\n className={`w-full aspect-square rounded-[2px] ${cellColor(val)}`}\n title={`${h}:00 — avg ${Math.round(val * 100)}% usage`}\n />\n {h % 6 === 0 && (\n <span className=\"text-[7px] text-text-subtle tabular-nums\">{h}</span>\n )}\n </div>\n );\n })}\n </div>\n </div>\n\n {/* Heatmap: day × hour grid */}\n {grid && (\n <div>\n <span className=\"text-[9px] text-text-subtle\">Day x Hour heatmap</span>\n <div className=\"flex flex-col gap-[1px] mt-0.5\">\n {DAY_LABELS.map((label, d) => (\n <div key={label} className=\"flex items-center gap-[1px]\">\n <span className=\"text-[7px] text-text-subtle w-4 shrink-0 text-right\">{label.charAt(0)}</span>\n {HOUR_LABELS.map((h) => {\n const cell = grid[d]![h]!;\n return (\n <div\n key={h}\n className={`flex-1 aspect-square rounded-[1px] ${cellColor(cell.avg)}`}\n title={`${label} ${h}:00 — ${cell.count > 0 ? `avg ${Math.round(cell.avg * 100)}% (${cell.count} samples)` : \"no data\"}`}\n />\n );\n })}\n </div>\n ))}\n {/* Hour axis labels for heatmap */}\n <div className=\"flex items-center gap-[1px]\">\n <span className=\"w-4 shrink-0\" />\n {HOUR_LABELS.map((h) => (\n <div key={h} className=\"flex-1 text-center\">\n {h % 6 === 0 && <span className=\"text-[7px] text-text-subtle tabular-nums\">{h}</span>}\n </div>\n ))}\n </div>\n </div>\n </div>\n )}\n\n {/* Color legend */}\n <div className=\"flex items-center gap-1.5 text-[8px] text-text-subtle\">\n <span>Low</span>\n <div className=\"flex gap-[2px]\">\n <div className=\"size-2 rounded-[1px] bg-green-500/30\" />\n <div className=\"size-2 rounded-[1px] bg-green-500/60\" />\n <div className=\"size-2 rounded-[1px] bg-amber-500/50\" />\n <div className=\"size-2 rounded-[1px] bg-amber-500/80\" />\n <div className=\"size-2 rounded-[1px] bg-red-500/80\" />\n </div>\n <span>High</span>\n <span className=\"ml-1\">|</span>\n <div className=\"size-2 rounded-[1px] bg-surface-elevated border border-border/30\" />\n <span>No data</span>\n </div>\n </div>\n );\n}\n","import { useState, useEffect, useRef } from \"react\";\nimport { Activity, RefreshCw, Eye, Download, Upload, Plus, X, Settings, Trash2, Maximize2, Minimize2 } from \"lucide-react\";\nimport { Switch } from \"@/components/ui/switch\";\nimport type { UsageInfo, LimitBucket } from \"../../../types/chat\";\nimport {\n getAccounts,\n getActiveAccount,\n getAllAccountUsages,\n patchAccount,\n deleteAccount,\n type AccountInfo,\n type AccountUsageEntry,\n type OAuthProfileData,\n} from \"../../lib/api-settings\";\nimport { AddAccountDialog, ExportAccountsDialog, ImportAccountsDialog } from \"./account-dialogs\";\nimport { AccountRotationSettings } from \"./account-rotation-settings\";\nimport { UsagePatternChart } from \"./usage-pattern-chart\";\n\ninterface UsageBadgeProps {\n usage: UsageInfo;\n loading?: boolean;\n onClick?: () => void;\n}\n\nfunction pctColor(pct: number): string {\n if (pct >= 90) return \"text-red-500\";\n if (pct >= 70) return \"text-amber-500\";\n return \"text-green-500\";\n}\n\nfunction barColor(pct: number): string {\n if (pct >= 90) return \"bg-red-500\";\n if (pct >= 70) return \"bg-amber-500\";\n return \"bg-green-500\";\n}\n\nexport function UsageBadge({ usage, loading, onClick }: UsageBadgeProps) {\n const fiveHourPct = usage.fiveHour != null ? Math.round(usage.fiveHour * 100) : null;\n const sevenDayPct = usage.sevenDay != null ? Math.round(usage.sevenDay * 100) : null;\n\n const fiveHourLabel = fiveHourPct != null ? `${fiveHourPct}%` : \"--%\";\n const sevenDayLabel = sevenDayPct != null ? `${sevenDayPct}%` : \"--%\";\n\n const worstPct = Math.max(fiveHourPct ?? 0, sevenDayPct ?? 0);\n const colorClass = fiveHourPct != null || sevenDayPct != null ? pctColor(worstPct) : \"text-text-subtle\";\n\n return (\n <button\n onClick={onClick}\n className={`flex items-center gap-1 px-1.5 py-0.5 rounded text-[11px] font-medium tabular-nums transition-colors hover:bg-surface-hover ${colorClass}`}\n title=\"Click for usage details\"\n >\n {loading ? <RefreshCw className=\"size-3 animate-spin\" /> : <Activity className=\"size-3\" />}\n <span>5h:{fiveHourLabel}</span>\n <span className=\"text-text-subtle\">·</span>\n <span>Wk:{sevenDayLabel}</span>\n </button>\n );\n}\n\n// --- Detail panel ---\n\ninterface UsageDetailPanelProps {\n usage: UsageInfo;\n visible: boolean;\n onClose: () => void;\n onReload?: () => void;\n loading?: boolean;\n lastFetchedAt?: string | null;\n}\n\nfunction formatResetTime(bucket?: LimitBucket): string | null {\n if (!bucket) return null;\n let totalMins: number | null = null;\n if (bucket.resetsInMinutes != null) {\n totalMins = bucket.resetsInMinutes;\n } else if (bucket.resetsInHours != null) {\n totalMins = Math.round(bucket.resetsInHours * 60);\n } else if (bucket.resetsAt) {\n const diff = new Date(bucket.resetsAt).getTime() - Date.now();\n totalMins = diff > 0 ? Math.ceil(diff / 60_000) : 0;\n }\n if (totalMins == null) return null;\n if (totalMins <= 0) return \"now\";\n const d = Math.floor(totalMins / 1440);\n const h = Math.floor((totalMins % 1440) / 60);\n const m = totalMins % 60;\n if (d > 0) return m > 0 ? `${d}d ${h}h ${m}m` : h > 0 ? `${d}d ${h}h` : `${d}d`;\n if (h > 0) return m > 0 ? `${h}h ${m}m` : `${h}h`;\n return `${m}m`;\n}\n\nfunction BucketRow({ label, bucket }: { label: string; bucket?: LimitBucket }) {\n if (!bucket) return null;\n const pct = Math.round(bucket.utilization * 100);\n const reset = formatResetTime(bucket);\n\n return (\n <div className=\"space-y-1\">\n <div className=\"flex items-center justify-between\">\n <span className=\"text-xs font-medium text-text-primary\">{label}</span>\n {reset && (\n <span className=\"text-[10px] text-text-subtle\" title=\"Resets in\">↻ {reset}</span>\n )}\n </div>\n <div className=\"flex items-center gap-2\">\n <div className=\"flex-1 h-2 rounded-full bg-border overflow-hidden\">\n <div\n className={`h-full rounded-full transition-all ${barColor(pct)}`}\n style={{ width: `${Math.min(pct, 100)}%` }}\n />\n </div>\n <span className={`text-xs font-medium tabular-nums w-10 text-right ${pctColor(pct)}`}>\n {pct}%\n </span>\n </div>\n </div>\n );\n}\n\nfunction formatExpiry(expiresAt: number): string {\n const diff = expiresAt - Date.now();\n if (diff <= 0) return \"expired\";\n const mins = Math.ceil(diff / 60_000);\n const h = Math.floor(mins / 60);\n const d = Math.floor(h / 24);\n if (d > 0) return `${d}d ${h % 24}h`;\n if (h > 0) return `${h}h ${mins % 60}m`;\n return `${mins}m`;\n}\n\n/** Derive a human-readable token status from account info */\nfunction tokenStatus(info?: AccountInfo): { label: string; tip: string; color: string } {\n if (!info) return { label: \"unknown\", tip: \"No account info available\", color: \"text-text-subtle\" };\n if (!info.expiresAt) return { label: \"key\", tip: \"API key (no expiry)\", color: \"text-text-subtle\" };\n const expired = info.expiresAt * 1000 < Date.now(); // expiresAt is seconds\n if (expired && info.hasRefreshToken) return { label: \"expired\", tip: \"Token expired but has refresh token — will auto-renew\", color: \"text-amber-500\" };\n if (expired) return { label: \"expired\", tip: \"Token expired, no refresh token\", color: \"text-red-500\" };\n if (info.hasRefreshToken) return { label: \"long-lived\", tip: \"OAuth token with refresh — long-lived\", color: \"text-green-500\" };\n return { label: \"temp\", tip: \"Temporary token without refresh — will expire\", color: \"text-amber-500\" };\n}\n\nfunction formatLastUpdated(ts: number | null | undefined): string | null {\n if (!ts) return null;\n const secs = Math.round((Date.now() - ts) / 1000);\n if (secs < 5) return \"just now\";\n if (secs < 60) return `${secs}s ago`;\n const mins = Math.floor(secs / 60);\n if (mins < 60) return `${mins}m ago`;\n const hrs = Math.floor(mins / 60);\n const remainMins = mins % 60;\n if (hrs < 24) return remainMins > 0 ? `${hrs}h ${remainMins}m ago` : `${hrs}h ago`;\n const days = Math.floor(hrs / 24);\n return `${days}d ago`;\n}\n\nfunction AccountUsageCard({ entry, isActive, accountInfo, onToggle, onDelete, onExport, onViewProfile, flash, fullscreen }: {\n entry: AccountUsageEntry;\n isActive: boolean;\n accountInfo?: AccountInfo;\n onToggle?: (id: string, status: string) => void;\n onDelete?: (id: string, display: string) => void;\n onExport?: (id: string) => void;\n onViewProfile?: (profile: OAuthProfileData, accountId: string) => void;\n flash?: boolean;\n fullscreen?: boolean;\n}) {\n const { usage } = entry;\n const hasBuckets = usage.session || usage.weekly || usage.weeklyOpus || usage.weeklySonnet;\n const status = accountInfo?.status ?? entry.accountStatus;\n // Expired: has expiresAt in the past AND no refresh token to auto-renew\n const isExpired = !!(accountInfo && !accountInfo.hasRefreshToken && accountInfo.expiresAt && accountInfo.expiresAt < Math.floor(Date.now() / 1000));\n\n return (\n <div className={`rounded-md border p-2 transition-colors duration-500 ${fullscreen ? \"flex flex-col gap-1.5 overflow-hidden\" : \"space-y-1.5 min-w-[200px] shrink-0 snap-start\"} ${isExpired ? \"opacity-50\" : \"\"} ${flash ? \"bg-primary/10 border-primary/40\" : \"\"} ${isActive ? \"border-primary/30 bg-primary/5\" : \"border-border/50\"}`}>\n <div className=\"flex items-center gap-1.5\">\n <span className=\"text-xs font-medium truncate flex-1 min-w-0\">\n {entry.accountLabel ?? entry.accountId.slice(0, 8)}\n </span>\n {isExpired && (\n <span className=\"text-[9px] text-red-500 shrink-0 font-medium\">Expired</span>\n )}\n {!entry.isOAuth && !isExpired && (\n <span className=\"text-[9px] text-text-subtle shrink-0\">API key</span>\n )}\n {/* Account controls */}\n <div className=\"flex items-center gap-0.5 shrink-0\">\n {!isExpired && onViewProfile && accountInfo?.profileData && (\n <button\n className=\"p-1 rounded cursor-pointer text-text-subtle hover:text-foreground hover:bg-surface-elevated transition-colors\"\n onClick={() => onViewProfile(accountInfo.profileData!, entry.accountId)}\n title=\"View profile\"\n >\n <Eye className=\"size-3\" />\n </button>\n )}\n {!isExpired && onExport && entry.isOAuth && (\n <button\n className=\"p-1 rounded cursor-pointer text-text-subtle hover:text-blue-500 hover:bg-surface-elevated transition-colors\"\n onClick={() => onExport(entry.accountId)}\n title=\"Export this account\"\n >\n <Download className=\"size-3\" />\n </button>\n )}\n {!isExpired && onToggle && (\n <Switch\n checked={status !== \"disabled\"}\n onCheckedChange={() => onToggle(entry.accountId, status)}\n disabled={status === \"cooldown\"}\n className=\"scale-[0.6] cursor-pointer\"\n />\n )}\n {onDelete && (\n <button\n className=\"p-1 rounded cursor-pointer text-text-subtle hover:text-red-500 hover:bg-surface-elevated transition-colors\"\n onClick={() => onDelete(entry.accountId, entry.accountLabel ?? entry.accountId.slice(0, 8))}\n title=\"Remove account\"\n >\n <Trash2 className=\"size-3\" />\n </button>\n )}\n </div>\n </div>\n {hasBuckets ? (\n <div className={fullscreen ? \"flex-1 flex flex-col justify-evenly min-h-0\" : \"space-y-1.5\"}>\n <BucketRow label=\"5-Hour Session\" bucket={usage.session} />\n <BucketRow label=\"Weekly\" bucket={usage.weekly} />\n <BucketRow label=\"Weekly (Opus)\" bucket={usage.weeklyOpus} />\n <BucketRow label=\"Weekly (Sonnet)\" bucket={usage.weeklySonnet} />\n </div>\n ) : (\n <p className=\"text-[10px] text-text-subtle\">\n {entry.isOAuth ? \"No usage data yet\" : \"Usage tracking not available for API keys\"}\n </p>\n )}\n {/* Footer: updated · expires · type */}\n {(() => {\n const ts = tokenStatus(accountInfo);\n return (\n <div className=\"flex items-center gap-1.5 text-[9px] text-text-subtle flex-wrap\">\n {usage.lastFetchedAt && (\n <span title=\"Last usage data update\">↻ {formatLastUpdated(new Date(usage.lastFetchedAt).getTime())}</span>\n )}\n {accountInfo?.expiresAt && accountInfo.expiresAt * 1000 > Date.now() && (\n <span title=\"Token expires in\">⏱ {formatExpiry(accountInfo.expiresAt * 1000)}</span>\n )}\n <span className={ts.color} title={ts.tip}>© {ts.label}</span>\n </div>\n );\n })()}\n </div>\n );\n}\n\nexport function UsageDetailPanel({ usage, visible, onClose, onReload, loading, lastFetchedAt }: UsageDetailPanelProps) {\n const [allUsages, setAllUsages] = useState<AccountUsageEntry[]>([]);\n const [accounts, setAccounts] = useState<AccountInfo[]>([]);\n const [activeAccountId, setActiveAccountId] = useState<string | null>(null);\n const [initialLoading, setInitialLoading] = useState(true);\n const [refreshing, setRefreshing] = useState(false);\n const [flashIds, setFlashIds] = useState<Set<string>>(new Set());\n const [profileView, setProfileView] = useState<{ profile: OAuthProfileData; accountId: string } | null>(null);\n const [showAddDialog, setShowAddDialog] = useState(false);\n const [showExportDialog, setShowExportDialog] = useState(false);\n const [showImportDialog, setShowImportDialog] = useState(false);\n const [showRotationSettings, setShowRotationSettings] = useState(false);\n const [deleteTarget, setDeleteTarget] = useState<{ id: string; display: string } | null>(null);\n const [exportPreselect, setExportPreselect] = useState<string | null>(null);\n const [isFullscreen, setIsFullscreen] = useState(false);\n const [message, setMessage] = useState<string | null>(null);\n const msgTimer = useRef<ReturnType<typeof setTimeout>>(undefined);\n const prevUsagesRef = useRef<AccountUsageEntry[]>([]);\n\n function showMessage(msg: string) {\n if (msgTimer.current) clearTimeout(msgTimer.current);\n setMessage(msg);\n msgTimer.current = setTimeout(() => setMessage(null), 4000);\n }\n\n function handleSuccess(msg?: string) {\n loadAll();\n if (msg) showMessage(msg);\n }\n\n async function loadAll() {\n const isRefresh = allUsages.length > 0;\n if (isRefresh) setRefreshing(true); else setInitialLoading(true);\n\n const [usages, accs, active] = await Promise.allSettled([\n getAllAccountUsages(), getAccounts(), getActiveAccount(),\n ]);\n\n if (usages.status === \"fulfilled\") {\n const newUsages = usages.value;\n // Detect which accounts changed usage values\n if (isRefresh && prevUsagesRef.current.length > 0) {\n const changed = new Set<string>();\n const prevMap = new Map(prevUsagesRef.current.map(u => [u.accountId, u]));\n for (const nu of newUsages) {\n const prev = prevMap.get(nu.accountId);\n if (!prev) { changed.add(nu.accountId); continue; }\n const pu = prev.usage, cu = nu.usage;\n if (pu.session?.utilization !== cu.session?.utilization\n || pu.weekly?.utilization !== cu.weekly?.utilization\n || pu.weeklyOpus?.utilization !== cu.weeklyOpus?.utilization\n || pu.weeklySonnet?.utilization !== cu.weeklySonnet?.utilization) {\n changed.add(nu.accountId);\n }\n }\n if (changed.size > 0) {\n setFlashIds(changed);\n setTimeout(() => setFlashIds(new Set()), 1500);\n }\n }\n prevUsagesRef.current = newUsages;\n setAllUsages(newUsages);\n }\n if (accs.status === \"fulfilled\") setAccounts(accs.value);\n if (active.status === \"fulfilled\") setActiveAccountId(active.value?.id ?? null);\n setInitialLoading(false);\n setRefreshing(false);\n }\n\n useEffect(() => {\n if (!visible) return;\n loadAll();\n }, [visible]);\n\n // Re-fetch account usages after parent refreshes from Anthropic API\n useEffect(() => {\n if (!visible || !lastFetchedAt) return;\n loadAll();\n }, [lastFetchedAt]); // eslint-disable-line react-hooks/exhaustive-deps\n\n if (!visible) return null;\n\n const accountMap = new Map(accounts.map((a) => [a.id, a]));\n const hasCost = usage.queryCostUsd != null || usage.totalCostUsd != null;\n const hasMultipleAccounts = allUsages.length > 0;\n\n // Grid dimensions for fullscreen: cards fill viewport without scroll\n const fsCount = allUsages.length || 1;\n const fsCols = Math.ceil(Math.sqrt(fsCount));\n const fsRows = Math.ceil(fsCount / fsCols);\n\n async function handleToggle(id: string, status: string) {\n await patchAccount(id, { status: status === \"disabled\" ? \"active\" : \"disabled\" });\n loadAll();\n onReload?.();\n }\n\n async function confirmDeleteAccount() {\n if (!deleteTarget) return;\n try {\n await deleteAccount(deleteTarget.id);\n showMessage(`Account \"${deleteTarget.display}\" removed.`);\n loadAll();\n onReload?.();\n } catch (e) {\n showMessage(`Failed to remove: ${(e as Error).message}`);\n }\n setDeleteTarget(null);\n }\n\n function openExportAll() {\n setExportPreselect(null);\n setShowExportDialog(true);\n }\n\n return (\n <div className={`relative border-b border-border bg-surface px-3 py-2.5 ${isFullscreen ? \"fixed inset-0 z-50 flex flex-col gap-2.5 overflow-hidden\" : \"space-y-2.5 max-h-[350px] overflow-y-auto\"}`}>\n <div className=\"flex items-center justify-between shrink-0\">\n <div className=\"flex items-center gap-2\">\n <span className=\"text-xs font-semibold text-text-primary\">Usage & Accounts</span>\n {lastFetchedAt && (\n <span className=\"text-[10px] text-text-subtle\">{formatLastUpdated(new Date(lastFetchedAt).getTime())}</span>\n )}\n </div>\n <div className=\"flex items-center gap-1\">\n <button\n onClick={() => setShowRotationSettings(true)}\n className=\"text-xs text-text-subtle hover:text-text-primary px-1 cursor-pointer\"\n title=\"Rotation & retry settings\"\n >\n <Settings className=\"size-3\" />\n </button>\n {hasMultipleAccounts && (\n <button\n onClick={() => setIsFullscreen((v) => !v)}\n className=\"text-xs text-text-subtle hover:text-text-primary px-1 cursor-pointer\"\n title={isFullscreen ? \"Exit fullscreen\" : \"Fullscreen view\"}\n >\n {isFullscreen ? <Minimize2 className=\"size-3\" /> : <Maximize2 className=\"size-3\" />}\n </button>\n )}\n {onReload && (\n <button\n onClick={() => { onReload(); loadAll(); }}\n disabled={loading || refreshing}\n className=\"text-xs text-text-subtle hover:text-text-primary px-1 disabled:opacity-50 cursor-pointer\"\n title=\"Refresh\"\n >\n <RefreshCw className={`size-3 ${(loading || refreshing) ? \"animate-spin\" : \"\"}`} />\n </button>\n )}\n <button\n onClick={() => { setIsFullscreen(false); onClose(); }}\n className=\"text-xs text-text-subtle hover:text-text-primary px-1 cursor-pointer\"\n >\n <X className=\"size-3\" />\n </button>\n </div>\n </div>\n\n {message && (\n <div className=\"text-[11px] p-1.5 rounded bg-green-500/10 text-green-600 text-center animate-in fade-in duration-200\">\n {message}\n </div>\n )}\n\n {(hasMultipleAccounts || initialLoading) ? (\n <div\n className={isFullscreen\n ? \"flex-1 min-h-0 grid gap-2 overflow-hidden\"\n : \"flex gap-1.5 overflow-x-auto pb-1 -mx-3 px-3 snap-x snap-mandatory scrollbar-thin\"\n }\n style={isFullscreen ? {\n gridTemplateColumns: `repeat(${fsCols}, minmax(0, 1fr))`,\n gridTemplateRows: `repeat(${fsRows}, minmax(0, 1fr))`,\n } : undefined}\n >\n {initialLoading ? (\n <p className=\"text-[10px] text-text-subtle\">Loading...</p>\n ) : (\n allUsages.map((entry) => (\n <AccountUsageCard\n key={entry.accountId}\n entry={entry}\n isActive={entry.accountId === (activeAccountId ?? usage.activeAccountId)}\n accountInfo={accountMap.get(entry.accountId)}\n onToggle={handleToggle}\n onDelete={(id, display) => setDeleteTarget({ id, display })}\n onExport={(id) => { setExportPreselect(id); setShowExportDialog(true); }}\n onViewProfile={(profile, accountId) => setProfileView({ profile, accountId })}\n flash={flashIds.has(entry.accountId)}\n fullscreen={isFullscreen}\n />\n ))\n )}\n </div>\n ) : (\n <>\n {usage.session || usage.weekly || usage.weeklyOpus || usage.weeklySonnet ? (\n <div className=\"space-y-2.5\">\n <BucketRow label=\"5-Hour Session\" bucket={usage.session} />\n <BucketRow label=\"Weekly\" bucket={usage.weekly} />\n <BucketRow label=\"Weekly (Opus)\" bucket={usage.weeklyOpus} />\n <BucketRow label=\"Weekly (Sonnet)\" bucket={usage.weeklySonnet} />\n </div>\n ) : (\n <p className=\"text-xs text-text-subtle\">No usage data available</p>\n )}\n </>\n )}\n\n {hasCost && (\n <div className=\"border-t border-border pt-2 space-y-1\">\n {usage.queryCostUsd != null && (\n <div className=\"flex items-center justify-between text-xs\">\n <span className=\"text-text-subtle\">Last query</span>\n <span className=\"text-text-primary font-medium tabular-nums\">\n ${usage.queryCostUsd.toFixed(4)}\n </span>\n </div>\n )}\n {usage.totalCostUsd != null && (\n <div className=\"flex items-center justify-between text-xs\">\n <span className=\"text-text-subtle\">Session total</span>\n <span className=\"text-text-primary font-medium tabular-nums\">\n ${usage.totalCostUsd.toFixed(4)}\n </span>\n </div>\n )}\n </div>\n )}\n\n {/* Inline profile popup */}\n {profileView && (\n <div className=\"border-t border-border pt-2\">\n <div className=\"flex items-center justify-between mb-1\">\n <span className=\"text-[10px] font-medium text-text-subtle\">Profile</span>\n <button className=\"text-text-subtle hover:text-foreground cursor-pointer\" onClick={() => setProfileView(null)}>\n <X className=\"size-3\" />\n </button>\n </div>\n <div className=\"grid grid-cols-[70px_1fr] gap-x-2 gap-y-0.5 text-[10px]\">\n {profileView.profile.account?.display_name && <><span className=\"text-text-subtle\">Name</span><span>{profileView.profile.account.display_name}</span></>}\n {profileView.profile.account?.email && <><span className=\"text-text-subtle\">Email</span><span>{profileView.profile.account.email}</span></>}\n {profileView.profile.organization?.name && <><span className=\"text-text-subtle\">Org</span><span>{profileView.profile.organization.name}</span></>}\n {profileView.profile.organization?.organization_type && <><span className=\"text-text-subtle\">Type</span><span>{profileView.profile.organization.organization_type}</span></>}\n {profileView.profile.organization?.rate_limit_tier && <><span className=\"text-text-subtle\">Tier</span><span>{profileView.profile.organization.rate_limit_tier}</span></>}\n {profileView.profile.organization?.subscription_status && <><span className=\"text-text-subtle\">Status</span><span>{profileView.profile.organization.subscription_status}</span></>}\n </div>\n <UsagePatternChart accountId={profileView.accountId} />\n </div>\n )}\n\n {/* Action buttons */}\n <div className=\"border-t border-border pt-2 flex gap-1.5 shrink-0\">\n <button onClick={() => setShowAddDialog(true)} className=\"flex-1 flex items-center justify-center gap-1 rounded-md border border-border px-2 py-1 text-[11px] text-text-secondary hover:bg-surface-hover transition-colors cursor-pointer\">\n <Plus className=\"size-3\" /> Add\n </button>\n <button onClick={openExportAll} className=\"flex-1 flex items-center justify-center gap-1 rounded-md border border-border px-2 py-1 text-[11px] text-text-secondary hover:bg-surface-hover transition-colors cursor-pointer\">\n <Download className=\"size-3\" /> Export\n </button>\n <button onClick={() => setShowImportDialog(true)} className=\"flex-1 flex items-center justify-center gap-1 rounded-md border border-border px-2 py-1 text-[11px] text-text-secondary hover:bg-surface-hover transition-colors cursor-pointer\">\n <Upload className=\"size-3\" /> Import\n </button>\n </div>\n\n {/* Delete confirmation overlay */}\n {deleteTarget && (\n <div className=\"absolute inset-0 z-10 flex items-center justify-center bg-background/80 backdrop-blur-sm rounded-md\">\n <div className=\"bg-surface border border-border rounded-lg shadow-lg p-4 mx-4 max-w-[280px] w-full space-y-3\">\n <p className=\"text-xs text-text-primary text-center\">\n Remove <strong className=\"text-foreground\">{deleteTarget.display}</strong>?\n </p>\n <div className=\"flex gap-2\">\n <button onClick={() => setDeleteTarget(null)} className=\"flex-1 px-3 py-1.5 rounded-md text-xs border border-border text-text-secondary hover:bg-surface-hover cursor-pointer transition-colors\">\n Cancel\n </button>\n <button onClick={confirmDeleteAccount} className=\"flex-1 px-3 py-1.5 rounded-md text-xs bg-red-500 text-white hover:bg-red-600 cursor-pointer transition-colors\">\n Remove\n </button>\n </div>\n </div>\n </div>\n )}\n\n {/* Account dialogs */}\n <AddAccountDialog open={showAddDialog} onOpenChange={setShowAddDialog} onSuccess={handleSuccess} />\n <ExportAccountsDialog open={showExportDialog} onOpenChange={(v) => { setShowExportDialog(v); if (!v) setExportPreselect(null); }} accounts={accounts} preselectId={exportPreselect} onMessage={showMessage} />\n <ImportAccountsDialog open={showImportDialog} onOpenChange={setShowImportDialog} onSuccess={handleSuccess} />\n <AccountRotationSettings open={showRotationSettings} onOpenChange={setShowRotationSettings} />\n </div>\n );\n}\n","import { useState, useRef, useEffect, useCallback } from \"react\";\nimport { RefreshCw } from \"lucide-react\";\nimport { cn } from \"@/lib/utils\";\nimport { api } from \"@/lib/api-client\";\nimport type { TeamMessageItem } from \"@/hooks/use-chat\";\n\ninterface TeamActivityPanelProps {\n teamNames: string[];\n messages: TeamMessageItem[];\n}\n\nconst STATUS_COLORS: Record<string, string> = {\n active: \"bg-green-500\",\n idle: \"bg-yellow-500\",\n shutdown: \"bg-zinc-400\",\n};\n\nconst TYPE_BADGES: Record<string, { label: string; className: string }> = {\n task_assignment: { label: \"task\", className: \"bg-blue-500/20 text-blue-400\" },\n idle_notification: { label: \"idle\", className: \"bg-yellow-500/20 text-yellow-400\" },\n completion: { label: \"done\", className: \"bg-green-500/20 text-green-400\" },\n shutdown_request: { label: \"shutdown\", className: \"bg-red-500/20 text-red-400\" },\n shutdown_approved: { label: \"shutdown ✓\", className: \"bg-zinc-500/20 text-zinc-400\" },\n};\n\nexport function TeamActivityPanel({ teamNames, messages }: TeamActivityPanelProps) {\n const [selectedTeam, setSelectedTeam] = useState(teamNames[0] ?? \"\");\n const [members, setMembers] = useState<any[]>([]);\n const [loading, setLoading] = useState(false);\n const messagesEndRef = useRef<HTMLDivElement>(null);\n\n // Sync selected team when teamNames changes\n useEffect(() => {\n if (teamNames.length > 0 && !teamNames.includes(selectedTeam)) {\n setSelectedTeam(teamNames[0]!);\n }\n }, [teamNames, selectedTeam]);\n\n const fetchTeamDetail = useCallback(async (name: string) => {\n setLoading(true);\n try {\n const detail = await api.get<any>(`/api/teams/${encodeURIComponent(name)}`);\n setMembers(detail?.members ?? []);\n } catch { setMembers([]); }\n setLoading(false);\n }, []);\n\n // Fetch members on mount and tab switch\n useEffect(() => {\n if (selectedTeam) fetchTeamDetail(selectedTeam);\n }, [selectedTeam, fetchTeamDetail]);\n\n // Auto-scroll messages\n useEffect(() => {\n messagesEndRef.current?.scrollIntoView({ behavior: \"smooth\" });\n }, [messages.length]);\n\n const displayMessages = messages.slice(-200);\n\n return (\n <div className=\"space-y-0\">\n {/* Team tabs + refresh */}\n <div className=\"flex items-center gap-1 mb-2\">\n <div className=\"flex items-center gap-1 overflow-x-auto min-w-0 flex-1\">\n {teamNames.map((name) => (\n <button\n key={name}\n onClick={() => setSelectedTeam(name)}\n className={cn(\n \"px-2 py-0.5 text-[11px] rounded-md whitespace-nowrap transition-colors\",\n selectedTeam === name\n ? \"bg-primary/10 text-primary font-medium\"\n : \"text-text-subtle hover:text-text-primary\"\n )}\n >\n {name}\n </button>\n ))}\n </div>\n <button\n onClick={() => selectedTeam && fetchTeamDetail(selectedTeam)}\n className=\"text-text-subtle hover:text-foreground p-1 shrink-0\"\n aria-label=\"Refresh\"\n >\n <RefreshCw className={cn(\"size-3\", loading && \"animate-spin\")} />\n </button>\n </div>\n\n {/* Members */}\n {members.length > 0 && (\n <div className=\"pb-2 mb-2 border-b border-border/30\">\n <div className=\"text-[10px] text-text-subtle uppercase tracking-wider mb-1\">Members</div>\n <div className=\"space-y-1\">\n {members.map((m: any) => (\n <div key={m.name} className=\"flex items-center gap-2 text-xs\">\n <span className={cn(\"size-1.5 rounded-full shrink-0\", STATUS_COLORS[m.status] ?? \"bg-zinc-400\")} />\n <span className=\"font-medium truncate\">{m.name}</span>\n {m.model && m.model !== \"unknown\" && (\n <span className=\"text-text-subtle text-[10px]\">({m.model})</span>\n )}\n <span className=\"ml-auto text-text-subtle text-[10px]\">{m.status}</span>\n </div>\n ))}\n </div>\n </div>\n )}\n\n {/* Messages */}\n <div className=\"max-h-40 overflow-y-auto\">\n {displayMessages.length === 0 ? (\n <p className=\"text-xs text-text-subtle text-center py-2\">No messages yet</p>\n ) : (\n <div className=\"space-y-2\">\n {displayMessages.map((msg, i) => {\n const badge = msg.parsedType ? TYPE_BADGES[msg.parsedType] : null;\n const time = formatTime(msg.timestamp);\n return (\n <div key={`${msg.timestamp}-${i}`} className=\"text-xs\">\n <div className=\"flex items-center gap-1 text-text-subtle\">\n <span className=\"font-medium\" style={safeColor(msg.color)}>\n {msg.from}\n </span>\n <span>→</span>\n <span>{msg.to}</span>\n <span className=\"ml-auto text-[10px]\">{time}</span>\n </div>\n <div className=\"mt-0.5 text-foreground/90 break-words\">\n {badge && (\n <span className={cn(\"inline-block px-1 py-0 rounded text-[9px] mr-1\", badge.className)}>\n {badge.label}\n </span>\n )}\n {msg.summary ?? truncateText(msg.text)}\n </div>\n </div>\n );\n })}\n <div ref={messagesEndRef} />\n </div>\n )}\n </div>\n </div>\n );\n}\n\nfunction formatTime(timestamp: string): string {\n try {\n const d = new Date(timestamp);\n return d.toLocaleTimeString([], { hour: \"2-digit\", minute: \"2-digit\", second: \"2-digit\" });\n } catch { return \"\"; }\n}\n\n/** Sanitize color value to prevent CSS injection */\nfunction safeColor(color?: string): React.CSSProperties | undefined {\n if (!color) return undefined;\n if (/^#[0-9a-fA-F]{3,8}$/.test(color) || /^[a-zA-Z]{3,20}$/.test(color)) {\n return { color };\n }\n return undefined;\n}\n\nfunction truncateText(text: string, max = 120): string {\n if (!text) return \"\";\n try {\n const parsed = JSON.parse(text);\n return parsed.summary ?? parsed.text ?? text.slice(0, max);\n } catch {}\n return text.length > max ? text.slice(0, max) + \"...\" : text;\n}\n","import { useState, useEffect, useCallback, useRef, type MouseEvent } from \"react\";\nimport { History, Settings2, Loader2, MessageSquare, RefreshCw, Search, Pencil, Check, X, BellOff, Bug, ClipboardCheck, Pin, PinOff, Trash2, Users, Bot, Tags, CalendarX2 } from \"lucide-react\";\nimport { Activity } from \"lucide-react\";\nimport { api, projectUrl } from \"@/lib/api-client\";\nimport { useTabStore } from \"@/stores/tab-store\";\nimport { useNotificationStore, notificationTint } from \"@/stores/notification-store\";\nimport { cn } from \"@/lib/utils\";\nimport { AISettingsSection } from \"@/components/settings/ai-settings-section\";\nimport { TagSettingsSection } from \"@/components/settings/tag-settings-section\";\nimport { SessionContextMenu } from \"./session-context-menu\";\nimport { UsageDetailPanel } from \"./usage-badge\";\nimport { TeamActivityPanel } from \"./team-activity-panel\";\nimport { ProviderBadge } from \"./provider-selector\";\nimport { formatRelativeDate } from \"@/lib/format-date\";\nimport { useDebouncedValue } from \"@/hooks/use-debounced-value\";\nimport type { SessionInfo, SessionListResponse, ProjectTag } from \"../../../types/chat\";\nimport type { UsageInfo } from \"../../../types/chat\";\nimport type { TeamMessageItem } from \"@/hooks/use-chat\";\n\ntype PanelType = \"history\" | \"config\" | \"usage\" | \"team\" | null;\n\ninterface TeamActivityState {\n hasTeams: boolean;\n teamNames: string[];\n messageCount: number;\n unreadCount: number;\n}\n\ninterface ChatHistoryBarProps {\n projectName: string;\n usageInfo: UsageInfo;\n usageLoading?: boolean;\n refreshUsage?: () => void;\n lastFetchedAt?: string | null;\n sessionId?: string | null;\n providerId?: string;\n onSelectSession?: (session: SessionInfo) => void;\n onBugReport?: () => void;\n isConnected?: boolean;\n onReload?: () => void;\n teamActivity?: TeamActivityState;\n teamMessages?: TeamMessageItem[];\n onTeamOpen?: () => void;\n}\n\nfunction relativeTime(iso: string): string {\n const secs = Math.round((Date.now() - new Date(iso).getTime()) / 1000);\n if (secs < 5) return \"now\";\n if (secs < 60) return `${secs}s`;\n const mins = Math.floor(secs / 60);\n if (mins < 60) return `${mins}m`;\n return `${Math.floor(mins / 60)}h`;\n}\n\nfunction pctColor(pct: number): string {\n if (pct >= 90) return \"text-red-500\";\n if (pct >= 70) return \"text-amber-500\";\n return \"text-green-500\";\n}\n\nfunction DebugCopyButton({ sessionId, projectName }: { sessionId: string; projectName: string }) {\n const [copied, setCopied] = useState(false);\n return (\n <button\n onClick={() => {\n try {\n // Use ClipboardItem with pending promise so Safari doesn't lose user gesture\n const textPromise = api.get<{ ppmSessionId: string; sdkSessionId: string; jsonlPath: string | null; projectPath: string }>(\n `${projectUrl(projectName)}/chat/sessions/${sessionId}/debug?project=${encodeURIComponent(projectName)}`,\n ).then((data) => {\n const info = [\n `PPM Session: ${data.ppmSessionId}`,\n `SDK Session: ${data.sdkSessionId}`,\n data.jsonlPath ? `JSONL: ${data.jsonlPath}` : `JSONL: not found`,\n data.projectPath ? `Project: ${data.projectPath}` : null,\n ].filter(Boolean).join(\"\\n\");\n return new Blob([info], { type: \"text/plain\" });\n });\n navigator.clipboard.write([new ClipboardItem({ \"text/plain\": textPromise })]).then(() => {\n setCopied(true);\n setTimeout(() => setCopied(false), 1500);\n });\n } catch { /* silent */ }\n }}\n className={`p-1 rounded transition-colors ${copied ? \"text-green-500 bg-green-500/10\" : \"text-text-subtle hover:text-text-secondary hover:bg-surface-elevated\"}`}\n title={copied ? \"Copied!\" : \"Copy session debug info\"}\n >\n {copied ? <ClipboardCheck className=\"size-3\" /> : <Bug className=\"size-3\" />}\n </button>\n );\n}\n\nexport function ChatHistoryBar({\n projectName, usageInfo, usageLoading, refreshUsage, lastFetchedAt,\n sessionId, providerId, onSelectSession, onBugReport, isConnected, onReload,\n teamActivity, teamMessages, onTeamOpen,\n}: ChatHistoryBarProps) {\n const [activePanel, setActivePanel] = useState<PanelType>(null);\n const [sessions, setSessions] = useState<SessionInfo[]>([]);\n const [loading, setLoading] = useState(false);\n const notifications = useNotificationStore((s) => s.notifications);\n const hasUnread = useNotificationStore((s) => sessionId ? s.notifications.has(sessionId) : false);\n const clearForSession = useNotificationStore((s) => s.clearForSession);\n const [searchQuery, setSearchQuery] = useState(\"\");\n const debouncedSearch = useDebouncedValue(searchQuery, 300);\n const [editingId, setEditingId] = useState<string | null>(null);\n const [editingTitle, setEditingTitle] = useState(\"\");\n const [hasMore, setHasMore] = useState(false);\n const [loadingMore, setLoadingMore] = useState(false);\n const [projectTags, setProjectTags] = useState<ProjectTag[]>([]);\n const [selectedTagId, setSelectedTagId] = useState<number | null>(null);\n const [tagCounts, setTagCounts] = useState<Record<number, number>>({});\n const [showTagSettings, setShowTagSettings] = useState(false);\n const editInputRef = useRef<HTMLInputElement>(null);\n const openTab = useTabStore((s) => s.openTab);\n const PAGE_SIZE = 50;\n\n const togglePanel = (panel: PanelType) => {\n setActivePanel((prev) => prev === panel ? null : panel);\n };\n\n const load = useCallback(async (query?: string) => {\n if (!projectName) return;\n setLoading(true);\n try {\n const params = new URLSearchParams({ limit: String(PAGE_SIZE), offset: \"0\" });\n if (query) params.set(\"q\", query);\n const data = await api.get<SessionListResponse>(`${projectUrl(projectName)}/chat/sessions?${params}`);\n setSessions(data.sessions);\n setHasMore(data.hasMore);\n } catch {\n // silent\n } finally {\n setLoading(false);\n }\n }, [projectName]);\n\n const loadMore = useCallback(async () => {\n if (!projectName || loadingMore || !hasMore) return;\n setLoadingMore(true);\n try {\n // Offset by count of non-pinned sessions (pinned are injected separately by backend)\n const unpinnedCount = sessions.filter((s) => !s.pinned).length;\n const params = new URLSearchParams({ limit: String(PAGE_SIZE), offset: String(unpinnedCount) });\n if (debouncedSearch) params.set(\"q\", debouncedSearch);\n const data = await api.get<SessionListResponse>(`${projectUrl(projectName)}/chat/sessions?${params}`);\n setSessions((prev) => {\n const existingIds = new Set(prev.map((s) => s.id));\n const newSessions = data.sessions.filter((s) => !existingIds.has(s.id));\n return [...prev, ...newSessions];\n });\n setHasMore(data.hasMore);\n } catch {\n // silent\n } finally {\n setLoadingMore(false);\n }\n }, [projectName, loadingMore, hasMore, sessions, debouncedSearch]);\n\n // Load sessions when history panel opens\n useEffect(() => {\n if (activePanel === \"history\" && sessions.length === 0) load();\n }, [activePanel]); // eslint-disable-line react-hooks/exhaustive-deps\n\n // Re-fetch when debounced search query changes (server-side search)\n useEffect(() => {\n if (activePanel === \"history\") load(debouncedSearch || undefined);\n }, [debouncedSearch]); // eslint-disable-line react-hooks/exhaustive-deps\n\n // Fetch tags\n const loadTags = useCallback(async () => {\n if (!projectName) return;\n try {\n const data = await api.get<{ tags: ProjectTag[]; counts: Record<number, number> }>(\n `${projectUrl(projectName)}/tags`,\n );\n setProjectTags(data.tags);\n setTagCounts(data.counts);\n } catch { /* silent */ }\n }, [projectName]);\n\n useEffect(() => {\n if (activePanel === \"history\" && projectName) loadTags();\n }, [activePanel, projectName, loadTags]);\n\n function openSession(session: SessionInfo) {\n if (onSelectSession) {\n onSelectSession(session);\n setActivePanel(null);\n } else {\n openTab({\n type: \"chat\",\n title: session.title || \"Chat\",\n projectId: projectName ?? null,\n metadata: { projectName, sessionId: session.id, providerId: session.providerId },\n closable: true,\n });\n }\n }\n\n const startEditing = useCallback((session: SessionInfo, e: React.MouseEvent) => {\n e.stopPropagation();\n setEditingId(session.id);\n setEditingTitle(session.title || \"\");\n setTimeout(() => editInputRef.current?.select(), 0);\n }, []);\n\n const saveTitle = useCallback(async () => {\n if (!editingId || !editingTitle.trim() || !projectName) {\n setEditingId(null);\n return;\n }\n try {\n await api.patch(`${projectUrl(projectName)}/chat/sessions/${editingId}`, { title: editingTitle.trim() });\n setSessions((prev) => prev.map((s) => s.id === editingId ? { ...s, title: editingTitle.trim() } : s));\n } catch { /* silent */ }\n setEditingId(null);\n }, [editingId, editingTitle, projectName]);\n\n const cancelEditing = useCallback(() => setEditingId(null), []);\n\n const togglePin = useCallback(async (e: React.MouseEvent, session: SessionInfo) => {\n e.stopPropagation();\n if (!projectName) return;\n const url = `${projectUrl(projectName)}/chat/sessions/${session.id}/pin`;\n try {\n if (session.pinned) {\n await api.del(url);\n } else {\n await api.put(url);\n }\n setSessions((prev) => {\n const updated = prev.map((s) => s.id === session.id ? { ...s, pinned: !s.pinned } : s);\n return updated.sort((a, b) => {\n if (a.pinned && !b.pinned) return -1;\n if (!a.pinned && b.pinned) return 1;\n return new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime();\n });\n });\n } catch { /* silent */ }\n }, [projectName]);\n\n const deleteSession = useCallback(async (e: React.MouseEvent, session: SessionInfo) => {\n e.stopPropagation();\n if (!projectName) return;\n if (!window.confirm(\"Delete this session? This cannot be undone.\")) return;\n try {\n await api.del(`${projectUrl(projectName)}/chat/sessions/${session.id}?providerId=${session.providerId}`);\n setSessions((prev) => prev.filter((s) => s.id !== session.id));\n } catch { /* silent */ }\n }, [projectName]);\n\n const handleTagChanged = useCallback((sid: string, tag: { id: number; name: string; color: string } | null) => {\n setSessions((prev) => prev.map((s) => s.id === sid ? { ...s, tag } : s));\n loadTags(); // Refetch counts from API for accuracy\n }, [loadTags]);\n\n const bulkDelete = useCallback(async () => {\n if (!projectName) return;\n const days = window.prompt(\"Delete sessions older than how many days? (pinned sessions are kept)\", \"30\");\n if (!days) return;\n const num = parseInt(days, 10);\n if (!num || num < 1) return;\n if (!window.confirm(`Delete all unpinned sessions older than ${num} days? This cannot be undone.`)) return;\n setLoading(true);\n try {\n await api.del(`${projectUrl(projectName)}/chat/sessions?olderThanDays=${num}`);\n load(debouncedSearch || undefined);\n } catch { /* silent */ }\n }, [projectName, load, debouncedSearch]);\n\n // Keyboard shortcuts: 1-9 to assign tags to current session\n useEffect(() => {\n if (activePanel !== \"history\") return;\n const handler = (e: KeyboardEvent) => {\n if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) return;\n const num = parseInt(e.key);\n if (num >= 1 && num <= projectTags.length && sessionId) {\n const tag = projectTags[num - 1];\n if (tag) {\n api.patch(`${projectUrl(projectName)}/chat/sessions/${sessionId}/tag`, { tagId: tag.id }).catch(() => {});\n handleTagChanged(sessionId, { id: tag.id, name: tag.name, color: tag.color });\n }\n }\n };\n window.addEventListener(\"keydown\", handler);\n return () => window.removeEventListener(\"keydown\", handler);\n }, [activePanel, projectTags, sessionId, projectName, handleTagChanged]);\n\n // Filter by tag client-side (search is now server-side via ?q=)\n const filteredSessions = selectedTagId !== null\n ? sessions.filter((s) => s.tag?.id === selectedTagId)\n : sessions;\n\n // Usage badge display — only meaningful for Claude (SDK) provider\n const isClaudeProvider = !providerId || providerId === \"claude\";\n const fiveHourPct = usageInfo.fiveHour != null ? Math.round(usageInfo.fiveHour * 100) : null;\n const sevenDayPct = usageInfo.sevenDay != null ? Math.round(usageInfo.sevenDay * 100) : null;\n const worstPct = Math.max(fiveHourPct ?? 0, sevenDayPct ?? 0);\n const usageColor = fiveHourPct != null || sevenDayPct != null ? pctColor(worstPct) : \"text-text-subtle\";\n\n return (\n <div className=\"border-b border-border/50\">\n {/* Toolbar row — all buttons on one line */}\n <div className=\"flex items-center gap-1 px-2 py-1\">\n {/* History */}\n <button\n onClick={() => togglePanel(\"history\")}\n className={`flex items-center gap-1 px-1.5 py-0.5 rounded text-[11px] transition-colors ${\n activePanel === \"history\" ? \"text-primary bg-primary/10\" : \"text-text-secondary hover:text-foreground hover:bg-surface-elevated\"\n }`}\n >\n <History className=\"size-3\" />\n <span>History</span>\n </button>\n\n {/* Active provider + AI Settings (combined) */}\n {sessionId && providerId && providerId !== \"mock\" ? (\n <button\n onClick={() => togglePanel(\"config\")}\n className={`flex items-center gap-1 px-1.5 py-0.5 rounded text-[11px] transition-colors ${\n activePanel === \"config\" ? \"text-primary bg-primary/10\" : \"text-text-secondary hover:text-foreground hover:bg-surface-elevated\"\n }`}\n title=\"AI Settings\"\n >\n <ProviderBadge providerId={providerId} />\n <span className=\"capitalize\">{providerId}</span>\n </button>\n ) : (\n <button\n onClick={() => togglePanel(\"config\")}\n className={`p-1 rounded transition-colors ${\n activePanel === \"config\" ? \"text-primary bg-primary/10\" : \"text-text-subtle hover:text-text-secondary hover:bg-surface-elevated\"\n }`}\n title=\"AI Settings\"\n >\n <Settings2 className=\"size-3\" />\n </button>\n )}\n\n {/* Usage & Accounts — full display for Claude, minimal for other providers */}\n {isClaudeProvider ? (\n <button\n onClick={() => togglePanel(\"usage\")}\n className={`flex items-center gap-1 px-1.5 py-0.5 rounded text-[11px] font-medium tabular-nums transition-colors hover:bg-surface-elevated ${\n activePanel === \"usage\" ? \"bg-primary/10\" : \"\"\n } ${usageColor}`}\n title=\"Usage limits\"\n >\n <Activity className=\"size-3\" />\n {usageInfo.activeAccountLabel && (\n <span className=\"text-text-secondary font-normal truncate max-w-[60px]\">[{usageInfo.activeAccountLabel}]</span>\n )}\n <span>5h:{fiveHourPct != null ? `${fiveHourPct}%` : \"--%\"}</span>\n <span className=\"text-text-subtle\">·</span>\n <span>Wk:{sevenDayPct != null ? `${sevenDayPct}%` : \"--%\"}</span>\n </button>\n ) : null}\n\n {/* Team activity */}\n {teamActivity?.hasTeams && (\n <button\n onClick={() => {\n togglePanel(\"team\");\n onTeamOpen?.();\n }}\n className={`relative flex items-center gap-1 px-1.5 py-0.5 rounded text-[11px] transition-colors ${\n activePanel === \"team\" ? \"text-primary bg-primary/10\" : \"text-text-secondary hover:text-foreground hover:bg-surface-elevated\"\n }`}\n title=\"Team activity\"\n >\n <Users className=\"size-3\" />\n <span>Team</span>\n {(teamActivity.unreadCount ?? 0) > 0 && (\n <span className=\"absolute -top-0.5 -right-0.5 size-2 bg-primary rounded-full animate-pulse\" />\n )}\n </button>\n )}\n\n {/* Spacer */}\n <div className=\"flex-1\" />\n\n {/* Mark as read */}\n {hasUnread && sessionId && (\n <button\n onClick={() => clearForSession(sessionId)}\n className=\"p-1 rounded text-amber-500 hover:text-amber-400 hover:bg-surface-elevated transition-colors\"\n title=\"Mark as read\"\n >\n <BellOff className=\"size-3\" />\n </button>\n )}\n\n {/* Debug info — copy session IDs + JSONL path */}\n {sessionId && (\n <DebugCopyButton sessionId={sessionId} projectName={projectName} />\n )}\n\n {/* Reload messages + connection status */}\n {onReload && (\n <button\n onClick={(e: MouseEvent<HTMLButtonElement>) => {\n const icon = e.currentTarget.querySelector(\"svg\");\n if (icon) {\n icon.classList.add(\"animate-spin\");\n setTimeout(() => icon.classList.remove(\"animate-spin\"), 600);\n }\n onReload();\n }}\n className=\"relative size-4 flex items-center justify-center\"\n title={isConnected ? \"Reload messages\" : \"Disconnected — click to reload\"}\n >\n <RefreshCw className={`size-3 ${isConnected ? \"text-muted-foreground/60\" : \"text-red-400\"}`} strokeWidth={2.5} />\n <span className={`absolute -top-0.5 -right-0.5 size-1.5 rounded-full ${isConnected ? \"bg-green-500\" : \"bg-red-500 animate-pulse\"}`} />\n </button>\n )}\n </div>\n\n {/* Panels — only one visible at a time */}\n\n {/* History panel */}\n {activePanel === \"history\" && (\n <div className=\"border-t border-border/30 bg-surface\">\n {/* Search + refresh */}\n <div className=\"flex items-center gap-1.5 px-2 py-1 border-b border-border/30\">\n <Search className=\"size-3 text-text-subtle shrink-0\" />\n <input\n type=\"text\"\n value={searchQuery}\n onChange={(e) => setSearchQuery(e.target.value)}\n placeholder=\"Search sessions...\"\n className=\"flex-1 bg-transparent text-[11px] text-text-primary outline-none placeholder:text-text-subtle\"\n />\n <button\n onClick={bulkDelete}\n className=\"p-0.5 rounded text-text-subtle hover:text-red-400 transition-colors\"\n title=\"Delete old sessions...\"\n >\n <CalendarX2 className=\"size-3\" />\n </button>\n <button\n onClick={() => load(debouncedSearch || undefined)}\n disabled={loading}\n className=\"p-0.5 rounded text-text-subtle hover:text-text-secondary transition-colors disabled:opacity-50\"\n title=\"Refresh\"\n >\n <RefreshCw className={`size-3 ${loading ? \"animate-spin\" : \"\"}`} />\n </button>\n </div>\n\n {/* Tag filter chips */}\n {projectTags.length > 0 && (\n <div className=\"flex items-center gap-1 px-2 py-1 overflow-x-auto border-b border-border/30 scrollbar-none\">\n <button\n onClick={() => setSelectedTagId(null)}\n className={`shrink-0 rounded-md border px-2 py-1 text-[10px] transition-colors ${\n selectedTagId === null ? \"bg-primary/20 border-primary text-primary\" : \"border-border bg-surface text-text-secondary\"\n }`}\n >All ({sessions.length})</button>\n {projectTags.map((tag) => (\n <button\n key={tag.id}\n onClick={() => setSelectedTagId(selectedTagId === tag.id ? null : tag.id)}\n className={`shrink-0 flex items-center gap-1 rounded-md border px-2 py-1 text-[10px] transition-colors ${\n selectedTagId === tag.id ? \"border-current\" : \"border-border bg-surface\"\n }`}\n style={selectedTagId === tag.id ? { backgroundColor: tag.color + \"20\", color: tag.color, borderColor: tag.color } : undefined}\n >\n <span className=\"size-2 rounded-full shrink-0\" style={{ backgroundColor: tag.color }} />\n {tag.name} ({tagCounts[tag.id] ?? 0})\n </button>\n ))}\n <button\n onClick={() => setShowTagSettings(!showTagSettings)}\n className={`shrink-0 p-1 rounded transition-colors ${showTagSettings ? \"text-primary bg-primary/10\" : \"text-text-subtle hover:text-text-secondary\"}`}\n title=\"Manage tags\"\n >\n <Tags className=\"size-3\" />\n </button>\n </div>\n )}\n\n {/* Tag management panel (inline) */}\n {showTagSettings && (\n <div className=\"border-b border-border/30 px-2 py-2 max-h-[180px] overflow-y-auto bg-surface-elevated/50\">\n <TagSettingsSection projectName={projectName} onTagsChanged={loadTags} />\n </div>\n )}\n\n <div className=\"max-h-[200px] overflow-y-auto\">\n {loading && sessions.length === 0 ? (\n <div className=\"flex items-center justify-center py-3\">\n <Loader2 className=\"size-3.5 animate-spin text-text-subtle\" />\n </div>\n ) : filteredSessions.length === 0 ? (\n <div className=\"flex items-center justify-center py-3 text-[11px] text-text-subtle\">\n {searchQuery ? \"No matching sessions\" : \"No sessions yet\"}\n </div>\n ) : (\n <>\n {filteredSessions.map((session) => {\n const notif = notifications.get(session.id);\n const isUnread = !!notif;\n return (\n <SessionContextMenu\n key={session.id}\n session={session}\n projectName={projectName}\n projectTags={projectTags}\n onTogglePin={togglePin}\n onStartEditing={startEditing}\n onDeleteSession={deleteSession}\n onTagChanged={handleTagChanged}\n >\n <div\n className={cn(\n \"flex items-center gap-2 w-full px-3 py-1.5 text-left hover:bg-surface-elevated transition-colors group\",\n isUnread && \"font-medium text-foreground\",\n isUnread && notificationTint(notif.type),\n !isUnread && \"text-text-secondary\",\n )}\n >\n <ProviderBadge providerId={session.providerId} />\n {session.tag && (\n <span className=\"size-2 rounded-full shrink-0\" style={{ backgroundColor: session.tag.color }} title={session.tag.name} />\n )}\n {editingId === session.id ? (\n <form\n className=\"flex items-center gap-1 flex-1 min-w-0\"\n onSubmit={(e) => { e.preventDefault(); saveTitle(); }}\n >\n <input\n ref={editInputRef}\n value={editingTitle}\n onChange={(e) => setEditingTitle(e.target.value)}\n onBlur={saveTitle}\n onKeyDown={(e) => { if (e.key === \"Escape\") cancelEditing(); }}\n className=\"flex-1 min-w-0 bg-surface-elevated text-[11px] text-text-primary px-1 py-0.5 rounded border border-border outline-none focus:border-primary\"\n autoFocus\n />\n <button type=\"submit\" className=\"p-0.5 text-green-500 hover:text-green-400\" onClick={(e) => e.stopPropagation()}>\n <Check className=\"size-3\" />\n </button>\n <button type=\"button\" className=\"p-0.5 text-text-subtle hover:text-text-secondary\" onClick={(e) => { e.stopPropagation(); cancelEditing(); }}>\n <X className=\"size-3\" />\n </button>\n </form>\n ) : (\n <>\n <button\n onClick={() => openSession(session)}\n className=\"text-[11px] truncate flex-1 text-left flex items-center gap-1\"\n >\n {session.title?.startsWith(\"[PPM]\") && (\n <Bot className=\"size-3 text-muted-foreground shrink-0\" />\n )}\n {session.title?.startsWith(\"[PPM]\")\n ? session.title.slice(7)\n : session.title || \"Untitled\"}\n </button>\n <button\n onClick={(e) => togglePin(e, session)}\n className={`p-0.5 rounded transition-all ${\n session.pinned\n ? \"text-primary hover:text-primary/70\"\n : \"text-text-subtle hover:text-text-secondary can-hover:opacity-0 can-hover:group-hover:opacity-100\"\n }`}\n title={session.pinned ? \"Unpin session\" : \"Pin session\"}\n >\n {session.pinned ? <PinOff className=\"size-3\" /> : <Pin className=\"size-3\" />}\n </button>\n <button\n onClick={(e) => startEditing(session, e)}\n className=\"p-0.5 rounded text-text-subtle hover:text-text-secondary can-hover:opacity-0 can-hover:group-hover:opacity-100 transition-opacity\"\n title=\"Rename session\"\n >\n <Pencil className=\"size-3\" />\n </button>\n <button\n onClick={(e) => deleteSession(e, session)}\n className=\"p-0.5 rounded text-text-subtle hover:text-red-400 hover:bg-red-500/20 can-hover:opacity-0 can-hover:group-hover:opacity-100 transition-opacity\"\n title=\"Delete session\"\n >\n <Trash2 className=\"size-3\" />\n </button>\n </>\n )}\n {editingId !== session.id && session.updatedAt && (\n <span className=\"text-[10px] text-text-subtle shrink-0 w-16 text-right\">{formatRelativeDate(session.updatedAt)}</span>\n )}\n </div>\n </SessionContextMenu>\n );\n })}\n {hasMore && (\n <button\n onClick={loadMore}\n disabled={loadingMore}\n className=\"flex items-center justify-center gap-1 w-full py-1.5 text-[11px] text-text-subtle hover:text-text-secondary hover:bg-surface-elevated transition-colors\"\n >\n {loadingMore ? <Loader2 className=\"size-3 animate-spin\" /> : null}\n {loadingMore ? \"Loading...\" : \"Load more\"}\n </button>\n )}\n </>\n )}\n </div>\n </div>\n )}\n\n {/* Config panel */}\n {activePanel === \"config\" && (\n <div className=\"border-t border-border/30 bg-surface px-3 py-2 max-h-[280px] overflow-y-auto\">\n <AISettingsSection compact />\n </div>\n )}\n\n {/* Team activity panel */}\n {activePanel === \"team\" && teamActivity?.hasTeams && (\n <div className=\"border-t border-border/30 bg-surface px-3 py-2 max-h-[280px] overflow-y-auto\">\n <TeamActivityPanel\n teamNames={teamActivity.teamNames}\n messages={teamMessages ?? []}\n />\n </div>\n )}\n\n {/* Usage panel — only for Claude provider */}\n {activePanel === \"usage\" && isClaudeProvider && (\n <UsageDetailPanel\n usage={usageInfo}\n visible={true}\n onClose={() => setActivePanel(null)}\n onReload={refreshUsage}\n loading={usageLoading}\n lastFetchedAt={lastFetchedAt}\n />\n )}\n\n </div>\n );\n}\n","import { useState, useCallback, useRef, useEffect } from \"react\";\nimport { Loader2, Upload, X } from \"lucide-react\";\nimport { api, projectUrl } from \"@/lib/api-client\";\nimport { useChat } from \"@/hooks/use-chat\";\nimport { useUsage } from \"@/hooks/use-usage\";\nimport { useTabStore } from \"@/stores/tab-store\";\nimport { useSettingsStore } from \"@/stores/settings-store\";\nimport { usePanelStore } from \"@/stores/panel-store\";\nimport { useNotificationStore } from \"@/stores/notification-store\";\nimport { openBugReportPopup } from \"@/lib/report-bug\";\nimport { getAISettings } from \"@/lib/api-settings\";\nimport { MessageList } from \"./message-list\";\nimport { MessageInput, type ChatAttachment, type MessagePriority } from \"./message-input\";\nimport { SlashCommandPicker, type SlashItem } from \"./slash-command-picker\";\nimport { FilePicker } from \"./file-picker\";\nimport { ChatHistoryBar } from \"./chat-history-bar\";\n\nimport type { DragEvent } from \"react\";\nimport type { FileNode } from \"../../../types/project\";\nimport type { Session, SessionInfo } from \"../../../types/chat\";\n\ninterface ChatTabProps {\n metadata?: Record<string, unknown>;\n tabId?: string;\n}\n\nexport function ChatTab({ metadata, tabId }: ChatTabProps) {\n const [sessionId, setSessionId] = useState<string | null>(\n (metadata?.sessionId as string) ?? null,\n );\n const [providerId, setProviderId] = useState<string>(\n (metadata?.providerId as string) ?? \"claude\",\n );\n\n // Slash picker state\n const [slashItems, setSlashItems] = useState<SlashItem[]>([]);\n const [showSlashPicker, setShowSlashPicker] = useState(false);\n const [slashFilter, setSlashFilter] = useState(\"\");\n const [slashSelected, setSlashSelected] = useState<SlashItem | null>(null);\n const [slashRecentNames, setSlashRecentNames] = useState<string[]>([]);\n\n // File picker state\n const [fileItems, setFileItems] = useState<FileNode[]>([]);\n const [showFilePicker, setShowFilePicker] = useState(false);\n const [fileFilter, setFileFilter] = useState(\"\");\n const [fileSelected, setFileSelected] = useState<FileNode | null>(null);\n\n // Permission mode — per-session sticky, falls back to global default\n const [permissionMode, setPermissionMode] = useState<string | undefined>(\n (metadata?.permissionMode as string) ?? undefined,\n );\n\n // Pending message to send after WS connects (replaces unreliable setTimeout)\n const pendingSendRef = useRef<{ content: string; permissionMode?: string } | null>(null);\n\n // Drag-and-drop state\n const [isDragging, setIsDragging] = useState(false);\n const [externalFiles, setExternalFiles] = useState<File[] | null>(null);\n const [externalPaths, setExternalPaths] = useState<string[] | null>(null);\n const [disambiguateItems, setDisambiguateItems] = useState<FileNode[] | null>(null);\n const dragCounterRef = useRef(0);\n\n // Use tab's own project, not global activeProject (keep-alive: hidden tabs must not react to switches)\n const projectName = (metadata?.projectName as string) ?? \"\";\n const updateTab = useTabStore((s) => s.updateTab);\n const version = useSettingsStore((s) => s.version);\n\n // Usage runs independently — auto-refreshes on interval\n const { usageInfo, usageLoading, lastFetchedAt, refreshUsage } =\n useUsage(projectName, providerId);\n\n // Load global default permission mode on mount (if no per-session override)\n useEffect(() => {\n if (permissionMode) return;\n getAISettings().then((s) => {\n const provider = s.providers[s.default_provider ?? \"claude\"];\n setPermissionMode(provider?.permission_mode ?? \"bypassPermissions\");\n }).catch(() => {});\n }, []); // eslint-disable-line react-hooks/exhaustive-deps\n\n // Persist sessionId, providerId, and permissionMode to tab metadata\n useEffect(() => {\n if (!tabId || !sessionId) return;\n updateTab(tabId, {\n metadata: { ...metadata, sessionId, providerId, permissionMode },\n });\n }, [sessionId, providerId, permissionMode]); // eslint-disable-line react-hooks/exhaustive-deps\n\n const {\n messages,\n renderedMessages,\n expandCompact,\n isCompactExpanded,\n messagesLoading,\n isStreaming,\n phase,\n isReconnecting,\n connectingElapsed,\n pendingApproval,\n contextWindowPct,\n compactStatus,\n statusMessage,\n sessionTitle,\n sendMessage,\n respondToApproval,\n cancelStreaming,\n reconnect,\n refetchMessages,\n isConnected,\n teamActivity,\n teamMessages,\n markTeamRead,\n bashPartialOutput,\n } = useChat(sessionId, providerId, projectName);\n\n // Flush pending message once WS connects (replaces unreliable setTimeout)\n useEffect(() => {\n if (isConnected && pendingSendRef.current) {\n const { content, permissionMode: pm } = pendingSendRef.current;\n pendingSendRef.current = null;\n sendMessage(content, { permissionMode: pm });\n }\n }, [isConnected, sendMessage]);\n\n // Auto-clear notification badge when this tab is active and document is visible.\n // Handles the case where notification arrived while browser tab was hidden.\n useEffect(() => {\n if (!sessionId || !tabId) return;\n const maybeClear = () => {\n if (document.hidden) return;\n const { panels, focusedPanelId } = usePanelStore.getState();\n const panel = panels[focusedPanelId];\n if (panel?.activeTabId === tabId) {\n useNotificationStore.getState().clearForSession(sessionId);\n }\n };\n maybeClear();\n document.addEventListener(\"visibilitychange\", maybeClear);\n const unsub = usePanelStore.subscribe(maybeClear);\n // Also auto-clear when notification store changes (cross-tab broadcast may add for active session)\n const unsub2 = useNotificationStore.subscribe(maybeClear);\n return () => {\n document.removeEventListener(\"visibilitychange\", maybeClear);\n unsub();\n unsub2();\n };\n }, [sessionId, tabId]);\n\n // Update tab title when SDK summary arrives\n useEffect(() => {\n if (tabId && sessionTitle) {\n updateTab(tabId, { title: sessionTitle });\n }\n }, [sessionTitle]); // eslint-disable-line react-hooks/exhaustive-deps\n\n // Pending fork message — show in input for user to edit, not auto-send\n const [forkDraft, setForkDraft] = useState<string | undefined>(metadata?.pendingMessage as string | undefined);\n useEffect(() => {\n if (forkDraft && isConnected && sessionId && tabId) {\n // Clear from tab metadata once consumed\n updateTab(tabId, { metadata: { ...metadata, pendingMessage: undefined } });\n }\n }, [isConnected, sessionId]); // eslint-disable-line react-hooks/exhaustive-deps\n\n const handleNewSession = useCallback(() => {\n useTabStore.getState().openTab({\n type: \"chat\",\n title: \"AI Chat\",\n metadata: { projectName, providerId },\n projectId: projectName || null,\n closable: true,\n });\n }, [projectName, providerId]);\n\n const handleSelectSession = useCallback((session: SessionInfo) => {\n setSessionId(session.id);\n setProviderId(session.providerId);\n if (tabId) updateTab(tabId, { title: session.title || \"Chat\" });\n }, [tabId, updateTab]);\n\n /** Fork current session and open new tab with the forked session, resending userMessage */\n const handleFork = useCallback(async (userMessage: string, messageId?: string) => {\n if (!sessionId || !projectName) return;\n try {\n const { api, projectUrl } = await import(\"@/lib/api-client\");\n const forked = await api.post<{ id: string; forkedFrom: string }>(\n `${projectUrl(projectName)}/chat/sessions/${sessionId}/fork?providerId=${providerId}`,\n { messageId },\n );\n // Open new chat tab with forked session — it will send userMessage on connect\n useTabStore.getState().openTab({\n type: \"chat\",\n title: `Fork: ${userMessage.slice(0, 30)}`,\n metadata: { projectName, sessionId: forked.id, providerId, pendingMessage: userMessage },\n projectId: projectName || null,\n closable: true,\n });\n } catch (e) {\n console.error(\"Fork failed:\", e);\n }\n }, [sessionId, projectName, providerId]);\n\n /** Build message content with file references and inline text snippets prepended */\n const buildMessageWithAttachments = useCallback(\n (content: string, attachments: ChatAttachment[]): string => {\n if (attachments.length === 0) return content;\n\n const parts: string[] = [];\n\n // Inline text snippets (e.g. terminal output)\n for (const att of attachments) {\n if (att.textContent) parts.push(att.textContent);\n }\n\n // Server-uploaded file references\n const fileAtts = attachments.filter((a) => a.serverPath);\n if (fileAtts.length > 0) {\n const fileRefs = fileAtts.map((a) => a.serverPath!).join(\"\\n\");\n parts.push(\n fileAtts.length === 1\n ? `[Attached file: ${fileRefs}]`\n : `[Attached files:\\n${fileRefs}\\n]`,\n );\n }\n\n if (parts.length === 0) return content;\n return parts.join(\"\\n\\n\") + \"\\n\\n\" + content;\n },\n [],\n );\n\n const handleSend = useCallback(\n async (content: string, attachments: ChatAttachment[] = [], priority?: MessagePriority) => {\n const fullContent = buildMessageWithAttachments(content, attachments);\n if (!fullContent.trim()) return;\n\n if (!sessionId) {\n try {\n const pName = projectName;\n const session = await api.post<Session>(`${projectUrl(pName)}/chat/sessions`, {\n providerId,\n title: content.slice(0, 50),\n });\n setSessionId(session.id);\n setProviderId(session.providerId);\n // Queue message — will be sent by effect when WS reports isConnected\n pendingSendRef.current = { content: fullContent, permissionMode };\n return;\n } catch (e) {\n console.error(\"Failed to create session:\", e);\n return;\n }\n }\n sendMessage(fullContent, { permissionMode, priority });\n },\n [sessionId, providerId, projectName, sendMessage, buildMessageWithAttachments, permissionMode],\n );\n\n /** Stable wrapper for MessageInput onSend — clears forkDraft and delegates to handleSend */\n const handleInputSend = useCallback(\n (content: string, attachments: ChatAttachment[], priority?: MessagePriority) => {\n setForkDraft(undefined);\n handleSend(content, attachments, priority);\n },\n [handleSend],\n );\n\n /** Stable callback for slash items loaded — prevents MessageInput memo break */\n const handleSlashItemsLoaded = useCallback(\n (items: SlashItem[], recentNames?: string[]) => {\n setSlashItems(items);\n if (recentNames) setSlashRecentNames(recentNames);\n },\n [],\n );\n\n // --- Slash picker handlers ---\n const handleSlashStateChange = useCallback((visible: boolean, filter: string) => {\n setShowSlashPicker(visible);\n setSlashFilter(filter);\n }, []);\n\n const handleSlashSelect = useCallback((item: SlashItem) => {\n setSlashSelected(item);\n setShowSlashPicker(false);\n setSlashFilter(\"\");\n setTimeout(() => setSlashSelected(null), 50);\n // Record usage for recents (fire-and-forget)\n if (projectName) {\n api.post(`${projectUrl(projectName)}/chat/slash-recents`, { name: item.name, type: item.type }).catch(() => {});\n // Optimistic update: add to front of recents\n setSlashRecentNames((prev) => [item.name, ...prev.filter((n) => n !== item.name)].slice(0, 5));\n }\n }, [projectName]);\n\n const handleSlashClose = useCallback(() => {\n setShowSlashPicker(false);\n setSlashFilter(\"\");\n }, []);\n\n // Stable callback: clear external paths once consumed (avoids inline lambda breaking MessageInput memo)\n const handleExternalPathsConsumed = useCallback(() => setExternalPaths(null), []);\n\n // --- Disambiguation picker handler (OS drag resolve with multiple matches) ---\n const handleDisambiguate = useCallback((matches: FileNode[]) => {\n setDisambiguateItems(matches);\n }, []);\n\n const handleDisambiguateSelect = useCallback((item: FileNode) => {\n setExternalPaths([item.path]);\n setDisambiguateItems(null);\n }, []);\n\n // --- File picker handlers ---\n const handleFileStateChange = useCallback((visible: boolean, filter: string) => {\n setShowFilePicker(visible);\n setFileFilter(filter);\n }, []);\n\n const handleFileSelect = useCallback((item: FileNode) => {\n setFileSelected(item);\n setShowFilePicker(false);\n setFileFilter(\"\");\n setTimeout(() => setFileSelected(null), 50);\n }, []);\n\n const handleFileClose = useCallback(() => {\n setShowFilePicker(false);\n setFileFilter(\"\");\n }, []);\n\n // --- Drag-and-drop on entire chat area ---\n const handleDragEnter = useCallback((e: DragEvent) => {\n e.preventDefault();\n dragCounterRef.current++;\n if (e.dataTransfer.types.includes(\"application/x-ppm-path\") || e.dataTransfer.types.includes(\"Files\")) {\n setIsDragging(true);\n }\n }, []);\n\n const handleDragLeave = useCallback((e: DragEvent) => {\n e.preventDefault();\n dragCounterRef.current--;\n if (dragCounterRef.current === 0) {\n setIsDragging(false);\n }\n }, []);\n\n const handleDragOver = useCallback((e: DragEvent) => {\n e.preventDefault();\n }, []);\n\n const handleDrop = useCallback((e: DragEvent) => {\n e.preventDefault();\n dragCounterRef.current = 0;\n setIsDragging(false);\n\n // Check for internal file tree drag (custom MIME) first\n const ppmPath = e.dataTransfer.getData(\"application/x-ppm-path\");\n if (ppmPath) {\n setExternalPaths([ppmPath]);\n return;\n }\n\n const files = Array.from(e.dataTransfer.files);\n if (files.length > 0) {\n setExternalFiles(files);\n // Reset after a tick so the effect fires even with same files\n setTimeout(() => setExternalFiles(null), 100);\n }\n }, []);\n\n return (\n <div\n className=\"flex flex-col h-full relative\"\n onDragEnter={handleDragEnter}\n onDragLeave={handleDragLeave}\n onDragOver={handleDragOver}\n onDrop={handleDrop}\n >\n {/* Drag overlay */}\n {isDragging && (\n <div className=\"absolute inset-0 z-50 flex items-center justify-center bg-background/80 backdrop-blur-sm border-2 border-dashed border-primary rounded-lg pointer-events-none\">\n <div className=\"flex flex-col items-center gap-2 text-primary\">\n <Upload className=\"size-8\" />\n <span className=\"text-sm font-medium\">Drop files to attach</span>\n </div>\n </div>\n )}\n\n {/* Reconnect overlay */}\n {isReconnecting && (\n <div className=\"absolute inset-0 z-50 flex items-center justify-center bg-background/60 backdrop-blur-sm\">\n <div className=\"flex items-center gap-2 text-sm text-muted-foreground\">\n <Loader2 className=\"size-4 animate-spin\" />\n <span>Reconnecting...</span>\n </div>\n </div>\n )}\n\n {/* Messages */}\n <MessageList\n messages={renderedMessages}\n onExpandCompact={expandCompact}\n isCompactExpanded={isCompactExpanded}\n messagesLoading={messagesLoading}\n pendingApproval={pendingApproval}\n onApprovalResponse={respondToApproval}\n isStreaming={isStreaming}\n phase={phase}\n connectingElapsed={connectingElapsed}\n statusMessage={statusMessage}\n compactStatus={compactStatus}\n projectName={projectName}\n onFork={!isStreaming ? handleFork : undefined}\n onSelectSession={handleSelectSession}\n bashPartialOutput={bashPartialOutput}\n />\n\n {/* Bottom toolbar */}\n <div className=\"border-t border-border bg-background shrink-0\">\n {/* Unified toolbar: History, Config, Usage, Bug report, Connection */}\n <ChatHistoryBar\n projectName={projectName}\n usageInfo={usageInfo}\n usageLoading={usageLoading}\n refreshUsage={refreshUsage}\n lastFetchedAt={lastFetchedAt}\n sessionId={sessionId}\n providerId={providerId}\n onSelectSession={handleSelectSession}\n onBugReport={sessionId ? () => openBugReportPopup(version, { sessionId, projectName }) : undefined}\n isConnected={isConnected}\n onReload={() => {\n if (!isConnected) reconnect();\n refetchMessages();\n }}\n teamActivity={teamActivity}\n teamMessages={teamMessages}\n onTeamOpen={markTeamRead}\n />\n\n {/* Pickers (in-flow, above input — only one visible at a time) */}\n <SlashCommandPicker\n items={slashItems}\n filter={slashFilter}\n onSelect={handleSlashSelect}\n onClose={handleSlashClose}\n visible={showSlashPicker}\n recentNames={slashRecentNames}\n projectName={projectName}\n />\n <FilePicker\n items={fileItems}\n filter={fileFilter}\n onSelect={handleFileSelect}\n onClose={handleFileClose}\n visible={showFilePicker}\n />\n {disambiguateItems && (\n <FilePicker\n items={disambiguateItems}\n filter=\"\"\n onSelect={handleDisambiguateSelect}\n onClose={() => setDisambiguateItems(null)}\n visible={true}\n />\n )}\n\n {/* Input */}\n <MessageInput\n onSend={handleInputSend}\n isStreaming={isStreaming}\n onCancel={cancelStreaming}\n autoFocus={!(metadata?.sessionId) || !!forkDraft}\n initialValue={forkDraft}\n projectName={projectName}\n onSlashStateChange={handleSlashStateChange}\n onSlashItemsLoaded={handleSlashItemsLoaded}\n slashSelected={slashSelected}\n onFileStateChange={handleFileStateChange}\n onFileItemsLoaded={setFileItems}\n fileSelected={fileSelected}\n externalFiles={externalFiles}\n externalPaths={externalPaths}\n onExternalPathsConsumed={handleExternalPathsConsumed}\n onDisambiguate={handleDisambiguate}\n permissionMode={permissionMode}\n onModeChange={setPermissionMode}\n providerId={providerId}\n onProviderChange={!sessionId ? setProviderId : undefined}\n />\n </div>\n\n {/* Bug report popup is now global — see BugReportPopup in app.tsx */}\n </div>\n );\n}\n"],"file":"chat-tab-DbuBr2ax.js"}
1
+ {"version":3,"mappings":"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","names":["useLayoutEffect","useEffect"],"ignoreList":[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,26,27],"sources":["../../../node_modules/.bun/lucide-react@0.577.0+b1ab299f0a400331/node_modules/lucide-react/dist/esm/icons/activity.js","../../../node_modules/.bun/lucide-react@0.577.0+b1ab299f0a400331/node_modules/lucide-react/dist/esm/icons/calendar-x-2.js","../../../node_modules/.bun/lucide-react@0.577.0+b1ab299f0a400331/node_modules/lucide-react/dist/esm/icons/circle-x.js","../../../node_modules/.bun/lucide-react@0.577.0+b1ab299f0a400331/node_modules/lucide-react/dist/esm/icons/clipboard-check.js","../../../node_modules/.bun/lucide-react@0.577.0+b1ab299f0a400331/node_modules/lucide-react/dist/esm/icons/clipboard-list.js","../../../node_modules/.bun/lucide-react@0.577.0+b1ab299f0a400331/node_modules/lucide-react/dist/esm/icons/hand.js","../../../node_modules/.bun/lucide-react@0.577.0+b1ab299f0a400331/node_modules/lucide-react/dist/esm/icons/history.js","../../../node_modules/.bun/lucide-react@0.577.0+b1ab299f0a400331/node_modules/lucide-react/dist/esm/icons/image.js","../../../node_modules/.bun/lucide-react@0.577.0+b1ab299f0a400331/node_modules/lucide-react/dist/esm/icons/list-ordered.js","../../../node_modules/.bun/lucide-react@0.577.0+b1ab299f0a400331/node_modules/lucide-react/dist/esm/icons/list-todo.js","../../../node_modules/.bun/lucide-react@0.577.0+b1ab299f0a400331/node_modules/lucide-react/dist/esm/icons/maximize-2.js","../../../node_modules/.bun/lucide-react@0.577.0+b1ab299f0a400331/node_modules/lucide-react/dist/esm/icons/mic-off.js","../../../node_modules/.bun/lucide-react@0.577.0+b1ab299f0a400331/node_modules/lucide-react/dist/esm/icons/minimize-2.js","../../../node_modules/.bun/lucide-react@0.577.0+b1ab299f0a400331/node_modules/lucide-react/dist/esm/icons/paperclip.js","../../../node_modules/.bun/lucide-react@0.577.0+b1ab299f0a400331/node_modules/lucide-react/dist/esm/icons/shield-alert.js","../../../node_modules/.bun/lucide-react@0.577.0+b1ab299f0a400331/node_modules/lucide-react/dist/esm/icons/shield-off.js","../../../node_modules/.bun/lucide-react@0.577.0+b1ab299f0a400331/node_modules/lucide-react/dist/esm/icons/slash.js","../../../node_modules/.bun/lucide-react@0.577.0+b1ab299f0a400331/node_modules/lucide-react/dist/esm/icons/square-terminal.js","../../../node_modules/.bun/lucide-react@0.577.0+b1ab299f0a400331/node_modules/lucide-react/dist/esm/icons/tags.js","../../../node_modules/.bun/lucide-react@0.577.0+b1ab299f0a400331/node_modules/lucide-react/dist/esm/icons/users.js","../../../node_modules/.bun/lucide-react@0.577.0+b1ab299f0a400331/node_modules/lucide-react/dist/esm/icons/zap.js","../../../src/web/hooks/use-websocket.ts","../../../src/web/lib/flatten-expansions.ts","../../../src/web/lib/notification-sounds.ts","../../../src/web/hooks/use-chat.ts","../../../src/web/hooks/use-usage.ts","../../../node_modules/.bun/use-stick-to-bottom@1.1.3+b1ab299f0a400331/node_modules/use-stick-to-bottom/dist/useStickToBottom.js","../../../node_modules/.bun/use-stick-to-bottom@1.1.3+b1ab299f0a400331/node_modules/use-stick-to-bottom/dist/StickToBottom.js","../../../src/web/components/chat/tool-cards.tsx","../../../src/web/components/chat/pre-compact-button.tsx","../../../src/web/components/shared/markdown-error-boundary.tsx","../../../src/web/components/chat/chat-welcome.tsx","../../../src/web/components/chat/question-card.tsx","../../../src/web/components/chat/message-list.tsx","../../../src/web/hooks/use-voice-input.ts","../../../src/web/lib/file-support.ts","../../../src/web/components/chat/attachment-chips.tsx","../../../src/web/components/chat/mode-selector.tsx","../../../src/web/components/chat/message-input.tsx","../../../src/shared/fuzzy-search.ts","../../../src/web/components/chat/slash-command-picker.tsx","../../../src/web/components/chat/file-picker.tsx","../../../src/web/components/settings/tag-settings-section.tsx","../../../src/web/components/chat/account-dialogs.tsx","../../../src/web/components/chat/account-rotation-settings.tsx","../../../src/web/components/chat/usage-pattern-chart.tsx","../../../src/web/components/chat/usage-badge.tsx","../../../src/web/components/chat/team-activity-panel.tsx","../../../src/web/components/chat/chat-history-bar.tsx","../../../src/web/components/chat/chat-tab.tsx"],"sourcesContent":["/**\n * @license lucide-react v0.577.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.js';\n\nconst __iconNode = [\n [\n \"path\",\n {\n d: \"M22 12h-2.48a2 2 0 0 0-1.93 1.46l-2.35 8.36a.25.25 0 0 1-.48 0L9.24 2.18a.25.25 0 0 0-.48 0l-2.35 8.36A2 2 0 0 1 4.49 12H2\",\n key: \"169zse\"\n }\n ]\n];\nconst Activity = createLucideIcon(\"activity\", __iconNode);\n\nexport { __iconNode, Activity as default };\n//# sourceMappingURL=activity.js.map\n","/**\n * @license lucide-react v0.577.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.js';\n\nconst __iconNode = [\n [\"path\", { d: \"M8 2v4\", key: \"1cmpym\" }],\n [\"path\", { d: \"M16 2v4\", key: \"4m81vk\" }],\n [\"path\", { d: \"M21 13V6a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h8\", key: \"3spt84\" }],\n [\"path\", { d: \"M3 10h18\", key: \"8toen8\" }],\n [\"path\", { d: \"m17 22 5-5\", key: \"1k6ppv\" }],\n [\"path\", { d: \"m17 17 5 5\", key: \"p7ous7\" }]\n];\nconst CalendarX2 = createLucideIcon(\"calendar-x-2\", __iconNode);\n\nexport { __iconNode, CalendarX2 as default };\n//# sourceMappingURL=calendar-x-2.js.map\n","/**\n * @license lucide-react v0.577.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.js';\n\nconst __iconNode = [\n [\"circle\", { cx: \"12\", cy: \"12\", r: \"10\", key: \"1mglay\" }],\n [\"path\", { d: \"m15 9-6 6\", key: \"1uzhvr\" }],\n [\"path\", { d: \"m9 9 6 6\", key: \"z0biqf\" }]\n];\nconst CircleX = createLucideIcon(\"circle-x\", __iconNode);\n\nexport { __iconNode, CircleX as default };\n//# sourceMappingURL=circle-x.js.map\n","/**\n * @license lucide-react v0.577.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.js';\n\nconst __iconNode = [\n [\"rect\", { width: \"8\", height: \"4\", x: \"8\", y: \"2\", rx: \"1\", ry: \"1\", key: \"tgr4d6\" }],\n [\n \"path\",\n {\n d: \"M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2\",\n key: \"116196\"\n }\n ],\n [\"path\", { d: \"m9 14 2 2 4-4\", key: \"df797q\" }]\n];\nconst ClipboardCheck = createLucideIcon(\"clipboard-check\", __iconNode);\n\nexport { __iconNode, ClipboardCheck as default };\n//# sourceMappingURL=clipboard-check.js.map\n","/**\n * @license lucide-react v0.577.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.js';\n\nconst __iconNode = [\n [\"rect\", { width: \"8\", height: \"4\", x: \"8\", y: \"2\", rx: \"1\", ry: \"1\", key: \"tgr4d6\" }],\n [\n \"path\",\n {\n d: \"M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2\",\n key: \"116196\"\n }\n ],\n [\"path\", { d: \"M12 11h4\", key: \"1jrz19\" }],\n [\"path\", { d: \"M12 16h4\", key: \"n85exb\" }],\n [\"path\", { d: \"M8 11h.01\", key: \"1dfujw\" }],\n [\"path\", { d: \"M8 16h.01\", key: \"18s6g9\" }]\n];\nconst ClipboardList = createLucideIcon(\"clipboard-list\", __iconNode);\n\nexport { __iconNode, ClipboardList as default };\n//# sourceMappingURL=clipboard-list.js.map\n","/**\n * @license lucide-react v0.577.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.js';\n\nconst __iconNode = [\n [\"path\", { d: \"M18 11V6a2 2 0 0 0-2-2a2 2 0 0 0-2 2\", key: \"1fvzgz\" }],\n [\"path\", { d: \"M14 10V4a2 2 0 0 0-2-2a2 2 0 0 0-2 2v2\", key: \"1kc0my\" }],\n [\"path\", { d: \"M10 10.5V6a2 2 0 0 0-2-2a2 2 0 0 0-2 2v8\", key: \"10h0bg\" }],\n [\n \"path\",\n {\n d: \"M18 8a2 2 0 1 1 4 0v6a8 8 0 0 1-8 8h-2c-2.8 0-4.5-.86-5.99-2.34l-3.6-3.6a2 2 0 0 1 2.83-2.82L7 15\",\n key: \"1s1gnw\"\n }\n ]\n];\nconst Hand = createLucideIcon(\"hand\", __iconNode);\n\nexport { __iconNode, Hand as default };\n//# sourceMappingURL=hand.js.map\n","/**\n * @license lucide-react v0.577.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.js';\n\nconst __iconNode = [\n [\"path\", { d: \"M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8\", key: \"1357e3\" }],\n [\"path\", { d: \"M3 3v5h5\", key: \"1xhq8a\" }],\n [\"path\", { d: \"M12 7v5l4 2\", key: \"1fdv2h\" }]\n];\nconst History = createLucideIcon(\"history\", __iconNode);\n\nexport { __iconNode, History as default };\n//# sourceMappingURL=history.js.map\n","/**\n * @license lucide-react v0.577.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.js';\n\nconst __iconNode = [\n [\"rect\", { width: \"18\", height: \"18\", x: \"3\", y: \"3\", rx: \"2\", ry: \"2\", key: \"1m3agn\" }],\n [\"circle\", { cx: \"9\", cy: \"9\", r: \"2\", key: \"af1f0g\" }],\n [\"path\", { d: \"m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21\", key: \"1xmnt7\" }]\n];\nconst Image = createLucideIcon(\"image\", __iconNode);\n\nexport { __iconNode, Image as default };\n//# sourceMappingURL=image.js.map\n","/**\n * @license lucide-react v0.577.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.js';\n\nconst __iconNode = [\n [\"path\", { d: \"M11 5h10\", key: \"1cz7ny\" }],\n [\"path\", { d: \"M11 12h10\", key: \"1438ji\" }],\n [\"path\", { d: \"M11 19h10\", key: \"11t30w\" }],\n [\"path\", { d: \"M4 4h1v5\", key: \"10yrso\" }],\n [\"path\", { d: \"M4 9h2\", key: \"r1h2o0\" }],\n [\"path\", { d: \"M6.5 20H3.4c0-1 2.6-1.925 2.6-3.5a1.5 1.5 0 0 0-2.6-1.02\", key: \"xtkcd5\" }]\n];\nconst ListOrdered = createLucideIcon(\"list-ordered\", __iconNode);\n\nexport { __iconNode, ListOrdered as default };\n//# sourceMappingURL=list-ordered.js.map\n","/**\n * @license lucide-react v0.577.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.js';\n\nconst __iconNode = [\n [\"path\", { d: \"M13 5h8\", key: \"a7qcls\" }],\n [\"path\", { d: \"M13 12h8\", key: \"h98zly\" }],\n [\"path\", { d: \"M13 19h8\", key: \"c3s6r1\" }],\n [\"path\", { d: \"m3 17 2 2 4-4\", key: \"1jhpwq\" }],\n [\"rect\", { x: \"3\", y: \"4\", width: \"6\", height: \"6\", rx: \"1\", key: \"cif1o7\" }]\n];\nconst ListTodo = createLucideIcon(\"list-todo\", __iconNode);\n\nexport { __iconNode, ListTodo as default };\n//# sourceMappingURL=list-todo.js.map\n","/**\n * @license lucide-react v0.577.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.js';\n\nconst __iconNode = [\n [\"path\", { d: \"M15 3h6v6\", key: \"1q9fwt\" }],\n [\"path\", { d: \"m21 3-7 7\", key: \"1l2asr\" }],\n [\"path\", { d: \"m3 21 7-7\", key: \"tjx5ai\" }],\n [\"path\", { d: \"M9 21H3v-6\", key: \"wtvkvv\" }]\n];\nconst Maximize2 = createLucideIcon(\"maximize-2\", __iconNode);\n\nexport { __iconNode, Maximize2 as default };\n//# sourceMappingURL=maximize-2.js.map\n","/**\n * @license lucide-react v0.577.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.js';\n\nconst __iconNode = [\n [\"path\", { d: \"M12 19v3\", key: \"npa21l\" }],\n [\"path\", { d: \"M15 9.34V5a3 3 0 0 0-5.68-1.33\", key: \"1gzdoj\" }],\n [\"path\", { d: \"M16.95 16.95A7 7 0 0 1 5 12v-2\", key: \"cqa7eg\" }],\n [\"path\", { d: \"M18.89 13.23A7 7 0 0 0 19 12v-2\", key: \"16hl24\" }],\n [\"path\", { d: \"m2 2 20 20\", key: \"1ooewy\" }],\n [\"path\", { d: \"M9 9v3a3 3 0 0 0 5.12 2.12\", key: \"r2i35w\" }]\n];\nconst MicOff = createLucideIcon(\"mic-off\", __iconNode);\n\nexport { __iconNode, MicOff as default };\n//# sourceMappingURL=mic-off.js.map\n","/**\n * @license lucide-react v0.577.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.js';\n\nconst __iconNode = [\n [\"path\", { d: \"m14 10 7-7\", key: \"oa77jy\" }],\n [\"path\", { d: \"M20 10h-6V4\", key: \"mjg0md\" }],\n [\"path\", { d: \"m3 21 7-7\", key: \"tjx5ai\" }],\n [\"path\", { d: \"M4 14h6v6\", key: \"rmj7iw\" }]\n];\nconst Minimize2 = createLucideIcon(\"minimize-2\", __iconNode);\n\nexport { __iconNode, Minimize2 as default };\n//# sourceMappingURL=minimize-2.js.map\n","/**\n * @license lucide-react v0.577.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.js';\n\nconst __iconNode = [\n [\n \"path\",\n {\n d: \"m16 6-8.414 8.586a2 2 0 0 0 2.829 2.829l8.414-8.586a4 4 0 1 0-5.657-5.657l-8.379 8.551a6 6 0 1 0 8.485 8.485l8.379-8.551\",\n key: \"1miecu\"\n }\n ]\n];\nconst Paperclip = createLucideIcon(\"paperclip\", __iconNode);\n\nexport { __iconNode, Paperclip as default };\n//# sourceMappingURL=paperclip.js.map\n","/**\n * @license lucide-react v0.577.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.js';\n\nconst __iconNode = [\n [\n \"path\",\n {\n d: \"M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z\",\n key: \"oel41y\"\n }\n ],\n [\"path\", { d: \"M12 8v4\", key: \"1got3b\" }],\n [\"path\", { d: \"M12 16h.01\", key: \"1drbdi\" }]\n];\nconst ShieldAlert = createLucideIcon(\"shield-alert\", __iconNode);\n\nexport { __iconNode, ShieldAlert as default };\n//# sourceMappingURL=shield-alert.js.map\n","/**\n * @license lucide-react v0.577.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.js';\n\nconst __iconNode = [\n [\"path\", { d: \"m2 2 20 20\", key: \"1ooewy\" }],\n [\n \"path\",\n {\n d: \"M5 5a1 1 0 0 0-1 1v7c0 5 3.5 7.5 7.67 8.94a1 1 0 0 0 .67.01c2.35-.82 4.48-1.97 5.9-3.71\",\n key: \"1jlk70\"\n }\n ],\n [\n \"path\",\n {\n d: \"M9.309 3.652A12.252 12.252 0 0 0 11.24 2.28a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1v7a9.784 9.784 0 0 1-.08 1.264\",\n key: \"18rp1v\"\n }\n ]\n];\nconst ShieldOff = createLucideIcon(\"shield-off\", __iconNode);\n\nexport { __iconNode, ShieldOff as default };\n//# sourceMappingURL=shield-off.js.map\n","/**\n * @license lucide-react v0.577.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.js';\n\nconst __iconNode = [[\"path\", { d: \"M22 2 2 22\", key: \"y4kqgn\" }]];\nconst Slash = createLucideIcon(\"slash\", __iconNode);\n\nexport { __iconNode, Slash as default };\n//# sourceMappingURL=slash.js.map\n","/**\n * @license lucide-react v0.577.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.js';\n\nconst __iconNode = [\n [\"path\", { d: \"m7 11 2-2-2-2\", key: \"1lz0vl\" }],\n [\"path\", { d: \"M11 13h4\", key: \"1p7l4v\" }],\n [\"rect\", { width: \"18\", height: \"18\", x: \"3\", y: \"3\", rx: \"2\", ry: \"2\", key: \"1m3agn\" }]\n];\nconst SquareTerminal = createLucideIcon(\"square-terminal\", __iconNode);\n\nexport { __iconNode, SquareTerminal as default };\n//# sourceMappingURL=square-terminal.js.map\n","/**\n * @license lucide-react v0.577.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.js';\n\nconst __iconNode = [\n [\n \"path\",\n {\n d: \"M13.172 2a2 2 0 0 1 1.414.586l6.71 6.71a2.4 2.4 0 0 1 0 3.408l-4.592 4.592a2.4 2.4 0 0 1-3.408 0l-6.71-6.71A2 2 0 0 1 6 9.172V3a1 1 0 0 1 1-1z\",\n key: \"16rjxf\"\n }\n ],\n [\n \"path\",\n { d: \"M2 7v6.172a2 2 0 0 0 .586 1.414l6.71 6.71a2.4 2.4 0 0 0 3.191.193\", key: \"178nd4\" }\n ],\n [\"circle\", { cx: \"10.5\", cy: \"6.5\", r: \".5\", fill: \"currentColor\", key: \"12ikhr\" }]\n];\nconst Tags = createLucideIcon(\"tags\", __iconNode);\n\nexport { __iconNode, Tags as default };\n//# sourceMappingURL=tags.js.map\n","/**\n * @license lucide-react v0.577.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.js';\n\nconst __iconNode = [\n [\"path\", { d: \"M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2\", key: \"1yyitq\" }],\n [\"path\", { d: \"M16 3.128a4 4 0 0 1 0 7.744\", key: \"16gr8j\" }],\n [\"path\", { d: \"M22 21v-2a4 4 0 0 0-3-3.87\", key: \"kshegd\" }],\n [\"circle\", { cx: \"9\", cy: \"7\", r: \"4\", key: \"nufk8\" }]\n];\nconst Users = createLucideIcon(\"users\", __iconNode);\n\nexport { __iconNode, Users as default };\n//# sourceMappingURL=users.js.map\n","/**\n * @license lucide-react v0.577.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.js';\n\nconst __iconNode = [\n [\n \"path\",\n {\n d: \"M4 14a1 1 0 0 1-.78-1.63l9.9-10.2a.5.5 0 0 1 .86.46l-1.92 6.02A1 1 0 0 0 13 10h7a1 1 0 0 1 .78 1.63l-9.9 10.2a.5.5 0 0 1-.86-.46l1.92-6.02A1 1 0 0 0 11 14z\",\n key: \"1xq2db\"\n }\n ]\n];\nconst Zap = createLucideIcon(\"zap\", __iconNode);\n\nexport { __iconNode, Zap as default };\n//# sourceMappingURL=zap.js.map\n","import { useEffect, useRef, useCallback } from \"react\";\nimport { WsClient } from \"@/lib/ws-client\";\n\ninterface UseWebSocketOptions {\n url: string;\n onMessage?: (event: MessageEvent) => void;\n autoConnect?: boolean;\n}\n\nexport function useWebSocket({\n url,\n onMessage,\n autoConnect = true,\n}: UseWebSocketOptions) {\n const clientRef = useRef<WsClient | null>(null);\n\n useEffect(() => {\n const client = new WsClient(url);\n clientRef.current = client;\n\n if (onMessage) {\n client.onMessage(onMessage);\n }\n\n if (autoConnect) {\n client.connect();\n }\n\n return () => {\n client.disconnect();\n clientRef.current = null;\n };\n }, [url, autoConnect]); // eslint-disable-line react-hooks/exhaustive-deps\n\n const send = useCallback((data: string | ArrayBuffer) => {\n clientRef.current?.send(data);\n }, []);\n\n const connect = useCallback(() => {\n clientRef.current?.connect();\n }, []);\n\n const disconnect = useCallback(() => {\n clientRef.current?.disconnect();\n }, []);\n\n return { send, connect, disconnect };\n}\n","import type { ChatMessage } from \"../../types/chat\";\n\n/** Simple deterministic hash of a jsonlPath → short prefix (non-cryptographic). */\nfunction hashPath(path: string): string {\n let h = 0;\n for (let i = 0; i < path.length; i++) h = ((h << 5) - h + path.charCodeAt(i)) | 0;\n return Math.abs(h).toString(36);\n}\n\n/**\n * Prefix pre-compact message IDs so they don't collide with current-session IDs.\n * React keys rely on `id`; collisions cause render corruption.\n */\nexport function prefixPreCompactIds(msgs: ChatMessage[], jsonlPath: string): ChatMessage[] {\n const prefix = `pc-${hashPath(jsonlPath)}-`;\n return msgs.map((m) => (m.id ? { ...m, id: `${prefix}${m.id}` } : m));\n}\n\n/**\n * Flatten messages with expansions prepended before their corresponding compact message.\n * Key of `expansions` = compact message id. Values = already-prefixed ChatMessage[].\n * Preserves reference equality when expansions is empty (zero-cost for non-compact chats).\n */\nexport function flattenWithExpansions(\n messages: ChatMessage[],\n expansions: Map<string, ChatMessage[]>,\n): ChatMessage[] {\n if (expansions.size === 0) return messages;\n const out: ChatMessage[] = [];\n for (const m of messages) {\n const pre = m.id ? expansions.get(m.id) : undefined;\n if (pre && pre.length > 0) out.push(...pre);\n out.push(m);\n }\n return out;\n}\n","let audioCtx: AudioContext | null = null;\n\nfunction getAudioContext(): AudioContext {\n if (!audioCtx) audioCtx = new AudioContext();\n return audioCtx;\n}\n\nfunction playTone(freq: number, duration: number, startTime: number, gain: number, type: OscillatorType = \"sine\") {\n const ctx = getAudioContext();\n const osc = ctx.createOscillator();\n const vol = ctx.createGain();\n osc.type = type;\n osc.frequency.value = freq;\n vol.gain.setValueAtTime(gain, startTime);\n vol.gain.exponentialRampToValueAtTime(0.001, startTime + duration);\n osc.connect(vol);\n vol.connect(ctx.destination);\n osc.start(startTime);\n osc.stop(startTime + duration);\n}\n\n/** Chat completed — pleasant double chime ascending */\nfunction playDone() {\n const ctx = getAudioContext();\n const t = ctx.currentTime;\n playTone(523, 0.15, t, 0.15); // C5\n playTone(659, 0.2, t + 0.12, 0.15); // E5\n}\n\n/** Approval request — urgent two-tone alert */\nfunction playApproval() {\n const ctx = getAudioContext();\n const t = ctx.currentTime;\n playTone(880, 0.12, t, 0.18, \"square\"); // A5\n playTone(698, 0.12, t + 0.15, 0.18, \"square\"); // F5\n playTone(880, 0.15, t + 0.3, 0.15, \"square\"); // A5\n}\n\n/** Question — soft ascending triplet */\nfunction playQuestion() {\n const ctx = getAudioContext();\n const t = ctx.currentTime;\n playTone(440, 0.12, t, 0.12); // A4\n playTone(523, 0.12, t + 0.1, 0.12); // C5\n playTone(659, 0.18, t + 0.2, 0.12); // E5\n}\n\nconst SOUND_MAP: Record<string, () => void> = {\n done: playDone,\n approval_request: playApproval,\n question: playQuestion,\n};\n\n/** Play notification sound by type. No-op if type unknown or AudioContext unavailable. */\nexport function playNotificationSound(type: string): void {\n try {\n SOUND_MAP[type]?.();\n } catch {\n // AudioContext may be blocked if no user gesture yet — silently ignore\n }\n}\n","import { useState, useCallback, useRef, useEffect, useMemo, startTransition } from \"react\";\nimport { useWebSocket } from \"./use-websocket\";\nimport { api, getAuthToken, projectUrl } from \"@/lib/api-client\";\nimport { flattenWithExpansions, prefixPreCompactIds } from \"@/lib/flatten-expansions\";\nimport { useNotificationStore } from \"@/stores/notification-store\";\nimport { useStreamingStore } from \"@/stores/streaming-store\";\nimport { usePanelStore } from \"@/stores/panel-store\";\nimport { playNotificationSound } from \"@/lib/notification-sounds\";\nimport { toast } from \"sonner\";\nimport type { ChatMessage, ChatEvent } from \"../../types/chat\";\nimport type { ChatWsServerMessage, SessionPhase } from \"../../types/api\";\n\ninterface ApprovalRequest {\n requestId: string;\n tool: string;\n input: unknown;\n}\n\nexport interface TeamMessageItem {\n from: string;\n to: string;\n text: string;\n timestamp: string;\n summary?: string;\n parsedType?: string;\n color?: string;\n}\n\ninterface TeamActivityState {\n hasTeams: boolean;\n teamNames: string[];\n messageCount: number;\n unreadCount: number;\n}\n\nconst EMPTY_TEAM_ACTIVITY: TeamActivityState = { hasTeams: false, teamNames: [], messageCount: 0, unreadCount: 0 };\n\nexport interface BashPartialEntry {\n content: string;\n lineCount: number;\n}\n\ninterface UseChatReturn {\n messages: ChatMessage[];\n /** Messages flattened with pre-compact expansions prepended before their compact cards. */\n renderedMessages: ChatMessage[];\n /** Fetch pre-compact transcript and store expansion. Returns loaded message count. */\n expandCompact: (compactMessageId: string, jsonlPath: string) => Promise<number>;\n /** Whether a given compactMessageId has been expanded. */\n isCompactExpanded: (compactMessageId: string) => boolean;\n messagesLoading: boolean;\n isStreaming: boolean;\n phase: SessionPhase;\n isReconnecting: boolean;\n connectingElapsed: number;\n pendingApproval: ApprovalRequest | null;\n contextWindowPct: number | null;\n compactStatus: \"compacting\" | null;\n statusMessage: string | null;\n sessionTitle: string | null;\n /** Team activity state from WS events */\n teamActivity: TeamActivityState;\n /** All team messages (ref-backed, updated live) */\n teamMessages: TeamMessageItem[];\n /** Mark team messages as read (reset unread counter) */\n markTeamRead: () => void;\n /** Partial bash output keyed by toolUseId (ref-backed for perf) */\n bashPartialOutput: React.RefObject<Map<string, BashPartialEntry>>;\n sendMessage: (content: string, opts?: { permissionMode?: string; priority?: 'now' | 'next' | 'later'; images?: Array<{ data: string; mediaType: string }> }) => void;\n respondToApproval: (requestId: string, approved: boolean, data?: unknown) => void;\n cancelStreaming: () => void;\n reconnect: () => void;\n refetchMessages: () => void;\n isConnected: boolean;\n}\n\n/** Check if the chat tab for this session is the active foreground tab */\nfunction isSessionTabActive(sid: string): boolean {\n if (document.hidden) return false;\n const { panels, focusedPanelId } = usePanelStore.getState();\n const panel = panels[focusedPanelId];\n if (!panel) return false;\n const activeTab = panel.tabs.find((t) => t.id === panel.activeTabId);\n return activeTab?.type === \"chat\" && activeTab.metadata?.sessionId === sid;\n}\n\nexport function useChat(sessionId: string | null, providerId = \"claude\", projectName = \"\"): UseChatReturn {\n const [messages, setMessages] = useState<ChatMessage[]>([]);\n /** Map of compactMessageId → pre-compact messages (already ID-prefixed). Ephemeral. */\n const [expansions, setExpansions] = useState<Map<string, ChatMessage[]>>(new Map());\n const [messagesLoading, setMessagesLoading] = useState(false);\n const [phase, setPhase] = useState<SessionPhase>(\"idle\");\n const [isReconnecting, setIsReconnecting] = useState(false);\n const [connectingElapsed, setConnectingElapsed] = useState(0);\n const [pendingApproval, setPendingApproval] = useState<ApprovalRequest | null>(null);\n const [contextWindowPct, setContextWindowPct] = useState<number | null>(null);\n const [compactStatus, setCompactStatus] = useState<\"compacting\" | null>(null);\n const [statusMessage, setStatusMessage] = useState<string | null>(null);\n const [sessionTitle, setSessionTitle] = useState<string | null>(null);\n const [isConnected, setIsConnected] = useState(false);\n const streamingContentRef = useRef(\"\");\n const streamingEventsRef = useRef<ChatEvent[]>([]);\n const bashOutputRef = useRef<Map<string, BashPartialEntry>>(new Map());\n const streamingAccountRef = useRef<{ accountId: string; accountLabel: string } | null>(null);\n const phaseRef = useRef<SessionPhase>(\"idle\");\n const pendingMessageRef = useRef<string | null>(null);\n const sendRef = useRef<(data: string) => void>(() => {});\n const refetchRef = useRef<(() => void) | null>(null);\n /** True while replaying turn_events — suppresses setPendingApproval */\n const isReplayingRef = useRef(false);\n const sessionIdRef = useRef(sessionId);\n sessionIdRef.current = sessionId;\n const projectNameRef = useRef(projectName);\n projectNameRef.current = projectName;\n /** Toast ID for the current pending approval notification */\n const approvalToastRef = useRef<string | number | null>(null);\n /** RAF handle for throttled syncMessages */\n const syncRafRef = useRef<number>(0);\n\n // Team activity tracking\n const teamActivityRef = useRef<{\n teamNames: Set<string>;\n messages: TeamMessageItem[];\n }>({ teamNames: new Set(), messages: [] });\n const teamUnreadRef = useRef(0);\n const [teamActivity, setTeamActivity] = useState<TeamActivityState>(EMPTY_TEAM_ACTIVITY);\n const [teamMessages, setTeamMessages] = useState<TeamMessageItem[]>([]);\n\n const updateTeamActivity = useCallback(() => {\n const ref = teamActivityRef.current;\n setTeamActivity({\n hasTeams: ref.teamNames.size > 0,\n teamNames: Array.from(ref.teamNames),\n messageCount: ref.messages.length,\n unreadCount: teamUnreadRef.current,\n });\n // Snapshot messages array so React detects changes\n setTeamMessages([...ref.messages]);\n }, []);\n\n const markTeamRead = useCallback(() => {\n teamUnreadRef.current = 0;\n updateTeamActivity();\n }, [updateTeamActivity]);\n\n // Derived state\n const isStreaming = phase !== \"idle\";\n\n // Sync streaming state to global store (for favicon + tab icon indicators)\n useEffect(() => {\n if (!sessionId) return;\n useStreamingStore.getState().setStreaming(sessionId, phase !== \"idle\");\n return () => { useStreamingStore.getState().setStreaming(sessionId, false); };\n }, [sessionId, phase]);\n\n /**\n * Route a child event to its parent Agent/Task tool_use's children array.\n * Creates a new parent object to ensure React detects the change on re-render.\n * Returns true if routed (caller should skip flat append), false if no parent found.\n */\n const routeToParent = useCallback((childEvent: ChatEvent, parentToolUseId: string): boolean => {\n const idx = streamingEventsRef.current.findIndex(\n (e) => e.type === \"tool_use\"\n && (e.tool === \"Agent\" || e.tool === \"Task\")\n && (e as any).toolUseId === parentToolUseId,\n );\n if (idx === -1) return false;\n const parent = streamingEventsRef.current[idx]!;\n if (parent.type !== \"tool_use\") return false;\n const newChildren = [...(parent.children ?? []), childEvent];\n streamingEventsRef.current[idx] = { ...parent, children: newChildren };\n return true;\n }, []);\n\n /** Flush refs into React state (called from throttled timer or directly) */\n const flushMessages = useCallback(() => {\n syncRafRef.current = 0;\n const content = streamingContentRef.current;\n const events = [...streamingEventsRef.current];\n const account = streamingAccountRef.current;\n // startTransition marks streaming updates as low-priority so React\n // yields to user interactions (text selection, copy, scroll) first.\n startTransition(() => {\n setMessages((prev) => {\n const last = prev[prev.length - 1];\n if (last?.role === \"assistant\" && !last.id.startsWith(\"final-\")) {\n return [...prev.slice(0, -1), { ...last, content, events, ...account }];\n }\n return [...prev, {\n id: `streaming-${Date.now()}`,\n role: \"assistant\" as const,\n content,\n events,\n timestamp: new Date().toISOString(),\n ...account,\n }];\n });\n });\n }, []);\n\n /** Throttled sync — batches rapid WS events into one render per ~100ms.\n * Previously used rAF (~16ms / 60fps) which blocked the main thread with\n * frequent ReactMarkdown re-parses, causing lag during text selection/copy.\n * 100ms (10fps) keeps streaming smooth while leaving idle time for interactions. */\n const syncMessages = useCallback(() => {\n if (!syncRafRef.current) {\n syncRafRef.current = window.setTimeout(flushMessages, 100) as unknown as number;\n }\n }, [flushMessages]);\n\n /** Process a single stream event — reused by live events and turn_events replay */\n const processStreamEvent = useCallback((data: unknown) => {\n const ev = data as any;\n const evType = ev?.type;\n if (!evType) return;\n\n switch (evType) {\n case \"account_info\": {\n streamingAccountRef.current = { accountId: ev.accountId, accountLabel: ev.accountLabel };\n setStatusMessage(null);\n break;\n }\n\n case \"account_retry\": {\n // Update streaming account to the new one being tried\n if (ev.accountId && ev.accountLabel) {\n streamingAccountRef.current = { accountId: ev.accountId, accountLabel: ev.accountLabel };\n }\n // Clear previous streaming events (error text from failed attempt)\n // and start fresh with only the retry notification\n streamingEventsRef.current = [ev as ChatEvent];\n syncMessages();\n break;\n }\n\n case \"status_update\": {\n const label = ev.accountLabel ? ` (${ev.accountLabel})` : \"\";\n setStatusMessage(`${ev.message}${label}`);\n break;\n }\n\n case \"text\": {\n const pid = ev.parentToolUseId as string | undefined;\n if (pid && routeToParent(ev as ChatEvent, pid)) {\n syncMessages();\n break;\n }\n streamingContentRef.current += ev.content;\n streamingEventsRef.current.push(ev as ChatEvent);\n syncMessages();\n break;\n }\n\n case \"thinking\": {\n const pid = ev.parentToolUseId as string | undefined;\n if (pid && routeToParent(ev as ChatEvent, pid)) {\n syncMessages();\n break;\n }\n streamingEventsRef.current.push(ev as ChatEvent);\n syncMessages();\n break;\n }\n\n case \"tool_use\": {\n const pid = ev.parentToolUseId as string | undefined;\n if (pid && routeToParent(ev as ChatEvent, pid)) {\n syncMessages();\n break;\n }\n streamingEventsRef.current.push(ev as ChatEvent);\n syncMessages();\n break;\n }\n\n case \"tool_result\": {\n // Clear bash partial output for this tool\n const trId = ev.toolUseId as string;\n if (trId) bashOutputRef.current.delete(trId);\n\n const pid = ev.parentToolUseId as string | undefined;\n if (pid && routeToParent(ev as ChatEvent, pid)) {\n syncMessages();\n break;\n }\n streamingEventsRef.current.push(ev as ChatEvent);\n syncMessages();\n break;\n }\n\n case \"approval_request\": {\n streamingEventsRef.current.push(ev as ChatEvent);\n // During turn_events replay, session_state already set the correct\n // pendingApproval — skip re-setting it for historical (already-answered) events\n if (isReplayingRef.current) break;\n setPendingApproval({\n requestId: ev.requestId,\n tool: ev.tool,\n input: ev.input,\n });\n if (sessionIdRef.current && !isSessionTabActive(sessionIdRef.current)) {\n const nType = ev.tool === \"AskUserQuestion\" ? \"question\" : \"approval_request\";\n // Unread state added via server-side session:unread_changed broadcast — only play sound + toast here\n playNotificationSound(nType);\n // Persistent toast with action to navigate to the waiting session\n const sid = sessionIdRef.current;\n const isQuestion = ev.tool === \"AskUserQuestion\";\n approvalToastRef.current = toast[isQuestion ? \"info\" : \"warning\"](\n isQuestion ? \"AI has a question\" : `${ev.tool} needs permission`,\n {\n description: projectNameRef.current || `Session ${sid.slice(0, 8)}`,\n duration: Infinity,\n action: {\n label: \"Go to session\",\n onClick: () => {\n const { panels } = usePanelStore.getState();\n for (const [panelId, panel] of Object.entries(panels)) {\n const tab = panel.tabs.find((t) => t.metadata?.sessionId === sid);\n if (tab) {\n usePanelStore.getState().setActiveTab(tab.id, panelId);\n break;\n }\n }\n },\n },\n },\n );\n }\n break;\n }\n\n case \"error\": {\n streamingEventsRef.current.push(ev as ChatEvent);\n const errEvents = [...streamingEventsRef.current];\n setMessages((prev) => {\n const last = prev[prev.length - 1];\n if (last?.role === \"assistant\") {\n return [...prev.slice(0, -1), { ...last, events: errEvents }];\n }\n return [...prev, {\n id: `error-${Date.now()}`,\n role: \"system\" as const,\n content: ev.message,\n events: [ev as ChatEvent],\n timestamp: new Date().toISOString(),\n }];\n });\n // Phase reset comes from BE via phase_changed\n break;\n }\n\n case \"team_detected\": {\n const teamName = ev.teamName as string;\n if (teamName) {\n teamActivityRef.current.teamNames.add(teamName);\n // Fetch full team data from REST\n api.get<any>(`/api/teams/${encodeURIComponent(teamName)}`).then((res: any) => {\n if (res?.messages) {\n const existing = teamActivityRef.current.messages;\n const newMsgs = (res.messages as any[]).filter(\n (m: any) => !existing.some((e) => e.timestamp === m.timestamp && e.from === m.from)\n );\n existing.push(...newMsgs);\n existing.sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());\n if (existing.length > 500) existing.splice(0, existing.length - 500);\n }\n updateTeamActivity();\n }).catch(() => {});\n updateTeamActivity();\n }\n break;\n }\n\n case \"team_inbox\": {\n const msgs = (ev as any).messages as any[];\n if (Array.isArray(msgs)) {\n const existing = teamActivityRef.current.messages;\n existing.push(...msgs);\n if (existing.length > 500) existing.splice(0, existing.length - 500);\n teamUnreadRef.current += msgs.length;\n updateTeamActivity();\n }\n break;\n }\n\n case \"team_updated\": {\n updateTeamActivity();\n break;\n }\n\n case \"bash_output\": {\n const tuId = ev.toolUseId as string;\n if (tuId) {\n const existing = bashOutputRef.current.get(tuId);\n if (existing) {\n existing.content += ev.content;\n // Cap at ~500KB to prevent browser OOM on long-running commands\n if (existing.content.length > 500_000) {\n existing.content = existing.content.slice(-500_000);\n }\n existing.lineCount = ev.lineCount as number;\n } else {\n bashOutputRef.current.set(tuId, {\n content: ev.content as string,\n lineCount: ev.lineCount as number,\n });\n }\n syncMessages();\n }\n break;\n }\n\n case \"done\": {\n // Idempotent: may receive duplicate done (provider + stream loop finally)\n if (phaseRef.current === \"idle\") break;\n if (ev.contextWindowPct != null) {\n setContextWindowPct(ev.contextWindowPct);\n }\n if (sessionIdRef.current && !isSessionTabActive(sessionIdRef.current)) {\n // Unread state added via server-side session:unread_changed broadcast — only play sound here\n playNotificationSound(\"done\");\n }\n // Cancel any pending throttled sync — done handler writes final state directly\n if (syncRafRef.current) { clearTimeout(syncRafRef.current); syncRafRef.current = 0; }\n // Finalize the streaming message — preserve SDK UUID for fork/rewind\n const finalContent = streamingContentRef.current;\n const finalEvents = [...streamingEventsRef.current];\n const finalAccount = streamingAccountRef.current;\n const doneUuid = ev.lastMessageUuid as string | undefined;\n setMessages((prev) => {\n const last = prev[prev.length - 1];\n if (last?.role === \"assistant\") {\n return [...prev.slice(0, -1), {\n ...last,\n id: `final-${Date.now()}`,\n content: finalContent || last.content,\n events: finalEvents.length > 0 ? finalEvents : last.events,\n ...(doneUuid && { sdkUuid: doneUuid }),\n }];\n }\n // No assistant message flushed yet (rAF was still pending when cancelled).\n // Create one from accumulated refs so the response isn't silently lost.\n if (finalContent || finalEvents.length > 0) {\n return [...prev, {\n id: `final-${Date.now()}`,\n role: \"assistant\" as const,\n content: finalContent,\n events: finalEvents,\n timestamp: new Date().toISOString(),\n ...(doneUuid && { sdkUuid: doneUuid }),\n ...finalAccount,\n }];\n }\n return prev;\n });\n streamingContentRef.current = \"\";\n streamingEventsRef.current = [];\n streamingAccountRef.current = null;\n bashOutputRef.current.clear();\n setStatusMessage(null);\n // Phase transition to idle comes from BE via phase_changed\n break;\n }\n }\n }, [routeToParent, syncMessages]);\n\n const handleMessage = useCallback((event: MessageEvent) => {\n let data: ChatWsServerMessage;\n try {\n data = JSON.parse(event.data as string) as ChatWsServerMessage;\n } catch {\n return;\n }\n\n // Ignore keepalive pings\n if ((data as any).type === \"ping\") return;\n\n // Dispatch file change events for real-time editor reload\n if ((data as any).type === \"file:changed\") {\n window.dispatchEvent(new CustomEvent(\"file:changed\", { detail: data }));\n return;\n }\n\n // Cross-tab/device unread sync — server broadcasts when unread state changes\n if ((data as any).type === \"session:unread_changed\") {\n const { sessionId: sid, unreadCount, unreadType, projectName: pn, sessionTitle: sTitle } = data as any;\n useNotificationStore.getState().handleUnreadChanged(sid, unreadCount, unreadType, pn, sTitle);\n return;\n }\n\n // Dispatch global Jira events so components can listen via window events\n if (typeof (data as any).type === \"string\" && (data as any).type.startsWith(\"jira:\")) {\n window.dispatchEvent(new CustomEvent((data as any).type, { detail: data }));\n return;\n }\n\n // Handle title updates from SDK summary\n if ((data as any).type === \"title_updated\") {\n setSessionTitle((data as any).title ?? null);\n return;\n }\n\n // Handle compact status events\n if ((data as any).type === \"compact_status\") {\n const status = (data as any).status;\n if (status === \"compacting\") {\n setCompactStatus(\"compacting\");\n } else if (status === \"done\") {\n setCompactStatus(null);\n // Do NOT refetch here — compact_done arrives mid-stream while the SDK\n // continues processing. Calling refetchMessages() would: (1) replace\n // all messages with REST history (killing the in-progress streaming\n // assistant message), (2) reset streamingContentRef/streamingEventsRef,\n // and (3) let the next flushMessages overwrite the last REST message\n // with empty streaming content — making the UI appear frozen.\n // The turn-end idle transition already calls refetchRef (phase→idle\n // handler) which safely loads compacted history after streaming stops.\n }\n return;\n }\n\n // Handle phase transitions from BE\n if ((data as any).type === \"phase_changed\") {\n const p = (data as any).phase as SessionPhase;\n setPhase(p);\n phaseRef.current = p;\n setConnectingElapsed(p === \"connecting\" ? ((data as any).elapsed ?? 0) : 0);\n // Safety: idle phase means no turn running — ensure compact indicator does not linger.\n // BE should broadcast compact_status=done too, but this is a belt-and-braces clear.\n if (p === \"idle\") setCompactStatus(null);\n return;\n }\n\n // Handle session state (replaces connected + status)\n if ((data as any).type === \"session_state\") {\n setIsConnected(true);\n const state = data as any;\n const p = state.phase as SessionPhase;\n setPhase(p);\n phaseRef.current = p;\n if (state.sessionTitle) setSessionTitle(state.sessionTitle);\n if (state.pendingApproval) {\n setPendingApproval({\n requestId: state.pendingApproval.requestId,\n tool: state.pendingApproval.tool,\n input: state.pendingApproval.input,\n });\n }\n // Sync compact indicator from authoritative server state (covers reconnect).\n // state.compactStatus is \"compacting\" | null — treat undefined as null for back-compat.\n setCompactStatus(state.compactStatus === \"compacting\" ? \"compacting\" : null);\n // If idle, refetch history (completed turns) and hide overlay\n if (p === \"idle\") {\n refetchRef.current?.();\n setIsReconnecting(false);\n }\n // If streaming, turn_events message will follow\n return;\n }\n\n // Handle turn_events (reconnect sync with rAF chunking)\n if ((data as any).type === \"turn_events\") {\n const events = (data as any).events as unknown[];\n const userMessage = (data as any).userMessage as string | null;\n if (!events?.length && !userMessage) { setIsReconnecting(false); return; }\n\n // Remove stale streaming assistant message + inject current turn's user message\n setMessages(prev => {\n let updated = prev;\n // Only remove in-progress streaming assistant (not finalized or REST-loaded)\n const last = updated[updated.length - 1];\n if (last?.role === \"assistant\" && last.id.startsWith(\"streaming-\")) {\n updated = updated.slice(0, -1);\n }\n // Add the current turn's user message if not already present\n if (userMessage) {\n const lastAfter = updated[updated.length - 1];\n if (lastAfter?.role !== \"user\" || lastAfter.content !== userMessage) {\n updated = [...updated, {\n id: `user-replay-${Date.now()}`,\n role: \"user\" as const,\n content: userMessage,\n timestamp: new Date().toISOString(),\n }];\n }\n }\n return updated;\n });\n\n // Reset streaming refs\n streamingContentRef.current = \"\";\n streamingEventsRef.current = [];\n streamingAccountRef.current = null;\n\n // Process events in chunks via requestAnimationFrame to avoid blocking main thread\n isReplayingRef.current = true;\n const CHUNK_SIZE = 100;\n let offset = 0;\n const processChunk = () => {\n const end = Math.min(offset + CHUNK_SIZE, events.length);\n for (let i = offset; i < end; i++) {\n processStreamEvent(events[i]);\n }\n offset = end;\n if (offset < events.length) {\n requestAnimationFrame(processChunk);\n } else {\n isReplayingRef.current = false;\n setIsReconnecting(false);\n }\n };\n requestAnimationFrame(processChunk);\n return;\n }\n\n // Route content events through processStreamEvent\n processStreamEvent(data);\n }, [processStreamEvent]);\n\n const wsUrl = sessionId && projectName\n ? `/ws/project/${encodeURIComponent(projectName)}/chat/${sessionId}`\n : \"\";\n\n const { send, connect: wsReconnect } = useWebSocket({\n url: wsUrl,\n onMessage: handleMessage,\n autoConnect: !!sessionId && !!projectName,\n });\n\n // Keep sendRef in sync so handleMessage can flush queued messages\n sendRef.current = send;\n\n // Load history and reset state when session changes\n useEffect(() => {\n let cancelled = false;\n\n setPhase(\"idle\");\n phaseRef.current = \"idle\";\n setPendingApproval(null);\n if (approvalToastRef.current != null) { toast.dismiss(approvalToastRef.current); approvalToastRef.current = null; }\n setCompactStatus(null);\n // Clear ephemeral pre-compact expansions on session change\n setExpansions(new Map());\n streamingContentRef.current = \"\";\n streamingEventsRef.current = [];\n bashOutputRef.current.clear();\n if (syncRafRef.current) { clearTimeout(syncRafRef.current); syncRafRef.current = 0; }\n setIsConnected(false);\n // Reset team state\n teamActivityRef.current = { teamNames: new Set(), messages: [] };\n teamUnreadRef.current = 0;\n setTeamActivity(EMPTY_TEAM_ACTIVITY);\n setTeamMessages([]);\n\n if (sessionId && projectName) {\n setMessagesLoading(true);\n fetch(`${projectUrl(projectName)}/chat/sessions/${sessionId}/messages?providerId=${providerId}`, {\n headers: { Authorization: `Bearer ${getAuthToken()}` },\n })\n .then((r) => r.json())\n .then((json: any) => {\n if (cancelled || phaseRef.current !== \"idle\") return;\n if (json.ok && Array.isArray(json.data) && json.data.length > 0) {\n setMessages(json.data);\n } else {\n setMessages([]);\n }\n })\n .catch(() => {\n if (!cancelled && phaseRef.current === \"idle\") setMessages([]);\n })\n .finally(() => {\n if (!cancelled) setMessagesLoading(false);\n });\n } else {\n setMessages([]);\n }\n\n return () => {\n cancelled = true;\n };\n }, [sessionId, providerId, projectName]);\n\n const sendMessage = useCallback(\n (content: string, opts?: { permissionMode?: string; priority?: 'now' | 'next' | 'later'; images?: Array<{ data: string; mediaType: string }> }) => {\n if (!content.trim()) return;\n\n const isFollowUp = phaseRef.current !== \"idle\";\n\n if (isFollowUp) {\n // Cancel pending throttled sync before finalizing\n if (syncRafRef.current) { clearTimeout(syncRafRef.current); syncRafRef.current = 0; }\n // Streaming follow-up: finalize current assistant message, then send\n const finalContent = streamingContentRef.current;\n const finalEvents = [...streamingEventsRef.current];\n setMessages((prev) => {\n const last = prev[prev.length - 1];\n if (last?.role === \"assistant\") {\n return [\n ...prev.slice(0, -1),\n { ...last, id: `final-${Date.now()}`, content: finalContent || last.content, events: finalEvents.length > 0 ? finalEvents : last.events },\n ];\n }\n return prev;\n });\n }\n\n // Add user message\n setMessages((prev) => [\n ...prev,\n {\n id: `user-${Date.now()}`,\n role: \"user\" as const,\n content,\n timestamp: new Date().toISOString(),\n },\n ]);\n\n // Reset streaming state for new turn\n streamingContentRef.current = \"\";\n streamingEventsRef.current = [];\n pendingMessageRef.current = null;\n if (!isFollowUp) {\n setPhase(\"initializing\");\n phaseRef.current = \"initializing\";\n } else {\n setPhase(\"thinking\");\n phaseRef.current = \"thinking\";\n }\n setPendingApproval(null);\n if (approvalToastRef.current != null) { toast.dismiss(approvalToastRef.current); approvalToastRef.current = null; }\n\n send(JSON.stringify({\n type: \"message\",\n content,\n permissionMode: opts?.permissionMode,\n priority: opts?.priority,\n images: opts?.images,\n }));\n },\n [send],\n );\n\n const respondToApproval = useCallback(\n (requestId: string, approved: boolean, data?: unknown) => {\n send(\n JSON.stringify({\n type: \"approval_response\",\n requestId,\n approved,\n data,\n }),\n );\n\n // Merge answers into the AskUserQuestion tool_use event so FE shows selected answers\n if (approved && data) {\n const evts = streamingEventsRef.current;\n const askEvt = evts.find(\n (e: ChatEvent) =>\n e.type === \"approval_request\" &&\n (e as any).requestId === requestId &&\n (e as any).tool === \"AskUserQuestion\",\n );\n if (askEvt) {\n const inp = (askEvt as any).input;\n if (inp && typeof inp === \"object\") {\n (inp as Record<string, unknown>).answers = data;\n }\n }\n setMessages((prev) => [...prev]);\n }\n\n setPendingApproval(null);\n if (approvalToastRef.current != null) { toast.dismiss(approvalToastRef.current); approvalToastRef.current = null; }\n },\n [send],\n );\n\n const cancelStreaming = useCallback(() => {\n if (phaseRef.current === \"idle\") return;\n send(JSON.stringify({ type: \"cancel\" }));\n const finalContent = streamingContentRef.current;\n const finalEvents = [...streamingEventsRef.current];\n setMessages((prev) => {\n const last = prev[prev.length - 1];\n if (last?.role === \"assistant\") {\n return [\n ...prev.slice(0, -1),\n {\n ...last,\n id: `final-${Date.now()}`,\n content: finalContent || last.content,\n events: finalEvents.length > 0 ? finalEvents : last.events,\n },\n ];\n }\n return prev;\n });\n streamingContentRef.current = \"\";\n streamingEventsRef.current = [];\n bashOutputRef.current.clear();\n pendingMessageRef.current = null;\n setPhase(\"idle\");\n phaseRef.current = \"idle\";\n setPendingApproval(null);\n if (approvalToastRef.current != null) { toast.dismiss(approvalToastRef.current); approvalToastRef.current = null; }\n }, [send]);\n\n const reconnect = useCallback(() => {\n setIsConnected(false);\n setIsReconnecting(true);\n wsReconnect();\n }, [wsReconnect]);\n\n const refetchMessages = useCallback(() => {\n if (!sessionId || !projectName) return;\n // No setMessagesLoading(true) here — keep current messages visible while\n // fetching in the background (stale-while-revalidate). The initial load\n // in the session-change useEffect already handles the first-time loading screen.\n fetch(`${projectUrl(projectName)}/chat/sessions/${sessionId}/messages?providerId=${providerId}`, {\n headers: { Authorization: `Bearer ${getAuthToken()}` },\n })\n .then((r) => r.json())\n .then((json: any) => {\n if (json.ok && Array.isArray(json.data) && json.data.length > 0) {\n setMessages(json.data);\n streamingContentRef.current = \"\";\n streamingEventsRef.current = [];\n }\n })\n .catch(() => {});\n }, [sessionId, providerId, projectName]);\n\n // Keep refetchRef in sync\n refetchRef.current = refetchMessages;\n\n /** Fetch pre-compact transcript. Idempotent: re-expanding same id replaces entry. */\n const expandCompact = useCallback(async (compactMessageId: string, jsonlPath: string): Promise<number> => {\n if (!projectName) throw new Error(\"No project context available\");\n // Claude's compact summary references the CURRENT session file (pre+summary+post).\n // Strip the `pc-{hash}-` prefix added by prefixPreCompactIds for nested expansions\n // so BE receives the raw session uuid and truncates at the correct boundary.\n const rawUuid = compactMessageId.replace(/^pc-[^-]+-/, \"\");\n const url =\n `${projectUrl(projectName)}/chat/pre-compact-messages` +\n `?jsonlPath=${encodeURIComponent(jsonlPath)}` +\n `&before=${encodeURIComponent(rawUuid)}`;\n const loaded = await api.get<ChatMessage[]>(url);\n const prefixed = prefixPreCompactIds(loaded, jsonlPath);\n setExpansions((prev) => {\n const next = new Map(prev);\n next.set(compactMessageId, prefixed);\n return next;\n });\n return prefixed.length;\n }, [projectName]);\n\n const isCompactExpanded = useCallback((id: string) => expansions.has(id), [expansions]);\n\n /** Flattened view: expansions prepended before their compact cards. */\n const renderedMessages = useMemo(\n () => flattenWithExpansions(messages, expansions),\n [messages, expansions],\n );\n\n return {\n messages,\n renderedMessages,\n expandCompact,\n isCompactExpanded,\n messagesLoading,\n isStreaming,\n phase,\n isReconnecting,\n connectingElapsed,\n pendingApproval,\n contextWindowPct,\n compactStatus,\n statusMessage,\n sessionTitle,\n teamActivity,\n teamMessages,\n markTeamRead,\n bashPartialOutput: bashOutputRef,\n sendMessage,\n respondToApproval,\n cancelStreaming,\n reconnect,\n refetchMessages,\n isConnected,\n };\n}\n","import { useState, useCallback, useEffect, useRef } from \"react\";\nimport { getAuthToken, projectUrl } from \"@/lib/api-client\";\nimport type { UsageInfo } from \"../../types/chat\";\n\nconst POLL_INTERVAL = 120_000; // read cache every 2min\n\ninterface UseUsageReturn {\n usageInfo: UsageInfo;\n usageLoading: boolean;\n /** ISO timestamp from BE — when usage was actually fetched from Anthropic API */\n lastFetchedAt: string | null;\n refreshUsage: () => void;\n}\n\nexport function useUsage(projectName: string, providerId = \"claude\"): UseUsageReturn {\n const [usageInfo, setUsageInfo] = useState<UsageInfo>({});\n const [usageLoading, setUsageLoading] = useState(false);\n const [lastFetchedAt, setLastFetchedAt] = useState<string | null>(null);\n const timerRef = useRef<ReturnType<typeof setInterval> | null>(null);\n\n const doFetch = useCallback((forceRefresh = false) => {\n if (!projectName) return;\n setUsageLoading(true);\n const qs = forceRefresh ? \"&refresh=1\" : \"\";\n fetch(`${projectUrl(projectName)}/chat/usage?providerId=${providerId}${qs}`, {\n headers: { Authorization: `Bearer ${getAuthToken()}` },\n })\n .then((r) => r.json())\n .then((json: any) => {\n if (json.ok && json.data) {\n setUsageInfo((prev) => ({ ...prev, ...json.data }));\n if (json.data.lastFetchedAt) setLastFetchedAt(json.data.lastFetchedAt);\n }\n })\n .catch(() => {})\n .finally(() => setUsageLoading(false));\n }, [projectName, providerId]);\n\n // Read cache on mount + auto-read every POLL_INTERVAL\n useEffect(() => {\n doFetch();\n timerRef.current = setInterval(() => doFetch(), POLL_INTERVAL);\n return () => { if (timerRef.current) clearInterval(timerRef.current); };\n }, [doFetch]);\n\n /** Manual refresh — tells BE to fetch fresh from Anthropic API */\n const refreshUsage = useCallback(() => doFetch(true), [doFetch]);\n\n return { usageInfo, usageLoading, lastFetchedAt, refreshUsage };\n}\n","/*!---------------------------------------------------------------------------------------------\n * Copyright (c) StackBlitz. All rights reserved.\n * Licensed under the MIT License. See License.txt in the project root for license information.\n *--------------------------------------------------------------------------------------------*/\nimport { useCallback, useMemo, useRef, useState, } from \"react\";\nconst DEFAULT_SPRING_ANIMATION = {\n /**\n * A value from 0 to 1, on how much to damp the animation.\n * 0 means no damping, 1 means full damping.\n *\n * @default 0.7\n */\n damping: 0.7,\n /**\n * The stiffness of how fast/slow the animation gets up to speed.\n *\n * @default 0.05\n */\n stiffness: 0.05,\n /**\n * The inertial mass associated with the animation.\n * Higher numbers make the animation slower.\n *\n * @default 1.25\n */\n mass: 1.25,\n};\nconst STICK_TO_BOTTOM_OFFSET_PX = 70;\nconst SIXTY_FPS_INTERVAL_MS = 1000 / 60;\nconst RETAIN_ANIMATION_DURATION_MS = 350;\nlet mouseDown = false;\nglobalThis.document?.addEventListener(\"mousedown\", () => {\n mouseDown = true;\n});\nglobalThis.document?.addEventListener(\"mouseup\", () => {\n mouseDown = false;\n});\nglobalThis.document?.addEventListener(\"click\", () => {\n mouseDown = false;\n});\nexport const useStickToBottom = (options = {}) => {\n const [escapedFromLock, updateEscapedFromLock] = useState(false);\n const [isAtBottom, updateIsAtBottom] = useState(options.initial !== false);\n const [isNearBottom, setIsNearBottom] = useState(false);\n const optionsRef = useRef(null);\n optionsRef.current = options;\n const isSelecting = useCallback(() => {\n if (!mouseDown) {\n return false;\n }\n const selection = window.getSelection();\n if (!selection || !selection.rangeCount) {\n return false;\n }\n const range = selection.getRangeAt(0);\n return (range.commonAncestorContainer.contains(scrollRef.current) ||\n scrollRef.current?.contains(range.commonAncestorContainer));\n }, []);\n const setIsAtBottom = useCallback((isAtBottom) => {\n state.isAtBottom = isAtBottom;\n updateIsAtBottom(isAtBottom);\n }, []);\n const setEscapedFromLock = useCallback((escapedFromLock) => {\n state.escapedFromLock = escapedFromLock;\n updateEscapedFromLock(escapedFromLock);\n }, []);\n // biome-ignore lint/correctness/useExhaustiveDependencies: not needed\n const state = useMemo(() => {\n let lastCalculation;\n return {\n escapedFromLock,\n isAtBottom,\n resizeDifference: 0,\n accumulated: 0,\n velocity: 0,\n listeners: new Set(),\n get scrollTop() {\n return scrollRef.current?.scrollTop ?? 0;\n },\n set scrollTop(scrollTop) {\n if (scrollRef.current) {\n scrollRef.current.scrollTop = scrollTop;\n state.ignoreScrollToTop = scrollRef.current.scrollTop;\n }\n },\n get targetScrollTop() {\n if (!scrollRef.current || !contentRef.current) {\n return 0;\n }\n return (scrollRef.current.scrollHeight - 1 - scrollRef.current.clientHeight);\n },\n get calculatedTargetScrollTop() {\n if (!scrollRef.current || !contentRef.current) {\n return 0;\n }\n const { targetScrollTop } = this;\n if (!options.targetScrollTop) {\n return targetScrollTop;\n }\n if (lastCalculation?.targetScrollTop === targetScrollTop) {\n return lastCalculation.calculatedScrollTop;\n }\n const calculatedScrollTop = Math.max(Math.min(options.targetScrollTop(targetScrollTop, {\n scrollElement: scrollRef.current,\n contentElement: contentRef.current,\n }), targetScrollTop), 0);\n lastCalculation = { targetScrollTop, calculatedScrollTop };\n requestAnimationFrame(() => {\n lastCalculation = undefined;\n });\n return calculatedScrollTop;\n },\n get scrollDifference() {\n return this.calculatedTargetScrollTop - this.scrollTop;\n },\n get isNearBottom() {\n return this.scrollDifference <= STICK_TO_BOTTOM_OFFSET_PX;\n },\n };\n }, []);\n const scrollToBottom = useCallback((scrollOptions = {}) => {\n if (typeof scrollOptions === \"string\") {\n scrollOptions = { animation: scrollOptions };\n }\n if (!scrollOptions.preserveScrollPosition) {\n setIsAtBottom(true);\n }\n const waitElapsed = Date.now() + (Number(scrollOptions.wait) || 0);\n const behavior = mergeAnimations(optionsRef.current, scrollOptions.animation);\n const { ignoreEscapes = false } = scrollOptions;\n let durationElapsed;\n let startTarget = state.calculatedTargetScrollTop;\n if (scrollOptions.duration instanceof Promise) {\n scrollOptions.duration.finally(() => {\n durationElapsed = Date.now();\n });\n }\n else {\n durationElapsed = waitElapsed + (scrollOptions.duration ?? 0);\n }\n const next = async () => {\n const promise = new Promise(requestAnimationFrame).then(() => {\n if (!state.isAtBottom) {\n state.animation = undefined;\n return false;\n }\n const { scrollTop } = state;\n const tick = performance.now();\n const tickDelta = (tick - (state.lastTick ?? tick)) / SIXTY_FPS_INTERVAL_MS;\n state.animation || (state.animation = { behavior, promise, ignoreEscapes });\n if (state.animation.behavior === behavior) {\n state.lastTick = tick;\n }\n if (isSelecting()) {\n return next();\n }\n if (waitElapsed > Date.now()) {\n return next();\n }\n if (scrollTop < Math.min(startTarget, state.calculatedTargetScrollTop)) {\n if (state.animation?.behavior === behavior) {\n if (behavior === \"instant\") {\n state.scrollTop = state.calculatedTargetScrollTop;\n return next();\n }\n state.velocity =\n (behavior.damping * state.velocity +\n behavior.stiffness * state.scrollDifference) /\n behavior.mass;\n state.accumulated += state.velocity * tickDelta;\n state.scrollTop += state.accumulated;\n if (state.scrollTop !== scrollTop) {\n state.accumulated = 0;\n }\n }\n return next();\n }\n if (durationElapsed > Date.now()) {\n startTarget = state.calculatedTargetScrollTop;\n return next();\n }\n state.animation = undefined;\n /**\n * If we're still below the target, then queue\n * up another scroll to the bottom with the last\n * requested animatino.\n */\n if (state.scrollTop < state.calculatedTargetScrollTop) {\n return scrollToBottom({\n animation: mergeAnimations(optionsRef.current, optionsRef.current.resize),\n ignoreEscapes,\n duration: Math.max(0, durationElapsed - Date.now()) || undefined,\n });\n }\n return state.isAtBottom;\n });\n return promise.then((isAtBottom) => {\n requestAnimationFrame(() => {\n if (!state.animation) {\n state.lastTick = undefined;\n state.velocity = 0;\n }\n });\n return isAtBottom;\n });\n };\n if (scrollOptions.wait !== true) {\n state.animation = undefined;\n }\n if (state.animation?.behavior === behavior) {\n return state.animation.promise;\n }\n return next();\n }, [setIsAtBottom, isSelecting, state]);\n const stopScroll = useCallback(() => {\n setEscapedFromLock(true);\n setIsAtBottom(false);\n }, [setEscapedFromLock, setIsAtBottom]);\n const handleScroll = useCallback(({ target }) => {\n if (target !== scrollRef.current) {\n return;\n }\n const { scrollTop, ignoreScrollToTop } = state;\n let { lastScrollTop = scrollTop } = state;\n state.lastScrollTop = scrollTop;\n state.ignoreScrollToTop = undefined;\n if (ignoreScrollToTop && ignoreScrollToTop > scrollTop) {\n /**\n * When the user scrolls up while the animation plays, the `scrollTop` may\n * not come in separate events; if this happens, to make sure `isScrollingUp`\n * is correct, set the lastScrollTop to the ignored event.\n */\n lastScrollTop = ignoreScrollToTop;\n }\n setIsNearBottom(state.isNearBottom);\n /**\n * Scroll events may come before a ResizeObserver event,\n * so in order to ignore resize events correctly we use a\n * timeout.\n *\n * @see https://github.com/WICG/resize-observer/issues/25#issuecomment-248757228\n */\n setTimeout(() => {\n /**\n * When theres a resize difference ignore the resize event.\n */\n if (state.resizeDifference || scrollTop === ignoreScrollToTop) {\n return;\n }\n if (isSelecting()) {\n setEscapedFromLock(true);\n setIsAtBottom(false);\n return;\n }\n const isScrollingDown = scrollTop > lastScrollTop;\n const isScrollingUp = scrollTop < lastScrollTop;\n if (state.animation?.ignoreEscapes) {\n state.scrollTop = lastScrollTop;\n return;\n }\n if (isScrollingUp) {\n setEscapedFromLock(true);\n setIsAtBottom(false);\n }\n if (isScrollingDown) {\n setEscapedFromLock(false);\n }\n if (!state.escapedFromLock && state.isNearBottom) {\n setIsAtBottom(true);\n }\n }, 1);\n }, [setEscapedFromLock, setIsAtBottom, isSelecting, state]);\n const handleWheel = useCallback(({ target, deltaY }) => {\n let element = target;\n while (![\"scroll\", \"auto\"].includes(getComputedStyle(element).overflow)) {\n if (!element.parentElement) {\n return;\n }\n element = element.parentElement;\n }\n /**\n * The browser may cancel the scrolling from the mouse wheel\n * if we update it from the animation in meantime.\n * To prevent this, always escape when the wheel is scrolled up.\n */\n if (element === scrollRef.current &&\n deltaY < 0 &&\n scrollRef.current.scrollHeight > scrollRef.current.clientHeight &&\n !state.animation?.ignoreEscapes) {\n setEscapedFromLock(true);\n setIsAtBottom(false);\n }\n }, [setEscapedFromLock, setIsAtBottom, state]);\n const scrollRef = useRefCallback((scroll) => {\n scrollRef.current?.removeEventListener(\"scroll\", handleScroll);\n scrollRef.current?.removeEventListener(\"wheel\", handleWheel);\n scroll?.addEventListener(\"scroll\", handleScroll, { passive: true });\n scroll?.addEventListener(\"wheel\", handleWheel, { passive: true });\n }, []);\n const contentRef = useRefCallback((content) => {\n state.resizeObserver?.disconnect();\n if (!content) {\n return;\n }\n let previousHeight;\n state.resizeObserver = new ResizeObserver(([entry]) => {\n const { height } = entry.contentRect;\n const difference = height - (previousHeight ?? height);\n state.resizeDifference = difference;\n /**\n * Sometimes the browser can overscroll past the target,\n * so check for this and adjust appropriately.\n */\n if (state.scrollTop > state.targetScrollTop) {\n state.scrollTop = state.targetScrollTop;\n }\n setIsNearBottom(state.isNearBottom);\n if (difference >= 0) {\n /**\n * If it's a positive resize, scroll to the bottom when\n * we're already at the bottom.\n */\n const animation = mergeAnimations(optionsRef.current, previousHeight\n ? optionsRef.current.resize\n : optionsRef.current.initial);\n scrollToBottom({\n animation,\n wait: true,\n preserveScrollPosition: true,\n duration: animation === \"instant\" ? undefined : RETAIN_ANIMATION_DURATION_MS,\n });\n }\n else {\n /**\n * Else if it's a negative resize, check if we're near the bottom\n * if we are want to un-escape from the lock, because the resize\n * could have caused the container to be at the bottom.\n */\n if (state.isNearBottom) {\n setEscapedFromLock(false);\n setIsAtBottom(true);\n }\n }\n previousHeight = height;\n /**\n * Reset the resize difference after the scroll event\n * has fired. Requires a rAF to wait for the scroll event,\n * and a setTimeout to wait for the other timeout we have in\n * resizeObserver in case the scroll event happens after the\n * resize event.\n */\n requestAnimationFrame(() => {\n setTimeout(() => {\n if (state.resizeDifference === difference) {\n state.resizeDifference = 0;\n }\n }, 1);\n });\n });\n state.resizeObserver?.observe(content);\n }, []);\n return {\n contentRef,\n scrollRef,\n scrollToBottom,\n stopScroll,\n isAtBottom: isAtBottom || isNearBottom,\n isNearBottom,\n escapedFromLock,\n state,\n };\n};\nfunction useRefCallback(callback, deps) {\n // biome-ignore lint/correctness/useExhaustiveDependencies: not needed\n const result = useCallback((ref) => {\n result.current = ref;\n return callback(ref);\n }, deps);\n return result;\n}\nconst animationCache = new Map();\nfunction mergeAnimations(...animations) {\n const result = { ...DEFAULT_SPRING_ANIMATION };\n let instant = false;\n for (const animation of animations) {\n if (animation === \"instant\") {\n instant = true;\n continue;\n }\n if (typeof animation !== \"object\") {\n continue;\n }\n instant = false;\n result.damping = animation.damping ?? result.damping;\n result.stiffness = animation.stiffness ?? result.stiffness;\n result.mass = animation.mass ?? result.mass;\n }\n const key = JSON.stringify(result);\n if (!animationCache.has(key)) {\n animationCache.set(key, Object.freeze(result));\n }\n return instant ? \"instant\" : animationCache.get(key);\n}\n","/*!---------------------------------------------------------------------------------------------\n * Copyright (c) StackBlitz. All rights reserved.\n * Licensed under the MIT License. See License.txt in the project root for license information.\n *--------------------------------------------------------------------------------------------*/\nimport * as React from \"react\";\nimport { createContext, useContext, useEffect, useImperativeHandle, useLayoutEffect, useMemo, useRef, } from \"react\";\nimport { useStickToBottom, } from \"./useStickToBottom.js\";\nconst StickToBottomContext = createContext(null);\nconst useIsomorphicLayoutEffect = typeof window !== \"undefined\" ? useLayoutEffect : useEffect;\nexport function StickToBottom({ instance, children, resize, initial, mass, damping, stiffness, targetScrollTop: currentTargetScrollTop, contextRef, ...props }) {\n const customTargetScrollTop = useRef(null);\n const targetScrollTop = React.useCallback((target, elements) => {\n const get = context?.targetScrollTop ?? currentTargetScrollTop;\n return get?.(target, elements) ?? target;\n }, [currentTargetScrollTop]);\n const defaultInstance = useStickToBottom({\n mass,\n damping,\n stiffness,\n resize,\n initial,\n targetScrollTop,\n });\n const { scrollRef, contentRef, scrollToBottom, stopScroll, isAtBottom, escapedFromLock, state, } = instance ?? defaultInstance;\n const context = useMemo(() => ({\n scrollToBottom,\n stopScroll,\n scrollRef,\n isAtBottom,\n escapedFromLock,\n contentRef,\n state,\n get targetScrollTop() {\n return customTargetScrollTop.current;\n },\n set targetScrollTop(targetScrollTop) {\n customTargetScrollTop.current = targetScrollTop;\n },\n }), [\n scrollToBottom,\n isAtBottom,\n contentRef,\n scrollRef,\n stopScroll,\n escapedFromLock,\n state,\n ]);\n useImperativeHandle(contextRef, () => context, [context]);\n useIsomorphicLayoutEffect(() => {\n if (!scrollRef.current) {\n return;\n }\n if (getComputedStyle(scrollRef.current).overflow === \"visible\") {\n scrollRef.current.style.overflow = \"auto\";\n }\n }, []);\n return (React.createElement(StickToBottomContext.Provider, { value: context },\n React.createElement(\"div\", { ...props }, typeof children === \"function\" ? children(context) : children)));\n}\n(function (StickToBottom) {\n function Content({ children, scrollClassName, ...props }) {\n const context = useStickToBottomContext();\n return (React.createElement(\"div\", { ref: context.scrollRef, style: {\n height: \"100%\",\n width: \"100%\",\n scrollbarGutter: \"stable both-edges\",\n }, className: scrollClassName },\n React.createElement(\"div\", { ...props, ref: context.contentRef }, typeof children === \"function\" ? children(context) : children)));\n }\n StickToBottom.Content = Content;\n})(StickToBottom || (StickToBottom = {}));\n/**\n * Use this hook inside a <StickToBottom> component to gain access to whether the component is at the bottom of the scrollable area.\n */\nexport function useStickToBottomContext() {\n const context = useContext(StickToBottomContext);\n if (!context) {\n throw new Error(\"use-stick-to-bottom component context must be used within a StickToBottom component\");\n }\n return context;\n}\n","/**\n * Tool card components for chat message rendering.\n * Handles summary + details for all SDK tool types.\n */\nimport { useState, useEffect, useRef, useMemo, lazy, Suspense } from \"react\";\nimport type { BashPartialEntry } from \"../../hooks/use-chat\";\nconst MarkdownRenderer = lazy(() =>\n import(\"@/components/shared/markdown-renderer\").then((m) => ({ default: m.MarkdownRenderer }))\n);\nimport {\n ChevronDown,\n ChevronRight,\n AlertCircle,\n Loader2,\n CheckCircle2,\n XCircle,\n ExternalLink,\n ListTodo,\n Search,\n Bot,\n Globe,\n Code,\n Columns2,\n} from \"lucide-react\";\nimport type { ChatEvent } from \"../../../types/chat\";\nimport { useShallow } from \"zustand/react/shallow\";\nimport { useTabStore } from \"@/stores/tab-store\";\nimport { basename } from \"@/lib/utils\";\n\n/** Extract tool name and input from a ChatEvent */\nfunction extractToolInfo(tool: ChatEvent): { toolName: string; input: Record<string, unknown> } {\n const isApproval = tool.type === \"approval_request\";\n const toolName = tool.type === \"tool_use\"\n ? tool.tool\n : isApproval\n ? (tool as any).tool ?? \"Tool\"\n : \"Tool\";\n const input = tool.type === \"tool_use\"\n ? (tool.input as Record<string, unknown>)\n : isApproval\n ? ((tool as any).input as Record<string, unknown>) ?? {}\n : {};\n return { toolName, input };\n}\n\n/** Unified tool card: shows tool-specific summary + expandable details */\nexport function ToolCard({\n tool,\n result,\n completed,\n projectName,\n bashPartialOutput,\n}: {\n tool: ChatEvent;\n result?: ChatEvent;\n completed?: boolean;\n projectName?: string;\n bashPartialOutput?: React.RefObject<Map<string, BashPartialEntry>>;\n}) {\n const [expanded, setExpanded] = useState(false);\n\n if (tool.type === \"error\") {\n return (\n <div className=\"flex items-center gap-2 rounded bg-red-500/10 border border-red-500/20 px-2 py-1.5 text-xs text-red-400\">\n <AlertCircle className=\"size-3\" />\n <span>{tool.message}</span>\n </div>\n );\n }\n\n const { toolName, input } = extractToolInfo(tool);\n const hasResult = result?.type === \"tool_result\";\n const isError = hasResult && !!(result as any).isError;\n const hasAnswers = toolName === \"AskUserQuestion\" && !!(input as any)?.answers;\n const wasApproved = tool.type === \"approval_request\" && (tool as any).approved != null;\n const isSubagent = (toolName === \"Agent\" || toolName === \"Task\") && tool.type === \"tool_use\";\n const children = isSubagent ? (tool as any).children as ChatEvent[] | undefined : undefined;\n const hasChildren = children && children.length > 0;\n const isDone = hasResult || hasAnswers || wasApproved || completed;\n\n // Read partial bash output for streaming Bash tools\n const toolUseId = tool.type === \"tool_use\" ? (tool as any).toolUseId as string | undefined : undefined;\n const partial = toolName === \"Bash\" && !hasResult && toolUseId\n ? bashPartialOutput?.current?.get(toolUseId)\n : undefined;\n const isStreamingBash = !!partial;\n\n // Auto-expand ToolCard when streaming bash output\n useEffect(() => {\n if (isStreamingBash && !expanded) setExpanded(true);\n }, [isStreamingBash]); // eslint-disable-line react-hooks/exhaustive-deps\n\n return (\n <div className={`rounded border text-xs ${isSubagent ? \"border-accent/30 bg-accent/5\" : \"border-border bg-background\"}`}>\n <button\n onClick={() => setExpanded(!expanded)}\n className=\"flex items-center gap-2 px-2 py-1.5 w-full text-left hover:bg-surface transition-colors min-w-0\"\n >\n {expanded ? <ChevronDown className=\"size-3 shrink-0\" /> : <ChevronRight className=\"size-3 shrink-0\" />}\n {isError\n ? <XCircle className=\"size-3 text-red-400 shrink-0\" />\n : isDone\n ? <CheckCircle2 className=\"size-3 text-green-400 shrink-0\" />\n : <Loader2 className=\"size-3 text-yellow-400 shrink-0 animate-spin\" />}\n <span className=\"truncate text-text-primary\">\n <ToolSummary name={toolName} input={input} />\n </span>\n {isStreamingBash && (\n <span className=\"ml-auto text-[10px] text-yellow-400 shrink-0\">{partial!.lineCount} line{partial!.lineCount !== 1 ? \"s\" : \"\"} streaming...</span>\n )}\n {hasChildren && !isStreamingBash && (\n <span className=\"ml-auto text-[10px] text-text-subtle shrink-0\">{children!.length} steps</span>\n )}\n </button>\n {expanded && (\n <div className=\"px-2 pb-2 space-y-1.5 select-text\">\n {(tool.type === \"tool_use\" || tool.type === \"approval_request\") && (\n <ToolDetails name={toolName} input={input} projectName={projectName} />\n )}\n {/* Streaming bash output */}\n {partial && <StreamingBashOutput content={partial.content} lineCount={partial.lineCount} />}\n {/* Subagent children: render nested tool events */}\n {hasChildren && (\n <SubagentChildren events={children!} projectName={projectName} />\n )}\n {hasResult && (\n <ToolResultView toolName={toolName} output={(result as any).output} />\n )}\n </div>\n )}\n </div>\n );\n}\n\n/** Render one-line summary per tool type */\nfunction ToolSummary({ name, input }: { name: string; input: Record<string, unknown> }) {\n const s = (v: unknown) => String(v ?? \"\");\n switch (name) {\n case \"Read\":\n case \"Write\":\n case \"Edit\":\n case \"MultiEdit\":\n case \"NotebookEdit\":\n return <>{name} <span className=\"text-text-subtle\">{basename(s(input.file_path))}</span></>;\n case \"Bash\": {\n const preview = input.description ? s(input.description) : s(input.command);\n return <>{name} <span className={`text-text-subtle${input.description ? \"\" : \" font-mono\"}`}>{truncate(preview, 60)}</span></>;\n }\n case \"Glob\":\n return <>{name} <span className=\"font-mono text-text-subtle\">{s(input.pattern)}</span></>;\n case \"Grep\":\n return <>{name} <span className=\"font-mono text-text-subtle\">{truncate(s(input.pattern), 40)}</span></>;\n case \"WebSearch\":\n return <><Search className=\"size-3 inline\" /> {name} <span className=\"text-text-subtle\">{truncate(s(input.query), 50)}</span></>;\n case \"WebFetch\":\n return <><Globe className=\"size-3 inline\" /> {name} <span className=\"text-text-subtle\">{truncate(s(input.url), 50)}</span></>;\n case \"ToolSearch\":\n return <><Search className=\"size-3 inline\" /> {name} <span className=\"text-text-subtle\">{truncate(s(input.query), 50)}</span></>;\n case \"Agent\":\n case \"Task\":\n return <><Bot className=\"size-3 inline\" /> {name} <span className=\"text-text-subtle\">{truncate(s(input.description || input.prompt), 60)}</span></>;\n case \"TodoWrite\": {\n const todos = Array.isArray(input.todos) ? input.todos as Array<{ content: string; status: string }> : [];\n const done = todos.filter((t) => t.status === \"completed\").length;\n return <><ListTodo className=\"size-3 inline\" /> {name} <span className=\"text-text-subtle\">{done}/{todos.length} done</span></>;\n }\n case \"AskUserQuestion\": {\n const qs = Array.isArray(input.questions) ? input.questions as Array<{ question: string }> : [];\n const hasAns = !!(input.answers);\n return <>{name} <span className=\"text-text-subtle\">{qs.length} question{qs.length !== 1 ? \"s\" : \"\"}{hasAns ? \" ✓\" : \"\"}</span></>;\n }\n default:\n return <>{name}</>;\n }\n}\n\n/** Render expanded details per tool type */\nfunction ToolDetails({\n name,\n input,\n projectName,\n}: {\n name: string;\n input: Record<string, unknown>;\n projectName?: string;\n}) {\n const s = (v: unknown) => String(v ?? \"\");\n const { openTab } = useTabStore(useShallow((state) => ({ openTab: state.openTab })));\n\n /** Open a file in a new editor tab */\n const openFile = (filePath: string) => {\n if (!projectName) return;\n openTab({\n type: \"editor\",\n title: basename(filePath),\n metadata: { filePath, projectName },\n projectId: projectName,\n closable: true,\n });\n };\n\n /** Open inline diff tab for Edit tool changes */\n const openEditDiff = (filePath: string, oldStr: string, newStr: string) => {\n openTab({\n type: \"git-diff\",\n title: `Diff ${basename(filePath)}`,\n metadata: { filePath, projectName, original: oldStr, modified: newStr },\n projectId: projectName ?? null,\n closable: true,\n });\n };\n\n switch (name) {\n case \"Bash\":\n return (\n <div className=\"space-y-1\">\n {!!input.description && <p className=\"text-text-subtle italic\">{s(input.description)}</p>}\n <pre className=\"font-mono text-text-secondary overflow-x-auto whitespace-pre-wrap break-all\">{s(input.command)}</pre>\n </div>\n );\n case \"Read\":\n case \"Write\":\n case \"Edit\":\n case \"MultiEdit\":\n case \"NotebookEdit\": {\n const filePath = s(input.file_path);\n return (\n <div className=\"space-y-1\">\n <button\n type=\"button\"\n className=\"font-mono text-text-secondary break-all hover:text-primary hover:underline text-left flex items-center gap-1\"\n onClick={() => openFile(filePath)}\n title=\"Open file in editor\"\n >\n <ExternalLink className=\"size-3 shrink-0\" />\n {filePath}\n </button>\n {name === \"Edit\" && (!!input.old_string || !!input.new_string) && (\n <button\n type=\"button\"\n className=\"text-text-subtle hover:text-primary hover:underline text-left flex items-center gap-1\"\n onClick={() => openEditDiff(filePath, s(input.old_string), s(input.new_string))}\n title=\"View diff in new tab\"\n >\n <Columns2 className=\"size-3 shrink-0\" />\n View Diff\n </button>\n )}\n {name === \"Write\" && !!input.content && (\n <pre className=\"font-mono text-text-subtle overflow-x-auto max-h-32 whitespace-pre-wrap\">{truncate(s(input.content), 300)}</pre>\n )}\n </div>\n );\n }\n case \"Glob\":\n return <p className=\"font-mono text-text-secondary\">{s(input.pattern)}{input.path ? ` in ${s(input.path)}` : \"\"}</p>;\n case \"Grep\":\n return (\n <div className=\"space-y-0.5\">\n <p className=\"font-mono text-text-secondary\">/{s(input.pattern)}/</p>\n {!!input.path && <p className=\"text-text-subtle\">in {s(input.path)}</p>}\n </div>\n );\n case \"TodoWrite\":\n return <TodoDetails todos={(input.todos as Array<{ content: string; status: string }>) ?? []} />;\n case \"Agent\":\n case \"Task\":\n return (\n <div className=\"space-y-1\">\n {!!input.description && <p className=\"text-text-secondary font-medium\">{s(input.description)}</p>}\n {!!input.subagent_type && <p className=\"text-text-subtle\">Type: {s(input.subagent_type)}</p>}\n {!!input.prompt && <MiniMarkdown content={s(input.prompt)} maxHeight=\"max-h-48\" />}\n </div>\n );\n case \"ToolSearch\":\n return (\n <div className=\"space-y-0.5\">\n <p className=\"font-mono text-text-secondary\">{s(input.query)}</p>\n {!!input.max_results && <p className=\"text-text-subtle\">Max results: {s(input.max_results)}</p>}\n </div>\n );\n case \"WebFetch\":\n return (\n <div className=\"space-y-0.5\">\n <a href={s(input.url)} target=\"_blank\" rel=\"noopener noreferrer\" className=\"font-mono text-primary hover:underline break-all flex items-center gap-1\">\n <Globe className=\"size-3 shrink-0\" />\n {s(input.url)}\n </a>\n {!!input.prompt && <p className=\"text-text-subtle\">{truncate(s(input.prompt), 100)}</p>}\n </div>\n );\n case \"AskUserQuestion\": {\n const qs = (input.questions as Array<{ question: string; header?: string; options: Array<{ label: string; description?: string }>; multiSelect?: boolean }>) ?? [];\n const answers = (input.answers as Record<string, string>) ?? {};\n return (\n <div className=\"space-y-2\">\n {qs.map((q, i) => (\n <div key={i} className=\"space-y-0.5\">\n <p className=\"text-text-primary font-medium\">{q.header ? `${q.header}: ` : \"\"}{q.question}</p>\n <div className=\"flex flex-wrap gap-1\">\n {q.options.map((opt, oi) => {\n const answer = answers[q.question] ?? \"\";\n const isSelected = answer.split(\", \").includes(opt.label);\n return (\n <span key={oi} className={`inline-block rounded px-1.5 py-0.5 text-xs border ${\n isSelected ? \"border-accent bg-accent/20 text-text-primary\" : \"border-border text-text-subtle\"\n }`}>\n {opt.label}\n </span>\n );\n })}\n </div>\n {answers[q.question] && (\n <p className=\"text-foreground text-xs\">Answer: {answers[q.question]}</p>\n )}\n </div>\n ))}\n </div>\n );\n }\n default:\n return (\n <pre className=\"overflow-x-auto text-text-secondary font-mono whitespace-pre-wrap break-all\">\n {JSON.stringify(input, null, 2)}\n </pre>\n );\n }\n}\n\n/** Todo list display with checkboxes */\nfunction TodoDetails({ todos }: { todos: Array<{ content: string; status: string }> }) {\n return (\n <div className=\"space-y-0.5\">\n {todos.map((todo, i) => (\n <div key={i} className=\"flex items-start gap-1.5\">\n <span className={`shrink-0 mt-0.5 ${\n todo.status === \"completed\"\n ? \"text-green-400\"\n : todo.status === \"in_progress\"\n ? \"text-yellow-400\"\n : \"text-text-subtle\"\n }`}>\n {todo.status === \"completed\" ? \"✓\" : todo.status === \"in_progress\" ? \"▶\" : \"○\"}\n </span>\n <span className={todo.status === \"completed\" ? \"line-through text-text-subtle\" : \"text-text-secondary\"}>\n {todo.content}\n </span>\n </div>\n ))}\n </div>\n );\n}\n\n/** Render tool result with smart formatting — markdown for Agent, collapsible JSON for others */\nfunction ToolResultView({ toolName, output }: { toolName: string; output: string }) {\n const [showRaw, setShowRaw] = useState(false);\n\n // For Agent/Task results: try to extract text content from JSON array result\n const agentContent = useMemo(() => {\n if (toolName !== \"Agent\" && toolName !== \"Task\") return null;\n try {\n const parsed = JSON.parse(output);\n if (Array.isArray(parsed)) {\n // SDK returns [{type:\"text\", text:\"...\"}, ...] — extract text blocks\n const texts = parsed\n .filter((item: any) => item.type === \"text\" && item.text)\n .map((item: any) => item.text)\n .join(\"\\n\\n\");\n if (texts) return texts;\n }\n if (typeof parsed === \"string\") return parsed;\n } catch {\n // Not JSON — might be plain text\n if (output && !output.startsWith(\"[{\")) return output;\n }\n return null;\n }, [toolName, output]);\n\n // Agent with extracted markdown content\n if (agentContent) {\n return (\n <div className=\"border-t border-border pt-1.5 space-y-1\">\n <MiniMarkdown content={agentContent} maxHeight=\"max-h-60\" />\n {/* Toggle to show raw JSON */}\n <button\n type=\"button\"\n onClick={() => setShowRaw(!showRaw)}\n className=\"flex items-center gap-1 text-[10px] text-text-subtle hover:text-text-secondary transition-colors\"\n >\n <Code className=\"size-3\" />\n {showRaw ? \"Hide\" : \"Show\"} raw\n </button>\n {showRaw && (\n <pre className=\"overflow-x-auto text-text-subtle font-mono max-h-40 whitespace-pre-wrap break-all text-[10px]\">\n {output}\n </pre>\n )}\n </div>\n );\n }\n\n // Default: collapsible raw output\n return (\n <CollapsibleOutput output={output} />\n );\n}\n\n/** Collapsible raw output — collapsed by default if > 3 lines */\nfunction CollapsibleOutput({ output }: { output: string }) {\n const lineCount = output.split(\"\\n\").length;\n const isLong = lineCount > 3 || output.length > 200;\n const [collapsed, setCollapsed] = useState(isLong);\n\n return (\n <div className=\"border-t border-border pt-1.5\">\n {isLong && (\n <button\n type=\"button\"\n onClick={() => setCollapsed(!collapsed)}\n className=\"flex items-center gap-1 text-[10px] text-text-subtle hover:text-text-secondary transition-colors mb-1\"\n >\n {collapsed ? <ChevronRight className=\"size-3\" /> : <ChevronDown className=\"size-3\" />}\n Output ({lineCount} lines)\n </button>\n )}\n <pre className={`overflow-x-auto text-text-subtle font-mono whitespace-pre-wrap break-all ${\n collapsed ? \"max-h-16 overflow-hidden\" : \"max-h-60\"\n }`}>\n {output}\n </pre>\n </div>\n );\n}\n\n/** Render subagent child events — nested tool_use/tool_result + text */\nfunction SubagentChildren({ events, projectName }: { events: ChatEvent[]; projectName?: string }) {\n // Group children similar to InterleavedEvents: pair tool_use + tool_result, merge text\n type ChildGroup =\n | { kind: \"text\"; content: string }\n | { kind: \"tool\"; tool: ChatEvent; result?: ChatEvent };\n\n const groups: ChildGroup[] = [];\n let textBuffer = \"\";\n\n for (const ev of events) {\n if (ev.type === \"text\") {\n textBuffer += ev.content;\n } else if (ev.type === \"tool_use\") {\n if (textBuffer) { groups.push({ kind: \"text\", content: textBuffer }); textBuffer = \"\"; }\n groups.push({ kind: \"tool\", tool: ev });\n } else if (ev.type === \"tool_result\") {\n // Match to last unmatched tool_use by toolUseId\n const trId = (ev as any).toolUseId;\n const match = trId\n ? groups.find((g) => g.kind === \"tool\" && g.tool.type === \"tool_use\" && (g.tool as any).toolUseId === trId && !g.result) as (ChildGroup & { kind: \"tool\" }) | undefined\n : groups.findLast((g) => g.kind === \"tool\" && !g.result) as (ChildGroup & { kind: \"tool\" }) | undefined;\n if (match) match.result = ev;\n }\n }\n if (textBuffer) groups.push({ kind: \"text\", content: textBuffer });\n\n return (\n <div className=\"border-l-2 border-accent/20 pl-2 space-y-1 mt-1\">\n {groups.map((g, i) => {\n if (g.kind === \"text\") {\n return (\n <div key={`st-${i}`} className=\"text-text-secondary text-[11px]\">\n <MiniMarkdown content={g.content} maxHeight=\"max-h-24\" />\n </div>\n );\n }\n return <ToolCard key={`sc-${i}`} tool={g.tool} result={g.result} completed={!!(g.result)} projectName={projectName} />;\n })}\n </div>\n );\n}\n\n/** Inline markdown renderer for tool details (prompt, result) */\nfunction MiniMarkdown({ content, maxHeight = \"max-h-48\" }: { content: string; maxHeight?: string }) {\n return (\n <Suspense fallback={<div className=\"animate-pulse h-4 bg-muted rounded\" />}>\n <MarkdownRenderer content={content} className={`text-text-secondary overflow-auto ${maxHeight}`} />\n </Suspense>\n );\n}\n\n\n/** Real-time streaming bash output with auto-scroll */\nfunction StreamingBashOutput({ content, lineCount }: { content: string; lineCount: number }) {\n const preRef = useRef<HTMLPreElement>(null);\n const userScrolledRef = useRef(false);\n\n useEffect(() => {\n if (preRef.current && !userScrolledRef.current) {\n preRef.current.scrollTop = preRef.current.scrollHeight;\n }\n }, [content]);\n\n return (\n <div className=\"border-t border-border pt-1.5\">\n <div className=\"flex items-center gap-1 text-[10px] text-yellow-400 mb-1\">\n <Loader2 className=\"size-3 animate-spin\" />\n <span>Output ({lineCount} line{lineCount !== 1 ? \"s\" : \"\"}, streaming...)</span>\n </div>\n <pre\n ref={preRef}\n onScroll={(e) => {\n const el = e.currentTarget;\n userScrolledRef.current = el.scrollTop + el.clientHeight < el.scrollHeight - 20;\n }}\n className=\"overflow-x-auto overflow-y-auto max-h-60 text-text-subtle font-mono whitespace-pre-wrap break-all text-[11px]\"\n >\n {content.split(\"\\n\").slice(-200).join(\"\\n\")}\n </pre>\n </div>\n );\n}\n\nfunction truncate(str?: string, max = 50): string {\n if (!str) return \"\";\n return str.length > max ? str.slice(0, max) + \"…\" : str;\n}\n","import { AlertCircle, ChevronUp, History, Loader2 } from \"lucide-react\";\n\n/** Detects a JSONL transcript path in Claude's compact summary message text. */\nconst JSONL_PATH_RE = /read the full transcript at:\\s*(\\S+\\.jsonl)/i;\n\nexport function extractJsonlPath(text: string): string | null {\n const match = text.match(JSONL_PATH_RE);\n return match?.[1]?.trim() ?? null;\n}\n\nexport type PreCompactStatus = \"idle\" | \"loading\" | \"loaded\" | \"error\";\n\ninterface PreCompactButtonProps {\n status: PreCompactStatus;\n onLoad?: () => void;\n count?: number;\n}\n\n/**\n * Button shown when Claude's compact summary is detected.\n * Clicking triggers the pre-compact-messages fetch. Shows loading/loaded/error states.\n * Responsive: full-width on mobile, inline on desktop. Min 44px touch target.\n */\nexport function PreCompactButton({ status, onLoad, count }: PreCompactButtonProps) {\n const isBusy = status === \"loading\";\n const isLoaded = status === \"loaded\";\n const isError = status === \"error\";\n\n const label = isBusy\n ? \"Loading previous conversation...\"\n : isLoaded\n ? `Previous conversation loaded${count != null ? ` (${count})` : \"\"}`\n : isError\n ? \"Failed to load — retry\"\n : \"Load previous conversation\";\n\n const Icon = isBusy ? Loader2 : isLoaded ? ChevronUp : isError ? AlertCircle : History;\n\n return (\n <button\n type=\"button\"\n onClick={onLoad}\n disabled={isBusy || isLoaded}\n className=\"mt-2 inline-flex items-center justify-center gap-2 rounded-md border border-border bg-surface/50 px-4 py-2.5 text-sm text-text-primary hover:bg-surface transition-colors disabled:opacity-70 disabled:cursor-default w-full md:w-auto min-h-[44px]\"\n >\n <Icon className={`size-4 shrink-0 ${isBusy ? \"animate-spin\" : \"\"} ${isError ? \"text-red-400\" : \"\"}`} />\n <span>{label}</span>\n </button>\n );\n}\n","import { Component, type ReactNode } from \"react\";\n\ninterface Props {\n /** Plain text fallback when fallback ReactNode is not provided */\n fallbackContent?: string;\n /** Custom fallback ReactNode — takes precedence over fallbackContent */\n fallback?: ReactNode;\n children: ReactNode;\n}\n\ninterface State {\n hasError: boolean;\n}\n\n/**\n * Error boundary that catches React DOM reconciliation errors\n * (e.g. \"removeChild\" failures from rehype-raw or browser extensions).\n * Falls back to provided content instead of crashing the whole app.\n */\nexport class RenderErrorBoundary extends Component<Props, State> {\n override state: State = { hasError: false };\n\n static getDerivedStateFromError(): State {\n return { hasError: true };\n }\n\n override render() {\n if (this.state.hasError) {\n if (this.props.fallback) return this.props.fallback;\n return this.props.fallbackContent ? (\n <div className=\"text-sm whitespace-pre-wrap break-words text-text-primary opacity-80\">\n {this.props.fallbackContent}\n </div>\n ) : null;\n }\n return this.props.children;\n }\n}\n","import { Bot } from \"lucide-react\";\nimport { SessionListPanel } from \"./session-list-panel\";\nimport type { SessionInfo } from \"../../../types/chat\";\n\ninterface ChatWelcomeProps {\n projectName: string;\n onSelectSession: (session: SessionInfo) => void;\n}\n\nexport function ChatWelcome({ projectName, onSelectSession }: ChatWelcomeProps) {\n return (\n <div className=\"flex flex-col items-center justify-center h-full gap-6 text-text-secondary overflow-y-auto\">\n <div className=\"flex flex-col items-center gap-3\">\n <Bot className=\"size-10 text-text-subtle\" />\n <p className=\"text-sm\">Send a message to start a new conversation</p>\n </div>\n\n <SessionListPanel\n projectName={projectName}\n onSelectSession={onSelectSession}\n className=\"w-full px-4\"\n />\n </div>\n );\n}\n","import { useState, useCallback, useMemo, useEffect, useRef } from \"react\";\n\n/* ── Types ── */\nexport interface QuestionOption {\n label: string;\n description?: string;\n}\n\nexport interface Question {\n question: string;\n header?: string;\n options: QuestionOption[];\n multiSelect?: boolean;\n}\n\ninterface QuestionCardProps {\n questions: Question[];\n onSubmit: (answers: Record<string, string>) => void;\n onSkip: () => void;\n}\n\n/* ── Hook: form state ── */\nfunction useQuestionForm(questions: Question[]) {\n const [answers, setAnswers] = useState<Record<number, string[]>>({});\n const [customInputs, setCustomInputs] = useState<Record<number, string>>({});\n const [activeTab, setActiveTab] = useState(0);\n\n const handleSingleSelect = useCallback((qi: number, label: string) => {\n setAnswers((p) => ({ ...p, [qi]: [label] }));\n setCustomInputs((p) => ({ ...p, [qi]: \"\" }));\n }, []);\n\n const handleMultiSelect = useCallback((qi: number, label: string) => {\n setAnswers((p) => {\n const cur = p[qi] || [];\n return { ...p, [qi]: cur.includes(label) ? cur.filter((l) => l !== label) : [...cur, label] };\n });\n }, []);\n\n const handleCustomInput = useCallback((qi: number, value: string) => {\n setCustomInputs((p) => ({ ...p, [qi]: value }));\n if (value) setAnswers((p) => ({ ...p, [qi]: [] }));\n }, []);\n\n const hasAnswer = useCallback(\n (qi: number) => (answers[qi]?.length ?? 0) > 0 || (customInputs[qi]?.trim().length ?? 0) > 0,\n [answers, customInputs],\n );\n\n const allAnswered = useMemo(() => questions.every((_, i) => hasAnswer(i)), [questions, hasAnswer]);\n\n const getFinalAnswer = useCallback(\n (qi: number) => {\n const custom = customInputs[qi]?.trim();\n if (custom) return custom;\n return (answers[qi] ?? []).join(\", \");\n },\n [answers, customInputs],\n );\n\n const goToNextTab = useCallback(() => setActiveTab((p) => Math.min(p + 1, questions.length - 1)), [questions.length]);\n const goToPrevTab = useCallback(() => setActiveTab((p) => Math.max(p - 1, 0)), []);\n\n return {\n answers, customInputs, activeTab, setActiveTab,\n handleSingleSelect, handleMultiSelect, handleCustomInput,\n hasAnswer, allAnswered, getFinalAnswer, goToNextTab, goToPrevTab,\n };\n}\n\n/* ── Hook: keyboard navigation ── */\nfunction useQuestionKeyboard(config: {\n questions: Question[];\n activeTab: number;\n totalOptions: number;\n allAnswered: boolean;\n hasAnswer: (i: number) => boolean;\n onSelectOption: (i: number) => void;\n goToNextTab: () => void;\n goToPrevTab: () => void;\n onSubmit: () => void;\n customInputRef: React.RefObject<HTMLInputElement | null>;\n enabled: boolean;\n}) {\n const [focusedOption, setFocusedOption] = useState(0);\n const containerRef = useRef<HTMLDivElement>(null);\n\n useEffect(() => setFocusedOption(0), [config.activeTab]);\n\n useEffect(() => {\n if (!config.enabled) return;\n const handleKeyDown = (e: KeyboardEvent) => {\n const isTyping = document.activeElement === config.customInputRef.current;\n\n // Number keys 1-9\n if (!isTyping && e.key >= \"1\" && e.key <= \"9\") {\n e.preventDefault();\n const idx = parseInt(e.key) - 1;\n if (idx < config.totalOptions - 1) { setFocusedOption(idx); config.onSelectOption(idx); }\n return;\n }\n // O/0 → focus custom input\n if (!isTyping && (e.key === \"o\" || e.key === \"O\" || e.key === \"0\")) {\n e.preventDefault();\n config.customInputRef.current?.focus();\n setFocusedOption(config.totalOptions - 1);\n return;\n }\n // Tab between questions\n if (e.key === \"Tab\" && config.questions.length > 1) {\n e.preventDefault();\n e.shiftKey ? config.goToPrevTab() : config.goToNextTab();\n return;\n }\n if (!isTyping) {\n if (e.key === \"ArrowLeft\") { e.preventDefault(); config.goToPrevTab(); return; }\n if (e.key === \"ArrowRight\") { e.preventDefault(); config.goToNextTab(); return; }\n if (e.key === \"ArrowUp\") { e.preventDefault(); setFocusedOption((p) => Math.max(0, p - 1)); return; }\n if (e.key === \"ArrowDown\") { e.preventDefault(); setFocusedOption((p) => Math.min(config.totalOptions - 1, p + 1)); return; }\n if (e.key === \" \") { e.preventDefault(); config.onSelectOption(focusedOption); return; }\n }\n if (e.key === \"Enter\") {\n e.preventDefault();\n if (config.allAnswered) config.onSubmit();\n else if (config.hasAnswer(config.activeTab)) config.goToNextTab();\n return;\n }\n if (e.key === \"Escape\" && isTyping) { config.customInputRef.current?.blur(); }\n };\n\n const el = containerRef.current;\n if (el) {\n el.addEventListener(\"keydown\", handleKeyDown);\n el.setAttribute(\"tabindex\", \"0\");\n if (!el.contains(document.activeElement)) el.focus();\n }\n return () => { el?.removeEventListener(\"keydown\", handleKeyDown); };\n }, [config, focusedOption]);\n\n return { focusedOption, setFocusedOption, containerRef };\n}\n\n/* ── Component ── */\nexport function QuestionCard({ questions, onSubmit, onSkip }: QuestionCardProps) {\n const customInputRef = useRef<HTMLInputElement>(null);\n const form = useQuestionForm(questions);\n const currentQ = questions[form.activeTab];\n const totalOptions = currentQ ? currentQ.options.length + 1 : 0;\n const hasMultiple = questions.length > 1;\n\n const handleSubmit = useCallback(() => {\n if (!form.allAnswered) return;\n const result: Record<string, string> = {};\n questions.forEach((q, i) => { result[q.question] = form.getFinalAnswer(i); });\n onSubmit(result);\n }, [form.allAnswered, form.getFinalAnswer, questions, onSubmit]);\n\n const handleSelectOption = useCallback(\n (index: number) => {\n if (!currentQ || index < 0) return;\n if (index < currentQ.options.length) {\n const label = currentQ.options[index]?.label;\n if (!label) return;\n if (currentQ.multiSelect) form.handleMultiSelect(form.activeTab, label);\n else form.handleSingleSelect(form.activeTab, label);\n } else if (index === currentQ.options.length) {\n customInputRef.current?.focus();\n }\n },\n [currentQ, form],\n );\n\n const kb = useQuestionKeyboard({\n questions, activeTab: form.activeTab, totalOptions,\n allAnswered: form.allAnswered, hasAnswer: form.hasAnswer,\n onSelectOption: handleSelectOption,\n goToNextTab: form.goToNextTab, goToPrevTab: form.goToPrevTab,\n onSubmit: handleSubmit, customInputRef, enabled: true,\n });\n\n const selectWithFocus = useCallback(\n (index: number) => { handleSelectOption(index); kb.setFocusedOption(index); },\n [handleSelectOption, kb.setFocusedOption],\n );\n\n return (\n <div\n ref={kb.containerRef}\n className=\"rounded-lg border-2 border-primary/30 bg-primary/5 p-3 space-y-3 outline-none animate-in slide-in-from-bottom-2\"\n >\n {/* Header */}\n <div className=\"flex items-center justify-between text-sm font-medium text-text-primary\">\n <span>\n AI has {hasMultiple ? `${questions.length} questions` : \"a question\"}\n </span>\n <span className=\"text-[10px] text-text-secondary font-normal\">\n {hasMultiple ? \"←→ tabs · \" : \"\"}↑↓ options · 1-{Math.min(totalOptions - 1, 9)} select · Enter submit\n </span>\n </div>\n\n {/* Tabs */}\n {hasMultiple && (\n <div className=\"flex gap-1 p-1 bg-background rounded-md overflow-x-auto border border-border\">\n {questions.map((q, i) => (\n <button\n key={i}\n className={`flex items-center gap-1.5 px-3 py-1.5 rounded text-xs whitespace-nowrap transition-all ${\n form.activeTab === i\n ? \"bg-primary text-primary-foreground\"\n : form.hasAnswer(i)\n ? \"text-primary bg-transparent\"\n : \"text-text-secondary hover:bg-surface-elevated\"\n }`}\n onClick={() => { form.setActiveTab(i); kb.setFocusedOption(0); }}\n tabIndex={-1}\n >\n <span\n className={`flex items-center justify-center w-4 h-4 rounded-full text-[10px] font-semibold ${\n form.activeTab === i\n ? \"bg-white/20\"\n : form.hasAnswer(i)\n ? \"bg-primary/20 text-primary\"\n : \"bg-surface-elevated text-text-secondary\"\n }`}\n >\n {form.hasAnswer(i) ? \"✓\" : i + 1}\n </span>\n <span className=\"max-w-[100px] overflow-hidden text-ellipsis\">{q.header || `Q${i + 1}`}</span>\n </button>\n ))}\n </div>\n )}\n\n {/* Current question */}\n {currentQ && (\n <div className=\"space-y-2\">\n {!hasMultiple && currentQ.header && (\n <div className=\"text-[11px] font-semibold uppercase tracking-wide text-text-secondary\">{currentQ.header}</div>\n )}\n <div className=\"text-sm text-text-primary\">{currentQ.question}</div>\n {currentQ.multiSelect && <div className=\"text-[11px] text-text-secondary\">Select multiple</div>}\n\n {/* Options */}\n <div className=\"flex flex-col gap-1.5\">\n {currentQ.options.map((opt, oi) => {\n const isSelected = (form.answers[form.activeTab] || []).includes(opt.label);\n const isFocused = kb.focusedOption === oi;\n return (\n <button\n key={oi}\n onClick={() => selectWithFocus(oi)}\n className={`text-left flex items-start gap-2.5 rounded px-2.5 py-2 text-xs border transition-all ${\n isSelected\n ? \"border-primary bg-primary/10 text-text-primary\"\n : \"border-border bg-background text-text-secondary hover:border-primary/40 hover:bg-primary/5\"\n } ${isFocused ? \"ring-2 ring-primary/40 ring-offset-1 ring-offset-background\" : \"\"}`}\n >\n <span className={`flex items-center justify-center w-4.5 h-4.5 rounded text-[10px] font-semibold shrink-0 mt-px ${\n isSelected ? \"bg-primary/20 text-primary\" : \"bg-surface-elevated text-text-secondary\"\n }`}>\n {oi + 1}\n </span>\n <div className=\"flex flex-col gap-0.5 flex-1\">\n <span className=\"font-medium text-text-primary\">{opt.label}</span>\n {opt.description && <span className=\"text-[11px] text-text-secondary\">{opt.description}</span>}\n </div>\n </button>\n );\n })}\n\n {/* Other / custom input */}\n <div\n className={`flex items-start gap-2.5 rounded px-2.5 py-2 text-xs border border-dashed transition-all border-border bg-transparent ${\n kb.focusedOption === totalOptions - 1 ? \"ring-2 ring-primary/40 ring-offset-1 ring-offset-background\" : \"\"\n }`}\n >\n <span className=\"flex items-center justify-center w-4.5 h-4.5 rounded bg-surface-elevated text-text-secondary text-[10px] font-semibold shrink-0 mt-px\">\n O\n </span>\n <input\n ref={customInputRef}\n type=\"text\"\n className=\"flex-1 px-2 py-1 text-xs bg-surface border border-border rounded text-text-primary outline-none placeholder:text-text-subtle focus:border-primary\"\n placeholder=\"Other (press O to type)...\"\n value={form.customInputs[form.activeTab] || \"\"}\n onChange={(e) => form.handleCustomInput(form.activeTab, e.target.value)}\n onFocus={() => kb.setFocusedOption(totalOptions - 1)}\n />\n </div>\n </div>\n </div>\n )}\n\n {/* Buttons */}\n <div className=\"flex gap-2 justify-end pt-1\">\n {hasMultiple && (\n <>\n <button\n className=\"px-3 py-1.5 text-xs rounded border border-border bg-background text-text-primary hover:bg-surface-elevated disabled:opacity-40 disabled:cursor-not-allowed transition-colors\"\n onClick={form.goToPrevTab}\n disabled={form.activeTab === 0}\n tabIndex={-1}\n >\n ← Prev\n </button>\n <button\n className=\"px-3 py-1.5 text-xs rounded border border-border bg-background text-text-primary hover:bg-surface-elevated disabled:opacity-40 disabled:cursor-not-allowed transition-colors\"\n onClick={form.goToNextTab}\n disabled={form.activeTab === questions.length - 1}\n tabIndex={-1}\n >\n Next →\n </button>\n </>\n )}\n <button\n onClick={onSkip}\n className=\"px-4 py-1.5 rounded border border-border bg-background text-text-secondary text-xs hover:bg-surface-elevated transition-colors\"\n tabIndex={-1}\n >\n Skip\n </button>\n <button\n onClick={handleSubmit}\n disabled={!form.allAnswered}\n className=\"px-4 py-1.5 rounded bg-primary text-primary-foreground text-xs font-medium hover:bg-primary/80 transition-colors disabled:opacity-40 disabled:cursor-not-allowed\"\n tabIndex={-1}\n >\n Submit {form.allAnswered ? \"✓\" : `(${questions.filter((_, i) => form.hasAnswer(i)).length}/${questions.length})`}\n </button>\n </div>\n </div>\n );\n}\n","import { useEffect, useRef, useState, useMemo, useCallback, memo, lazy, Suspense } from \"react\";\nimport { StickToBottom, useStickToBottomContext } from \"use-stick-to-bottom\";\nimport { getAuthToken } from \"@/lib/api-client\";\nimport type { ChatMessage, ChatEvent } from \"../../../types/chat\";\nimport type { SessionPhase } from \"../../../types/api\";\nimport type { BashPartialEntry } from \"../../hooks/use-chat\";\nimport { ToolCard } from \"./tool-cards\";\nimport { extractJsonlPath } from \"./pre-compact-button\";\nconst MarkdownRenderer = lazy(() =>\n import(\"@/components/shared/markdown-renderer\").then((m) => ({ default: m.MarkdownRenderer }))\n);\nimport { cn, basename } from \"@/lib/utils\";\nimport { RenderErrorBoundary } from \"@/components/shared/markdown-error-boundary\";\n\nimport {\n AlertCircle,\n ShieldAlert,\n Bot,\n ChevronDown,\n ChevronRight,\n FileText,\n Image as ImageIcon,\n Copy,\n Check,\n CheckCircle2,\n Loader2,\n RotateCcw,\n TerminalSquare,\n ChevronUp,\n Tag,\n XCircle,\n ExternalLink,\n Slash,\n} from \"lucide-react\";\nimport { ChatWelcome } from \"./chat-welcome\";\nimport { QuestionCard } from \"./question-card\";\nimport type { Question } from \"./question-card\";\nimport { useTabStore } from \"@/stores/tab-store\";\nimport { api } from \"@/lib/api-client\";\nimport { useProjectStore } from \"@/stores/project-store\";\nimport { useImageOverlay } from \"@/stores/image-overlay-store\";\n\ninterface MessageListProps {\n messages: ChatMessage[];\n messagesLoading?: boolean;\n pendingApproval: { requestId: string; tool: string; input: unknown } | null;\n onApprovalResponse: (requestId: string, approved: boolean, data?: unknown) => void;\n isStreaming: boolean;\n phase?: SessionPhase;\n connectingElapsed?: number;\n statusMessage?: string | null;\n compactStatus?: \"compacting\" | null;\n projectName?: string;\n /** Called when user clicks Fork/Rewind — opens new forked chat tab */\n onFork?: (userMessage: string, messageId?: string) => void;\n /** Called when user selects a recent session from the welcome screen */\n onSelectSession?: (session: import(\"../../../types/chat\").SessionInfo) => void;\n /** Partial bash output ref from useChat for real-time streaming */\n bashPartialOutput?: React.RefObject<Map<string, BashPartialEntry>>;\n /** Fetches pre-compact transcript and prepends messages. Returns loaded count. */\n onExpandCompact?: (compactMessageId: string, jsonlPath: string) => Promise<number>;\n /** Whether a given compact message has already been expanded. */\n isCompactExpanded?: (compactMessageId: string) => boolean;\n}\n\nexport function MessageList({\n messages,\n messagesLoading,\n pendingApproval,\n onApprovalResponse,\n isStreaming,\n phase,\n onSelectSession,\n connectingElapsed,\n statusMessage,\n compactStatus,\n projectName,\n onFork,\n bashPartialOutput,\n onExpandCompact,\n isCompactExpanded,\n}: MessageListProps) {\n // Scroll handled by StickToBottom wrapper — no manual scroll logic needed\n\n const PAGE_SIZE = 50;\n const [visibleCount, setVisibleCount] = useState(PAGE_SIZE);\n\n // Reset visible count when conversation identity changes (not on every streaming tick)\n const conversationId = messages[0]?.id;\n useEffect(() => { setVisibleCount(PAGE_SIZE); }, [conversationId]);\n\n const filtered = useMemo(() => messages.filter((msg) => {\n const hasContent = msg.content && msg.content.trim().length > 0;\n const hasEvents = msg.events && msg.events.length > 0;\n // User bubbles only render text — hide SDK tool-result user messages\n // that have no text content (their events are merged into assistant)\n if (msg.role === \"user\") return hasContent;\n return hasContent || hasEvents;\n }), [messages]);\n\n const displayed = useMemo(() => {\n const start = Math.max(0, filtered.length - visibleCount);\n return filtered.slice(start);\n }, [filtered, visibleCount]);\n\n const hasMoreInMemory = visibleCount < filtered.length;\n\n // Stable fork handler — avoids new closure per message (preserves MessageBubble memo)\n const handleFork = useCallback((msgContent: string, msgId: string | undefined) => {\n onFork?.(msgContent, msgId);\n }, [onFork]);\n\n // Scroll anchor bridge published from inside StickToBottom (needs the context's scrollRef).\n const scrollAnchorRef = useRef<ScrollAnchorHandle | null>(null);\n\n // Find the topmost displayed message that has an unexpanded compact JSONL path.\n const findTopUnexpandedCompact = useCallback((): { id: string; jsonlPath: string } | null => {\n if (!onExpandCompact || !isCompactExpanded) return null;\n for (const msg of displayed) {\n if (isCompactExpanded(msg.id)) continue;\n // Check user message content for JSONL path\n const path = extractJsonlPath(msg.content || \"\");\n if (path) return { id: msg.id, jsonlPath: path };\n // Check assistant events for JSONL path\n if (msg.events) {\n for (const ev of msg.events) {\n if (ev.type === \"text\") {\n const evPath = extractJsonlPath(ev.content || \"\");\n if (evPath) return { id: msg.id, jsonlPath: evPath };\n }\n }\n }\n }\n return null;\n }, [displayed, onExpandCompact, isCompactExpanded]);\n\n const topUnexpandedCompact = findTopUnexpandedCompact();\n const hasMore = hasMoreInMemory || !!topUnexpandedCompact;\n\n // Unified load-more: first show more in-memory messages, then auto-expand compact history\n const [autoLoadingCompact, setAutoLoadingCompact] = useState(false);\n const loadMore = useCallback(async () => {\n if (hasMoreInMemory) {\n scrollAnchorRef.current?.capture();\n setVisibleCount((c) => c + PAGE_SIZE);\n requestAnimationFrame(() => requestAnimationFrame(() => scrollAnchorRef.current?.restore()));\n return;\n }\n // All in-memory messages visible — try expanding topmost compact\n if (!topUnexpandedCompact || !onExpandCompact || autoLoadingCompact) return;\n setAutoLoadingCompact(true);\n try {\n scrollAnchorRef.current?.capture();\n const count = await onExpandCompact(topUnexpandedCompact.id, topUnexpandedCompact.jsonlPath);\n setVisibleCount((c) => c + count);\n requestAnimationFrame(() => requestAnimationFrame(() => scrollAnchorRef.current?.restore()));\n } finally {\n setAutoLoadingCompact(false);\n }\n }, [hasMoreInMemory, topUnexpandedCompact, onExpandCompact, autoLoadingCompact]);\n\n if (messagesLoading) {\n return (\n <div className=\"flex flex-col items-center justify-center h-full gap-3 text-text-secondary\">\n <Bot className=\"size-10 text-text-subtle animate-pulse\" />\n <p className=\"text-sm\">Loading messages...</p>\n </div>\n );\n }\n\n if (messages.length === 0 && !isStreaming) {\n return (\n <ChatWelcome\n projectName={projectName || \"\"}\n onSelectSession={onSelectSession || (() => {})}\n />\n );\n }\n\n return (\n <div className=\"relative flex-1 overflow-hidden flex flex-col min-h-0\">\n <StickToBottom className=\"flex-1 overflow-y-auto overflow-x-hidden [contain:strict] [overflow-anchor:auto]\" resize=\"smooth\" initial=\"instant\">\n <StickToBottom.Content className=\"p-4 space-y-4 select-none [&>*]:[overflow-anchor:auto]\">\n <ScrollAnchorBridge bridgeRef={scrollAnchorRef} />\n {hasMore && (\n <LoadMoreSentinel onLoadMore={loadMore} loading={autoLoadingCompact} />\n )}\n {displayed.map((msg, idx) => {\n const globalIdx = filtered.length - displayed.length + idx;\n const prevMsg = globalIdx > 0 ? filtered[globalIdx - 1] : undefined;\n return (\n <RenderErrorBoundary key={msg.id} fallbackContent={msg.content}>\n <MessageBubble\n message={msg}\n isStreaming={isStreaming && msg.id.startsWith(\"streaming-\")}\n projectName={projectName}\n onFork={msg.role === \"user\" && onFork ? handleFork : undefined}\n prevMsgId={prevMsg?.sdkUuid ?? prevMsg?.id}\n bashPartialOutput={bashPartialOutput}\n />\n </RenderErrorBoundary>\n );\n })}\n\n {pendingApproval && (\n pendingApproval.tool === \"AskUserQuestion\"\n ? <AskUserQuestionCard approval={pendingApproval} onRespond={onApprovalResponse} />\n : <ApprovalCard approval={pendingApproval} onRespond={onApprovalResponse} />\n )}\n\n {isStreaming && <ThinkingIndicator lastMessage={messages[messages.length - 1]} phase={phase} elapsed={connectingElapsed} statusMessage={compactStatus === \"compacting\" ? \"Compacting messages...\" : statusMessage} />}\n </StickToBottom.Content>\n <ScrollToBottomButton />\n </StickToBottom>\n </div>\n );\n}\n\n/** Imperative handle exposed by ScrollAnchorBridge — capture & restore scroll on prepend. */\ninterface ScrollAnchorHandle {\n /** Record current scrollTop + scrollHeight before a prepend. No-op if user is at bottom. */\n capture: () => void;\n /** After prepend commits, adjust scrollTop by the height delta so viewport stays locked. */\n restore: () => void;\n}\n\n/**\n * Consumes StickToBottom's scrollRef (only accessible inside its subtree) and publishes\n * capture/restore functions to a ref owned by the parent MessageList, so prepend-history\n * expansion can preserve scroll position across the re-render.\n */\nfunction ScrollAnchorBridge({ bridgeRef }: { bridgeRef: React.MutableRefObject<ScrollAnchorHandle | null> }) {\n const { scrollRef, isAtBottom } = useStickToBottomContext();\n const state = useRef<{ top: number; height: number } | null>(null);\n useEffect(() => {\n bridgeRef.current = {\n capture: () => {\n const el = scrollRef.current;\n if (!el || isAtBottom) { state.current = null; return; } // skip if sticking to bottom\n state.current = { top: el.scrollTop, height: el.scrollHeight };\n },\n restore: () => {\n const el = scrollRef.current;\n const s = state.current;\n if (!el || !s) return;\n const delta = el.scrollHeight - s.height;\n if (delta !== 0) el.scrollTop = s.top + delta;\n state.current = null;\n },\n };\n return () => { bridgeRef.current = null; };\n }, [bridgeRef, scrollRef, isAtBottom]);\n return null;\n}\n\n/** Floating button to scroll back to bottom when user has scrolled up */\nfunction ScrollToBottomButton() {\n const { isAtBottom, scrollToBottom } = useStickToBottomContext();\n if (isAtBottom) return null;\n return (\n <button\n onClick={() => scrollToBottom()}\n className=\"absolute bottom-4 left-1/2 -translate-x-1/2 z-10 flex items-center gap-1 px-3 py-1 rounded-full bg-surface-elevated border border-border text-xs text-text-secondary hover:text-foreground shadow-lg transition-all\"\n >\n <ChevronDown className=\"size-3\" />\n Scroll to bottom\n </button>\n );\n}\n\n/** IntersectionObserver sentinel — auto-triggers loadMore when scrolled near top.\n * Debounced to prevent cascade: after each trigger, waits 150ms before re-arming. */\nfunction LoadMoreSentinel({ onLoadMore, loading }: { onLoadMore: () => void; loading: boolean }) {\n const sentinelRef = useRef<HTMLDivElement>(null);\n const onLoadMoreRef = useRef(onLoadMore);\n onLoadMoreRef.current = onLoadMore;\n const cooldownRef = useRef(false);\n\n useEffect(() => {\n const el = sentinelRef.current;\n if (!el) return;\n const observer = new IntersectionObserver(\n ([entry]) => {\n if (!entry?.isIntersecting || cooldownRef.current) return;\n cooldownRef.current = true;\n onLoadMoreRef.current();\n setTimeout(() => { cooldownRef.current = false; }, 150);\n },\n { rootMargin: \"200px 0px 0px 0px\" },\n );\n observer.observe(el);\n return () => observer.disconnect();\n }, []);\n\n return (\n <div ref={sentinelRef} className=\"flex items-center justify-center py-2 text-xs text-text-secondary\">\n {loading && <><Loader2 className=\"size-3 animate-spin mr-1.5\" />Loading previous conversation...</>}\n </div>\n );\n}\n\nconst MessageBubble = memo(function MessageBubble({ message, isStreaming, projectName, onFork, prevMsgId, bashPartialOutput }: {\n message: ChatMessage; isStreaming: boolean; projectName?: string;\n onFork?: (content: string, messageId: string | undefined) => void;\n prevMsgId?: string;\n bashPartialOutput?: React.RefObject<Map<string, BashPartialEntry>>;\n}) {\n if (message.role === \"user\") {\n const handleFork = onFork ? () => onFork(message.content, prevMsgId) : undefined;\n return (\n <UserBubble\n content={message.content}\n messageId={message.id}\n projectName={projectName}\n onFork={handleFork}\n />\n );\n }\n\n if (message.role === \"system\") {\n return (\n <div className=\"flex items-center gap-2 rounded-lg bg-red-500/10 border border-red-500/20 px-3 py-2 text-sm text-red-400\">\n <AlertCircle className=\"size-4 shrink-0\" />\n <p>{message.content}</p>\n </div>\n );\n }\n\n // Assistant message — render events in order (text interleaved with tool calls)\n return (\n <div className=\"flex flex-col gap-2\">\n {message.events && message.events.length > 0\n ? <InterleavedEvents events={message.events} isStreaming={isStreaming} projectName={projectName} bashPartialOutput={bashPartialOutput} />\n : message.content && (\n <div className=\"text-sm text-text-primary select-text\">\n <MarkdownContent content={message.content} projectName={projectName} />\n </div>\n )}\n {message.accountLabel && (\n <p className=\"text-[10px] select-none\" style={{ color: \"var(--color-text-subtle)\" }}>\n via {message.accountLabel}\n </p>\n )}\n </div>\n );\n});\n\n/** Image extensions that can be previewed inline */\nconst IMAGE_EXTS = new Set([\".png\", \".jpg\", \".jpeg\", \".gif\", \".webp\"]);\n\ninterface SystemTag {\n name: string;\n label: string;\n content: string;\n}\n\nconst TAG_LABELS: Record<string, string> = {\n \"system-reminder\": \"Context\",\n \"claudeMd\": \"CLAUDE.md\",\n \"gitStatus\": \"Git Status\",\n \"currentDate\": \"Date\",\n \"fast_mode_info\": \"Fast Mode\",\n \"available-deferred-tools\": \"Tools\",\n \"task-notification\": \"Task Result\",\n \"environment_details\": \"Environment\",\n};\n\n/** Extract system-injected XML tags into structured objects + clean text */\nfunction extractSystemTags(text: string): { cleanText: string; tags: SystemTag[] } {\n const tags: SystemTag[] = [];\n const tagPattern = /<(system-reminder|available-deferred-tools|antml:[\\w-]+|fast_mode_info|claudeMd|gitStatus|currentDate|task-notification|environment_details)[^>]*>([\\s\\S]*?)<\\/\\1>/g;\n let match;\n while ((match = tagPattern.exec(text)) !== null) {\n const name = match[1]!;\n tags.push({\n name,\n label: TAG_LABELS[name] ?? name.replace(/^antml:/, \"\").replace(/-/g, \" \"),\n content: match[2]!.trim(),\n });\n }\n const cleanText = text.replace(tagPattern, \"\").trim();\n return { cleanText, tags };\n}\n\n/** Extract slash command tags from user message content */\ninterface SlashCommand {\n name: string;\n args?: string;\n}\nconst COMMAND_TAG_RE = /<command-message>[\\s\\S]*?<\\/command-message>\\s*<command-name>([\\s\\S]*?)<\\/command-name>(?:\\s*<command-args>([\\s\\S]*?)<\\/command-args>)?/;\n\nfunction parseCommandTags(text: string): { command: SlashCommand | null; cleanText: string } {\n const match = text.match(COMMAND_TAG_RE);\n if (!match) return { command: null, cleanText: text };\n const name = match[1]!.trim();\n const args = match[2]?.trim() || undefined;\n const cleanText = text.replace(COMMAND_TAG_RE, \"\").trim();\n return { command: { name, args }, cleanText };\n}\n\n/** Parse user message content, extracting attached file paths and the actual text */\nfunction parseUserAttachments(content: string): { files: string[]; text: string } {\n // Match: [Attached file: /path] or [Attached files:\\n/path1\\n/path2\\n]\n const singleMatch = content.match(/^\\[Attached file: (.+?)\\]\\n\\n?/);\n if (singleMatch) {\n return { files: [singleMatch[1]!], text: content.slice(singleMatch[0].length) };\n }\n\n const multiMatch = content.match(/^\\[Attached files:\\n([\\s\\S]+?)\\]\\n\\n?/);\n if (multiMatch) {\n const files = multiMatch[1]!.split(\"\\n\").map((l) => l.trim()).filter(Boolean);\n return { files, text: content.slice(multiMatch[0].length) };\n }\n\n return { files: [], text: content };\n}\n\n/** Build a preview URL for an uploaded file (served from /chat/uploads/:filename) */\nfunction uploadPreviewUrl(filePath: string, projectName?: string): string {\n const filename = basename(filePath);\n // Use a generic project name — the upload route is project-scoped but files are global\n return `/api/project/${encodeURIComponent(projectName ?? \"_\")}/chat/uploads/${encodeURIComponent(filename)}`;\n}\n\n/** Check if a file path is an image based on extension */\nfunction isImagePath(path: string): boolean {\n const dot = path.lastIndexOf(\".\");\n if (dot === -1) return false;\n return IMAGE_EXTS.has(path.slice(dot).toLowerCase());\n}\n\nfunction isPdfPath(path: string): boolean {\n return path.toLowerCase().endsWith(\".pdf\");\n}\n\n/** Detect if tags contain system-injected content (not real user input) */\nconst SYSTEM_TAG_NAMES = new Set([\"task-notification\", \"environment_details\"]);\n\n/** User message bubble — full width, collapsible, with system tag badges */\nfunction UserBubble({ content, messageId, projectName, onFork }: {\n content: string;\n messageId?: string;\n projectName?: string;\n onFork?: () => void;\n}) {\n const { files, text, tags, command } = useMemo(() => {\n const parsed = parseUserAttachments(content);\n const { cleanText: noSysTags, tags } = extractSystemTags(parsed.text);\n const { command, cleanText } = parseCommandTags(noSysTags);\n const bodyText = command?.args\n ? (cleanText ? `${command.args}\\n\\n${cleanText}` : command.args)\n : cleanText;\n return { files: parsed.files, text: bodyText, tags, command };\n }, [content]);\n\n const isSystemContext = tags.some((t) => SYSTEM_TAG_NAMES.has(t.name));\n\n const [expanded, setExpanded] = useState(false);\n const [isOverflowing, setIsOverflowing] = useState(false);\n const contentRef = useRef<HTMLDivElement>(null);\n\n useEffect(() => {\n const el = contentRef.current;\n if (!el) return;\n const check = () => setIsOverflowing(el.scrollHeight > el.clientHeight + 2);\n check();\n const ro = new ResizeObserver(check);\n ro.observe(el);\n return () => ro.disconnect();\n }, [text]);\n\n return (\n <div className={cn(\n \"group/user relative rounded-lg px-3 py-2 text-sm border shadow-sm\",\n isSystemContext\n ? \"bg-surface/40 border-border/40 text-text-secondary\"\n : \"bg-primary/10 border-primary/15 text-text-primary\",\n )}>\n {/* System tags as badges */}\n {tags.length > 0 && <SystemTagBadges tags={tags} />}\n\n {/* Slash command chip — args rendered in body for expand/collapse support */}\n {command && (\n <div className=\"flex items-center gap-1.5 mb-0.5\">\n <span className=\"inline-flex items-center gap-1 rounded-md bg-primary/15 border border-primary/20 px-2 py-0.5 text-xs font-medium text-primary\">\n <Slash className=\"size-3 shrink-0\" />\n {command.name}\n </span>\n </div>\n )}\n\n {/* Attached files — image thumbnails + file chips */}\n {files.length > 0 && (\n <div className=\"flex flex-wrap gap-1.5\">\n {files.map((filePath, i) =>\n isImagePath(filePath) ? (\n <AuthImageThumbnail key={i} filePath={filePath} projectName={projectName} />\n ) : (\n <div\n key={i}\n className=\"flex items-center gap-1 rounded-md border border-border/60 bg-background/40 px-1.5 py-0.5 text-[11px] text-text-secondary\"\n >\n <FileText className=\"size-3 shrink-0\" />\n <span className=\"truncate max-w-32\">{basename(filePath)}</span>\n </div>\n ),\n )}\n </div>\n )}\n\n {/* Text content — 2-line clamp by default, expandable */}\n {text && (\n <div\n ref={contentRef}\n className={cn(\n \"whitespace-pre-wrap break-words transition-all duration-200 select-text\",\n !expanded && \"line-clamp-2\",\n expanded && \"max-h-[50vh] overflow-y-auto\",\n )}\n >\n {isSystemContext ? <TextWithFilePaths text={text} projectName={projectName} /> : text}\n </div>\n )}\n {(isOverflowing || expanded) && (\n <button\n onClick={() => setExpanded(!expanded)}\n className={cn(\n \"flex items-center gap-1 text-xs mt-1 transition-colors\",\n isSystemContext ? \"text-text-subtle hover:text-text-secondary\" : \"text-primary/70 hover:text-primary\",\n )}\n >\n {expanded ? <><ChevronUp className=\"size-3\" />Show less</> : <><ChevronDown className=\"size-3\" />Show more</>}\n </button>\n )}\n {/* Fork/Rewind button — only for real user messages */}\n {!isSystemContext && onFork && (\n <button\n onClick={onFork}\n title=\"Retry from this message (fork session)\"\n className=\"absolute top-1.5 right-1.5 can-hover:opacity-0 can-hover:group-hover/user:opacity-100 transition-opacity size-5 flex items-center justify-center rounded text-text-subtle hover:text-text-primary\"\n >\n <RotateCcw className=\"size-3\" />\n </button>\n )}\n </div>\n );\n}\n\n/** Render system tags as collapsible badges */\nfunction SystemTagBadges({ tags }: { tags: SystemTag[] }) {\n return (\n <div className=\"flex flex-wrap gap-1.5\">\n {tags.map((tag, i) => (\n <SystemTagBadge key={i} tag={tag} />\n ))}\n </div>\n );\n}\n\nfunction SystemTagBadge({ tag }: { tag: SystemTag }) {\n const [open, setOpen] = useState(false);\n\n // Task notification: render formatted instead of raw XML\n if (tag.name === \"task-notification\") {\n return <TaskNotificationBadge content={tag.content} />;\n }\n\n return (\n <div className=\"text-xs\">\n <button\n onClick={() => setOpen(!open)}\n className=\"flex items-center gap-1 rounded-full border border-border/60 bg-surface/50 px-2 py-0.5 text-text-subtle hover:text-text-secondary hover:bg-surface transition-colors\"\n >\n <Tag className=\"size-2.5\" />\n <span>{tag.label}</span>\n <ChevronRight className={cn(\"size-2.5 transition-transform\", open && \"rotate-90\")} />\n </button>\n {open && (\n <div className=\"mt-1 rounded border border-border/40 bg-surface/30 px-2 py-1.5 text-[11px] text-text-subtle/80 whitespace-pre-wrap max-h-40 overflow-y-auto leading-relaxed\">\n {tag.content}\n </div>\n )}\n </div>\n );\n}\n\n/** Extract a sub-tag value from XML-like content */\nfunction xmlTag(content: string, tag: string): string | undefined {\n const m = content.match(new RegExp(`<${tag}>([\\\\s\\\\S]*?)</${tag}>`));\n return m?.[1]?.trim() || undefined;\n}\n\n/** Formatted badge for <task-notification> — shows status, summary, output file, result */\nfunction TaskNotificationBadge({ content }: { content: string }) {\n const [open, setOpen] = useState(false);\n const status = xmlTag(content, \"status\");\n const summary = xmlTag(content, \"summary\");\n const outputFile = xmlTag(content, \"output-file\");\n const result = xmlTag(content, \"result\");\n const isOk = status === \"completed\";\n\n return (\n <div className=\"text-xs\">\n <button\n onClick={() => setOpen(!open)}\n className=\"flex items-center gap-1.5 rounded-full border border-border/60 bg-surface/50 px-2 py-0.5 text-text-subtle hover:text-text-secondary hover:bg-surface transition-colors\"\n >\n {isOk ? <CheckCircle2 className=\"size-2.5 text-green-500\" /> : <XCircle className=\"size-2.5 text-yellow-500\" />}\n <span className=\"truncate max-w-80\">{summary ?? \"Task notification\"}</span>\n <ChevronRight className={cn(\"size-2.5 transition-transform shrink-0\", open && \"rotate-90\")} />\n </button>\n {open && (\n <div className=\"mt-1 rounded border border-border/40 bg-surface/30 px-2 py-1.5 space-y-1.5\">\n {/* Full summary (button truncates it) */}\n {summary && <p className=\"text-[11px] text-text-secondary\">{summary}</p>}\n {outputFile && <FilePathChip path={outputFile} />}\n {result && (\n <div className=\"text-[11px] text-text-subtle/80 max-h-60 overflow-y-auto leading-relaxed\">\n <MarkdownContent content={result} />\n </div>\n )}\n </div>\n )}\n </div>\n );\n}\n\n/** Clickable file path chip — opens file in editor tab */\nfunction FilePathChip({ path, projectName }: { path: string; projectName?: string }) {\n const handleClick = useCallback(() => {\n const openTab = useTabStore.getState().openTab;\n const pName = projectName ?? useProjectStore.getState().activeProject?.name;\n const fileName = basename(path);\n const meta: Record<string, unknown> = { filePath: path };\n if (pName) meta.projectName = pName;\n // Try to verify file exists, then open; fallback: open directly\n api.get(`/api/fs/read?path=${encodeURIComponent(path)}`).then(() => {\n openTab({ type: \"editor\", title: fileName, metadata: meta, projectId: null, closable: true });\n }).catch(() => {\n openTab({ type: \"editor\", title: fileName, metadata: meta, projectId: null, closable: true });\n });\n }, [path, projectName]);\n\n return (\n <button\n type=\"button\"\n onClick={handleClick}\n className=\"inline-flex items-center gap-1 rounded border border-border/50 bg-surface/50 px-1.5 py-0.5 font-mono text-[10px] text-text-secondary hover:text-text-primary hover:bg-surface transition-colors cursor-pointer\"\n >\n <FileText className=\"size-2.5 shrink-0\" />\n <span className=\"truncate max-w-60\">{basename(path)}</span>\n <ExternalLink className=\"size-2 shrink-0 opacity-50\" />\n </button>\n );\n}\n\n/** Render text with absolute file paths detected and turned into clickable chips */\nfunction TextWithFilePaths({ text, projectName }: { text: string; projectName?: string }) {\n const parts = useMemo(() => {\n // Match absolute file paths (at least 2 segments)\n const re = /(\\/(?:[\\w.\\-]+\\/)+[\\w.\\-]+)/g;\n const result: { kind: \"text\" | \"path\"; value: string }[] = [];\n let last = 0;\n let m;\n while ((m = re.exec(text)) !== null) {\n if (m.index > last) result.push({ kind: \"text\", value: text.slice(last, m.index) });\n result.push({ kind: \"path\", value: m[1]! });\n last = m.index + m[0].length;\n }\n if (last < text.length) result.push({ kind: \"text\", value: text.slice(last) });\n return result;\n }, [text]);\n\n return (\n <>\n {parts.map((p, i) =>\n p.kind === \"path\"\n ? <FilePathChip key={i} path={p.value} projectName={projectName} />\n : <span key={i}>{p.value}</span>,\n )}\n </>\n );\n}\n\n/** Hook: fetch an image via auth header, return blob URL */\nfunction useAuthBlob(src: string): { blobUrl: string | null; error: boolean } {\n const [blobUrl, setBlobUrl] = useState<string | null>(null);\n const [error, setError] = useState(false);\n\n useEffect(() => {\n let revoked = false;\n let url: string | undefined;\n const token = getAuthToken();\n fetch(src, { headers: token ? { Authorization: `Bearer ${token}` } : {} })\n .then((r) => { if (!r.ok) throw new Error(\"Failed\"); return r.blob(); })\n .then((blob) => {\n if (revoked) return;\n url = URL.createObjectURL(blob);\n setBlobUrl(url);\n })\n .catch(() => { if (!revoked) setError(true); });\n return () => { revoked = true; if (url) URL.revokeObjectURL(url); };\n }, [src]);\n\n return { blobUrl, error };\n}\n\n/** Fetches image with auth header, renders as blob URL — click opens lightbox */\nfunction AuthImage({ src, alt }: { src: string; alt: string }) {\n const { blobUrl, error } = useAuthBlob(src);\n const openOverlay = useImageOverlay((s) => s.open);\n\n if (error) {\n return (\n <div className=\"flex items-center gap-1.5 rounded-md border border-border bg-background/50 px-2 py-1 text-xs text-text-secondary\">\n <ImageIcon className=\"size-3.5 shrink-0\" />\n <span className=\"truncate max-w-40\">{alt}</span>\n </div>\n );\n }\n\n if (!blobUrl) {\n return <div className=\"rounded-md bg-surface border border-border h-24 w-32 animate-pulse\" />;\n }\n\n return (\n <button type=\"button\" onClick={() => openOverlay(blobUrl, alt)} className=\"block text-left\">\n <img\n src={blobUrl}\n alt={alt}\n className=\"rounded-md max-h-48 max-w-full object-contain border border-border cursor-pointer hover:opacity-90 transition-opacity\"\n />\n </button>\n );\n}\n\n/** Chip for attached images in user bubble — tiny preview replaces icon, click opens lightbox */\nfunction AuthImageThumbnail({ filePath, projectName }: { filePath: string; projectName?: string }) {\n const src = uploadPreviewUrl(filePath, projectName);\n const { blobUrl, error } = useAuthBlob(src);\n const openOverlay = useImageOverlay((s) => s.open);\n const name = basename(filePath);\n\n return (\n <button\n type=\"button\"\n onClick={() => blobUrl && openOverlay(blobUrl, name)}\n className=\"flex items-center gap-1 rounded-md border border-border/60 bg-background/40 px-1.5 py-0.5 text-[11px] text-text-secondary hover:bg-surface transition-colors cursor-pointer\"\n >\n {blobUrl ? (\n <img src={blobUrl} alt={name} className=\"size-4 rounded-sm object-cover shrink-0\" />\n ) : error ? (\n <ImageIcon className=\"size-3 shrink-0\" />\n ) : (\n <div className=\"size-4 rounded-sm bg-surface animate-pulse shrink-0\" />\n )}\n <span className=\"truncate max-w-32\">{name}</span>\n </button>\n );\n}\n\n/** Fetches file with auth, opens in new browser tab (for PDFs, etc.) */\nfunction AuthFileLink({ src, filename, mimeType }: { src: string; filename: string; mimeType: string }) {\n const [loading, setLoading] = useState(false);\n\n const handleClick = useCallback(async () => {\n setLoading(true);\n try {\n const token = getAuthToken();\n const res = await fetch(src, { headers: token ? { Authorization: `Bearer ${token}` } : {} });\n if (!res.ok) throw new Error(\"Failed to load\");\n const blob = await res.blob();\n const url = URL.createObjectURL(new Blob([blob], { type: mimeType }));\n window.open(url, \"_blank\");\n // Revoke after a delay to let the new tab load\n setTimeout(() => URL.revokeObjectURL(url), 60_000);\n } catch {\n // Fallback: try direct link\n window.open(src, \"_blank\");\n } finally {\n setLoading(false);\n }\n }, [src, mimeType]);\n\n return (\n <button\n type=\"button\"\n onClick={handleClick}\n disabled={loading}\n className=\"flex items-center gap-1.5 rounded-md border border-border bg-background/50 px-2 py-1 text-xs text-text-secondary hover:bg-surface hover:text-text-primary transition-colors cursor-pointer disabled:opacity-50\"\n >\n <FileText className=\"size-3.5 shrink-0 text-red-400\" />\n <span className=\"truncate max-w-40\">{filename}</span>\n {loading && <span className=\"animate-spin text-[10px]\">...</span>}\n </button>\n );\n}\n\n/**\n * Renders events in order — consecutive text events merged into one bubble,\n * tool_use/tool_result render as cards between text sections.\n * Last text group shows streaming cursor when actively streaming.\n */\ntype EventGroup =\n | { kind: \"text\"; content: string }\n | { kind: \"thinking\"; content: string }\n | { kind: \"tool\"; tool: ChatEvent; result?: ChatEvent; completed?: boolean };\n\nfunction InterleavedEvents({ events, isStreaming, projectName, bashPartialOutput }: {\n events: ChatEvent[];\n isStreaming: boolean;\n projectName?: string;\n bashPartialOutput?: React.RefObject<Map<string, BashPartialEntry>>;\n}) {\n // Group: consecutive text → merged text block; tool_use + tool_result paired by toolUseId\n const groups: EventGroup[] = [];\n let textBuffer = \"\";\n\n // First pass: create groups for text, thinking, and tool_use events\n let thinkingBuffer = \"\";\n for (let i = 0; i < events.length; i++) {\n const event = events[i]!;\n if (event.type === \"thinking\") {\n // Flush text buffer first if any\n if (textBuffer) { groups.push({ kind: \"text\", content: textBuffer }); textBuffer = \"\"; }\n thinkingBuffer += event.content;\n continue;\n }\n // Flush thinking buffer when non-thinking event arrives\n if (thinkingBuffer) {\n groups.push({ kind: \"thinking\", content: thinkingBuffer });\n thinkingBuffer = \"\";\n }\n if (event.type === \"account_retry\") {\n if (textBuffer) { groups.push({ kind: \"text\", content: textBuffer }); textBuffer = \"\"; }\n const label = (event as any).accountLabel ?? \"another account\";\n const reason = (event as any).reason ?? \"Auth failed\";\n groups.push({ kind: \"text\", content: `\\n\\n> ↻ ${reason} — retrying with **${label}**...\\n\\n` });\n continue;\n }\n if (event.type === \"text\") {\n textBuffer += event.content;\n } else if (event.type === \"tool_use\") {\n if (textBuffer) {\n groups.push({ kind: \"text\", content: textBuffer });\n textBuffer = \"\";\n }\n groups.push({ kind: \"tool\", tool: event });\n } else if (event.type === \"tool_result\") {\n // Skip tool_results in first pass — matched below\n } else {\n if (textBuffer) {\n groups.push({ kind: \"text\", content: textBuffer });\n textBuffer = \"\";\n }\n groups.push({ kind: \"tool\", tool: event });\n }\n }\n if (thinkingBuffer) {\n groups.push({ kind: \"thinking\", content: thinkingBuffer });\n }\n if (textBuffer) {\n groups.push({ kind: \"text\", content: textBuffer });\n }\n\n // Second pass: match tool_result events to their tool_use by toolUseId\n const toolResults = events.filter((e) => e.type === \"tool_result\");\n for (const tr of toolResults) {\n const trId = (tr as any).toolUseId;\n // Match by ID if available\n if (trId) {\n const match = groups.find(\n (g) => g.kind === \"tool\" && g.tool.type === \"tool_use\" && (g.tool as any).toolUseId === trId,\n ) as (EventGroup & { kind: \"tool\" }) | undefined;\n if (match) {\n match.result = tr;\n continue;\n }\n }\n // Fallback: attach to first tool group without a result\n const unmatched = groups.find(\n (g) => g.kind === \"tool\" && !g.result,\n ) as (EventGroup & { kind: \"tool\" }) | undefined;\n if (unmatched) {\n unmatched.result = tr;\n }\n }\n\n // Third pass: fallback to embedded result from buffer enrichment (reconnect).\n // When BE buffers tool_result, it also attaches result onto the matching tool_use event.\n for (const g of groups) {\n if (g.kind === \"tool\" && !g.result && g.tool.type === \"tool_use\") {\n const embedded = (g.tool as any).result;\n if (embedded) {\n g.result = { type: \"tool_result\", output: embedded.output, isError: embedded.isError } as ChatEvent;\n }\n }\n }\n\n // Mark tool groups without explicit tool_result as completed when:\n // 1. It's a Read and a later Edit on the same file has a result (Edit implies Read finished)\n // 2. Streaming is fully finished\n for (let gi = 0; gi < groups.length; gi++) {\n const g = groups[gi]!;\n if (g.kind === \"tool\" && !g.result) {\n let impliedDone = false;\n if (g.tool.type === \"tool_use\" && g.tool.tool === \"Read\") {\n const readPath = (g.tool.input as any)?.file_path;\n if (readPath) {\n impliedDone = groups.slice(gi + 1).some(\n (later) => later.kind === \"tool\" && later.result\n && later.tool.type === \"tool_use\" && later.tool.tool === \"Edit\"\n && (later.tool.input as any)?.file_path === readPath,\n );\n }\n }\n g.completed = impliedDone || !isStreaming;\n }\n }\n\n return (\n <>\n {groups.map((group, i) => {\n if (group.kind === \"thinking\") {\n return <ThinkingBlock key={`think-${i}`} content={group.content} isStreaming={isStreaming && i === groups.length - 1} />;\n }\n if (group.kind === \"text\") {\n const isLast = isStreaming && i === groups.length - 1;\n return (\n <div key={`text-${i}`} className=\"text-sm text-text-primary select-text\">\n <StreamingText content={group.content} animate={isLast} projectName={projectName} />\n </div>\n );\n }\n return <ToolCard key={`tool-${i}`} tool={group.tool} result={group.result} completed={group.completed} projectName={projectName} bashPartialOutput={bashPartialOutput} />;\n })}\n </>\n );\n}\n\n/** Collapsible thinking block — shows Claude's reasoning, collapsed by default when done */\nfunction ThinkingBlock({ content, isStreaming }: { content: string; isStreaming: boolean }) {\n const [expanded, setExpanded] = useState(isStreaming);\n const scrollRef = useRef<HTMLDivElement>(null);\n\n // Auto-collapse when streaming finishes\n useEffect(() => {\n if (!isStreaming && content.length > 0) setExpanded(false);\n }, [isStreaming, content.length]);\n\n // Auto-scroll to bottom during streaming\n useEffect(() => {\n if (isStreaming && expanded && scrollRef.current) {\n scrollRef.current.scrollTop = scrollRef.current.scrollHeight;\n }\n }, [content, isStreaming, expanded]);\n\n return (\n <div className=\"rounded border border-border/50 bg-surface/30 text-xs\">\n <button\n onClick={() => setExpanded(!expanded)}\n className=\"flex items-center gap-2 px-2 py-1.5 w-full text-left hover:bg-surface transition-colors text-text-subtle\"\n >\n {isStreaming ? <Loader2 className=\"size-3 animate-spin\" /> : <ChevronRight className={`size-3 transition-transform ${expanded ? \"rotate-90\" : \"\"}`} />}\n <span>Thinking{isStreaming ? \"...\" : \"\"}</span>\n {!isStreaming && <span className=\"text-text-subtle/50 ml-auto\">{content.length > 100 ? `${Math.round(content.length / 4)} tokens` : \"\"}</span>}\n </button>\n {expanded && (\n <div ref={scrollRef} className=\"max-h-60 overflow-y-auto\">\n <div className=\"px-2 pb-2 text-text-subtle/80 whitespace-pre-wrap text-[11px] leading-relaxed\">\n {content}\n </div>\n </div>\n )}\n </div>\n );\n}\n\n/**\n * Text component that renders streamed content directly.\n * WebSocket already delivers tokens incrementally — no fake animation needed.\n * When `isStreaming=true`, shows a blinking cursor at the end.\n */\nfunction StreamingText({ content, animate: isStreaming, projectName }: { content: string; animate: boolean; projectName?: string }) {\n return (\n <>\n <MarkdownContent content={content} projectName={projectName} isStreaming={isStreaming} />\n {isStreaming && (\n <span className=\"text-text-subtle text-sm animate-pulse\">Thinking...</span>\n )}\n </>\n );\n}\n\n/**\n * Shows streaming status with elapsed time and warnings:\n * - No assistant message: \"Connecting to Claude...\" with elapsed timer\n * - After tool: \"Processing...\"\n * - Text streaming: hidden\n */\nfunction ThinkingIndicator({ lastMessage, phase, elapsed, statusMessage }: { lastMessage?: ChatMessage; phase?: SessionPhase; elapsed?: number; statusMessage?: string | null }) {\n // Show indicator when:\n // 1. No assistant message yet (waiting for first response)\n // 2. Last event is tool_result (Claude thinking after tool execution)\n // 3. statusMessage is active (account routing/refreshing)\n // Hide when text is actively streaming (text itself is the indicator)\n\n const isWaiting = !lastMessage || lastMessage.role !== \"assistant\";\n const isAfterTool = (() => {\n if (!lastMessage?.events?.length) return false;\n const last = lastMessage.events[lastMessage.events.length - 1]!;\n return last.type === \"tool_result\";\n })();\n\n if (!statusMessage && !isWaiting && !isAfterTool) return null;\n\n const label = statusMessage\n ? statusMessage\n : phase === \"initializing\" ? \"Initializing\"\n : phase === \"connecting\" ? \"Connecting\"\n : phase === \"thinking\" ? \"Thinking\"\n : \"Processing\";\n\n const isLong = phase === \"connecting\" && (elapsed ?? 0) >= 30;\n\n return (\n <div className=\"flex flex-col gap-1 text-sm\">\n <div className=\"flex items-center gap-2 text-text-subtle\">\n <Loader2 className=\"size-3 animate-spin\" />\n <span>\n {label}\n {isWaiting && (elapsed ?? 0) > 0 && <span className=\"text-text-subtle/60\">... ({elapsed}s)</span>}\n </span>\n </div>\n {isLong && (\n <p className=\"text-xs text-yellow-500/80 ml-5\">\n Taking longer than usual — may be rate-limited or API slow. Try sending a new message to retry.\n </p>\n )}\n </div>\n );\n}\n\n/** Strip SDK teammate-message XML tags from text — team popover shows these */\nconst TEAMMATE_MSG_RE = /<teammate-message[^>]*>[\\s\\S]*?<\\/teammate-message>/g;\nfunction stripTeammateMessages(text: string): string {\n return text.replace(TEAMMATE_MSG_RE, \"\").replace(/\\n{3,}/g, \"\\n\\n\").trim();\n}\n\n/** Wrapper: delegates to shared MarkdownRenderer with code actions enabled */\nfunction MarkdownContent({ content, projectName, isStreaming }: { content: string; projectName?: string; isStreaming?: boolean }) {\n const cleaned = stripTeammateMessages(content);\n if (!cleaned) return null;\n return (\n <RenderErrorBoundary fallbackContent={cleaned}>\n <Suspense fallback={<div className=\"animate-pulse h-4 bg-muted rounded\" />}>\n <MarkdownRenderer content={cleaned} projectName={projectName} codeActions isStreaming={isStreaming} />\n </Suspense>\n </RenderErrorBoundary>\n );\n}\n\n/* ToolCard, ToolSummary, ToolDetails extracted to ./tool-cards.tsx */\n\nfunction ApprovalCard({\n approval,\n onRespond,\n}: {\n approval: { requestId: string; tool: string; input: unknown };\n onRespond: (requestId: string, approved: boolean, data?: unknown) => void;\n}) {\n return (\n <div className=\"rounded-lg border-2 border-yellow-500/40 bg-yellow-500/10 p-3 space-y-2\">\n <div className=\"flex items-center gap-2 text-yellow-400 text-sm font-medium\">\n <ShieldAlert className=\"size-4\" />\n <span>Tool Approval Required</span>\n </div>\n <div className=\"text-xs text-text-primary\">\n <span className=\"font-medium\">{approval.tool}</span>\n </div>\n <pre className=\"text-xs font-mono text-text-secondary overflow-x-auto bg-background rounded p-2 border border-border\">\n {JSON.stringify(approval.input, null, 2)}\n </pre>\n <div className=\"flex gap-2\">\n <button\n onClick={() => onRespond(approval.requestId, true)}\n className=\"px-4 py-1.5 rounded bg-green-600 text-white text-xs font-medium hover:bg-green-500 transition-colors\"\n >\n Allow\n </button>\n <button\n onClick={() => onRespond(approval.requestId, false)}\n className=\"px-4 py-1.5 rounded bg-red-600 text-white text-xs font-medium hover:bg-red-500 transition-colors\"\n >\n Deny\n </button>\n </div>\n </div>\n );\n}\n\n/** Interactive quiz form for AskUserQuestion — renders questions with selectable options + Other */\nfunction AskUserQuestionCard({\n approval,\n onRespond,\n}: {\n approval: { requestId: string; tool: string; input: unknown };\n onRespond: (requestId: string, approved: boolean, data?: unknown) => void;\n}) {\n const input = approval.input as { questions?: Question[] };\n const questions = input.questions ?? [];\n\n return (\n <QuestionCard\n questions={questions}\n onSubmit={(answers) => onRespond(approval.requestId, true, answers)}\n onSkip={() => onRespond(approval.requestId, false)}\n />\n );\n}\n","import { useState, useRef, useCallback } from \"react\";\n\n// Extend Window for webkit prefix\ninterface SpeechRecognitionEvent extends Event {\n results: SpeechRecognitionResultList;\n resultIndex: number;\n}\n\ntype SpeechRecognitionInstance = {\n lang: string;\n continuous: boolean;\n interimResults: boolean;\n start(): void;\n stop(): void;\n abort(): void;\n onresult: ((event: SpeechRecognitionEvent) => void) | null;\n onend: (() => void) | null;\n onerror: ((event: Event & { error: string }) => void) | null;\n};\n\ntype SpeechRecognitionConstructor = new () => SpeechRecognitionInstance;\n\nfunction getSpeechRecognition(): SpeechRecognitionConstructor | null {\n const w = window as unknown as {\n SpeechRecognition?: SpeechRecognitionConstructor;\n webkitSpeechRecognition?: SpeechRecognitionConstructor;\n };\n return w.SpeechRecognition ?? w.webkitSpeechRecognition ?? null;\n}\n\nexport function useVoiceInput(options?: { lang?: string }) {\n const [isListening, setIsListening] = useState(false);\n const [interimText, setInterimText] = useState(\"\");\n const recognitionRef = useRef<SpeechRecognitionInstance | null>(null);\n // Accumulate finalized text across multiple result events\n const finalizedRef = useRef(\"\");\n\n const supported = typeof window !== \"undefined\" && getSpeechRecognition() !== null;\n\n const start = useCallback(\n (onResult: (text: string, isFinal: boolean) => void) => {\n const SR = getSpeechRecognition();\n if (!SR) return;\n\n // Stop any existing session\n recognitionRef.current?.abort();\n\n const recognition = new SR();\n recognition.lang = options?.lang ?? \"vi-VN\";\n recognition.continuous = true;\n recognition.interimResults = true;\n\n finalizedRef.current = \"\";\n\n recognition.onresult = (event: SpeechRecognitionEvent) => {\n let interim = \"\";\n let newFinalized = \"\";\n\n for (let i = 0; i < event.results.length; i++) {\n const result = event.results[i]!;\n if (result.isFinal) {\n newFinalized += result[0]!.transcript;\n } else {\n interim += result[0]!.transcript;\n }\n }\n\n // Update finalized accumulator\n if (newFinalized) {\n finalizedRef.current = newFinalized;\n }\n\n const fullText = (finalizedRef.current + \" \" + interim).trim();\n setInterimText(interim);\n onResult(fullText, interim.length === 0 && finalizedRef.current.length > 0);\n };\n\n recognition.onend = () => {\n setIsListening(false);\n setInterimText(\"\");\n // Deliver final text if any\n if (finalizedRef.current) {\n onResult(finalizedRef.current.trim(), true);\n }\n };\n\n recognition.onerror = (event) => {\n // \"no-speech\" and \"aborted\" are expected, not real errors\n if (event.error !== \"no-speech\" && event.error !== \"aborted\") {\n console.warn(\"[voice-input] error:\", event.error);\n }\n setIsListening(false);\n setInterimText(\"\");\n };\n\n recognitionRef.current = recognition;\n recognition.start();\n setIsListening(true);\n },\n [options?.lang],\n );\n\n const stop = useCallback(() => {\n recognitionRef.current?.stop();\n recognitionRef.current = null;\n setIsListening(false);\n setInterimText(\"\");\n }, []);\n\n return { isListening, interimText, start, stop, supported };\n}\n","/**\n * Claude Code supported file types for chat attachments.\n * Supported files are uploaded and referenced; unsupported files get path inserted as text.\n */\n\n/** Image MIME types Claude Code can read via the Read tool (multimodal) */\nconst SUPPORTED_IMAGE_TYPES = new Set([\n \"image/png\",\n \"image/jpeg\",\n \"image/gif\",\n \"image/webp\",\n]);\n\n/** Document MIME types Claude Code can read */\nconst SUPPORTED_DOC_TYPES = new Set([\n \"application/pdf\",\n]);\n\n/** Text/code MIME prefixes Claude Code can read */\nconst TEXT_MIME_PREFIXES = [\n \"text/\",\n \"application/json\",\n \"application/xml\",\n \"application/javascript\",\n \"application/typescript\",\n \"application/x-yaml\",\n \"application/toml\",\n \"application/x-sh\",\n];\n\n/** File extensions considered text/code even if MIME is application/octet-stream */\nconst TEXT_EXTENSIONS = new Set([\n \".ts\", \".tsx\", \".js\", \".jsx\", \".mjs\", \".cjs\",\n \".py\", \".rb\", \".go\", \".rs\", \".java\", \".kt\", \".swift\",\n \".c\", \".cpp\", \".h\", \".hpp\", \".cs\",\n \".json\", \".yaml\", \".yml\", \".toml\", \".xml\",\n \".md\", \".mdx\", \".txt\", \".csv\", \".tsv\",\n \".html\", \".css\", \".scss\", \".less\", \".sass\",\n \".sh\", \".bash\", \".zsh\", \".fish\",\n \".sql\", \".graphql\", \".gql\",\n \".env\", \".ini\", \".cfg\", \".conf\",\n \".dockerfile\", \".makefile\",\n \".vue\", \".svelte\", \".astro\",\n \".ipynb\",\n]);\n\nexport function isImageFile(file: File): boolean {\n return SUPPORTED_IMAGE_TYPES.has(file.type);\n}\n\nexport function isSupportedFile(file: File): boolean {\n // Images\n if (SUPPORTED_IMAGE_TYPES.has(file.type)) return true;\n // Documents\n if (SUPPORTED_DOC_TYPES.has(file.type)) return true;\n // Text MIME types\n if (TEXT_MIME_PREFIXES.some((p) => file.type.startsWith(p))) return true;\n // Fallback: check extension\n const ext = getExtension(file.name);\n if (ext && TEXT_EXTENSIONS.has(ext)) return true;\n return false;\n}\n\nfunction getExtension(name: string): string {\n const dot = name.lastIndexOf(\".\");\n if (dot === -1) return \"\";\n return name.slice(dot).toLowerCase();\n}\n","import { useState } from \"react\";\nimport { X, FileText, Image as ImageIcon, Loader2, TerminalSquare, ChevronDown } from \"lucide-react\";\nimport { cn } from \"@/lib/utils\";\nimport type { ChatAttachment } from \"./message-input\";\n\ninterface AttachmentChipsProps {\n attachments: ChatAttachment[];\n onRemove: (id: string) => void;\n}\n\nexport function AttachmentChips({ attachments, onRemove }: AttachmentChipsProps) {\n const [expandedId, setExpandedId] = useState<string | null>(null);\n\n if (attachments.length === 0) return null;\n\n const expanded = expandedId ? attachments.find((a) => a.id === expandedId) : null;\n\n return (\n <div className=\"px-2 md:px-4 pt-2\">\n <div className=\"flex flex-wrap gap-1.5\">\n {attachments.map((att) => (\n <div\n key={att.id}\n className={cn(\n \"flex items-center gap-1.5 rounded-md border border-border bg-surface px-2 py-1 text-xs text-text-secondary max-w-48\",\n att.textContent && \"cursor-pointer hover:border-primary/50\",\n expandedId === att.id && \"border-primary/50 bg-surface-elevated\",\n )}\n onClick={() => {\n if (att.textContent) setExpandedId(expandedId === att.id ? null : att.id);\n }}\n >\n {/* Thumbnail or icon */}\n {att.previewUrl ? (\n <img src={att.previewUrl} alt={att.name} className=\"size-5 rounded object-cover shrink-0\" />\n ) : att.textContent ? (\n <TerminalSquare className=\"size-3.5 shrink-0 text-text-subtle\" />\n ) : att.isImage ? (\n <ImageIcon className=\"size-3.5 shrink-0 text-text-subtle\" />\n ) : (\n <FileText className=\"size-3.5 shrink-0 text-text-subtle\" />\n )}\n\n <span className=\"truncate\">{att.name}</span>\n\n {/* Expand indicator for text attachments */}\n {att.textContent && (\n <ChevronDown className={cn(\"size-3 shrink-0 text-text-subtle transition-transform\", expandedId === att.id && \"rotate-180\")} />\n )}\n\n {att.status === \"uploading\" ? (\n <Loader2 className=\"size-3 shrink-0 animate-spin text-text-subtle\" />\n ) : att.status === \"error\" ? (\n <span className=\"text-red-500 shrink-0\" title=\"Upload failed\">!</span>\n ) : null}\n\n {/* Remove button */}\n <button\n type=\"button\"\n onClick={(e) => { e.stopPropagation(); onRemove(att.id); if (expandedId === att.id) setExpandedId(null); }}\n className=\"shrink-0 rounded-sm p-0.5 hover:bg-border/50 transition-colors\"\n aria-label={`Remove ${att.name}`}\n >\n <X className=\"size-3\" />\n </button>\n </div>\n ))}\n </div>\n\n {/* Expanded preview for text attachment */}\n {expanded?.textContent && (\n <pre className=\"mt-1.5 max-h-40 overflow-auto rounded-md border border-border bg-background p-2 text-xs text-text-primary font-mono whitespace-pre-wrap break-words\">\n {stripCodeFence(expanded.textContent)}\n </pre>\n )}\n </div>\n );\n}\n\n/** Strip markdown code fence wrapper for preview display */\nfunction stripCodeFence(text: string): string {\n const trimmed = text.trim();\n const match = trimmed.match(/^```\\w*\\n([\\s\\S]*?)\\n```$/);\n return match ? match[1]! : trimmed;\n}\n","import { useRef, useEffect, useCallback, type KeyboardEvent } from \"react\";\nimport { Hand, Code, ClipboardList, ShieldOff, Check } from \"lucide-react\";\n\nconst MODES = [\n { id: \"default\", label: \"Ask before edits\", icon: Hand, description: \"Claude will ask for approval before making each edit\" },\n { id: \"acceptEdits\", label: \"Edit automatically\", icon: Code, description: \"Claude will edit files without asking first\" },\n { id: \"plan\", label: \"Plan mode\", icon: ClipboardList, description: \"Claude will present a plan before editing\" },\n { id: \"bypassPermissions\", label: \"Bypass permissions\", icon: ShieldOff, description: \"Claude will not ask before running commands\" },\n] as const;\n\nexport type ModeId = typeof MODES[number][\"id\"];\n\n/** Short label for the mode chip */\nexport function getModeLabel(id: string): string {\n return MODES.find((m) => m.id === id)?.label ?? \"Unknown\";\n}\n\n/** Icon component for the mode chip */\nexport function getModeIcon(id: string) {\n return MODES.find((m) => m.id === id)?.icon ?? Hand;\n}\n\ninterface ModeSelectorProps {\n value: string;\n onChange: (mode: string) => void;\n open: boolean;\n onOpenChange: (open: boolean) => void;\n}\n\nexport function ModeSelector({ value, onChange, open, onOpenChange }: ModeSelectorProps) {\n const panelRef = useRef<HTMLDivElement>(null);\n const focusedRef = useRef(0);\n\n // Close on click outside\n useEffect(() => {\n if (!open) return;\n const handler = (e: MouseEvent) => {\n if (panelRef.current && !panelRef.current.contains(e.target as Node)) {\n onOpenChange(false);\n }\n };\n document.addEventListener(\"mousedown\", handler);\n return () => document.removeEventListener(\"mousedown\", handler);\n }, [open, onOpenChange]);\n\n // Focus current mode on open\n useEffect(() => {\n if (open) {\n focusedRef.current = MODES.findIndex((m) => m.id === value);\n if (focusedRef.current < 0) focusedRef.current = 0;\n }\n }, [open, value]);\n\n const handleKeyDown = useCallback((e: KeyboardEvent) => {\n if (e.key === \"Escape\") {\n onOpenChange(false);\n return;\n }\n if (e.key === \"ArrowDown\" || e.key === \"ArrowUp\") {\n e.preventDefault();\n const dir = e.key === \"ArrowDown\" ? 1 : -1;\n focusedRef.current = (focusedRef.current + dir + MODES.length) % MODES.length;\n const el = panelRef.current?.querySelector(`[data-idx=\"${focusedRef.current}\"]`) as HTMLElement;\n el?.focus();\n }\n if (e.key === \"Enter\") {\n e.preventDefault();\n const mode = MODES[focusedRef.current];\n if (mode) { onChange(mode.id); onOpenChange(false); }\n }\n }, [onChange, onOpenChange]);\n\n if (!open) return null;\n\n return (\n <div\n ref={panelRef}\n role=\"listbox\"\n aria-label=\"Permission modes\"\n onKeyDown={handleKeyDown}\n onMouseDown={(e) => e.stopPropagation()}\n onClick={(e) => e.stopPropagation()}\n className=\"absolute bottom-full left-0 mb-1 z-50 w-72 md:w-80 rounded-lg border border-border bg-surface shadow-lg\"\n >\n <div className=\"flex items-center justify-between px-3 py-2 border-b border-border\">\n <span className=\"text-xs font-medium text-text-secondary\">Modes</span>\n <kbd className=\"text-[10px] px-1.5 py-0.5 rounded bg-surface-elevated text-text-subtle border border-border\">\n Shift + Tab\n </kbd>\n </div>\n <div className=\"py-1\">\n {MODES.map((mode, idx) => {\n const Icon = mode.icon;\n const isActive = mode.id === value;\n return (\n <button\n key={mode.id}\n data-idx={idx}\n role=\"option\"\n aria-selected={isActive}\n tabIndex={0}\n onClick={() => { onChange(mode.id); onOpenChange(false); }}\n className={`w-full flex items-start gap-3 px-3 py-2.5 text-left transition-colors hover:bg-surface-elevated focus:bg-surface-elevated focus:outline-none ${isActive ? \"bg-surface-elevated\" : \"\"}`}\n >\n <Icon className=\"size-4 mt-0.5 shrink-0 text-text-secondary\" />\n <div className=\"flex-1 min-w-0\">\n <div className=\"text-sm font-medium text-text-primary\">{mode.label}</div>\n <div className=\"text-xs text-text-subtle leading-snug\">{mode.description}</div>\n </div>\n {isActive && <Check className=\"size-4 mt-0.5 shrink-0 text-primary\" />}\n </button>\n );\n })}\n </div>\n </div>\n );\n}\n","import { useState, useRef, useCallback, useEffect, memo, type KeyboardEvent, type DragEvent, type ClipboardEvent } from \"react\";\nimport { ArrowUp, Square, Paperclip, Loader2, Mic, MicOff, Zap, ListOrdered, Clock } from \"lucide-react\";\nimport { useVoiceInput } from \"@/hooks/use-voice-input\";\nimport { api, projectUrl, getAuthToken } from \"@/lib/api-client\";\nimport { randomId } from \"@/lib/utils\";\nimport { isSupportedFile, isImageFile } from \"@/lib/file-support\";\nimport { AttachmentChips } from \"./attachment-chips\";\nimport { ModeSelector, getModeLabel, getModeIcon } from \"./mode-selector\";\nimport { ProviderSelector } from \"./provider-selector\";\nimport type { SlashItem } from \"./slash-command-picker\";\nimport type { FileNode } from \"../../../types/project\";\nimport { useFileStore } from \"@/stores/file-store\";\n\nexport interface ChatAttachment {\n id: string;\n name: string;\n file: File;\n isImage: boolean;\n previewUrl?: string;\n /** Server-side path after upload */\n serverPath?: string;\n /** Inline text content (e.g. terminal output) — no upload needed */\n textContent?: string;\n status: \"uploading\" | \"ready\" | \"error\";\n}\n\nexport type MessagePriority = 'now' | 'next' | 'later';\n\ninterface MessageInputProps {\n onSend: (content: string, attachments: ChatAttachment[], priority?: MessagePriority) => void;\n isStreaming?: boolean;\n onCancel?: () => void;\n disabled?: boolean;\n projectName?: string;\n /** Slash picker state change */\n onSlashStateChange?: (visible: boolean, filter: string) => void;\n onSlashItemsLoaded?: (items: SlashItem[], recentNames?: string[]) => void;\n slashSelected?: SlashItem | null;\n /** File picker state change */\n onFileStateChange?: (visible: boolean, filter: string) => void;\n onFileItemsLoaded?: (items: FileNode[]) => void;\n fileSelected?: FileNode | null;\n /** External files added via drag-drop on parent */\n externalFiles?: File[] | null;\n /** External paths from file tree drag or disambiguation */\n externalPaths?: string[] | null;\n /** Callback when external paths have been consumed (inserted into textarea) */\n onExternalPathsConsumed?: () => void;\n /** Callback when OS-dropped files resolve to multiple matches (disambiguation needed) */\n onDisambiguate?: (matches: FileNode[]) => void;\n /** Pre-fill input value (e.g. from command palette \"Ask AI\") */\n initialValue?: string;\n /** Auto-focus textarea on mount */\n autoFocus?: boolean;\n /** Current permission mode */\n permissionMode?: string;\n /** Permission mode change handler */\n onModeChange?: (mode: string) => void;\n /** Current provider ID */\n providerId?: string;\n /** Provider change handler — undefined when session is active (locked) */\n onProviderChange?: (providerId: string) => void;\n}\n\nexport const MessageInput = memo(function MessageInput({\n onSend,\n isStreaming,\n onCancel,\n disabled,\n projectName,\n onSlashStateChange,\n onSlashItemsLoaded,\n slashSelected,\n onFileStateChange,\n onFileItemsLoaded,\n fileSelected,\n externalFiles,\n externalPaths,\n onExternalPathsConsumed,\n onDisambiguate,\n initialValue,\n autoFocus,\n permissionMode,\n onModeChange,\n providerId,\n onProviderChange,\n}: MessageInputProps) {\n // Uncontrolled textarea: value lives in DOM + ref, not React state.\n // Only `hasText` state triggers re-renders (empty↔non-empty for send button).\n // This eliminates React re-render on every keystroke — critical for Chromium on iPad.\n const valueRef = useRef(initialValue ?? \"\");\n const [hasText, setHasText] = useState(() => (initialValue ?? \"\").trim().length > 0);\n const [attachments, setAttachments] = useState<ChatAttachment[]>([]);\n const [modeSelectorOpen, setModeSelectorOpen] = useState(false);\n const [pendingSend, setPendingSend] = useState(false);\n const [priority, setPriority] = useState<MessagePriority>('next');\n const textareaRef = useRef<HTMLTextAreaElement>(null);\n const mobileTextareaRef = useRef<HTMLTextAreaElement>(null);\n const fileInputRef = useRef<HTMLInputElement>(null);\n const slashItemsRef = useRef<SlashItem[]>([]);\n const fileItemsRef = useRef<FileNode[]>([]);\n const resizeRafRef = useRef(0);\n // Track picker open state to avoid unnecessary parent callbacks per keystroke\n const slashPickerOpenRef = useRef(false);\n const filePickerOpenRef = useRef(false);\n // CSS field-sizing: content handles auto-resize natively (Safari 18.2+, Chrome 123+).\n // Only fall back to JS scrollHeight resize when unsupported.\n const needsJsResize = useRef(\n typeof CSS === \"undefined\" || !CSS.supports(\"field-sizing\", \"content\"),\n );\n\n // File index: subscribe imperatively to avoid re-renders on every file store update.\n // The component only needs fileIndex for the effect below (populating fileItemsRef),\n // not for rendering — so we use Zustand's subscribe() instead of selector hooks.\n\n /** Write value to both textareas + ref + update hasText state */\n const writeTextareas = useCallback((newValue: string) => {\n valueRef.current = newValue;\n if (textareaRef.current) textareaRef.current.value = newValue;\n if (mobileTextareaRef.current) mobileTextareaRef.current.value = newValue;\n setHasText(newValue.trim().length > 0);\n }, []);\n\n /** Get the currently visible textarea */\n const getVisibleTextarea = useCallback(() => {\n return window.matchMedia(\"(min-width: 768px)\").matches\n ? textareaRef.current\n : mobileTextareaRef.current;\n }, []);\n\n // Voice input (Web Speech API)\n const voice = useVoiceInput();\n // Store pre-voice text so voice appends to existing input\n const preVoiceTextRef = useRef(\"\");\n const voiceResultCb = useCallback((text: string) => {\n const prefix = preVoiceTextRef.current;\n const newValue = prefix ? prefix + \" \" + text : text;\n writeTextareas(newValue);\n // Auto-resize textarea (only when CSS field-sizing is unsupported)\n if (needsJsResize.current) {\n requestAnimationFrame(() => {\n const ta = getVisibleTextarea();\n if (ta) {\n ta.style.height = \"auto\";\n ta.style.height = Math.min(ta.scrollHeight, 160) + \"px\";\n }\n });\n }\n }, [writeTextareas, getVisibleTextarea]);\n const handleVoiceToggle = useCallback(() => {\n if (voice.isListening) {\n voice.stop();\n } else {\n preVoiceTextRef.current = valueRef.current.trim();\n voice.start(voiceResultCb);\n }\n }, [voice.isListening, voice.start, voice.stop, voiceResultCb]);\n\n // Listen for global keyboard shortcut (Cmd+Shift+V) to toggle voice\n useEffect(() => {\n const handler = () => { if (voice.supported) handleVoiceToggle(); };\n window.addEventListener(\"toggle-voice-input\", handler);\n return () => window.removeEventListener(\"toggle-voice-input\", handler);\n }, [voice.supported, handleVoiceToggle]);\n\n // Listen for \"Send to Chat\" from terminal or other tabs — add as attachment chip\n useEffect(() => {\n const handler = (e: Event) => {\n const { text, label } = (e as CustomEvent).detail ?? {};\n if (!text) return;\n window.dispatchEvent(new Event(\"ppm:send-to-chat:ack\"));\n const att: ChatAttachment = {\n id: randomId(),\n name: label ?? \"Terminal output\",\n file: new File([], \"terminal-output.txt\"),\n isImage: false,\n textContent: text,\n status: \"ready\",\n };\n setAttachments((prev) => [...prev, att]);\n getVisibleTextarea()?.focus();\n };\n window.addEventListener(\"ppm:send-to-chat\", handler);\n return () => window.removeEventListener(\"ppm:send-to-chat\", handler);\n }, [getVisibleTextarea]);\n\n // Apply initialValue when it changes (e.g. \"Ask AI\" from command palette)\n useEffect(() => {\n if (initialValue) {\n writeTextareas(initialValue);\n // Focus and move cursor to end\n setTimeout(() => {\n const ta = textareaRef.current;\n if (ta) { ta.focus(); ta.selectionStart = ta.selectionEnd = ta.value.length; }\n }, 50);\n }\n }, [initialValue]); // eslint-disable-line react-hooks/exhaustive-deps\n\n // Auto-focus on mount when requested\n useEffect(() => {\n if (!autoFocus) return;\n setTimeout(() => { getVisibleTextarea()?.focus(); }, 100);\n }, []); // eslint-disable-line react-hooks/exhaustive-deps\n\n // Fetch slash items from server\n const fetchSlashItems = useCallback(() => {\n if (!projectName) {\n slashItemsRef.current = [];\n onSlashItemsLoaded?.([], []);\n return;\n }\n api\n .get<{ items: SlashItem[]; recentNames: string[] }>(`${projectUrl(projectName)}/chat/slash-items`)\n .then((data) => {\n slashItemsRef.current = data.items;\n onSlashItemsLoaded?.(data.items, data.recentNames);\n })\n .catch(() => {\n slashItemsRef.current = [];\n onSlashItemsLoaded?.([], []);\n });\n }, [projectName, onSlashItemsLoaded]);\n\n // Fetch slash items when projectName changes\n useEffect(() => { fetchSlashItems(); }, [fetchSlashItems]);\n\n // Re-fetch when cache is invalidated via refresh button\n useEffect(() => {\n const handler = () => fetchSlashItems();\n window.addEventListener(\"ppm:slash-items-refresh\", handler);\n return () => window.removeEventListener(\"ppm:slash-items-refresh\", handler);\n }, [fetchSlashItems]);\n\n // Sync file picker items from store index — subscribe imperatively to avoid re-renders.\n // Reads fileIndex on mount + whenever fileIndex/indexStatus changes in the store.\n useEffect(() => {\n const syncFromStore = () => {\n if (!projectName) {\n fileItemsRef.current = [];\n onFileItemsLoaded?.([]);\n return;\n }\n const { fileIndex } = useFileStore.getState();\n const nodes: FileNode[] = fileIndex.map((e) => ({ name: e.name, path: e.path, type: e.type }));\n fileItemsRef.current = nodes;\n onFileItemsLoaded?.(nodes);\n };\n syncFromStore();\n // Track previous values to only sync on relevant changes\n let prevIdx = useFileStore.getState().fileIndex;\n let prevStatus = useFileStore.getState().indexStatus;\n return useFileStore.subscribe((state) => {\n if (state.fileIndex !== prevIdx || state.indexStatus !== prevStatus) {\n prevIdx = state.fileIndex;\n prevStatus = state.indexStatus;\n syncFromStore();\n }\n });\n }, [projectName]); // eslint-disable-line react-hooks/exhaustive-deps\n\n // Handle parent selecting a slash item\n useEffect(() => {\n if (!slashSelected) return;\n const el = getVisibleTextarea();\n if (!el) return;\n const text = el.value;\n const cursorPos = el.selectionStart;\n const textBefore = text.slice(0, cursorPos);\n const textAfter = text.slice(cursorPos);\n // Find the /query pattern before cursor and replace it\n const replaced = textBefore.replace(/(?:^|\\s)\\/\\S*$/, (match) => {\n const prefix = match.startsWith(\"/\") ? \"\" : match[0]; // preserve whitespace\n return `${prefix}/${slashSelected.name} `;\n });\n writeTextareas(replaced + textAfter);\n onSlashStateChange?.(false, \"\");\n slashPickerOpenRef.current = false;\n onFileStateChange?.(false, \"\");\n filePickerOpenRef.current = false;\n el.focus();\n setTimeout(() => {\n el.selectionStart = el.selectionEnd = replaced.length;\n }, 0);\n }, [slashSelected]); // eslint-disable-line react-hooks/exhaustive-deps\n\n // Handle parent selecting a file\n useEffect(() => {\n if (!fileSelected) return;\n const el = getVisibleTextarea();\n if (!el) return;\n\n const text = el.value;\n const cursorPos = el.selectionStart;\n const textBefore = text.slice(0, cursorPos);\n const textAfter = text.slice(cursorPos);\n // Find the @ trigger before cursor\n const atMatch = textBefore.match(/@(\\S*)$/);\n if (atMatch) {\n const start = textBefore.length - atMatch[0].length;\n const newText = textBefore.slice(0, start) + `@${fileSelected.path} ` + textAfter;\n writeTextareas(newText);\n const newCursorPos = start + fileSelected.path.length + 2; // +2 for @ and space\n setTimeout(() => {\n el.selectionStart = el.selectionEnd = newCursorPos;\n el.focus();\n }, 0);\n } else {\n // Fallback: append at end\n const newText = text + `@${fileSelected.path} `;\n writeTextareas(newText);\n setTimeout(() => {\n el.selectionStart = el.selectionEnd = newText.length;\n el.focus();\n }, 0);\n }\n onFileStateChange?.(false, \"\");\n filePickerOpenRef.current = false;\n }, [fileSelected]); // eslint-disable-line react-hooks/exhaustive-deps\n\n // Handle external files dropped on parent (ChatTab)\n useEffect(() => {\n if (!externalFiles || externalFiles.length === 0) return;\n processFiles(externalFiles);\n }, [externalFiles]); // eslint-disable-line react-hooks/exhaustive-deps\n\n // Handle external paths from file tree drag or disambiguation\n useEffect(() => {\n if (!externalPaths || externalPaths.length === 0) return;\n const pathRefs = externalPaths.map((p) => `@${p}`).join(\" \");\n const cur = valueRef.current;\n const sep = cur.length > 0 && !cur.endsWith(\" \") ? \" \" : \"\";\n writeTextareas(cur + sep + pathRefs + \" \");\n getVisibleTextarea()?.focus();\n onExternalPathsConsumed?.();\n }, [externalPaths]); // eslint-disable-line react-hooks/exhaustive-deps\n\n /** Upload a single file to the server, return server path */\n const uploadFile = useCallback(\n async (file: File): Promise<string | null> => {\n if (!projectName) return null;\n try {\n const form = new FormData();\n form.append(\"files\", file);\n const headers: HeadersInit = {};\n const token = getAuthToken();\n if (token) headers[\"Authorization\"] = `Bearer ${token}`;\n const res = await fetch(`${projectUrl(projectName)}/chat/upload`, {\n method: \"POST\",\n headers,\n body: form,\n });\n const json = await res.json();\n if (json.ok && Array.isArray(json.data) && json.data.length > 0) {\n return json.data[0].path as string;\n }\n return null;\n } catch {\n return null;\n }\n },\n [projectName],\n );\n\n /** Process dropped/pasted/selected files — resolves paths via server when possible */\n const processFiles = useCallback(\n async (files: File[]) => {\n for (const file of files) {\n // Step 1: Try server-side filename resolution for all files\n if (projectName) {\n try {\n const data = await api.get<{ matches: FileNode[] }>(\n `${projectUrl(projectName)}/files/resolve?name=${encodeURIComponent(file.name)}`,\n );\n if (data.matches.length === 1) {\n const cur = valueRef.current;\n const sep = cur.length > 0 && !cur.endsWith(\" \") ? \" \" : \"\";\n writeTextareas(cur + sep + `@${data.matches[0]!.path} `);\n continue;\n }\n if (data.matches.length > 1) {\n onDisambiguate?.(data.matches);\n continue;\n }\n // 0 matches → fall through to existing behavior\n } catch {\n // Resolve failed → fall through\n }\n }\n\n // Step 2: Fallback — upload supported files, insert name for unsupported\n if (!isSupportedFile(file)) {\n const cur = valueRef.current;\n const sep = cur.length > 0 && !cur.endsWith(\" \") ? \" \" : \"\";\n writeTextareas(cur + sep + file.name);\n continue;\n }\n\n const id = randomId();\n const isImg = isImageFile(file);\n const previewUrl = isImg ? URL.createObjectURL(file) : undefined;\n\n const att: ChatAttachment = {\n id,\n name: file.name,\n file,\n isImage: isImg,\n previewUrl,\n status: \"uploading\",\n };\n\n setAttachments((prev) => [...prev, att]);\n\n // Upload in background\n uploadFile(file).then((serverPath) => {\n setAttachments((prev) =>\n prev.map((a) =>\n a.id === id\n ? { ...a, serverPath: serverPath ?? undefined, status: serverPath ? \"ready\" : \"error\" }\n : a,\n ),\n );\n });\n }\n (mobileTextareaRef.current ?? textareaRef.current)?.focus();\n },\n [uploadFile, writeTextareas, projectName, onDisambiguate],\n );\n\n const removeAttachment = useCallback((id: string) => {\n setAttachments((prev) => {\n const att = prev.find((a) => a.id === id);\n if (att?.previewUrl) URL.revokeObjectURL(att.previewUrl);\n return prev.filter((a) => a.id !== id);\n });\n }, []);\n\n /** Execute the actual send (called directly or after uploads complete) */\n const executeSend = useCallback(() => {\n const trimmed = valueRef.current.trim();\n const readyAttachments = attachments.filter((a) => a.status === \"ready\");\n if (!trimmed && readyAttachments.length === 0) {\n setPendingSend(false);\n return;\n }\n\n onSlashStateChange?.(false, \"\");\n slashPickerOpenRef.current = false;\n onFileStateChange?.(false, \"\");\n filePickerOpenRef.current = false;\n if (voice.isListening) voice.stop();\n onSend(trimmed, readyAttachments, isStreaming ? priority : undefined);\n writeTextareas(\"\");\n // Revoke preview URLs\n for (const att of attachments) {\n if (att.previewUrl) URL.revokeObjectURL(att.previewUrl);\n }\n setAttachments([]);\n setPendingSend(false);\n setPriority('next');\n if (needsJsResize.current) {\n if (textareaRef.current) textareaRef.current.style.height = \"auto\";\n if (mobileTextareaRef.current) mobileTextareaRef.current.style.height = \"auto\";\n }\n }, [attachments, onSend, onSlashStateChange, onFileStateChange, isStreaming, priority, writeTextareas]);\n\n const handleSend = useCallback(() => {\n if (disabled) return;\n\n // If files are still uploading, queue the send for when they finish\n if (attachments.some((a) => a.status === \"uploading\")) {\n const trimmed = valueRef.current.trim();\n if (trimmed || attachments.some((a) => a.status !== \"error\")) {\n setPendingSend(true);\n }\n return;\n }\n\n executeSend();\n }, [attachments, disabled, executeSend]);\n\n // Auto-send when queued and all uploads complete\n useEffect(() => {\n if (!pendingSend) return;\n if (attachments.some((a) => a.status === \"uploading\")) return;\n executeSend();\n }, [pendingSend, attachments, executeSend]);\n\n const handleKeyDown = useCallback(\n (e: KeyboardEvent<HTMLTextAreaElement>) => {\n if (e.key === \"Enter\" && !e.shiftKey) {\n e.preventDefault();\n handleSend();\n return;\n }\n // Shift+Tab: cycle permission mode\n if (e.shiftKey && e.key === \"Tab\") {\n e.preventDefault();\n const modeIds = [\"default\", \"acceptEdits\", \"plan\", \"bypassPermissions\"];\n const idx = modeIds.indexOf(permissionMode ?? \"bypassPermissions\");\n const next = modeIds[(idx + 1) % modeIds.length]!;\n onModeChange?.(next);\n }\n },\n [handleSend, permissionMode, onModeChange],\n );\n\n const updatePickerState = useCallback(\n (text: string, cursorPos: number) => {\n const textBefore = text.slice(0, cursorPos);\n\n // Fast path: if no trigger chars exist at all, skip regex + callbacks\n const hasSlash = textBefore.includes(\"/\");\n const hasAt = textBefore.includes(\"@\");\n if (!hasSlash && !hasAt) {\n // Close pickers only if they were actually open (avoid unnecessary parent setState)\n if (slashPickerOpenRef.current) { onSlashStateChange?.(false, \"\"); slashPickerOpenRef.current = false; }\n if (filePickerOpenRef.current) { onFileStateChange?.(false, \"\"); filePickerOpenRef.current = false; }\n return;\n }\n\n // Check for slash anywhere in text (after whitespace or at start)\n if (hasSlash) {\n const slashMatch = textBefore.match(/(?:^|\\s)\\/(\\S*)$/);\n if (slashMatch && slashItemsRef.current.length > 0) {\n const filter = slashMatch[1] ?? \"\";\n onSlashStateChange?.(true, filter);\n slashPickerOpenRef.current = true;\n if (filePickerOpenRef.current) { onFileStateChange?.(false, \"\"); filePickerOpenRef.current = false; }\n return;\n }\n }\n\n // Check for @ anywhere in text (after whitespace or at start)\n if (hasAt) {\n const atMatch = textBefore.match(/@(\\S*)$/);\n if (atMatch && fileItemsRef.current.length > 0) {\n onFileStateChange?.(true, atMatch[1] ?? \"\");\n filePickerOpenRef.current = true;\n if (slashPickerOpenRef.current) { onSlashStateChange?.(false, \"\"); slashPickerOpenRef.current = false; }\n return;\n }\n }\n\n // Nothing matched — close both pickers (only if open)\n if (slashPickerOpenRef.current) { onSlashStateChange?.(false, \"\"); slashPickerOpenRef.current = false; }\n if (filePickerOpenRef.current) { onFileStateChange?.(false, \"\"); filePickerOpenRef.current = false; }\n },\n [onSlashStateChange, onFileStateChange],\n );\n\n /** Unified onChange for both textareas — updates ref, syncs other textarea, triggers picker */\n const handleTextareaChange = useCallback(\n (e: React.ChangeEvent<HTMLTextAreaElement>) => {\n const el = e.target;\n const text = el.value;\n valueRef.current = text;\n // Sync the other textarea (handles viewport rotation edge case)\n const other = el === textareaRef.current ? mobileTextareaRef.current : textareaRef.current;\n if (other) other.value = text;\n // Only trigger re-render on empty↔non-empty transition (for send button state)\n setHasText(text.trim().length > 0);\n // Update picker state (slash/file autocomplete)\n updatePickerState(text, el.selectionStart);\n // JS auto-resize fallback — only when CSS field-sizing: content is unsupported\n if (needsJsResize.current) {\n if (resizeRafRef.current) cancelAnimationFrame(resizeRafRef.current);\n resizeRafRef.current = requestAnimationFrame(() => {\n resizeRafRef.current = 0;\n el.style.height = \"auto\";\n el.style.height = Math.min(el.scrollHeight, el === mobileTextareaRef.current ? 80 : 160) + \"px\";\n });\n }\n },\n [updatePickerState],\n );\n\n /** Handle paste — intercept images from clipboard */\n const handlePaste = useCallback(\n (e: ClipboardEvent<HTMLTextAreaElement>) => {\n const items = e.clipboardData?.items;\n if (!items) return;\n\n const files: File[] = [];\n for (const item of items) {\n if (item.kind === \"file\") {\n const file = item.getAsFile();\n if (file) files.push(file);\n }\n }\n if (files.length > 0) {\n e.preventDefault();\n processFiles(files);\n }\n },\n [processFiles],\n );\n\n /** Handle drop directly on textarea */\n const handleDrop = useCallback(\n (e: DragEvent<HTMLTextAreaElement>) => {\n e.preventDefault();\n // Check for internal file tree drag first\n const ppmPath = e.dataTransfer.getData(\"application/x-ppm-path\");\n if (ppmPath) {\n const cur = valueRef.current;\n const sep = cur.length > 0 && !cur.endsWith(\" \") ? \" \" : \"\";\n writeTextareas(cur + sep + `@${ppmPath} `);\n getVisibleTextarea()?.focus();\n return;\n }\n const files = Array.from(e.dataTransfer.files);\n if (files.length > 0) processFiles(files);\n },\n [processFiles, writeTextareas, getVisibleTextarea],\n );\n\n const handleDragOver = useCallback((e: DragEvent<HTMLTextAreaElement>) => {\n e.preventDefault();\n }, []);\n\n /** Open native file picker */\n const handleAttachClick = useCallback(() => {\n fileInputRef.current?.click();\n }, []);\n\n const handleFileInputChange = useCallback(\n (e: React.ChangeEvent<HTMLInputElement>) => {\n const files = Array.from(e.target.files ?? []);\n if (files.length > 0) processFiles(files);\n // Reset so same file can be selected again\n e.target.value = \"\";\n },\n [processFiles],\n );\n\n const hasContent = hasText || attachments.some((a) => a.status !== \"error\");\n const showCancel = isStreaming && !hasContent;\n\n return (\n <div className=\"p-2 md:p-3 bg-background\">\n {/* Rounded input container */}\n <div\n className=\"border border-border rounded-xl md:rounded-2xl bg-surface shadow-sm cursor-text\"\n onClick={(e) => {\n if (disabled) return;\n // Only focus when clicking outside the textarea (e.g. padding area)\n if (e.target instanceof HTMLTextAreaElement) return;\n getVisibleTextarea()?.focus();\n }}\n >\n {/* Attachment chips (inside container, aligned with input) */}\n <AttachmentChips attachments={attachments} onRemove={removeAttachment} />\n {/* Mobile: mode chip + provider selector row */}\n <div className=\"flex items-center gap-1 px-2 pt-2 md:hidden relative\">\n <ModeChip\n mode={permissionMode ?? \"bypassPermissions\"}\n onClick={() => setModeSelectorOpen((v) => !v)}\n />\n <ModeSelector\n value={permissionMode ?? \"bypassPermissions\"}\n onChange={(m) => onModeChange?.(m)}\n open={modeSelectorOpen}\n onOpenChange={setModeSelectorOpen}\n />\n {onProviderChange && projectName && (\n <ProviderSelector\n value={providerId ?? \"claude\"}\n onChange={onProviderChange}\n projectName={projectName}\n />\n )}\n {isStreaming && <PriorityToggle value={priority} onChange={setPriority} />}\n </div>\n {/* Mobile: single row — attach + textarea + mic + send */}\n <div className=\"flex items-end gap-1 md:hidden px-2 py-2\">\n <button\n type=\"button\"\n onClick={(e) => { e.stopPropagation(); handleAttachClick(); }}\n disabled={disabled}\n className=\"flex items-center justify-center size-7 shrink-0 rounded-full text-text-subtle hover:text-text-primary transition-colors disabled:opacity-50\"\n aria-label=\"Attach file\"\n >\n <Paperclip className=\"size-4\" />\n </button>\n <textarea\n ref={mobileTextareaRef}\n defaultValue={initialValue ?? \"\"}\n onChange={handleTextareaChange}\n onKeyDown={handleKeyDown}\n onPaste={handlePaste}\n onDrop={handleDrop}\n onDragOver={handleDragOver}\n placeholder={isStreaming ? \"Follow-up...\" : \"Ask anything...\"}\n disabled={disabled}\n rows={1}\n className=\"flex-1 resize-none bg-transparent py-1.5 text-sm text-foreground placeholder:text-text-subtle focus:outline-none disabled:opacity-50 max-h-20 [field-sizing:content]\"\n />\n {voice.supported && (\n <button\n type=\"button\"\n onClick={(e) => { e.stopPropagation(); handleVoiceToggle(); }}\n disabled={disabled}\n className={`flex items-center justify-center size-7 shrink-0 rounded-full transition-colors disabled:opacity-50 ${\n voice.isListening\n ? \"bg-red-600 text-white animate-pulse\"\n : \"text-text-subtle hover:text-text-primary\"\n }`}\n aria-label={voice.isListening ? \"Stop voice input\" : \"Start voice input\"}\n >\n {voice.isListening ? <MicOff className=\"size-4\" /> : <Mic className=\"size-4\" />}\n </button>\n )}\n {showCancel ? (\n <button\n onClick={(e) => { e.stopPropagation(); onCancel?.(); }}\n className=\"flex items-center justify-center size-7 shrink-0 rounded-full bg-red-600 text-white hover:bg-red-500 transition-colors\"\n aria-label=\"Stop\"\n >\n <Square className=\"size-3\" />\n </button>\n ) : (\n <button\n onClick={(e) => { e.stopPropagation(); pendingSend ? setPendingSend(false) : handleSend(); }}\n disabled={disabled || !hasContent}\n className=\"flex items-center justify-center size-7 shrink-0 rounded-full bg-primary text-primary-foreground hover:bg-primary/90 disabled:opacity-30 transition-colors\"\n aria-label={pendingSend ? \"Cancel queued send\" : \"Send\"}\n >\n {pendingSend ? <Loader2 className=\"size-3.5 animate-spin\" /> : <ArrowUp className=\"size-3.5\" />}\n </button>\n )}\n </div>\n\n {/* Desktop: textarea + action bar below */}\n <div className=\"hidden md:block\">\n <textarea\n ref={textareaRef}\n defaultValue={initialValue ?? \"\"}\n onChange={handleTextareaChange}\n onKeyDown={handleKeyDown}\n onPaste={handlePaste}\n onDrop={handleDrop}\n onDragOver={handleDragOver}\n placeholder={isStreaming ? \"Follow-up or Stop...\" : \"Ask anything...\"}\n disabled={disabled}\n rows={1}\n className=\"w-full resize-none bg-transparent px-4 pt-3 pb-1 text-sm text-foreground placeholder:text-text-subtle focus:outline-none disabled:opacity-50 max-h-40 [field-sizing:content]\"\n />\n <div className=\"flex items-center justify-between px-3 pb-2\">\n <div className=\"flex items-center gap-1\">\n <button\n type=\"button\"\n onClick={(e) => { e.stopPropagation(); handleAttachClick(); }}\n disabled={disabled}\n className=\"flex items-center justify-center size-8 rounded-full text-text-subtle hover:text-text-primary hover:bg-surface-elevated transition-colors disabled:opacity-50\"\n aria-label=\"Attach file\"\n >\n <Paperclip className=\"size-4\" />\n </button>\n {/* Mode indicator chip */}\n <div className=\"relative\">\n <ModeChip\n mode={permissionMode ?? \"bypassPermissions\"}\n onClick={() => setModeSelectorOpen((v) => !v)}\n />\n <ModeSelector\n value={permissionMode ?? \"bypassPermissions\"}\n onChange={(m) => onModeChange?.(m)}\n open={modeSelectorOpen}\n onOpenChange={setModeSelectorOpen}\n />\n </div>\n {/* Provider selector — only when no active session */}\n {onProviderChange && projectName && (\n <ProviderSelector\n value={providerId ?? \"claude\"}\n onChange={onProviderChange}\n projectName={projectName}\n />\n )}\n {isStreaming && <PriorityToggle value={priority} onChange={setPriority} />}\n </div>\n <div className=\"flex items-center gap-1\">\n {voice.supported && (\n <button\n type=\"button\"\n onClick={(e) => { e.stopPropagation(); handleVoiceToggle(); }}\n disabled={disabled}\n className={`flex items-center justify-center size-8 rounded-full transition-colors disabled:opacity-50 ${\n voice.isListening\n ? \"bg-red-600 text-white animate-pulse\"\n : \"text-text-subtle hover:text-text-primary hover:bg-surface-elevated\"\n }`}\n aria-label={voice.isListening ? \"Stop voice input\" : \"Start voice input\"}\n >\n {voice.isListening ? <MicOff className=\"size-4\" /> : <Mic className=\"size-4\" />}\n </button>\n )}\n {showCancel ? (\n <button\n onClick={(e) => { e.stopPropagation(); onCancel?.(); }}\n className=\"flex items-center justify-center size-8 rounded-full bg-red-600 text-white hover:bg-red-500 transition-colors\"\n aria-label=\"Stop response\"\n >\n <Square className=\"size-3.5\" />\n </button>\n ) : (\n <button\n onClick={(e) => { e.stopPropagation(); pendingSend ? setPendingSend(false) : handleSend(); }}\n disabled={disabled || !hasContent}\n className=\"flex items-center justify-center size-8 rounded-full bg-primary text-primary-foreground hover:bg-primary/90 disabled:opacity-30 disabled:cursor-not-allowed transition-colors\"\n aria-label={pendingSend ? \"Cancel queued send\" : \"Send message\"}\n >\n {pendingSend ? <Loader2 className=\"size-4 animate-spin\" /> : <ArrowUp className=\"size-4\" />}\n </button>\n )}\n </div>\n </div>\n </div>\n </div>\n\n <input ref={fileInputRef} type=\"file\" multiple className=\"hidden\" onChange={handleFileInputChange} />\n </div>\n );\n});\n\n/** Small chip showing current permission mode */\nfunction ModeChip({ mode, onClick }: { mode: string; onClick: () => void }) {\n const Icon = getModeIcon(mode);\n const label = getModeLabel(mode);\n return (\n <button\n type=\"button\"\n onClick={(e) => { e.stopPropagation(); onClick(); }}\n className=\"inline-flex items-center gap-1 px-2 py-1 rounded-md text-[11px] text-text-subtle hover:text-text-primary hover:bg-surface-elevated transition-colors border border-transparent hover:border-border\"\n aria-label={`Permission mode: ${label}`}\n >\n <Icon className=\"size-3\" />\n <span className=\"max-w-[100px] truncate\">{label}</span>\n </button>\n );\n}\n\nconst PRIORITY_OPTIONS: { value: MessagePriority; label: string; Icon: typeof Zap }[] = [\n { value: 'now', label: 'Interrupt', Icon: Zap },\n { value: 'next', label: 'Queue', Icon: ListOrdered },\n { value: 'later', label: 'Later', Icon: Clock },\n];\n\n/** Compact priority toggle — visible only during streaming */\nfunction PriorityToggle({ value, onChange }: { value: MessagePriority; onChange: (v: MessagePriority) => void }) {\n const cycle = useCallback(() => {\n const order: MessagePriority[] = ['next', 'later', 'now'];\n const idx = order.indexOf(value);\n onChange(order[(idx + 1) % order.length]!);\n }, [value, onChange]);\n\n const current = PRIORITY_OPTIONS.find((o) => o.value === value) ?? PRIORITY_OPTIONS[1]!;\n const Icon = current.Icon;\n\n return (\n <button\n type=\"button\"\n onClick={(e) => { e.stopPropagation(); cycle(); }}\n className=\"inline-flex items-center gap-1 px-2 py-1 rounded-md text-[11px] text-text-subtle hover:text-text-primary hover:bg-surface-elevated transition-colors border border-transparent hover:border-border\"\n aria-label={`Message priority: ${current.label}`}\n title={`Priority: ${current.label} (click to cycle)`}\n >\n <Icon className=\"size-3\" />\n <span>{current.label}</span>\n </button>\n );\n}\n","/** Minimal interface — any object with name + description is searchable */\nexport interface FuzzySearchable {\n name: string;\n description: string;\n}\n\n/** Iterative Levenshtein distance (single-row DP) */\nexport function levenshtein(a: string, b: string): number {\n if (a === b) return 0;\n if (a.length === 0) return b.length;\n if (b.length === 0) return a.length;\n\n let prev = Array.from({ length: b.length + 1 }, (_, i) => i);\n let curr = new Array<number>(b.length + 1);\n\n for (let i = 1; i <= a.length; i++) {\n curr[0] = i;\n for (let j = 1; j <= b.length; j++) {\n const cost = a[i - 1] === b[j - 1] ? 0 : 1;\n curr[j] = Math.min(\n curr[j - 1]! + 1, // insertion\n prev[j]! + 1, // deletion\n prev[j - 1]! + cost, // substitution\n );\n }\n [prev, curr] = [curr, prev];\n }\n return prev[b.length]!;\n}\n\ninterface FuzzyScore { rank: number; distance: number }\n\n/**\n * Score a query against a candidate string.\n * Returns null if no reasonable match. Rank: 0=prefix, 1=contains, 2=fuzzy.\n */\nexport function scoreFuzzy(query: string, candidate: string): FuzzyScore | null {\n const lq = query.toLowerCase();\n const lc = candidate.toLowerCase();\n\n if (lc.startsWith(lq)) return { rank: 0, distance: 0 };\n if (lc.includes(lq)) return { rank: 1, distance: lc.indexOf(lq) };\n\n const maxDist = Math.max(Math.floor(lq.length * 0.4), 2);\n const dist = levenshtein(lq, lc.slice(0, lq.length + maxDist));\n if (dist <= maxDist) return { rank: 2, distance: dist };\n\n return null;\n}\n\n/**\n * Search items by query with fuzzy matching.\n * Recently used items get a rank boost (sorted earlier within same rank tier).\n * Returns ranked results (best match first), truncated to limit.\n */\nexport function searchFuzzy<T extends FuzzySearchable>(\n items: T[],\n query: string,\n limit = 20,\n recentNames: string[] = [],\n): T[] {\n if (!query) return items;\n // Cap query length to prevent quadratic blowup in Levenshtein\n query = query.slice(0, 50);\n\n const recentSet = new Set(recentNames);\n const scored: Array<{ item: T; rank: number; distance: number; recent: boolean }> = [];\n\n for (const item of items) {\n const nameScore = scoreFuzzy(query, item.name);\n const descScore = scoreFuzzy(query, item.description);\n const best = [nameScore, descScore]\n .filter((s): s is FuzzyScore => s !== null)\n .sort((a, b) => a.rank - b.rank || a.distance - b.distance)[0];\n\n if (best) scored.push({ item, rank: best.rank, distance: best.distance, recent: recentSet.has(item.name) });\n }\n\n scored.sort((a, b) =>\n a.rank - b.rank\n || a.distance - b.distance\n || (a.recent === b.recent ? 0 : a.recent ? -1 : 1)\n || a.item.name.localeCompare(b.item.name),\n );\n\n return scored.slice(0, limit).map((s) => s.item);\n}\n","import { useState, useEffect, useRef, useCallback, useMemo, type KeyboardEvent } from \"react\";\nimport { Sparkles, Terminal, Zap, RefreshCw, Clock } from \"lucide-react\";\nimport { api, projectUrl } from \"@/lib/api-client\";\nimport { searchFuzzy } from \"../../../shared/fuzzy-search\";\n\nexport interface SlashItem {\n type: \"skill\" | \"command\" | \"builtin\";\n name: string;\n description: string;\n argumentHint?: string;\n scope?: \"project\" | \"user\" | \"bundled\";\n category?: string;\n aliases?: string[];\n}\n\ninterface SlashCommandPickerProps {\n items: SlashItem[];\n filter: string;\n onSelect: (item: SlashItem) => void;\n onClose: () => void;\n visible: boolean;\n /** Recently used item names (most recent first) */\n recentNames?: string[];\n /** Project name for cache invalidation */\n projectName?: string;\n}\n\nexport function SlashCommandPicker({\n items,\n filter,\n onSelect,\n onClose,\n visible,\n recentNames = [],\n projectName,\n}: SlashCommandPickerProps) {\n const [selectedIndex, setSelectedIndex] = useState(0);\n const [refreshing, setRefreshing] = useState(false);\n const listRef = useRef<HTMLDivElement>(null);\n\n const recentSet = useMemo(() => new Set(recentNames), [recentNames]);\n\n // Build display list: fuzzy search when filter is set, recents-first when idle\n const displayItems = useMemo(() => {\n if (filter) {\n // Client-side fuzzy search (Levenshtein) — replaces old server-side search\n return { items: searchFuzzy(items, filter, 20, recentNames), recentCount: 0 };\n }\n\n // No filter — show all items with recents first\n if (recentNames.length > 0) {\n const recents: SlashItem[] = [];\n const rest: SlashItem[] = [];\n for (const item of items) {\n if (recentSet.has(item.name)) recents.push(item);\n else rest.push(item);\n }\n recents.sort((a, b) => recentNames.indexOf(a.name) - recentNames.indexOf(b.name));\n return { items: [...recents, ...rest], recentCount: recents.length };\n }\n return { items, recentCount: 0 };\n }, [items, filter, recentNames, recentSet]);\n\n const filtered = displayItems.items;\n const recentCount = displayItems.recentCount;\n\n // Reset selection when filter changes\n useEffect(() => {\n setSelectedIndex(0);\n }, [filter]);\n\n // Scroll selected item into view\n useEffect(() => {\n const list = listRef.current;\n if (!list) return;\n const selected = list.children[selectedIndex] as HTMLElement | undefined;\n selected?.scrollIntoView({ block: \"nearest\" });\n }, [selectedIndex]);\n\n const handleKeyDown = useCallback(\n (e: KeyboardEvent | globalThis.KeyboardEvent) => {\n if (!visible || filtered.length === 0) return false;\n\n switch (e.key) {\n case \"ArrowUp\":\n e.preventDefault();\n setSelectedIndex((i) => (i > 0 ? i - 1 : filtered.length - 1));\n return true;\n case \"ArrowDown\":\n e.preventDefault();\n setSelectedIndex((i) => (i < filtered.length - 1 ? i + 1 : 0));\n return true;\n case \"Enter\":\n case \"Tab\":\n e.preventDefault();\n if (filtered[selectedIndex]) {\n onSelect(filtered[selectedIndex]);\n }\n return true;\n case \"Escape\":\n e.preventDefault();\n onClose();\n return true;\n }\n return false;\n },\n [visible, filtered, selectedIndex, onSelect, onClose],\n );\n\n // Global keyboard handler (captures before textarea)\n useEffect(() => {\n if (!visible) return;\n const handler = (e: globalThis.KeyboardEvent) => {\n if (handleKeyDown(e)) e.stopPropagation();\n };\n document.addEventListener(\"keydown\", handler, true);\n return () => document.removeEventListener(\"keydown\", handler, true);\n }, [visible, handleKeyDown]);\n\n const handleRefresh = useCallback(() => {\n if (!projectName || refreshing) return;\n setRefreshing(true);\n api.del(`${projectUrl(projectName)}/chat/slash-items/cache`)\n .then(() => {\n // Trigger re-fetch by dispatching custom event (MessageInput listens on projectName)\n window.dispatchEvent(new CustomEvent(\"ppm:slash-items-refresh\"));\n })\n .finally(() => setRefreshing(false));\n }, [projectName, refreshing]);\n\n if (!visible || filtered.length === 0) return null;\n\n return (\n <div className=\"max-h-52 overflow-y-auto border-b border-border bg-surface\">\n <div ref={listRef} className=\"py-1\">\n {filtered.map((item, i) => {\n // Show \"Recent\" separator before first item, \"All\" before first non-recent\n const showRecentLabel = recentCount > 0 && i === 0;\n const showAllLabel = recentCount > 0 && i === recentCount;\n\n return (\n <div key={`${item.type}-${item.name}`}>\n {showRecentLabel && (\n <div className=\"flex items-center justify-between px-3 pt-1 pb-0.5\">\n <span className=\"text-[10px] font-medium text-text-subtle uppercase tracking-wider flex items-center gap-1\">\n <Clock className=\"size-3\" />\n Recent\n </span>\n {projectName && (\n <button\n type=\"button\"\n onClick={(e) => { e.stopPropagation(); handleRefresh(); }}\n className=\"text-text-subtle hover:text-text-primary transition-colors p-0.5 rounded\"\n title=\"Refresh skill list\"\n aria-label=\"Refresh skill list\"\n >\n <RefreshCw className={`size-3 ${refreshing ? \"animate-spin\" : \"\"}`} />\n </button>\n )}\n </div>\n )}\n {showAllLabel && (\n <div className=\"px-3 pt-1.5 pb-0.5\">\n <span className=\"text-[10px] font-medium text-text-subtle uppercase tracking-wider\">All</span>\n </div>\n )}\n <button\n className={`flex items-start gap-3 w-full px-3 py-2 text-left transition-colors ${\n i === selectedIndex\n ? \"bg-primary/10 text-primary\"\n : \"hover:bg-surface-hover text-text-primary\"\n }`}\n onMouseEnter={() => setSelectedIndex(i)}\n onClick={() => onSelect(item)}\n >\n <span className=\"shrink-0 mt-0.5\">\n {item.type === \"builtin\" ? (\n <Zap className=\"size-4 text-emerald-500\" />\n ) : item.type === \"skill\" ? (\n <Sparkles className=\"size-4 text-amber-500\" />\n ) : (\n <Terminal className=\"size-4 text-blue-500\" />\n )}\n </span>\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex items-baseline gap-2\">\n <span className=\"font-medium text-sm\">/{item.name}</span>\n {item.argumentHint && (\n <span className=\"text-xs text-text-subtle\">{item.argumentHint}</span>\n )}\n <span className=\"text-xs text-text-subtle capitalize ml-auto\">\n {item.scope === \"bundled\" ? \"PPM\" : item.scope === \"user\" ? \"global\" : item.type}\n </span>\n </div>\n {item.description && (\n <p className=\"text-xs text-text-subtle mt-0.5 line-clamp-2\">\n {item.description}\n </p>\n )}\n </div>\n </button>\n </div>\n );\n })}\n </div>\n </div>\n );\n}\n","import { useState, useEffect, useRef, useCallback, type KeyboardEvent } from \"react\";\nimport { File, Folder } from \"lucide-react\";\nimport type { FileNode } from \"../../../types/project\";\n\ninterface FilePickerProps {\n items: FileNode[];\n filter: string;\n onSelect: (item: FileNode) => void;\n onClose: () => void;\n visible: boolean;\n}\n\nexport function FilePicker({\n items,\n filter,\n onSelect,\n onClose,\n visible,\n}: FilePickerProps) {\n const [selectedIndex, setSelectedIndex] = useState(0);\n const listRef = useRef<HTMLDivElement>(null);\n\n const filtered = (() => {\n if (!filter) return items.slice(0, 50);\n const q = filter.toLowerCase();\n return items\n .filter((node) => node.path.toLowerCase().includes(q) || node.name.toLowerCase().includes(q))\n .slice(0, 50);\n })();\n\n // Reset selection when filter changes\n useEffect(() => {\n setSelectedIndex(0);\n }, [filter]);\n\n // Scroll selected item into view\n useEffect(() => {\n const list = listRef.current;\n if (!list) return;\n const selected = list.children[selectedIndex] as HTMLElement | undefined;\n selected?.scrollIntoView({ block: \"nearest\" });\n }, [selectedIndex]);\n\n const handleKeyDown = useCallback(\n (e: KeyboardEvent | globalThis.KeyboardEvent) => {\n if (!visible || filtered.length === 0) return false;\n\n switch (e.key) {\n case \"ArrowUp\":\n e.preventDefault();\n setSelectedIndex((i) => (i > 0 ? i - 1 : filtered.length - 1));\n return true;\n case \"ArrowDown\":\n e.preventDefault();\n setSelectedIndex((i) => (i < filtered.length - 1 ? i + 1 : 0));\n return true;\n case \"Enter\":\n case \"Tab\":\n e.preventDefault();\n if (filtered[selectedIndex]) {\n onSelect(filtered[selectedIndex]);\n }\n return true;\n case \"Escape\":\n e.preventDefault();\n onClose();\n return true;\n }\n return false;\n },\n [visible, filtered, selectedIndex, onSelect, onClose],\n );\n\n // Global keyboard handler (captures before textarea)\n useEffect(() => {\n if (!visible) return;\n const handler = (e: globalThis.KeyboardEvent) => {\n if (handleKeyDown(e)) e.stopPropagation();\n };\n document.addEventListener(\"keydown\", handler, true);\n return () => document.removeEventListener(\"keydown\", handler, true);\n }, [visible, handleKeyDown]);\n\n if (!visible || filtered.length === 0) return null;\n\n return (\n <div className=\"max-h-52 overflow-y-auto border-b border-border bg-surface\">\n <div ref={listRef} className=\"py-1\">\n {filtered.map((item, i) => (\n <button\n key={item.path}\n className={`flex items-center gap-2 w-full px-3 py-1.5 text-left transition-colors ${\n i === selectedIndex\n ? \"bg-primary/10 text-primary\"\n : \"hover:bg-surface-hover text-text-primary\"\n }`}\n onMouseEnter={() => setSelectedIndex(i)}\n onClick={() => onSelect(item)}\n >\n <span className=\"shrink-0\">\n {item.type === \"directory\" ? (\n <Folder className=\"size-4 text-amber-500\" />\n ) : (\n <File className=\"size-4 text-blue-400\" />\n )}\n </span>\n <span className=\"text-sm truncate\">{item.path}</span>\n </button>\n ))}\n </div>\n </div>\n );\n}\n","import { useState, useEffect, useCallback } from \"react\";\nimport { Plus, Trash2, Pencil, Check, X, RotateCcw } from \"lucide-react\";\nimport { api, projectUrl } from \"@/lib/api-client\";\nimport type { ProjectTag } from \"../../../types/chat\";\n\ninterface TagSettingsSectionProps {\n projectName: string;\n onTagsChanged?: () => void;\n}\n\nexport function TagSettingsSection({ projectName, onTagsChanged }: TagSettingsSectionProps) {\n const [tags, setTags] = useState<ProjectTag[]>([]);\n const [defaultTagId, setDefaultTagId] = useState<number | null>(null);\n const [loading, setLoading] = useState(true);\n const [editingId, setEditingId] = useState<number | null>(null);\n const [editName, setEditName] = useState(\"\");\n const [editColor, setEditColor] = useState(\"\");\n const [newName, setNewName] = useState(\"\");\n const [newColor, setNewColor] = useState(\"#22c55e\");\n const [showAdd, setShowAdd] = useState(false);\n\n const baseUrl = `${projectUrl(projectName)}/tags`;\n\n const loadTags = useCallback(async () => {\n try {\n const data = await api.get<{ tags: ProjectTag[]; defaultTagId: number | null }>(baseUrl);\n setTags(data.tags);\n setDefaultTagId(data.defaultTagId);\n } catch { /* silent */ }\n setLoading(false);\n }, [baseUrl]);\n\n useEffect(() => { loadTags(); }, [loadTags]);\n\n const handleCreate = async () => {\n if (!newName.trim()) return;\n try {\n await api.post(baseUrl, { name: newName.trim(), color: newColor });\n setNewName(\"\");\n setShowAdd(false);\n loadTags();\n onTagsChanged?.();\n } catch { /* silent */ }\n };\n\n const handleUpdate = async (id: number) => {\n try {\n await api.patch(`${baseUrl}/${id}`, { name: editName.trim() || undefined, color: editColor || undefined });\n setEditingId(null);\n loadTags();\n onTagsChanged?.();\n } catch { /* silent */ }\n };\n\n const handleDelete = async (id: number, name: string) => {\n if (!window.confirm(`Delete tag \"${name}\"? Sessions with this tag will become untagged.`)) return;\n try {\n await api.del(`${baseUrl}/${id}`);\n loadTags();\n onTagsChanged?.();\n } catch { /* silent */ }\n };\n\n const handleSetDefault = async (tagId: number) => {\n const newId = tagId === defaultTagId ? null : tagId;\n try {\n await api.patch(`${baseUrl}/default-tag`, { tagId: newId });\n setDefaultTagId(newId);\n } catch { /* silent */ }\n };\n\n const handleReset = async () => {\n try {\n await api.post(`${baseUrl}/reset`, {});\n loadTags();\n onTagsChanged?.();\n } catch { /* silent */ }\n };\n\n if (loading) return <p className=\"text-[11px] text-muted-foreground animate-pulse\">Loading tags...</p>;\n\n return (\n <div className=\"space-y-2\">\n <div className=\"flex items-center justify-between\">\n <h3 className=\"text-xs font-medium text-muted-foreground\">Session Tags</h3>\n <div className=\"flex items-center gap-1\">\n <button onClick={handleReset} className=\"p-1 rounded text-text-subtle hover:text-text-secondary\" title=\"Reset to defaults\">\n <RotateCcw className=\"size-3\" />\n </button>\n <button onClick={() => setShowAdd(!showAdd)} className=\"p-1 rounded text-primary hover:bg-primary/10\" title=\"Add tag\">\n <Plus className=\"size-3.5\" />\n </button>\n </div>\n </div>\n\n {/* Add form */}\n {showAdd && (\n <div className=\"flex items-center gap-1.5 px-1\">\n <input type=\"color\" value={newColor} onChange={(e) => setNewColor(e.target.value)} className=\"size-6 rounded cursor-pointer border-0 p-0\" />\n <input\n value={newName}\n onChange={(e) => setNewName(e.target.value)}\n onKeyDown={(e) => { if (e.key === \"Enter\") handleCreate(); if (e.key === \"Escape\") setShowAdd(false); }}\n placeholder=\"Tag name\"\n className=\"flex-1 min-w-0 bg-surface-elevated text-[11px] text-text-primary px-2 py-1 rounded border border-border outline-none focus:border-primary\"\n autoFocus\n />\n <button onClick={handleCreate} className=\"p-1 text-green-500 hover:text-green-400\"><Check className=\"size-3.5\" /></button>\n <button onClick={() => setShowAdd(false)} className=\"p-1 text-text-subtle hover:text-text-secondary\"><X className=\"size-3.5\" /></button>\n </div>\n )}\n\n {/* Tag list */}\n <div className=\"space-y-0.5\">\n {tags.map((tag) => (\n <div key={tag.id} className=\"flex items-center gap-1.5 px-1 py-1 rounded hover:bg-surface-elevated group\">\n {editingId === tag.id ? (\n <>\n <input type=\"color\" value={editColor} onChange={(e) => setEditColor(e.target.value)} className=\"size-5 rounded cursor-pointer border-0 p-0\" />\n <input\n value={editName}\n onChange={(e) => setEditName(e.target.value)}\n onKeyDown={(e) => { if (e.key === \"Enter\") handleUpdate(tag.id); if (e.key === \"Escape\") setEditingId(null); }}\n className=\"flex-1 min-w-0 bg-surface-elevated text-[11px] px-1.5 py-0.5 rounded border border-border outline-none focus:border-primary\"\n autoFocus\n />\n <button onClick={() => handleUpdate(tag.id)} className=\"p-0.5 text-green-500\"><Check className=\"size-3\" /></button>\n <button onClick={() => setEditingId(null)} className=\"p-0.5 text-text-subtle\"><X className=\"size-3\" /></button>\n </>\n ) : (\n <>\n <span className=\"size-3 rounded-full shrink-0\" style={{ backgroundColor: tag.color }} />\n <span className=\"flex-1 text-[11px] text-text-primary truncate\">{tag.name}</span>\n <button\n onClick={() => handleSetDefault(tag.id)}\n className={`px-1.5 py-0.5 rounded text-[9px] font-medium transition-colors ${\n tag.id === defaultTagId\n ? \"bg-primary/15 text-primary border border-primary/30\"\n : \"text-text-subtle border border-transparent can-hover:opacity-0 can-hover:group-hover:opacity-100 hover:bg-surface-elevated hover:border-border\"\n }`}\n title={tag.id === defaultTagId ? \"Default tag (click to unset)\" : \"Set as default for new sessions\"}\n >\n {tag.id === defaultTagId ? \"Default\" : \"Set default\"}\n </button>\n <button\n onClick={() => { setEditingId(tag.id); setEditName(tag.name); setEditColor(tag.color); }}\n className=\"p-0.5 rounded text-text-subtle hover:text-text-secondary can-hover:opacity-0 can-hover:group-hover:opacity-100\"\n >\n <Pencil className=\"size-3\" />\n </button>\n <button\n onClick={() => handleDelete(tag.id, tag.name)}\n className=\"p-0.5 rounded text-text-subtle hover:text-red-400 can-hover:opacity-0 can-hover:group-hover:opacity-100\"\n >\n <Trash2 className=\"size-3\" />\n </button>\n </>\n )}\n </div>\n ))}\n {tags.length === 0 && (\n <p className=\"text-[11px] text-muted-foreground py-2 text-center\">No tags. Click + to create one.</p>\n )}\n </div>\n </div>\n );\n}\n","import { useState } from \"react\";\nimport { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from \"@/components/ui/dialog\";\nimport { Button } from \"@/components/ui/button\";\nimport { Input } from \"@/components/ui/input\";\nimport { Label } from \"@/components/ui/label\";\nimport { Loader2, Download, Copy, Lock } from \"lucide-react\";\nimport { getAuthToken } from \"../../lib/api-client\";\nimport {\n addAccount,\n getOAuthUrl,\n exchangeOAuthCode,\n importAccounts,\n type AccountInfo,\n} from \"../../lib/api-settings\";\n\nconst DEFAULT_PASSWORD = \"ppm-hienlh\";\n\n// ── Add Account Dialog ─────────────────────────────────────────────\n\ninterface AddAccountDialogProps {\n open: boolean;\n onOpenChange: (open: boolean) => void;\n onSuccess: (msg?: string) => void;\n}\n\nexport function AddAccountDialog({ open, onOpenChange, onSuccess }: AddAccountDialogProps) {\n const [newToken, setNewToken] = useState(\"\");\n const [newLabel, setNewLabel] = useState(\"\");\n const [adding, setAdding] = useState(false);\n const [addError, setAddError] = useState<string | null>(null);\n const [oauthState, setOauthState] = useState<string | null>(null);\n const [oauthCode, setOauthCode] = useState(\"\");\n const [oauthLoading, setOauthLoading] = useState(false);\n const [oauthStep, setOauthStep] = useState<\"idle\" | \"waiting\">(\"idle\");\n\n function resetOAuth() {\n setOauthState(null);\n setOauthCode(\"\");\n setOauthStep(\"idle\");\n setAddError(null);\n }\n\n function handleClose() {\n onOpenChange(false);\n resetOAuth();\n setNewToken(\"\");\n setNewLabel(\"\");\n setAddError(null);\n }\n\n async function handleOAuthLogin() {\n setOauthLoading(true);\n setAddError(null);\n try {\n const { url, state } = await getOAuthUrl();\n setOauthState(state);\n setOauthStep(\"waiting\");\n window.open(url, \"_blank\");\n } catch (e) {\n setAddError((e as Error).message);\n }\n setOauthLoading(false);\n }\n\n async function handleOAuthExchange() {\n if (!oauthCode.trim() || !oauthState) return;\n setOauthLoading(true);\n setAddError(null);\n try {\n let code = oauthCode.trim();\n if (code.includes(\"#\")) code = code.split(\"#\")[0] ?? code;\n await exchangeOAuthCode(code, oauthState);\n handleClose();\n onSuccess(\"Account connected via OAuth!\");\n } catch (e) {\n setAddError((e as Error).message);\n }\n setOauthLoading(false);\n }\n\n async function handleAddToken() {\n if (!newToken.trim()) return;\n setAdding(true);\n setAddError(null);\n try {\n await addAccount({ apiKey: newToken.trim(), label: newLabel.trim() || undefined });\n handleClose();\n onSuccess(\"Account added!\");\n } catch (e) {\n setAddError((e as Error).message);\n }\n setAdding(false);\n }\n\n const tokenHint = newToken.trim()\n ? newToken.trim().startsWith(\"sk-ant-oat\") ? \"OAuth token (Claude Max/Pro)\"\n : newToken.trim().startsWith(\"sk-ant-api\") ? \"API key\"\n : \"Unknown format\"\n : \"\";\n\n return (\n <Dialog open={open} onOpenChange={(v) => { if (!v) handleClose(); }}>\n <DialogContent className=\"sm:max-w-md\">\n <DialogHeader>\n <DialogTitle className=\"text-sm\">Add Claude Account</DialogTitle>\n <DialogDescription className=\"text-xs leading-relaxed\">\n Connect via OAuth (recommended) or paste a token manually.\n </DialogDescription>\n </DialogHeader>\n <div className=\"space-y-3\">\n {/* OAuth login */}\n <div className=\"rounded-md border p-3 space-y-2\">\n <p className=\"text-[11px] font-medium\">Recommended: Login with Claude</p>\n {oauthStep === \"idle\" ? (\n <Button size=\"sm\" className=\"w-full h-8 text-xs\" onClick={handleOAuthLogin} disabled={oauthLoading}>\n {oauthLoading ? <><Loader2 className=\"size-3 animate-spin mr-1\" /> Opening...</> : \"Login with Claude\"}\n </Button>\n ) : (\n <div className=\"space-y-2\">\n <p className=\"text-[10px] text-muted-foreground\">Authorize in the opened tab, then paste the code:</p>\n <Input placeholder=\"Paste code here...\" value={oauthCode} onChange={(e) => setOauthCode(e.target.value)} className=\"text-xs h-8 font-mono\" autoFocus />\n <div className=\"flex gap-1.5\">\n <Button size=\"sm\" className=\"flex-1 h-7 text-xs\" onClick={handleOAuthExchange} disabled={!oauthCode.trim() || oauthLoading}>\n {oauthLoading ? <><Loader2 className=\"size-3 animate-spin mr-1\" /> Connecting...</> : \"Connect\"}\n </Button>\n <Button size=\"sm\" variant=\"ghost\" className=\"h-7 text-xs\" onClick={resetOAuth}>Cancel</Button>\n </div>\n </div>\n )}\n </div>\n <div className=\"flex items-center gap-2\">\n <div className=\"flex-1 border-t\" />\n <span className=\"text-[10px] text-muted-foreground\">or paste token</span>\n <div className=\"flex-1 border-t\" />\n </div>\n {/* Manual token */}\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"add-token\" className=\"text-xs\">Token</Label>\n <Input id=\"add-token\" type=\"password\" placeholder=\"sk-ant-...\" value={newToken} onChange={(e) => setNewToken(e.target.value)} className=\"text-xs h-8 font-mono\" />\n {tokenHint && <p className=\"text-[10px] text-muted-foreground\">Detected: {tokenHint}</p>}\n </div>\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"add-label\" className=\"text-xs\">Label (optional)</Label>\n <Input id=\"add-label\" placeholder=\"e.g. Personal, Work\" value={newLabel} onChange={(e) => setNewLabel(e.target.value)} className=\"text-xs h-8\" />\n </div>\n </div>\n {addError && <div className=\"text-[11px] p-2 rounded bg-red-500/10 text-red-600\">{addError}</div>}\n <DialogFooter>\n <Button size=\"sm\" variant=\"outline\" className=\"text-xs h-7\" onClick={handleClose}>Cancel</Button>\n <Button size=\"sm\" className=\"text-xs h-7\" onClick={handleAddToken} disabled={!newToken.trim() || adding}>\n {adding ? \"Adding...\" : \"Add Token\"}\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n );\n}\n\n// ── Export Accounts Dialog ──────────────────────────────────────────\n\ninterface ExportAccountsDialogProps {\n open: boolean;\n onOpenChange: (open: boolean) => void;\n accounts: AccountInfo[];\n preselectId?: string | null;\n onMessage?: (msg: string) => void;\n}\n\nexport function ExportAccountsDialog({ open, onOpenChange, accounts, preselectId, onMessage }: ExportAccountsDialogProps) {\n const exportable = accounts.filter((a) => a.hasRefreshToken);\n const [selected, setSelected] = useState<Set<string>>(new Set());\n const [password, setPassword] = useState(\"\");\n const [fullTransfer, setFullTransfer] = useState(false);\n const [refreshBefore, setRefreshBefore] = useState(false);\n const [exporting, setExporting] = useState(false);\n const [initialized, setInitialized] = useState(false);\n\n // Initialize selection when dialog opens\n if (open && !initialized) {\n setSelected(preselectId ? new Set([preselectId]) : new Set(exportable.map((a) => a.id)));\n setInitialized(true);\n }\n if (!open && initialized) {\n setInitialized(false);\n }\n\n function handleClose() {\n onOpenChange(false);\n setPassword(\"\");\n setFullTransfer(false);\n setRefreshBefore(false);\n }\n\n async function doExport(toClipboard: boolean) {\n if (selected.size === 0) return;\n setExporting(true);\n const effectivePassword = password.trim() || DEFAULT_PASSWORD;\n try {\n const headers: HeadersInit = { \"Content-Type\": \"application/json\" };\n const token = getAuthToken();\n if (token) headers[\"Authorization\"] = `Bearer ${token}`;\n const res = await fetch(\"/api/accounts/export\", {\n method: \"POST\",\n headers,\n body: JSON.stringify({ password: effectivePassword, accountIds: [...selected], includeRefreshToken: fullTransfer, refreshBeforeExport: refreshBefore }),\n });\n if (!res.ok) { const j = await res.json() as any; throw new Error(j.error ?? `Export failed: ${res.status}`); }\n const text = await res.text();\n if (toClipboard) {\n try {\n await navigator.clipboard.writeText(text);\n onMessage?.(\"Backup copied to clipboard!\");\n } catch {\n downloadBlob(text);\n onMessage?.(\"Backup downloaded.\");\n }\n } else {\n downloadBlob(text);\n onMessage?.(\"Backup downloaded.\");\n }\n handleClose();\n } catch { /* silent */ }\n setExporting(false);\n }\n\n const valid = selected.size > 0 && !exporting;\n\n return (\n <Dialog open={open} onOpenChange={(v) => { if (!v) handleClose(); }}>\n <DialogContent className=\"sm:max-w-md\">\n <DialogHeader>\n <DialogTitle className=\"text-sm flex items-center gap-1.5\"><Lock className=\"size-3.5\" /> Export Accounts</DialogTitle>\n <DialogDescription className=\"text-xs\">Select accounts and set a password to protect the backup.</DialogDescription>\n </DialogHeader>\n <div className=\"space-y-3\">\n {/* Account selection */}\n <div className=\"space-y-1\">\n <div className=\"flex items-center justify-between mb-1\">\n <p className=\"text-[11px] font-medium text-muted-foreground\">Accounts to export</p>\n <button className=\"text-[10px] text-primary hover:underline cursor-pointer\" onClick={() => setSelected(selected.size === exportable.length ? new Set() : new Set(exportable.map((a) => a.id)))}>\n {selected.size === exportable.length ? \"Deselect all\" : \"Select all\"}\n </button>\n </div>\n {exportable.length === 0 ? (\n <p className=\"text-[10px] text-muted-foreground p-2 border rounded\">No exportable accounts.</p>\n ) : (\n <div className=\"max-h-36 overflow-y-auto space-y-1 border rounded p-2\">\n {exportable.map((acc) => (\n <div key={acc.id} className=\"flex items-center gap-2\">\n <input type=\"checkbox\" id={`exp-${acc.id}`} checked={selected.has(acc.id)} onChange={(e) => { const s = new Set(selected); e.target.checked ? s.add(acc.id) : s.delete(acc.id); setSelected(s); }} className=\"size-3.5 accent-primary cursor-pointer\" />\n <label htmlFor={`exp-${acc.id}`} className=\"text-xs cursor-pointer truncate\">\n {acc.label ?? acc.email ?? acc.id.slice(0, 8)}\n </label>\n </div>\n ))}\n </div>\n )}\n </div>\n {/* Password (optional) */}\n <div className=\"space-y-1.5\">\n <Label className=\"text-xs\">Password <span className=\"text-muted-foreground font-normal\">(optional)</span></Label>\n <Input type=\"password\" placeholder=\"Leave empty for default\" value={password} onChange={(e) => setPassword(e.target.value)} className=\"text-xs h-8\" autoComplete=\"new-password\" />\n </div>\n {/* Options */}\n <div className=\"flex items-center gap-2\">\n <input type=\"checkbox\" id=\"exp-full\" checked={fullTransfer} onChange={(e) => setFullTransfer(e.target.checked)} className=\"size-3.5 accent-primary cursor-pointer\" />\n <label htmlFor=\"exp-full\" className=\"text-[11px] cursor-pointer\">Include refresh tokens (full transfer)</label>\n </div>\n <div className=\"flex items-center gap-2\">\n <input type=\"checkbox\" id=\"exp-refresh\" checked={refreshBefore} onChange={(e) => setRefreshBefore(e.target.checked)} className=\"size-3.5 accent-primary cursor-pointer\" />\n <label htmlFor=\"exp-refresh\" className=\"text-[11px] cursor-pointer\">Refresh tokens before export</label>\n </div>\n {/* Warning */}\n {fullTransfer ? (\n <div className=\"rounded-md border border-red-500/30 bg-red-500/5 p-2.5\">\n <p className=\"text-[10px] font-medium text-red-600\">Full transfer — source accounts will expire</p>\n <p className=\"text-[10px] text-muted-foreground\">Refresh tokens included. Source machine expires in ~1h after target refreshes.</p>\n </div>\n ) : refreshBefore ? (\n <div className=\"rounded-md border border-amber-500/30 bg-amber-500/5 p-2.5\">\n <p className=\"text-[10px] font-medium text-amber-600\">Refresh before export — invalidates previous shares</p>\n </div>\n ) : (\n <div className=\"rounded-md border border-green-500/30 bg-green-500/5 p-2.5\">\n <p className=\"text-[10px] font-medium text-green-600\">Share current token (safe)</p>\n </div>\n )}\n <p className=\"text-[10px] text-muted-foreground\">Encrypted with AES-256-GCM + scrypt.</p>\n </div>\n <DialogFooter className=\"gap-1.5 flex-col sm:flex-row\">\n <Button size=\"sm\" variant=\"outline\" className=\"text-xs h-7 cursor-pointer\" onClick={handleClose}>Cancel</Button>\n <Button size=\"sm\" variant=\"outline\" className=\"text-xs h-7 cursor-pointer\" disabled={!valid} onClick={() => doExport(true)}>\n <Copy className=\"size-3 mr-1\" /> Copy\n </Button>\n <Button size=\"sm\" className=\"text-xs h-7 cursor-pointer\" disabled={!valid} onClick={() => doExport(false)}>\n {exporting ? <><Loader2 className=\"size-3 animate-spin mr-1\" /> Exporting...</> : <><Download className=\"size-3 mr-1\" /> Download</>}\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n );\n}\n\n// ── Import Accounts Dialog ─────────────────────────────────────────\n\ninterface ImportAccountsDialogProps {\n open: boolean;\n onOpenChange: (open: boolean) => void;\n onSuccess: (msg?: string) => void;\n}\n\nexport function ImportAccountsDialog({ open, onOpenChange, onSuccess }: ImportAccountsDialogProps) {\n const [data, setData] = useState(\"\");\n const [password, setPassword] = useState(\"\");\n const [importing, setImporting] = useState(false);\n const [error, setError] = useState<string | null>(null);\n\n function handleClose() {\n onOpenChange(false);\n setData(\"\");\n setPassword(\"\");\n setError(null);\n }\n\n async function doImport() {\n if (!data.trim()) return;\n setImporting(true);\n setError(null);\n try {\n const result = await importAccounts({ data: data.trim(), password: password.trim() || DEFAULT_PASSWORD });\n handleClose();\n onSuccess(`Imported ${result.imported} account(s)`);\n } catch (e) {\n setError((e as Error).message || \"Import failed\");\n }\n setImporting(false);\n }\n\n return (\n <Dialog open={open} onOpenChange={(v) => { if (!v) handleClose(); }}>\n <DialogContent className=\"sm:max-w-md\">\n <DialogHeader>\n <DialogTitle className=\"text-sm flex items-center gap-1.5\"><Lock className=\"size-3.5\" /> Import Accounts</DialogTitle>\n <DialogDescription className=\"text-xs\">Paste backup data and enter the export password. Imported accounts are temporary (~1h).</DialogDescription>\n </DialogHeader>\n <div className=\"space-y-3\">\n <div className=\"space-y-1.5\">\n <Label className=\"text-xs\">Backup data</Label>\n <textarea value={data} onChange={(e) => setData(e.target.value)} placeholder=\"Paste backup JSON here...\" rows={4} className=\"w-full text-xs p-2 rounded border border-border bg-background font-mono resize-none focus:outline-none focus:ring-1 focus:ring-primary\" />\n </div>\n <div className=\"space-y-1.5\">\n <Label className=\"text-xs\">Password <span className=\"text-muted-foreground font-normal\">(optional)</span></Label>\n <Input type=\"password\" placeholder=\"Leave empty for default\" value={password} onChange={(e) => setPassword(e.target.value)} className=\"text-xs h-8\" autoComplete=\"current-password\" />\n </div>\n </div>\n {error && <div className=\"text-[11px] p-2 rounded bg-red-500/10 text-red-600\">{error}</div>}\n <DialogFooter>\n <Button size=\"sm\" variant=\"outline\" className=\"text-xs h-7 cursor-pointer\" onClick={handleClose}>Cancel</Button>\n <Button size=\"sm\" className=\"text-xs h-7 cursor-pointer\" disabled={!data.trim() || importing} onClick={doImport}>\n {importing ? <><Loader2 className=\"size-3 animate-spin mr-1\" /> Importing...</> : \"Import\"}\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n );\n}\n\n// ── Helpers ─────────────────────────────────────────────────────────\n\nfunction downloadBlob(text: string) {\n const blob = new Blob([text], { type: \"application/json\" });\n const a = document.createElement(\"a\");\n a.href = URL.createObjectURL(blob);\n a.download = \"ppm-accounts-backup.json\";\n a.click();\n URL.revokeObjectURL(a.href);\n}\n","import { useState, useEffect, useSyncExternalStore } from \"react\";\nimport { Settings, X } from \"lucide-react\";\nimport { Dialog, DialogContent, DialogHeader, DialogTitle } from \"@/components/ui/dialog\";\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from \"@/components/ui/select\";\nimport { cn } from \"@/lib/utils\";\nimport {\n getAccountSettings,\n updateAccountSettings,\n type AccountSettings,\n} from \"../../lib/api-settings\";\n\ninterface AccountRotationSettingsProps {\n open: boolean;\n onOpenChange: (open: boolean) => void;\n}\n\nconst mdQuery = typeof window !== \"undefined\" ? window.matchMedia(\"(min-width: 768px)\") : null;\nfunction subscribeMedia(cb: () => void) {\n mdQuery?.addEventListener(\"change\", cb);\n return () => mdQuery?.removeEventListener(\"change\", cb);\n}\nfunction getIsDesktop() {\n return mdQuery?.matches ?? true;\n}\n\nfunction SettingsContent() {\n const [settings, setSettings] = useState<AccountSettings | null>(null);\n const [loading, setLoading] = useState(true);\n\n useEffect(() => {\n setLoading(true);\n getAccountSettings()\n .then(setSettings)\n .finally(() => setLoading(false));\n }, []);\n\n if (loading) {\n return <p className=\"text-xs text-text-subtle py-4 text-center\">Loading...</p>;\n }\n if (!settings) {\n return <p className=\"text-xs text-text-subtle py-4 text-center\">Failed to load settings</p>;\n }\n\n return (\n <div className=\"space-y-4\">\n {/* Strategy */}\n <div className=\"space-y-1.5\">\n <label className=\"text-xs font-medium text-text-primary\">Rotation Strategy</label>\n <Select\n value={settings.strategy}\n onValueChange={async (v) => {\n const updated = await updateAccountSettings({ strategy: v as AccountSettings[\"strategy\"] });\n setSettings(updated);\n }}\n >\n <SelectTrigger className=\"w-full h-9 text-xs\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"round-robin\">Round-robin</SelectItem>\n <SelectItem value=\"fill-first\">Fill-first</SelectItem>\n <SelectItem value=\"lowest-usage\">Lowest usage</SelectItem>\n </SelectContent>\n </Select>\n <p className=\"text-[10px] text-text-subtle\">\n {settings.strategy === \"round-robin\" && \"Cycles through accounts evenly\"}\n {settings.strategy === \"fill-first\" && \"Uses one account until its limit, then moves on\"}\n {settings.strategy === \"lowest-usage\" && \"Picks the account with the lowest current usage\"}\n </p>\n </div>\n\n {/* Max Retry */}\n <div className=\"space-y-1.5\">\n <label className=\"text-xs font-medium text-text-primary\">Max Retry</label>\n <input\n type=\"number\"\n min={0}\n value={settings.maxRetry}\n className=\"w-full h-9 text-xs border rounded-md px-3 bg-background\"\n onChange={async (e) => {\n const v = parseInt(e.target.value, 10);\n if (!isNaN(v) && v >= 0) {\n const updated = await updateAccountSettings({ maxRetry: v });\n setSettings(updated);\n }\n }}\n />\n <p className=\"text-[10px] text-text-subtle\">\n How many accounts to try on failure. 0 = try all available accounts.\n </p>\n </div>\n\n {/* Active accounts */}\n <div className=\"flex items-center justify-between text-xs border-t border-border pt-3\">\n <span className=\"text-text-subtle\">Active accounts</span>\n <span className=\"font-medium text-text-primary\">{settings.activeCount}</span>\n </div>\n </div>\n );\n}\n\nexport function AccountRotationSettings({ open, onOpenChange }: AccountRotationSettingsProps) {\n const isDesktop = useSyncExternalStore(subscribeMedia, getIsDesktop);\n\n if (!open) return null;\n\n // Desktop: Dialog\n if (isDesktop) {\n return (\n <Dialog open={open} onOpenChange={onOpenChange}>\n <DialogContent className=\"sm:max-w-sm\">\n <DialogHeader>\n <DialogTitle className=\"text-sm flex items-center gap-2\">\n <Settings className=\"size-4\" /> Rotation & Retry\n </DialogTitle>\n </DialogHeader>\n <SettingsContent />\n </DialogContent>\n </Dialog>\n );\n }\n\n // Mobile: Bottom sheet\n return (\n <>\n <div\n className=\"fixed inset-0 z-50 transition-opacity duration-200 opacity-100\"\n onClick={() => onOpenChange(false)}\n style={{ backgroundColor: \"rgba(0,0,0,0.5)\" }}\n />\n <div\n className={cn(\n \"fixed bottom-0 left-0 right-0 z-50 bg-background rounded-t-2xl border-t border-border shadow-2xl\",\n \"transition-transform duration-300 ease-out max-h-[85vh] overflow-y-auto\",\n \"translate-y-0\",\n )}\n >\n {/* Drag handle */}\n <div className=\"flex justify-center pt-3 pb-1\">\n <div className=\"w-10 h-1 rounded-full bg-border\" />\n </div>\n\n {/* Header */}\n <div className=\"flex items-center justify-between px-4 py-2 border-b border-border\">\n <span className=\"text-sm font-semibold flex items-center gap-2\">\n <Settings className=\"size-4\" /> Rotation & Retry\n </span>\n <button\n onClick={() => onOpenChange(false)}\n className=\"flex items-center justify-center size-7 rounded-md hover:bg-surface-elevated transition-colors\"\n >\n <X className=\"size-4\" />\n </button>\n </div>\n\n {/* Content */}\n <div className=\"px-4 py-4 pb-8\">\n <SettingsContent />\n </div>\n </div>\n </>\n );\n}\n","import { useState, useEffect, useMemo } from \"react\";\nimport { Loader2 } from \"lucide-react\";\nimport { getUsageHistory, type UsageSnapshot } from \"../../lib/api-settings\";\n\nconst DAY_LABELS = [\"Mon\", \"Tue\", \"Wed\", \"Thu\", \"Fri\", \"Sat\", \"Sun\"];\nconst HOUR_LABELS = Array.from({ length: 24 }, (_, i) => i);\n\ntype ViewMode = \"5h\" | \"weekly\";\n\ninterface AggregatedCell {\n sum: number;\n count: number;\n avg: number;\n}\n\n/** Aggregate snapshots into a 7×24 grid (day-of-week × hour-of-day) */\nfunction buildHeatmap(snapshots: UsageSnapshot[], mode: ViewMode): AggregatedCell[][] {\n // grid[dayOfWeek 0-6][hour 0-23]\n const grid: AggregatedCell[][] = Array.from({ length: 7 }, () =>\n Array.from({ length: 24 }, () => ({ sum: 0, count: 0, avg: 0 })),\n );\n\n for (const snap of snapshots) {\n const val = mode === \"5h\" ? snap.five_hour_util : snap.weekly_util;\n if (val == null) continue;\n const d = new Date(snap.recorded_at + (snap.recorded_at.endsWith(\"Z\") ? \"\" : \"Z\"));\n const dow = (d.getDay() + 6) % 7; // Monday=0\n const hour = d.getHours();\n grid[dow]![hour]!.sum += val;\n grid[dow]![hour]!.count += 1;\n }\n\n // Compute averages\n for (const row of grid) {\n for (const cell of row) {\n cell.avg = cell.count > 0 ? cell.sum / cell.count : 0;\n }\n }\n return grid;\n}\n\n/** Aggregate snapshots by day-of-week (average utilization) */\nfunction buildDayAvg(grid: AggregatedCell[][]): number[] {\n return grid.map((row) => {\n const totalSum = row.reduce((s, c) => s + c.sum, 0);\n const totalCount = row.reduce((s, c) => s + c.count, 0);\n return totalCount > 0 ? totalSum / totalCount : 0;\n });\n}\n\n/** Aggregate snapshots by hour-of-day (average utilization) */\nfunction buildHourAvg(grid: AggregatedCell[][]): number[] {\n return HOUR_LABELS.map((h) => {\n let sum = 0, count = 0;\n for (const row of grid) {\n sum += row[h]!.sum;\n count += row[h]!.count;\n }\n return count > 0 ? sum / count : 0;\n });\n}\n\nfunction cellColor(val: number): string {\n if (val === 0) return \"bg-surface-elevated\";\n if (val < 0.3) return \"bg-green-500/30\";\n if (val < 0.5) return \"bg-green-500/60\";\n if (val < 0.7) return \"bg-amber-500/50\";\n if (val < 0.9) return \"bg-amber-500/80\";\n return \"bg-red-500/80\";\n}\n\nfunction barColor(val: number): string {\n if (val < 0.3) return \"bg-green-500\";\n if (val < 0.7) return \"bg-amber-500\";\n return \"bg-red-500\";\n}\n\nexport function UsagePatternChart({ accountId }: { accountId: string }) {\n const [snapshots, setSnapshots] = useState<UsageSnapshot[] | null>(null);\n const [loading, setLoading] = useState(true);\n const [mode, setMode] = useState<ViewMode>(\"5h\");\n\n useEffect(() => {\n setLoading(true);\n getUsageHistory(accountId)\n .then(setSnapshots)\n .catch(() => setSnapshots([]))\n .finally(() => setLoading(false));\n }, [accountId]);\n\n const grid = useMemo(() => snapshots ? buildHeatmap(snapshots, mode) : null, [snapshots, mode]);\n const dayAvg = useMemo(() => grid ? buildDayAvg(grid) : [], [grid]);\n const hourAvg = useMemo(() => grid ? buildHourAvg(grid) : [], [grid]);\n const maxDay = Math.max(...dayAvg, 0.01);\n const maxHour = Math.max(...hourAvg, 0.01);\n\n if (loading) {\n return (\n <div className=\"flex items-center justify-center py-3\">\n <Loader2 className=\"size-3 animate-spin text-text-subtle\" />\n </div>\n );\n }\n\n if (!snapshots || snapshots.length === 0) {\n return (\n <div className=\"text-[10px] text-text-subtle py-2 text-center\">\n No usage history yet\n </div>\n );\n }\n\n const dataPoints = snapshots.length;\n const daysWithData = new Set(snapshots.map((s) => new Date(s.recorded_at + (s.recorded_at.endsWith(\"Z\") ? \"\" : \"Z\")).toDateString())).size;\n\n return (\n <div className=\"mt-2 space-y-2\">\n <div className=\"flex items-center justify-between\">\n <span className=\"text-[10px] font-medium text-text-subtle\">Usage Pattern (7d)</span>\n <div className=\"flex gap-0.5 text-[9px]\">\n <button\n onClick={() => setMode(\"5h\")}\n className={`px-1.5 py-0.5 rounded cursor-pointer transition-colors ${mode === \"5h\" ? \"bg-primary/15 text-primary\" : \"text-text-subtle hover:text-text-secondary\"}`}\n title=\"5-hour rolling window limit — resets every 5 hours\"\n >\n 5h\n </button>\n <button\n onClick={() => setMode(\"weekly\")}\n className={`px-1.5 py-0.5 rounded cursor-pointer transition-colors ${mode === \"weekly\" ? \"bg-primary/15 text-primary\" : \"text-text-subtle hover:text-text-secondary\"}`}\n title=\"Weekly limit — resets every 7 days\"\n >\n Wk\n </button>\n </div>\n </div>\n\n {/* Explanation */}\n <p className=\"text-[9px] text-text-subtle leading-tight\">\n Avg {mode === \"5h\" ? \"5-hour\" : \"weekly\"} limit usage over {daysWithData}d ({dataPoints} samples). Higher % = closer to rate limit. Hover cells for details.\n </p>\n\n {/* Day of week bars */}\n <div>\n <span className=\"text-[9px] text-text-subtle\">Avg usage by day of week</span>\n <div className=\"flex flex-col gap-[2px] mt-0.5\">\n {DAY_LABELS.map((label, i) => {\n const val = dayAvg[i] ?? 0;\n return (\n <div key={label} className=\"flex items-center gap-1\">\n <span className=\"text-[8px] text-text-subtle w-5 shrink-0 text-right tabular-nums\">{label}</span>\n <div className=\"flex-1 h-2.5 bg-surface-elevated rounded-sm overflow-hidden\">\n <div\n className={`h-full rounded-sm transition-all ${barColor(val)}`}\n style={{ width: `${Math.round((val / maxDay) * 100)}%` }}\n />\n </div>\n <span className=\"text-[8px] text-text-subtle w-6 shrink-0 text-right tabular-nums\">\n {Math.round(val * 100)}%\n </span>\n </div>\n );\n })}\n </div>\n </div>\n\n {/* Hour of day heatmap */}\n <div>\n <span className=\"text-[9px] text-text-subtle\">Avg usage by hour (0h-23h)</span>\n <div className=\"flex gap-[1px] mt-0.5\">\n {HOUR_LABELS.map((h) => {\n const val = hourAvg[h] ?? 0;\n return (\n <div key={h} className=\"flex-1 flex flex-col items-center gap-[1px]\">\n <div\n className={`w-full aspect-square rounded-[2px] ${cellColor(val)}`}\n title={`${h}:00 — avg ${Math.round(val * 100)}% usage`}\n />\n {h % 6 === 0 && (\n <span className=\"text-[7px] text-text-subtle tabular-nums\">{h}</span>\n )}\n </div>\n );\n })}\n </div>\n </div>\n\n {/* Heatmap: day × hour grid */}\n {grid && (\n <div>\n <span className=\"text-[9px] text-text-subtle\">Day x Hour heatmap</span>\n <div className=\"flex flex-col gap-[1px] mt-0.5\">\n {DAY_LABELS.map((label, d) => (\n <div key={label} className=\"flex items-center gap-[1px]\">\n <span className=\"text-[7px] text-text-subtle w-4 shrink-0 text-right\">{label.charAt(0)}</span>\n {HOUR_LABELS.map((h) => {\n const cell = grid[d]![h]!;\n return (\n <div\n key={h}\n className={`flex-1 aspect-square rounded-[1px] ${cellColor(cell.avg)}`}\n title={`${label} ${h}:00 — ${cell.count > 0 ? `avg ${Math.round(cell.avg * 100)}% (${cell.count} samples)` : \"no data\"}`}\n />\n );\n })}\n </div>\n ))}\n {/* Hour axis labels for heatmap */}\n <div className=\"flex items-center gap-[1px]\">\n <span className=\"w-4 shrink-0\" />\n {HOUR_LABELS.map((h) => (\n <div key={h} className=\"flex-1 text-center\">\n {h % 6 === 0 && <span className=\"text-[7px] text-text-subtle tabular-nums\">{h}</span>}\n </div>\n ))}\n </div>\n </div>\n </div>\n )}\n\n {/* Color legend */}\n <div className=\"flex items-center gap-1.5 text-[8px] text-text-subtle\">\n <span>Low</span>\n <div className=\"flex gap-[2px]\">\n <div className=\"size-2 rounded-[1px] bg-green-500/30\" />\n <div className=\"size-2 rounded-[1px] bg-green-500/60\" />\n <div className=\"size-2 rounded-[1px] bg-amber-500/50\" />\n <div className=\"size-2 rounded-[1px] bg-amber-500/80\" />\n <div className=\"size-2 rounded-[1px] bg-red-500/80\" />\n </div>\n <span>High</span>\n <span className=\"ml-1\">|</span>\n <div className=\"size-2 rounded-[1px] bg-surface-elevated border border-border/30\" />\n <span>No data</span>\n </div>\n </div>\n );\n}\n","import { useState, useEffect, useRef } from \"react\";\nimport { Activity, RefreshCw, Eye, Download, Upload, Plus, X, Settings, Trash2, Maximize2, Minimize2 } from \"lucide-react\";\nimport { Switch } from \"@/components/ui/switch\";\nimport type { UsageInfo, LimitBucket } from \"../../../types/chat\";\nimport {\n getAccounts,\n getActiveAccount,\n getAllAccountUsages,\n patchAccount,\n deleteAccount,\n type AccountInfo,\n type AccountUsageEntry,\n type OAuthProfileData,\n} from \"../../lib/api-settings\";\nimport { AddAccountDialog, ExportAccountsDialog, ImportAccountsDialog } from \"./account-dialogs\";\nimport { AccountRotationSettings } from \"./account-rotation-settings\";\nimport { UsagePatternChart } from \"./usage-pattern-chart\";\n\ninterface UsageBadgeProps {\n usage: UsageInfo;\n loading?: boolean;\n onClick?: () => void;\n}\n\nfunction pctColor(pct: number): string {\n if (pct >= 90) return \"text-red-500\";\n if (pct >= 70) return \"text-amber-500\";\n return \"text-green-500\";\n}\n\nfunction barColor(pct: number): string {\n if (pct >= 90) return \"bg-red-500\";\n if (pct >= 70) return \"bg-amber-500\";\n return \"bg-green-500\";\n}\n\nexport function UsageBadge({ usage, loading, onClick }: UsageBadgeProps) {\n const fiveHourPct = usage.fiveHour != null ? Math.round(usage.fiveHour * 100) : null;\n const sevenDayPct = usage.sevenDay != null ? Math.round(usage.sevenDay * 100) : null;\n\n const fiveHourLabel = fiveHourPct != null ? `${fiveHourPct}%` : \"--%\";\n const sevenDayLabel = sevenDayPct != null ? `${sevenDayPct}%` : \"--%\";\n\n const worstPct = Math.max(fiveHourPct ?? 0, sevenDayPct ?? 0);\n const colorClass = fiveHourPct != null || sevenDayPct != null ? pctColor(worstPct) : \"text-text-subtle\";\n\n return (\n <button\n onClick={onClick}\n className={`flex items-center gap-1 px-1.5 py-0.5 rounded text-[11px] font-medium tabular-nums transition-colors hover:bg-surface-hover ${colorClass}`}\n title=\"Click for usage details\"\n >\n {loading ? <RefreshCw className=\"size-3 animate-spin\" /> : <Activity className=\"size-3\" />}\n <span>5h:{fiveHourLabel}</span>\n <span className=\"text-text-subtle\">·</span>\n <span>Wk:{sevenDayLabel}</span>\n </button>\n );\n}\n\n// --- Detail panel ---\n\ninterface UsageDetailPanelProps {\n usage: UsageInfo;\n visible: boolean;\n onClose: () => void;\n onReload?: () => void;\n loading?: boolean;\n lastFetchedAt?: string | null;\n}\n\nfunction formatResetTime(bucket?: LimitBucket): string | null {\n if (!bucket) return null;\n let totalMins: number | null = null;\n if (bucket.resetsInMinutes != null) {\n totalMins = bucket.resetsInMinutes;\n } else if (bucket.resetsInHours != null) {\n totalMins = Math.round(bucket.resetsInHours * 60);\n } else if (bucket.resetsAt) {\n const diff = new Date(bucket.resetsAt).getTime() - Date.now();\n totalMins = diff > 0 ? Math.ceil(diff / 60_000) : 0;\n }\n if (totalMins == null) return null;\n if (totalMins <= 0) return \"now\";\n const d = Math.floor(totalMins / 1440);\n const h = Math.floor((totalMins % 1440) / 60);\n const m = totalMins % 60;\n if (d > 0) return m > 0 ? `${d}d ${h}h ${m}m` : h > 0 ? `${d}d ${h}h` : `${d}d`;\n if (h > 0) return m > 0 ? `${h}h ${m}m` : `${h}h`;\n return `${m}m`;\n}\n\nfunction BucketRow({ label, bucket }: { label: string; bucket?: LimitBucket }) {\n if (!bucket) return null;\n const pct = Math.round(bucket.utilization * 100);\n const reset = formatResetTime(bucket);\n\n return (\n <div className=\"space-y-1\">\n <div className=\"flex items-center justify-between\">\n <span className=\"text-xs font-medium text-text-primary\">{label}</span>\n {reset && (\n <span className=\"text-[10px] text-text-subtle\" title=\"Resets in\">↻ {reset}</span>\n )}\n </div>\n <div className=\"flex items-center gap-2\">\n <div className=\"flex-1 h-2 rounded-full bg-border overflow-hidden\">\n <div\n className={`h-full rounded-full transition-all ${barColor(pct)}`}\n style={{ width: `${Math.min(pct, 100)}%` }}\n />\n </div>\n <span className={`text-xs font-medium tabular-nums w-10 text-right ${pctColor(pct)}`}>\n {pct}%\n </span>\n </div>\n </div>\n );\n}\n\nfunction formatExpiry(expiresAt: number): string {\n const diff = expiresAt - Date.now();\n if (diff <= 0) return \"expired\";\n const mins = Math.ceil(diff / 60_000);\n const h = Math.floor(mins / 60);\n const d = Math.floor(h / 24);\n if (d > 0) return `${d}d ${h % 24}h`;\n if (h > 0) return `${h}h ${mins % 60}m`;\n return `${mins}m`;\n}\n\n/** Derive a human-readable token status from account info */\nfunction tokenStatus(info?: AccountInfo): { label: string; tip: string; color: string } {\n if (!info) return { label: \"unknown\", tip: \"No account info available\", color: \"text-text-subtle\" };\n if (!info.expiresAt) return { label: \"key\", tip: \"API key (no expiry)\", color: \"text-text-subtle\" };\n const expired = info.expiresAt * 1000 < Date.now(); // expiresAt is seconds\n if (expired && info.hasRefreshToken) return { label: \"expired\", tip: \"Token expired but has refresh token — will auto-renew\", color: \"text-amber-500\" };\n if (expired) return { label: \"expired\", tip: \"Token expired, no refresh token\", color: \"text-red-500\" };\n if (info.hasRefreshToken) return { label: \"long-lived\", tip: \"OAuth token with refresh — long-lived\", color: \"text-green-500\" };\n return { label: \"temp\", tip: \"Temporary token without refresh — will expire\", color: \"text-amber-500\" };\n}\n\nfunction formatLastUpdated(ts: number | null | undefined): string | null {\n if (!ts) return null;\n const secs = Math.round((Date.now() - ts) / 1000);\n if (secs < 5) return \"just now\";\n if (secs < 60) return `${secs}s ago`;\n const mins = Math.floor(secs / 60);\n if (mins < 60) return `${mins}m ago`;\n const hrs = Math.floor(mins / 60);\n const remainMins = mins % 60;\n if (hrs < 24) return remainMins > 0 ? `${hrs}h ${remainMins}m ago` : `${hrs}h ago`;\n const days = Math.floor(hrs / 24);\n return `${days}d ago`;\n}\n\nfunction AccountUsageCard({ entry, isActive, accountInfo, onToggle, onDelete, onExport, onViewProfile, flash, fullscreen }: {\n entry: AccountUsageEntry;\n isActive: boolean;\n accountInfo?: AccountInfo;\n onToggle?: (id: string, status: string) => void;\n onDelete?: (id: string, display: string) => void;\n onExport?: (id: string) => void;\n onViewProfile?: (profile: OAuthProfileData, accountId: string) => void;\n flash?: boolean;\n fullscreen?: boolean;\n}) {\n const { usage } = entry;\n const hasBuckets = usage.session || usage.weekly || usage.weeklyOpus || usage.weeklySonnet;\n const status = accountInfo?.status ?? entry.accountStatus;\n // Expired: has expiresAt in the past AND no refresh token to auto-renew\n const isExpired = !!(accountInfo && !accountInfo.hasRefreshToken && accountInfo.expiresAt && accountInfo.expiresAt < Math.floor(Date.now() / 1000));\n\n return (\n <div className={`rounded-md border p-2 transition-colors duration-500 ${fullscreen ? \"flex flex-col gap-1.5 overflow-hidden\" : \"space-y-1.5 min-w-[200px] shrink-0 snap-start\"} ${isExpired ? \"opacity-50\" : \"\"} ${flash ? \"bg-primary/10 border-primary/40\" : \"\"} ${isActive ? \"border-primary/30 bg-primary/5\" : \"border-border/50\"}`}>\n <div className=\"flex items-center gap-1.5\">\n <span className=\"text-xs font-medium truncate flex-1 min-w-0\">\n {entry.accountLabel ?? entry.accountId.slice(0, 8)}\n </span>\n {isExpired && (\n <span className=\"text-[9px] text-red-500 shrink-0 font-medium\">Expired</span>\n )}\n {!entry.isOAuth && !isExpired && (\n <span className=\"text-[9px] text-text-subtle shrink-0\">API key</span>\n )}\n {/* Account controls */}\n <div className=\"flex items-center gap-0.5 shrink-0\">\n {!isExpired && onViewProfile && accountInfo?.profileData && (\n <button\n className=\"p-1 rounded cursor-pointer text-text-subtle hover:text-foreground hover:bg-surface-elevated transition-colors\"\n onClick={() => onViewProfile(accountInfo.profileData!, entry.accountId)}\n title=\"View profile\"\n >\n <Eye className=\"size-3\" />\n </button>\n )}\n {!isExpired && onExport && entry.isOAuth && (\n <button\n className=\"p-1 rounded cursor-pointer text-text-subtle hover:text-blue-500 hover:bg-surface-elevated transition-colors\"\n onClick={() => onExport(entry.accountId)}\n title=\"Export this account\"\n >\n <Download className=\"size-3\" />\n </button>\n )}\n {!isExpired && onToggle && (\n <Switch\n checked={status !== \"disabled\"}\n onCheckedChange={() => onToggle(entry.accountId, status)}\n disabled={status === \"cooldown\"}\n className=\"scale-[0.6] cursor-pointer\"\n />\n )}\n {onDelete && (\n <button\n className=\"p-1 rounded cursor-pointer text-text-subtle hover:text-red-500 hover:bg-surface-elevated transition-colors\"\n onClick={() => onDelete(entry.accountId, entry.accountLabel ?? entry.accountId.slice(0, 8))}\n title=\"Remove account\"\n >\n <Trash2 className=\"size-3\" />\n </button>\n )}\n </div>\n </div>\n {hasBuckets ? (\n <div className={fullscreen ? \"flex-1 flex flex-col justify-evenly min-h-0\" : \"space-y-1.5\"}>\n <BucketRow label=\"5-Hour Session\" bucket={usage.session} />\n <BucketRow label=\"Weekly\" bucket={usage.weekly} />\n <BucketRow label=\"Weekly (Opus)\" bucket={usage.weeklyOpus} />\n <BucketRow label=\"Weekly (Sonnet)\" bucket={usage.weeklySonnet} />\n </div>\n ) : (\n <p className=\"text-[10px] text-text-subtle\">\n {entry.isOAuth ? \"No usage data yet\" : \"Usage tracking not available for API keys\"}\n </p>\n )}\n {/* Footer: updated · expires · type */}\n {(() => {\n const ts = tokenStatus(accountInfo);\n return (\n <div className=\"flex items-center gap-1.5 text-[9px] text-text-subtle flex-wrap\">\n {usage.lastFetchedAt && (\n <span title=\"Last usage data update\">↻ {formatLastUpdated(new Date(usage.lastFetchedAt).getTime())}</span>\n )}\n {accountInfo?.expiresAt && accountInfo.expiresAt * 1000 > Date.now() && (\n <span title=\"Token expires in\">⏱ {formatExpiry(accountInfo.expiresAt * 1000)}</span>\n )}\n <span className={ts.color} title={ts.tip}>© {ts.label}</span>\n </div>\n );\n })()}\n </div>\n );\n}\n\nexport function UsageDetailPanel({ usage, visible, onClose, onReload, loading, lastFetchedAt }: UsageDetailPanelProps) {\n const [allUsages, setAllUsages] = useState<AccountUsageEntry[]>([]);\n const [accounts, setAccounts] = useState<AccountInfo[]>([]);\n const [activeAccountId, setActiveAccountId] = useState<string | null>(null);\n const [initialLoading, setInitialLoading] = useState(true);\n const [refreshing, setRefreshing] = useState(false);\n const [flashIds, setFlashIds] = useState<Set<string>>(new Set());\n const [profileView, setProfileView] = useState<{ profile: OAuthProfileData; accountId: string } | null>(null);\n const [showAddDialog, setShowAddDialog] = useState(false);\n const [showExportDialog, setShowExportDialog] = useState(false);\n const [showImportDialog, setShowImportDialog] = useState(false);\n const [showRotationSettings, setShowRotationSettings] = useState(false);\n const [deleteTarget, setDeleteTarget] = useState<{ id: string; display: string } | null>(null);\n const [exportPreselect, setExportPreselect] = useState<string | null>(null);\n const [isFullscreen, setIsFullscreen] = useState(false);\n const [message, setMessage] = useState<string | null>(null);\n const msgTimer = useRef<ReturnType<typeof setTimeout>>(undefined);\n const prevUsagesRef = useRef<AccountUsageEntry[]>([]);\n\n function showMessage(msg: string) {\n if (msgTimer.current) clearTimeout(msgTimer.current);\n setMessage(msg);\n msgTimer.current = setTimeout(() => setMessage(null), 4000);\n }\n\n function handleSuccess(msg?: string) {\n loadAll();\n if (msg) showMessage(msg);\n }\n\n async function loadAll() {\n const isRefresh = allUsages.length > 0;\n if (isRefresh) setRefreshing(true); else setInitialLoading(true);\n\n const [usages, accs, active] = await Promise.allSettled([\n getAllAccountUsages(), getAccounts(), getActiveAccount(),\n ]);\n\n if (usages.status === \"fulfilled\") {\n const newUsages = usages.value;\n // Detect which accounts changed usage values\n if (isRefresh && prevUsagesRef.current.length > 0) {\n const changed = new Set<string>();\n const prevMap = new Map(prevUsagesRef.current.map(u => [u.accountId, u]));\n for (const nu of newUsages) {\n const prev = prevMap.get(nu.accountId);\n if (!prev) { changed.add(nu.accountId); continue; }\n const pu = prev.usage, cu = nu.usage;\n if (pu.session?.utilization !== cu.session?.utilization\n || pu.weekly?.utilization !== cu.weekly?.utilization\n || pu.weeklyOpus?.utilization !== cu.weeklyOpus?.utilization\n || pu.weeklySonnet?.utilization !== cu.weeklySonnet?.utilization) {\n changed.add(nu.accountId);\n }\n }\n if (changed.size > 0) {\n setFlashIds(changed);\n setTimeout(() => setFlashIds(new Set()), 1500);\n }\n }\n prevUsagesRef.current = newUsages;\n setAllUsages(newUsages);\n }\n if (accs.status === \"fulfilled\") setAccounts(accs.value);\n if (active.status === \"fulfilled\") setActiveAccountId(active.value?.id ?? null);\n setInitialLoading(false);\n setRefreshing(false);\n }\n\n useEffect(() => {\n if (!visible) return;\n loadAll();\n }, [visible]);\n\n // Re-fetch account usages after parent refreshes from Anthropic API\n useEffect(() => {\n if (!visible || !lastFetchedAt) return;\n loadAll();\n }, [lastFetchedAt]); // eslint-disable-line react-hooks/exhaustive-deps\n\n if (!visible) return null;\n\n const accountMap = new Map(accounts.map((a) => [a.id, a]));\n const hasCost = usage.queryCostUsd != null || usage.totalCostUsd != null;\n const hasMultipleAccounts = allUsages.length > 0;\n\n // Grid dimensions for fullscreen: cards fill viewport without scroll\n const fsCount = allUsages.length || 1;\n const fsCols = Math.ceil(Math.sqrt(fsCount));\n const fsRows = Math.ceil(fsCount / fsCols);\n\n async function handleToggle(id: string, status: string) {\n await patchAccount(id, { status: status === \"disabled\" ? \"active\" : \"disabled\" });\n loadAll();\n onReload?.();\n }\n\n async function confirmDeleteAccount() {\n if (!deleteTarget) return;\n try {\n await deleteAccount(deleteTarget.id);\n showMessage(`Account \"${deleteTarget.display}\" removed.`);\n loadAll();\n onReload?.();\n } catch (e) {\n showMessage(`Failed to remove: ${(e as Error).message}`);\n }\n setDeleteTarget(null);\n }\n\n function openExportAll() {\n setExportPreselect(null);\n setShowExportDialog(true);\n }\n\n return (\n <div className={`relative border-b border-border bg-surface px-3 py-2.5 ${isFullscreen ? \"fixed inset-0 z-50 flex flex-col gap-2.5 overflow-hidden\" : \"space-y-2.5 max-h-[350px] overflow-y-auto\"}`}>\n <div className=\"flex items-center justify-between shrink-0\">\n <div className=\"flex items-center gap-2\">\n <span className=\"text-xs font-semibold text-text-primary\">Usage & Accounts</span>\n {lastFetchedAt && (\n <span className=\"text-[10px] text-text-subtle\">{formatLastUpdated(new Date(lastFetchedAt).getTime())}</span>\n )}\n </div>\n <div className=\"flex items-center gap-1\">\n <button\n onClick={() => setShowRotationSettings(true)}\n className=\"text-xs text-text-subtle hover:text-text-primary px-1 cursor-pointer\"\n title=\"Rotation & retry settings\"\n >\n <Settings className=\"size-3\" />\n </button>\n {hasMultipleAccounts && (\n <button\n onClick={() => setIsFullscreen((v) => !v)}\n className=\"text-xs text-text-subtle hover:text-text-primary px-1 cursor-pointer\"\n title={isFullscreen ? \"Exit fullscreen\" : \"Fullscreen view\"}\n >\n {isFullscreen ? <Minimize2 className=\"size-3\" /> : <Maximize2 className=\"size-3\" />}\n </button>\n )}\n {onReload && (\n <button\n onClick={() => { onReload(); loadAll(); }}\n disabled={loading || refreshing}\n className=\"text-xs text-text-subtle hover:text-text-primary px-1 disabled:opacity-50 cursor-pointer\"\n title=\"Refresh\"\n >\n <RefreshCw className={`size-3 ${(loading || refreshing) ? \"animate-spin\" : \"\"}`} />\n </button>\n )}\n <button\n onClick={() => { setIsFullscreen(false); onClose(); }}\n className=\"text-xs text-text-subtle hover:text-text-primary px-1 cursor-pointer\"\n >\n <X className=\"size-3\" />\n </button>\n </div>\n </div>\n\n {message && (\n <div className=\"text-[11px] p-1.5 rounded bg-green-500/10 text-green-600 text-center animate-in fade-in duration-200\">\n {message}\n </div>\n )}\n\n {(hasMultipleAccounts || initialLoading) ? (\n <div\n className={isFullscreen\n ? \"flex-1 min-h-0 grid gap-2 overflow-hidden\"\n : \"flex gap-1.5 overflow-x-auto pb-1 -mx-3 px-3 snap-x snap-mandatory scrollbar-thin\"\n }\n style={isFullscreen ? {\n gridTemplateColumns: `repeat(${fsCols}, minmax(0, 1fr))`,\n gridTemplateRows: `repeat(${fsRows}, minmax(0, 1fr))`,\n } : undefined}\n >\n {initialLoading ? (\n <p className=\"text-[10px] text-text-subtle\">Loading...</p>\n ) : (\n allUsages.map((entry) => (\n <AccountUsageCard\n key={entry.accountId}\n entry={entry}\n isActive={entry.accountId === (activeAccountId ?? usage.activeAccountId)}\n accountInfo={accountMap.get(entry.accountId)}\n onToggle={handleToggle}\n onDelete={(id, display) => setDeleteTarget({ id, display })}\n onExport={(id) => { setExportPreselect(id); setShowExportDialog(true); }}\n onViewProfile={(profile, accountId) => setProfileView({ profile, accountId })}\n flash={flashIds.has(entry.accountId)}\n fullscreen={isFullscreen}\n />\n ))\n )}\n </div>\n ) : (\n <>\n {usage.session || usage.weekly || usage.weeklyOpus || usage.weeklySonnet ? (\n <div className=\"space-y-2.5\">\n <BucketRow label=\"5-Hour Session\" bucket={usage.session} />\n <BucketRow label=\"Weekly\" bucket={usage.weekly} />\n <BucketRow label=\"Weekly (Opus)\" bucket={usage.weeklyOpus} />\n <BucketRow label=\"Weekly (Sonnet)\" bucket={usage.weeklySonnet} />\n </div>\n ) : (\n <p className=\"text-xs text-text-subtle\">No usage data available</p>\n )}\n </>\n )}\n\n {hasCost && (\n <div className=\"border-t border-border pt-2 space-y-1\">\n {usage.queryCostUsd != null && (\n <div className=\"flex items-center justify-between text-xs\">\n <span className=\"text-text-subtle\">Last query</span>\n <span className=\"text-text-primary font-medium tabular-nums\">\n ${usage.queryCostUsd.toFixed(4)}\n </span>\n </div>\n )}\n {usage.totalCostUsd != null && (\n <div className=\"flex items-center justify-between text-xs\">\n <span className=\"text-text-subtle\">Session total</span>\n <span className=\"text-text-primary font-medium tabular-nums\">\n ${usage.totalCostUsd.toFixed(4)}\n </span>\n </div>\n )}\n </div>\n )}\n\n {/* Inline profile popup */}\n {profileView && (\n <div className=\"border-t border-border pt-2\">\n <div className=\"flex items-center justify-between mb-1\">\n <span className=\"text-[10px] font-medium text-text-subtle\">Profile</span>\n <button className=\"text-text-subtle hover:text-foreground cursor-pointer\" onClick={() => setProfileView(null)}>\n <X className=\"size-3\" />\n </button>\n </div>\n <div className=\"grid grid-cols-[70px_1fr] gap-x-2 gap-y-0.5 text-[10px]\">\n {profileView.profile.account?.display_name && <><span className=\"text-text-subtle\">Name</span><span>{profileView.profile.account.display_name}</span></>}\n {profileView.profile.account?.email && <><span className=\"text-text-subtle\">Email</span><span>{profileView.profile.account.email}</span></>}\n {profileView.profile.organization?.name && <><span className=\"text-text-subtle\">Org</span><span>{profileView.profile.organization.name}</span></>}\n {profileView.profile.organization?.organization_type && <><span className=\"text-text-subtle\">Type</span><span>{profileView.profile.organization.organization_type}</span></>}\n {profileView.profile.organization?.rate_limit_tier && <><span className=\"text-text-subtle\">Tier</span><span>{profileView.profile.organization.rate_limit_tier}</span></>}\n {profileView.profile.organization?.subscription_status && <><span className=\"text-text-subtle\">Status</span><span>{profileView.profile.organization.subscription_status}</span></>}\n </div>\n <UsagePatternChart accountId={profileView.accountId} />\n </div>\n )}\n\n {/* Action buttons */}\n <div className=\"border-t border-border pt-2 flex gap-1.5 shrink-0\">\n <button onClick={() => setShowAddDialog(true)} className=\"flex-1 flex items-center justify-center gap-1 rounded-md border border-border px-2 py-1 text-[11px] text-text-secondary hover:bg-surface-hover transition-colors cursor-pointer\">\n <Plus className=\"size-3\" /> Add\n </button>\n <button onClick={openExportAll} className=\"flex-1 flex items-center justify-center gap-1 rounded-md border border-border px-2 py-1 text-[11px] text-text-secondary hover:bg-surface-hover transition-colors cursor-pointer\">\n <Download className=\"size-3\" /> Export\n </button>\n <button onClick={() => setShowImportDialog(true)} className=\"flex-1 flex items-center justify-center gap-1 rounded-md border border-border px-2 py-1 text-[11px] text-text-secondary hover:bg-surface-hover transition-colors cursor-pointer\">\n <Upload className=\"size-3\" /> Import\n </button>\n </div>\n\n {/* Delete confirmation overlay */}\n {deleteTarget && (\n <div className=\"absolute inset-0 z-10 flex items-center justify-center bg-background/80 backdrop-blur-sm rounded-md\">\n <div className=\"bg-surface border border-border rounded-lg shadow-lg p-4 mx-4 max-w-[280px] w-full space-y-3\">\n <p className=\"text-xs text-text-primary text-center\">\n Remove <strong className=\"text-foreground\">{deleteTarget.display}</strong>?\n </p>\n <div className=\"flex gap-2\">\n <button onClick={() => setDeleteTarget(null)} className=\"flex-1 px-3 py-1.5 rounded-md text-xs border border-border text-text-secondary hover:bg-surface-hover cursor-pointer transition-colors\">\n Cancel\n </button>\n <button onClick={confirmDeleteAccount} className=\"flex-1 px-3 py-1.5 rounded-md text-xs bg-red-500 text-white hover:bg-red-600 cursor-pointer transition-colors\">\n Remove\n </button>\n </div>\n </div>\n </div>\n )}\n\n {/* Account dialogs */}\n <AddAccountDialog open={showAddDialog} onOpenChange={setShowAddDialog} onSuccess={handleSuccess} />\n <ExportAccountsDialog open={showExportDialog} onOpenChange={(v) => { setShowExportDialog(v); if (!v) setExportPreselect(null); }} accounts={accounts} preselectId={exportPreselect} onMessage={showMessage} />\n <ImportAccountsDialog open={showImportDialog} onOpenChange={setShowImportDialog} onSuccess={handleSuccess} />\n <AccountRotationSettings open={showRotationSettings} onOpenChange={setShowRotationSettings} />\n </div>\n );\n}\n","import { useState, useRef, useEffect, useCallback } from \"react\";\nimport { RefreshCw } from \"lucide-react\";\nimport { cn } from \"@/lib/utils\";\nimport { api } from \"@/lib/api-client\";\nimport type { TeamMessageItem } from \"@/hooks/use-chat\";\n\ninterface TeamActivityPanelProps {\n teamNames: string[];\n messages: TeamMessageItem[];\n}\n\nconst STATUS_COLORS: Record<string, string> = {\n active: \"bg-green-500\",\n idle: \"bg-yellow-500\",\n shutdown: \"bg-zinc-400\",\n};\n\nconst TYPE_BADGES: Record<string, { label: string; className: string }> = {\n task_assignment: { label: \"task\", className: \"bg-blue-500/20 text-blue-400\" },\n idle_notification: { label: \"idle\", className: \"bg-yellow-500/20 text-yellow-400\" },\n completion: { label: \"done\", className: \"bg-green-500/20 text-green-400\" },\n shutdown_request: { label: \"shutdown\", className: \"bg-red-500/20 text-red-400\" },\n shutdown_approved: { label: \"shutdown ✓\", className: \"bg-zinc-500/20 text-zinc-400\" },\n};\n\nexport function TeamActivityPanel({ teamNames, messages }: TeamActivityPanelProps) {\n const [selectedTeam, setSelectedTeam] = useState(teamNames[0] ?? \"\");\n const [members, setMembers] = useState<any[]>([]);\n const [loading, setLoading] = useState(false);\n const messagesEndRef = useRef<HTMLDivElement>(null);\n\n // Sync selected team when teamNames changes\n useEffect(() => {\n if (teamNames.length > 0 && !teamNames.includes(selectedTeam)) {\n setSelectedTeam(teamNames[0]!);\n }\n }, [teamNames, selectedTeam]);\n\n const fetchTeamDetail = useCallback(async (name: string) => {\n setLoading(true);\n try {\n const detail = await api.get<any>(`/api/teams/${encodeURIComponent(name)}`);\n setMembers(detail?.members ?? []);\n } catch { setMembers([]); }\n setLoading(false);\n }, []);\n\n // Fetch members on mount and tab switch\n useEffect(() => {\n if (selectedTeam) fetchTeamDetail(selectedTeam);\n }, [selectedTeam, fetchTeamDetail]);\n\n // Auto-scroll messages\n useEffect(() => {\n messagesEndRef.current?.scrollIntoView({ behavior: \"smooth\" });\n }, [messages.length]);\n\n const displayMessages = messages.slice(-200);\n\n return (\n <div className=\"space-y-0\">\n {/* Team tabs + refresh */}\n <div className=\"flex items-center gap-1 mb-2\">\n <div className=\"flex items-center gap-1 overflow-x-auto min-w-0 flex-1\">\n {teamNames.map((name) => (\n <button\n key={name}\n onClick={() => setSelectedTeam(name)}\n className={cn(\n \"px-2 py-0.5 text-[11px] rounded-md whitespace-nowrap transition-colors\",\n selectedTeam === name\n ? \"bg-primary/10 text-primary font-medium\"\n : \"text-text-subtle hover:text-text-primary\"\n )}\n >\n {name}\n </button>\n ))}\n </div>\n <button\n onClick={() => selectedTeam && fetchTeamDetail(selectedTeam)}\n className=\"text-text-subtle hover:text-foreground p-1 shrink-0\"\n aria-label=\"Refresh\"\n >\n <RefreshCw className={cn(\"size-3\", loading && \"animate-spin\")} />\n </button>\n </div>\n\n {/* Members */}\n {members.length > 0 && (\n <div className=\"pb-2 mb-2 border-b border-border/30\">\n <div className=\"text-[10px] text-text-subtle uppercase tracking-wider mb-1\">Members</div>\n <div className=\"space-y-1\">\n {members.map((m: any) => (\n <div key={m.name} className=\"flex items-center gap-2 text-xs\">\n <span className={cn(\"size-1.5 rounded-full shrink-0\", STATUS_COLORS[m.status] ?? \"bg-zinc-400\")} />\n <span className=\"font-medium truncate\">{m.name}</span>\n {m.model && m.model !== \"unknown\" && (\n <span className=\"text-text-subtle text-[10px]\">({m.model})</span>\n )}\n <span className=\"ml-auto text-text-subtle text-[10px]\">{m.status}</span>\n </div>\n ))}\n </div>\n </div>\n )}\n\n {/* Messages */}\n <div className=\"max-h-40 overflow-y-auto\">\n {displayMessages.length === 0 ? (\n <p className=\"text-xs text-text-subtle text-center py-2\">No messages yet</p>\n ) : (\n <div className=\"space-y-2\">\n {displayMessages.map((msg, i) => {\n const badge = msg.parsedType ? TYPE_BADGES[msg.parsedType] : null;\n const time = formatTime(msg.timestamp);\n return (\n <div key={`${msg.timestamp}-${i}`} className=\"text-xs\">\n <div className=\"flex items-center gap-1 text-text-subtle\">\n <span className=\"font-medium\" style={safeColor(msg.color)}>\n {msg.from}\n </span>\n <span>→</span>\n <span>{msg.to}</span>\n <span className=\"ml-auto text-[10px]\">{time}</span>\n </div>\n <div className=\"mt-0.5 text-foreground/90 break-words\">\n {badge && (\n <span className={cn(\"inline-block px-1 py-0 rounded text-[9px] mr-1\", badge.className)}>\n {badge.label}\n </span>\n )}\n {msg.summary ?? truncateText(msg.text)}\n </div>\n </div>\n );\n })}\n <div ref={messagesEndRef} />\n </div>\n )}\n </div>\n </div>\n );\n}\n\nfunction formatTime(timestamp: string): string {\n try {\n const d = new Date(timestamp);\n return d.toLocaleTimeString([], { hour: \"2-digit\", minute: \"2-digit\", second: \"2-digit\" });\n } catch { return \"\"; }\n}\n\n/** Sanitize color value to prevent CSS injection */\nfunction safeColor(color?: string): React.CSSProperties | undefined {\n if (!color) return undefined;\n if (/^#[0-9a-fA-F]{3,8}$/.test(color) || /^[a-zA-Z]{3,20}$/.test(color)) {\n return { color };\n }\n return undefined;\n}\n\nfunction truncateText(text: string, max = 120): string {\n if (!text) return \"\";\n try {\n const parsed = JSON.parse(text);\n return parsed.summary ?? parsed.text ?? text.slice(0, max);\n } catch {}\n return text.length > max ? text.slice(0, max) + \"...\" : text;\n}\n","import { useState, useEffect, useCallback, useRef, type MouseEvent } from \"react\";\nimport { History, Settings2, Loader2, MessageSquare, RefreshCw, Search, Pencil, Check, X, BellOff, Bug, ClipboardCheck, Pin, PinOff, Trash2, Users, Bot, Tags, CalendarX2 } from \"lucide-react\";\nimport { Activity } from \"lucide-react\";\nimport { api, projectUrl } from \"@/lib/api-client\";\nimport { useTabStore } from \"@/stores/tab-store\";\nimport { useNotificationStore, notificationTint } from \"@/stores/notification-store\";\nimport { cn } from \"@/lib/utils\";\nimport { AISettingsSection } from \"@/components/settings/ai-settings-section\";\nimport { TagSettingsSection } from \"@/components/settings/tag-settings-section\";\nimport { SessionContextMenu } from \"./session-context-menu\";\nimport { UsageDetailPanel } from \"./usage-badge\";\nimport { TeamActivityPanel } from \"./team-activity-panel\";\nimport { ProviderBadge } from \"./provider-selector\";\nimport { formatRelativeDate } from \"@/lib/format-date\";\nimport { useDebouncedValue } from \"@/hooks/use-debounced-value\";\nimport type { SessionInfo, SessionListResponse, ProjectTag } from \"../../../types/chat\";\nimport type { UsageInfo } from \"../../../types/chat\";\nimport type { TeamMessageItem } from \"@/hooks/use-chat\";\n\ntype PanelType = \"history\" | \"config\" | \"usage\" | \"team\" | null;\n\ninterface TeamActivityState {\n hasTeams: boolean;\n teamNames: string[];\n messageCount: number;\n unreadCount: number;\n}\n\ninterface ChatHistoryBarProps {\n projectName: string;\n usageInfo: UsageInfo;\n usageLoading?: boolean;\n refreshUsage?: () => void;\n lastFetchedAt?: string | null;\n sessionId?: string | null;\n providerId?: string;\n onSelectSession?: (session: SessionInfo) => void;\n onBugReport?: () => void;\n isConnected?: boolean;\n onReload?: () => void;\n teamActivity?: TeamActivityState;\n teamMessages?: TeamMessageItem[];\n onTeamOpen?: () => void;\n}\n\nfunction relativeTime(iso: string): string {\n const secs = Math.round((Date.now() - new Date(iso).getTime()) / 1000);\n if (secs < 5) return \"now\";\n if (secs < 60) return `${secs}s`;\n const mins = Math.floor(secs / 60);\n if (mins < 60) return `${mins}m`;\n return `${Math.floor(mins / 60)}h`;\n}\n\nfunction pctColor(pct: number): string {\n if (pct >= 90) return \"text-red-500\";\n if (pct >= 70) return \"text-amber-500\";\n return \"text-green-500\";\n}\n\nfunction DebugCopyButton({ sessionId, projectName }: { sessionId: string; projectName: string }) {\n const [copied, setCopied] = useState(false);\n return (\n <button\n onClick={() => {\n try {\n // Use ClipboardItem with pending promise so Safari doesn't lose user gesture\n const textPromise = api.get<{ ppmSessionId: string; sdkSessionId: string; jsonlPath: string | null; projectPath: string }>(\n `${projectUrl(projectName)}/chat/sessions/${sessionId}/debug?project=${encodeURIComponent(projectName)}`,\n ).then((data) => {\n const info = [\n `PPM Session: ${data.ppmSessionId}`,\n `SDK Session: ${data.sdkSessionId}`,\n data.jsonlPath ? `JSONL: ${data.jsonlPath}` : `JSONL: not found`,\n data.projectPath ? `Project: ${data.projectPath}` : null,\n ].filter(Boolean).join(\"\\n\");\n return new Blob([info], { type: \"text/plain\" });\n });\n navigator.clipboard.write([new ClipboardItem({ \"text/plain\": textPromise })]).then(() => {\n setCopied(true);\n setTimeout(() => setCopied(false), 1500);\n });\n } catch { /* silent */ }\n }}\n className={`p-1 rounded transition-colors ${copied ? \"text-green-500 bg-green-500/10\" : \"text-text-subtle hover:text-text-secondary hover:bg-surface-elevated\"}`}\n title={copied ? \"Copied!\" : \"Copy session debug info\"}\n >\n {copied ? <ClipboardCheck className=\"size-3\" /> : <Bug className=\"size-3\" />}\n </button>\n );\n}\n\nexport function ChatHistoryBar({\n projectName, usageInfo, usageLoading, refreshUsage, lastFetchedAt,\n sessionId, providerId, onSelectSession, onBugReport, isConnected, onReload,\n teamActivity, teamMessages, onTeamOpen,\n}: ChatHistoryBarProps) {\n const [activePanel, setActivePanel] = useState<PanelType>(null);\n const [sessions, setSessions] = useState<SessionInfo[]>([]);\n const [loading, setLoading] = useState(false);\n const notifications = useNotificationStore((s) => s.notifications);\n const hasUnread = useNotificationStore((s) => sessionId ? s.notifications.has(sessionId) : false);\n const clearForSession = useNotificationStore((s) => s.clearForSession);\n const [searchQuery, setSearchQuery] = useState(\"\");\n const debouncedSearch = useDebouncedValue(searchQuery, 300);\n const [editingId, setEditingId] = useState<string | null>(null);\n const [editingTitle, setEditingTitle] = useState(\"\");\n const [hasMore, setHasMore] = useState(false);\n const [loadingMore, setLoadingMore] = useState(false);\n const [projectTags, setProjectTags] = useState<ProjectTag[]>([]);\n const [selectedTagId, setSelectedTagId] = useState<number | null>(null);\n const [tagCounts, setTagCounts] = useState<Record<number, number>>({});\n const [showTagSettings, setShowTagSettings] = useState(false);\n const editInputRef = useRef<HTMLInputElement>(null);\n const openTab = useTabStore((s) => s.openTab);\n const PAGE_SIZE = 50;\n\n const togglePanel = (panel: PanelType) => {\n setActivePanel((prev) => prev === panel ? null : panel);\n };\n\n const load = useCallback(async (query?: string) => {\n if (!projectName) return;\n setLoading(true);\n try {\n const params = new URLSearchParams({ limit: String(PAGE_SIZE), offset: \"0\" });\n if (query) params.set(\"q\", query);\n const data = await api.get<SessionListResponse>(`${projectUrl(projectName)}/chat/sessions?${params}`);\n setSessions(data.sessions);\n setHasMore(data.hasMore);\n } catch {\n // silent\n } finally {\n setLoading(false);\n }\n }, [projectName]);\n\n const loadMore = useCallback(async () => {\n if (!projectName || loadingMore || !hasMore) return;\n setLoadingMore(true);\n try {\n // Offset by count of non-pinned sessions (pinned are injected separately by backend)\n const unpinnedCount = sessions.filter((s) => !s.pinned).length;\n const params = new URLSearchParams({ limit: String(PAGE_SIZE), offset: String(unpinnedCount) });\n if (debouncedSearch) params.set(\"q\", debouncedSearch);\n const data = await api.get<SessionListResponse>(`${projectUrl(projectName)}/chat/sessions?${params}`);\n setSessions((prev) => {\n const existingIds = new Set(prev.map((s) => s.id));\n const newSessions = data.sessions.filter((s) => !existingIds.has(s.id));\n return [...prev, ...newSessions];\n });\n setHasMore(data.hasMore);\n } catch {\n // silent\n } finally {\n setLoadingMore(false);\n }\n }, [projectName, loadingMore, hasMore, sessions, debouncedSearch]);\n\n // Load sessions when history panel opens\n useEffect(() => {\n if (activePanel === \"history\" && sessions.length === 0) load();\n }, [activePanel]); // eslint-disable-line react-hooks/exhaustive-deps\n\n // Re-fetch when debounced search query changes (server-side search)\n useEffect(() => {\n if (activePanel === \"history\") load(debouncedSearch || undefined);\n }, [debouncedSearch]); // eslint-disable-line react-hooks/exhaustive-deps\n\n // Fetch tags\n const loadTags = useCallback(async () => {\n if (!projectName) return;\n try {\n const data = await api.get<{ tags: ProjectTag[]; counts: Record<number, number> }>(\n `${projectUrl(projectName)}/tags`,\n );\n setProjectTags(data.tags);\n setTagCounts(data.counts);\n } catch { /* silent */ }\n }, [projectName]);\n\n useEffect(() => {\n if (activePanel === \"history\" && projectName) loadTags();\n }, [activePanel, projectName, loadTags]);\n\n function openSession(session: SessionInfo) {\n if (onSelectSession) {\n onSelectSession(session);\n setActivePanel(null);\n } else {\n openTab({\n type: \"chat\",\n title: session.title || \"Chat\",\n projectId: projectName ?? null,\n metadata: { projectName, sessionId: session.id, providerId: session.providerId },\n closable: true,\n });\n }\n }\n\n const startEditing = useCallback((session: SessionInfo, e: React.MouseEvent) => {\n e.stopPropagation();\n setEditingId(session.id);\n setEditingTitle(session.title || \"\");\n setTimeout(() => editInputRef.current?.select(), 0);\n }, []);\n\n const saveTitle = useCallback(async () => {\n if (!editingId || !editingTitle.trim() || !projectName) {\n setEditingId(null);\n return;\n }\n try {\n await api.patch(`${projectUrl(projectName)}/chat/sessions/${editingId}`, { title: editingTitle.trim() });\n setSessions((prev) => prev.map((s) => s.id === editingId ? { ...s, title: editingTitle.trim() } : s));\n } catch { /* silent */ }\n setEditingId(null);\n }, [editingId, editingTitle, projectName]);\n\n const cancelEditing = useCallback(() => setEditingId(null), []);\n\n const togglePin = useCallback(async (e: React.MouseEvent, session: SessionInfo) => {\n e.stopPropagation();\n if (!projectName) return;\n const url = `${projectUrl(projectName)}/chat/sessions/${session.id}/pin`;\n try {\n if (session.pinned) {\n await api.del(url);\n } else {\n await api.put(url);\n }\n setSessions((prev) => {\n const updated = prev.map((s) => s.id === session.id ? { ...s, pinned: !s.pinned } : s);\n return updated.sort((a, b) => {\n if (a.pinned && !b.pinned) return -1;\n if (!a.pinned && b.pinned) return 1;\n return new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime();\n });\n });\n } catch { /* silent */ }\n }, [projectName]);\n\n const deleteSession = useCallback(async (e: React.MouseEvent, session: SessionInfo) => {\n e.stopPropagation();\n if (!projectName) return;\n if (!window.confirm(\"Delete this session? This cannot be undone.\")) return;\n try {\n await api.del(`${projectUrl(projectName)}/chat/sessions/${session.id}?providerId=${session.providerId}`);\n setSessions((prev) => prev.filter((s) => s.id !== session.id));\n } catch { /* silent */ }\n }, [projectName]);\n\n const handleTagChanged = useCallback((sid: string, tag: { id: number; name: string; color: string } | null) => {\n setSessions((prev) => prev.map((s) => s.id === sid ? { ...s, tag } : s));\n loadTags(); // Refetch counts from API for accuracy\n }, [loadTags]);\n\n const bulkDelete = useCallback(async () => {\n if (!projectName) return;\n const days = window.prompt(\"Delete sessions older than how many days? (pinned sessions are kept)\", \"30\");\n if (!days) return;\n const num = parseInt(days, 10);\n if (!num || num < 1) return;\n if (!window.confirm(`Delete all unpinned sessions older than ${num} days? This cannot be undone.`)) return;\n setLoading(true);\n try {\n await api.del(`${projectUrl(projectName)}/chat/sessions?olderThanDays=${num}`);\n load(debouncedSearch || undefined);\n } catch { /* silent */ }\n }, [projectName, load, debouncedSearch]);\n\n // Keyboard shortcuts: 1-9 to assign tags to current session\n useEffect(() => {\n if (activePanel !== \"history\") return;\n const handler = (e: KeyboardEvent) => {\n if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) return;\n const num = parseInt(e.key);\n if (num >= 1 && num <= projectTags.length && sessionId) {\n const tag = projectTags[num - 1];\n if (tag) {\n api.patch(`${projectUrl(projectName)}/chat/sessions/${sessionId}/tag`, { tagId: tag.id }).catch(() => {});\n handleTagChanged(sessionId, { id: tag.id, name: tag.name, color: tag.color });\n }\n }\n };\n window.addEventListener(\"keydown\", handler);\n return () => window.removeEventListener(\"keydown\", handler);\n }, [activePanel, projectTags, sessionId, projectName, handleTagChanged]);\n\n // Filter by tag client-side (search is now server-side via ?q=)\n const filteredSessions = selectedTagId !== null\n ? sessions.filter((s) => s.tag?.id === selectedTagId)\n : sessions;\n\n // Usage badge display — only meaningful for Claude (SDK) provider\n const isClaudeProvider = !providerId || providerId === \"claude\";\n const fiveHourPct = usageInfo.fiveHour != null ? Math.round(usageInfo.fiveHour * 100) : null;\n const sevenDayPct = usageInfo.sevenDay != null ? Math.round(usageInfo.sevenDay * 100) : null;\n const worstPct = Math.max(fiveHourPct ?? 0, sevenDayPct ?? 0);\n const usageColor = fiveHourPct != null || sevenDayPct != null ? pctColor(worstPct) : \"text-text-subtle\";\n\n return (\n <div className=\"border-b border-border/50\">\n {/* Toolbar row — all buttons on one line */}\n <div className=\"flex items-center gap-1 px-2 py-1\">\n {/* History */}\n <button\n onClick={() => togglePanel(\"history\")}\n className={`flex items-center gap-1 px-1.5 py-0.5 rounded text-[11px] transition-colors ${\n activePanel === \"history\" ? \"text-primary bg-primary/10\" : \"text-text-secondary hover:text-foreground hover:bg-surface-elevated\"\n }`}\n >\n <History className=\"size-3\" />\n <span>History</span>\n </button>\n\n {/* Active provider + AI Settings (combined) */}\n {sessionId && providerId && providerId !== \"mock\" ? (\n <button\n onClick={() => togglePanel(\"config\")}\n className={`flex items-center gap-1 px-1.5 py-0.5 rounded text-[11px] transition-colors ${\n activePanel === \"config\" ? \"text-primary bg-primary/10\" : \"text-text-secondary hover:text-foreground hover:bg-surface-elevated\"\n }`}\n title=\"AI Settings\"\n >\n <ProviderBadge providerId={providerId} />\n <span className=\"capitalize\">{providerId}</span>\n </button>\n ) : (\n <button\n onClick={() => togglePanel(\"config\")}\n className={`p-1 rounded transition-colors ${\n activePanel === \"config\" ? \"text-primary bg-primary/10\" : \"text-text-subtle hover:text-text-secondary hover:bg-surface-elevated\"\n }`}\n title=\"AI Settings\"\n >\n <Settings2 className=\"size-3\" />\n </button>\n )}\n\n {/* Usage & Accounts — full display for Claude, minimal for other providers */}\n {isClaudeProvider ? (\n <button\n onClick={() => togglePanel(\"usage\")}\n className={`flex items-center gap-1 px-1.5 py-0.5 rounded text-[11px] font-medium tabular-nums transition-colors hover:bg-surface-elevated ${\n activePanel === \"usage\" ? \"bg-primary/10\" : \"\"\n } ${usageColor}`}\n title=\"Usage limits\"\n >\n <Activity className=\"size-3\" />\n {usageInfo.activeAccountLabel && (\n <span className=\"text-text-secondary font-normal truncate max-w-[60px]\">[{usageInfo.activeAccountLabel}]</span>\n )}\n <span>5h:{fiveHourPct != null ? `${fiveHourPct}%` : \"--%\"}</span>\n <span className=\"text-text-subtle\">·</span>\n <span>Wk:{sevenDayPct != null ? `${sevenDayPct}%` : \"--%\"}</span>\n </button>\n ) : null}\n\n {/* Team activity */}\n {teamActivity?.hasTeams && (\n <button\n onClick={() => {\n togglePanel(\"team\");\n onTeamOpen?.();\n }}\n className={`relative flex items-center gap-1 px-1.5 py-0.5 rounded text-[11px] transition-colors ${\n activePanel === \"team\" ? \"text-primary bg-primary/10\" : \"text-text-secondary hover:text-foreground hover:bg-surface-elevated\"\n }`}\n title=\"Team activity\"\n >\n <Users className=\"size-3\" />\n <span>Team</span>\n {(teamActivity.unreadCount ?? 0) > 0 && (\n <span className=\"absolute -top-0.5 -right-0.5 size-2 bg-primary rounded-full animate-pulse\" />\n )}\n </button>\n )}\n\n {/* Spacer */}\n <div className=\"flex-1\" />\n\n {/* Mark as read */}\n {hasUnread && sessionId && (\n <button\n onClick={() => clearForSession(sessionId)}\n className=\"p-1 rounded text-amber-500 hover:text-amber-400 hover:bg-surface-elevated transition-colors\"\n title=\"Mark as read\"\n >\n <BellOff className=\"size-3\" />\n </button>\n )}\n\n {/* Debug info — copy session IDs + JSONL path */}\n {sessionId && (\n <DebugCopyButton sessionId={sessionId} projectName={projectName} />\n )}\n\n {/* Reload messages + connection status */}\n {onReload && (\n <button\n onClick={(e: MouseEvent<HTMLButtonElement>) => {\n const icon = e.currentTarget.querySelector(\"svg\");\n if (icon) {\n icon.classList.add(\"animate-spin\");\n setTimeout(() => icon.classList.remove(\"animate-spin\"), 600);\n }\n onReload();\n }}\n className=\"relative size-4 flex items-center justify-center\"\n title={isConnected ? \"Reload messages\" : \"Disconnected — click to reload\"}\n >\n <RefreshCw className={`size-3 ${isConnected ? \"text-muted-foreground/60\" : \"text-red-400\"}`} strokeWidth={2.5} />\n <span className={`absolute -top-0.5 -right-0.5 size-1.5 rounded-full ${isConnected ? \"bg-green-500\" : \"bg-red-500 animate-pulse\"}`} />\n </button>\n )}\n </div>\n\n {/* Panels — only one visible at a time */}\n\n {/* History panel */}\n {activePanel === \"history\" && (\n <div className=\"border-t border-border/30 bg-surface\">\n {/* Search + refresh */}\n <div className=\"flex items-center gap-1.5 px-2 py-1 border-b border-border/30\">\n <Search className=\"size-3 text-text-subtle shrink-0\" />\n <input\n type=\"text\"\n value={searchQuery}\n onChange={(e) => setSearchQuery(e.target.value)}\n placeholder=\"Search sessions...\"\n className=\"flex-1 bg-transparent text-[11px] text-text-primary outline-none placeholder:text-text-subtle\"\n />\n <button\n onClick={bulkDelete}\n className=\"p-0.5 rounded text-text-subtle hover:text-red-400 transition-colors\"\n title=\"Delete old sessions...\"\n >\n <CalendarX2 className=\"size-3\" />\n </button>\n <button\n onClick={() => load(debouncedSearch || undefined)}\n disabled={loading}\n className=\"p-0.5 rounded text-text-subtle hover:text-text-secondary transition-colors disabled:opacity-50\"\n title=\"Refresh\"\n >\n <RefreshCw className={`size-3 ${loading ? \"animate-spin\" : \"\"}`} />\n </button>\n </div>\n\n {/* Tag filter chips */}\n {projectTags.length > 0 && (\n <div className=\"flex items-center gap-1 px-2 py-1 overflow-x-auto border-b border-border/30 scrollbar-none\">\n <button\n onClick={() => setSelectedTagId(null)}\n className={`shrink-0 rounded-md border px-2 py-1 text-[10px] transition-colors ${\n selectedTagId === null ? \"bg-primary/20 border-primary text-primary\" : \"border-border bg-surface text-text-secondary\"\n }`}\n >All ({sessions.length})</button>\n {projectTags.map((tag) => (\n <button\n key={tag.id}\n onClick={() => setSelectedTagId(selectedTagId === tag.id ? null : tag.id)}\n className={`shrink-0 flex items-center gap-1 rounded-md border px-2 py-1 text-[10px] transition-colors ${\n selectedTagId === tag.id ? \"border-current\" : \"border-border bg-surface\"\n }`}\n style={selectedTagId === tag.id ? { backgroundColor: tag.color + \"20\", color: tag.color, borderColor: tag.color } : undefined}\n >\n <span className=\"size-2 rounded-full shrink-0\" style={{ backgroundColor: tag.color }} />\n {tag.name} ({tagCounts[tag.id] ?? 0})\n </button>\n ))}\n <button\n onClick={() => setShowTagSettings(!showTagSettings)}\n className={`shrink-0 p-1 rounded transition-colors ${showTagSettings ? \"text-primary bg-primary/10\" : \"text-text-subtle hover:text-text-secondary\"}`}\n title=\"Manage tags\"\n >\n <Tags className=\"size-3\" />\n </button>\n </div>\n )}\n\n {/* Tag management panel (inline) */}\n {showTagSettings && (\n <div className=\"border-b border-border/30 px-2 py-2 max-h-[180px] overflow-y-auto bg-surface-elevated/50\">\n <TagSettingsSection projectName={projectName} onTagsChanged={loadTags} />\n </div>\n )}\n\n <div className=\"max-h-[200px] overflow-y-auto\">\n {loading && sessions.length === 0 ? (\n <div className=\"flex items-center justify-center py-3\">\n <Loader2 className=\"size-3.5 animate-spin text-text-subtle\" />\n </div>\n ) : filteredSessions.length === 0 ? (\n <div className=\"flex items-center justify-center py-3 text-[11px] text-text-subtle\">\n {searchQuery ? \"No matching sessions\" : \"No sessions yet\"}\n </div>\n ) : (\n <>\n {filteredSessions.map((session) => {\n const notif = notifications.get(session.id);\n const isUnread = !!notif;\n return (\n <SessionContextMenu\n key={session.id}\n session={session}\n projectName={projectName}\n projectTags={projectTags}\n onTogglePin={togglePin}\n onStartEditing={startEditing}\n onDeleteSession={deleteSession}\n onTagChanged={handleTagChanged}\n >\n <div\n className={cn(\n \"flex items-center gap-2 w-full px-3 py-1.5 text-left hover:bg-surface-elevated transition-colors group\",\n isUnread && \"font-medium text-foreground\",\n isUnread && notificationTint(notif.type),\n !isUnread && \"text-text-secondary\",\n )}\n >\n <ProviderBadge providerId={session.providerId} />\n {session.tag && (\n <span className=\"size-2 rounded-full shrink-0\" style={{ backgroundColor: session.tag.color }} title={session.tag.name} />\n )}\n {editingId === session.id ? (\n <form\n className=\"flex items-center gap-1 flex-1 min-w-0\"\n onSubmit={(e) => { e.preventDefault(); saveTitle(); }}\n >\n <input\n ref={editInputRef}\n value={editingTitle}\n onChange={(e) => setEditingTitle(e.target.value)}\n onBlur={saveTitle}\n onKeyDown={(e) => { if (e.key === \"Escape\") cancelEditing(); }}\n className=\"flex-1 min-w-0 bg-surface-elevated text-[11px] text-text-primary px-1 py-0.5 rounded border border-border outline-none focus:border-primary\"\n autoFocus\n />\n <button type=\"submit\" className=\"p-0.5 text-green-500 hover:text-green-400\" onClick={(e) => e.stopPropagation()}>\n <Check className=\"size-3\" />\n </button>\n <button type=\"button\" className=\"p-0.5 text-text-subtle hover:text-text-secondary\" onClick={(e) => { e.stopPropagation(); cancelEditing(); }}>\n <X className=\"size-3\" />\n </button>\n </form>\n ) : (\n <>\n <button\n onClick={() => openSession(session)}\n className=\"text-[11px] truncate flex-1 text-left flex items-center gap-1\"\n >\n {session.title?.startsWith(\"[PPM]\") && (\n <Bot className=\"size-3 text-muted-foreground shrink-0\" />\n )}\n {session.title?.startsWith(\"[PPM]\")\n ? session.title.slice(7)\n : session.title || \"Untitled\"}\n </button>\n <button\n onClick={(e) => togglePin(e, session)}\n className={`p-0.5 rounded transition-all ${\n session.pinned\n ? \"text-primary hover:text-primary/70\"\n : \"text-text-subtle hover:text-text-secondary can-hover:opacity-0 can-hover:group-hover:opacity-100\"\n }`}\n title={session.pinned ? \"Unpin session\" : \"Pin session\"}\n >\n {session.pinned ? <PinOff className=\"size-3\" /> : <Pin className=\"size-3\" />}\n </button>\n <button\n onClick={(e) => startEditing(session, e)}\n className=\"p-0.5 rounded text-text-subtle hover:text-text-secondary can-hover:opacity-0 can-hover:group-hover:opacity-100 transition-opacity\"\n title=\"Rename session\"\n >\n <Pencil className=\"size-3\" />\n </button>\n <button\n onClick={(e) => deleteSession(e, session)}\n className=\"p-0.5 rounded text-text-subtle hover:text-red-400 hover:bg-red-500/20 can-hover:opacity-0 can-hover:group-hover:opacity-100 transition-opacity\"\n title=\"Delete session\"\n >\n <Trash2 className=\"size-3\" />\n </button>\n </>\n )}\n {editingId !== session.id && session.updatedAt && (\n <span className=\"text-[10px] text-text-subtle shrink-0 w-16 text-right\">{formatRelativeDate(session.updatedAt)}</span>\n )}\n </div>\n </SessionContextMenu>\n );\n })}\n {hasMore && (\n <button\n onClick={loadMore}\n disabled={loadingMore}\n className=\"flex items-center justify-center gap-1 w-full py-1.5 text-[11px] text-text-subtle hover:text-text-secondary hover:bg-surface-elevated transition-colors\"\n >\n {loadingMore ? <Loader2 className=\"size-3 animate-spin\" /> : null}\n {loadingMore ? \"Loading...\" : \"Load more\"}\n </button>\n )}\n </>\n )}\n </div>\n </div>\n )}\n\n {/* Config panel */}\n {activePanel === \"config\" && (\n <div className=\"border-t border-border/30 bg-surface px-3 py-2 max-h-[280px] overflow-y-auto\">\n <AISettingsSection compact />\n </div>\n )}\n\n {/* Team activity panel */}\n {activePanel === \"team\" && teamActivity?.hasTeams && (\n <div className=\"border-t border-border/30 bg-surface px-3 py-2 max-h-[280px] overflow-y-auto\">\n <TeamActivityPanel\n teamNames={teamActivity.teamNames}\n messages={teamMessages ?? []}\n />\n </div>\n )}\n\n {/* Usage panel — only for Claude provider */}\n {activePanel === \"usage\" && isClaudeProvider && (\n <UsageDetailPanel\n usage={usageInfo}\n visible={true}\n onClose={() => setActivePanel(null)}\n onReload={refreshUsage}\n loading={usageLoading}\n lastFetchedAt={lastFetchedAt}\n />\n )}\n\n </div>\n );\n}\n","import { useState, useCallback, useRef, useEffect } from \"react\";\nimport { Loader2, Upload, X } from \"lucide-react\";\nimport { api, projectUrl } from \"@/lib/api-client\";\nimport { useChat } from \"@/hooks/use-chat\";\nimport { useUsage } from \"@/hooks/use-usage\";\nimport { useTabStore } from \"@/stores/tab-store\";\nimport { useSettingsStore } from \"@/stores/settings-store\";\nimport { usePanelStore } from \"@/stores/panel-store\";\nimport { useNotificationStore } from \"@/stores/notification-store\";\nimport { openBugReportPopup } from \"@/lib/report-bug\";\nimport { getAISettings } from \"@/lib/api-settings\";\nimport { MessageList } from \"./message-list\";\nimport { MessageInput, type ChatAttachment, type MessagePriority } from \"./message-input\";\nimport { SlashCommandPicker, type SlashItem } from \"./slash-command-picker\";\nimport { FilePicker } from \"./file-picker\";\nimport { ChatHistoryBar } from \"./chat-history-bar\";\n\nimport type { DragEvent } from \"react\";\nimport type { FileNode } from \"../../../types/project\";\nimport type { Session, SessionInfo } from \"../../../types/chat\";\n\ninterface ChatTabProps {\n metadata?: Record<string, unknown>;\n tabId?: string;\n}\n\nexport function ChatTab({ metadata, tabId }: ChatTabProps) {\n const [sessionId, setSessionId] = useState<string | null>(\n (metadata?.sessionId as string) ?? null,\n );\n const [providerId, setProviderId] = useState<string>(\n (metadata?.providerId as string) ?? \"claude\",\n );\n\n // Slash picker state\n const [slashItems, setSlashItems] = useState<SlashItem[]>([]);\n const [showSlashPicker, setShowSlashPicker] = useState(false);\n const [slashFilter, setSlashFilter] = useState(\"\");\n const [slashSelected, setSlashSelected] = useState<SlashItem | null>(null);\n const [slashRecentNames, setSlashRecentNames] = useState<string[]>([]);\n\n // File picker state\n const [fileItems, setFileItems] = useState<FileNode[]>([]);\n const [showFilePicker, setShowFilePicker] = useState(false);\n const [fileFilter, setFileFilter] = useState(\"\");\n const [fileSelected, setFileSelected] = useState<FileNode | null>(null);\n\n // Permission mode — per-session sticky, falls back to global default\n const [permissionMode, setPermissionMode] = useState<string | undefined>(\n (metadata?.permissionMode as string) ?? undefined,\n );\n\n // Pending message to send after WS connects (replaces unreliable setTimeout)\n const pendingSendRef = useRef<{ content: string; permissionMode?: string } | null>(null);\n\n // Drag-and-drop state\n const [isDragging, setIsDragging] = useState(false);\n const [externalFiles, setExternalFiles] = useState<File[] | null>(null);\n const [externalPaths, setExternalPaths] = useState<string[] | null>(null);\n const [disambiguateItems, setDisambiguateItems] = useState<FileNode[] | null>(null);\n const dragCounterRef = useRef(0);\n\n // Use tab's own project, not global activeProject (keep-alive: hidden tabs must not react to switches)\n const projectName = (metadata?.projectName as string) ?? \"\";\n const updateTab = useTabStore((s) => s.updateTab);\n const version = useSettingsStore((s) => s.version);\n\n // Usage runs independently — auto-refreshes on interval\n const { usageInfo, usageLoading, lastFetchedAt, refreshUsage } =\n useUsage(projectName, providerId);\n\n // Load global default permission mode on mount (if no per-session override)\n useEffect(() => {\n if (permissionMode) return;\n getAISettings().then((s) => {\n const provider = s.providers[s.default_provider ?? \"claude\"];\n setPermissionMode(provider?.permission_mode ?? \"bypassPermissions\");\n }).catch(() => {});\n }, []); // eslint-disable-line react-hooks/exhaustive-deps\n\n // Persist sessionId, providerId, and permissionMode to tab metadata\n useEffect(() => {\n if (!tabId || !sessionId) return;\n updateTab(tabId, {\n metadata: { ...metadata, sessionId, providerId, permissionMode },\n });\n }, [sessionId, providerId, permissionMode]); // eslint-disable-line react-hooks/exhaustive-deps\n\n const {\n messages,\n renderedMessages,\n expandCompact,\n isCompactExpanded,\n messagesLoading,\n isStreaming,\n phase,\n isReconnecting,\n connectingElapsed,\n pendingApproval,\n contextWindowPct,\n compactStatus,\n statusMessage,\n sessionTitle,\n sendMessage,\n respondToApproval,\n cancelStreaming,\n reconnect,\n refetchMessages,\n isConnected,\n teamActivity,\n teamMessages,\n markTeamRead,\n bashPartialOutput,\n } = useChat(sessionId, providerId, projectName);\n\n // Flush pending message once WS connects (replaces unreliable setTimeout)\n useEffect(() => {\n if (isConnected && pendingSendRef.current) {\n const { content, permissionMode: pm } = pendingSendRef.current;\n pendingSendRef.current = null;\n sendMessage(content, { permissionMode: pm });\n }\n }, [isConnected, sendMessage]);\n\n // Auto-clear notification badge when this tab is active and document is visible.\n // Handles the case where notification arrived while browser tab was hidden.\n useEffect(() => {\n if (!sessionId || !tabId) return;\n const maybeClear = () => {\n if (document.hidden) return;\n const { panels, focusedPanelId } = usePanelStore.getState();\n const panel = panels[focusedPanelId];\n if (panel?.activeTabId === tabId) {\n useNotificationStore.getState().clearForSession(sessionId);\n }\n };\n maybeClear();\n document.addEventListener(\"visibilitychange\", maybeClear);\n const unsub = usePanelStore.subscribe(maybeClear);\n // Also auto-clear when notification store changes (cross-tab broadcast may add for active session)\n const unsub2 = useNotificationStore.subscribe(maybeClear);\n return () => {\n document.removeEventListener(\"visibilitychange\", maybeClear);\n unsub();\n unsub2();\n };\n }, [sessionId, tabId]);\n\n // Update tab title when SDK summary arrives\n useEffect(() => {\n if (tabId && sessionTitle) {\n updateTab(tabId, { title: sessionTitle });\n }\n }, [sessionTitle]); // eslint-disable-line react-hooks/exhaustive-deps\n\n // Pending fork message — show in input for user to edit, not auto-send\n const [forkDraft, setForkDraft] = useState<string | undefined>(metadata?.pendingMessage as string | undefined);\n useEffect(() => {\n if (forkDraft && isConnected && sessionId && tabId) {\n // Clear from tab metadata once consumed\n updateTab(tabId, { metadata: { ...metadata, pendingMessage: undefined } });\n }\n }, [isConnected, sessionId]); // eslint-disable-line react-hooks/exhaustive-deps\n\n const handleNewSession = useCallback(() => {\n useTabStore.getState().openTab({\n type: \"chat\",\n title: \"AI Chat\",\n metadata: { projectName, providerId },\n projectId: projectName || null,\n closable: true,\n });\n }, [projectName, providerId]);\n\n const handleSelectSession = useCallback((session: SessionInfo) => {\n setSessionId(session.id);\n setProviderId(session.providerId);\n if (tabId) updateTab(tabId, { title: session.title || \"Chat\" });\n }, [tabId, updateTab]);\n\n /** Fork current session and open new tab with the forked session, resending userMessage */\n const handleFork = useCallback(async (userMessage: string, messageId?: string) => {\n if (!sessionId || !projectName) return;\n try {\n const { api, projectUrl } = await import(\"@/lib/api-client\");\n const forked = await api.post<{ id: string; forkedFrom: string }>(\n `${projectUrl(projectName)}/chat/sessions/${sessionId}/fork?providerId=${providerId}`,\n { messageId },\n );\n // Open new chat tab with forked session — it will send userMessage on connect\n useTabStore.getState().openTab({\n type: \"chat\",\n title: `Fork: ${userMessage.slice(0, 30)}`,\n metadata: { projectName, sessionId: forked.id, providerId, pendingMessage: userMessage },\n projectId: projectName || null,\n closable: true,\n });\n } catch (e) {\n console.error(\"Fork failed:\", e);\n }\n }, [sessionId, projectName, providerId]);\n\n /** Build message content with file references and inline text snippets prepended */\n const buildMessageWithAttachments = useCallback(\n (content: string, attachments: ChatAttachment[]): string => {\n if (attachments.length === 0) return content;\n\n const parts: string[] = [];\n\n // Inline text snippets (e.g. terminal output)\n for (const att of attachments) {\n if (att.textContent) parts.push(att.textContent);\n }\n\n // Server-uploaded file references\n const fileAtts = attachments.filter((a) => a.serverPath);\n if (fileAtts.length > 0) {\n const fileRefs = fileAtts.map((a) => a.serverPath!).join(\"\\n\");\n parts.push(\n fileAtts.length === 1\n ? `[Attached file: ${fileRefs}]`\n : `[Attached files:\\n${fileRefs}\\n]`,\n );\n }\n\n if (parts.length === 0) return content;\n return parts.join(\"\\n\\n\") + \"\\n\\n\" + content;\n },\n [],\n );\n\n const handleSend = useCallback(\n async (content: string, attachments: ChatAttachment[] = [], priority?: MessagePriority) => {\n const fullContent = buildMessageWithAttachments(content, attachments);\n if (!fullContent.trim()) return;\n\n if (!sessionId) {\n try {\n const pName = projectName;\n const session = await api.post<Session>(`${projectUrl(pName)}/chat/sessions`, {\n providerId,\n title: content.slice(0, 50),\n });\n setSessionId(session.id);\n setProviderId(session.providerId);\n // Queue message — will be sent by effect when WS reports isConnected\n pendingSendRef.current = { content: fullContent, permissionMode };\n return;\n } catch (e) {\n console.error(\"Failed to create session:\", e);\n return;\n }\n }\n sendMessage(fullContent, { permissionMode, priority });\n },\n [sessionId, providerId, projectName, sendMessage, buildMessageWithAttachments, permissionMode],\n );\n\n /** Stable wrapper for MessageInput onSend — clears forkDraft and delegates to handleSend */\n const handleInputSend = useCallback(\n (content: string, attachments: ChatAttachment[], priority?: MessagePriority) => {\n setForkDraft(undefined);\n handleSend(content, attachments, priority);\n },\n [handleSend],\n );\n\n /** Stable callback for slash items loaded — prevents MessageInput memo break */\n const handleSlashItemsLoaded = useCallback(\n (items: SlashItem[], recentNames?: string[]) => {\n setSlashItems(items);\n if (recentNames) setSlashRecentNames(recentNames);\n },\n [],\n );\n\n // --- Slash picker handlers ---\n const handleSlashStateChange = useCallback((visible: boolean, filter: string) => {\n setShowSlashPicker(visible);\n setSlashFilter(filter);\n }, []);\n\n const handleSlashSelect = useCallback((item: SlashItem) => {\n setSlashSelected(item);\n setShowSlashPicker(false);\n setSlashFilter(\"\");\n setTimeout(() => setSlashSelected(null), 50);\n // Record usage for recents (fire-and-forget)\n if (projectName) {\n api.post(`${projectUrl(projectName)}/chat/slash-recents`, { name: item.name, type: item.type }).catch(() => {});\n // Optimistic update: add to front of recents\n setSlashRecentNames((prev) => [item.name, ...prev.filter((n) => n !== item.name)].slice(0, 5));\n }\n }, [projectName]);\n\n const handleSlashClose = useCallback(() => {\n setShowSlashPicker(false);\n setSlashFilter(\"\");\n }, []);\n\n // Stable callback: clear external paths once consumed (avoids inline lambda breaking MessageInput memo)\n const handleExternalPathsConsumed = useCallback(() => setExternalPaths(null), []);\n\n // --- Disambiguation picker handler (OS drag resolve with multiple matches) ---\n const handleDisambiguate = useCallback((matches: FileNode[]) => {\n setDisambiguateItems(matches);\n }, []);\n\n const handleDisambiguateSelect = useCallback((item: FileNode) => {\n setExternalPaths([item.path]);\n setDisambiguateItems(null);\n }, []);\n\n // --- File picker handlers ---\n const handleFileStateChange = useCallback((visible: boolean, filter: string) => {\n setShowFilePicker(visible);\n setFileFilter(filter);\n }, []);\n\n const handleFileSelect = useCallback((item: FileNode) => {\n setFileSelected(item);\n setShowFilePicker(false);\n setFileFilter(\"\");\n setTimeout(() => setFileSelected(null), 50);\n }, []);\n\n const handleFileClose = useCallback(() => {\n setShowFilePicker(false);\n setFileFilter(\"\");\n }, []);\n\n // --- Drag-and-drop on entire chat area ---\n const handleDragEnter = useCallback((e: DragEvent) => {\n e.preventDefault();\n dragCounterRef.current++;\n if (e.dataTransfer.types.includes(\"application/x-ppm-path\") || e.dataTransfer.types.includes(\"Files\")) {\n setIsDragging(true);\n }\n }, []);\n\n const handleDragLeave = useCallback((e: DragEvent) => {\n e.preventDefault();\n dragCounterRef.current--;\n if (dragCounterRef.current === 0) {\n setIsDragging(false);\n }\n }, []);\n\n const handleDragOver = useCallback((e: DragEvent) => {\n e.preventDefault();\n }, []);\n\n const handleDrop = useCallback((e: DragEvent) => {\n e.preventDefault();\n dragCounterRef.current = 0;\n setIsDragging(false);\n\n // Check for internal file tree drag (custom MIME) first\n const ppmPath = e.dataTransfer.getData(\"application/x-ppm-path\");\n if (ppmPath) {\n setExternalPaths([ppmPath]);\n return;\n }\n\n const files = Array.from(e.dataTransfer.files);\n if (files.length > 0) {\n setExternalFiles(files);\n // Reset after a tick so the effect fires even with same files\n setTimeout(() => setExternalFiles(null), 100);\n }\n }, []);\n\n return (\n <div\n className=\"flex flex-col h-full relative\"\n onDragEnter={handleDragEnter}\n onDragLeave={handleDragLeave}\n onDragOver={handleDragOver}\n onDrop={handleDrop}\n >\n {/* Drag overlay */}\n {isDragging && (\n <div className=\"absolute inset-0 z-50 flex items-center justify-center bg-background/80 backdrop-blur-sm border-2 border-dashed border-primary rounded-lg pointer-events-none\">\n <div className=\"flex flex-col items-center gap-2 text-primary\">\n <Upload className=\"size-8\" />\n <span className=\"text-sm font-medium\">Drop files to attach</span>\n </div>\n </div>\n )}\n\n {/* Reconnect overlay */}\n {isReconnecting && (\n <div className=\"absolute inset-0 z-50 flex items-center justify-center bg-background/60 backdrop-blur-sm\">\n <div className=\"flex items-center gap-2 text-sm text-muted-foreground\">\n <Loader2 className=\"size-4 animate-spin\" />\n <span>Reconnecting...</span>\n </div>\n </div>\n )}\n\n {/* Messages */}\n <MessageList\n messages={renderedMessages}\n onExpandCompact={expandCompact}\n isCompactExpanded={isCompactExpanded}\n messagesLoading={messagesLoading}\n pendingApproval={pendingApproval}\n onApprovalResponse={respondToApproval}\n isStreaming={isStreaming}\n phase={phase}\n connectingElapsed={connectingElapsed}\n statusMessage={statusMessage}\n compactStatus={compactStatus}\n projectName={projectName}\n onFork={!isStreaming ? handleFork : undefined}\n onSelectSession={handleSelectSession}\n bashPartialOutput={bashPartialOutput}\n />\n\n {/* Bottom toolbar */}\n <div className=\"border-t border-border bg-background shrink-0\">\n {/* Unified toolbar: History, Config, Usage, Bug report, Connection */}\n <ChatHistoryBar\n projectName={projectName}\n usageInfo={usageInfo}\n usageLoading={usageLoading}\n refreshUsage={refreshUsage}\n lastFetchedAt={lastFetchedAt}\n sessionId={sessionId}\n providerId={providerId}\n onSelectSession={handleSelectSession}\n onBugReport={sessionId ? () => openBugReportPopup(version, { sessionId, projectName }) : undefined}\n isConnected={isConnected}\n onReload={() => {\n if (!isConnected) reconnect();\n refetchMessages();\n }}\n teamActivity={teamActivity}\n teamMessages={teamMessages}\n onTeamOpen={markTeamRead}\n />\n\n {/* Pickers (in-flow, above input — only one visible at a time) */}\n <SlashCommandPicker\n items={slashItems}\n filter={slashFilter}\n onSelect={handleSlashSelect}\n onClose={handleSlashClose}\n visible={showSlashPicker}\n recentNames={slashRecentNames}\n projectName={projectName}\n />\n <FilePicker\n items={fileItems}\n filter={fileFilter}\n onSelect={handleFileSelect}\n onClose={handleFileClose}\n visible={showFilePicker}\n />\n {disambiguateItems && (\n <FilePicker\n items={disambiguateItems}\n filter=\"\"\n onSelect={handleDisambiguateSelect}\n onClose={() => setDisambiguateItems(null)}\n visible={true}\n />\n )}\n\n {/* Input */}\n <MessageInput\n onSend={handleInputSend}\n isStreaming={isStreaming}\n onCancel={cancelStreaming}\n autoFocus={!(metadata?.sessionId) || !!forkDraft}\n initialValue={forkDraft}\n projectName={projectName}\n onSlashStateChange={handleSlashStateChange}\n onSlashItemsLoaded={handleSlashItemsLoaded}\n slashSelected={slashSelected}\n onFileStateChange={handleFileStateChange}\n onFileItemsLoaded={setFileItems}\n fileSelected={fileSelected}\n externalFiles={externalFiles}\n externalPaths={externalPaths}\n onExternalPathsConsumed={handleExternalPathsConsumed}\n onDisambiguate={handleDisambiguate}\n permissionMode={permissionMode}\n onModeChange={setPermissionMode}\n providerId={providerId}\n onProviderChange={!sessionId ? setProviderId : undefined}\n />\n </div>\n\n {/* Bug report popup is now global — see BugReportPopup in app.tsx */}\n </div>\n );\n}\n"],"file":"chat-tab-DBYwH_Aa.js"}