@yancyyu/openhermit 1.5.8

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 (980) hide show
  1. package/LICENSE +611 -0
  2. package/README.md +220 -0
  3. package/bin/hermit.mjs +364 -0
  4. package/bin/kill-dev.js +20 -0
  5. package/dist-renderer/assets/01-BApSFlV4.png +0 -0
  6. package/dist-renderer/assets/02-CRQGs29u.png +0 -0
  7. package/dist-renderer/assets/03-BFCM2jnD.png +0 -0
  8. package/dist-renderer/assets/04-B2FThbKO.png +0 -0
  9. package/dist-renderer/assets/05-D9p0Znkd.png +0 -0
  10. package/dist-renderer/assets/06-DZAfbDlP.png +0 -0
  11. package/dist-renderer/assets/07-B_PXWGCc.png +0 -0
  12. package/dist-renderer/assets/08-DGRMZ6sl.png +0 -0
  13. package/dist-renderer/assets/09-SGCQvc7U.png +0 -0
  14. package/dist-renderer/assets/10-Cve81Q3W.png +0 -0
  15. package/dist-renderer/assets/11-DGglolDW.png +0 -0
  16. package/dist-renderer/assets/12-C3lnu79c.png +0 -0
  17. package/dist-renderer/assets/13-M59meqdw.png +0 -0
  18. package/dist-renderer/assets/ProjectEditorOverlay-BNoDw9T1.js +57 -0
  19. package/dist-renderer/assets/TeamGraphOverlay-CfGRKQIu.js +1 -0
  20. package/dist-renderer/assets/_basePickBy-Ct8Hm5_h.js +1 -0
  21. package/dist-renderer/assets/_baseUniq-BofrAFBx.js +1 -0
  22. package/dist-renderer/assets/apl-B4CMkyY2.js +1 -0
  23. package/dist-renderer/assets/arc-AbJgatzR.js +1 -0
  24. package/dist-renderer/assets/architectureDiagram-VXUJARFQ-gpniCJVk.js +36 -0
  25. package/dist-renderer/assets/asciiarmor-Df11BRmG.js +1 -0
  26. package/dist-renderer/assets/asn1-EdZsLKOL.js +1 -0
  27. package/dist-renderer/assets/asterisk-B-8jnY81.js +1 -0
  28. package/dist-renderer/assets/blockDiagram-VD42YOAC-aBbbmONC.js +122 -0
  29. package/dist-renderer/assets/brainfuck-C4LP7Hcl.js +1 -0
  30. package/dist-renderer/assets/c4Diagram-YG6GDRKO-DJio1IsU.js +10 -0
  31. package/dist-renderer/assets/channel-CZ8sd5Xf.js +1 -0
  32. package/dist-renderer/assets/chunk-4BX2VUAB-D1_HKao2.js +1 -0
  33. package/dist-renderer/assets/chunk-55IACEB6-NAmVxF4k.js +1 -0
  34. package/dist-renderer/assets/chunk-B4BG7PRW-Ce829laz.js +165 -0
  35. package/dist-renderer/assets/chunk-DI55MBZ5-Ct2Le12y.js +220 -0
  36. package/dist-renderer/assets/chunk-FMBD7UC4-Cie3DzKk.js +15 -0
  37. package/dist-renderer/assets/chunk-QN33PNHL-4f5Yb50e.js +1 -0
  38. package/dist-renderer/assets/chunk-QZHKN3VN-D9ranl9c.js +1 -0
  39. package/dist-renderer/assets/chunk-TZMSLE5B-bdGZWlEy.js +1 -0
  40. package/dist-renderer/assets/classDiagram-2ON5EDUG-CMcfSKj5.js +1 -0
  41. package/dist-renderer/assets/classDiagram-v2-WZHVMYZB-CMcfSKj5.js +1 -0
  42. package/dist-renderer/assets/clike-B9uivgTg.js +1 -0
  43. package/dist-renderer/assets/clojure-BMjYHr_A.js +1 -0
  44. package/dist-renderer/assets/clone-CMuwA8RV.js +1 -0
  45. package/dist-renderer/assets/cmake-BQqOBYOt.js +1 -0
  46. package/dist-renderer/assets/cobol-CWcv1MsR.js +1 -0
  47. package/dist-renderer/assets/coffeescript-S37ZYGWr.js +1 -0
  48. package/dist-renderer/assets/commonlisp-DBKNyK5s.js +1 -0
  49. package/dist-renderer/assets/cose-bilkent-S5V4N54A-C6tvfcVi.js +1 -0
  50. package/dist-renderer/assets/crystal-SjHAIU92.js +1 -0
  51. package/dist-renderer/assets/css-BnMrqG3P.js +1 -0
  52. package/dist-renderer/assets/cypher-C_CwsFkJ.js +1 -0
  53. package/dist-renderer/assets/cytoscape.esm-DsxaTqgk.js +331 -0
  54. package/dist-renderer/assets/d-pRatUO7H.js +1 -0
  55. package/dist-renderer/assets/dagre-6UL2VRFP-B-4qcZam.js +4 -0
  56. package/dist-renderer/assets/defaultLocale-DX6XiGOO.js +1 -0
  57. package/dist-renderer/assets/diagram-PSM6KHXK-CwT3TLjx.js +24 -0
  58. package/dist-renderer/assets/diagram-QEK2KX5R-BWH6-ZFd.js +43 -0
  59. package/dist-renderer/assets/diagram-S2PKOQOG-DfpPnfi1.js +24 -0
  60. package/dist-renderer/assets/diff-DbItnlRl.js +1 -0
  61. package/dist-renderer/assets/dockerfile-BKs6k2Af.js +1 -0
  62. package/dist-renderer/assets/dtd-DF_7sFjM.js +1 -0
  63. package/dist-renderer/assets/dylan-DwRh75JA.js +1 -0
  64. package/dist-renderer/assets/ebnf-CDyGwa7X.js +1 -0
  65. package/dist-renderer/assets/ecl-Cabwm37j.js +1 -0
  66. package/dist-renderer/assets/eiffel-CnydiIhH.js +1 -0
  67. package/dist-renderer/assets/elm-vLlmbW-K.js +1 -0
  68. package/dist-renderer/assets/erDiagram-Q2GNP2WA-BFbEFR4x.js +60 -0
  69. package/dist-renderer/assets/erlang-BNw1qcRV.js +1 -0
  70. package/dist-renderer/assets/factor-kuTfRLto.js +1 -0
  71. package/dist-renderer/assets/favicon-B8xY-GVk.png +0 -0
  72. package/dist-renderer/assets/fcl-Kvtd6kyn.js +1 -0
  73. package/dist-renderer/assets/flowDiagram-NV44I4VS-Dg3cf5hW.js +162 -0
  74. package/dist-renderer/assets/forth-Ffai-XNe.js +1 -0
  75. package/dist-renderer/assets/fortran-DYz_wnZ1.js +1 -0
  76. package/dist-renderer/assets/ganttDiagram-JELNMOA3-B21y55W5.js +267 -0
  77. package/dist-renderer/assets/gas-Bneqetm1.js +1 -0
  78. package/dist-renderer/assets/gherkin-heZmZLOM.js +1 -0
  79. package/dist-renderer/assets/gitGraphDiagram-V2S2FVAM-BDV3BJzn.js +65 -0
  80. package/dist-renderer/assets/graph-BfaZ4hZt.js +1 -0
  81. package/dist-renderer/assets/groovy-D9Dt4D0W.js +1 -0
  82. package/dist-renderer/assets/haskell-BWDZoCOh.js +1 -0
  83. package/dist-renderer/assets/haxe-H-WmDvRZ.js +1 -0
  84. package/dist-renderer/assets/http-DBlCnlav.js +1 -0
  85. package/dist-renderer/assets/idl-BEugSyMb.js +1 -0
  86. package/dist-renderer/assets/index-BMXHMpkG.js +1 -0
  87. package/dist-renderer/assets/index-CCqtDawH.js +1 -0
  88. package/dist-renderer/assets/index-CVMSpK8C.js +1 -0
  89. package/dist-renderer/assets/index-CZltVMDP.js +1844 -0
  90. package/dist-renderer/assets/index-CaG9mf8s.css +1 -0
  91. package/dist-renderer/assets/index-Ct0-y9TF.js +1 -0
  92. package/dist-renderer/assets/index-pMg_LlsS.js +1 -0
  93. package/dist-renderer/assets/infoDiagram-HS3SLOUP-DvMlS0CL.js +2 -0
  94. package/dist-renderer/assets/init-Gi6I4Gst.js +1 -0
  95. package/dist-renderer/assets/javascript-qCveANmP.js +1 -0
  96. package/dist-renderer/assets/journeyDiagram-XKPGCS4Q-DIyMluRv.js +139 -0
  97. package/dist-renderer/assets/julia-DuME0IfC.js +1 -0
  98. package/dist-renderer/assets/kanban-definition-3W4ZIXB7-CVOx8f-7.js +89 -0
  99. package/dist-renderer/assets/katex-DGN8GczM.js +261 -0
  100. package/dist-renderer/assets/layout-BPKIXUf4.js +1 -0
  101. package/dist-renderer/assets/linear-CScZGLr2.js +1 -0
  102. package/dist-renderer/assets/livescript-BwQOo05w.js +1 -0
  103. package/dist-renderer/assets/lua-BgMRiT3U.js +1 -0
  104. package/dist-renderer/assets/mathematica-DTrFuWx2.js +1 -0
  105. package/dist-renderer/assets/mbox-CNhZ1qSd.js +1 -0
  106. package/dist-renderer/assets/mindmap-definition-VGOIOE7T-CmDQ7Wo6.js +68 -0
  107. package/dist-renderer/assets/mirc-CjQqDB4T.js +1 -0
  108. package/dist-renderer/assets/mllike-CXdrOF99.js +1 -0
  109. package/dist-renderer/assets/modelica-Dc1JOy9r.js +1 -0
  110. package/dist-renderer/assets/mscgen-BA5vi2Kp.js +1 -0
  111. package/dist-renderer/assets/mumps-BT43cFF4.js +1 -0
  112. package/dist-renderer/assets/nginx-DdIZxoE0.js +1 -0
  113. package/dist-renderer/assets/nsis-LdVXkNf5.js +1 -0
  114. package/dist-renderer/assets/ntriples-BfvgReVJ.js +1 -0
  115. package/dist-renderer/assets/octave-Ck1zUtKM.js +1 -0
  116. package/dist-renderer/assets/ordinal-Cboi1Yqb.js +1 -0
  117. package/dist-renderer/assets/oz-BzwKVEFT.js +1 -0
  118. package/dist-renderer/assets/pascal--L3eBynH.js +1 -0
  119. package/dist-renderer/assets/perl-CdXCOZ3F.js +1 -0
  120. package/dist-renderer/assets/pieDiagram-ADFJNKIX-DbVClin-.js +30 -0
  121. package/dist-renderer/assets/pig-CevX1Tat.js +1 -0
  122. package/dist-renderer/assets/powershell-CFHJl5sT.js +1 -0
  123. package/dist-renderer/assets/properties-C78fOPTZ.js +1 -0
  124. package/dist-renderer/assets/protobuf-ChK-085T.js +1 -0
  125. package/dist-renderer/assets/pug-DukmZTjD.js +1 -0
  126. package/dist-renderer/assets/puppet-DMA9R1ak.js +1 -0
  127. package/dist-renderer/assets/python-BuPzkPfP.js +1 -0
  128. package/dist-renderer/assets/q-pXgVlZs6.js +1 -0
  129. package/dist-renderer/assets/quadrantDiagram-AYHSOK5B-CAB0MYcW.js +7 -0
  130. package/dist-renderer/assets/r-DUYO_cvP.js +1 -0
  131. package/dist-renderer/assets/requirementDiagram-UZGBJVZJ-w2Lfpg3T.js +64 -0
  132. package/dist-renderer/assets/rpm-CTu-6PCP.js +1 -0
  133. package/dist-renderer/assets/ruby-B2Rjki9n.js +1 -0
  134. package/dist-renderer/assets/sankeyDiagram-TZEHDZUN-kvG1QoKY.js +10 -0
  135. package/dist-renderer/assets/sas-B4kiWyti.js +1 -0
  136. package/dist-renderer/assets/scheme-C41bIUwD.js +1 -0
  137. package/dist-renderer/assets/sequenceDiagram-WL72ISMW-DCVBQ23J.js +145 -0
  138. package/dist-renderer/assets/shell-CjFT_Tl9.js +1 -0
  139. package/dist-renderer/assets/sieve-C3Gn_uJK.js +1 -0
  140. package/dist-renderer/assets/simple-mode-GW_nhZxv.js +1 -0
  141. package/dist-renderer/assets/smalltalk-CnHTOXQT.js +1 -0
  142. package/dist-renderer/assets/solr-DehyRSwq.js +1 -0
  143. package/dist-renderer/assets/sparql-DkYu6x3z.js +1 -0
  144. package/dist-renderer/assets/splashScene-C8lWNnm4.js +1 -0
  145. package/dist-renderer/assets/spreadsheet-BCZA_wO0.js +1 -0
  146. package/dist-renderer/assets/sql-D0XecflT.js +1 -0
  147. package/dist-renderer/assets/stateDiagram-FKZM4ZOC-ItZ0JBvq.js +1 -0
  148. package/dist-renderer/assets/stateDiagram-v2-4FDKWEC3-Hpmw4dMm.js +1 -0
  149. package/dist-renderer/assets/stex-C3f8Ysf7.js +1 -0
  150. package/dist-renderer/assets/stylus-B533Al4x.js +1 -0
  151. package/dist-renderer/assets/swift-BzpIVaGY.js +1 -0
  152. package/dist-renderer/assets/tcl-DVfN8rqt.js +1 -0
  153. package/dist-renderer/assets/textile-CnDTJFAw.js +1 -0
  154. package/dist-renderer/assets/tiddlywiki-DO-Gjzrf.js +1 -0
  155. package/dist-renderer/assets/tiki-DGYXhP31.js +1 -0
  156. package/dist-renderer/assets/timeline-definition-IT6M3QCI-BzSFaAjV.js +61 -0
  157. package/dist-renderer/assets/toml-Bm5Em-hy.js +1 -0
  158. package/dist-renderer/assets/treemap-GDKQZRPO-fSz4hQn0.js +162 -0
  159. package/dist-renderer/assets/troff-wAsdV37c.js +1 -0
  160. package/dist-renderer/assets/ttcn-CfJYG6tj.js +1 -0
  161. package/dist-renderer/assets/ttcn-cfg-B9xdYoR4.js +1 -0
  162. package/dist-renderer/assets/turtle-B1tBg_DP.js +1 -0
  163. package/dist-renderer/assets/vb-CmGdzxic.js +1 -0
  164. package/dist-renderer/assets/vbscript-BuJXcnF6.js +1 -0
  165. package/dist-renderer/assets/velocity-D8B20fx6.js +1 -0
  166. package/dist-renderer/assets/verilog-C6RDOZhf.js +1 -0
  167. package/dist-renderer/assets/vhdl-lSbBsy5d.js +1 -0
  168. package/dist-renderer/assets/webidl-ZXfAyPTL.js +1 -0
  169. package/dist-renderer/assets/xquery-CQfU5ijd.js +1 -0
  170. package/dist-renderer/assets/xychartDiagram-PRI3JC2R-CT1kaGlv.js +7 -0
  171. package/dist-renderer/assets/yacas-BJ4BC0dw.js +1 -0
  172. package/dist-renderer/assets/z80-Hz9HOZM7.js +1 -0
  173. package/dist-renderer/index.html +1274 -0
  174. package/package.json +181 -0
  175. package/src/features/README.md +24 -0
  176. package/src/features/agent-graph/README.md +21 -0
  177. package/src/features/agent-graph/STABLE_SLOT_LAYOUT_PLAN.md +2846 -0
  178. package/src/features/agent-graph/core/domain/buildInlineActivityEntries.ts +416 -0
  179. package/src/features/agent-graph/core/domain/collapseOverflowStacks.ts +126 -0
  180. package/src/features/agent-graph/core/domain/graphOwnerIdentity.ts +55 -0
  181. package/src/features/agent-graph/core/domain/taskGraphSemantics.ts +48 -0
  182. package/src/features/agent-graph/renderer/adapters/TeamGraphAdapter.ts +1400 -0
  183. package/src/features/agent-graph/renderer/hooks/useGraphActivityContext.ts +34 -0
  184. package/src/features/agent-graph/renderer/hooks/useGraphCreateTaskDialog.tsx +126 -0
  185. package/src/features/agent-graph/renderer/hooks/useGraphMemberPopoverContext.ts +34 -0
  186. package/src/features/agent-graph/renderer/hooks/useGraphSidebarVisibility.ts +52 -0
  187. package/src/features/agent-graph/renderer/hooks/useTeamGraphAdapter.ts +173 -0
  188. package/src/features/agent-graph/renderer/hooks/useTeamGraphSurfaceActions.ts +98 -0
  189. package/src/features/agent-graph/renderer/index.ts +14 -0
  190. package/src/features/agent-graph/renderer/ui/GraphActivityCard.tsx +96 -0
  191. package/src/features/agent-graph/renderer/ui/GraphActivityHud.tsx +498 -0
  192. package/src/features/agent-graph/renderer/ui/GraphBlockingEdgePopover.tsx +207 -0
  193. package/src/features/agent-graph/renderer/ui/GraphNodePopover.tsx +573 -0
  194. package/src/features/agent-graph/renderer/ui/GraphProvisioningHud.tsx +113 -0
  195. package/src/features/agent-graph/renderer/ui/GraphTaskCard.tsx +149 -0
  196. package/src/features/agent-graph/renderer/ui/GraphTransientHandoffHud.tsx +176 -0
  197. package/src/features/agent-graph/renderer/ui/TeamGraphOverlay.tsx +224 -0
  198. package/src/features/agent-graph/renderer/ui/TeamGraphTab.tsx +257 -0
  199. package/src/features/agent-graph/renderer/ui/buildTransientHandoffMessage.ts +70 -0
  200. package/src/features/recent-projects/contracts/api.ts +5 -0
  201. package/src/features/recent-projects/contracts/channels.ts +2 -0
  202. package/src/features/recent-projects/contracts/dto.ts +24 -0
  203. package/src/features/recent-projects/contracts/index.ts +4 -0
  204. package/src/features/recent-projects/contracts/normalize.ts +32 -0
  205. package/src/features/recent-projects/core/application/models/ListDashboardRecentProjectsResponse.ts +6 -0
  206. package/src/features/recent-projects/core/application/ports/ClockPort.ts +3 -0
  207. package/src/features/recent-projects/core/application/ports/ListDashboardRecentProjectsOutputPort.ts +5 -0
  208. package/src/features/recent-projects/core/application/ports/LoggerPort.ts +5 -0
  209. package/src/features/recent-projects/core/application/ports/RecentProjectsCachePort.ts +5 -0
  210. package/src/features/recent-projects/core/application/ports/RecentProjectsSourcePort.ts +14 -0
  211. package/src/features/recent-projects/core/application/use-cases/ListDashboardRecentProjectsUseCase.ts +191 -0
  212. package/src/features/recent-projects/core/domain/models/ProviderId.ts +1 -0
  213. package/src/features/recent-projects/core/domain/models/RecentProjectAggregate.ts +14 -0
  214. package/src/features/recent-projects/core/domain/models/RecentProjectCandidate.ts +14 -0
  215. package/src/features/recent-projects/core/domain/models/RecentProjectOpenTarget.ts +3 -0
  216. package/src/features/recent-projects/core/domain/policies/mergeRecentProjectCandidates.ts +88 -0
  217. package/src/features/recent-projects/main/adapters/input/http/registerRecentProjectsHttp.ts +30 -0
  218. package/src/features/recent-projects/main/adapters/output/presenters/DashboardRecentProjectsPresenter.ts +27 -0
  219. package/src/features/recent-projects/main/adapters/output/sources/ClaudeRecentProjectsSourceAdapter.ts +91 -0
  220. package/src/features/recent-projects/main/adapters/output/sources/CodexRecentProjectsSourceAdapter.ts +326 -0
  221. package/src/features/recent-projects/main/composition/createRecentProjectsFeature.ts +43 -0
  222. package/src/features/recent-projects/main/index.ts +3 -0
  223. package/src/features/recent-projects/main/infrastructure/cache/InMemoryRecentProjectsCache.ts +34 -0
  224. package/src/features/recent-projects/main/infrastructure/codex/CodexAppServerClient.ts +116 -0
  225. package/src/features/recent-projects/main/infrastructure/identity/RecentProjectIdentityResolver.ts +20 -0
  226. package/src/features/recent-projects/main/infrastructure/identity/normalizeIdentityPath.ts +10 -0
  227. package/src/features/recent-projects/renderer/adapters/RecentProjectsSectionAdapter.ts +132 -0
  228. package/src/features/recent-projects/renderer/hooks/useOpenRecentProject.ts +143 -0
  229. package/src/features/recent-projects/renderer/hooks/useRecentProjectsSection.ts +289 -0
  230. package/src/features/recent-projects/renderer/index.ts +2 -0
  231. package/src/features/recent-projects/renderer/ui/RecentProjectCard.tsx +221 -0
  232. package/src/features/recent-projects/renderer/ui/RecentProjectsSection.tsx +167 -0
  233. package/src/features/recent-projects/renderer/utils/activeProjectTeams.ts +48 -0
  234. package/src/features/recent-projects/renderer/utils/navigation.ts +65 -0
  235. package/src/features/recent-projects/renderer/utils/projectDecorations.ts +11 -0
  236. package/src/features/recent-projects/renderer/utils/recentProjectOpenHistory.ts +268 -0
  237. package/src/features/recent-projects/renderer/utils/recentProjectsClientCache.ts +78 -0
  238. package/src/main/constants/messageTags.ts +46 -0
  239. package/src/main/constants/worktreePatterns.ts +47 -0
  240. package/src/main/server.ts +3705 -0
  241. package/src/main/services/UpdateService.ts +166 -0
  242. package/src/main/services/ccConnect/CcConnectBridge.ts +313 -0
  243. package/src/main/services/ccConnect/CcConnectClient.ts +397 -0
  244. package/src/main/services/ccConnect/MessageBridge.ts +162 -0
  245. package/src/main/services/ccConnect/ProjectMappingStore.ts +148 -0
  246. package/src/main/services/ccConnect/index.ts +8 -0
  247. package/src/main/services/teams-mvp/TeamProvisioningService.ts +275 -0
  248. package/src/main/services/teams-mvp/TeamWorkspaceService.ts +404 -0
  249. package/src/main/services/teams-mvp/index.ts +26 -0
  250. package/src/main/types/chunks.ts +506 -0
  251. package/src/main/types/domain.ts +342 -0
  252. package/src/main/types/index.ts +24 -0
  253. package/src/main/types/jsonl.ts +355 -0
  254. package/src/main/types/messages.ts +395 -0
  255. package/src/renderer/App.tsx +287 -0
  256. package/src/renderer/api/httpClient.ts +2207 -0
  257. package/src/renderer/api/index.ts +19 -0
  258. package/src/renderer/api/providers.ts +77 -0
  259. package/src/renderer/assets/participant-avatars/01.png +0 -0
  260. package/src/renderer/assets/participant-avatars/02.png +0 -0
  261. package/src/renderer/assets/participant-avatars/03.png +0 -0
  262. package/src/renderer/assets/participant-avatars/04.png +0 -0
  263. package/src/renderer/assets/participant-avatars/05.png +0 -0
  264. package/src/renderer/assets/participant-avatars/06.png +0 -0
  265. package/src/renderer/assets/participant-avatars/07.png +0 -0
  266. package/src/renderer/assets/participant-avatars/08.png +0 -0
  267. package/src/renderer/assets/participant-avatars/09.png +0 -0
  268. package/src/renderer/assets/participant-avatars/10.png +0 -0
  269. package/src/renderer/assets/participant-avatars/11.png +0 -0
  270. package/src/renderer/assets/participant-avatars/12.png +0 -0
  271. package/src/renderer/assets/participant-avatars/13.png +0 -0
  272. package/src/renderer/components/chat/AIChatGroup.tsx +519 -0
  273. package/src/renderer/components/chat/ChatHistory.tsx +1115 -0
  274. package/src/renderer/components/chat/ChatHistoryEmptyState.tsx +15 -0
  275. package/src/renderer/components/chat/ChatHistoryItem.tsx +144 -0
  276. package/src/renderer/components/chat/ChatHistoryLoadingState.tsx +45 -0
  277. package/src/renderer/components/chat/CompactBoundary.tsx +169 -0
  278. package/src/renderer/components/chat/ContextBadge.tsx +582 -0
  279. package/src/renderer/components/chat/DisplayItemList.tsx +431 -0
  280. package/src/renderer/components/chat/LastOutputDisplay.tsx +259 -0
  281. package/src/renderer/components/chat/SessionContextPanel/DirectoryTree/DirectoryTreeNode.tsx +125 -0
  282. package/src/renderer/components/chat/SessionContextPanel/DirectoryTree/buildDirectoryTree.ts +47 -0
  283. package/src/renderer/components/chat/SessionContextPanel/DirectoryTree/types.ts +12 -0
  284. package/src/renderer/components/chat/SessionContextPanel/components/ClaudeMdFilesSection.tsx +90 -0
  285. package/src/renderer/components/chat/SessionContextPanel/components/ClaudeMdSection.tsx +86 -0
  286. package/src/renderer/components/chat/SessionContextPanel/components/CollapsibleSection.tsx +77 -0
  287. package/src/renderer/components/chat/SessionContextPanel/components/FlatInjectionList.tsx +248 -0
  288. package/src/renderer/components/chat/SessionContextPanel/components/MentionedFilesSection.tsx +50 -0
  289. package/src/renderer/components/chat/SessionContextPanel/components/RankedInjectionList.tsx +284 -0
  290. package/src/renderer/components/chat/SessionContextPanel/components/SessionContextHeader.tsx +290 -0
  291. package/src/renderer/components/chat/SessionContextPanel/components/SessionContextHelpTooltip.tsx +165 -0
  292. package/src/renderer/components/chat/SessionContextPanel/components/TaskCoordinationSection.tsx +47 -0
  293. package/src/renderer/components/chat/SessionContextPanel/components/ThinkingTextSection.tsx +47 -0
  294. package/src/renderer/components/chat/SessionContextPanel/components/ToolOutputsSection.tsx +47 -0
  295. package/src/renderer/components/chat/SessionContextPanel/components/UserMessagesSection.tsx +47 -0
  296. package/src/renderer/components/chat/SessionContextPanel/index.tsx +314 -0
  297. package/src/renderer/components/chat/SessionContextPanel/items/ClaudeMdItem.tsx +76 -0
  298. package/src/renderer/components/chat/SessionContextPanel/items/MentionedFileItem.tsx +91 -0
  299. package/src/renderer/components/chat/SessionContextPanel/items/TaskCoordinationItem.tsx +116 -0
  300. package/src/renderer/components/chat/SessionContextPanel/items/ThinkingTextItem.tsx +96 -0
  301. package/src/renderer/components/chat/SessionContextPanel/items/ToolBreakdownItem.tsx +38 -0
  302. package/src/renderer/components/chat/SessionContextPanel/items/ToolOutputItem.tsx +113 -0
  303. package/src/renderer/components/chat/SessionContextPanel/items/UserMessageItem.tsx +69 -0
  304. package/src/renderer/components/chat/SessionContextPanel/types.ts +96 -0
  305. package/src/renderer/components/chat/SessionContextPanel/utils/formatting.ts +6 -0
  306. package/src/renderer/components/chat/SessionContextPanel/utils/pathParsing.ts +23 -0
  307. package/src/renderer/components/chat/SystemChatGroup.tsx +60 -0
  308. package/src/renderer/components/chat/UserChatGroup.tsx +668 -0
  309. package/src/renderer/components/chat/items/BaseItem.tsx +213 -0
  310. package/src/renderer/components/chat/items/ExecutionTrace.tsx +279 -0
  311. package/src/renderer/components/chat/items/LinkedToolItem.tsx +235 -0
  312. package/src/renderer/components/chat/items/MetricsPill.tsx +215 -0
  313. package/src/renderer/components/chat/items/SlashItem.tsx +81 -0
  314. package/src/renderer/components/chat/items/SubagentItem.tsx +592 -0
  315. package/src/renderer/components/chat/items/TeammateMessageItem.tsx +261 -0
  316. package/src/renderer/components/chat/items/TextItem.tsx +82 -0
  317. package/src/renderer/components/chat/items/ThinkingItem.tsx +82 -0
  318. package/src/renderer/components/chat/items/baseItemHelpers.ts +42 -0
  319. package/src/renderer/components/chat/items/linkedTool/CollapsibleOutputSection.tsx +57 -0
  320. package/src/renderer/components/chat/items/linkedTool/DefaultToolViewer.tsx +56 -0
  321. package/src/renderer/components/chat/items/linkedTool/EditToolViewer.tsx +74 -0
  322. package/src/renderer/components/chat/items/linkedTool/ReadToolViewer.tsx +102 -0
  323. package/src/renderer/components/chat/items/linkedTool/SkillToolViewer.tsx +67 -0
  324. package/src/renderer/components/chat/items/linkedTool/ToolErrorDisplay.tsx +43 -0
  325. package/src/renderer/components/chat/items/linkedTool/WriteToolViewer.tsx +66 -0
  326. package/src/renderer/components/chat/items/linkedTool/index.ts +13 -0
  327. package/src/renderer/components/chat/items/linkedTool/renderHelpers.tsx +259 -0
  328. package/src/renderer/components/chat/markdownComponents.tsx +257 -0
  329. package/src/renderer/components/chat/markdownCopyUtils.ts +18 -0
  330. package/src/renderer/components/chat/searchHighlightUtils.ts +166 -0
  331. package/src/renderer/components/chat/viewers/CodeBlockViewer.tsx +244 -0
  332. package/src/renderer/components/chat/viewers/DiffViewer.tsx +459 -0
  333. package/src/renderer/components/chat/viewers/FileLink.tsx +182 -0
  334. package/src/renderer/components/chat/viewers/MarkdownViewer.tsx +1093 -0
  335. package/src/renderer/components/chat/viewers/MermaidDiagram.tsx +116 -0
  336. package/src/renderer/components/chat/viewers/index.ts +3 -0
  337. package/src/renderer/components/chat/viewers/syntaxHighlighter.ts +583 -0
  338. package/src/renderer/components/common/AppLogo.tsx +61 -0
  339. package/src/renderer/components/common/CliInstallWarningBanner.tsx +57 -0
  340. package/src/renderer/components/common/ConfirmDialog.tsx +176 -0
  341. package/src/renderer/components/common/ConnectionStatusBadge.tsx +56 -0
  342. package/src/renderer/components/common/ContextSwitchOverlay.tsx +38 -0
  343. package/src/renderer/components/common/CopyButton.tsx +85 -0
  344. package/src/renderer/components/common/CopyablePath.tsx +68 -0
  345. package/src/renderer/components/common/ErrorBoundary.tsx +211 -0
  346. package/src/renderer/components/common/ExportDropdown.tsx +142 -0
  347. package/src/renderer/components/common/FileTree.tsx +182 -0
  348. package/src/renderer/components/common/GlobalProviderStatusHeader.tsx +316 -0
  349. package/src/renderer/components/common/OngoingIndicator.tsx +67 -0
  350. package/src/renderer/components/common/ProviderBrandLogo.tsx +206 -0
  351. package/src/renderer/components/common/RepositoryDropdown.tsx +230 -0
  352. package/src/renderer/components/common/TokenUsageDisplay.tsx +581 -0
  353. package/src/renderer/components/common/WarningBanner.tsx +25 -0
  354. package/src/renderer/components/common/WorkspaceIndicator.tsx +183 -0
  355. package/src/renderer/components/common/WorktreeBadge.tsx +123 -0
  356. package/src/renderer/components/dashboard/CliStatusBanner.tsx +1845 -0
  357. package/src/renderer/components/dashboard/DashboardView.tsx +274 -0
  358. package/src/renderer/components/extensions/ExtensionStoreView.tsx +591 -0
  359. package/src/renderer/components/extensions/ExtensionsSubTabTrigger.tsx +52 -0
  360. package/src/renderer/components/extensions/apikeys/ApiKeyCard.tsx +143 -0
  361. package/src/renderer/components/extensions/apikeys/ApiKeyFormDialog.tsx +282 -0
  362. package/src/renderer/components/extensions/apikeys/ApiKeysPanel.tsx +280 -0
  363. package/src/renderer/components/extensions/common/InstallButton.tsx +186 -0
  364. package/src/renderer/components/extensions/common/InstallCountBadge.tsx +21 -0
  365. package/src/renderer/components/extensions/common/SearchInput.tsx +70 -0
  366. package/src/renderer/components/extensions/common/SourceBadge.tsx +31 -0
  367. package/src/renderer/components/extensions/mcp/CustomMcpServerDialog.tsx +560 -0
  368. package/src/renderer/components/extensions/mcp/McpServerCard.tsx +314 -0
  369. package/src/renderer/components/extensions/mcp/McpServerDetailDialog.tsx +669 -0
  370. package/src/renderer/components/extensions/mcp/McpServersPanel.tsx +543 -0
  371. package/src/renderer/components/extensions/plugins/CapabilityChips.tsx +70 -0
  372. package/src/renderer/components/extensions/plugins/CategoryChips.tsx +67 -0
  373. package/src/renderer/components/extensions/plugins/PluginCard.tsx +146 -0
  374. package/src/renderer/components/extensions/plugins/PluginDetailDialog.tsx +270 -0
  375. package/src/renderer/components/extensions/plugins/PluginsPanel.tsx +436 -0
  376. package/src/renderer/components/extensions/skills/SkillCodeEditor.tsx +117 -0
  377. package/src/renderer/components/extensions/skills/SkillDetailDialog.tsx +372 -0
  378. package/src/renderer/components/extensions/skills/SkillEditorDialog.tsx +856 -0
  379. package/src/renderer/components/extensions/skills/SkillImportDialog.tsx +343 -0
  380. package/src/renderer/components/extensions/skills/SkillReviewDialog.tsx +166 -0
  381. package/src/renderer/components/extensions/skills/SkillsPanel.tsx +784 -0
  382. package/src/renderer/components/extensions/skills/skillDraftUtils.ts +234 -0
  383. package/src/renderer/components/extensions/skills/skillFolderNameUtils.ts +19 -0
  384. package/src/renderer/components/extensions/skills/skillProjectUtils.ts +13 -0
  385. package/src/renderer/components/extensions/skills/skillValidationUtils.ts +31 -0
  386. package/src/renderer/components/layout/MiddlePanel.tsx +18 -0
  387. package/src/renderer/components/layout/MoreMenu.tsx +243 -0
  388. package/src/renderer/components/layout/PaneContainer.tsx +27 -0
  389. package/src/renderer/components/layout/PaneContent.tsx +84 -0
  390. package/src/renderer/components/layout/PaneResizeHandle.tsx +85 -0
  391. package/src/renderer/components/layout/PaneSplitDropZone.tsx +54 -0
  392. package/src/renderer/components/layout/PaneView.tsx +75 -0
  393. package/src/renderer/components/layout/SessionTabContent.tsx +102 -0
  394. package/src/renderer/components/layout/Sidebar.tsx +205 -0
  395. package/src/renderer/components/layout/SortableTab.tsx +261 -0
  396. package/src/renderer/components/layout/TabBar.tsx +354 -0
  397. package/src/renderer/components/layout/TabBarActions.tsx +176 -0
  398. package/src/renderer/components/layout/TabBarRow.tsx +99 -0
  399. package/src/renderer/components/layout/TabContextMenu.tsx +171 -0
  400. package/src/renderer/components/layout/TabbedLayout.tsx +186 -0
  401. package/src/renderer/components/layout/TeamTabSectionNav.tsx +146 -0
  402. package/src/renderer/components/notifications/NotificationRow.tsx +228 -0
  403. package/src/renderer/components/notifications/NotificationsView.tsx +371 -0
  404. package/src/renderer/components/report/AssessmentBadge.tsx +78 -0
  405. package/src/renderer/components/report/ReportSection.tsx +58 -0
  406. package/src/renderer/components/report/SessionReportTab.tsx +102 -0
  407. package/src/renderer/components/report/sections/CostSection.tsx +259 -0
  408. package/src/renderer/components/report/sections/ErrorSection.tsx +100 -0
  409. package/src/renderer/components/report/sections/FrictionSection.tsx +91 -0
  410. package/src/renderer/components/report/sections/GitSection.tsx +72 -0
  411. package/src/renderer/components/report/sections/InsightsSection.tsx +207 -0
  412. package/src/renderer/components/report/sections/KeyTakeawaysSection.tsx +55 -0
  413. package/src/renderer/components/report/sections/OverviewSection.tsx +64 -0
  414. package/src/renderer/components/report/sections/QualitySection.tsx +151 -0
  415. package/src/renderer/components/report/sections/SubagentSection.tsx +88 -0
  416. package/src/renderer/components/report/sections/TimelineSection.tsx +111 -0
  417. package/src/renderer/components/report/sections/TokenSection.tsx +116 -0
  418. package/src/renderer/components/report/sections/ToolSection.tsx +77 -0
  419. package/src/renderer/components/runtime/ProviderModelBadges.tsx +142 -0
  420. package/src/renderer/components/runtime/ProviderRuntimeBackendSelector.tsx +327 -0
  421. package/src/renderer/components/runtime/ProviderRuntimeSettingsDialog.tsx +288 -0
  422. package/src/renderer/components/runtime/providerConnectionUi.ts +408 -0
  423. package/src/renderer/components/schedules/SchedulesView.tsx +529 -0
  424. package/src/renderer/components/search/CommandPalette.tsx +610 -0
  425. package/src/renderer/components/search/SearchBar.tsx +171 -0
  426. package/src/renderer/components/settings/NotificationTriggerSettings/components/AddTriggerForm.tsx +233 -0
  427. package/src/renderer/components/settings/NotificationTriggerSettings/components/ColorPaletteSelector.tsx +144 -0
  428. package/src/renderer/components/settings/NotificationTriggerSettings/components/DynamicConfigSection.tsx +189 -0
  429. package/src/renderer/components/settings/NotificationTriggerSettings/components/GeneralInfoSection.tsx +68 -0
  430. package/src/renderer/components/settings/NotificationTriggerSettings/components/IgnorePatternsSection.tsx +73 -0
  431. package/src/renderer/components/settings/NotificationTriggerSettings/components/ModeSelector.tsx +45 -0
  432. package/src/renderer/components/settings/NotificationTriggerSettings/components/RepositoryScopeSection.tsx +63 -0
  433. package/src/renderer/components/settings/NotificationTriggerSettings/components/SectionHeader.tsx +15 -0
  434. package/src/renderer/components/settings/NotificationTriggerSettings/components/TriggerCard.tsx +150 -0
  435. package/src/renderer/components/settings/NotificationTriggerSettings/components/TriggerCardHeader.tsx +125 -0
  436. package/src/renderer/components/settings/NotificationTriggerSettings/components/TriggerConfiguration.tsx +342 -0
  437. package/src/renderer/components/settings/NotificationTriggerSettings/components/TriggerPreview.tsx +108 -0
  438. package/src/renderer/components/settings/NotificationTriggerSettings/hooks/useAddTriggerFormHandlers.ts +218 -0
  439. package/src/renderer/components/settings/NotificationTriggerSettings/hooks/useAddTriggerFormState.ts +135 -0
  440. package/src/renderer/components/settings/NotificationTriggerSettings/hooks/useRepositoryLookup.ts +47 -0
  441. package/src/renderer/components/settings/NotificationTriggerSettings/hooks/useTriggerCardState.ts +281 -0
  442. package/src/renderer/components/settings/NotificationTriggerSettings/hooks/useTriggerForm.ts +185 -0
  443. package/src/renderer/components/settings/NotificationTriggerSettings/index.tsx +87 -0
  444. package/src/renderer/components/settings/NotificationTriggerSettings/types.ts +39 -0
  445. package/src/renderer/components/settings/NotificationTriggerSettings/utils/constants.ts +50 -0
  446. package/src/renderer/components/settings/NotificationTriggerSettings/utils/trigger.ts +113 -0
  447. package/src/renderer/components/settings/SettingsTabs.tsx +110 -0
  448. package/src/renderer/components/settings/SettingsView.tsx +153 -0
  449. package/src/renderer/components/settings/components/SettingRow.tsx +44 -0
  450. package/src/renderer/components/settings/components/SettingsSectionHeader.tsx +24 -0
  451. package/src/renderer/components/settings/components/SettingsSelect.tsx +100 -0
  452. package/src/renderer/components/settings/components/SettingsToggle.tsx +45 -0
  453. package/src/renderer/components/settings/components/index.ts +8 -0
  454. package/src/renderer/components/settings/hooks/index.ts +6 -0
  455. package/src/renderer/components/settings/hooks/useSettingsConfig.ts +270 -0
  456. package/src/renderer/components/settings/hooks/useSettingsHandlers.ts +468 -0
  457. package/src/renderer/components/settings/sections/AdvancedSection.tsx +234 -0
  458. package/src/renderer/components/settings/sections/CliStatusSection.tsx +930 -0
  459. package/src/renderer/components/settings/sections/ConfigEditorDialog.tsx +391 -0
  460. package/src/renderer/components/settings/sections/GeneralSection.tsx +665 -0
  461. package/src/renderer/components/settings/sections/HarnessSection.tsx +133 -0
  462. package/src/renderer/components/settings/sections/PlatformsSection.tsx +517 -0
  463. package/src/renderer/components/settings/sections/index.ts +8 -0
  464. package/src/renderer/components/sidebar/DateGroupedSessions.tsx +1115 -0
  465. package/src/renderer/components/sidebar/GlobalTaskList.tsx +853 -0
  466. package/src/renderer/components/sidebar/SessionContextMenu.tsx +182 -0
  467. package/src/renderer/components/sidebar/SessionFiltersPopover.tsx +115 -0
  468. package/src/renderer/components/sidebar/SessionItem.tsx +393 -0
  469. package/src/renderer/components/sidebar/SidebarSessions.tsx +542 -0
  470. package/src/renderer/components/sidebar/SidebarTaskItem.tsx +286 -0
  471. package/src/renderer/components/sidebar/TaskContextMenu.tsx +86 -0
  472. package/src/renderer/components/sidebar/TaskFiltersPopover.tsx +203 -0
  473. package/src/renderer/components/sidebar/WorkspaceBrowser.tsx +370 -0
  474. package/src/renderer/components/sidebar/dateGroupedSessionsSelection.ts +33 -0
  475. package/src/renderer/components/sidebar/projectGroupPagination.ts +89 -0
  476. package/src/renderer/components/sidebar/taskFiltersState.ts +82 -0
  477. package/src/renderer/components/splash/splashScene.ts +979 -0
  478. package/src/renderer/components/team/CcSessionsSection.tsx +202 -0
  479. package/src/renderer/components/team/ClaudeLogsDialog.tsx +71 -0
  480. package/src/renderer/components/team/ClaudeLogsFilterPopover.tsx +213 -0
  481. package/src/renderer/components/team/ClaudeLogsPanel.tsx +170 -0
  482. package/src/renderer/components/team/ClaudeLogsSection.tsx +154 -0
  483. package/src/renderer/components/team/CliLogsRichView.tsx +640 -0
  484. package/src/renderer/components/team/CollapsibleTeamSection.tsx +177 -0
  485. package/src/renderer/components/team/HarnessCards.ts +38 -0
  486. package/src/renderer/components/team/MemberBadge.tsx +117 -0
  487. package/src/renderer/components/team/ProcessesSection.tsx +193 -0
  488. package/src/renderer/components/team/ProvisioningProgressBlock.tsx +389 -0
  489. package/src/renderer/components/team/RoleSelect.tsx +171 -0
  490. package/src/renderer/components/team/StepProgressBar.tsx +165 -0
  491. package/src/renderer/components/team/TaskTooltip.tsx +197 -0
  492. package/src/renderer/components/team/TeamDetailView.tsx +3002 -0
  493. package/src/renderer/components/team/TeamEmptyState.tsx +102 -0
  494. package/src/renderer/components/team/TeamListFilterPopover.tsx +183 -0
  495. package/src/renderer/components/team/TeamListView.tsx +1336 -0
  496. package/src/renderer/components/team/TeamProvisioningBanner.tsx +16 -0
  497. package/src/renderer/components/team/TeamProvisioningPanel.tsx +115 -0
  498. package/src/renderer/components/team/TeamSessionsSection.tsx +267 -0
  499. package/src/renderer/components/team/ToolApprovalDiffPreview.tsx +206 -0
  500. package/src/renderer/components/team/ToolApprovalSheet.tsx +675 -0
  501. package/src/renderer/components/team/UnreadCommentsBadge.tsx +37 -0
  502. package/src/renderer/components/team/activity/ActiveTasksBlock.tsx +191 -0
  503. package/src/renderer/components/team/activity/ActivityItem.tsx +1649 -0
  504. package/src/renderer/components/team/activity/ActivityTimeline.tsx +959 -0
  505. package/src/renderer/components/team/activity/AnimatedHeightReveal.tsx +117 -0
  506. package/src/renderer/components/team/activity/LeadThoughtsGroup.tsx +1152 -0
  507. package/src/renderer/components/team/activity/MessageExpandDialog.tsx +213 -0
  508. package/src/renderer/components/team/activity/PendingRepliesBlock.tsx +275 -0
  509. package/src/renderer/components/team/activity/ReplyQuoteBlock.tsx +79 -0
  510. package/src/renderer/components/team/activity/ThoughtBodyContent.tsx +150 -0
  511. package/src/renderer/components/team/activity/activityMarkdown.ts +36 -0
  512. package/src/renderer/components/team/activity/activityMessageContext.ts +68 -0
  513. package/src/renderer/components/team/activity/collapseState.ts +66 -0
  514. package/src/renderer/components/team/activity/useNewItemKeys.ts +70 -0
  515. package/src/renderer/components/team/attachments/AttachmentDisplay.tsx +132 -0
  516. package/src/renderer/components/team/attachments/AttachmentPreviewItem.tsx +62 -0
  517. package/src/renderer/components/team/attachments/AttachmentPreviewList.tsx +193 -0
  518. package/src/renderer/components/team/attachments/AttachmentThumbnail.tsx +42 -0
  519. package/src/renderer/components/team/attachments/DropZoneOverlay.tsx +54 -0
  520. package/src/renderer/components/team/attachments/ImageLightbox.tsx +132 -0
  521. package/src/renderer/components/team/attachments/SourceMessageAttachments.tsx +70 -0
  522. package/src/renderer/components/team/dialogs/AddMemberDialog.tsx +222 -0
  523. package/src/renderer/components/team/dialogs/AdvancedCliSection.tsx +347 -0
  524. package/src/renderer/components/team/dialogs/AnthropicFastModeSelector.tsx +120 -0
  525. package/src/renderer/components/team/dialogs/CreateTaskDialog.tsx +489 -0
  526. package/src/renderer/components/team/dialogs/CreateTeamDialog.tsx +484 -0
  527. package/src/renderer/components/team/dialogs/EditTeamDialog.tsx +448 -0
  528. package/src/renderer/components/team/dialogs/EffortLevelSelector.tsx +69 -0
  529. package/src/renderer/components/team/dialogs/GlobalTaskDetailDialog.tsx +165 -0
  530. package/src/renderer/components/team/dialogs/LaunchTeamDialog.tsx +2859 -0
  531. package/src/renderer/components/team/dialogs/LimitContextCheckbox.tsx +57 -0
  532. package/src/renderer/components/team/dialogs/MembersJsonEditor.tsx +123 -0
  533. package/src/renderer/components/team/dialogs/OptionalSettingsSection.tsx +124 -0
  534. package/src/renderer/components/team/dialogs/PlatformManualForm.tsx +145 -0
  535. package/src/renderer/components/team/dialogs/PlatformSetupQR.tsx +289 -0
  536. package/src/renderer/components/team/dialogs/ProjectPathSelector.tsx +330 -0
  537. package/src/renderer/components/team/dialogs/ProvisioningProviderStatusList.tsx +744 -0
  538. package/src/renderer/components/team/dialogs/ReviewDialog.tsx +130 -0
  539. package/src/renderer/components/team/dialogs/SendMessageDialog.tsx +530 -0
  540. package/src/renderer/components/team/dialogs/SkipPermissionsCheckbox.tsx +62 -0
  541. package/src/renderer/components/team/dialogs/StatusHistoryTimeline.tsx +229 -0
  542. package/src/renderer/components/team/dialogs/TaskAttachments.tsx +394 -0
  543. package/src/renderer/components/team/dialogs/TaskCommentAwaitingReply.tsx +57 -0
  544. package/src/renderer/components/team/dialogs/TaskCommentInput.tsx +420 -0
  545. package/src/renderer/components/team/dialogs/TaskCommentsSection.tsx +620 -0
  546. package/src/renderer/components/team/dialogs/TaskDetailDialog.tsx +1480 -0
  547. package/src/renderer/components/team/dialogs/TeamModelSelector.tsx +560 -0
  548. package/src/renderer/components/team/dialogs/TeammateRuntimeCompatibilityNotice.tsx +60 -0
  549. package/src/renderer/components/team/dialogs/ToolApprovalSettingsPanel.tsx +173 -0
  550. package/src/renderer/components/team/dialogs/editTeamRuntimeChanges.ts +179 -0
  551. package/src/renderer/components/team/dialogs/globalTaskDetailDialogLoading.ts +34 -0
  552. package/src/renderer/components/team/dialogs/launchDialogPrefill.ts +125 -0
  553. package/src/renderer/components/team/dialogs/memberModelScope.ts +77 -0
  554. package/src/renderer/components/team/dialogs/platformMeta.ts +118 -0
  555. package/src/renderer/components/team/dialogs/projectPathOptions.ts +50 -0
  556. package/src/renderer/components/team/dialogs/providerPrepareDiagnostics.ts +222 -0
  557. package/src/renderer/components/team/dialogs/providerPrepareRequestSignature.ts +122 -0
  558. package/src/renderer/components/team/dialogs/provisioningMemberScope.ts +10 -0
  559. package/src/renderer/components/team/dialogs/provisioningModelIssues.ts +124 -0
  560. package/src/renderer/components/team/dialogs/teamNameSets.ts +67 -0
  561. package/src/renderer/components/team/dialogs/teamRelaunchFlow.ts +30 -0
  562. package/src/renderer/components/team/dialogs/teammateLaunchMode.ts +49 -0
  563. package/src/renderer/components/team/dialogs/teammateRuntimeCompatibility.tsx +101 -0
  564. package/src/renderer/components/team/editor/CodeMirrorEditor.tsx +506 -0
  565. package/src/renderer/components/team/editor/EditorBinaryPlaceholder.tsx +43 -0
  566. package/src/renderer/components/team/editor/EditorBinaryState.tsx +29 -0
  567. package/src/renderer/components/team/editor/EditorBreadcrumb.tsx +84 -0
  568. package/src/renderer/components/team/editor/EditorContextMenu.tsx +213 -0
  569. package/src/renderer/components/team/editor/EditorEmptyState.tsx +35 -0
  570. package/src/renderer/components/team/editor/EditorErrorBoundary.tsx +63 -0
  571. package/src/renderer/components/team/editor/EditorErrorState.tsx +41 -0
  572. package/src/renderer/components/team/editor/EditorFileTree.tsx +903 -0
  573. package/src/renderer/components/team/editor/EditorImagePreview.tsx +138 -0
  574. package/src/renderer/components/team/editor/EditorSearchPanel.tsx +508 -0
  575. package/src/renderer/components/team/editor/EditorSelectionMenu.tsx +112 -0
  576. package/src/renderer/components/team/editor/EditorShortcutsHelp.tsx +125 -0
  577. package/src/renderer/components/team/editor/EditorStatusBar.tsx +72 -0
  578. package/src/renderer/components/team/editor/EditorTabBar.tsx +265 -0
  579. package/src/renderer/components/team/editor/EditorTabContextMenu.tsx +88 -0
  580. package/src/renderer/components/team/editor/EditorToolbar.tsx +163 -0
  581. package/src/renderer/components/team/editor/FileIcon.tsx +66 -0
  582. package/src/renderer/components/team/editor/GitStatusBadge.tsx +47 -0
  583. package/src/renderer/components/team/editor/GoToLineDialog.tsx +186 -0
  584. package/src/renderer/components/team/editor/MarkdownPreviewPane.tsx +57 -0
  585. package/src/renderer/components/team/editor/MarkdownSplitView.tsx +127 -0
  586. package/src/renderer/components/team/editor/NewFileDialog.tsx +131 -0
  587. package/src/renderer/components/team/editor/ProjectEditorOverlay.tsx +924 -0
  588. package/src/renderer/components/team/editor/QuickOpenDialog.tsx +163 -0
  589. package/src/renderer/components/team/editor/SearchInFilesPanel.tsx +358 -0
  590. package/src/renderer/components/team/editor/fileIcons.ts +222 -0
  591. package/src/renderer/components/team/kanban/KanbanBoard.tsx +664 -0
  592. package/src/renderer/components/team/kanban/KanbanColumn.tsx +61 -0
  593. package/src/renderer/components/team/kanban/KanbanFilterPopover.tsx +210 -0
  594. package/src/renderer/components/team/kanban/KanbanGridLayout.tsx +460 -0
  595. package/src/renderer/components/team/kanban/KanbanSearchInput.tsx +284 -0
  596. package/src/renderer/components/team/kanban/KanbanSortPopover.tsx +140 -0
  597. package/src/renderer/components/team/kanban/KanbanTaskCard.test.tsx +199 -0
  598. package/src/renderer/components/team/kanban/KanbanTaskCard.tsx +446 -0
  599. package/src/renderer/components/team/kanban/TrashDialog.tsx +112 -0
  600. package/src/renderer/components/team/members/CurrentTaskIndicator.tsx +56 -0
  601. package/src/renderer/components/team/members/LeadModelRow.test.tsx +133 -0
  602. package/src/renderer/components/team/members/LeadModelRow.tsx +183 -0
  603. package/src/renderer/components/team/members/MemberCard.tsx +665 -0
  604. package/src/renderer/components/team/members/MemberDetailDialog.tsx +309 -0
  605. package/src/renderer/components/team/members/MemberDetailHeader.tsx +183 -0
  606. package/src/renderer/components/team/members/MemberDetailStats.tsx +80 -0
  607. package/src/renderer/components/team/members/MemberDraftRow.test.tsx +184 -0
  608. package/src/renderer/components/team/members/MemberDraftRow.tsx +515 -0
  609. package/src/renderer/components/team/members/MemberExecutionLog.tsx +224 -0
  610. package/src/renderer/components/team/members/MemberHoverCard.tsx +292 -0
  611. package/src/renderer/components/team/members/MemberLaunchDiagnosticsButton.tsx +60 -0
  612. package/src/renderer/components/team/members/MemberList.tsx +405 -0
  613. package/src/renderer/components/team/members/MemberLogsTab.tsx +958 -0
  614. package/src/renderer/components/team/members/MemberMessagesTab.tsx +251 -0
  615. package/src/renderer/components/team/members/MemberPresenceDot.tsx +28 -0
  616. package/src/renderer/components/team/members/MemberRoleEditor.tsx +84 -0
  617. package/src/renderer/components/team/members/MemberStatsTab.tsx +299 -0
  618. package/src/renderer/components/team/members/MemberTasksTab.tsx +87 -0
  619. package/src/renderer/components/team/members/MemberWorkspaceTab.tsx +141 -0
  620. package/src/renderer/components/team/members/MembersEditorSection.tsx +495 -0
  621. package/src/renderer/components/team/members/SubagentRecentMessagesPreview.tsx +125 -0
  622. package/src/renderer/components/team/members/TeamRosterEditorSection.tsx +153 -0
  623. package/src/renderer/components/team/members/memberActivityEntries.ts +42 -0
  624. package/src/renderer/components/team/members/memberDetailTypes.ts +3 -0
  625. package/src/renderer/components/team/members/memberNameSets.ts +65 -0
  626. package/src/renderer/components/team/members/membersEditorTypes.ts +21 -0
  627. package/src/renderer/components/team/members/membersEditorUtils.ts +267 -0
  628. package/src/renderer/components/team/messages/MessageComposer.tsx +939 -0
  629. package/src/renderer/components/team/messages/MessagesFilterPopover.tsx +228 -0
  630. package/src/renderer/components/team/messages/MessagesPanel.tsx +1508 -0
  631. package/src/renderer/components/team/messages/OpenCodeDeliveryWarning.tsx +151 -0
  632. package/src/renderer/components/team/messages/StatusBlock.tsx +126 -0
  633. package/src/renderer/components/team/provisioningSteps.ts +363 -0
  634. package/src/renderer/components/team/review/ChangeReviewDialog.tsx +133 -0
  635. package/src/renderer/components/team/schedule/CcCronScheduleDialog.tsx +218 -0
  636. package/src/renderer/components/team/schedule/CronScheduleInput.tsx +254 -0
  637. package/src/renderer/components/team/schedule/ScheduleEmptyState.tsx +15 -0
  638. package/src/renderer/components/team/schedule/ScheduleRunLogDialog.tsx +277 -0
  639. package/src/renderer/components/team/schedule/ScheduleRunRow.tsx +106 -0
  640. package/src/renderer/components/team/schedule/ScheduleSection.tsx +281 -0
  641. package/src/renderer/components/team/schedule/ScheduleStatusBadge.tsx +55 -0
  642. package/src/renderer/components/team/sidebar/TeamSidebarHost.tsx +76 -0
  643. package/src/renderer/components/team/sidebar/TeamSidebarPortalManager.ts +173 -0
  644. package/src/renderer/components/team/sidebar/TeamSidebarPortalSource.tsx +66 -0
  645. package/src/renderer/components/team/sidebar/TeamSidebarRail.tsx +68 -0
  646. package/src/renderer/components/team/sidebar/teamSidebarUiState.ts +136 -0
  647. package/src/renderer/components/team/taskLogs/ExactTaskLogCard.tsx +132 -0
  648. package/src/renderer/components/team/taskLogs/ExactTaskLogsSection.tsx +258 -0
  649. package/src/renderer/components/team/taskLogs/ExecutionSessionsSection.tsx +48 -0
  650. package/src/renderer/components/team/taskLogs/TaskActivityLinkedToolCard.tsx +31 -0
  651. package/src/renderer/components/team/taskLogs/TaskActivitySection.tsx +462 -0
  652. package/src/renderer/components/team/taskLogs/TaskLogStreamSection.tsx +375 -0
  653. package/src/renderer/components/team/taskLogs/TaskLogsPanel.tsx +294 -0
  654. package/src/renderer/components/team/taskLogs/featureGates.ts +22 -0
  655. package/src/renderer/components/team/tasks/TaskList.tsx +111 -0
  656. package/src/renderer/components/team/tasks/TaskRow.tsx +65 -0
  657. package/src/renderer/components/team/teamProjectSelection.ts +156 -0
  658. package/src/renderer/components/team/teamSessionFetchGuards.ts +26 -0
  659. package/src/renderer/components/team/useClaudeLogsController.ts +668 -0
  660. package/src/renderer/components/team/useTeamProvisioningPresentation.ts +54 -0
  661. package/src/renderer/components/terminal/TerminalLogPanel.tsx +37 -0
  662. package/src/renderer/components/ui/ChipInteractionLayer.tsx +255 -0
  663. package/src/renderer/components/ui/CodeChipBadge.tsx +37 -0
  664. package/src/renderer/components/ui/ExpandableContent.tsx +110 -0
  665. package/src/renderer/components/ui/MemberSelect.tsx +209 -0
  666. package/src/renderer/components/ui/MentionInteractionLayer.tsx +121 -0
  667. package/src/renderer/components/ui/MentionSuggestionList.tsx +274 -0
  668. package/src/renderer/components/ui/MentionableTextarea.tsx +1426 -0
  669. package/src/renderer/components/ui/SlashCommandInteractionLayer.tsx +88 -0
  670. package/src/renderer/components/ui/TaskReferenceInteractionLayer.tsx +101 -0
  671. package/src/renderer/components/ui/UrlInteractionLayer.tsx +102 -0
  672. package/src/renderer/components/ui/alert-dialog.tsx +127 -0
  673. package/src/renderer/components/ui/auto-resize-textarea.tsx +93 -0
  674. package/src/renderer/components/ui/badge.tsx +37 -0
  675. package/src/renderer/components/ui/button.tsx +54 -0
  676. package/src/renderer/components/ui/checkbox.tsx +29 -0
  677. package/src/renderer/components/ui/combobox.tsx +168 -0
  678. package/src/renderer/components/ui/context-menu.tsx +124 -0
  679. package/src/renderer/components/ui/dialog.tsx +114 -0
  680. package/src/renderer/components/ui/hover-card.tsx +30 -0
  681. package/src/renderer/components/ui/input.tsx +22 -0
  682. package/src/renderer/components/ui/label.tsx +21 -0
  683. package/src/renderer/components/ui/popover.tsx +31 -0
  684. package/src/renderer/components/ui/select.tsx +150 -0
  685. package/src/renderer/components/ui/tabs.tsx +52 -0
  686. package/src/renderer/components/ui/textarea.tsx +21 -0
  687. package/src/renderer/components/ui/tiptap/TiptapBubbleMenu.tsx +75 -0
  688. package/src/renderer/components/ui/tiptap/TiptapEditor.tsx +73 -0
  689. package/src/renderer/components/ui/tiptap/TiptapToolbar.tsx +269 -0
  690. package/src/renderer/components/ui/tiptap/index.ts +3 -0
  691. package/src/renderer/components/ui/tiptap/presets.ts +46 -0
  692. package/src/renderer/components/ui/tiptap/tiptapStyles.css +235 -0
  693. package/src/renderer/components/ui/tiptap/types.ts +32 -0
  694. package/src/renderer/components/ui/tiptap/useTiptapEditor.ts +94 -0
  695. package/src/renderer/components/ui/tooltip.tsx +32 -0
  696. package/src/renderer/constants/cssVariables.ts +226 -0
  697. package/src/renderer/constants/layout.ts +6 -0
  698. package/src/renderer/constants/teamColors.ts +397 -0
  699. package/src/renderer/constants/teamRoles.ts +41 -0
  700. package/src/renderer/contexts/TabUIContext.tsx +51 -0
  701. package/src/renderer/contexts/useTabUIContext.ts +18 -0
  702. package/src/renderer/favicon.png +0 -0
  703. package/src/renderer/features/CLAUDE.md +19 -0
  704. package/src/renderer/hooks/navigation/utils.ts +263 -0
  705. package/src/renderer/hooks/useAttachments.ts +312 -0
  706. package/src/renderer/hooks/useAutoScrollBottom.ts +285 -0
  707. package/src/renderer/hooks/useBranchSync.ts +105 -0
  708. package/src/renderer/hooks/useChipDraftPersistence.ts +172 -0
  709. package/src/renderer/hooks/useCliInstaller.ts +106 -0
  710. package/src/renderer/hooks/useCollapsedGroups.ts +71 -0
  711. package/src/renderer/hooks/useComposerDraft.ts +504 -0
  712. package/src/renderer/hooks/useContinuousScrollNav.ts +50 -0
  713. package/src/renderer/hooks/useCreateTeamDraft.ts +280 -0
  714. package/src/renderer/hooks/useDraftPersistence.ts +140 -0
  715. package/src/renderer/hooks/useEditorKeyboardShortcuts.ts +257 -0
  716. package/src/renderer/hooks/useEffectiveCliProviderStatus.ts +66 -0
  717. package/src/renderer/hooks/useExtensionsTabState.ts +206 -0
  718. package/src/renderer/hooks/useFileListCacheWarmer.ts +38 -0
  719. package/src/renderer/hooks/useFileSuggestions.ts +255 -0
  720. package/src/renderer/hooks/useKeyboardShortcuts.ts +363 -0
  721. package/src/renderer/hooks/useLazyFileContent.ts +150 -0
  722. package/src/renderer/hooks/useMarkCommentsRead.ts +27 -0
  723. package/src/renderer/hooks/useMarkdownScrollSync.ts +158 -0
  724. package/src/renderer/hooks/useMemberStats.ts +45 -0
  725. package/src/renderer/hooks/useMentionDetection.ts +375 -0
  726. package/src/renderer/hooks/usePersistedGridLayout.ts +109 -0
  727. package/src/renderer/hooks/useResizableColumns.ts +140 -0
  728. package/src/renderer/hooks/useResizablePanel.ts +144 -0
  729. package/src/renderer/hooks/useStableTeamMentionMeta.ts +68 -0
  730. package/src/renderer/hooks/useSyncedAnimationStyle.ts +29 -0
  731. package/src/renderer/hooks/useTabNavigationController.ts +524 -0
  732. package/src/renderer/hooks/useTabUI.ts +252 -0
  733. package/src/renderer/hooks/useTaskLocalState.ts +163 -0
  734. package/src/renderer/hooks/useTaskSuggestions.ts +131 -0
  735. package/src/renderer/hooks/useTeamMessagesExpanded.ts +34 -0
  736. package/src/renderer/hooks/useTeamMessagesRead.ts +52 -0
  737. package/src/renderer/hooks/useTeamSuggestions.ts +78 -0
  738. package/src/renderer/hooks/useTheme.ts +138 -0
  739. package/src/renderer/hooks/useToolApprovalDiff.ts +212 -0
  740. package/src/renderer/hooks/useUnreadCommentCount.ts +14 -0
  741. package/src/renderer/hooks/useViewedFiles.ts +74 -0
  742. package/src/renderer/hooks/useViewportCommentRead.ts +147 -0
  743. package/src/renderer/hooks/useViewportObserver.ts +138 -0
  744. package/src/renderer/hooks/useVisibleAIGroup.ts +122 -0
  745. package/src/renderer/hooks/useVisibleFileSection.ts +114 -0
  746. package/src/renderer/hooks/useZoomFactor.ts +36 -0
  747. package/src/renderer/index.css +1560 -0
  748. package/src/renderer/index.html +1293 -0
  749. package/src/renderer/lib/utils.ts +6 -0
  750. package/src/renderer/main.tsx +30 -0
  751. package/src/renderer/sentry.ts +104 -0
  752. package/src/renderer/services/__tests__/createTeamPreferences.test.ts +67 -0
  753. package/src/renderer/services/commentReadStorage.ts +349 -0
  754. package/src/renderer/services/composerDraftStorage.ts +271 -0
  755. package/src/renderer/services/contextStorage.ts +201 -0
  756. package/src/renderer/services/createTeamDraftStorage.ts +151 -0
  757. package/src/renderer/services/createTeamPreferences.ts +361 -0
  758. package/src/renderer/services/dashboardCliStatusBannerPreference.ts +20 -0
  759. package/src/renderer/services/draftStorage.ts +128 -0
  760. package/src/renderer/services/layout-system/BrowserGridLayoutRepository.ts +111 -0
  761. package/src/renderer/services/layout-system/GridLayoutRepository.ts +8 -0
  762. package/src/renderer/services/layout-system/gridLayoutSchema.ts +137 -0
  763. package/src/renderer/services/layout-system/gridLayoutTypes.ts +17 -0
  764. package/src/renderer/store/index.ts +1556 -0
  765. package/src/renderer/store/slices/changeReviewSlice.ts +1694 -0
  766. package/src/renderer/store/slices/cliInstallerSlice.ts +689 -0
  767. package/src/renderer/store/slices/configSlice.ts +111 -0
  768. package/src/renderer/store/slices/connectionSlice.ts +221 -0
  769. package/src/renderer/store/slices/contextSlice.ts +394 -0
  770. package/src/renderer/store/slices/conversationSlice.ts +510 -0
  771. package/src/renderer/store/slices/editorSlice.ts +1455 -0
  772. package/src/renderer/store/slices/extensionsSlice.ts +1415 -0
  773. package/src/renderer/store/slices/notificationSlice.ts +277 -0
  774. package/src/renderer/store/slices/paneSlice.ts +357 -0
  775. package/src/renderer/store/slices/projectSlice.ts +70 -0
  776. package/src/renderer/store/slices/repositorySlice.ts +165 -0
  777. package/src/renderer/store/slices/scheduleSlice.ts +246 -0
  778. package/src/renderer/store/slices/sessionDetailSlice.ts +755 -0
  779. package/src/renderer/store/slices/sessionSlice.ts +539 -0
  780. package/src/renderer/store/slices/subagentSlice.ts +145 -0
  781. package/src/renderer/store/slices/tabSlice.ts +842 -0
  782. package/src/renderer/store/slices/tabUISlice.ts +319 -0
  783. package/src/renderer/store/slices/teamSlice.ts +5080 -0
  784. package/src/renderer/store/slices/uiSlice.ts +45 -0
  785. package/src/renderer/store/types.ts +103 -0
  786. package/src/renderer/store/utils/paneHelpers.ts +134 -0
  787. package/src/renderer/store/utils/pathResolution.ts +121 -0
  788. package/src/renderer/store/utils/stateResetHelpers.ts +72 -0
  789. package/src/renderer/types/api.ts +8 -0
  790. package/src/renderer/types/claudeMd.ts +74 -0
  791. package/src/renderer/types/contextInjection.ts +309 -0
  792. package/src/renderer/types/data.ts +144 -0
  793. package/src/renderer/types/groups.ts +406 -0
  794. package/src/renderer/types/inlineChip.ts +110 -0
  795. package/src/renderer/types/mention.ts +38 -0
  796. package/src/renderer/types/notifications.ts +18 -0
  797. package/src/renderer/types/panes.ts +35 -0
  798. package/src/renderer/types/sessionReport.ts +386 -0
  799. package/src/renderer/types/tabs.ts +249 -0
  800. package/src/renderer/types/teamMessagesPanelMode.ts +1 -0
  801. package/src/renderer/utils/__tests__/teamEffortOptions.test.ts +217 -0
  802. package/src/renderer/utils/__tests__/teamModelAvailability.codexCatalog.test.ts +383 -0
  803. package/src/renderer/utils/agentMessageFormatting.ts +139 -0
  804. package/src/renderer/utils/aiGroupEnhancer.ts +78 -0
  805. package/src/renderer/utils/aiGroupHelpers.ts +208 -0
  806. package/src/renderer/utils/attachmentUtils.ts +60 -0
  807. package/src/renderer/utils/bootstrapPromptSanitizer.ts +225 -0
  808. package/src/renderer/utils/bugReportUtils.ts +157 -0
  809. package/src/renderer/utils/buildSelectionAction.ts +116 -0
  810. package/src/renderer/utils/chipUtils.ts +372 -0
  811. package/src/renderer/utils/claudeCodeOnlyProviders.ts +126 -0
  812. package/src/renderer/utils/claudeMdTracker.ts +644 -0
  813. package/src/renderer/utils/codemirrorLanguages.ts +141 -0
  814. package/src/renderer/utils/codemirrorSelectionInfo.ts +41 -0
  815. package/src/renderer/utils/codemirrorTheme.ts +138 -0
  816. package/src/renderer/utils/contextMath.ts +55 -0
  817. package/src/renderer/utils/contextTracker.ts +1100 -0
  818. package/src/renderer/utils/crossTeamPendingReplies.ts +92 -0
  819. package/src/renderer/utils/dateGrouping.ts +91 -0
  820. package/src/renderer/utils/diffViewedStorage.ts +120 -0
  821. package/src/renderer/utils/displayItemBuilder.ts +587 -0
  822. package/src/renderer/utils/displaySummary.ts +74 -0
  823. package/src/renderer/utils/editorBridge.ts +90 -0
  824. package/src/renderer/utils/fileTreeBuilder.ts +110 -0
  825. package/src/renderer/utils/formatAgentRole.ts +24 -0
  826. package/src/renderer/utils/formatters.ts +61 -0
  827. package/src/renderer/utils/groupTransformer.ts +744 -0
  828. package/src/renderer/utils/idleNotificationSemantics.ts +76 -0
  829. package/src/renderer/utils/keyboardUtils.ts +92 -0
  830. package/src/renderer/utils/lastOutputDetector.ts +150 -0
  831. package/src/renderer/utils/markdownPlugins.ts +60 -0
  832. package/src/renderer/utils/memberAvatarCatalog.ts +38 -0
  833. package/src/renderer/utils/memberHelpers.ts +858 -0
  834. package/src/renderer/utils/memberLaunchDiagnostics.ts +216 -0
  835. package/src/renderer/utils/memberRuntimeSummary.ts +122 -0
  836. package/src/renderer/utils/memberSpawnStatusPolling.ts +29 -0
  837. package/src/renderer/utils/mentionLinkify.ts +90 -0
  838. package/src/renderer/utils/mentionSuggestions.ts +34 -0
  839. package/src/renderer/utils/mergeTeamMessages.ts +27 -0
  840. package/src/renderer/utils/messageRenderEquality.ts +158 -0
  841. package/src/renderer/utils/modelExtractor.ts +90 -0
  842. package/src/renderer/utils/multimodelProviderVisibility.ts +32 -0
  843. package/src/renderer/utils/openCodeModelRecommendations.ts +1326 -0
  844. package/src/renderer/utils/openCodeRuntimeDeliveryDiagnostics.ts +79 -0
  845. package/src/renderer/utils/pathDisplay.ts +149 -0
  846. package/src/renderer/utils/pathNormalize.ts +61 -0
  847. package/src/renderer/utils/pathUtils.ts +47 -0
  848. package/src/renderer/utils/platformKeys.ts +24 -0
  849. package/src/renderer/utils/previewRegistry.ts +45 -0
  850. package/src/renderer/utils/projectColor.ts +54 -0
  851. package/src/renderer/utils/projectLookup.ts +64 -0
  852. package/src/renderer/utils/providerBackendIdentity.ts +43 -0
  853. package/src/renderer/utils/providerSlashCommands.ts +122 -0
  854. package/src/renderer/utils/quickOpenCache.ts +40 -0
  855. package/src/renderer/utils/refreshCliStatus.ts +17 -0
  856. package/src/renderer/utils/reportAssessments.ts +555 -0
  857. package/src/renderer/utils/reviewDecisionScope.ts +81 -0
  858. package/src/renderer/utils/reviewKey.ts +132 -0
  859. package/src/renderer/utils/runtimeDisplayName.ts +26 -0
  860. package/src/renderer/utils/scheduleFormatters.ts +45 -0
  861. package/src/renderer/utils/sessionAnalyzer.ts +1346 -0
  862. package/src/renderer/utils/sessionExporter.ts +427 -0
  863. package/src/renderer/utils/sessionTitleParser.ts +69 -0
  864. package/src/renderer/utils/skillCommandSuggestions.ts +77 -0
  865. package/src/renderer/utils/slashCommandExtractor.ts +154 -0
  866. package/src/renderer/utils/streamJsonParser.ts +393 -0
  867. package/src/renderer/utils/stringUtils.ts +50 -0
  868. package/src/renderer/utils/syntaxHighlighter.ts +158 -0
  869. package/src/renderer/utils/tabLabelDisambiguation.ts +99 -0
  870. package/src/renderer/utils/taskChangePresence.ts +19 -0
  871. package/src/renderer/utils/taskChangeRequest.ts +122 -0
  872. package/src/renderer/utils/taskCommentPendingReply.ts +74 -0
  873. package/src/renderer/utils/taskGrouping.ts +151 -0
  874. package/src/renderer/utils/taskReferenceUtils.ts +331 -0
  875. package/src/renderer/utils/teamEffortOptions.ts +150 -0
  876. package/src/renderer/utils/teamLaunchSummaryCopy.ts +17 -0
  877. package/src/renderer/utils/teamMessageExpandStorage.ts +39 -0
  878. package/src/renderer/utils/teamMessageFiltering.ts +95 -0
  879. package/src/renderer/utils/teamMessageKey.ts +14 -0
  880. package/src/renderer/utils/teamMessageReadStorage.ts +49 -0
  881. package/src/renderer/utils/teamModelAvailability.ts +465 -0
  882. package/src/renderer/utils/teamModelCatalog.ts +502 -0
  883. package/src/renderer/utils/teamModelContext.ts +34 -0
  884. package/src/renderer/utils/teamProvisioningPresentation.ts +772 -0
  885. package/src/renderer/utils/teamRuntimeSummary.ts +53 -0
  886. package/src/renderer/utils/toolLinkingEngine.ts +117 -0
  887. package/src/renderer/utils/toolRendering/index.ts +14 -0
  888. package/src/renderer/utils/toolRendering/toolContentChecks.ts +58 -0
  889. package/src/renderer/utils/toolRendering/toolSummaryHelpers.ts +276 -0
  890. package/src/renderer/utils/toolRendering/toolTokens.ts +57 -0
  891. package/src/renderer/utils/unwrapIpc.ts +31 -0
  892. package/src/renderer/utils/urlMatchUtils.ts +45 -0
  893. package/src/renderer/vite-env.d.ts +21 -0
  894. package/src/shared/constants/agentBlocks.ts +129 -0
  895. package/src/shared/constants/attachments.ts +175 -0
  896. package/src/shared/constants/cache.ts +12 -0
  897. package/src/shared/constants/cli.ts +15 -0
  898. package/src/shared/constants/crossTeam.ts +126 -0
  899. package/src/shared/constants/index.ts +15 -0
  900. package/src/shared/constants/kanban.ts +9 -0
  901. package/src/shared/constants/memberColors.ts +140 -0
  902. package/src/shared/constants/opencodeTaskLogAttribution.ts +1 -0
  903. package/src/shared/constants/teamLimits.ts +2 -0
  904. package/src/shared/constants/trafficLights.ts +60 -0
  905. package/src/shared/constants/triggerColors.ts +126 -0
  906. package/src/shared/constants/window.ts +12 -0
  907. package/src/shared/types/api.ts +1062 -0
  908. package/src/shared/types/ccConnect.ts +399 -0
  909. package/src/shared/types/cliInstaller.ts +335 -0
  910. package/src/shared/types/editor.ts +279 -0
  911. package/src/shared/types/extensions/api.ts +90 -0
  912. package/src/shared/types/extensions/apikey.ts +40 -0
  913. package/src/shared/types/extensions/common.ts +16 -0
  914. package/src/shared/types/extensions/index.ts +67 -0
  915. package/src/shared/types/extensions/mcp.ts +132 -0
  916. package/src/shared/types/extensions/plugin.ts +85 -0
  917. package/src/shared/types/extensions/skill.ts +173 -0
  918. package/src/shared/types/index.ts +48 -0
  919. package/src/shared/types/ipc.ts +5 -0
  920. package/src/shared/types/notifications.ts +407 -0
  921. package/src/shared/types/providers.ts +116 -0
  922. package/src/shared/types/review.ts +319 -0
  923. package/src/shared/types/schedule.ts +124 -0
  924. package/src/shared/types/team.ts +1726 -0
  925. package/src/shared/types/terminal.ts +49 -0
  926. package/src/shared/types/visualization.ts +60 -0
  927. package/src/shared/utils/__tests__/contextMetrics.test.ts +260 -0
  928. package/src/shared/utils/__tests__/ephemeralProjectPath.test.ts +42 -0
  929. package/src/shared/utils/__tests__/teamProvider.test.ts +29 -0
  930. package/src/shared/utils/agentLanguage.ts +122 -0
  931. package/src/shared/utils/anthropicLaunchModel.ts +96 -0
  932. package/src/shared/utils/anthropicModelDefaults.ts +3 -0
  933. package/src/shared/utils/apiErrorDetector.ts +13 -0
  934. package/src/shared/utils/boardTaskActivityLabels.ts +128 -0
  935. package/src/shared/utils/boardTaskActivityPresentation.ts +75 -0
  936. package/src/shared/utils/cliArgsParser.ts +129 -0
  937. package/src/shared/utils/contentSanitizer.ts +207 -0
  938. package/src/shared/utils/contextMetrics.ts +236 -0
  939. package/src/shared/utils/costFormatting.ts +45 -0
  940. package/src/shared/utils/diffContextHash.ts +22 -0
  941. package/src/shared/utils/effortLevels.ts +73 -0
  942. package/src/shared/utils/ephemeralProjectPath.ts +41 -0
  943. package/src/shared/utils/errorHandling.ts +26 -0
  944. package/src/shared/utils/extensionNormalizers.ts +336 -0
  945. package/src/shared/utils/idleNotificationSemantics.ts +86 -0
  946. package/src/shared/utils/inboxNoise.ts +158 -0
  947. package/src/shared/utils/leadDetection.ts +63 -0
  948. package/src/shared/utils/logger.ts +69 -0
  949. package/src/shared/utils/markdownTextSearch.ts +210 -0
  950. package/src/shared/utils/mcpScopes.ts +36 -0
  951. package/src/shared/utils/modelParser.ts +158 -0
  952. package/src/shared/utils/opencodeModelRef.ts +78 -0
  953. package/src/shared/utils/platformPath.ts +103 -0
  954. package/src/shared/utils/pricing.ts +129 -0
  955. package/src/shared/utils/providerBackend.ts +90 -0
  956. package/src/shared/utils/providerExtensionCapabilities.ts +92 -0
  957. package/src/shared/utils/providerModelSelection.ts +5 -0
  958. package/src/shared/utils/providerModelVisibility.ts +47 -0
  959. package/src/shared/utils/rateLimitDetector.ts +334 -0
  960. package/src/shared/utils/reviewState.ts +74 -0
  961. package/src/shared/utils/sentryConfig.ts +26 -0
  962. package/src/shared/utils/skillRoots.ts +93 -0
  963. package/src/shared/utils/slashCommands.ts +128 -0
  964. package/src/shared/utils/taskChangePresence.ts +35 -0
  965. package/src/shared/utils/taskChangeSince.ts +51 -0
  966. package/src/shared/utils/taskChangeState.ts +49 -0
  967. package/src/shared/utils/taskHistory.ts +82 -0
  968. package/src/shared/utils/taskIdentity.ts +32 -0
  969. package/src/shared/utils/teamGraphDefaultLayout.ts +97 -0
  970. package/src/shared/utils/teamMemberColors.ts +107 -0
  971. package/src/shared/utils/teamMemberName.ts +77 -0
  972. package/src/shared/utils/teamProvider.ts +73 -0
  973. package/src/shared/utils/teamStableOwnerId.ts +12 -0
  974. package/src/shared/utils/teammateMessageParser.ts +52 -0
  975. package/src/shared/utils/tokenFormatting.ts +91 -0
  976. package/src/shared/utils/toolSummary.ts +279 -0
  977. package/src/shared/utils/version.ts +29 -0
  978. package/src/types/agent-teams-controller.d.ts +163 -0
  979. package/src/types/node-pty.d.ts +22 -0
  980. package/src/types/pidusage.d.ts +23 -0
