specrails-desktop 2.18.2 → 2.19.0

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 (346) hide show
  1. package/README.md +1 -1
  2. package/client/dist/assets/{ActivityFeedPage-mGnv5SOe.js → ActivityFeedPage-C0_k6n5T.js} +1 -1
  3. package/client/dist/assets/AgentBrowserCapture-NHUCEnTh.js +1 -0
  4. package/client/dist/assets/AgentModeCodePane-jddXrhPY.js +2 -0
  5. package/client/dist/assets/AgentsPage-BPjf6bId.js +86 -0
  6. package/client/dist/assets/AnalyticsPage-BaJjj5mO.js +1 -0
  7. package/client/dist/assets/{BarChart-BqHUOnl9.js → BarChart-HPiao--v.js} +1 -1
  8. package/client/dist/assets/CodePage-CAD4KfkC.js +2 -0
  9. package/client/dist/assets/{DesktopAnalyticsPage-CR-m97Ui.js → DesktopAnalyticsPage-B2RBxIvj.js} +1 -1
  10. package/client/dist/assets/DocsDialog-BJDvUEGj.js +11 -0
  11. package/client/dist/assets/DocsPage-B6H-DBs8.js +11 -0
  12. package/client/dist/assets/{ExportDropdown-C-o1Q4ef.js → ExportDropdown-CYMcbLTD.js} +1 -1
  13. package/client/dist/assets/IntegrationsPage-Dx_UpQVk.js +3 -0
  14. package/client/dist/assets/JobDetailPage-BlTnsRAN.js +16 -0
  15. package/client/dist/assets/JobsPage-EB0PbXOk.js +1 -0
  16. package/client/dist/assets/LoopBuilderPage-DfWfhhJY.js +7 -0
  17. package/client/dist/assets/LoopsPage-DCVATTv5.js +1 -0
  18. package/client/dist/assets/{addspec-B9dY_F5T.js → addspec-BXV6sx5l.js} +1 -1
  19. package/client/dist/assets/{addspec-tbxr3sHa.js → addspec-CS895X6A.js} +1 -1
  20. package/client/dist/assets/{addspec-W0r16BOC2.js → addspec-CXfEOrD42.js} +1 -1
  21. package/client/dist/assets/{addspec-D43TlMVp2.js → addspec-D28kw9ow2.js} +1 -1
  22. package/client/dist/assets/{addspec-B9nhMikb.js → addspec-D39EEg02.js} +1 -1
  23. package/client/dist/assets/{addspec-DfgIZ5lO.js → addspec-DC1tgsFG.js} +1 -1
  24. package/client/dist/assets/{addspec-BtNaJHu2.js → addspec-DcHALPEp.js} +1 -1
  25. package/client/dist/assets/{addspec-BloGn_ey.js → addspec-DxW6oPsk.js} +1 -1
  26. package/client/dist/assets/{agent-Bm2tPrdr.js → agent-B5Ycau5M.js} +1 -1
  27. package/client/dist/assets/{agent-DMUrz9ZS2.js → agent-BEz08l4_2.js} +1 -1
  28. package/client/dist/assets/{agent-C4bO1l_v.js → agent-BIX51CEA.js} +1 -1
  29. package/client/dist/assets/{agent-9WdSxPU5.js → agent-Bc9Kwf65.js} +1 -1
  30. package/client/dist/assets/{agent-pO8x4jbR.js → agent-BiM7puyn.js} +1 -1
  31. package/client/dist/assets/{agent-CgKsh88Z2.js → agent-C0kYSv3m2.js} +1 -1
  32. package/client/dist/assets/agent-CCzWlfqs.js +1 -0
  33. package/client/dist/assets/{agent-ASIyVcg0.js → agent-DiCSo31C.js} +1 -1
  34. package/client/dist/assets/{agents-DfGvYtpq2.js → agents-BFrHy5d-2.js} +1 -1
  35. package/client/dist/assets/{agents-Dli56gr82.js → agents-BFy2Kq_k2.js} +1 -1
  36. package/client/dist/assets/{agents-C07al8rI.js → agents-BXDCFJQa.js} +1 -1
  37. package/client/dist/assets/{agents-BAxAwHy-.js → agents-BySH0Gvw.js} +1 -1
  38. package/client/dist/assets/{agents-Eqw_AQ6H.js → agents-CTdwacTO.js} +1 -1
  39. package/client/dist/assets/{agents-BjT_xLjv.js → agents-Cev2oFfX.js} +1 -1
  40. package/client/dist/assets/{agents-BUYBb825.js → agents-CsDEv4Fj.js} +1 -1
  41. package/client/dist/assets/{agents-52t8NVpS.js → agents-z0kA-y0Q.js} +1 -1
  42. package/client/dist/assets/dashboard-Bl2_Yjs6.js +1 -0
  43. package/client/dist/assets/dashboard-C_vS9RaM2.js +1 -0
  44. package/client/dist/assets/dashboard-CcEpALV_.js +1 -0
  45. package/client/dist/assets/dashboard-CfltY66K.js +1 -0
  46. package/client/dist/assets/dashboard-CoED_41r.js +1 -0
  47. package/client/dist/assets/dashboard-DLeC5ziO.js +1 -0
  48. package/client/dist/assets/dashboard-DxEPkALo2.js +1 -0
  49. package/client/dist/assets/dashboard-MgDVmaYS2.js +1 -0
  50. package/client/dist/assets/{dist-js-CUegfF6k.js → dist-js-ClEfL6Bo.js} +1 -1
  51. package/client/dist/assets/{dist-js-BHhtSerX.js → dist-js-b2_z7zAi.js} +1 -1
  52. package/client/dist/assets/index-9yNUBwEi.css +2 -0
  53. package/client/dist/assets/{index-B7vB20Jw.js → index-BeujshsU.js} +57 -57
  54. package/client/dist/assets/{lib-PQoHlR5u.js → lib-CfphudAm.js} +1 -1
  55. package/client/dist/assets/loops-BEUiCzEI.js +1 -0
  56. package/client/dist/assets/loops-BlrQiK122.js +1 -0
  57. package/client/dist/assets/loops-Bs0ectZ32.js +1 -0
  58. package/client/dist/assets/loops-CLZV8HHY2.js +1 -0
  59. package/client/dist/assets/loops-CQuuMlyZ.js +1 -0
  60. package/client/dist/assets/loops-DuuWQWTI.js +1 -0
  61. package/client/dist/assets/loops-KOTryzk5.js +1 -0
  62. package/client/dist/assets/loops-t1mUZtf0.js +1 -0
  63. package/client/dist/assets/nav--1ErXZrS.js +1 -0
  64. package/client/dist/assets/nav-18nqSJ8h.js +1 -0
  65. package/client/dist/assets/nav-BuonHb17.js +1 -0
  66. package/client/dist/assets/nav-C-sCoOLY2.js +1 -0
  67. package/client/dist/assets/nav-ClXz_3Ic2.js +1 -0
  68. package/client/dist/assets/nav-CtqgSQeE2.js +1 -0
  69. package/client/dist/assets/nav-S3YvCaVv.js +1 -0
  70. package/client/dist/assets/nav-czg8NkN-.js +1 -0
  71. package/client/dist/assets/settings-BOZBX-Vq.js +1 -0
  72. package/client/dist/assets/settings-CX29QcQW.js +1 -0
  73. package/client/dist/assets/settings-CfOWHPzc2.js +1 -0
  74. package/client/dist/assets/settings-DaXhxNug2.js +1 -0
  75. package/client/dist/assets/settings-DmyDpuzu.js +1 -0
  76. package/client/dist/assets/settings-DwPRLcxr2.js +1 -0
  77. package/client/dist/assets/settings-MjYVJvDE.js +1 -0
  78. package/client/dist/assets/settings-zJFdTzKE.js +1 -0
  79. package/client/dist/assets/{setup-BYoDYoPY2.js → setup-BmXfxlzA2.js} +1 -1
  80. package/client/dist/assets/{setup-faMApo0U2.js → setup-BxVydxbl2.js} +1 -1
  81. package/client/dist/assets/{setup-C5UbM0RP.js → setup-DIRZ1Lf-.js} +1 -1
  82. package/client/dist/assets/{setup-Bf9eLKxU.js → setup-DwbKSnKQ.js} +1 -1
  83. package/client/dist/assets/{setup-JFkWWZnR.js → setup-Dx13pESZ.js} +1 -1
  84. package/client/dist/assets/{setup-CqGrWb6G.js → setup-G4ZvDKxX.js} +1 -1
  85. package/client/dist/assets/{setup-tloaTobG2.js → setup-SLAi4xxQ2.js} +1 -1
  86. package/client/dist/assets/{setup-4us-Bz3H.js → setup-Z01j3st4.js} +1 -1
  87. package/client/dist/assets/{upload-PnJDPHZH.js → upload-Ox9jUq1X.js} +1 -1
  88. package/client/dist/assets/{value-BTDefcOV.js → value-BeQVX5gN.js} +1 -1
  89. package/client/dist/index.html +10 -10
  90. package/docs/codex.md +5 -5
  91. package/docs/customizing.md +4 -4
  92. package/docs/gemini.md +2 -2
  93. package/docs/getting-started.md +1 -1
  94. package/docs/guide/de/integrations/1-ai-providers.md +2 -2
  95. package/docs/guide/de/integrations/5-mcp-server.md +20 -1
  96. package/docs/guide/de/pipeline/1-rails-and-jobs.md +4 -4
  97. package/docs/guide/de/pipeline/2-the-job-detail-view.md +1 -1
  98. package/docs/guide/de/pipeline/4-picking-an-engine-per-rail.md +2 -2
  99. package/docs/guide/de/pipeline/5-the-loop-builder.md +2 -2
  100. package/docs/guide/en/integrations/1-ai-providers.md +2 -2
  101. package/docs/guide/en/integrations/5-mcp-server.md +20 -1
  102. package/docs/guide/en/pipeline/1-rails-and-jobs.md +4 -4
  103. package/docs/guide/en/pipeline/2-the-job-detail-view.md +1 -1
  104. package/docs/guide/en/pipeline/4-picking-an-engine-per-rail.md +2 -2
  105. package/docs/guide/en/pipeline/5-the-loop-builder.md +2 -2
  106. package/docs/guide/es/integrations/1-ai-providers.md +2 -2
  107. package/docs/guide/es/integrations/5-mcp-server.md +20 -1
  108. package/docs/guide/es/pipeline/1-rails-and-jobs.md +4 -4
  109. package/docs/guide/es/pipeline/2-the-job-detail-view.md +1 -1
  110. package/docs/guide/es/pipeline/4-picking-an-engine-per-rail.md +2 -2
  111. package/docs/guide/es/pipeline/5-the-loop-builder.md +2 -2
  112. package/docs/guide/fr/integrations/1-ai-providers.md +2 -2
  113. package/docs/guide/fr/integrations/5-mcp-server.md +20 -1
  114. package/docs/guide/fr/pipeline/1-rails-and-jobs.md +4 -4
  115. package/docs/guide/fr/pipeline/2-the-job-detail-view.md +1 -1
  116. package/docs/guide/fr/pipeline/4-picking-an-engine-per-rail.md +2 -2
  117. package/docs/guide/fr/pipeline/5-the-loop-builder.md +2 -2
  118. package/docs/guide/it/integrations/1-ai-providers.md +2 -2
  119. package/docs/guide/it/integrations/5-mcp-server.md +20 -1
  120. package/docs/guide/it/pipeline/1-rails-and-jobs.md +4 -4
  121. package/docs/guide/it/pipeline/2-the-job-detail-view.md +1 -1
  122. package/docs/guide/it/pipeline/4-picking-an-engine-per-rail.md +2 -2
  123. package/docs/guide/it/pipeline/5-the-loop-builder.md +2 -2
  124. package/docs/guide/ja/integrations/1-ai-providers.md +2 -2
  125. package/docs/guide/ja/integrations/5-mcp-server.md +20 -1
  126. package/docs/guide/ja/pipeline/1-rails-and-jobs.md +4 -4
  127. package/docs/guide/ja/pipeline/2-the-job-detail-view.md +1 -1
  128. package/docs/guide/ja/pipeline/4-picking-an-engine-per-rail.md +2 -2
  129. package/docs/guide/ja/pipeline/5-the-loop-builder.md +2 -2
  130. package/docs/guide/pt/integrations/1-ai-providers.md +2 -2
  131. package/docs/guide/pt/integrations/5-mcp-server.md +20 -1
  132. package/docs/guide/pt/pipeline/1-rails-and-jobs.md +4 -4
  133. package/docs/guide/pt/pipeline/2-the-job-detail-view.md +1 -1
  134. package/docs/guide/pt/pipeline/4-picking-an-engine-per-rail.md +2 -2
  135. package/docs/guide/pt/pipeline/5-the-loop-builder.md +2 -2
  136. package/docs/guide/zh/integrations/1-ai-providers.md +2 -2
  137. package/docs/guide/zh/integrations/5-mcp-server.md +20 -1
  138. package/docs/guide/zh/pipeline/1-rails-and-jobs.md +4 -4
  139. package/docs/guide/zh/pipeline/2-the-job-detail-view.md +1 -1
  140. package/docs/guide/zh/pipeline/4-picking-an-engine-per-rail.md +2 -2
  141. package/docs/guide/zh/pipeline/5-the-loop-builder.md +2 -2
  142. package/docs/internals/switch-to-agent-mode-design.md +301 -0
  143. package/docs/internals/visual-design-audit.md +184 -0
  144. package/docs/mcp.md +26 -1
  145. package/docs/running-pipelines.md +10 -10
  146. package/docs/tracking-cost.md +1 -1
  147. package/package.json +1 -1
  148. package/server/dist/agent-chat-manager.js +107 -33
  149. package/server/dist/agent-chat-router.js +170 -8
  150. package/server/dist/agent-cwd-manager.js +2 -47
  151. package/server/dist/agent-operator-prompt.js +277 -0
  152. package/server/dist/agent-store.js +27 -5
  153. package/server/dist/attachment-manager.js +138 -0
  154. package/server/dist/desktop-db.js +15 -0
  155. package/server/dist/loop-factory.js +12 -2
  156. package/server/dist/mcp/guide.js +186 -51
  157. package/server/dist/mcp/tools/analytics.js +1 -1
  158. package/server/dist/mcp/tools/app-settings.js +45 -5
  159. package/server/dist/mcp/tools/chat.js +9 -2
  160. package/server/dist/mcp/tools/code.js +3 -2
  161. package/server/dist/mcp/tools/jobs.js +18 -6
  162. package/server/dist/mcp/tools/loops.js +57 -5
  163. package/server/dist/mcp/tools/meta.js +43 -3
  164. package/server/dist/mcp/tools/rails.js +6 -6
  165. package/server/dist/mcp/tools/setup.js +2 -1
  166. package/server/dist/mcp/tools/specs.js +60 -38
  167. package/server/dist/mcp/tools/types.js +17 -8
  168. package/server/dist/mcp/tools/watch.js +71 -8
  169. package/server/dist/project-router-helpers.js +1 -1
  170. package/server/dist/project-router-tickets.js +43 -4
  171. package/server/dist/providers/claude-adapter.js +6 -0
  172. package/server/dist/providers/codex-adapter.js +10 -0
  173. package/server/dist/providers/gemini-adapter.js +3 -0
  174. package/server/dist/rails-router.js +8 -17
  175. package/server/dist/spec-models.js +1 -0
  176. package/client/dist/assets/AgentsPage-BH1isg5j.js +0 -86
  177. package/client/dist/assets/AnalyticsPage-ByyshYRI.js +0 -1
  178. package/client/dist/assets/CodePage-CU7T_HYs.js +0 -2
  179. package/client/dist/assets/DocsDialog-CEmZp_8V.js +0 -11
  180. package/client/dist/assets/DocsPage-DYbAQ1AT.js +0 -11
  181. package/client/dist/assets/IntegrationsPage-C6fKdwVH.js +0 -3
  182. package/client/dist/assets/JobDetailPage-CKfIfBgE.js +0 -16
  183. package/client/dist/assets/JobsPage-nBng0riZ.js +0 -1
  184. package/client/dist/assets/LoopBuilderPage-Co_TYqYk.js +0 -7
  185. package/client/dist/assets/LoopsPage-a2hd37Ya.js +0 -1
  186. package/client/dist/assets/agent-Dwkz5mPS.js +0 -1
  187. package/client/dist/assets/dashboard-BTiH361F.js +0 -1
  188. package/client/dist/assets/dashboard-COSm_14E.js +0 -1
  189. package/client/dist/assets/dashboard-CfOs70FN2.js +0 -1
  190. package/client/dist/assets/dashboard-Cyzp4ox9.js +0 -1
  191. package/client/dist/assets/dashboard-CzHDp14_2.js +0 -1
  192. package/client/dist/assets/dashboard-D6Woy2B5.js +0 -1
  193. package/client/dist/assets/dashboard-DwbH4oVH.js +0 -1
  194. package/client/dist/assets/dashboard-cWhU3krA2.js +0 -1
  195. package/client/dist/assets/index-B0FxQALR.css +0 -2
  196. package/client/dist/assets/loops-BDhEfnx82.js +0 -1
  197. package/client/dist/assets/loops-BM5QggwH.js +0 -1
  198. package/client/dist/assets/loops-CPjLAAxj.js +0 -1
  199. package/client/dist/assets/loops-D8Iu6Jss.js +0 -1
  200. package/client/dist/assets/loops-DKnk79TB2.js +0 -1
  201. package/client/dist/assets/loops-DLW0aFMm.js +0 -1
  202. package/client/dist/assets/loops-Dcv3dFy_2.js +0 -1
  203. package/client/dist/assets/loops-y7wFRx6M.js +0 -1
  204. package/client/dist/assets/nav-1hj2izqB2.js +0 -1
  205. package/client/dist/assets/nav-BBUKMAu-2.js +0 -1
  206. package/client/dist/assets/nav-CLqkP-26.js +0 -1
  207. package/client/dist/assets/nav-Cv46L837.js +0 -1
  208. package/client/dist/assets/nav-DE3yWQw6.js +0 -1
  209. package/client/dist/assets/nav-DgA-rjLk2.js +0 -1
  210. package/client/dist/assets/nav-EG2pr6vN.js +0 -1
  211. package/client/dist/assets/nav-r3mIJVfU.js +0 -1
  212. package/client/dist/assets/settings-BKmORxlB2.js +0 -1
  213. package/client/dist/assets/settings-BVv2JgQ8.js +0 -1
  214. package/client/dist/assets/settings-Bghm3pAV.js +0 -1
  215. package/client/dist/assets/settings-BuC_aplX.js +0 -1
  216. package/client/dist/assets/settings-Bw_Qk8E32.js +0 -1
  217. package/client/dist/assets/settings-CAKgJv7p.js +0 -1
  218. package/client/dist/assets/settings-CEUgk1Ck.js +0 -1
  219. package/client/dist/assets/settings-C_PDE20s2.js +0 -1
  220. /package/client/dist/assets/{activity-Uj576ckT.js → activity-BlvN5rhP.js} +0 -0
  221. /package/client/dist/assets/{activity-w1qMFwxh.js → activity-C5SeKbom.js} +0 -0
  222. /package/client/dist/assets/{activity-BySXFT2J2.js → activity-DIJexk6v2.js} +0 -0
  223. /package/client/dist/assets/{activity-B82z3Pna.js → activity-DLdwUYv7.js} +0 -0
  224. /package/client/dist/assets/{activity-BVpZrs0J.js → activity-DOKV_Wyf.js} +0 -0
  225. /package/client/dist/assets/{activity-BId0pwuS2.js → activity-DVuiy8TI2.js} +0 -0
  226. /package/client/dist/assets/{activity-DIWnV0Pl.js → activity-Dsf3J7iW.js} +0 -0
  227. /package/client/dist/assets/{agentstudio-C7xxqVWn.js → agentstudio-B8H2H4Db2.js} +0 -0
  228. /package/client/dist/assets/{agentstudio-D-UoNAIA.js → agentstudio-B9zthaTJ.js} +0 -0
  229. /package/client/dist/assets/{agentstudio-0o3XKrcy.js → agentstudio-BDMpU01s.js} +0 -0
  230. /package/client/dist/assets/{agentstudio-Bu8m5jDw.js → agentstudio-CSZo7p8K.js} +0 -0
  231. /package/client/dist/assets/{agentstudio-ObTpVbBP.js → agentstudio-CdD6J23W.js} +0 -0
  232. /package/client/dist/assets/{agentstudio-PzFB3vfh2.js → agentstudio-D80fZu_M2.js} +0 -0
  233. /package/client/dist/assets/{agentstudio-OJjjz6Uf2.js → agentstudio-EuKglkaF2.js} +0 -0
  234. /package/client/dist/assets/{aiedit-DRrNiaB4.js → aiedit-B_VOTUo42.js} +0 -0
  235. /package/client/dist/assets/{aiedit-sP_iao2j.js → aiedit-Cxoe_0wv.js} +0 -0
  236. /package/client/dist/assets/{aiedit-DAM_WRAl2.js → aiedit-D59sPapq2.js} +0 -0
  237. /package/client/dist/assets/{aiedit-BaLZalLa.js → aiedit-DX3BBpXF.js} +0 -0
  238. /package/client/dist/assets/{aiedit-BIQqv2XM2.js → aiedit-GYOYWCHJ2.js} +0 -0
  239. /package/client/dist/assets/{aiedit-BjlUzasF.js → aiedit-TON-4N9S.js} +0 -0
  240. /package/client/dist/assets/{aiedit-BmDufyGU.js → aiedit-ybQmdsST.js} +0 -0
  241. /package/client/dist/assets/{analytics-MVJRjcY52.js → analytics-B2jjXLCP2.js} +0 -0
  242. /package/client/dist/assets/{analytics-C4z_NgJZ.js → analytics-BTkXLTfa.js} +0 -0
  243. /package/client/dist/assets/{analytics-DL2vJCbq.js → analytics-BotqRU_R.js} +0 -0
  244. /package/client/dist/assets/{analytics-e1mkNTgy2.js → analytics-Ck3vl2Qo2.js} +0 -0
  245. /package/client/dist/assets/{analytics-Ds_PNxtO2.js → analytics-CqMJQb7N.js} +0 -0
  246. /package/client/dist/assets/{analytics-B_rxNC9X.js → analytics-DGOoMkaZ.js} +0 -0
  247. /package/client/dist/assets/{analytics-BDqqCzN9.js → analytics-DL7uwNAI2.js} +0 -0
  248. /package/client/dist/assets/{attachments-BP0Ac1yt.js → attachments-7w-BR2V2.js} +0 -0
  249. /package/client/dist/assets/{attachments-CPBVeHX72.js → attachments-B5QX1Xfa2.js} +0 -0
  250. /package/client/dist/assets/{attachments-Db7JfA8a.js → attachments-BcE7kZG-2.js} +0 -0
  251. /package/client/dist/assets/{attachments-CJaO1vCH.js → attachments-BsJJXFzT.js} +0 -0
  252. /package/client/dist/assets/{attachments-8QCKHCEF2.js → attachments-Cbh4AwjP.js} +0 -0
  253. /package/client/dist/assets/{attachments-CV2uaTGE.js → attachments-D0fGlFve.js} +0 -0
  254. /package/client/dist/assets/{attachments-CaOjdN332.js → attachments-D3DJpd3e2.js} +0 -0
  255. /package/client/dist/assets/{browser-C9rUUxo4.js → browser-9GR11iTi.js} +0 -0
  256. /package/client/dist/assets/{browser-CEqx11iy.js → browser-BOxg0p3n.js} +0 -0
  257. /package/client/dist/assets/{browser-543jbxE4.js → browser-BSh8xUWV.js} +0 -0
  258. /package/client/dist/assets/{browser-CLC0etB_2.js → browser-CaV3BJNY2.js} +0 -0
  259. /package/client/dist/assets/{browser-0xeW8fUa2.js → browser-CmWGMRGf2.js} +0 -0
  260. /package/client/dist/assets/{browser-ExmELH702.js → browser-D6wbpBOL2.js} +0 -0
  261. /package/client/dist/assets/{browser-DyG2l9Wa.js → browser-dsgDU1HB.js} +0 -0
  262. /package/client/dist/assets/{chat-DagUyoVA.js → chat-B90iLDI4.js} +0 -0
  263. /package/client/dist/assets/{chat-Cu6F3d6g2.js → chat-CAo0jCnj2.js} +0 -0
  264. /package/client/dist/assets/{chat-CN8BMAMc.js → chat-CW54IP3u.js} +0 -0
  265. /package/client/dist/assets/{chat-NQPCBQo72.js → chat-CYHUUW0p2.js} +0 -0
  266. /package/client/dist/assets/{chat-CCytD3P8.js → chat-D1PXX_p2.js} +0 -0
  267. /package/client/dist/assets/{chat-IjutwlBg.js → chat-DhpcWjOf.js} +0 -0
  268. /package/client/dist/assets/{chat-vVpwkOp52.js → chat-cFG-UfRA2.js} +0 -0
  269. /package/client/dist/assets/{code-Dhb6jbAU.js → code--qUk_yn-.js} +0 -0
  270. /package/client/dist/assets/{code-Ce-nBIwj.js → code-B1aOMCVV.js} +0 -0
  271. /package/client/dist/assets/{code-BaG05zK62.js → code-B5UrmjaE2.js} +0 -0
  272. /package/client/dist/assets/{code-Dap54vOb.js → code-CjBHU0nw.js} +0 -0
  273. /package/client/dist/assets/{code-_NOCo-Ol2.js → code-Cjv4_HkK2.js} +0 -0
  274. /package/client/dist/assets/{code-32l6Naxu2.js → code-D7GLAjrz2.js} +0 -0
  275. /package/client/dist/assets/{code-C5cibF2F.js → code-DZKjfzBN.js} +0 -0
  276. /package/client/dist/assets/{commands-Cc2WpuXa.js → commands-B9XaopQC.js} +0 -0
  277. /package/client/dist/assets/{commands-k9wbNYoi.js → commands-BCx1uDIE.js} +0 -0
  278. /package/client/dist/assets/{commands-CM99Upsz2.js → commands-Bhw9Hqsa2.js} +0 -0
  279. /package/client/dist/assets/{commands-Bc8y9eNo2.js → commands-D0RnooQF2.js} +0 -0
  280. /package/client/dist/assets/{commands-Bl3-KVfj.js → commands-DuaQT-it.js} +0 -0
  281. /package/client/dist/assets/{commands-DASXJD-l.js → commands-MqNuGGO4.js} +0 -0
  282. /package/client/dist/assets/{commands-C5WQ9blx2.js → commands-oB9q2vy_2.js} +0 -0
  283. /package/client/dist/assets/{common-CHARW6MZ2.js → common-BXzqeuiC2.js} +0 -0
  284. /package/client/dist/assets/{common-0aY9HQBP.js → common-BaJ6MF7T.js} +0 -0
  285. /package/client/dist/assets/{common-CQCJzAQq2.js → common-C-jaOdxz2.js} +0 -0
  286. /package/client/dist/assets/{common-DNhhqjlJ.js → common-CKjy-R8-.js} +0 -0
  287. /package/client/dist/assets/{common-YdyZtmaI2.js → common-CRd-Ge5u2.js} +0 -0
  288. /package/client/dist/assets/{common-5jScBJqt.js → common-DPGR3Tfo.js} +0 -0
  289. /package/client/dist/assets/{common-BdaRciTu.js → common-wEB9RBdf.js} +0 -0
  290. /package/client/dist/assets/{explore-ChtxIiSM.js → explore-B_79NLR5.js} +0 -0
  291. /package/client/dist/assets/{explore-BzdMEpuf.js → explore-D-htKul_.js} +0 -0
  292. /package/client/dist/assets/{explore-B82VMUJ_.js → explore-D5b2jgvR.js} +0 -0
  293. /package/client/dist/assets/{explore-DfcUfiPw2.js → explore-Dvp-5qOw2.js} +0 -0
  294. /package/client/dist/assets/{explore-CbW0381i2.js → explore-FaKfWCr72.js} +0 -0
  295. /package/client/dist/assets/{explore-BGz1rvQE.js → explore-IS8VyaSZ.js} +0 -0
  296. /package/client/dist/assets/{explore-yRF7wFSp2.js → explore-OZhWV0Eb2.js} +0 -0
  297. /package/client/dist/assets/{format-command-D96da4fZ.js → format-command-CHImQv6W.js} +0 -0
  298. /package/client/dist/assets/{integrations-CnrX7vqw2.js → integrations-B97cGWfo2.js} +0 -0
  299. /package/client/dist/assets/{integrations-BJvrlcc7.js → integrations-Bp3leAh3.js} +0 -0
  300. /package/client/dist/assets/{integrations-CPKYkG36.js → integrations-C3Ma3GdM.js} +0 -0
  301. /package/client/dist/assets/{integrations-BA5pM05t.js → integrations-C5MUA-0-.js} +0 -0
  302. /package/client/dist/assets/{integrations-B0OBPU9_.js → integrations-CiQbCn_W.js} +0 -0
  303. /package/client/dist/assets/{integrations-DeJFRyXz2.js → integrations-Du_0rDfi2.js} +0 -0
  304. /package/client/dist/assets/{integrations-DBou159M2.js → integrations-t4X5UVcx2.js} +0 -0
  305. /package/client/dist/assets/{jira-mi37H4Kt.js → jira-B1XaR6fj.js} +0 -0
  306. /package/client/dist/assets/{jira-Srx2P5zS.js → jira-BCHx9zUF.js} +0 -0
  307. /package/client/dist/assets/{jira-DeotPx6P2.js → jira-BqXyykbt2.js} +0 -0
  308. /package/client/dist/assets/{jira-BwLBgmfI2.js → jira-BzFvFn7t2.js} +0 -0
  309. /package/client/dist/assets/{jira-BDgxAFTF2.js → jira-D6IDTIsc2.js} +0 -0
  310. /package/client/dist/assets/{jira-NwlUCcQD.js → jira-DSZ5xFRl.js} +0 -0
  311. /package/client/dist/assets/{jira-D3GGzXOI.js → jira-DdEKW9Pu.js} +0 -0
  312. /package/client/dist/assets/{jobs-D8adm2BX.js → jobs-BX9dP3Qn.js} +0 -0
  313. /package/client/dist/assets/{jobs-B56vQEkh2.js → jobs-BgHEgCWk2.js} +0 -0
  314. /package/client/dist/assets/{jobs-K_Y8KAZ2.js → jobs-CU-khJTt.js} +0 -0
  315. /package/client/dist/assets/{jobs-BYIk6Tg_.js → jobs-Ce0SE81t.js} +0 -0
  316. /package/client/dist/assets/{jobs-C-lM-iax2.js → jobs-CeOcGHd_2.js} +0 -0
  317. /package/client/dist/assets/{jobs-CYdevGgT.js → jobs-Ct5v2p6T.js} +0 -0
  318. /package/client/dist/assets/{jobs-OwM0ZWhI2.js → jobs-m7L0K9uC2.js} +0 -0
  319. /package/client/dist/assets/{mcp-D3E4_Mmk2.js → mcp-4QbfLPa4.js} +0 -0
  320. /package/client/dist/assets/{mcp-B6nwJJmR.js → mcp-CXy61khH.js} +0 -0
  321. /package/client/dist/assets/{mcp-CNgFiTEh.js → mcp-CmOBPDjE.js} +0 -0
  322. /package/client/dist/assets/{mcp-mupPuPDO2.js → mcp-NlLc9T8u2.js} +0 -0
  323. /package/client/dist/assets/{mcp-B4irfLJ2.js → mcp-_DUUXTHB2.js} +0 -0
  324. /package/client/dist/assets/{mcp-BP7PpbDr2.js → mcp-dlOyUQQy2.js} +0 -0
  325. /package/client/dist/assets/{mcp-BOoEGTSQ.js → mcp-zqSVP7nA.js} +0 -0
  326. /package/client/dist/assets/{specs-D4z5d8uq.js → specs-57pue7UQ.js} +0 -0
  327. /package/client/dist/assets/{specs-CWNKAScX2.js → specs-BWQ-0EXt2.js} +0 -0
  328. /package/client/dist/assets/{specs-B8mwScoM2.js → specs-BYSOlQRw2.js} +0 -0
  329. /package/client/dist/assets/{specs-B5XOoH-02.js → specs-CkP0B28v2.js} +0 -0
  330. /package/client/dist/assets/{specs-DgvIRZtM.js → specs-RSYuLgF4.js} +0 -0
  331. /package/client/dist/assets/{specs-DuRzE5wE.js → specs-d-zU7_3Q.js} +0 -0
  332. /package/client/dist/assets/{specs-Bj_3fDrQ.js → specs-nM5Cpcqe.js} +0 -0
  333. /package/client/dist/assets/{terminal-DoMTHedv2.js → terminal-Adi3W-GZ2.js} +0 -0
  334. /package/client/dist/assets/{terminal-DOwfkxqa2.js → terminal-BA6FCZna2.js} +0 -0
  335. /package/client/dist/assets/{terminal-CH0fdHvP.js → terminal-BCIUSkrg.js} +0 -0
  336. /package/client/dist/assets/{terminal-B2sExUis.js → terminal-C8KlD61D.js} +0 -0
  337. /package/client/dist/assets/{terminal-DjSSa5qP.js → terminal-D3RTuBwy.js} +0 -0
  338. /package/client/dist/assets/{terminal-w0i-OCp32.js → terminal-DCkOEjSj2.js} +0 -0
  339. /package/client/dist/assets/{terminal-CPMigUX7.js → terminal-DOuNm2BI.js} +0 -0
  340. /package/client/dist/assets/{tickets-D00mSt832.js → tickets-B2q36GlQ2.js} +0 -0
  341. /package/client/dist/assets/{tickets-CQTana1u.js → tickets-Bsyv_znj.js} +0 -0
  342. /package/client/dist/assets/{tickets-D7CSuWjO.js → tickets-C_UPra_1.js} +0 -0
  343. /package/client/dist/assets/{tickets-heDAaj8C2.js → tickets-D72LbUtU2.js} +0 -0
  344. /package/client/dist/assets/{tickets-DvbFg_-c2.js → tickets-DDjU7UfE2.js} +0 -0
  345. /package/client/dist/assets/{tickets-onKZz6Dd.js → tickets-DR4ZrPMf.js} +0 -0
  346. /package/client/dist/assets/{tickets-Dq7HDy8n.js → tickets-DiUA3rXq.js} +0 -0