@@ -0,0 +1,1455 @@
1
+ /**
2
+ * Editor slice — manages project editor state.
3
+ *
4
+ * Group 1: File tree state + actions (iter-1)
5
+ * Group 2: Tab management (iter-2)
6
+ * Group 3: Dirty/save state (iter-2)
7
+ * Group 4: File operations (iter-3)
8
+ */
9
+
10
+ import { api } from '@renderer/api';
11
+ import { getLanguageFromFileName } from '@renderer/utils/codemirrorLanguages';
12
+ import { editorBridge } from '@renderer/utils/editorBridge';
13
+ import { invalidateQuickOpenCache } from '@renderer/utils/quickOpenCache';
14
+ import { computeDisambiguatedTabs } from '@renderer/utils/tabLabelDisambiguation';
15
+ import { createLogger } from '@shared/utils/logger';
16
+ import {
17
+ getBasename,
18
+ isPathPrefix,
19
+ isWindowsishPath,
20
+ joinPath,
21
+ lastSeparatorIndex,
22
+ splitPath,
23
+ stripTrailingSeparators,
24
+ } from '@shared/utils/platformPath';
25
+
26
+ import type { AppState } from '../types';
27
+ import type {
28
+ EditorFileChangeEvent,
29
+ EditorFileTab,
30
+ FileTreeEntry,
31
+ GitFileStatus,
32
+ } from '@shared/types/editor';
33
+ import type { StateCreator } from 'zustand';
34
+
35
+ const log = createLogger('Store:editor');
36
+
37
+ /** Remove a key from a record. Returns the same reference if key doesn't exist. */
38
+ function omitKey<V>(record: Record<string, V>, key: string): Record<string, V> {
39
+ if (!(key in record)) return record;
40
+ const result = { ...record };
41
+ delete result[key];
42
+ return result;
43
+ }
44
+
45
+ /**
46
+ * Cooldown map: filePath → timestamp of last successful save.
47
+ *
48
+ * Used to suppress watcher events that arrive after editorSaving is cleared
49
+ * (race condition: atomic write → IPC response → clear saving flag → watcher fires).
50
+ * macOS FSEvents can delay up to ~1s; 2s cooldown covers all platforms safely.
51
+ *
52
+ * Module-level (not in store state) to avoid unnecessary re-renders.
53
+ */
54
+ const recentSaveTimestamps = new Map<string, number>();
55
+ const SAVE_COOLDOWN_MS = 2000;
56
+
57
+ /**
58
+ * Throttle timers for watcher-driven updates.
59
+ * Keeping these module-level avoids store re-renders during bursts.
60
+ */
61
+ let gitStatusThrottleTimer: ReturnType<typeof setTimeout> | null = null;
62
+ const GIT_STATUS_THROTTLE_MS = 1500;
63
+ const dirRefreshDebounceTimers = new Map<string, ReturnType<typeof setTimeout>>();
64
+ const DIR_REFRESH_DEBOUNCE_MS = 350;
65
+
66
+ // Watcher event logging can be extremely expensive during bursts.
67
+ // Keep a lightweight aggregate counter instead of logging per event.
68
+ let watcherEventLogTimer: ReturnType<typeof setTimeout> | null = null;
69
+ let watcherEventCounts: Record<EditorFileChangeEvent['type'], number> = {
70
+ change: 0,
71
+ create: 0,
72
+ delete: 0,
73
+ };
74
+
75
+ let watchedFilesSyncTimer: ReturnType<typeof setTimeout> | null = null;
76
+ let lastWatchedFilesKey = '';
77
+ let watchedDirsSyncTimer: ReturnType<typeof setTimeout> | null = null;
78
+ let lastWatchedDirsKey = '';
79
+ const WATCHED_DIRS_DEBOUNCE_MS = 250;
80
+ const MAX_WATCHED_DIRS = 120;
81
+
82
+ function scheduleSyncWatchedFiles(get: () => AppState): void {
83
+ const state = get();
84
+ if (!state.editorWatcherEnabled) return;
85
+ const projectPath = state.editorProjectPath;
86
+ if (!projectPath) return;
87
+
88
+ const filePaths = state.editorOpenTabs.map((t) => t.filePath).filter(Boolean);
89
+ filePaths.sort((a, b) => a.localeCompare(b));
90
+ const key = `${projectPath}\n${filePaths.join('\n')}`;
91
+ if (key === lastWatchedFilesKey) return;
92
+ lastWatchedFilesKey = key;
93
+
94
+ if (watchedFilesSyncTimer) clearTimeout(watchedFilesSyncTimer);
95
+ watchedFilesSyncTimer = setTimeout(() => {
96
+ watchedFilesSyncTimer = null;
97
+ void api.editor.setWatchedFiles(filePaths);
98
+ }, 150);
99
+ }
100
+
101
+ function scheduleSyncWatchedDirs(get: () => AppState): void {
102
+ const state = get();
103
+ if (!state.editorWatcherEnabled) return;
104
+ const projectPath = state.editorProjectPath;
105
+ if (!projectPath) return;
106
+
107
+ const expanded = Object.entries(state.editorExpandedDirs)
108
+ .filter(([, v]) => v === true)
109
+ .map(([k]) => k);
110
+
111
+ // Always include root (depth=0), plus expanded folders (depth=0).
112
+ // Cap to protect chokidar from too many watched paths if user expands a lot.
113
+ const dirs = [projectPath, ...expanded].slice(0, MAX_WATCHED_DIRS);
114
+ dirs.sort((a, b) => a.localeCompare(b));
115
+ const key = `${projectPath}\n${dirs.join('\n')}`;
116
+ if (key === lastWatchedDirsKey) return;
117
+ lastWatchedDirsKey = key;
118
+
119
+ if (watchedDirsSyncTimer) clearTimeout(watchedDirsSyncTimer);
120
+ watchedDirsSyncTimer = setTimeout(() => {
121
+ watchedDirsSyncTimer = null;
122
+ void api.editor.setWatchedDirs(dirs);
123
+ }, WATCHED_DIRS_DEBOUNCE_MS);
124
+ }
125
+
126
+ /**
127
+ * Open request sequence for editor initialization.
128
+ * Cancels stale async work (notably React 18 StrictMode dev effect mount/unmount).
129
+ */
130
+ let editorOpenSeq = 0;
131
+
132
+ /**
133
+ * Cooldown map: filePath → timestamp of last successful move.
134
+ * Suppresses watcher events triggered by our own move operations.
135
+ */
136
+ const recentMoveTimestamps = new Map<string, number>();
137
+ const MOVE_COOLDOWN_MS = 2000;
138
+
139
+ function scheduleIdleWork(cb: () => void): void {
140
+ // Prefer requestIdleCallback when available; fall back to a short timeout.
141
+ // This keeps editor open responsive for large repos.
142
+ // timeout ensures the callback fires within 2s even if the event loop is busy
143
+ // (without it, requestIdleCallback can be delayed indefinitely).
144
+ try {
145
+ const ric = (
146
+ window as unknown as {
147
+ requestIdleCallback?: (fn: () => void, opts?: { timeout: number }) => number;
148
+ }
149
+ ).requestIdleCallback;
150
+ if (typeof ric === 'function') {
151
+ ric(cb, { timeout: 2000 });
152
+ return;
153
+ }
154
+ } catch {
155
+ // ignore
156
+ }
157
+ setTimeout(cb, 150);
158
+ }
159
+
160
+ // =============================================================================
161
+ // Slice Interface
162
+ // =============================================================================
163
+
164
+ export interface EditorSlice {
165
+ // ═══════════════════════════════════════════════════════
166
+ // Group 1: File tree state + actions
167
+ // ═══════════════════════════════════════════════════════
168
+ editorProjectPath: string | null;
169
+ editorFileTree: FileTreeEntry[] | null;
170
+ editorFileTreeLoading: boolean;
171
+ editorFileTreeError: string | null;
172
+ editorExpandedDirs: Record<string, boolean>;
173
+
174
+ openEditor: (projectPath: string) => Promise<void>;
175
+ closeEditor: () => void;
176
+ loadFileTree: (dirPath: string) => Promise<void>;
177
+ expandDirectory: (dirPath: string) => Promise<void>;
178
+ collapseDirectory: (dirPath: string) => void;
179
+
180
+ // ═══════════════════════════════════════════════════════
181
+ // Group 2: Tab management
182
+ // ═══════════════════════════════════════════════════════
183
+ editorOpenTabs: EditorFileTab[];
184
+ editorActiveTabId: string | null;
185
+
186
+ openFile: (filePath: string) => void;
187
+ closeEditorTab: (tabId: string) => void;
188
+ closeOtherEditorTabs: (keepTabId: string) => void;
189
+ closeEditorTabsToLeft: (tabId: string) => void;
190
+ closeEditorTabsToRight: (tabId: string) => void;
191
+ closeAllEditorTabs: () => void;
192
+ setActiveEditorTab: (tabId: string) => void;
193
+ reorderEditorTabs: (activeId: string, overId: string) => void;
194
+
195
+ // ═══════════════════════════════════════════════════════
196
+ // Group 3: Content + Save
197
+ // Content lives in EditorState (Map<tabId, EditorState> in useRef).
198
+ // Store only tracks dirty flags, loading, and save status.
199
+ // ═══════════════════════════════════════════════════════
200
+ editorFileLoading: Record<string, boolean>;
201
+ editorModifiedFiles: Record<string, boolean>;
202
+ editorSaving: Record<string, boolean>;
203
+ editorSaveError: Record<string, string>;
204
+
205
+ markFileModified: (filePath: string) => void;
206
+ markFileSaved: (filePath: string) => void;
207
+ saveFile: (filePath: string) => Promise<void>;
208
+ saveAllFiles: () => Promise<void>;
209
+ discardChanges: (filePath: string) => void;
210
+ hasUnsavedChanges: () => boolean;
211
+
212
+ // ═══════════════════════════════════════════════════════
213
+ // Group 4: File operations (iter-3)
214
+ // ═══════════════════════════════════════════════════════
215
+ editorCreating: boolean;
216
+ editorCreateError: string | null;
217
+
218
+ createFileInTree: (parentDir: string, fileName: string) => Promise<string | null>;
219
+ createDirInTree: (parentDir: string, dirName: string) => Promise<string | null>;
220
+ deleteFileFromTree: (filePath: string) => Promise<boolean>;
221
+ moveFileInTree: (sourcePath: string, destDir: string) => Promise<boolean>;
222
+ renameFileInTree: (sourcePath: string, newName: string) => Promise<boolean>;
223
+
224
+ // ═══════════════════════════════════════════════════════
225
+ // Group 5: Git status + file watcher + line wrap (iter-5)
226
+ // ═══════════════════════════════════════════════════════
227
+ editorGitFiles: GitFileStatus[];
228
+ editorGitBranch: string | null;
229
+ editorIsGitRepo: boolean;
230
+ editorGitLoading: boolean;
231
+ editorWatcherEnabled: boolean;
232
+ editorLineWrap: boolean;
233
+ /** Files changed on disk while open (absolute paths) */
234
+ editorExternalChanges: Record<string, EditorFileChangeEvent['type']>;
235
+ /** Baseline mtime per file (for conflict detection) */
236
+ editorFileMtimes: Record<string, number>;
237
+ /** File path with active save conflict (null = no conflict) */
238
+ editorConflictFile: string | null;
239
+
240
+ /** Pending line to scroll to after file loads (1-based). Set by search result click. */
241
+ editorPendingGoToLine: number | null;
242
+ setPendingGoToLine: (line: number | null) => void;
243
+
244
+ /** File path to reveal in editor (opens editor, expands dirs, opens tab, focuses in tree). */
245
+ editorPendingRevealFile: string | null;
246
+ /** Request to reveal a file in the editor. Opens editor overlay if needed. */
247
+ revealFileInEditor: (filePath: string) => void;
248
+ /** Request to reveal a folder in the editor tree. Expands parent dirs + the folder itself. */
249
+ revealFolderInEditor: (folderPath: string) => void;
250
+ /** Process the pending reveal: expand parent dirs and open the file tab. */
251
+ revealAndOpenFile: (filePath: string) => Promise<void>;
252
+ clearPendingRevealFile: () => void;
253
+
254
+ fetchGitStatus: () => Promise<void>;
255
+ toggleWatcher: (enable: boolean) => Promise<void>;
256
+ toggleLineWrap: () => void;
257
+ handleExternalFileChange: (event: EditorFileChangeEvent) => void;
258
+ clearExternalChange: (filePath: string) => void;
259
+ setFileMtime: (filePath: string, mtimeMs: number) => void;
260
+ forceOverwrite: (filePath: string) => Promise<void>;
261
+ resolveConflict: () => void;
262
+ }
263
+
264
+ // =============================================================================
265
+ // Slice Creator
266
+ // =============================================================================
267
+
268
+ export const createEditorSlice: StateCreator<AppState, [], [], EditorSlice> = (set, get) => ({
269
+ // Group 1 initial state
270
+ editorProjectPath: null,
271
+ editorFileTree: null,
272
+ editorFileTreeLoading: false,
273
+ editorFileTreeError: null,
274
+ editorExpandedDirs: {},
275
+
276
+ // Group 2 initial state
277
+ editorOpenTabs: [],
278
+ editorActiveTabId: null,
279
+
280
+ // Group 3 initial state
281
+ editorFileLoading: {},
282
+ editorModifiedFiles: {},
283
+ editorSaving: {},
284
+ editorSaveError: {},
285
+
286
+ // Group 4 initial state
287
+ editorCreating: false,
288
+ editorCreateError: null,
289
+
290
+ // Group 5 initial state
291
+ editorGitFiles: [],
292
+ editorGitBranch: null,
293
+ editorIsGitRepo: false,
294
+ editorGitLoading: false,
295
+ editorWatcherEnabled: false,
296
+ editorLineWrap: (() => {
297
+ try {
298
+ return localStorage.getItem('editor-line-wrap') === 'true';
299
+ } catch {
300
+ return false;
301
+ }
302
+ })(),
303
+ editorExternalChanges: {},
304
+ editorFileMtimes: {},
305
+ editorConflictFile: null,
306
+ editorPendingGoToLine: null,
307
+ editorPendingRevealFile: null,
308
+
309
+ setPendingGoToLine: (line: number | null) => set({ editorPendingGoToLine: line }),
310
+
311
+ revealFileInEditor: (filePath: string) => {
312
+ set({ editorPendingRevealFile: filePath });
313
+ },
314
+
315
+ revealFolderInEditor: (folderPath: string) => {
316
+ // Set pending reveal so EditorFileTree scrolls to the folder
317
+ set({ editorPendingRevealFile: folderPath });
318
+
319
+ // Expand parent dirs + the folder itself
320
+ const { editorProjectPath, editorFileTree, expandDirectory } = get();
321
+ if (!editorProjectPath || !editorFileTree) return;
322
+
323
+ const root = stripTrailingSeparators(editorProjectPath);
324
+ const rootParts = splitPath(root);
325
+ const folderParts = splitPath(folderPath);
326
+ const win = isWindowsishPath(root);
327
+ const eq = (a: string, b: string): boolean =>
328
+ win ? a.toLowerCase() === b.toLowerCase() : a === b;
329
+ const hasPrefix =
330
+ folderParts.length >= rootParts.length &&
331
+ rootParts.every((seg, i) => eq(seg, folderParts[i]));
332
+
333
+ if (hasPrefix) {
334
+ const segments = folderParts.slice(rootParts.length);
335
+ let currentDir = root;
336
+ // Expand each segment including the folder itself
337
+ const doExpand = async (): Promise<void> => {
338
+ for (const seg of segments) {
339
+ currentDir = joinPath(currentDir, seg);
340
+ await expandDirectory(currentDir);
341
+ }
342
+ set({ editorPendingRevealFile: null });
343
+ };
344
+ void doExpand();
345
+ }
346
+ },
347
+
348
+ clearPendingRevealFile: () => {
349
+ set({ editorPendingRevealFile: null });
350
+ },
351
+
352
+ revealAndOpenFile: async (filePath: string) => {
353
+ const { editorProjectPath, editorFileTree, expandDirectory, openFile } = get();
354
+ if (!editorProjectPath) return;
355
+
356
+ // Guard: file tree must be loaded before we can reveal.
357
+ // If it's still null, bail out WITHOUT clearing pendingRevealFile
358
+ // so the caller effect can retry after the tree loads.
359
+ if (!editorFileTree) {
360
+ log.info('revealAndOpenFile: tree not loaded yet, deferring reveal');
361
+ return;
362
+ }
363
+
364
+ // Compute parent directories from projectRoot to the file.
365
+ // Must handle both `/` and `\` because paths may arrive from any OS.
366
+ const root = stripTrailingSeparators(editorProjectPath);
367
+ const rootParts = splitPath(root);
368
+ const fileParts = splitPath(filePath);
369
+ const win = isWindowsishPath(root);
370
+ const eq = (a: string, b: string): boolean =>
371
+ win ? a.toLowerCase() === b.toLowerCase() : a === b;
372
+ const hasPrefix =
373
+ fileParts.length >= rootParts.length && rootParts.every((seg, i) => eq(seg, fileParts[i]));
374
+
375
+ if (hasPrefix) {
376
+ const segments = fileParts.slice(rootParts.length);
377
+ // Expand each parent directory sequentially (root → child → grandchild).
378
+ // Skip the last segment (the file name itself).
379
+ // Each expandDirectory call is awaited so that its children are merged
380
+ // into the tree before the next level is expanded.
381
+ let currentDir = root;
382
+ for (let i = 0; i < segments.length - 1; i++) {
383
+ currentDir = joinPath(currentDir, segments[i] ?? '');
384
+ await expandDirectory(currentDir);
385
+ }
386
+ }
387
+
388
+ // Open the file as a tab
389
+ openFile(filePath);
390
+ // Clear reveal state
391
+ set({ editorPendingRevealFile: null });
392
+ },
393
+
394
+ // ═══════════════════════════════════════════════════════
395
+ // Group 1: File tree actions
396
+ // ═══════════════════════════════════════════════════════
397
+
398
+ openEditor: async (projectPath: string) => {
399
+ const openSeq = ++editorOpenSeq;
400
+ set({
401
+ editorProjectPath: projectPath,
402
+ editorFileTree: null,
403
+ editorFileTreeLoading: true,
404
+ editorFileTreeError: null,
405
+ editorExpandedDirs: {},
406
+ editorOpenTabs: [],
407
+ editorActiveTabId: null,
408
+ editorFileLoading: {},
409
+ editorModifiedFiles: {},
410
+ editorSaving: {},
411
+ editorSaveError: {},
412
+ editorCreating: false,
413
+ editorCreateError: null,
414
+ editorGitFiles: [],
415
+ editorGitBranch: null,
416
+ editorIsGitRepo: false,
417
+ editorGitLoading: false,
418
+ editorWatcherEnabled: false,
419
+ editorExternalChanges: {},
420
+ editorFileMtimes: {},
421
+ editorConflictFile: null,
422
+ editorPendingGoToLine: null,
423
+ });
424
+
425
+ try {
426
+ const tOpen = performance.now();
427
+ await api.editor.open(projectPath);
428
+ const openMs = performance.now() - tOpen;
429
+
430
+ // Cancel stale opens (e.g. StrictMode effect cleanup, or rapid project switching)
431
+ if (editorOpenSeq !== openSeq || get().editorProjectPath !== projectPath) {
432
+ return;
433
+ }
434
+
435
+ // Load file tree first so UI becomes interactive quickly.
436
+ // Git status and file watching can be expensive on large projects, so they are NOT awaited here.
437
+ const tReadDir = performance.now();
438
+ const result = await api.editor.readDir(projectPath);
439
+ const readDirMs = performance.now() - tReadDir;
440
+
441
+ if (editorOpenSeq !== openSeq || get().editorProjectPath !== projectPath) {
442
+ return;
443
+ }
444
+
445
+ const tSet = performance.now();
446
+ set({
447
+ editorFileTree: result.entries,
448
+ editorFileTreeLoading: false,
449
+ });
450
+ const setMs = performance.now() - tSet;
451
+
452
+ log.info(
453
+ `[perf] openEditor: open=${openMs.toFixed(1)}ms, readDir=${readDirMs.toFixed(1)}ms, set=${setMs.toFixed(1)}ms, entries=${result.entries.length}`
454
+ );
455
+
456
+ // Enable watcher by default (like most editors), but defer startup until idle so open stays fast.
457
+ // Allow users to persistently disable it via localStorage toggle.
458
+ const watcherDesired = (() => {
459
+ try {
460
+ return localStorage.getItem('editor-watcher-enabled') !== 'false';
461
+ } catch {
462
+ return true;
463
+ }
464
+ })();
465
+
466
+ scheduleIdleWork(() => {
467
+ if (editorOpenSeq !== openSeq || get().editorProjectPath !== projectPath) return;
468
+ if (watcherDesired) void get().toggleWatcher(true);
469
+ // Defer initial git status a bit more — it can be expensive on large repos.
470
+ setTimeout(() => {
471
+ if (editorOpenSeq !== openSeq || get().editorProjectPath !== projectPath) return;
472
+ void get().fetchGitStatus();
473
+ }, 1200);
474
+ });
475
+ } catch (error) {
476
+ // Ignore errors from stale opens (e.g. StrictMode cleanup during dev)
477
+ if (editorOpenSeq !== openSeq || get().editorProjectPath !== projectPath) {
478
+ return;
479
+ }
480
+ const message = error instanceof Error ? error.message : String(error);
481
+ log.error('Failed to open editor:', message);
482
+ set({
483
+ editorFileTreeLoading: false,
484
+ editorFileTreeError: message,
485
+ });
486
+ }
487
+ },
488
+
489
+ closeEditor: () => {
490
+ // Cancel any in-flight openEditor async work
491
+ editorOpenSeq++;
492
+ // Cancel any pending watcher sync (avoid calling into main after close)
493
+ if (watchedFilesSyncTimer) {
494
+ clearTimeout(watchedFilesSyncTimer);
495
+ watchedFilesSyncTimer = null;
496
+ }
497
+ if (watchedDirsSyncTimer) {
498
+ clearTimeout(watchedDirsSyncTimer);
499
+ watchedDirsSyncTimer = null;
500
+ }
501
+ lastWatchedFilesKey = '';
502
+ lastWatchedDirsKey = '';
503
+
504
+ // Clear cooldown timestamps (no stale entries across editor sessions)
505
+ recentSaveTimestamps.clear();
506
+ recentMoveTimestamps.clear();
507
+
508
+ // Best-effort IPC cleanup
509
+ api.editor.close().catch((e: unknown) => {
510
+ log.error('editor:close failed:', e);
511
+ });
512
+
513
+ // Cleanup bridge (destroys EditorView, clears caches)
514
+ editorBridge.destroy();
515
+
516
+ set({
517
+ editorProjectPath: null,
518
+ editorFileTree: null,
519
+ editorFileTreeLoading: false,
520
+ editorFileTreeError: null,
521
+ editorExpandedDirs: {},
522
+ editorOpenTabs: [],
523
+ editorActiveTabId: null,
524
+ editorFileLoading: {},
525
+ editorModifiedFiles: {},
526
+ editorSaving: {},
527
+ editorSaveError: {},
528
+ editorCreating: false,
529
+ editorCreateError: null,
530
+ editorGitFiles: [],
531
+ editorGitBranch: null,
532
+ editorIsGitRepo: false,
533
+ editorGitLoading: false,
534
+ editorWatcherEnabled: false,
535
+ editorExternalChanges: {},
536
+ editorFileMtimes: {},
537
+ editorConflictFile: null,
538
+ editorPendingGoToLine: null,
539
+ });
540
+ },
541
+
542
+ loadFileTree: async (dirPath: string) => {
543
+ set({ editorFileTreeLoading: true, editorFileTreeError: null });
544
+
545
+ try {
546
+ const t0 = performance.now();
547
+ const result = await api.editor.readDir(dirPath);
548
+ const ipcMs = performance.now() - t0;
549
+ const t1 = performance.now();
550
+ set({
551
+ editorFileTree: result.entries,
552
+ editorFileTreeLoading: false,
553
+ });
554
+ const setMs = performance.now() - t1;
555
+ log.info(
556
+ `[perf] loadFileTree: IPC=${ipcMs.toFixed(1)}ms, set=${setMs.toFixed(1)}ms, entries=${result.entries.length}`
557
+ );
558
+ } catch (error) {
559
+ const message = error instanceof Error ? error.message : String(error);
560
+ log.error('Failed to load file tree:', message);
561
+ set({
562
+ editorFileTreeLoading: false,
563
+ editorFileTreeError: message,
564
+ });
565
+ }
566
+ },
567
+
568
+ expandDirectory: async (dirPath: string) => {
569
+ const { editorExpandedDirs } = get();
570
+
571
+ // Skip set() if already expanded — prevents unnecessary re-render
572
+ const wasExpanded = !!editorExpandedDirs[dirPath];
573
+ if (!wasExpanded) {
574
+ set({
575
+ editorExpandedDirs: { ...editorExpandedDirs, [dirPath]: true },
576
+ });
577
+ scheduleSyncWatchedDirs(get);
578
+ }
579
+
580
+ try {
581
+ const t0 = performance.now();
582
+ const result = await api.editor.readDir(dirPath);
583
+ const ipcMs = performance.now() - t0;
584
+ // Use fresh tree from store after await to avoid overwriting concurrent updates
585
+ const currentTree = get().editorFileTree;
586
+ const t1 = performance.now();
587
+ const updatedTree = mergeChildrenIntoTree(currentTree ?? [], dirPath, result.entries);
588
+ const mergeMs = performance.now() - t1;
589
+ const t2 = performance.now();
590
+ set({ editorFileTree: updatedTree });
591
+ const setMs = performance.now() - t2;
592
+ log.info(
593
+ `[perf] expandDirectory: IPC=${ipcMs.toFixed(1)}ms, merge=${mergeMs.toFixed(1)}ms, set=${setMs.toFixed(1)}ms, entries=${result.entries.length}, wasExpanded=${wasExpanded}`
594
+ );
595
+ } catch (error) {
596
+ const message = error instanceof Error ? error.message : String(error);
597
+ log.error('Failed to expand directory:', message);
598
+ const current = get().editorExpandedDirs;
599
+ set({ editorExpandedDirs: omitKey(current, dirPath) });
600
+ }
601
+ },
602
+
603
+ collapseDirectory: (dirPath: string) => {
604
+ const { editorExpandedDirs } = get();
605
+ set({ editorExpandedDirs: omitKey(editorExpandedDirs, dirPath) });
606
+ scheduleSyncWatchedDirs(get);
607
+ },
608
+
609
+ // ═══════════════════════════════════════════════════════
610
+ // Group 2: Tab management
611
+ // ═══════════════════════════════════════════════════════
612
+
613
+ openFile: (filePath: string) => {
614
+ const { editorOpenTabs } = get();
615
+
616
+ // Dedup: if file already open, just activate it
617
+ const existing = editorOpenTabs.find((t) => t.filePath === filePath);
618
+ if (existing) {
619
+ set({ editorActiveTabId: existing.id });
620
+ return;
621
+ }
622
+
623
+ const fileName = getBasename(filePath) || 'file';
624
+ const language = getLanguageFromFileName(fileName);
625
+
626
+ const tab: EditorFileTab = {
627
+ id: filePath,
628
+ filePath,
629
+ fileName,
630
+ language,
631
+ };
632
+
633
+ const newTabs = computeDisambiguatedTabs([...editorOpenTabs, tab]);
634
+
635
+ set({
636
+ editorOpenTabs: newTabs,
637
+ editorActiveTabId: tab.id,
638
+ });
639
+
640
+ scheduleSyncWatchedFiles(get);
641
+ },
642
+
643
+ closeEditorTab: (tabId: string) => {
644
+ const { editorOpenTabs, editorActiveTabId, editorModifiedFiles, editorSaveError } = get();
645
+ const filtered = editorOpenTabs.filter((t) => t.id !== tabId);
646
+
647
+ // Clean up dirty/error state for closed tab
648
+ const restModified = omitKey(editorModifiedFiles, tabId);
649
+ const restErrors = omitKey(editorSaveError, tabId);
650
+
651
+ // Clear cached EditorState from bridge
652
+ editorBridge.deleteState(tabId);
653
+
654
+ // Clear draft from localStorage
655
+ try {
656
+ localStorage.removeItem(`editor-draft:${tabId}`);
657
+ } catch {
658
+ // localStorage may not be available
659
+ }
660
+
661
+ let newActiveId = editorActiveTabId;
662
+ if (editorActiveTabId === tabId) {
663
+ // Activate adjacent tab
664
+ const closedIndex = editorOpenTabs.findIndex((t) => t.id === tabId);
665
+ if (filtered.length > 0) {
666
+ newActiveId = filtered[Math.min(closedIndex, filtered.length - 1)].id;
667
+ } else {
668
+ newActiveId = null;
669
+ }
670
+ }
671
+
672
+ // Recompute disambiguation after removing tab
673
+ const disambiguated = computeDisambiguatedTabs(filtered);
674
+
675
+ set({
676
+ editorOpenTabs: disambiguated,
677
+ editorActiveTabId: newActiveId,
678
+ editorModifiedFiles: restModified,
679
+ editorSaveError: restErrors,
680
+ });
681
+
682
+ scheduleSyncWatchedFiles(get);
683
+ },
684
+
685
+ closeOtherEditorTabs: (keepTabId: string) => {
686
+ const { editorOpenTabs } = get();
687
+ const toClose = editorOpenTabs.filter((t) => t.id !== keepTabId);
688
+ for (const tab of toClose) get().closeEditorTab(tab.id);
689
+ },
690
+
691
+ closeEditorTabsToLeft: (tabId: string) => {
692
+ const { editorOpenTabs } = get();
693
+ const idx = editorOpenTabs.findIndex((t) => t.id === tabId);
694
+ if (idx <= 0) return;
695
+ const toClose = editorOpenTabs.slice(0, idx);
696
+ for (const tab of toClose) get().closeEditorTab(tab.id);
697
+ },
698
+
699
+ closeEditorTabsToRight: (tabId: string) => {
700
+ const { editorOpenTabs } = get();
701
+ const idx = editorOpenTabs.findIndex((t) => t.id === tabId);
702
+ if (idx < 0 || idx >= editorOpenTabs.length - 1) return;
703
+ const toClose = editorOpenTabs.slice(idx + 1);
704
+ for (const tab of toClose) get().closeEditorTab(tab.id);
705
+ },
706
+
707
+ closeAllEditorTabs: () => {
708
+ const { editorOpenTabs } = get();
709
+ for (const tab of [...editorOpenTabs]) get().closeEditorTab(tab.id);
710
+ },
711
+
712
+ setActiveEditorTab: (tabId: string) => {
713
+ set({ editorActiveTabId: tabId });
714
+ },
715
+
716
+ reorderEditorTabs: (activeId: string, overId: string) => {
717
+ if (activeId === overId) return;
718
+ const { editorOpenTabs } = get();
719
+ const oldIndex = editorOpenTabs.findIndex((t) => t.id === activeId);
720
+ const newIndex = editorOpenTabs.findIndex((t) => t.id === overId);
721
+ if (oldIndex === -1 || newIndex === -1) return;
722
+
723
+ const updated = [...editorOpenTabs];
724
+ const [moved] = updated.splice(oldIndex, 1);
725
+ updated.splice(newIndex, 0, moved);
726
+ set({ editorOpenTabs: updated });
727
+ },
728
+
729
+ // ═══════════════════════════════════════════════════════
730
+ // Group 3: Content + Save
731
+ // ═══════════════════════════════════════════════════════
732
+
733
+ markFileModified: (filePath: string) => {
734
+ const { editorModifiedFiles } = get();
735
+ if (editorModifiedFiles[filePath]) return; // Already marked
736
+ set({ editorModifiedFiles: { ...editorModifiedFiles, [filePath]: true } });
737
+ },
738
+
739
+ markFileSaved: (filePath: string) => {
740
+ const { editorModifiedFiles } = get();
741
+ set({ editorModifiedFiles: omitKey(editorModifiedFiles, filePath) });
742
+ },
743
+
744
+ saveFile: async (filePath: string) => {
745
+ const content = editorBridge.getContent(filePath);
746
+ if (content === null) {
747
+ log.error('saveFile: no content available for', filePath);
748
+ return;
749
+ }
750
+
751
+ set((s) => ({
752
+ editorSaving: { ...s.editorSaving, [filePath]: true },
753
+ editorSaveError: omitKey(s.editorSaveError, filePath),
754
+ }));
755
+
756
+ try {
757
+ // Pass baseline mtime for conflict detection (if available)
758
+ const baselineMtime = get().editorFileMtimes[filePath];
759
+ const result = await api.editor.writeFile(filePath, content, baselineMtime);
760
+
761
+ // Record save timestamp BEFORE clearing editorSaving (watcher race guard)
762
+ recentSaveTimestamps.set(filePath, Date.now());
763
+
764
+ // Update baseline mtime with the new value after successful save
765
+ set((s) => ({
766
+ editorModifiedFiles: omitKey(s.editorModifiedFiles, filePath),
767
+ editorSaving: omitKey(s.editorSaving, filePath),
768
+ editorFileMtimes: { ...s.editorFileMtimes, [filePath]: result.mtimeMs },
769
+ editorExternalChanges: omitKey(s.editorExternalChanges, filePath),
770
+ }));
771
+
772
+ try {
773
+ localStorage.removeItem(`editor-draft:${filePath}`);
774
+ } catch {
775
+ // localStorage may not be available
776
+ }
777
+ } catch (error) {
778
+ const message = error instanceof Error ? error.message : String(error);
779
+
780
+ // Handle conflict errors specifically
781
+ if (message.startsWith('CONFLICT')) {
782
+ log.error('Save conflict detected:', filePath);
783
+ set((s) => ({
784
+ editorSaving: omitKey(s.editorSaving, filePath),
785
+ editorConflictFile: filePath,
786
+ }));
787
+ return;
788
+ }
789
+
790
+ log.error('Failed to save file:', message);
791
+ set((s) => ({
792
+ editorSaving: omitKey(s.editorSaving, filePath),
793
+ editorSaveError: { ...s.editorSaveError, [filePath]: message },
794
+ }));
795
+ }
796
+ },
797
+
798
+ saveAllFiles: async () => {
799
+ const { editorModifiedFiles } = get();
800
+ const modifiedContent = editorBridge.getAllModifiedContent(editorModifiedFiles);
801
+
802
+ const promises: Promise<void>[] = [];
803
+ for (const [filePath, content] of modifiedContent) {
804
+ promises.push(
805
+ (async () => {
806
+ set((s) => ({
807
+ editorSaving: { ...s.editorSaving, [filePath]: true },
808
+ }));
809
+
810
+ try {
811
+ const baselineMtime = get().editorFileMtimes[filePath];
812
+ const result = await api.editor.writeFile(filePath, content, baselineMtime);
813
+
814
+ // Record save timestamp BEFORE clearing editorSaving (watcher race guard)
815
+ recentSaveTimestamps.set(filePath, Date.now());
816
+
817
+ set((s) => ({
818
+ editorModifiedFiles: omitKey(s.editorModifiedFiles, filePath),
819
+ editorSaving: omitKey(s.editorSaving, filePath),
820
+ editorFileMtimes: { ...s.editorFileMtimes, [filePath]: result.mtimeMs },
821
+ editorExternalChanges: omitKey(s.editorExternalChanges, filePath),
822
+ }));
823
+ try {
824
+ localStorage.removeItem(`editor-draft:${filePath}`);
825
+ } catch {
826
+ // ignore
827
+ }
828
+ } catch (error) {
829
+ const message = error instanceof Error ? error.message : String(error);
830
+
831
+ if (message.startsWith('CONFLICT')) {
832
+ log.error('Save conflict detected:', filePath);
833
+ set((s) => ({
834
+ editorSaving: omitKey(s.editorSaving, filePath),
835
+ editorConflictFile: filePath,
836
+ }));
837
+ return;
838
+ }
839
+
840
+ log.error('Failed to save file:', filePath, message);
841
+ set((s) => ({
842
+ editorSaving: omitKey(s.editorSaving, filePath),
843
+ editorSaveError: { ...s.editorSaveError, [filePath]: message },
844
+ }));
845
+ }
846
+ })()
847
+ );
848
+ }
849
+
850
+ await Promise.allSettled(promises);
851
+ },
852
+
853
+ discardChanges: (filePath: string) => {
854
+ const { editorModifiedFiles, editorSaveError } = get();
855
+ set({
856
+ editorModifiedFiles: omitKey(editorModifiedFiles, filePath),
857
+ editorSaveError: omitKey(editorSaveError, filePath),
858
+ });
859
+
860
+ try {
861
+ localStorage.removeItem(`editor-draft:${filePath}`);
862
+ } catch {
863
+ // localStorage may not be available
864
+ }
865
+ },
866
+
867
+ hasUnsavedChanges: () => {
868
+ return Object.keys(get().editorModifiedFiles).length > 0;
869
+ },
870
+
871
+ // ═══════════════════════════════════════════════════════
872
+ // Group 4: File operations
873
+ // ═══════════════════════════════════════════════════════
874
+
875
+ createFileInTree: async (parentDir: string, fileName: string) => {
876
+ set({ editorCreating: true, editorCreateError: null });
877
+
878
+ try {
879
+ const result = await api.editor.createFile(parentDir, fileName);
880
+
881
+ // Refresh parent directory in the tree
882
+ await refreshDirectory(get, set, parentDir);
883
+
884
+ set({ editorCreating: false });
885
+ return result.filePath;
886
+ } catch (error) {
887
+ const message = error instanceof Error ? error.message : String(error);
888
+ log.error('Failed to create file:', message);
889
+ set({ editorCreating: false, editorCreateError: message });
890
+ return null;
891
+ }
892
+ },
893
+
894
+ createDirInTree: async (parentDir: string, dirName: string) => {
895
+ set({ editorCreating: true, editorCreateError: null });
896
+
897
+ try {
898
+ const result = await api.editor.createDir(parentDir, dirName);
899
+
900
+ // Refresh parent directory in the tree
901
+ await refreshDirectory(get, set, parentDir);
902
+
903
+ set({ editorCreating: false });
904
+ return result.dirPath;
905
+ } catch (error) {
906
+ const message = error instanceof Error ? error.message : String(error);
907
+ log.error('Failed to create directory:', message);
908
+ set({ editorCreating: false, editorCreateError: message });
909
+ return null;
910
+ }
911
+ },
912
+
913
+ deleteFileFromTree: async (filePath: string) => {
914
+ try {
915
+ await api.editor.deleteFile(filePath);
916
+
917
+ // Close tab if the deleted file is open
918
+ const { editorOpenTabs } = get();
919
+ const tabsToClose = editorOpenTabs.filter((t) => isPathPrefix(filePath, t.filePath));
920
+ for (const tab of tabsToClose) {
921
+ get().closeEditorTab(tab.id);
922
+ }
923
+
924
+ // Refresh parent directory
925
+ const parentDir = filePath.substring(0, lastSeparatorIndex(filePath));
926
+ if (parentDir) {
927
+ await refreshDirectory(get, set, parentDir);
928
+ }
929
+
930
+ return true;
931
+ } catch (error) {
932
+ const message = error instanceof Error ? error.message : String(error);
933
+ log.error('Failed to delete file:', message);
934
+ return false;
935
+ }
936
+ },
937
+
938
+ moveFileInTree: async (sourcePath: string, destDir: string) => {
939
+ const { editorSaving } = get();
940
+
941
+ // Guard: don't move during save
942
+ if (editorSaving[sourcePath]) {
943
+ log.error('moveFileInTree: blocked — file is being saved:', sourcePath);
944
+ return false;
945
+ }
946
+
947
+ try {
948
+ const result = await api.editor.moveFile(sourcePath, destDir);
949
+ const { newPath, isDirectory } = result;
950
+ const oldParent = sourcePath.substring(0, lastSeparatorIndex(sourcePath));
951
+
952
+ // Record move timestamps for watcher cooldown
953
+ recentMoveTimestamps.set(sourcePath, Date.now());
954
+ recentMoveTimestamps.set(newPath, Date.now());
955
+
956
+ // Atomic remap of all path-keyed state
957
+ set((s) => {
958
+ const tabs = s.editorOpenTabs.map((tab) => {
959
+ const remapped = remapPath(tab.filePath, sourcePath, newPath);
960
+ if (remapped === tab.filePath) return tab;
961
+ const fileName = getBasename(remapped) || 'file';
962
+ return {
963
+ ...tab,
964
+ id: remapped,
965
+ filePath: remapped,
966
+ fileName,
967
+ language: getLanguageFromFileName(fileName),
968
+ };
969
+ });
970
+
971
+ return {
972
+ editorOpenTabs: computeDisambiguatedTabs(tabs),
973
+ editorActiveTabId:
974
+ remapPath(s.editorActiveTabId ?? '', sourcePath, newPath) || s.editorActiveTabId,
975
+ editorModifiedFiles: remapRecord(s.editorModifiedFiles, sourcePath, newPath),
976
+ editorSaving: remapRecord(s.editorSaving, sourcePath, newPath),
977
+ editorSaveError: remapRecord(s.editorSaveError, sourcePath, newPath),
978
+ editorFileLoading: remapRecord(s.editorFileLoading, sourcePath, newPath),
979
+ editorExternalChanges: remapRecord(s.editorExternalChanges, sourcePath, newPath),
980
+ editorFileMtimes: remapRecord(s.editorFileMtimes, sourcePath, newPath),
981
+ editorExpandedDirs: remapRecord(s.editorExpandedDirs, sourcePath, newPath),
982
+ };
983
+ });
984
+
985
+ // Keep open-files-only watcher in sync with remapped tab paths
986
+ scheduleSyncWatchedFiles(get);
987
+
988
+ // Remap bridge state for each affected tab
989
+ const { editorOpenTabs } = get();
990
+ for (const tab of editorOpenTabs) {
991
+ // Check if this tab was affected by the move
992
+ const originalPath = reverseRemapPath(tab.filePath, sourcePath, newPath);
993
+ if (originalPath !== tab.filePath) {
994
+ editorBridge.remapState(originalPath, tab.filePath);
995
+ }
996
+ }
997
+ // Also remap for single file case (directories have no direct bridge state)
998
+ if (!isDirectory) {
999
+ editorBridge.remapState(sourcePath, newPath);
1000
+ }
1001
+
1002
+ // Remap localStorage drafts
1003
+ try {
1004
+ for (let i = 0; i < localStorage.length; i++) {
1005
+ const key = localStorage.key(i);
1006
+ if (key?.startsWith('editor-draft:')) {
1007
+ const draftPath = key.slice('editor-draft:'.length);
1008
+ const remapped = remapPath(draftPath, sourcePath, newPath);
1009
+ if (remapped !== draftPath) {
1010
+ const value = localStorage.getItem(key);
1011
+ localStorage.removeItem(key);
1012
+ if (value !== null) localStorage.setItem(`editor-draft:${remapped}`, value);
1013
+ }
1014
+ }
1015
+ }
1016
+ } catch {
1017
+ // localStorage may not be available
1018
+ }
1019
+
1020
+ // Remap recentSaveTimestamps
1021
+ for (const [key, ts] of [...recentSaveTimestamps.entries()]) {
1022
+ const remapped = remapPath(key, sourcePath, newPath);
1023
+ if (remapped !== key) {
1024
+ recentSaveTimestamps.delete(key);
1025
+ recentSaveTimestamps.set(remapped, ts);
1026
+ }
1027
+ }
1028
+
1029
+ // Refresh directories and git status in background
1030
+ void refreshDirectory(get, set, oldParent);
1031
+ void refreshDirectory(get, set, destDir);
1032
+ void get().fetchGitStatus();
1033
+
1034
+ return true;
1035
+ } catch (error) {
1036
+ const message = error instanceof Error ? error.message : String(error);
1037
+ log.error('moveFileInTree failed:', message);
1038
+ return false;
1039
+ }
1040
+ },
1041
+
1042
+ renameFileInTree: async (sourcePath: string, newName: string) => {
1043
+ const { editorSaving } = get();
1044
+
1045
+ if (editorSaving[sourcePath]) {
1046
+ log.error('renameFileInTree: blocked — file is being saved:', sourcePath);
1047
+ return false;
1048
+ }
1049
+
1050
+ try {
1051
+ const result = await api.editor.renameFile(sourcePath, newName);
1052
+ const { newPath, isDirectory } = result;
1053
+ const parentDir = sourcePath.substring(0, lastSeparatorIndex(sourcePath));
1054
+
1055
+ recentMoveTimestamps.set(sourcePath, Date.now());
1056
+ recentMoveTimestamps.set(newPath, Date.now());
1057
+
1058
+ set((s) => {
1059
+ const tabs = s.editorOpenTabs.map((tab) => {
1060
+ const remapped = remapPath(tab.filePath, sourcePath, newPath);
1061
+ if (remapped === tab.filePath) return tab;
1062
+ const fileName = getBasename(remapped) || 'file';
1063
+ return {
1064
+ ...tab,
1065
+ id: remapped,
1066
+ filePath: remapped,
1067
+ fileName,
1068
+ language: getLanguageFromFileName(fileName),
1069
+ };
1070
+ });
1071
+
1072
+ return {
1073
+ editorOpenTabs: computeDisambiguatedTabs(tabs),
1074
+ editorActiveTabId:
1075
+ remapPath(s.editorActiveTabId ?? '', sourcePath, newPath) || s.editorActiveTabId,
1076
+ editorModifiedFiles: remapRecord(s.editorModifiedFiles, sourcePath, newPath),
1077
+ editorSaving: remapRecord(s.editorSaving, sourcePath, newPath),
1078
+ editorSaveError: remapRecord(s.editorSaveError, sourcePath, newPath),
1079
+ editorFileLoading: remapRecord(s.editorFileLoading, sourcePath, newPath),
1080
+ editorExternalChanges: remapRecord(s.editorExternalChanges, sourcePath, newPath),
1081
+ editorFileMtimes: remapRecord(s.editorFileMtimes, sourcePath, newPath),
1082
+ editorExpandedDirs: remapRecord(s.editorExpandedDirs, sourcePath, newPath),
1083
+ };
1084
+ });
1085
+
1086
+ // Keep open-files-only watcher in sync with remapped tab paths
1087
+ scheduleSyncWatchedFiles(get);
1088
+
1089
+ // Remap bridge state
1090
+ const { editorOpenTabs } = get();
1091
+ for (const tab of editorOpenTabs) {
1092
+ const originalPath = reverseRemapPath(tab.filePath, sourcePath, newPath);
1093
+ if (originalPath !== tab.filePath) {
1094
+ editorBridge.remapState(originalPath, tab.filePath);
1095
+ }
1096
+ }
1097
+ if (!isDirectory) {
1098
+ editorBridge.remapState(sourcePath, newPath);
1099
+ }
1100
+
1101
+ // Remap localStorage drafts
1102
+ try {
1103
+ for (let i = 0; i < localStorage.length; i++) {
1104
+ const key = localStorage.key(i);
1105
+ if (key?.startsWith('editor-draft:')) {
1106
+ const draftPath = key.slice('editor-draft:'.length);
1107
+ const remapped = remapPath(draftPath, sourcePath, newPath);
1108
+ if (remapped !== draftPath) {
1109
+ const value = localStorage.getItem(key);
1110
+ localStorage.removeItem(key);
1111
+ if (value !== null) localStorage.setItem(`editor-draft:${remapped}`, value);
1112
+ }
1113
+ }
1114
+ }
1115
+ } catch {
1116
+ // localStorage may not be available
1117
+ }
1118
+
1119
+ for (const [key, ts] of [...recentSaveTimestamps.entries()]) {
1120
+ const remapped = remapPath(key, sourcePath, newPath);
1121
+ if (remapped !== key) {
1122
+ recentSaveTimestamps.delete(key);
1123
+ recentSaveTimestamps.set(remapped, ts);
1124
+ }
1125
+ }
1126
+
1127
+ void refreshDirectory(get, set, parentDir);
1128
+ void get().fetchGitStatus();
1129
+
1130
+ return true;
1131
+ } catch (error) {
1132
+ const message = error instanceof Error ? error.message : String(error);
1133
+ log.error('renameFileInTree failed:', message);
1134
+ return false;
1135
+ }
1136
+ },
1137
+
1138
+ // ═══════════════════════════════════════════════════════
1139
+ // Group 5: Git status + file watcher + line wrap
1140
+ // ═══════════════════════════════════════════════════════
1141
+
1142
+ fetchGitStatus: async () => {
1143
+ set({ editorGitLoading: true });
1144
+ try {
1145
+ const t0 = performance.now();
1146
+ const result = await api.editor.gitStatus();
1147
+ const ipcMs = performance.now() - t0;
1148
+ const t1 = performance.now();
1149
+ set({
1150
+ editorGitFiles: result.files,
1151
+ editorGitBranch: result.branch,
1152
+ editorIsGitRepo: result.isGitRepo,
1153
+ editorGitLoading: false,
1154
+ });
1155
+ const setMs = performance.now() - t1;
1156
+ log.info(
1157
+ `[perf] fetchGitStatus: IPC=${ipcMs.toFixed(1)}ms, set=${setMs.toFixed(1)}ms, files=${result.files.length}`
1158
+ );
1159
+ } catch (error) {
1160
+ log.error('Failed to fetch git status:', error);
1161
+ set({ editorGitLoading: false });
1162
+ }
1163
+ },
1164
+
1165
+ toggleWatcher: async (enable: boolean) => {
1166
+ try {
1167
+ await api.editor.watchDir(enable);
1168
+ set({ editorWatcherEnabled: enable });
1169
+ try {
1170
+ localStorage.setItem('editor-watcher-enabled', String(enable));
1171
+ } catch {
1172
+ // localStorage may not be available
1173
+ }
1174
+ if (enable) {
1175
+ scheduleSyncWatchedFiles(get);
1176
+ scheduleSyncWatchedDirs(get);
1177
+ } else {
1178
+ // Ensure main process stops watching files promptly.
1179
+ lastWatchedFilesKey = '';
1180
+ lastWatchedDirsKey = '';
1181
+ void api.editor.setWatchedFiles([]);
1182
+ void api.editor.setWatchedDirs([]);
1183
+ }
1184
+ } catch (error) {
1185
+ log.error('Failed to toggle watcher:', error);
1186
+ }
1187
+ },
1188
+
1189
+ toggleLineWrap: () => {
1190
+ set((s) => {
1191
+ const next = !s.editorLineWrap;
1192
+ try {
1193
+ localStorage.setItem('editor-line-wrap', String(next));
1194
+ } catch {
1195
+ // localStorage may not be available
1196
+ }
1197
+ return { editorLineWrap: next };
1198
+ });
1199
+ },
1200
+
1201
+ handleExternalFileChange: (event: EditorFileChangeEvent) => {
1202
+ // Avoid per-event logging (can freeze renderer during bursts on large repos).
1203
+ watcherEventCounts[event.type] = (watcherEventCounts[event.type] ?? 0) + 1;
1204
+ if (!watcherEventLogTimer) {
1205
+ watcherEventLogTimer = setTimeout(() => {
1206
+ watcherEventLogTimer = null;
1207
+ const counts = watcherEventCounts;
1208
+ watcherEventCounts = { change: 0, create: 0, delete: 0 };
1209
+ // Keep a single lightweight summary line.
1210
+ log.info(
1211
+ `[perf] editor watcher events (2s): change=${counts.change}, create=${counts.create}, delete=${counts.delete}`
1212
+ );
1213
+ }, 2000);
1214
+ }
1215
+ const { editorOpenTabs, editorProjectPath, editorSaving } = get();
1216
+
1217
+ // Ignore watcher events for files we are currently saving (our own write)
1218
+ if (editorSaving[event.path]) return;
1219
+
1220
+ // Ignore watcher events within cooldown after save
1221
+ // (covers race: save completes → editorSaving cleared → watcher fires late)
1222
+ const lastSaveTime = recentSaveTimestamps.get(event.path);
1223
+ if (lastSaveTime && Date.now() - lastSaveTime < SAVE_COOLDOWN_MS) return;
1224
+
1225
+ // Ignore watcher events within cooldown after move
1226
+ const lastMoveTime = recentMoveTimestamps.get(event.path);
1227
+ if (lastMoveTime && Date.now() - lastMoveTime < MOVE_COOLDOWN_MS) return;
1228
+
1229
+ // Track changes for open files
1230
+ const isOpenFile = editorOpenTabs.some((t) => t.filePath === event.path);
1231
+ if (isOpenFile || event.type === 'delete') {
1232
+ set((s) => ({
1233
+ editorExternalChanges: {
1234
+ ...s.editorExternalChanges,
1235
+ [event.path]: event.type,
1236
+ },
1237
+ }));
1238
+ }
1239
+
1240
+ // Refresh git status on change — throttled to avoid expensive work during bursts.
1241
+ // Main process already caches git status for 5s, but IPC + store updates still cost.
1242
+ if (!gitStatusThrottleTimer) {
1243
+ gitStatusThrottleTimer = setTimeout(() => {
1244
+ gitStatusThrottleTimer = null;
1245
+ void get().fetchGitStatus();
1246
+ }, GIT_STATUS_THROTTLE_MS);
1247
+ }
1248
+
1249
+ // Refresh parent directory in tree for create/delete
1250
+ if (event.type === 'create' || event.type === 'delete') {
1251
+ invalidateQuickOpenCache();
1252
+ const parentDir = event.path.substring(0, lastSeparatorIndex(event.path));
1253
+ if (parentDir && editorProjectPath) {
1254
+ const existing = dirRefreshDebounceTimers.get(parentDir);
1255
+ if (existing) clearTimeout(existing);
1256
+ const timer = setTimeout(() => {
1257
+ dirRefreshDebounceTimers.delete(parentDir);
1258
+ void refreshDirectory(get, set, parentDir);
1259
+ }, DIR_REFRESH_DEBOUNCE_MS);
1260
+ dirRefreshDebounceTimers.set(parentDir, timer);
1261
+ }
1262
+ }
1263
+ },
1264
+
1265
+ clearExternalChange: (filePath: string) => {
1266
+ set((s) => ({
1267
+ editorExternalChanges: omitKey(s.editorExternalChanges, filePath),
1268
+ }));
1269
+ },
1270
+
1271
+ setFileMtime: (filePath: string, mtimeMs: number) => {
1272
+ set((s) => ({
1273
+ editorFileMtimes: { ...s.editorFileMtimes, [filePath]: mtimeMs },
1274
+ }));
1275
+ },
1276
+
1277
+ forceOverwrite: async (filePath: string) => {
1278
+ const content = editorBridge.getContent(filePath);
1279
+ if (content === null) {
1280
+ log.error('forceOverwrite: no content available for', filePath);
1281
+ return;
1282
+ }
1283
+
1284
+ set((s) => ({
1285
+ editorSaving: { ...s.editorSaving, [filePath]: true },
1286
+ editorConflictFile: null,
1287
+ }));
1288
+
1289
+ try {
1290
+ // No baselineMtimeMs → skip conflict check on backend
1291
+ const result = await api.editor.writeFile(filePath, content);
1292
+
1293
+ // Record save timestamp BEFORE clearing editorSaving (watcher race guard)
1294
+ recentSaveTimestamps.set(filePath, Date.now());
1295
+
1296
+ set((s) => ({
1297
+ editorModifiedFiles: omitKey(s.editorModifiedFiles, filePath),
1298
+ editorSaving: omitKey(s.editorSaving, filePath),
1299
+ editorFileMtimes: { ...s.editorFileMtimes, [filePath]: result.mtimeMs },
1300
+ editorExternalChanges: omitKey(s.editorExternalChanges, filePath),
1301
+ }));
1302
+
1303
+ try {
1304
+ localStorage.removeItem(`editor-draft:${filePath}`);
1305
+ } catch {
1306
+ // localStorage may not be available
1307
+ }
1308
+ } catch (error) {
1309
+ const message = error instanceof Error ? error.message : String(error);
1310
+ log.error('Failed to force overwrite:', message);
1311
+ set((s) => ({
1312
+ editorSaving: omitKey(s.editorSaving, filePath),
1313
+ editorSaveError: { ...s.editorSaveError, [filePath]: message },
1314
+ }));
1315
+ }
1316
+ },
1317
+
1318
+ resolveConflict: () => {
1319
+ set({ editorConflictFile: null });
1320
+ },
1321
+ });
1322
+
1323
+ // =============================================================================
1324
+ // Helpers
1325
+ // =============================================================================
1326
+
1327
+ /**
1328
+ * Refresh a directory's children in the file tree via IPC readDir + merge.
1329
+ */
1330
+ async function refreshDirectory(
1331
+ get: () => AppState,
1332
+ set: (partial: Partial<AppState>) => void,
1333
+ dirPath: string
1334
+ ): Promise<void> {
1335
+ try {
1336
+ const t0 = performance.now();
1337
+ const result = await api.editor.readDir(dirPath);
1338
+ log.info(
1339
+ `[perf] refreshDirectory: IPC=${(performance.now() - t0).toFixed(1)}ms, entries=${result.entries.length}, dir=${getBasename(dirPath)}`
1340
+ );
1341
+ const currentTree = get().editorFileTree;
1342
+ if (!currentTree) return;
1343
+
1344
+ const projectPath = get().editorProjectPath;
1345
+ if (dirPath === projectPath) {
1346
+ // Root refresh — tree IS the root's children, so preserve expanded subtrees
1347
+ // by merging new entries with existing children data
1348
+ const existingByPath = new Map<string, FileTreeEntry>();
1349
+ for (const entry of currentTree) {
1350
+ existingByPath.set(entry.path, entry);
1351
+ }
1352
+ const merged = result.entries.map((entry) => {
1353
+ const existing = existingByPath.get(entry.path);
1354
+ // Preserve expanded subtree children for directories that still exist
1355
+ if (existing?.children && entry.type === 'directory') {
1356
+ return { ...entry, children: existing.children };
1357
+ }
1358
+ return entry;
1359
+ });
1360
+ set({ editorFileTree: merged });
1361
+ } else {
1362
+ const updatedTree = mergeChildrenIntoTree(currentTree, dirPath, result.entries);
1363
+ set({ editorFileTree: updatedTree });
1364
+ }
1365
+ } catch (error) {
1366
+ log.error('Failed to refresh directory:', error);
1367
+ }
1368
+ }
1369
+
1370
+ /**
1371
+ * Remap a single path: if it matches oldPath exactly or is a child of oldPath,
1372
+ * replace the prefix with newPath.
1373
+ */
1374
+ function remapPath(p: string, oldPath: string, newPath: string): string {
1375
+ const oldParts = splitPath(oldPath);
1376
+ const pParts = splitPath(p);
1377
+ if (oldParts.length === 0) return p;
1378
+
1379
+ const win = isWindowsishPath(oldPath) || isWindowsishPath(p) || isWindowsishPath(newPath);
1380
+ const eq = (a: string, b: string): boolean =>
1381
+ win ? a.toLowerCase() === b.toLowerCase() : a === b;
1382
+
1383
+ const matchesPrefix =
1384
+ pParts.length >= oldParts.length && oldParts.every((seg, i) => eq(seg, pParts[i]));
1385
+ if (!matchesPrefix) return p;
1386
+
1387
+ const suffix = pParts.slice(oldParts.length);
1388
+ return suffix.length > 0 ? joinPath(newPath, ...suffix) : newPath;
1389
+ }
1390
+
1391
+ /**
1392
+ * Reverse remap: given a potentially-remapped path, recover the original path.
1393
+ * Used to identify which bridge caches to remap.
1394
+ */
1395
+ function reverseRemapPath(p: string, oldPath: string, newPath: string): string {
1396
+ const newParts = splitPath(newPath);
1397
+ const pParts = splitPath(p);
1398
+ if (newParts.length === 0) return p;
1399
+
1400
+ const win = isWindowsishPath(oldPath) || isWindowsishPath(p) || isWindowsishPath(newPath);
1401
+ const eq = (a: string, b: string): boolean =>
1402
+ win ? a.toLowerCase() === b.toLowerCase() : a === b;
1403
+
1404
+ const matchesPrefix =
1405
+ pParts.length >= newParts.length && newParts.every((seg, i) => eq(seg, pParts[i]));
1406
+ if (!matchesPrefix) return p;
1407
+
1408
+ const suffix = pParts.slice(newParts.length);
1409
+ return suffix.length > 0 ? joinPath(oldPath, ...suffix) : oldPath;
1410
+ }
1411
+
1412
+ /**
1413
+ * Remap all keys in a Record that match or are children of oldPath.
1414
+ */
1415
+ function remapRecord<V>(
1416
+ record: Record<string, V>,
1417
+ oldPath: string,
1418
+ newPath: string
1419
+ ): Record<string, V> {
1420
+ const result: Record<string, V> = {};
1421
+ let changed = false;
1422
+ for (const [key, value] of Object.entries(record)) {
1423
+ const remapped = remapPath(key, oldPath, newPath);
1424
+ if (remapped !== key) changed = true;
1425
+ result[remapped] = value;
1426
+ }
1427
+ return changed ? result : record;
1428
+ }
1429
+
1430
+ /**
1431
+ * Recursively merge children into the tree at the matching directory path.
1432
+ * Returns the same array reference if nothing changed — preserves React.memo equality.
1433
+ */
1434
+ function mergeChildrenIntoTree(
1435
+ tree: FileTreeEntry[],
1436
+ targetPath: string,
1437
+ children: FileTreeEntry[]
1438
+ ): FileTreeEntry[] {
1439
+ let changed = false;
1440
+ const result = tree.map((entry) => {
1441
+ if (entry.path === targetPath && entry.type === 'directory') {
1442
+ changed = true;
1443
+ return { ...entry, children };
1444
+ }
1445
+ if (entry.children) {
1446
+ const updated = mergeChildrenIntoTree(entry.children, targetPath, children);
1447
+ if (updated !== entry.children) {
1448
+ changed = true;
1449
+ return { ...entry, children: updated };
1450
+ }
1451
+ }
1452
+ return entry;
1453
+ });
1454
+ return changed ? result : tree;
1455
+ }