@@ -0,0 +1,301 @@
1
+ # Switch to Agent Mode — Design Dossier
2
+
3
+ > **Status: pre-implementation design artifact (explore mode).** This document consolidates 8 structured investigation slices into a single implementation plan. No application code is written here. It defines the shell swap, the shared conversation-view extraction, the left-sidebar restructure, the three "On workspace" tools, the full attachment-parity plan, search wiring, and the cross-cutting concerns. Section 10 carries the consolidated RISKS and the numbered OPEN DECISIONS that a human must resolve before implementation begins.
4
+
5
+ ---
6
+
7
+ ## 1. Overview + decided constraints
8
+
9
+ **Goal.** Add an app-global **Agent Mode** UI that replaces the Kanban route-driven center with a Cursor-style agent workspace: a full-height conversation surface, a restructured left sidebar (project→conversation trees), a hidden navbar search (moved into the sidebar), and a right "On workspace" toolbar exposing three tools (Terminal, Files, Browser). The floating agent bubble/panel are suppressed while in Agent Mode — the same conversation UI is hoisted inline.
10
+
11
+ **Decided constraints (fixed inputs to this design):**
12
+
13
+ 1. **`uiMode` is persisted.** A new app-global `uiMode: 'kanban' | 'agent'` lives in its own small context (`UiModeProvider`), localStorage-backed, no project dependency. It gates the shell swap, the sidebar buttons, the search pill, and the floating surfaces.
14
+ 2. **Brave option A — inline CodePage.** The Files tool embeds the existing `CodePage` inline (decoupled from `ProjectLayout` and the `/code` route) rather than shipping a stripped-down viewer. This is a real refactor of `CodePage` into a controlled component.
15
+ 3. **Brave option B — full attachment parity + wired Browser.** The Agent composer reaches **full parity** with the Explore Add-Spec composer: `RichAttachmentEditor` (file-pick / drag-drop / image-paste / @-pills) **plus** the live `BrowserCaptureModal` / `CapturedDomPanel` browser-capture flow, all uploaded through a new app-global agent attachment endpoint and folded into the provider prompt. The Browser "On workspace" tool reuses the same capture stack.
16
+ 4. **All three "On workspace" tools require an active project.** They are disabled when `activeProjectId` is null.
17
+
18
+ **Two things that stay byte-identical:** Kanban mode must render exactly as today, and the CommandPalette (Cmd+K) keeps its own global keydown listener so the shortcut keeps working regardless of which mode is active.
19
+
20
+ ---
21
+
22
+ ## 2. Architecture diagram (shell swap + provider tree)
23
+
24
+ ```
25
+ main.tsx → <BrowserRouter> (single app-wide router; main.tsx:17-19)
26
+ └─ SharedWebSocketProvider (App.tsx:433 — above routes; CodePage/FileTree/FileViewer depend on it)
27
+ └─ App()
28
+ └─ DesktopProvider (App.tsx:440 — projects, activeProjectId, api base)
29
+ └─ ★ UiModeProvider ★ ◄── NEW, inserted here (wraps TitleBar onward, closes before </DesktopProvider> App.tsx:465)
30
+ ├─ TitleBar (App.tsx:442) ── reads uiMode → HIDES SearchPill when 'agent' (TitleBar.tsx:175,215)
31
+ ├─ SpecGen / ContractRefine / Smash / SidebarPin / Terminals / RailMetrics / MinimizedChats providers
32
+ │ (TerminalsProviderWithDesktop App.tsx:447 — already app-level; terminal state is mode-agnostic)
33
+ └─ AgentChatProvider (App.tsx:450) ── reads uiMode → SUPPRESSES AgentChatPanel(:293)+AgentBubble(:296)
34
+ └─ TicketDetailModalProvider ...
35
+ └─ DesktopApp (App.tsx:453 / body 230-330)
36
+ │
37
+ │ flex h-full row:
38
+ ├─ ArcSidebar (App.tsx:233-239) ── reads uiMode → sidebar buttons + project/conversation trees
39
+ │
40
+ ├─ main-area column <div flex flex-col flex-1> (App.tsx:242-297)
41
+ │ ├─ project-switching progress bar (:244-249)
42
+ │ ├─ content box <div flex-1 overflow-hidden> (:252-296)
43
+ │ │ {isInSetup ? SetupWizard
44
+ │ │ : uiMode==='agent' ? ★ <AgentModeSurface/> ★ ◄── NEW center swap
45
+ │ │ : <Suspense><Routes> … ProjectLayout … </Routes></Suspense>} (existing, :261-294)
46
+ │ │
47
+ │ └─ ★ <BottomPanel/> + <StatusBar/> ★ ◄── HOISTED here from ProjectLayout (both modes, one instance)
48
+ │ gated on activeProject; chevronSlot gated on uiMode==='kanban'
49
+ │
50
+ └─ right sidebar (App.tsx:302)
51
+ {uiMode==='agent'
52
+ ? activeProject && !isInSetup && ★ <AgentWorkspaceSidebar/> ★ ◄── NEW (Browser/Terminal/Files)
53
+ : activeProject && !isInSetup && !/loops && <ProjectRightSidebar/>} (existing)
54
+ ```
55
+
56
+ Key structural moves shown above:
57
+
58
+ - **`UiModeProvider` mounts just inside `DesktopProvider`** (App.tsx:440) so `TitleBar`, `AgentChatProvider`, `ArcSidebar`, the center, and both right sidebars all read it.
59
+ - **`BottomPanel` + `StatusBar` are HOISTED** out of the route element `ProjectLayout` (currently ProjectLayout.tsx:124-134) into the `DesktopApp` main-area column (App.tsx ~:296), so the terminal survives when the center is no longer a route. `TerminalsProvider` is already app-level (App.tsx:447), so only the *visual* panel needs hoisting.
60
+
61
+ ---
62
+
63
+ ## 3. Component / module inventory
64
+
65
+ ### NEW files to create
66
+
67
+ | File | Responsibility | Key anchors |
68
+ |---|---|---|
69
+ | `client/src/context/UiModeContext.tsx` | App-global `uiMode: 'kanban'\|'agent'` + `setUiMode`/`toggle`, localStorage-persisted; `useUiMode()` with NOOP fallback for tests. | Insert provider at App.tsx:440-441; consumed by TitleBar/ArcSidebar/AgentChatContext/DesktopApp. |
70
+ | `client/src/components/agent-chat/AgentConversationView.tsx` | Shared inner UI (banners + sticky-scroll message list + streaming + activity chip + composer) consumed by BOTH the floating panel and the inline surface; `variant: 'floating'\|'inline'`. | Extracted from AgentChatPanel.tsx:158-269. |
71
+ | `client/src/components/agent-chat/AgentModeSurface.tsx` | Inline center surface: EMPTY (`active===null` → centered composer card) vs ACTIVE (`<AgentConversationView variant='inline'/>` + optional Files/code split). Does NOT call `ensureActive` on mount. | Mounted at App.tsx center swap (:252-296). |
72
+ | `client/src/components/agent-chat/AgentWorkspaceSidebar.tsx` | Right "On workspace" toolbar in Agent Mode: Browser / Terminal / Files buttons, each disabled when `activeProjectId` is null. | Mounted at App.tsx:302 (agent branch). |
73
+ | `client/src/components/agent-chat/AgentModeCodePane.tsx` (optional wrapper) | Resizable split host that mounts `<CodePage embedded/>` as a secondary pane beside the thread; own `<Suspense>` boundary. | Wraps CodePage.tsx (lazy). |
74
+ | `server/agent-attachment-routes` (in `agent-chat-router.ts`) | App-global attachment endpoints: `POST/GET/DELETE /api/agent/conversations/:id/attachments` (multer single 'file', 25MB, mime filter). | Mirror project-router-tickets.ts:1839-1917. |
75
+ | `server/agent-store` migration | Persist attachment id list per agent turn (new nullable `attachments` JSON column on `agent_messages`, or sibling `agent_message_attachments` table). | agent-store.ts:24-30,111-128. |
76
+
77
+ ### EXISTING files to modify
78
+
79
+ | File | Change | Key anchors |
80
+ |---|---|---|
81
+ | `client/src/App.tsx` | Insert `UiModeProvider`; branch center on `uiMode`; branch right sidebar; hoist `BottomPanel`+`StatusBar` into main-area column; source `viewportHeight`/`panelState`/`connectionStatus` at DesktopApp scope. | 230-330 (shell), 440-461 (providers). |
82
+ | `client/src/components/ProjectLayout.tsx` | REMOVE the `BottomPanel`/`StatusBar` render (hoisted); keep `<Outlet/>`, ChatContext; gate chevronSlot on `uiMode==='kanban'`; relocate the `viewportHeight` ResizeObserver + `panelState` up. | 66-95, 124-134. |
83
+ | `client/src/components/TitleBar.tsx` | Hide `SearchPill` when `uiMode==='agent'` at both mount sites. | 66-129 (pill), 175 & 215 (mounts). |
84
+ | `client/src/context/AgentChatContext.tsx` | Gate `AgentChatPanel`(:293) + `AgentBubble`(:296) off when `uiMode==='agent'`; expose a public `refreshConversations`; extend `newConversation(projectId?)`. | 39-70, 246-253, 290-298. |
85
+ | `client/src/components/agent-chat/AgentChatPanel.tsx` | Reduce to thin chrome wrapper rendering `<AgentConversationView variant='floating'/>`; keep `useMovableResizableModal`/maximize/header/`ResizeGrips`. | 30-119 (chrome), 121-284 (body). |
86
+ | `client/src/components/ArcSidebar.tsx` | Add `useAgentChat()` + `useUiMode()`; add [Switch mode]/[New agent]/[Search] buttons above Loops; make `ProjectItem` expandable; render conversation children + Home group; branch `handleSelectProject` by mode. | 1-9, 19-106, 135-166, 206-259. |
87
+ | `client/src/pages/CodePage.tsx` | Add controlled/`embedded` props; neutralize `navigate({pathname:'/code'})` calls; gate URL→state effects when embedded. | 51-96, 129-160. |
88
+ | `server/agent-chat-router.ts` | Parse `attachments:{ids}` on `POST /:id/send`; add attachment endpoints. | 144-163. |
89
+ | `server/agent-chat-manager.ts` | Resolve attachment textBlocks via `attachmentManager.getClaudeArgs`, fold into `prompt` before pinned-project prefix; append note for codex/gemini into prompt. | 72-235 (fold near :97-102, chokepoint :151). |
90
+ | `server/attachment-manager.ts` | Add app-global agent storage root (`~/.specrails/agent/<conversationId>/attachments/`) or overload; keep traversal guards. | 89-133, 287-348. |
91
+
92
+ ---
93
+
94
+ ## 4. `AgentConversationView` extraction plan (shared by floating panel + inline surface)
95
+
96
+ **Boundary.** `AgentChatPanel.tsx` currently fuses two separable concerns. Split them:
97
+
98
+ - **WINDOW-CHROME WRAPPER (stays in `AgentChatPanel.tsx`):** `useMovableResizableModal` (AgentChatPanel:30-37), `maximized` + `MAXIMIZED_STYLE` (:27,:41), the `motion.div` shell (:123-135), the header drag band with `Bot` icon + maximize/minimize buttons (:137-155), the `ResizeGrips` overlay (:277-281), and the persisted geometry `persistKey 'specrails-desktop:agent-panel-geom'`. **Chrome-only — must NOT leak into the inline variant.**
99
+ - **SHARED `<AgentConversationView>` (NEW):** the two banners (no-provider :158-166, MCP-degraded :169-177), the messages scroll region including empty placeholder (:180-199), `messages.map(<AgentMessage/>)` (:188-190), the streaming block with `useSmoothStream` + `<AgentActivityChip/>` (:191-198), and the entire composer (:202-269). The local state that powers these **moves with the view:** `input` (:26), `scrollRef`/`pinnedRef` sticky-scroll (:28,:49-58), `useSmoothStream(streamingText,isStreaming)` (:43), prompt-history `histIndex`/`history`/`recall`/`onComposerKeyDown` (:61-109). The Shift+Tab tier-cycle handler `onPanelKeyDown` (:113-119) must be **re-bound onto the shared view's root wrapper** (it only calls `cycleTier()` from context, so it is portable).
100
+
101
+ **Prop / context boundary.** The view is **almost entirely context-driven** via `useAgentChat()` (AgentChatContext:39-70): `active`, `messages`, `streamingText`, `isStreaming`, `liveTools`, `mcpEnabled`, `enablingMcp`, `enableMcpServer`, `providersReady`, `send`, `abort`, `cycleTier`, `setProvider`, `setModel`, `setPinnedProject`. The **only props** are presentational: `variant: 'floating'|'inline'` (switches padding, rounded corners, whether the composer docks bottom vs centers), and optionally a `newConversation` callback carrying the active project for the inline [New agent] flow.
102
+
103
+ **EMPTY vs ACTIVE (inline surface).** Derived purely from context: `active===null` → **EMPTY** (centered "Plan, Build" composer card); `active!==null` → **ACTIVE** (full thread + docked composer). Note the floating panel's in-scroll placeholder uses `messages.length===0 && !isStreaming` (:181) while a conversation may still be active — the inline surface must use `active===null` instead. Because `open()/ensureActive()` (:176-209) auto-creates/loads a conversation, `AgentModeSurface` must **not** call `ensureActive` on mount, or the EMPTY state can never appear.
104
+
105
+ **Re-homing the project selector.** `AgentProjectSelector` (AgentProjectSelector.tsx:13-91) currently sits in the panel **header (chrome)** (AgentChatPanel:143). Extracting only the inner subtree would drop it from Agent Mode — it must be **re-homed into the composer or the empty-state card**.
106
+
107
+ **Pin defaulting.** `newConversation()` (AgentChatContext:246-253) hardcodes `pinnedProjectId: active?.pinned_project_id ?? null` and takes no args. Extend to `newConversation(projectId?: string|null)` using `projectId!==undefined ? projectId : (active?.pinned_project_id ?? null)` so the floating panel's arg-less call (AgentChatPanel:145) stays backward-compatible while the inline [New agent] can pass `useDesktop().activeProjectId` (useDesktop:269).
108
+
109
+ ---
110
+
111
+ ## 5. Left sidebar restructure (`ArcSidebar.tsx`)
112
+
113
+ `ArcSidebar` is self-contained (consumes `useDesktop()` + `useSidebarPin()` at :122, imports at :1-9) and sits inside both `DesktopProvider` and `AgentChatProvider` (App.tsx:440-457), so it can safely add `useAgentChat()` (NOOP fallback at AgentChatContext:301-318 keeps it test-mountable) and `useUiMode()`.
114
+
115
+ **New buttons cluster (inserted ABOVE the Loops block, before ArcSidebar:206):**
116
+
117
+ - **[Switch to Agent/Kanban mode]** — always shown (both modes). `onClick=toggleUiMode`. JSX mirrors the Loops button (:209-225): `type=button`, icon (Bot/Sparkles for agent, LayoutGrid/Columns for kanban) + `{expanded && <span>…</span>}`. Label flips on current `uiMode`.
118
+ - **[New agent]** — agent mode only. `onClick=newConversation(activeProjectId?)` (see §4 pin defaulting). Icon `Plus`.
119
+ - **[Search]** — agent mode only (replaces the hidden navbar pill). `onClick` dispatches the synthetic Cmd+K keydown (see §8). Icon `Search`.
120
+
121
+ **Project→conversation trees (agent mode):**
122
+
123
+ - Group `useAgentChat().conversations` (agent-api.ts:10-20) by `pinned_project_id` via `useMemo`: `Map<string|null, AgentConversation[]>`. Use a **sentinel** for the `null` key.
124
+ - Each `ProjectItem` (:19-106) becomes expandable: add a leading chevron hit-area (ChevronRight/ChevronDown) that toggles tree expansion **independently** of row activation. Split the current whole-row `onClick=onSelect` (:67): chevron toggles expand, row body sets active project. Only render the chevron in agent mode when the project has >0 conversations.
125
+ - Render conversation children (indented) for `pinned_project_id === project.id`; each `onClick=selectConversation(id)` (async, loads thread) **and** ensures `uiMode==='agent'`; highlight when `conv.id === active?.id`. Fallback label for nullable `title` (agent-api.ts:12) — e.g. "Untitled".
126
+ - A synthetic **Home pseudo-group** (agent mode only) renders conversations with `pinned_project_id === null`, not nested under any project.
127
+
128
+ **Expand state.** Held locally in `ArcSidebar` as `useState<Set<string>>` (or `Record`), initialized from and persisted to a **new** localStorage key (e.g. `specrails-desktop:agentTreeExpanded`), default-expanding the active project. This is orthogonal to the existing sidebar-width `expanded` flag (:149, from `useSidebarPin`), which only controls label visibility.
129
+
130
+ **Click semantics reconciliation.** Branch `handleSelectProject` (:135-147) by `uiMode`. In **agent mode** skip the `navigate('/')`/route-memory path (the center is the agent surface, not routes) and just `setActiveProjectId(projectId)` (useDesktop:90-102) + toggle expand — `setActiveProjectId` has side effects (`writeSavedProjectId` + `setApiActiveProjectId` :91-92, 400ms switching flag) but must NOT trigger visible route navigation. In **kanban mode** keep today's behavior exactly.
131
+
132
+ **Refresh gap.** `AgentChatContextValue` exposes **no** standalone refresh — `conversations[]` is only refreshed inside `open()`/`toggle()` (AgentChatContext:193,204). Entering agent mode must expose/call a `refreshConversations` (preferred over calling `open()`, which would set `visibility='open'` and mount the now-suppressed floating panel).
133
+
134
+ ---
135
+
136
+ ## 6. The three "On workspace" tools
137
+
138
+ All three are gated on `!!activeProjectId` (disabled tooltip otherwise), matching the decided rule.
139
+
140
+ ### 6a. Terminal — hoist `BottomPanel`
141
+
142
+ `TerminalsProvider` is **already app-level** (App.tsx:447) and keyed off the active project, so PTYs, xterm instances, WS sockets and per-project panel state already survive route/project switches (durable refs at TerminalsContext:209-229; server-sync effect keyed on `activeProjectId` :297-334; disposal only on explicit kill/unmount :428-466). The **single hidden `#specrails-terminal-host`** (:178-194) and the single-adopter `TerminalViewport` reparenting invariant (TerminalViewport:126-139) mean unmounting the visible panel does NOT dispose the terminal — the DOM node simply returns to the host.
143
+
144
+ - **Reuse:** `useTerminals()` (already in DesktopApp App.tsx:152), `useProjectTerminals(activeProjectId)` (ProjectLayout:68), `setVisibility(projectId,'restored')` (TerminalsContext:273), `togglePanel(projectId)` (:277, the Cmd+J action App.tsx:196), `<BottomPanel projectId provider providers state viewportHeight statusBarHeight/>` (BottomPanel:36, returns null when hidden), `projectProviders(project)`, `PanelChevronButton`.
145
+ - **New work:** hoist the `BottomPanel` render (ProjectLayout:124-133) to the DesktopApp main-area column (App.tsx ~:296), driven by `activeProjectId` + `useProjectTerminals(activeProjectId)`, so **one** instance serves both modes; move the `viewportHeight` ResizeObserver (ProjectLayout:69-83) + `STATUSBAR_HEIGHT_PX` up; gate `chevronSlot` on `uiMode==='kanban'` (ProjectLayout:90-95); wire the sidebar Terminal button to `setVisibility(activeProjectId,'restored')`. **Ensure only ONE `BottomPanel`/`TerminalViewport` is mounted at any time** (remove the ProjectLayout copy) to preserve the single-adopter invariant.
146
+
147
+ ### 6b. Files — inline `CodePage`
148
+
149
+ `CodePage`'s data layer is bound to the **active project**, not route context: `FileTree`/`FileViewer`/provenance fetch via `getApiBase()` (api.ts:13-28, module singleton set by `DesktopProvider`) and read `activeProjectId` via `useDesktop()`. It consumes **zero** context that `ProjectLayout` provides (no `useTerminals`, no `useChat`, no `BottomPanel`); the only transitive app-level provider is `useSharedWebSocket` (App.tsx:433, above routes). The **only** true shell coupling is React Router: `useSearchParams`/`useNavigate` pinned to `pathname:'/code'` (CodePage:133,140,147,159).
150
+
151
+ - **Reuse:** `getApiBase()` (throws when no active project — enforces the "requires active project" rule), `useDesktop().activeProjectId`, controlled `FileTree` props (`onOpenFile`, `selectedPath`, `filterJobId`, `filterTicketId`), controlled `FileViewer` props (`relPath`, `onFilterJob`, …), `useSharedWebSocket()`.
152
+ - **New work:** refactor `CodePage` into a **controlled** component — add optional props (`selectedPath?`, `onSelectedPathChange?`, `jobFilter?`/`ticketFilter?`, `onFilterChange?`, `embedded?`); when provided, lift internal state and **replace the `navigate({pathname:'/code'})` calls (:133,140,147,159) with callbacks**; gate the URL→state sync effect (:71-75) + searchParams effects to no-op when embedded. Mount inside `AgentModeSurface` as a **split** secondary pane (thread | code) with its own `<Suspense>` boundary (CodePage is `React.lazy`, App.tsx:21). Store code selection in the Agent Mode context/state (NOT the URL). Mind the nested-split min-width math (`MIN_MAIN_WIDTH=520` + `MIN_TREE_WIDTH=240`).
153
+
154
+ ### 6c. Browser — capture reuse
155
+
156
+ The Explore browser-capture stack is fully project-scoped and reusable verbatim.
157
+
158
+ - **Reuse:** `BrowserCaptureModal` (props `{open,onClose,projectId,pendingSpecId,onCaptured,confirmLabel}`), `CapturedDomPanel` (props `{dom,onRemove?}`), the `client/src/lib/browser-capture.ts` REST helpers (`createBrowserSession`, `navigateBrowser`, `captureBrowserRegion`, `captureBrowserBreakpoints`, `uploadCaptureImage`), `isBrowserCaptureEnabled()` (client flag `VITE_FEATURE_BROWSER_CAPTURE`), and server `requireBrowserCaptureEnabled`/`SPECRAILS_BROWSER_CAPTURE` (feature-flags.ts:9-13). Server session endpoints at project-router-terminals.ts:200-410.
159
+ - **New work:** wire the sidebar Browser button to open `BrowserCaptureModal` with the current `activeProjectId` and the agent conversation's pending id; route `onCaptured` into the composer capture chips (see §7). Requires an active project (browser sessions are project-scoped).
160
+
161
+ ---
162
+
163
+ ## 7. Full attachment-parity plan
164
+
165
+ Today the agent turn is **text-only**. `POST /api/agent/conversations/:id/send` accepts `{text, tierLevel?, model?}` (agent-chat-router:150-162) → `AgentChatManager.sendMessage` builds `prompt = optional '[Active project…]' prefix + userText` (agent-chat-manager:100-102) → `runAiCliInvocation` passes it as an argv positional (`buildOpts.prompt`, :151). There is **no** upload endpoint, **no** attachment path, and **no** per-turn `ai_invocations` accounting. The Explore side already has the complete mechanism; most of the work is wiring, not new storage.
166
+
167
+ ### 7a. Enumerated Explore affordances to port (parity list)
168
+
169
+ From `RichAttachmentEditor` + `ExploreSpecShell`:
170
+
171
+ 1. **File picker** — footer "Attach files" button (RichAttachmentEditor:562-570) → hidden `<input type=file multiple accept=ATTACHMENT_ACCEPT_MIME>` (:578-585) → `handleBrowseChange` (:490-498) → `uploadFiles`.
172
+ 2. **Drag-drop** — root drop zone (:504-511) + "Drop to add context" overlay (:587-606) → `handleDrop` (:475-485) → `uploadFiles`.
173
+ 3. **Image paste** — `handlePaste` (:390-422): clipboard image item → renamed `paste-<date>-<n>.png` → `uploadFile`; non-image paste forced to plain text (HTML stripped).
174
+ 4. **Backspace/Delete on a pill** — removes attachment (`handleKeyDown` :342-388) + fires `onAttachmentRemoved` (Explore → DELETE on the attachments endpoint, ExploreSpecShell:966-971).
175
+ 5. **@-mention pills** — inline pills in the contenteditable (`buildPill` :79-98, serialized `@[filename](id)` :104-126). Pills are created **only** by uploads (no free-text @-typeahead).
176
+ 6. **Browser capture ("From a website")** — `footerExtra` Globe button (ExploreSpecShell:978-989) → `BrowserCaptureModal` (:1150-1159). User drives a live headless browser, selects a region/element, optionally annotates and/or captures at 3 breakpoints. `captureBrowserRegion`/`captureBrowserBreakpoints` write **TWO** attachments per capture (PNG screenshot + DOM JSON) into the same pending dir, returning `CaptureResult`. `handleCaptured` (:258-291) either auto-sends a feedback turn (`sendCaptureFeedback` :574-600) or queues a chip under the composer (:991-1033) that rides the next message.
177
+
178
+ ### 7b. Attachment data model
179
+
180
+ - **Client:** an attachment is the `Attachment` type (`{id, filename, mimeType, storedName, …}`). The editor tracks `attached: Attachment[]` + DOM pills; `getAttachmentIds()` (RichAttachmentEditor:306-311) reads all `[data-attachment-id]`. Captures are tracked separately in `ExploreSpecShell.captures: BrowserCaptureEntry[]` (screenshotId, domAttachmentId, breakpoints[]) so both ids merge in.
181
+ - **Server:** each attachment = file + `<id>.meta.json` sidecar under `~/.specrails/projects/<slug>/attachments/<ticketKey>/` (attachment-manager:96-133). `ticketKey` is a `pendingSpecId` (UUID) during Explore, path-sanitized. On Create Spec, `migrateToTicket` (attachment-manager:248-273) renames the pending dir to the real ticket id.
182
+
183
+ ### 7c. Server storage + upload endpoints (NEW for agent)
184
+
185
+ - **New app-global upload route:** `POST /api/agent/conversations/:id/attachments` (multer `single('file')`, `memoryStorage`, 25MB cap, `isSupportedUploadedFile` filter) → `attachmentManager.upload(...)` with an agent-scoped key; returns `201 {attachment}`. Add `GET` (list), `GET /:attachmentId` (download), `DELETE` for parity. Mirror project-router-tickets.ts:1839-1917.
186
+ - **New agent-scoped storage root:** `~/.specrails/agent/<conversationId>/attachments/` (the agent per-conversation dir already exists for mcp.json, agent-mcp-config.ts:135,162-169). Add an `agentAttachmentsRoot()`/overload on `AttachmentManager` (or a thin wrapper), **keeping the path-traversal guards** (attachment-manager:100-110). Do NOT reuse `projects/<slug>/attachments` with a synthetic slug — it risks colliding with a real project slug.
187
+ - **Cleanup:** `deleteAgentConversation` (agent-store:107) must remove the attachment dir (a `deleteAll` analog).
188
+
189
+ ### 7d. Agent send-flow changes (payload / adapter contract / per-provider)
190
+
191
+ - **Payload:** extend `POST /:id/send` to parse `attachments: { ids: string[] }` (the `conversationId` is the storage key — no slug needed). Mirror the chat parse at project-router-chat.ts:276-286.
192
+ - **Fold point:** in `AgentChatManager.sendMessage`, resolve `attachmentManager.getClaudeArgs(agentKey, ids)` (attachment-manager:287, **async** — pdf/xlsx extraction) → `{imageFlags:[], textBlocks:string[]}` and fold `textBlocks` into `prompt` **before** the pinned-project prefix (between agent-chat-manager:97 and :100), exactly mirroring chat-manager:585-609 ("## Attached Resources" + `USER_ATTACHMENT_SYSTEM_NOTE`). `buildOpts.prompt` (:151) is the single chokepoint. Wrap in try/catch so extraction failure degrades to a text-only turn.
193
+ - **Adapter contract:** prompt transport is **argv, not stdin** for all three providers — claude `-p opts.prompt` (claude-adapter:97,110), codex positional to `exec` (codex-adapter:88, folds systemPrompt :101), gemini `-p opts.prompt` (gemini-adapter:76). `SpawnOptions.attachmentTextBlocks?` is **reserved but unused** (providers/types.ts:40-41) — either fold-into-prompt (fast, matches Explore, recommended) or wire `attachmentTextBlocks` through the 3 adapters' `buildArgs` (cleaner, more edits).
194
+ - **Per-provider image reality:** `getClaudeArgs` emits images as `@<abs-path>` refs. **Claude Code resolves these natively → images work.** codex/gemini receive the `@path` as **literal prompt text → the image is silently NOT loaded.** Extracted-text attachments (pdf/csv/xlsx/txt/json/sql) become inline `<user-attachment>` text and work for **all three**. LCD for v1 = text-extractable attachments for all providers; images are claude-only until per-adapter native image flags are added. This needs a UX decision (block images on non-claude vs attempt native flags) — see Open Decisions.
195
+ - **Persistence:** `agent_messages` has only `{id, conversation_id, role, content}` (agent-store:24-30). To re-render pills/thumbnails after refresh, add a nullable `attachments` JSON column (new migration) or a sibling `agent_message_attachments` table.
196
+ - **Metering (recommended, optional):** agent turns are currently **unmetered** — `AgentChatManager` is absent from the `ai_invocations` consumer set. Adding `finaliseInvocationResult` in the same change makes attachment-heavy turns visible on `/analytics`, matching Explore.
197
+
198
+ ---
199
+
200
+ ## 8. Search / Cmd+K plan
201
+
202
+ The "navbar search box" is the **`SearchPill` inside `TitleBar.tsx`** (:64-129), not `Navbar.tsx` (which has no search). It does no inline searching — `handleClick` (:70-74) dispatches a synthetic `Cmd+K` keydown that the already-mounted `CommandPalette` (App.tsx:321-325, cmdk-based) listens for via a **global window keydown listener** (CommandPalette:53-62). There is **no imperative open API** — the synthetic-keydown dispatch is the only sanctioned seam.
203
+
204
+ - **Hide** `SearchPill` when `uiMode==='agent'` (gate render at TitleBar:175 and :215). The palette's own listener keeps Cmd+K working with the pill hidden.
205
+ - **New sidebar [Search] button** (agent mode) copies `SearchPill.handleClick` verbatim: `window.dispatchEvent(new KeyboardEvent('keydown', { key: 'k', metaKey: true, bubbles: true }))`. Cross-platform: the palette listener accepts `metaKey||ctrlKey`, and the synthetic event sets `metaKey:true`, so it works on Windows/Linux too.
206
+ - **Optional:** extract a tiny shared `openCommandPalette()` helper so both call sites share one function.
207
+
208
+ Note the dispatch **toggles** — clicking [Search] while the palette is already open would close it (CommandPalette:57). v1 reuses the generic palette (projects/commands/jobs/nav); a scoped agent-conversation search would require refactoring `CommandPalette` to accept controlled open state.
209
+
210
+ ---
211
+
212
+ ## 9. Cross-cutting concerns
213
+
214
+ - **Feature flag.** Gate the whole feature behind a client flag (e.g. `VITE_FEATURE_AGENT_MODE`, default per policy) resolved through `client/src/lib/feature-flags.ts` (string `"false"` = off, everything else = on), mirroring `VITE_FEATURE_CODE_EXPLORER`/`VITE_FEATURE_TERMINAL_PANEL`. When off, `UiModeProvider` pins `'kanban'` and none of the new buttons render — Kanban stays byte-identical.
215
+ - **i18n (`agent` namespace).** The `agent` namespace already exists (×8 locales). Add keys for the new surfaces: `switchToAgentMode`/`switchToKanbanMode`, `newAgent`, `search` (reuse `titleBar.searchShortcut`/`searchPlaceholder` from `nav.json` for the button label), the "On workspace" tool labels (`workspace.terminal`/`.files`/`.browser`), disabled-tooltip ("requires an active project"), the EMPTY-state "Plan, Build" copy, and Home-group label. **All 8 locales must mirror English** (locale-parity test enforces key tree + placeholders).
216
+ - **WS events.** No new WS event types are strictly required for the shell swap (uiMode is client-local). Attachment parity may add attachment refs to the existing `agent_done`/message echo, or a new project-scoped-free (app-global, no `projectId`) event so non-initiating subscribers render user-turn attachments. Browser capture reuses the existing project-scoped browser WS. Terminal reuses the dedicated `/ws/terminal/:id`.
217
+ - **Coverage / tests (per CLAUDE.md — server 80% lines/functions/statements + 70% branches, client 80% lines/statements + 70% functions, 70% global).**
218
+ - *Client:* `UiModeContext` (persistence, NOOP fallback), `AgentConversationView` (EMPTY vs ACTIVE from context, tier-cycle rebind, composer submit), `AgentModeSurface`, `AgentWorkspaceSidebar` (disabled gating), `ArcSidebar` agent-mode branch (grouping, expand state, click semantics, Home group), `CodePage` embedded/controlled mode (navigate suppression, URL-effect no-op), the [Search] dispatch. Attachment editor reuse is largely covered by existing Explore tests; add agent-composer wiring tests.
219
+ - *Server:* new agent attachment endpoints (upload/list/delete, 25MB cap, mime filter, traversal guard on `conversationId`), `AgentChatManager.sendMessage` prompt-fold (text blocks in, extraction-failure degrades to text-only), agent-scoped storage root, conversation-delete cleanup, and (if added) `ai_invocations` recording.
220
+ - Structurally-unreachable Tauri-only paths may be excluded with an inline documented reason (per policy), never to mask missing tests.
221
+
222
+ ---
223
+
224
+ ## 10. Consolidated RISKS + numbered OPEN DECISIONS
225
+
226
+ ### RISKS (deduped across slices, most-load-bearing first)
227
+
228
+ 1. **Double-mount of the terminal (single-adopter invariant).** If `BottomPanel` is hoisted to `DesktopApp` but `ProjectLayout` still renders its own copy (ProjectLayout:124-133) in Kanban, TWO `TerminalViewport`s fight over the same active container in `#specrails-terminal-host` — the second `appendChild` steals the node, the first's cleanup moves it back → flicker/blank terminal. The ProjectLayout copy **must** be removed.
229
+ 2. **CodePage's hardcoded `navigate({pathname:'/code'})` (CodePage:133,140,147,159).** Embedded without neutralizing these, any file/filter click routes the whole app to `/code` and pops the user out of Agent Mode. Highest CodePage coupling. Paired risk: the searchParams source-of-truth effect (:71-75) would force-reset `relPath` on every non-`/code` search change unless gated by `embedded`.
230
+ 3. **Hoisting `BottomPanel`/`StatusBar` risks Kanban layout regressions.** `StatusBar` needs `connectionStatus` from `usePipeline()` (ProjectLayout:25) — verify it works at DesktopApp scope. The `viewportHeight`/`STATUSBAR_HEIGHT_PX` maximize math depends on the panel's container height and whether a StatusBar exists; wrong values overshoot/undershoot maximize. Kanban must render exactly as today.
231
+ 4. **Images silently no-op on codex/gemini.** `@<abs-path>` is literal text to them; users will believe the image was seen. Needs per-provider UX gating. Also `getClaudeArgs` `@path` refs only resolve if the agent CLI spawn has read access to the agent attachments dir — confirm cwd/permissions parity with the chat spawn.
232
+ 5. **Agent attachment storage has no natural home / lifecycle.** Agent conversations are app-global (no slug); the `projects/<slug>/attachments/<ticketKey>` convention doesn't fit and a synthetic slug risks collision. An unpinned "Home" conversation has no project at all. Agent convos never become tickets, so there's no migration/permanence path, and `deleteAgentConversation` doesn't clean up files today.
233
+ 6. **`AgentChatContext` has no public `refreshConversations`** (only `open()`/`toggle()` refresh, :193,204) — the sidebar project tree can show a stale/empty list on mode entry; and `selectConversation` only loads the thread (doesn't set visibility/uiMode), so sidebar clicks need new coordination.
234
+ 7. **`AgentProjectSelector` lives in the panel header (chrome).** Naive extraction of only the inner subtree drops the project selector from Agent Mode — it must be re-homed into the composer/empty-state.
235
+ 8. **Center-as-route assumption breaks in Agent Mode.** `useProjectRouteMemory` (App.tsx:166) and `/code`/`/jobs` routes don't mount; route memory must still restore correctly on switching back to Kanban.
236
+ 9. **`ProjectItem` onClick split** (chevron-toggle vs row-activate) risks breaking keyboard handling (`handleSelectKeyDown` :57-61) and the remove-confirm `stopPropagation` flow (:88-101); nested interactives need a11y care.
237
+ 10. **Prompt bloat / argv limits.** Folding large extracted PDF/xlsx text into one argv prompt can hit OS `E2BIG`; same exposure as ChatManager but worth noting.
238
+
239
+ ### OPEN DECISIONS (the human must resolve these before implementation — this is the next phase)
240
+
241
+ 1. **Do global routes `/loops` and `/docs` (App.tsx:263-269) still function in Agent Mode, or is the entire center replaced by `AgentModeSurface` regardless of URL?** Determines whether the `uiMode` branch wraps only the project-route block or the whole `<Routes>`.
242
+ 2. **Is `BottomPanel`+`StatusBar` truly hoisted to `DesktopApp` (single instance, both modes) or duplicated into `AgentModeSurface`?** Hoisting is cleaner but edits `ProjectLayout`; confirm the acceptable refactor scope. (Recommendation across slices: single hoisted instance.)
243
+ 3. **Is there any footer/StatusBar in Agent Mode at all?** If not, `statusBarHeight` passed to `BottomPanel` is 0 and the terminal chevron entry point lives solely in the right sidebar.
244
+ 4. **Where does the terminal render vertically in Agent Mode** — under the agent center only, or spanning full width beneath both the agent surface and the `AgentWorkspaceSidebar`? (Today's BottomPanel spans only the routed main column.)
245
+ 5. **Can Agent Mode be entered with no active project (Home group only), or does the toggle force an active project?** Affects `activeProject` gating on `AgentWorkspaceSidebar` and the hoisted `BottomPanel`.
246
+ 6. **When switching Kanban→Agent while the panel is `restored`/`maximized`, does it stay open (same session) or auto-hide?** And is the sidebar Terminal button open-only (`setVisibility('restored')`) or a toggle (`togglePanel`)?
247
+ 7. **Does entering Agent Mode call `open()` (mounts the now-suppressed floating panel) or a new lighter `refreshConversations`?** (Recommendation: expose a standalone refresh.)
248
+ 8. **Does `[New agent]` pin to the currently-active project or always create a Home (null) conversation?** And does `newConversation` gain an explicit `projectId` param, or read `useDesktop` internally? (Recommendation: explicit arg keeps the context decoupled from routing.)
249
+ 9. **Persistence key + shape for per-project tree expansion** (Set vs Record) and default (expand active only vs expand all vs remember last).
250
+ 10. **In Agent Mode, does a project-row click with zero conversations still toggle (empty group) or only set active?**
251
+ 11. **Does the inline EMPTY state reuse the exact ACTIVE composer (variant-driven layout) or a dedicated "Plan, Build" card variant?** (Recommendation: same composer, variant layout, to guarantee attachment parity.)
252
+ 12. **Where do provider/model/tier controls + the project/Home indicator live in Agent Mode** — inside the composer row (as today) or a header strip?
253
+ 13. **When entering Agent Mode with an existing active conversation, show ACTIVE immediately or reset to EMPTY?** (`active` persists across mode toggles.)
254
+ 14. **Files: split vs full-replace when opened.** (Recommendation: SPLIT — thread + collapsible code pane — since the workflow is "chat while inspecting code"; confirm nested-split min-width math fits `MIN_MAIN_WIDTH=520`+`MIN_TREE_WIDTH=240`.)
255
+ 15. **Where does code selection persist** — per-conversation (reopen where left off) or per session — and is it context-state only (design keeps Agent Mode off `/code`) or also reflected as a shareable query param?
256
+ 16. **Does the embedded Files pane keep the provenance toolbar/filters (jobId/ticketId), or a stripped tree+viewer?** (Provenance filters are URL-driven today; keeping them needs the same controlled-state treatment.)
257
+ 17. **Does `[Search]` open the same generic palette (projects/commands/jobs/nav) or a scoped agent-conversation search?** (Recommendation: reuse generic palette for v1.)
258
+ 18. **Agent attachment storage key scheme:** distinct root `~/.specrails/agent/<conversationId>/attachments/` (recommended) vs reusing `AttachmentManager` with a synthetic slug/ticketKey.
259
+ 19. **What backs attachment storage for unpinned "Home" conversations with no active/pinned project** — is an active project mandatory to attach, or do we introduce app-global attachment storage?
260
+ 20. **Do agent turns persist their attachment id list** (new migration on `agent_messages` / new table) so historical turns re-render pills, or is display-on-send enough (folded into prompt only)?
261
+ 21. **Do we meter agent turns in `ai_invocations` now** (none today) or keep agent unmetered and only fold attachments?
262
+ 22. **Images on codex/gemini:** block/disable image attachments for non-claude providers, or attempt native image flags? What parity bar is acceptable for v1?
263
+ 23. **Does the agent composer replicate the auto-send-capture-feedback behavior (`sendCaptureFeedback`)** or only queue captures as chips that ride the next manual message?
264
+ 24. **Are the 25MB per-file cap and `ATTACHMENT_ACCEPT_MIME` allowlist acceptable unchanged for agent mode**, or does agent mode want different limits/types?
265
+ 25. **Attachment transport at send:** pre-upload then pass ids (Explore model, recommended for streaming compat) vs multipart on the send call itself.
266
+
267
+ ---
268
+
269
+ ## 11. RESOLVED DECISIONS (human sign-off, 2026-07-01)
270
+
271
+ The 25 open decisions are resolved as follows. The 4 taste-forks were decided by the human; the rest take the recommended defaults.
272
+
273
+ **Human-decided forks:**
274
+ - **D1 → Loops/Docs STAY in Agent Mode.** The center branch is `isInSetup ? Wizard : (location==='/loops'||'/docs') ? <Routes> : uiMode==='agent' ? <AgentModeSurface/> : <Routes>`. Global routes render in the center without leaving Agent Mode; only project-route/dashboard content is replaced by the agent surface.
275
+ - **D14 → Files = SPLIT (Cursor-style).** Thread stays visible; `CodePage` opens in a resizable secondary pane beside it, with a maximize toggle to widen the code pane (Cursor behaviour). Honour `MIN_MAIN_WIDTH=520`+`MIN_TREE_WIDTH=240`.
276
+ - **D5 → Agent Mode CAN be entered with no active project.** Home-group (null-pinned) conversations are first-class. `AgentWorkspaceSidebar` Browser/Terminal/Files are disabled until a project is active; **file/paste/drag attachments still work** (they are conversation-keyed, project-independent). Only Browser-capture requires an active project (sessions are project-scoped).
277
+ - **D22 → Images: NATIVE FLAGS, verified against real binaries (2026-07-01).** Empirical `--help` check on the installed CLIs:
278
+ - **codex-cli 0.141.0 → `-i, --image <FILE>...`** (present on both `codex` and `codex exec`). Wire this into `buildCodexArgs` (codex-adapter:75) — real native image support.
279
+ - **claude 2.1.197 →** images via `@<abs-path>` in the prompt, resolved natively (unchanged).
280
+ - **gemini 0.49.0 → NO dedicated image flag** (`-i` is `--prompt-interactive`). Relies on `@path` in-prompt injection (already emitted by `getClaudeArgs`); the Gemini model is multimodal so it *may* vision-load it under `-p`, but this is **UNVERIFIED** and must be smoke-tested live during implementation. Fallback: if it doesn't load, images degrade to claude+codex only and are disabled in the composer for gemini convos.
281
+ - **Contract:** add `supportsImageInput` to `ProviderCapabilities` (types.ts:91-120) and gate the composer's image affordance on the flag (never on `provider === id`). Add a new image-paths field on `SpawnOptions` threaded through `buildCodexArgs` (emits `--image`); claude/gemini keep the `@path` route.
282
+
283
+ **Default-resolved:**
284
+ - **D2/D3 →** Single hoisted `BottomPanel`+`StatusBar` in `DesktopApp` (remove ProjectLayout copy). StatusBar exists in both modes (maximize math needs its height); footer chevron gated to `uiMode==='kanban'`.
285
+ - **D4 →** Terminal spans full width beneath surface + `AgentWorkspaceSidebar`.
286
+ - **D6 →** Entering Agent Mode does NOT auto-hide an open panel. Sidebar Terminal button = `togglePanel(activeProjectId)`.
287
+ - **D7 →** Expose a standalone `refreshConversations` (do NOT call `open()`).
288
+ - **D8 →** `newConversation(projectId?)` explicit arg; `[New agent]` defaults the pin to `activeProjectId` (null ⇒ Home).
289
+ - **D9 →** Expansion state = `Set<string>` in localStorage `specrails-desktop:agentTreeExpanded`, default-expand the active project.
290
+ - **D10 →** Project-row click always sets active + toggles expand (even with zero conversations).
291
+ - **D11 →** EMPTY reuses the ACTIVE composer via `variant` layout (guarantees attachment parity), styled as the centered "Plan, Build" card.
292
+ - **D12 →** provider/model/tier + project/Home indicator stay in the composer row.
293
+ - **D13 →** Entering Agent Mode with an existing `active` shows ACTIVE immediately.
294
+ - **D15/D16 →** Code selection persists per-conversation in Agent-Mode context state (NOT the URL); embedded Files pane keeps the provenance toolbar/filters (controlled-state, not URL-driven).
295
+ - **D17 →** `[Search]` reuses the generic CommandPalette (synthetic Cmd+K dispatch) for v1.
296
+ - **D18/D19 →** Distinct storage root `~/.specrails/agent/<conversationId>/attachments/`; attachments are conversation-keyed and work for Home convos with no project.
297
+ - **D20 →** Persist the attachment id list per turn (new nullable `attachments` JSON column on `agent_messages`) so history re-renders pills.
298
+ - **D21 →** METER agent turns in `ai_invocations` now (`surface='agent'` or similar), for Analytics coherence.
299
+ - **D23 →** Agent composer queues captures as chips that ride the next manual message (no auto-send-feedback in v1).
300
+ - **D24 →** Keep 25MB cap + `ATTACHMENT_ACCEPT_MIME` allowlist unchanged.
301
+ - **D25 →** Pre-upload then pass ids (streaming-compatible).
@@ -0,0 +1,184 @@
1
+ # Specrails Desktop — Design Synthesis (Visual System · Mechanical Fixes · Rails Redesign)
2
+
3
+ Verified against working tree: `client/package.json` has no animate plugin; `globals.css:64` = `--radius: 0.75rem`; `RailRow.tsx:289` shadow uses undefined `--accent-info`; `glow-*` utilities exist at `globals.css:345-350`; 175 `aurora-light:` dual-class sites.
4
+
5
+ ---
6
+
7
+ # PART 1 — VISUAL SYSTEM
8
+
9
+ The canon below is the **majority-existing code**, promoted to law. Reference implementations: **AnalyticsPage** (layout/cards/type), **agent-chat family** (motion/glass/tokens), **Add Spec + StatusBar** (token hygiene). Nothing here invents a new aesthetic; it deletes the two losing dialects (raw-palette+`aurora-light:` forks, dracula literals) and keeps the winning one.
10
+
11
+ ## 1.1 Color: one token dialect
12
+ - **Only semantic tokens.** `accent-primary` (brand/AI/specs) · `accent-secondary` (rails) · `accent-info` (running/live/links/inline-code) · `accent-success` (done/go) · `accent-warning` (cost/caution) · `accent-highlight` (explore) · `destructive` (error/delete) · `muted`/`muted-foreground` (inactive). The 175-site `text-blue-400 aurora-light:text-accent-info` idiom collapses to the token alone — the token is already themed for all five themes.
13
+ - **Text on accent backgrounds = `text-background`** (the `SetupWizard.tsx:550` pattern), never `text-white`. On `bg-primary`/`bg-destructive` use their `-foreground` pair.
14
+ - **Glows** only via the existing utilities `glow-primary/-secondary/-info/-success/-warning/-highlight` (`globals.css:345-350`) or the `color-mix(in srgb, var(--color-accent-*) N%, transparent)` form. `hsl(var(--accent-*)/N)` is INVALID (tokens are `--color-*` holding complete `hsl()` values) — it silently renders nothing.
15
+ - **Status mapping (frozen):** running = `accent-info` + `Loader2` spin · success = `accent-success` + `CheckCircle2` · failed = `destructive` + `XCircle` · canceled/queued = `muted` + `Ban` · warning/cost = `accent-warning` + `AlertTriangle`/`DollarSign`.
16
+ - **Priority canon** (the ProposeSpecModal set — the only fully tokenized one): critical=`destructive`, high=`accent-warning`, medium=`accent-info`, low=`muted`.
17
+ - **Left sidebar title = accent-primary, right sidebars = accent-secondary** (existing rule; document, keep).
18
+
19
+ ## 1.2 Icon scale (lucide, `w-N h-N` pair idiom — never `size={}` props)
20
+ | Context | Size | Margin |
21
+ |---|---|---|
22
+ | Nav rows, page/dialog headers, icon-buttons ≥ h-8, palette rows | `w-4 h-4` | `mr-2` |
23
+ | Dense rows, `size="sm"` button leading icons, toolbars, settings headings | `w-3.5 h-3.5` | `mr-1.5` |
24
+ | Chips/badges/breadcrumbs/`h-7` buttons/select chevrons (ui/select `w-3` is the codified convention) | `w-3 h-3` | `mr-1` |
25
+ | Micro close/status inside pills & tab strips | `w-2.5 h-2.5` | — |
26
+ | Step/hero icons | `w-6 h-6` | — |
27
+ | Page-level empty-state pictograms (`w-6` for narrow panels) | `w-8 h-8` | — |
28
+ Chevron next to a row icon = one tier below the row icon. Stroke = lucide default 2 (exceptions: TitleBar window controls, PanelChevronButton). **No emoji, no text glyphs (`×`, `↺`, `✓`, `+`) where a lucide icon exists** — `kbd` glyphs (`⌘⇧↵`) exempt.
29
+
30
+ ## 1.3 Lines, radius, shadow (three-stop border system)
31
+ Prereq: delete/rename `--radius: 0.75rem` (`globals.css:64`) so bare `rounded` stops meaning 12px; raise `[data-theme="specrails"] --color-border` from `/0.11` to `~/0.16` for modifier headroom. Sanctioned opacity stops: **full, `/60`, `/40`, `/50` (cards only)**. Kill list: `/10 /20 /25 /70`, `shadow-black/*`, raw `rgba()`/`hsl()` shadow literals, bare `border` without color, `ring-1 ring-border` as pseudo-border.
32
+ | Tier | Recipe |
33
+ |---|---|
34
+ | App-frame chrome hairline (sidebars, title/status bar, pane splits, board headers) | `border-border` (full — the token IS the hairline) |
35
+ | Inset divider (rows inside cards/modals, `divide-*`) | `border-border/40` |
36
+ | Page block card | `rounded-xl border border-border/50 bg-card/40 p-4` (hero: `p-5 bg-gradient-to-br from-card/80 to-card/40`) |
37
+ | Row card | `rounded-lg border-border/40 bg-card/60 hover:border-border/60` |
38
+ | Menu/popover | `rounded-lg border-border/60 bg-popover/95 backdrop-blur-md shadow-xl` |
39
+ | Tooltip | `rounded-md border-border shadow-md` (as-is) |
40
+ | Centered modal | `rounded-xl border-border/40 bg-popover shadow-2xl backdrop-blur-md` |
41
+ | Floating glass (agent bubble/panel, dock) | `rounded-2xl border-border/60 bg-card/90 shadow-2xl backdrop-blur-xl` |
42
+ | Form controls | `rounded-md` (input/button/select trigger all match) |
43
+ | Focus | `focus-visible:ring-2 focus-visible:ring-ring` everywhere |
44
+ | Scrims | standard `bg-black/60 backdrop-blur-sm`; immersive (lightbox/onboarding) `bg-black/80 backdrop-blur-md` |
45
+
46
+ ## 1.4 Motion vocabulary
47
+ Prereq (fix #1): `npm i tw-animate-css` + `@import "tw-animate-css";` in `globals.css` — resurrects the ~22 authored-but-dead `animate-in/out` surfaces.
48
+ - **150ms micro** — hover colors, opacity, chevron rotate, overlay fades. Bare `transition-colors` (default duration).
49
+ - **200ms structural-small** — modal/popover/tooltip enter: `animate-in fade-in-0 zoom-in-95 duration-200`, exit via `data-[state=closed]:animate-out`.
50
+ - **250–300ms structural-large** — panel slides (terminal, sidebars), height disclosures (`grid-template-rows 0fr→1fr` trick), `ease-out`.
51
+ - **400–500ms ambient** — progress bars, status frames only.
52
+ - **Easings:** enters `ease-out`; house arrival overshoot `cubic-bezier(0.34,1.56,0.64,1)` registered once as `--ease-arrive` in `@theme`, reserved for floating-object arrival (chat panel, chips, swipe snap-back); springs `stiffness 350–420 / damping 28–34` for layout morphs (AgentModeSurface/AgentBubble values).
53
+ - **Morph vs fade:** `layoutId` spring when the SAME object persists (chip⇄panel, tab underline, kanban cross-column); fade+zoom for transient surfaces. Never `transition-all` on size-changing containers (use `transition-[width]` etc.).
54
+ - **Reduced motion:** one `<MotionConfig reducedMotion="user">` at app root; `motion-safe:` on decorative loops; global `prefers-reduced-motion` clamp in `globals.css` with `.motion-keep` escape for spinners.
55
+
56
+ ## 1.5 Spacing & type rhythm
57
+ - Page gutter `px-6`; scrolling pages `py-6`; full-height pages: header `px-6 pt-4 pb-3 border-b border-border`.
58
+ - Three container archetypes: **workspace** full-bleed (Dashboard, Code, LoopBuilder) · **data page** `max-w-6xl mx-auto w-full` (Analytics, Jobs, JobDetail, Loops) · **reading/forms** `max-w-3xl` (Settings, Docs).
59
+ - Section gap `gap-6`/`space-y-6`; heading→content `space-y-2`.
60
+ - Two sanctioned header voices: **page header** `h1 text-lg font-semibold` + `text-xs text-muted-foreground mt-0.5` subtitle; **workspace pane header** `px-4 h-12 border-b border-border/40` + `text-sm font-semibold` tinted h2 (SpecsBoard/RailsBoard form). No third variant.
61
+ - Type scale: kicker `text-xs font-semibold uppercase tracking-wider text-muted-foreground` · card title `text-sm font-semibold` · body `text-sm` · metadata `text-xs` · dense-chrome floor `text-[11px]` · badge floor `text-[10px]` (**`text-[9px]` banned**) · display numbers `text-5xl font-semibold tabular-nums tracking-tight`, heroes only.
62
+ - Contrast floor: never below `text-muted-foreground/50` under 12px. Interactive elements never below `text-[11px]`, native form controls never below `h-7 text-xs`.
63
+ - One shared `<EmptyState icon title description action?>` (RecentJobs pattern: dashed card, `w-8 h-8 opacity-20` icon, `text-sm font-medium` title, `text-xs text-muted-foreground/60` hint, `py-12`).
64
+
65
+ ## 1.6 Concept → icon + accent (canonical)
66
+ | Concept | Icon | Accent |
67
+ |---|---|---|
68
+ | Spec/ticket entity | `Ticket` | accent-primary |
69
+ | Rails / execution lane | `Layers` (exclusive — epic filter → `FolderTree`, batch → `ListChecks`) | accent-secondary |
70
+ | Job | `Briefcase` | accent-info |
71
+ | Job running / in-flight action | `Loader2` spin | accent-info |
72
+ | Job success | `CheckCircle2` | accent-success |
73
+ | Job failed | `XCircle` | destructive |
74
+ | Job canceled | `Ban` | muted-foreground |
75
+ | App agent (chat/mode) | `Bot` | accent-primary |
76
+ | Pipeline agents/profiles | `BrainCircuit` | accent-primary |
77
+ | MCP | `Plug` | accent-info |
78
+ | AI generate/magic | `Sparkles` | accent-primary (one tint) |
79
+ | Explore | `MessagesSquare` | accent-highlight |
80
+ | Quick spec | `Zap` | accent-secondary |
81
+ | Sidebar chat / comments | `MessageSquare` / `MessageSquareText` | accent-primary |
82
+ | Project analytics / Desktop analytics | `BarChart3` / `PieChart` | neutral |
83
+ | Docs / Code / Terminal / Loops | `BookOpen` / `Code2` / `Terminal` / `Workflow` | neutral |
84
+ | Cost | `DollarSign` | accent-warning |
85
+ | Refresh data / retry op / browser reload | `RefreshCw` / `RotateCcw` / `RotateCw` | — |
86
+ | Send | `Send w-3.5` | — |
87
+ | Provider | `Cpu` (kill the `🤖⚡✨` emoji) | neutral |
88
+
89
+ **Regression guards (CI grep-deny, extends the existing `dracula-*` guard):** `aurora-light:` outside globals.css · `#50fa7b|#ff5555|#f1fa8c|#6272a4|#44475a|rgba\(139,233|rgba\(189,147` · `text-white` on `bg-accent`/`bg-emerald`/`bg-green` lines · `hsl\(var\(--` · `border-border/(10|20|25|70)` · `shadow-black/` · `text-\[9px\]` · bare `rounded[" ]` (post-codemod).
90
+
91
+ ---
92
+
93
+ # PART 2 — MECHANICAL FIX LIST (top 40, deduplicated, ranked by visibility)
94
+
95
+ 1. `client/package.json` + `client/src/globals.css:2` — missing animate plugin → `npm i tw-animate-css` + `@import "tw-animate-css";`. Single change lights up ~22 dead surfaces (dialog.tsx:31/75/98, select.tsx:66, tooltip.tsx:17, TicketDetailModal:338, ExploreSpecShell:768, JobDetailModal:190, lightbox, AiEditShell:179, ProposeSpecModal:451, SmashConfirmModal:73, OnboardingWizard:725/732). Highest ROI line in the entire audit.
96
+ 2. `client/src/globals.css:64` — `--radius: 0.75rem;` → delete (nothing consumes it; it hijacks bare `rounded` to 12px, inverting the radius scale at 254 sites). Follow with codemod: bare `rounded` → `rounded-sm` (chips/kbd) / `rounded-md` (controls).
97
+ 3. `components/ui/badge.tsx:14-20` — raw `bg-emerald-500/20 … aurora-light:*` pairs → semantic alone: `success: bg-accent-success/15 text-accent-success border-accent-success/30`, `running: …accent-info…`, `warning: …accent-warning…`, `failed: …destructive…`, `queued/canceled: bg-muted text-muted-foreground border-border`. The shared status primitive — every job/status chip inherits.
98
+ 4. `components/RailRow.tsx:289,310,312,530` — invalid `shadow-[…hsl(var(--accent-info)/0.35)…]` (never rendered) → `glow-info` utility or `color-mix(in_srgb,var(--color-accent-info)_35%,transparent)` form; `:554` delete the duplicate hardcoded dot `bg-emerald-400 shadow-[0_0_4px_hsl(142_70%_56%/0.8)]` → `bg-accent-success` + fixed :310 shadow.
99
+ 5. `components/CommandGrid.tsx:32-86` — dracula rgba glow literals → `hover:glow-info hover:border-accent-info/50` (map per card accent); `:225/:227` — `Zap w-3.5` → `w-4 h-4` to match sibling ArrowRight.
100
+ 6. `components/ProjectHealthWidget.tsx:51,92,110-124,141` + `components/HealthIndicatorBadge.tsx:28-31` — `#50fa7b/#f1fa8c/#ff5555` → `text-accent-success/text-accent-warning/text-destructive` (`var(--color-*)` for Recharts fills); ProjectHealthWidget:124 `AlertCircle` → `Ban`.
101
+ 7. `components/terminal/TerminalSearchOverlay.tsx:94,105-138` — `#f8f8f2/#6272a4/#44475a` → `text-foreground/bg-accent-primary/40/bg-muted`; `:107,115,126` `size={14}` → `className="w-3.5 h-3.5"`.
102
+ 8. `types/context-scope.ts:51-54` — `text-white` ×4 in `submitAccentForTier` → `text-background` (white on pastel accent-warning is unreadable).
103
+ 9. `components/UltracodeLaunchDialog.tsx:91` — `bg-emerald-500 text-white hover:bg-emerald-400 focus-visible:ring-emerald-400` → `bg-accent-success text-background hover:bg-accent-success/90 focus-visible:ring-accent-success`; `:58` `h-4.5 w-4.5` → `h-4 w-4` (only off-scale icon in the app).
104
+ 10. `components/RailControls.tsx:78-85` — raw red/emerald/amber + `aurora-light:` → `text-destructive/text-accent-success/text-accent-warning`; `:42` `hsl(191_97%_77%/0.22)` → `color-mix(in srgb, var(--color-accent-info) 22%, transparent)`; `:88` add `disabled:opacity-40`; bump button `h-5 w-5`→`h-7 w-7`, icon `w-2.5`→`w-3.5`.
105
+ 11. `components/RailsBoard.tsx:167` — `text-emerald-400 bg-emerald-400/10` → `text-accent-success bg-accent-success/10`.
106
+ 12. `components/ActiveJobCard.tsx:78,83` — `border-blue-500/30 bg-blue-500/5` / `text-blue-400` → `border-accent-info/30 bg-accent-info/5` / `text-accent-info` (copy JobStatusPanel:189).
107
+ 13. `components/JobStatusPanel.tsx:191-199` — success/fail frame raw → `border-accent-success/30 bg-accent-success/5 text-accent-success` / destructive set; `:219,317,342` `text-yellow-400` → `text-accent-warning`; `:365` `text-cyan-400/80` → `text-accent-info`; `:423,447,450` `text-muted-foreground/30` → `/50`.
108
+ 14. `components/PipelineProgress.tsx:34-87` — all phase-state raw palette → `accent-info` (running) / `accent-success` (done) / `destructive` (failed).
109
+ 15. `components/TicketStatusIndicator.tsx:33-47,138-140` — raw status dots/chips → tokens per §1.1 status map.
110
+ 16. `components/Navbar.tsx:38-43` — connection pill raw blue/orange/green/red → `accent-info/accent-warning/accent-success/destructive`.
111
+ 17. `pages/AnalyticsPage.tsx:413-417` — load-failure banner `accent-warning` → `border-destructive/30 bg-destructive/10 text-destructive` (errors are destructive, app-wide).
112
+ 18. `components/analytics/SpendingTimeline.tsx:82` — smash series `var(--color-accent-highlight)` (duplicates explore) → `var(--color-accent-secondary)`.
113
+ 19. Scrim normalization — `JobDetailModal.tsx:183` `black/50`→`black/60 backdrop-blur-sm`; `ai-edit/AiEditShell.tsx:179` `black/40`→`black/60`; `TicketDetailModal.tsx:798` + `SplitViewShell.tsx:177` `black/70` (no blur)→`black/60 backdrop-blur-sm`; `OnboardingWizard.tsx:725` + `AttachmentPreviewLightbox.tsx:74` → `black/80 backdrop-blur-md`.
114
+ 20. `components/ui/dialog.tsx:75,98` — `border-border/30 … shadow-xl` → `border-border/40 … shadow-2xl` (base dialog currently weaker than its own flagship children).
115
+ 21. `components/ui/select.tsx:17` — SelectTrigger `rounded-lg` → `rounded-md` (matches input/button); `:24` add `transition-transform duration-150 group-data-[state=open]:rotate-180` to chevron.
116
+ 22. `components/ui/card.tsx:9` — append `transition-colors` (consumers adding `hover:border-*` currently snap).
117
+ 23. `components/CommandPalette.tsx:141` — `border-border/30` → `border-border/40`; after fix #1, append `animate-in fade-in-0 zoom-in-95 slide-in-from-top-2 duration-150 ease-out` + dimmed overlay `bg-black/50 backdrop-blur-sm`.
118
+ 24. `components/TicketDetailModal.tsx:337-338` — drop `shadow-black/50` (keep `shadow-2xl`); `:623` popover `rounded border-border/50 shadow-lg` → `rounded-lg border-border/60 shadow-xl`.
119
+ 25. App-frame hairline continuity — `components/StatusBar.tsx:84` `border-t border-border/30` → `border-border`; `components/ChatPanel.tsx:79,114,211,220` structural `/30` → full; `components/agent-chat/AgentModeCodePane.tsx:74` `/60` → full (sibling AgentWorkspaceSidebar:77 already full).
120
+ 26. `components/settings/TerminalSettingsSection.tsx:196,262` — bare `border` (renders `currentColor`, harsh) → add `border-input`; `:266` `border-t` → `border-t border-border`.
121
+ 27. Invisible dividers — `components/SpecComparePicker.tsx:96` `divide-border/10` → `/40`; `/20` hairlines at `RailRow.tsx:645`, `JobStatusPanel.tsx:237`, `JobComparisonModal.tsx:43`, `LogViewer.tsx`, `ChatInput.tsx`, `InstallInstructionsModal.tsx`, `TicketGridView.tsx` → `/40`; `RailRow.tsx:690` `/25` → `/40`; `pages/CodePage.tsx:292` `/70` → `/60`.
122
+ 28. Analytics skeleton/loaded border flash — `components/analytics/{CostScatter:43,ModelBreakdown:20,ProviderBreakdownCard:39,QuickVsExploreCard:93,SpendingHero:59,SpendingTimeline:13,TopTicketsCrossSurface:14}` skeleton `border-border/40` → `/50`.
123
+ 29. H1 normalization — `pages/JobsPage.tsx:95` `text-sm font-semibold` → `text-lg font-semibold`; `pages/AnalyticsPage.tsx:313` `text-base` → `text-lg`; `pages/DesktopAnalyticsPage.tsx:340` `text-sm` → `text-lg`; `pages/ActivityFeedPage.tsx:78` `text-sm font-medium` → `text-lg font-semibold`.
124
+ 30. Gutter normalization — `pages/SettingsPage.tsx:248` `max-w-2xl mx-auto px-4` → `max-w-3xl mx-auto px-6`; `pages/JobsPage.tsx:93` `max-w-4xl px-4` → `max-w-6xl px-6`; `pages/JobDetailPage.tsx:395,348,360` `px-4` → `px-6`; `pages/AnalyticsPage.tsx:308-310` `p-4 pb-12`/`-mx-4 px-4` → `px-6 pt-6 pb-12`/`-mx-6 px-6`.
125
+ 31. `text-[9px]` purge → `text-[10px]`: `SpecCard.tsx:153-224` (or delete override, Badge base is 10px), `TicketPostitCard.tsx:244-286`, `TicketGridView.tsx:185-239`, `TicketListView.tsx:351-383`, `RecentJobs.tsx:368,377`, `DesktopAnalyticsPage.tsx:207`, `RailRow.tsx:600` (+ `tabular-nums`).
126
+ 32. 10px interactive controls — `TicketListView.tsx:218,234` `text-[10px]` → `text-[11px]`; `:251,266` `h-6 text-[10px]` → `h-7 text-xs`; `RecentJobs.tsx:200,216,283-292` → `text-[11px]`, `:233,240` → `h-7 text-xs`; `GlobalSettingsPage.tsx:554,643` checkbox labels → `text-xs`, `:438-681` descriptions `text-[10px]` → `text-[11px]`.
127
+ 33. `components/RailRow.tsx:693` — empty state `text-[10px] text-muted-foreground/30` + `border-border/25` → `text-[11px] text-muted-foreground/60` + `border-border/40` (the surface's main onboarding cue fails contrast).
128
+ 34. Destructive-red sweep — `SpecCard.tsx:235`, `RailRow.tsx:510,613-620`, `agents/PromptDialog.tsx:120` `bg-red-500 text-white hover:bg-red-600` → `bg-destructive text-destructive-foreground hover:bg-destructive/90`; `TicketGridView.tsx:495` `bg-red-950/10` → `bg-destructive/10`; `JobComparisonModal.tsx:90` `text-rose-400` → `text-destructive`.
129
+ 35. `text-white` sweep → `text-background` (or `-foreground` pair): `ProposeSpecModal.tsx:661`, `App.tsx:466`, `agent-chat/AgentComposer.tsx:267`, `AgentConversationView.tsx:69`, `jira/JiraIntegrationCard.tsx:93`, `ai-edit/AiEditShell.tsx:904`, `ChatPanel.tsx:86`, `LoopsPage.tsx:325`, `LoopBuilderPage.tsx:429`, `IntegrationsPage.tsx:255,632,676`; `FeatureProposalModal.tsx:259,272` `bg-green-600` → `bg-accent-success text-background`, `:314` `CheckCircle` → `CheckCircle2 text-accent-success`.
130
+ 36. Icon one-liners — `ArcSidebar.tsx:385` BarChart2→PieChart; `DesktopAnalyticsPage.tsx:339` TrendingUp→PieChart; `CommandPalette.tsx:261` FileText→BookOpen; `ActivityFeedPage.tsx:30` Zap→Loader2; `RecentJobs.tsx:183` ClipboardList→Briefcase; `AgentWorkspaceSidebar.tsx:3` TerminalSquare→Terminal + FileCode2→Code2; `GlobalSettingsPage.tsx:13` TerminalSquare→Terminal; `SetupWizard.tsx:451` Bot→Plug; `CodePage.tsx:4` RotateCw→RefreshCw.
131
+ 37. Markdown/code accent unification → `accent-info`: `ExploreSpecShell.tsx:1236-1240` (accent-primary→info), `SetupChat.tsx:13` + `TicketDetailModal.tsx:1040,1055` + `LogViewer.tsx:446,449` (cyan-300/blue-400→`text-accent-info`), `JobsPage.tsx:127` `prose-code:text-cyan-300` → `prose-code:text-accent-info`; Sparkles tint: `ProposeSpecModal.tsx:405`, `SpecDraftPanel.tsx:39`, `AiEditComposer.tsx:63` `text-primary/70` → `text-accent-primary`.
132
+ 38. Motion micro — terminal `duration-120` delete (`PanelChevronButton.tsx:32`, `TerminalSidebar.tsx:92,129,145`, `TerminalDragHandle.tsx:86`, `TerminalTopBar.tsx:126`); `ArcSidebar.tsx:401` + `ProjectRightSidebar.tsx:58` `transition-all` → `transition-[width]`; `SpecCard.tsx:124` + `RailRow.tsx:295,527` `animate-jiggle` → `motion-safe:animate-jiggle`; `RailRow.tsx:535` snap-back curve → `cubic-bezier(0.34,1.56,0.64,1)`.
133
+ 39. Reduced-motion consolidation — wrap the agent-chat portal root in `context/AgentChatContext.tsx` with one `<MotionConfig reducedMotion="user">`, delete the local one at `AgentModeSurface.tsx:41`; add the global `prefers-reduced-motion` clamp to `globals.css`.
134
+ 40. Glyph → lucide — `AddProjectDialog.tsx:284` `'✓'` → `<Check className="w-2.5 h-2.5"/>`; `ChatHeader.tsx:51` `'+'` → `<Plus className="h-3.5 w-3.5"/>`, `:61-100` four hand-rolled SVGs → `Trash2/Minimize2/Maximize2/X` `h-3.5`; `ModelSelector.tsx:211` `'↺'` → `<RotateCcw className="w-3 h-3"/>`; `×` dismissals (`ProposeSpecModal.tsx:527`, `ChatPanel.tsx:140`, `AttachmentChip.tsx:86`, `AttachmentsSection.tsx:140`, `AnalyticsPage.tsx:332`) → `<X className="w-3 h-3"/>`; `SpecsBoard.tsx:693` done-bucket drag tint `bg-emerald-500/[0.04] aurora-light:*` → `bg-accent-success/[0.06]`; `DashboardPage.tsx:1047` ghost `border-primary/40` → `border-accent-info/40`.
135
+
136
+ Deferred to a dedicated bulk change (mechanical but broad): the remaining ~40-file `aurora-light:` fork sweep (S1), `pages/AgentsPage.tsx:82-96` yellow banner, `TicketContextMenu`, `AiEditShell` body, `ActivityFeedPage` maps, kicker `font-medium→font-semibold` in 6 analytics files, `ui/card.tsx` recipe migration.
137
+
138
+ ---
139
+
140
+ # PART 3 — RAILS REDESIGN PROPOSAL
141
+
142
+ ## 3.1 Diagnosis (one sentence)
143
+ A single idle rail exposes **13 controls, six of them 10px native `<select>`s explained only by icon+`title`**, the primary action is the smallest target on the card (20px), a running rail is a **black box** (all config unmounts → layout jump; no loop/engine/model/phase shown), failure is **invisible after a 4s toast** (`RailStatus 'failed'` is dead code), and rail identity is three disagreeing coordinate systems (id-number vs array-index vs server-index) sitting on a localStorage/server split-brain.
144
+
145
+ ## 3.2 Target layout (normal density)
146
+
147
+ ```
148
+ ┌─ rail card ─ rounded-xl border-border/40 bg-card/60 ─────────────────┐
149
+ │ ⠿ ● Backend refactor 2 specs [▶ Run] [⋮] │ header h-9
150
+ │ ─────────────────────────────────────────────── border-border/40 ── │
151
+ │ #142 Fix auth token refresh ▲ high │ droppable body
152
+ │ #147 Add rate limiter ■ med │ (SpecCard rows)
153
+ │ ─────────────────────────────────────────────────────────────────── │
154
+ │ Implement · Claude · Sonnet · balanced · normal effort ⌄ │ config summary
155
+ └──────────────────────────────────────────────────────────────────── ┘
156
+ ```
157
+
158
+ - **Header:** grip (`GripVertical w-3.5 text-muted-foreground/30`) · status dot **`w-2 h-2`** (up from 6px; `bg-accent-success glow-success` running, `bg-destructive` failed, `bg-muted-foreground/25` idle) · free-form name (kill the forced `"Rail "` prefix wrapper at `DashboardPage.tsx:325`; server already models name-or-null) · ticket count `text-[10px] tabular-nums bg-muted/40` · **`[▶ Run]` = `h-7` labeled button** (`text-accent-success`, `w-3.5` icon — no longer a 20px ghost) · **`[⋮]` kebab** (Radix DropdownMenu, menu-tier recipe): Rename / Duplicate / Move all to Specs / Delete (`text-destructive`). Kebab replaces the undiscoverable 800ms long-press-jiggle on desktop; jiggle+swipe stay for touch.
159
+ - **Config summary strip (the key consolidation):** the six micro-selects collapse into **one readable sentence of chips** — `<Loop> · <Engine> · <Model> · <Profile> · <Effort>` at `text-[11px]`, each chip a real `ui/tooltip.tsx`-labeled target. Clicking any chip (or `⌄`) opens **one config popover** (menu-tier recipe, `rounded-lg border-border/60 bg-popover/95 backdrop-blur-md shadow-xl`, grid-rows disclosure at 250ms) containing proper **labeled Radix `ui/select`s** — Loop, Engine, Model, Profile, Reasoning effort — plus the **Interactive switch** (moved here from the transport row; it is config, not transport). Visible controls per idle rail: **13 → 5** (grip, name, Run, kebab, config strip). All selector visibility rules (multi-provider, claude-only profile, mode-derived model/effort) survive unchanged inside the popover.
160
+ - **Empty body:** `text-[11px] text-muted-foreground/60` on `border-border/40` dashed — "Drag specs here" finally visible.
161
+
162
+ ## 3.3 States and motion
163
+
164
+ | State | Card | Config strip | Right slot | Motion |
165
+ |---|---|---|---|---|
166
+ | **idle** | `border-border/40` | chips, editable | `▶ Run` (disabled `opacity-40` at 0 tickets) | drop-hover: body `bg-accent-info/[0.06]` + card `border-accent-info/50 glow-info` — ONE accent for one interaction (kill the `primary` variant at RailRow:530) |
167
+ | **running** | `border-accent-info/40` + **`glow-info`** (works once fix #4 lands) | **stays mounted**, `opacity-60 pointer-events-none` — no layout jump; chips show the resolved run (`loopName · provider · model` already returned by `rails-router.ts:86-105`, today thrown away) | `■ Stop` (`text-destructive`) + `Log` | below strip: live line `Loader2 w-3 animate-spin text-accent-info` + `elapsed · steps · lines` `text-[11px] tabular-nums`, plus **mini `PipelineProgress`** (compact variant of the existing component, fed by the same WS stream `RailMetricsContext` already consumes) with `AgentActivityChip`-style AnimatePresence crossfade on phase change |
168
+ | **done** | 800ms `border-accent-success/50 glow-success` wash, then back to idle | re-enabled | `▶ Run` | persistent last-run line replaces the live line: `CheckCircle2 w-3 text-accent-success` `Shipped 3 specs · 12m · $0.84` until next config touch; completion toast uses the rail **label**, not "Rail {{n}}" |
169
+ | **failed** | **persistent** `border-destructive/40` + dot `bg-destructive` (resurrect the dead `'failed'` status: `applyRailJobOutcome` at `RailsBoard.tsx:56-72` must set it — the `RailControls.tsx:81-98` retry affordance already exists, unreachable today) | re-enabled | `AlertTriangle` Retry + `Log` + dismiss-`X` | card does one `motion-safe` shake-in (4px, 300ms); state survives until retry/dismiss |
170
+
171
+ **Stop guard:** Stop cancels N billed jobs — replace bare kill with a 5s undo toast (`ThemedToaster` action pattern, `App.tsx:434`) or a one-line confirm; no new component needed.
172
+
173
+ ## 3.4 Reused components (nothing new except one popover)
174
+ `ui/select.tsx` + `ui/tooltip.tsx` + `ui/dropdown` primitives (Radix, already shipped) · `PipelineProgress` (add a `variant="mini"` — h-1.5 bar + phase label) · `Badge` (post fix #3, for ticket/status chips) · `AgentActivityChip` crossfade pattern (copy verbatim for phase/status text) · `glow-*` utilities (`globals.css:345-350`) · `TicketDetailModalProvider` (compact ticket-pill clicks, unchanged) · `MoveToRailPopover`, `UltracodeLaunchDialog` (kept as-is, minus emerald CTA per fix #9) · existing WS events + `GET /rails` enrichment (`activeLoopRuns.loopName/provider/model/iteration` — zero server work to show run identity) · shared `<EmptyState>` (new, but mandated app-wide by §1.5). Compact density is **retired as a separate design system**: it becomes the same card with the body collapsed — the compact branch is already the token-correct one, so normal density adopts its token language rather than maintaining two.
175
+
176
+ ## 3.5 Phased plan
177
+
178
+ **Phase 0 — identity + persistence prerequisite (structural bug fix, blocks everything).** Collapse the three coordinate systems: put the **server railIndex** on `RailState` as the single key; rename (`DashboardPage.tsx:329` `parseInt(railId)-1`), profile/engine/launch/stop (`:764,:820,:835` `findIndex`), and WS mirrors (`:362,:375,:404`) all route through it. Today, reordering/deleting a rail mis-routes PUTs and lights up the wrong card. Fix N+1 fetches by hoisting profile/loop fetches to `RailsBoard` (one fetch, pass down). Delete dead plumbing (`RailControls.tsx:31` `loopAvailable`/`onModeChange`, `DashboardPage.tsx:758-760`).
179
+
180
+ **Phase 1 — cosmetic reskin (pure class/markup, ships independently).** All rails-touching mechanical fixes (#4, #9, #10, #11, #31, #33, #34, #38): working glow-info running border, token status dot at `w-2 h-2`, `h-7` labeled Run/Stop, 11px floor on selects/pills/empty state (`h-6 text-[11px]` interim on the native selects), one drop accent (accent-info), destructive-token delete affordances, icon on the loop-model select, toast copy uses rail label + translated loop name (`DashboardPage.tsx:938,407-412`). **Config strip stays mounted while running** (disabled, not unmounted) — kills the layout jump with zero structural change. Add the run-identity chips from the already-delivered `activeLoopRuns` payload.
181
+
182
+ **Phase 2 — structural.** (a) **Rails fully server-side**: server already persists tickets/mode/profile/engine/name and broadcasts `rail.updated`; localStorage demotes to cache; lift the hardwired `[0,1,2]` (`rails-store.ts:59`) so rails 4+ exist for the mobile companion too. (b) **Config popover** replacing the six inline selects (the 13→5 consolidation above), Interactive as a switch inside it. (c) **Failed state wiring** end-to-end (`applyRailJobOutcome` → `'failed'`, retry/dismiss UI). (d) **Mini PipelineProgress + last-run summary line.** (e) **Kebab menu + Stop undo-toast + free-form names** (drop the `"Rail "` prefix contract, default label only when name is null). (f) Motion polish: spring arrival for added rails, FLIP (`motion` `layoutId`) for spec cards moving board⇄rail — same `LayoutGroup` work as the SpecsBoard cross-column fix, shared spring `{stiffness:350,damping:34}`.
183
+
184
+ Acceptance for "premium": no text below 11px on the surface, one accent per interaction, running rail answers "what is running, with what, how far along" without leaving the dashboard, failure is visible until acknowledged, and every glow actually renders.
package/docs/mcp.md CHANGED
@@ -154,10 +154,11 @@ bridge and connect to:
154
154
  http://127.0.0.1:4200/api/mcp
155
155
  ```
156
156
 
157
- with the scoped token as a bearer header:
157
+ with the scoped token in either header form (both are accepted):
158
158
 
159
159
  ```
160
160
  Authorization: Bearer <your mcp token>
161
+ X-Desktop-Token: <your mcp token>
161
162
  ```
162
163
 
163
164
  Get the token from **Settings ▸ MCP ▸ Copy token** (or read
@@ -167,6 +168,30 @@ HTTP-capable clients, not for exposing Specrails on a network. (`4200` is the
167
168
  default app port; if you've changed it, the panel's config reflects the real
168
169
  one.)
169
170
 
171
+ #### One-liners for the common CLIs
172
+
173
+ **Claude Code** (HTTP transport):
174
+
175
+ ```bash
176
+ claude mcp add --transport http specrails http://localhost:4200/api/mcp \
177
+ --header "X-Desktop-Token: <your mcp token>"
178
+ ```
179
+
180
+ **Gemini CLI** (HTTP transport — same shape):
181
+
182
+ ```bash
183
+ gemini mcp add --transport http specrails http://localhost:4200/api/mcp \
184
+ --header "X-Desktop-Token: <your mcp token>"
185
+ ```
186
+
187
+ **Codex CLI** (stdio — register the bundled bridge; copy the exact bridge
188
+ command from **Settings ▸ MCP ▸ Copy config**, it embeds the app's bundled
189
+ Node + script path):
190
+
191
+ ```bash
192
+ codex mcp add specrails -- <bridge command from Settings ▸ MCP>
193
+ ```
194
+
170
195
  ## Security model
171
196
 
172
197
  - **A scoped token, not the master token.** The MCP surface is authenticated by