manigent-server 0.1.0__py3-none-any.whl

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 (1004) hide show
  1. manigent/_catalog/mcps/chess/README.md +38 -0
  2. manigent/_catalog/mcps/chess/mcp.json +9 -0
  3. manigent/_catalog/mcps/registry.json +9 -0
  4. manigent/_catalog/skills/brief/SKILL.md +339 -0
  5. manigent/_catalog/skills/broaden-options/SKILL.md +133 -0
  6. manigent/_catalog/skills/calendar/SKILL.md +44 -0
  7. manigent/_catalog/skills/calendar/starter_config.jsonc +25 -0
  8. manigent/_catalog/skills/gmail/SKILL.md +73 -0
  9. manigent/_catalog/skills/google-calendar/SKILL.md +44 -0
  10. manigent/_catalog/skills/google-drive/SKILL.md +42 -0
  11. manigent/_catalog/skills/google-drive-write/SKILL.md +45 -0
  12. manigent/_catalog/skills/google-drive-write/starter_config.jsonc +45 -0
  13. manigent/_catalog/skills/grill-me/SKILL.md +173 -0
  14. manigent/_catalog/skills/homekit/SKILL.md +37 -0
  15. manigent/_catalog/skills/http-api/SKILL.md +117 -0
  16. manigent/_catalog/skills/ib/SKILL.md +89 -0
  17. manigent/_catalog/skills/ib/starter_config.jsonc +35 -0
  18. manigent/_catalog/skills/mailbox/SKILL.md +47 -0
  19. manigent/_catalog/skills/mailbox/starter_config.jsonc +38 -0
  20. manigent/_catalog/skills/media/SKILL.md +93 -0
  21. manigent/_catalog/skills/media/starter_config.jsonc +63 -0
  22. manigent/_catalog/skills/osxphotos/SKILL.md +41 -0
  23. manigent/_catalog/skills/photo-story/SKILL.md +285 -0
  24. manigent/_catalog/skills/playwright-browser/SKILL.md +291 -0
  25. manigent/_catalog/skills/playwright-browser/starter_config.jsonc +44 -0
  26. manigent/_catalog/skills/playwright-save-skill/SKILL.md +149 -0
  27. manigent/_catalog/skills/registry.json +166 -0
  28. manigent/_catalog/skills/web-artifacts/SKILL.md +194 -0
  29. manigent/api/action_executor.py +404 -0
  30. manigent/api/action_validator.py +531 -0
  31. manigent/api/actions.py +221 -0
  32. manigent/api/client.py +558 -0
  33. manigent/api/control.py +806 -0
  34. manigent/api/edge_protocol.py +123 -0
  35. manigent/api/fs_protocol.py +450 -0
  36. manigent/api/host_protocol.py +298 -0
  37. manigent/api/managed_job_client.py +124 -0
  38. manigent/api/responses.py +342 -0
  39. manigent/api/retry_transport.py +178 -0
  40. manigent/api/turn_protocol.py +78 -0
  41. manigent/cli_lifecycle.py +467 -0
  42. manigent/cli_server.py +978 -0
  43. manigent/doctor.py +1167 -0
  44. manigent/hub_protocol/canonical.py +375 -0
  45. manigent/hub_protocol/codes.py +171 -0
  46. manigent/hub_protocol/constants.py +71 -0
  47. manigent/hub_protocol/envelope.py +218 -0
  48. manigent/hub_protocol/frames.py +788 -0
  49. manigent/hub_protocol/http.py +260 -0
  50. manigent/hub_protocol/ids.py +170 -0
  51. manigent/hub_protocol/keys.py +195 -0
  52. manigent/hub_protocol/text.py +75 -0
  53. manigent/llm/base.py +1072 -0
  54. manigent/llm/prompt_builder.py +2450 -0
  55. manigent/llm/triggers.py +68 -0
  56. manigent/models/agent.py +2905 -0
  57. manigent/models/agent_bundle.py +298 -0
  58. manigent/models/brief_tab.py +311 -0
  59. manigent/models/chat_message.py +231 -0
  60. manigent/models/chatroom.py +1108 -0
  61. manigent/models/context.py +1159 -0
  62. manigent/models/desk_label.py +836 -0
  63. manigent/models/desk_read_state.py +220 -0
  64. manigent/models/desk_sign_off.py +267 -0
  65. manigent/models/desk_sop.py +965 -0
  66. manigent/models/desk_todo.py +1603 -0
  67. manigent/models/desk_todo_link.py +404 -0
  68. manigent/models/file_cache.py +75 -0
  69. manigent/models/host.py +732 -0
  70. manigent/models/install_token.py +401 -0
  71. manigent/models/invitation_code.py +162 -0
  72. manigent/models/knowledge_pack.py +552 -0
  73. manigent/models/managed_job.py +295 -0
  74. manigent/models/model_config.py +564 -0
  75. manigent/models/model_defaults.py +238 -0
  76. manigent/models/onboarding_plan.py +161 -0
  77. manigent/models/participant.py +449 -0
  78. manigent/models/persistable.py +892 -0
  79. manigent/models/plan_markdown.py +1774 -0
  80. manigent/models/project.py +1343 -0
  81. manigent/models/project_plan.py +8104 -0
  82. manigent/models/project_report.py +145 -0
  83. manigent/models/provenance.py +261 -0
  84. manigent/models/provider_agents.py +101 -0
  85. manigent/models/public_request.py +108 -0
  86. manigent/models/reflection_schedule.py +191 -0
  87. manigent/models/scheduled_message.py +285 -0
  88. manigent/models/share_token.py +190 -0
  89. manigent/models/skill_version.py +31 -0
  90. manigent/models/sop_template.py +187 -0
  91. manigent/models/thinking_partner_plan.py +187 -0
  92. manigent/models/tunnel_binding.py +129 -0
  93. manigent/models/user.py +1276 -0
  94. manigent/models/work_tracker.py +510 -0
  95. manigent/runner/invocation_registry.py +187 -0
  96. manigent/server/agent_capabilities.py +119 -0
  97. manigent/server/agent_visibility.py +119 -0
  98. manigent/server/announcements.py +119 -0
  99. manigent/server/app.py +807 -0
  100. manigent/server/auth.py +355 -0
  101. manigent/server/batch_timeout.py +94 -0
  102. manigent/server/channels/access.py +194 -0
  103. manigent/server/channels/adapter.py +295 -0
  104. manigent/server/channels/config.py +693 -0
  105. manigent/server/channels/contacts.py +39 -0
  106. manigent/server/channels/email_adapter.py +436 -0
  107. manigent/server/channels/email_address.py +145 -0
  108. manigent/server/channels/email_form.py +242 -0
  109. manigent/server/channels/email_format.py +285 -0
  110. manigent/server/channels/email_mail.py +427 -0
  111. manigent/server/channels/email_mailgun.py +164 -0
  112. manigent/server/channels/email_text.py +280 -0
  113. manigent/server/channels/file_links.py +127 -0
  114. manigent/server/channels/gateway.py +2998 -0
  115. manigent/server/channels/hub_delivery.py +211 -0
  116. manigent/server/channels/hub_store.py +1730 -0
  117. manigent/server/channels/line.py +645 -0
  118. manigent/server/channels/line_format.py +589 -0
  119. manigent/server/channels/log_redaction.py +145 -0
  120. manigent/server/channels/store.py +3316 -0
  121. manigent/server/channels/telegram.py +422 -0
  122. manigent/server/channels/telegram_format.py +312 -0
  123. manigent/server/channels/webchat.py +261 -0
  124. manigent/server/channels/webchat_widget.js +1310 -0
  125. manigent/server/channels/whatsapp.py +707 -0
  126. manigent/server/channels/whatsapp_format.py +511 -0
  127. manigent/server/chat_search_index.py +1203 -0
  128. manigent/server/config.py +61 -0
  129. manigent/server/context.py +121 -0
  130. manigent/server/coordinator_rule.py +115 -0
  131. manigent/server/desk_todo_notify.py +300 -0
  132. manigent/server/edge_hub.py +577 -0
  133. manigent/server/email.py +354 -0
  134. manigent/server/errors.py +83 -0
  135. manigent/server/home_fs_relay.py +392 -0
  136. manigent/server/hub/adapter.py +1395 -0
  137. manigent/server/hub/agent_send_guard.py +196 -0
  138. manigent/server/hub/avatar.py +342 -0
  139. manigent/server/hub/browser.py +778 -0
  140. manigent/server/hub/client.py +859 -0
  141. manigent/server/hub/demand.py +49 -0
  142. manigent/server/hub/directory.py +385 -0
  143. manigent/server/hub/enroll.py +400 -0
  144. manigent/server/hub/http.py +134 -0
  145. manigent/server/hub/identity.py +61 -0
  146. manigent/server/hub/keys.py +588 -0
  147. manigent/server/hub/mirror.py +802 -0
  148. manigent/server/hub/operator.py +817 -0
  149. manigent/server/hub/retain.py +361 -0
  150. manigent/server/hub/room_bridge.py +741 -0
  151. manigent/server/hub/runtime.py +691 -0
  152. manigent/server/hub/trust.py +182 -0
  153. manigent/server/hub/views.py +316 -0
  154. manigent/server/idempotency.py +205 -0
  155. manigent/server/in_flight_restore.py +181 -0
  156. manigent/server/inspection.py +816 -0
  157. manigent/server/isolation_gate.py +516 -0
  158. manigent/server/isolation_notice.py +310 -0
  159. manigent/server/managed_job_events.py +242 -0
  160. manigent/server/mcp_registry.py +118 -0
  161. manigent/server/message_forward.py +78 -0
  162. manigent/server/message_scheduler.py +176 -0
  163. manigent/server/notification_assets.py +164 -0
  164. manigent/server/oauth_login/cli_state.py +120 -0
  165. manigent/server/oauth_login/config.py +90 -0
  166. manigent/server/oauth_login/errors.py +78 -0
  167. manigent/server/oauth_login/flow.py +223 -0
  168. manigent/server/oauth_login/handoff.py +63 -0
  169. manigent/server/oauth_login/providers.py +45 -0
  170. manigent/server/oauth_state.py +81 -0
  171. manigent/server/owner_isolation.py +466 -0
  172. manigent/server/participant_liveness.py +82 -0
  173. manigent/server/public_profile.py +149 -0
  174. manigent/server/public_requests/candidates.py +116 -0
  175. manigent/server/public_requests/dispatch.py +121 -0
  176. manigent/server/public_requests/ranking.py +153 -0
  177. manigent/server/recent_activity.py +250 -0
  178. manigent/server/reflection_completion_subscriber.py +151 -0
  179. manigent/server/reflection_scheduler.py +239 -0
  180. manigent/server/requester_key_migration.py +261 -0
  181. manigent/server/room_flow.py +139 -0
  182. manigent/server/routes/_batch.py +218 -0
  183. manigent/server/routes/_fd_routing.py +155 -0
  184. manigent/server/routes/_mentions.py +70 -0
  185. manigent/server/routes/agent_env.py +188 -0
  186. manigent/server/routes/agent_fs.py +450 -0
  187. manigent/server/routes/agents.py +1206 -0
  188. manigent/server/routes/announcements.py +79 -0
  189. manigent/server/routes/auth.py +387 -0
  190. manigent/server/routes/brief_tabs.py +404 -0
  191. manigent/server/routes/changelog.py +237 -0
  192. manigent/server/routes/channels.py +993 -0
  193. manigent/server/routes/chatrooms.py +871 -0
  194. manigent/server/routes/desk_labels.py +298 -0
  195. manigent/server/routes/desk_latest_messages.py +116 -0
  196. manigent/server/routes/desk_read_state.py +110 -0
  197. manigent/server/routes/desk_sign_off.py +88 -0
  198. manigent/server/routes/desk_sops.py +216 -0
  199. manigent/server/routes/desk_todos.py +692 -0
  200. manigent/server/routes/desk_tour.py +44 -0
  201. manigent/server/routes/files.py +572 -0
  202. manigent/server/routes/hosts.py +484 -0
  203. manigent/server/routes/hub.py +534 -0
  204. manigent/server/routes/hub_channels.py +234 -0
  205. manigent/server/routes/inbox.py +581 -0
  206. manigent/server/routes/install.ps1 +436 -0
  207. manigent/server/routes/install.py +324 -0
  208. manigent/server/routes/install.sh +566 -0
  209. manigent/server/routes/isolation_check.py +145 -0
  210. manigent/server/routes/knowledge_packs.py +477 -0
  211. manigent/server/routes/managed_jobs.py +196 -0
  212. manigent/server/routes/mcps.py +271 -0
  213. manigent/server/routes/messages.py +1972 -0
  214. manigent/server/routes/model_defaults.py +279 -0
  215. manigent/server/routes/oauth.py +237 -0
  216. manigent/server/routes/oauth_login.py +351 -0
  217. manigent/server/routes/owner_isolation.py +91 -0
  218. manigent/server/routes/project_plans.py +1521 -0
  219. manigent/server/routes/project_reports.py +235 -0
  220. manigent/server/routes/projects.py +2047 -0
  221. manigent/server/routes/public_agents.py +151 -0
  222. manigent/server/routes/public_requests.py +181 -0
  223. manigent/server/routes/reflection.py +93 -0
  224. manigent/server/routes/scheduled_messages.py +263 -0
  225. manigent/server/routes/search.py +94 -0
  226. manigent/server/routes/sharing.py +156 -0
  227. manigent/server/routes/skills.py +406 -0
  228. manigent/server/routes/templates.py +219 -0
  229. manigent/server/routes/turn_tokens.py +119 -0
  230. manigent/server/routes/webchat.py +406 -0
  231. manigent/server/routes/websocket.py +431 -0
  232. manigent/server/serving_only.py +200 -0
  233. manigent/server/signup_policy.py +120 -0
  234. manigent/server/skill_registry.py +164 -0
  235. manigent/server/socket_events.py +858 -0
  236. manigent/server/timeout_checker.py +446 -0
  237. manigent/server/tunnel_subscriber.py +1000 -0
  238. manigent/server/turn_tokens.py +285 -0
  239. manigent/server/ws_hub.py +556 -0
  240. manigent/server_cli.py +31 -0
  241. manigent/sync/changelog.py +885 -0
  242. manigent/sync/console_subscriber.py +222 -0
  243. manigent/sync/runloop.py +619 -0
  244. manigent/sync/runloop_manager.py +197 -0
  245. manigent/sync/subscriber.py +98 -0
  246. manigent/utils/agent_processes.py +276 -0
  247. manigent/utils/agent_storage.py +125 -0
  248. manigent/utils/email.py +563 -0
  249. manigent/utils/env_store.py +174 -0
  250. manigent/utils/extra_dirs.py +85 -0
  251. manigent/utils/file_io.py +485 -0
  252. manigent/utils/invoke_timeout.py +87 -0
  253. manigent/utils/jsonc.py +78 -0
  254. manigent/utils/knowledge_index.py +148 -0
  255. manigent/utils/nofollow_fs.py +279 -0
  256. manigent/utils/notification_center.py +67 -0
  257. manigent/utils/version.py +57 -0
  258. manigent/web/frontend/.design-sync-entry.tsx +74 -0
  259. manigent/web/frontend/.design-sync-provider.tsx +46 -0
  260. manigent/web/frontend/.eslintrc.cjs +183 -0
  261. manigent/web/frontend/.gitignore +6 -0
  262. manigent/web/frontend/app/page.tsx +39 -0
  263. manigent/web/frontend/hub-terms.html +192 -0
  264. manigent/web/frontend/index.html +15 -0
  265. manigent/web/frontend/landing.html +15 -0
  266. manigent/web/frontend/package-lock.json +8028 -0
  267. manigent/web/frontend/package.json +55 -0
  268. manigent/web/frontend/postcss.config.js +6 -0
  269. manigent/web/frontend/privacy.html +217 -0
  270. manigent/web/frontend/private-beta-announcement.html +19 -0
  271. manigent/web/frontend/public/apple-touch-icon.png +0 -0
  272. manigent/web/frontend/public/brand-hero-banner.png +0 -0
  273. manigent/web/frontend/public/brand-hero.png +0 -0
  274. manigent/web/frontend/public/favicon.png +0 -0
  275. manigent/web/frontend/public/favicon.svg +8 -0
  276. manigent/web/frontend/public/icon-transparent-sm.png +0 -0
  277. manigent/web/frontend/public/icon.png +0 -0
  278. manigent/web/frontend/public/manigent-mark.svg +7 -0
  279. manigent/web/frontend/public/vite.svg +5 -0
  280. manigent/web/frontend/public.html +15 -0
  281. manigent/web/frontend/src/App.activeAccountRedirect.test.tsx +236 -0
  282. manigent/web/frontend/src/App.hubHostDm.test.tsx +119 -0
  283. manigent/web/frontend/src/App.hubHostProject.test.tsx +142 -0
  284. manigent/web/frontend/src/App.hubRoutes.test.tsx +187 -0
  285. manigent/web/frontend/src/App.oauthAddCallback.test.tsx +263 -0
  286. manigent/web/frontend/src/App.tsx +246 -0
  287. manigent/web/frontend/src/api/attachments.ts +32 -0
  288. manigent/web/frontend/src/api/batchLoader.test.ts +212 -0
  289. manigent/web/frontend/src/api/batchLoader.ts +124 -0
  290. manigent/web/frontend/src/api/chatroomsBatch.test.tsx +392 -0
  291. manigent/web/frontend/src/api/client.ts +321 -0
  292. manigent/web/frontend/src/api/crossAccountAuth.test.ts +388 -0
  293. manigent/web/frontend/src/api/endpoints/agentEnv.ts +24 -0
  294. manigent/web/frontend/src/api/endpoints/agentFs.ts +125 -0
  295. manigent/web/frontend/src/api/endpoints/agentSearch.ts +19 -0
  296. manigent/web/frontend/src/api/endpoints/agents.ts +66 -0
  297. manigent/web/frontend/src/api/endpoints/announcements.ts +26 -0
  298. manigent/web/frontend/src/api/endpoints/auth.ts +361 -0
  299. manigent/web/frontend/src/api/endpoints/authLogin.test.ts +236 -0
  300. manigent/web/frontend/src/api/endpoints/batch.ts +64 -0
  301. manigent/web/frontend/src/api/endpoints/briefTabs.ts +81 -0
  302. manigent/web/frontend/src/api/endpoints/channels.ts +285 -0
  303. manigent/web/frontend/src/api/endpoints/computers.ts +212 -0
  304. manigent/web/frontend/src/api/endpoints/deskLabels.ts +151 -0
  305. manigent/web/frontend/src/api/endpoints/deskLatestMessages.ts +23 -0
  306. manigent/web/frontend/src/api/endpoints/deskReadState.ts +41 -0
  307. manigent/web/frontend/src/api/endpoints/deskSignOffs.ts +74 -0
  308. manigent/web/frontend/src/api/endpoints/deskSops.ts +53 -0
  309. manigent/web/frontend/src/api/endpoints/deskTodoAttachments.ts +69 -0
  310. manigent/web/frontend/src/api/endpoints/deskTodoProjects.test.ts +99 -0
  311. manigent/web/frontend/src/api/endpoints/deskTodoProjects.ts +114 -0
  312. manigent/web/frontend/src/api/endpoints/deskTodos.ts +164 -0
  313. manigent/web/frontend/src/api/endpoints/deskTour.ts +16 -0
  314. manigent/web/frontend/src/api/endpoints/dm.ts +137 -0
  315. manigent/web/frontend/src/api/endpoints/files.ts +55 -0
  316. manigent/web/frontend/src/api/endpoints/hub.contract.test.ts +94 -0
  317. manigent/web/frontend/src/api/endpoints/hub.keys.ts +260 -0
  318. manigent/web/frontend/src/api/endpoints/hub.test.ts +203 -0
  319. manigent/web/frontend/src/api/endpoints/hub.ts +654 -0
  320. manigent/web/frontend/src/api/endpoints/index.ts +15 -0
  321. manigent/web/frontend/src/api/endpoints/install.ts +74 -0
  322. manigent/web/frontend/src/api/endpoints/isolationCheck.ts +36 -0
  323. manigent/web/frontend/src/api/endpoints/knowledgePacks.ts +179 -0
  324. manigent/web/frontend/src/api/endpoints/mcps.ts +36 -0
  325. manigent/web/frontend/src/api/endpoints/messages.held.test.ts +43 -0
  326. manigent/web/frontend/src/api/endpoints/messages.ts +95 -0
  327. manigent/web/frontend/src/api/endpoints/modelConfigs.ts +232 -0
  328. manigent/web/frontend/src/api/endpoints/oauthApi.test.ts +110 -0
  329. manigent/web/frontend/src/api/endpoints/projectPlans.ts +608 -0
  330. manigent/web/frontend/src/api/endpoints/projectReports.ts +36 -0
  331. manigent/web/frontend/src/api/endpoints/projects.ts +124 -0
  332. manigent/web/frontend/src/api/endpoints/publicRequests.ts +75 -0
  333. manigent/web/frontend/src/api/endpoints/reflection.ts +32 -0
  334. manigent/web/frontend/src/api/endpoints/roomFlow.ts +14 -0
  335. manigent/web/frontend/src/api/endpoints/scheduledMessages.ts +60 -0
  336. manigent/web/frontend/src/api/endpoints/search.ts +27 -0
  337. manigent/web/frontend/src/api/endpoints/sharing.ts +42 -0
  338. manigent/web/frontend/src/api/endpoints/skills.ts +67 -0
  339. manigent/web/frontend/src/api/endpoints/templates.ts +72 -0
  340. manigent/web/frontend/src/api/loaders.ts +22 -0
  341. manigent/web/frontend/src/api/singleIssuer.test.ts +148 -0
  342. manigent/web/frontend/src/api/transientRetry.test.ts +176 -0
  343. manigent/web/frontend/src/api/transientRetry.ts +140 -0
  344. manigent/web/frontend/src/api/websocket.resync.test.ts +111 -0
  345. manigent/web/frontend/src/api/websocket.ts +335 -0
  346. manigent/web/frontend/src/components/agent/AgentNameLink.tsx +24 -0
  347. manigent/web/frontend/src/components/agent/AgentViewActions.test.tsx +41 -0
  348. manigent/web/frontend/src/components/agent/AgentViewActions.tsx +89 -0
  349. manigent/web/frontend/src/components/agent/CreateAgentPage.tsx +535 -0
  350. manigent/web/frontend/src/components/agent/JsonConfigEditor.tsx +33 -0
  351. manigent/web/frontend/src/components/agent/KnowledgePackList.tsx +103 -0
  352. manigent/web/frontend/src/components/agent/McpCatalog.tsx +261 -0
  353. manigent/web/frontend/src/components/agent/McpConfigModal.tsx +224 -0
  354. manigent/web/frontend/src/components/agent/PreConsentModal.tsx +172 -0
  355. manigent/web/frontend/src/components/agent/SkillCatalog.tsx +364 -0
  356. manigent/web/frontend/src/components/agent/SkillConfigModal.tsx +221 -0
  357. manigent/web/frontend/src/components/auth/AddAccountPage.test.tsx +181 -0
  358. manigent/web/frontend/src/components/auth/AddAccountPage.tsx +201 -0
  359. manigent/web/frontend/src/components/auth/AuthShell.tsx +462 -0
  360. manigent/web/frontend/src/components/auth/LoginPage.test.tsx +384 -0
  361. manigent/web/frontend/src/components/auth/LoginPage.tsx +129 -0
  362. manigent/web/frontend/src/components/auth/OAuthCallbackPage.test.tsx +185 -0
  363. manigent/web/frontend/src/components/auth/OAuthCallbackPage.tsx +154 -0
  364. manigent/web/frontend/src/components/auth/OAuthLinkedBanner.tsx +44 -0
  365. manigent/web/frontend/src/components/auth/ProviderButtons.tsx +94 -0
  366. manigent/web/frontend/src/components/auth/SignupPage.tsx +437 -0
  367. manigent/web/frontend/src/components/auth/StepProgress.tsx +60 -0
  368. manigent/web/frontend/src/components/auth/VerifyEmailPage.test.tsx +155 -0
  369. manigent/web/frontend/src/components/auth/VerifyEmailPage.tsx +211 -0
  370. manigent/web/frontend/src/components/auth/index.ts +3 -0
  371. manigent/web/frontend/src/components/auth/oauthErrors.test.ts +107 -0
  372. manigent/web/frontend/src/components/auth/oauthErrors.ts +82 -0
  373. manigent/web/frontend/src/components/auth/useUsernameCheck.test.tsx +68 -0
  374. manigent/web/frontend/src/components/auth/useUsernameCheck.ts +86 -0
  375. manigent/web/frontend/src/components/beta/BetaPage.tsx +21 -0
  376. manigent/web/frontend/src/components/beta/DayAtMyDesk.tsx +120 -0
  377. manigent/web/frontend/src/components/beta/Footer.tsx +82 -0
  378. manigent/web/frontend/src/components/beta/Hero.tsx +224 -0
  379. manigent/web/frontend/src/components/beta/Nav.tsx +51 -0
  380. manigent/web/frontend/src/components/beta/Pillars.tsx +172 -0
  381. manigent/web/frontend/src/components/beta/TwoModels.tsx +146 -0
  382. manigent/web/frontend/src/components/beta/constants.ts +3 -0
  383. manigent/web/frontend/src/components/briefing/BriefingFrame.test.tsx +208 -0
  384. manigent/web/frontend/src/components/briefing/BriefingFrame.tsx +151 -0
  385. manigent/web/frontend/src/components/briefing/BriefingPage.test.tsx +176 -0
  386. manigent/web/frontend/src/components/briefing/BriefingPage.tsx +157 -0
  387. manigent/web/frontend/src/components/briefing/BriefingPage.wsDelete.test.tsx +177 -0
  388. manigent/web/frontend/src/components/briefing/briefing.css +147 -0
  389. manigent/web/frontend/src/components/briefing/heightBootstrap.test.ts +121 -0
  390. manigent/web/frontend/src/components/briefing/heightBootstrap.ts +224 -0
  391. manigent/web/frontend/src/components/briefing/parseBriefMessage.test.ts +90 -0
  392. manigent/web/frontend/src/components/briefing/sampleBriefing.ts +148 -0
  393. manigent/web/frontend/src/components/channels/ChannelBadge.tsx +35 -0
  394. manigent/web/frontend/src/components/chatroom/ChannelThreadControls.tsx +127 -0
  395. manigent/web/frontend/src/components/chatroom/ChannelThreadNotice.test.tsx +207 -0
  396. manigent/web/frontend/src/components/chatroom/ChannelThreadNotice.tsx +80 -0
  397. manigent/web/frontend/src/components/chatroom/ChatroomView.channel.test.tsx +550 -0
  398. manigent/web/frontend/src/components/chatroom/ChatroomView.flow.test.tsx +186 -0
  399. manigent/web/frontend/src/components/chatroom/ChatroomView.isolation.test.tsx +70 -0
  400. manigent/web/frontend/src/components/chatroom/ChatroomView.tsx +450 -0
  401. manigent/web/frontend/src/components/chatroom/DelegationRow.test.tsx +170 -0
  402. manigent/web/frontend/src/components/chatroom/DelegationRow.tsx +217 -0
  403. manigent/web/frontend/src/components/chatroom/DeliveryStatusLine.test.tsx +33 -0
  404. manigent/web/frontend/src/components/chatroom/DeliveryStatusLine.tsx +46 -0
  405. manigent/web/frontend/src/components/chatroom/ForwardModal.test.tsx +109 -0
  406. manigent/web/frontend/src/components/chatroom/ForwardModal.tsx +304 -0
  407. manigent/web/frontend/src/components/chatroom/HeldNotice.tsx +77 -0
  408. manigent/web/frontend/src/components/chatroom/MentionDropdown.tsx +97 -0
  409. manigent/web/frontend/src/components/chatroom/MessageInput.tsx +408 -0
  410. manigent/web/frontend/src/components/chatroom/MessageItem.tsx +425 -0
  411. manigent/web/frontend/src/components/chatroom/MessageList.outside.test.tsx +98 -0
  412. manigent/web/frontend/src/components/chatroom/MessageList.test.tsx +245 -0
  413. manigent/web/frontend/src/components/chatroom/MessageList.tsx +406 -0
  414. manigent/web/frontend/src/components/chatroom/OfflineRemedyNote.test.tsx +211 -0
  415. manigent/web/frontend/src/components/chatroom/OfflineRemedyNote.tsx +182 -0
  416. manigent/web/frontend/src/components/chatroom/OutsideBadge.tsx +18 -0
  417. manigent/web/frontend/src/components/chatroom/RoomFlowControl.tsx +49 -0
  418. manigent/web/frontend/src/components/chatroom/RoomFlowStrip.test.tsx +47 -0
  419. manigent/web/frontend/src/components/chatroom/RoomFlowStrip.tsx +25 -0
  420. manigent/web/frontend/src/components/chatroom/SaveToPackModal.tsx +350 -0
  421. manigent/web/frontend/src/components/chatroom/StagedAttachments.test.tsx +56 -0
  422. manigent/web/frontend/src/components/chatroom/StagedAttachments.tsx +160 -0
  423. manigent/web/frontend/src/components/chatroom/SystemNote.tsx +18 -0
  424. manigent/web/frontend/src/components/chatroom/contactSender.test.tsx +130 -0
  425. manigent/web/frontend/src/components/chatroom/index.ts +3 -0
  426. manigent/web/frontend/src/components/chatroom/recipientStatus.test.ts +344 -0
  427. manigent/web/frontend/src/components/chatroom/recipientStatus.ts +197 -0
  428. manigent/web/frontend/src/components/common/AnnouncementBanners.test.tsx +221 -0
  429. manigent/web/frontend/src/components/common/AnnouncementBanners.tsx +180 -0
  430. manigent/web/frontend/src/components/common/Avatar.test.tsx +50 -0
  431. manigent/web/frontend/src/components/common/Avatar.tsx +102 -0
  432. manigent/web/frontend/src/components/common/Badge.tsx +30 -0
  433. manigent/web/frontend/src/components/common/BrandName.tsx +16 -0
  434. manigent/web/frontend/src/components/common/ClearHistoryButton.tsx +74 -0
  435. manigent/web/frontend/src/components/common/CollapsibleHeader.tsx +64 -0
  436. manigent/web/frontend/src/components/common/ConnectionStatus.test.tsx +56 -0
  437. manigent/web/frontend/src/components/common/ConnectionStatus.tsx +28 -0
  438. manigent/web/frontend/src/components/common/DropOverlay.tsx +27 -0
  439. manigent/web/frontend/src/components/common/InlineEditableTitle.test.tsx +369 -0
  440. manigent/web/frontend/src/components/common/InlineEditableTitle.tsx +287 -0
  441. manigent/web/frontend/src/components/common/IsolationUnavailablePanel.tsx +19 -0
  442. manigent/web/frontend/src/components/common/Loading.tsx +22 -0
  443. manigent/web/frontend/src/components/common/ModelSelector.tsx +248 -0
  444. manigent/web/frontend/src/components/common/Overlay.tsx +15 -0
  445. manigent/web/frontend/src/components/common/ResizeHandle.tsx +52 -0
  446. manigent/web/frontend/src/components/common/TagAutocompleteInput.test.tsx +92 -0
  447. manigent/web/frontend/src/components/common/TagAutocompleteInput.tsx +246 -0
  448. manigent/web/frontend/src/components/common/TagPillInput.tsx +181 -0
  449. manigent/web/frontend/src/components/common/TeamAndAgentPicker.tsx +83 -0
  450. manigent/web/frontend/src/components/common/Timestamp.tsx +61 -0
  451. manigent/web/frontend/src/components/common/VerifiedBadge.tsx +20 -0
  452. manigent/web/frontend/src/components/common/index.ts +8 -0
  453. manigent/web/frontend/src/components/composer/PhPopover.tsx +239 -0
  454. manigent/web/frontend/src/components/composer/PromptEditor.test.tsx +472 -0
  455. manigent/web/frontend/src/components/composer/PromptEditor.tsx +540 -0
  456. manigent/web/frontend/src/components/composer/caret.ts +159 -0
  457. manigent/web/frontend/src/components/composer/placeholders.test.ts +352 -0
  458. manigent/web/frontend/src/components/composer/placeholders.ts +354 -0
  459. manigent/web/frontend/src/components/composer/promptEditor.css +161 -0
  460. manigent/web/frontend/src/components/computer/ComputerPage.test.tsx +452 -0
  461. manigent/web/frontend/src/components/computer/ComputerPage.tsx +811 -0
  462. manigent/web/frontend/src/components/computer/ConnectComputerModal.tsx +135 -0
  463. manigent/web/frontend/src/components/computer/CopyCommand.tsx +77 -0
  464. manigent/web/frontend/src/components/computer/DisconnectComputerModal.tsx +121 -0
  465. manigent/web/frontend/src/components/computer/SoftwareCard.tsx +145 -0
  466. manigent/web/frontend/src/components/computer/TerminalCard.test.tsx +114 -0
  467. manigent/web/frontend/src/components/computer/TerminalCard.tsx +131 -0
  468. manigent/web/frontend/src/components/computer/TerminalPanel.tsx +226 -0
  469. manigent/web/frontend/src/components/computer/TrustPanel.test.tsx +58 -0
  470. manigent/web/frontend/src/components/computer/TrustPanel.tsx +143 -0
  471. manigent/web/frontend/src/components/computer/computerCopy.test.ts +322 -0
  472. manigent/web/frontend/src/components/computer/computerCopy.ts +413 -0
  473. manigent/web/frontend/src/components/computer/sections.ts +21 -0
  474. manigent/web/frontend/src/components/computer/terminalProtocol.test.ts +135 -0
  475. manigent/web/frontend/src/components/computer/terminalProtocol.ts +170 -0
  476. manigent/web/frontend/src/components/context/ContextGuide.test.tsx +26 -0
  477. manigent/web/frontend/src/components/context/ContextGuide.tsx +144 -0
  478. manigent/web/frontend/src/components/context/ContextLanding.tsx +46 -0
  479. manigent/web/frontend/src/components/desk/CardBand.a11y.test.tsx +138 -0
  480. manigent/web/frontend/src/components/desk/CardBand.tsx +132 -0
  481. manigent/web/frontend/src/components/desk/DashTile.tsx +81 -0
  482. manigent/web/frontend/src/components/desk/DeskAgentPicker.retained.test.tsx +79 -0
  483. manigent/web/frontend/src/components/desk/DeskAgentPicker.tsx +151 -0
  484. manigent/web/frontend/src/components/desk/DeskComposer.detachTodo.test.tsx +233 -0
  485. manigent/web/frontend/src/components/desk/DeskComposer.dispatch.test.tsx +208 -0
  486. manigent/web/frontend/src/components/desk/DeskComposer.loadTodoDraft.test.tsx +242 -0
  487. manigent/web/frontend/src/components/desk/DeskComposer.restage.test.tsx +258 -0
  488. manigent/web/frontend/src/components/desk/DeskComposer.saveTodo.test.tsx +265 -0
  489. manigent/web/frontend/src/components/desk/DeskComposer.tsx +828 -0
  490. manigent/web/frontend/src/components/desk/DeskPage.crossHighlight.test.tsx +427 -0
  491. manigent/web/frontend/src/components/desk/DeskPage.link.test.tsx +271 -0
  492. manigent/web/frontend/src/components/desk/DeskPage.tsx +1066 -0
  493. manigent/web/frontend/src/components/desk/DeskPage.welcomeTour.test.tsx +262 -0
  494. manigent/web/frontend/src/components/desk/DeskSideRail.tsx +100 -0
  495. manigent/web/frontend/src/components/desk/DeskWelcomeTour.tsx +513 -0
  496. manigent/web/frontend/src/components/desk/DeskZeroState.tsx +45 -0
  497. manigent/web/frontend/src/components/desk/LabelChip.tsx +147 -0
  498. manigent/web/frontend/src/components/desk/LabelFilter.tsx +474 -0
  499. manigent/web/frontend/src/components/desk/LabelInput.test.tsx +439 -0
  500. manigent/web/frontend/src/components/desk/LabelInput.tsx +477 -0
  501. manigent/web/frontend/src/components/desk/LabelManageSheet.test.tsx +384 -0
  502. manigent/web/frontend/src/components/desk/LabelManageSheet.tsx +498 -0
  503. manigent/web/frontend/src/components/desk/PhaseRail.test.tsx +170 -0
  504. manigent/web/frontend/src/components/desk/PhaseRail.tsx +120 -0
  505. manigent/web/frontend/src/components/desk/PlanBand.test.tsx +655 -0
  506. manigent/web/frontend/src/components/desk/PlanBand.tsx +216 -0
  507. manigent/web/frontend/src/components/desk/SaveTodoModal.tsx +126 -0
  508. manigent/web/frontend/src/components/desk/SopEditor.tsx +280 -0
  509. manigent/web/frontend/src/components/desk/SopItem.tsx +171 -0
  510. manigent/web/frontend/src/components/desk/SopRail.test.tsx +517 -0
  511. manigent/web/frontend/src/components/desk/SopRail.tsx +250 -0
  512. manigent/web/frontend/src/components/desk/TicketChip.tsx +51 -0
  513. manigent/web/frontend/src/components/desk/TodoItem.attachments.test.tsx +96 -0
  514. manigent/web/frontend/src/components/desk/TodoItem.menu.test.tsx +142 -0
  515. manigent/web/frontend/src/components/desk/TodoItem.tsx +573 -0
  516. manigent/web/frontend/src/components/desk/TodoRail.longpress.test.tsx +396 -0
  517. manigent/web/frontend/src/components/desk/TodoRail.test.tsx +1155 -0
  518. manigent/web/frontend/src/components/desk/TodoRail.tsx +918 -0
  519. manigent/web/frontend/src/components/desk/TodoRowMenu.tsx +147 -0
  520. manigent/web/frontend/src/components/desk/UpdateCard.bands.test.tsx +308 -0
  521. manigent/web/frontend/src/components/desk/UpdateCard.sendBack.test.tsx +444 -0
  522. manigent/web/frontend/src/components/desk/UpdateCard.tsx +943 -0
  523. manigent/web/frontend/src/components/desk/UpdateCard.untrusted.test.tsx +161 -0
  524. manigent/web/frontend/src/components/desk/desk.css +1921 -0
  525. manigent/web/frontend/src/components/desk/desk.css.test.ts +582 -0
  526. manigent/web/frontend/src/components/desk/deskBands.test.ts +389 -0
  527. manigent/web/frontend/src/components/desk/deskBands.ts +226 -0
  528. manigent/web/frontend/src/components/desk/m16Surfaces.test.ts +134 -0
  529. manigent/web/frontend/src/components/desk/sopSyntax.ts +73 -0
  530. manigent/web/frontend/src/components/discovery/AgentDirectory.tsx +71 -0
  531. manigent/web/frontend/src/components/discovery/AgentFilterBar.tsx +46 -0
  532. manigent/web/frontend/src/components/discovery/AgentSearchBar.tsx +56 -0
  533. manigent/web/frontend/src/components/dm/AgentSummaryCard.tsx +154 -0
  534. manigent/web/frontend/src/components/dm/ComposeFields.tsx +145 -0
  535. manigent/web/frontend/src/components/dm/DMChatView.flow.test.tsx +374 -0
  536. manigent/web/frontend/src/components/dm/DMChatView.tsx +841 -0
  537. manigent/web/frontend/src/components/dm/DMZeroState.tsx +204 -0
  538. manigent/web/frontend/src/components/dm/DmComposeModal.tsx +301 -0
  539. manigent/web/frontend/src/components/dm/PersonalizeModal.tsx +366 -0
  540. manigent/web/frontend/src/components/dm/index.ts +1 -0
  541. manigent/web/frontend/src/components/files/FileItem.tsx +138 -0
  542. manigent/web/frontend/src/components/files/FileList.outside.test.tsx +29 -0
  543. manigent/web/frontend/src/components/files/FileList.tsx +48 -0
  544. manigent/web/frontend/src/components/files/FilePreview.test.tsx +75 -0
  545. manigent/web/frontend/src/components/files/FilePreview.tsx +325 -0
  546. manigent/web/frontend/src/components/files/FileViewerModal.test.tsx +157 -0
  547. manigent/web/frontend/src/components/files/FileViewerModal.tsx +261 -0
  548. manigent/web/frontend/src/components/files/HighlightedCode.tsx +39 -0
  549. manigent/web/frontend/src/components/files/SaveToAgentPanel.test.tsx +156 -0
  550. manigent/web/frontend/src/components/files/SaveToAgentPanel.tsx +545 -0
  551. manigent/web/frontend/src/components/files/index.ts +3 -0
  552. manigent/web/frontend/src/components/hero/AgentAvatar.tsx +66 -0
  553. manigent/web/frontend/src/components/hero/Button.tsx +66 -0
  554. manigent/web/frontend/src/components/hero/Hero.tsx +133 -0
  555. manigent/web/frontend/src/components/homefs/HomeFolderBrowser.test.tsx +393 -0
  556. manigent/web/frontend/src/components/homefs/HomeFolderBrowser.tsx +792 -0
  557. manigent/web/frontend/src/components/hub/BridgeDeliveryLine.test.tsx +40 -0
  558. manigent/web/frontend/src/components/hub/BridgeDeliveryLine.tsx +36 -0
  559. manigent/web/frontend/src/components/hub/HubAvatar.test.tsx +93 -0
  560. manigent/web/frontend/src/components/hub/HubAvatar.tsx +33 -0
  561. manigent/web/frontend/src/components/hub/HubChatView.test.tsx +695 -0
  562. manigent/web/frontend/src/components/hub/HubChatView.tsx +504 -0
  563. manigent/web/frontend/src/components/hub/HubContactLabel.test.tsx +92 -0
  564. manigent/web/frontend/src/components/hub/HubContactLabel.tsx +47 -0
  565. manigent/web/frontend/src/components/hub/HubDeliveryLine.tsx +49 -0
  566. manigent/web/frontend/src/components/hub/HubDisclosure.tsx +49 -0
  567. manigent/web/frontend/src/components/hub/HubEnrollmentDialog.test.tsx +144 -0
  568. manigent/web/frontend/src/components/hub/HubEnrollmentDialog.tsx +244 -0
  569. manigent/web/frontend/src/components/hub/HubMessageBody.test.tsx +26 -0
  570. manigent/web/frontend/src/components/hub/HubMessageBody.tsx +14 -0
  571. manigent/web/frontend/src/components/hub/HubOfflineConfirm.tsx +45 -0
  572. manigent/web/frontend/src/components/hub/HubThreadBanner.tsx +33 -0
  573. manigent/web/frontend/src/components/hub/HubThreadGuard.tsx +59 -0
  574. manigent/web/frontend/src/components/hub/RemoteAgentCard.test.tsx +50 -0
  575. manigent/web/frontend/src/components/hub/RemoteAgentCard.tsx +174 -0
  576. manigent/web/frontend/src/components/hub/RemoteAgentSection.test.tsx +446 -0
  577. manigent/web/frontend/src/components/hub/RemoteAgentSection.tsx +219 -0
  578. manigent/web/frontend/src/components/hub/ReportDialog.test.tsx +96 -0
  579. manigent/web/frontend/src/components/hub/ReportDialog.tsx +162 -0
  580. manigent/web/frontend/src/components/hub/RetainButton.test.tsx +103 -0
  581. manigent/web/frontend/src/components/hub/RetainButton.tsx +114 -0
  582. manigent/web/frontend/src/components/hub/RetainDialog.test.tsx +68 -0
  583. manigent/web/frontend/src/components/hub/RetainDialog.tsx +115 -0
  584. manigent/web/frontend/src/components/hub/RetainedList.test.tsx +94 -0
  585. manigent/web/frontend/src/components/hub/RetainedList.tsx +32 -0
  586. manigent/web/frontend/src/components/hub/RetainedRow.tsx +129 -0
  587. manigent/web/frontend/src/components/hub/StandInChip.test.tsx +37 -0
  588. manigent/web/frontend/src/components/hub/StandInChip.tsx +43 -0
  589. manigent/web/frontend/src/components/hub/UnusualNameChip.test.tsx +81 -0
  590. manigent/web/frontend/src/components/hub/UnusualNameChip.tsx +22 -0
  591. manigent/web/frontend/src/components/hub/hubLint.test.ts +265 -0
  592. manigent/web/frontend/src/components/landing/ComparisonSection.tsx +92 -0
  593. manigent/web/frontend/src/components/landing/ConsoleSection.tsx +402 -0
  594. manigent/web/frontend/src/components/landing/CrewSection.tsx +224 -0
  595. manigent/web/frontend/src/components/landing/FAQ.tsx +68 -0
  596. manigent/web/frontend/src/components/landing/Footer.tsx +58 -0
  597. manigent/web/frontend/src/components/landing/HowItWorks.tsx +117 -0
  598. manigent/web/frontend/src/components/landing/KnowledgeSection.tsx +91 -0
  599. manigent/web/frontend/src/components/landing/LandingPage.test.tsx +134 -0
  600. manigent/web/frontend/src/components/landing/LearningSection.tsx +486 -0
  601. manigent/web/frontend/src/components/landing/Nav.tsx +68 -0
  602. manigent/web/frontend/src/components/landing/Pricing.tsx +263 -0
  603. manigent/web/frontend/src/components/landing/Section.tsx +98 -0
  604. manigent/web/frontend/src/components/landing/StaffingSection.tsx +122 -0
  605. manigent/web/frontend/src/components/landing/WhyNowSection.tsx +92 -0
  606. manigent/web/frontend/src/components/landing/WhyTeamSection.tsx +73 -0
  607. manigent/web/frontend/src/components/landing/earlyAccess.ts +23 -0
  608. manigent/web/frontend/src/components/layout/AppLayout.tsx +77 -0
  609. manigent/web/frontend/src/components/layout/Sidebar.tsx +172 -0
  610. manigent/web/frontend/src/components/layout/index.ts +2 -0
  611. manigent/web/frontend/src/components/project/ActivityFeed.tsx +247 -0
  612. manigent/web/frontend/src/components/project/CreateProjectPage.tsx +543 -0
  613. manigent/web/frontend/src/components/project/DeleteProjectButton.tsx +39 -0
  614. manigent/web/frontend/src/components/project/ProjectPlanTab.phase.test.tsx +107 -0
  615. manigent/web/frontend/src/components/project/ProjectPlanTab.test.tsx +994 -0
  616. manigent/web/frontend/src/components/project/ProjectPlanTab.tsx +166 -0
  617. manigent/web/frontend/src/components/project/ProjectReportTab.tsx +139 -0
  618. manigent/web/frontend/src/components/project/ProjectView.tsx +390 -0
  619. manigent/web/frontend/src/components/project/index.ts +2 -0
  620. manigent/web/frontend/src/components/project/plan/PlanAcceptance.test.tsx +460 -0
  621. manigent/web/frontend/src/components/project/plan/PlanAcceptance.tsx +111 -0
  622. manigent/web/frontend/src/components/project/plan/PlanBlockEdit.tsx +111 -0
  623. manigent/web/frontend/src/components/project/plan/PlanBlockEditing.test.tsx +1132 -0
  624. manigent/web/frontend/src/components/project/plan/PlanBody.test.tsx +495 -0
  625. manigent/web/frontend/src/components/project/plan/PlanBody.tsx +992 -0
  626. manigent/web/frontend/src/components/project/plan/PlanCollide.tsx +254 -0
  627. manigent/web/frontend/src/components/project/plan/PlanCommentList.test.tsx +420 -0
  628. manigent/web/frontend/src/components/project/plan/PlanCommentList.tsx +391 -0
  629. manigent/web/frontend/src/components/project/plan/PlanDecisionNudge.test.tsx +226 -0
  630. manigent/web/frontend/src/components/project/plan/PlanDecisionNudge.tsx +128 -0
  631. manigent/web/frontend/src/components/project/plan/PlanDestructiveAccept.test.tsx +209 -0
  632. manigent/web/frontend/src/components/project/plan/PlanDiff.test.tsx +126 -0
  633. manigent/web/frontend/src/components/project/plan/PlanDiff.tsx +131 -0
  634. manigent/web/frontend/src/components/project/plan/PlanDiffSeries.test.tsx +500 -0
  635. manigent/web/frontend/src/components/project/plan/PlanDiffSeries.tsx +125 -0
  636. manigent/web/frontend/src/components/project/plan/PlanEditor.mounts.test.tsx +504 -0
  637. manigent/web/frontend/src/components/project/plan/PlanEditor.tsx +2159 -0
  638. manigent/web/frontend/src/components/project/plan/PlanHistory.tsx +69 -0
  639. manigent/web/frontend/src/components/project/plan/PlanInlineNotes.test.tsx +2398 -0
  640. manigent/web/frontend/src/components/project/plan/PlanNoteBase.test.tsx +215 -0
  641. manigent/web/frontend/src/components/project/plan/PlanNoteRow.test.tsx +562 -0
  642. manigent/web/frontend/src/components/project/plan/PlanNoteRow.tsx +908 -0
  643. manigent/web/frontend/src/components/project/plan/PlanQuotePop.tsx +254 -0
  644. manigent/web/frontend/src/components/project/plan/PlanStaleReconcile.test.tsx +244 -0
  645. manigent/web/frontend/src/components/project/plan/PlanSubmitWindow.test.tsx +281 -0
  646. manigent/web/frontend/src/components/project/plan/PlanTray.tsx +184 -0
  647. manigent/web/frontend/src/components/project/plan/PlanTrayMirror.test.tsx +242 -0
  648. manigent/web/frontend/src/components/project/plan/plan.css +1151 -0
  649. manigent/web/frontend/src/components/project/plan/planAnchor.test.tsx +459 -0
  650. manigent/web/frontend/src/components/project/plan/planAnchor.tsx +277 -0
  651. manigent/web/frontend/src/components/project/plan/planBlocks.test.tsx +470 -0
  652. manigent/web/frontend/src/components/project/plan/planBlocks.ts +242 -0
  653. manigent/web/frontend/src/components/project/plan/planContrast.test.ts +229 -0
  654. manigent/web/frontend/src/components/project/plan/planDiffs.test.ts +455 -0
  655. manigent/web/frontend/src/components/project/plan/planDiffs.ts +358 -0
  656. manigent/web/frontend/src/components/project/plan/planTestServer.ts +741 -0
  657. manigent/web/frontend/src/components/project/plan/planTransport.test.ts +160 -0
  658. manigent/web/frontend/src/components/project/plan/planTrayLogic.test.ts +1201 -0
  659. manigent/web/frontend/src/components/project/plan/planTrayLogic.ts +1164 -0
  660. manigent/web/frontend/src/components/publicAgents/PublicAgentsAccount.tsx +162 -0
  661. manigent/web/frontend/src/components/publicAgents/api.ts +55 -0
  662. manigent/web/frontend/src/components/publicAgents/parts.tsx +54 -0
  663. manigent/web/frontend/src/components/publicAgents/publicAgents.css +53 -0
  664. manigent/web/frontend/src/components/publicAgents/publicAgents.test.tsx +86 -0
  665. manigent/web/frontend/src/components/publicAgents/widget.test.ts +148 -0
  666. manigent/web/frontend/src/components/publicAgents/widget.ts +40 -0
  667. manigent/web/frontend/src/components/publicRequests/AgentDrawer.tsx +101 -0
  668. manigent/web/frontend/src/components/publicRequests/AgentPanel.tsx +173 -0
  669. manigent/web/frontend/src/components/publicRequests/AgentRankList.tsx +61 -0
  670. manigent/web/frontend/src/components/publicRequests/MatchRetainButton.tsx +67 -0
  671. manigent/web/frontend/src/components/publicRequests/ProgressBoard.tsx +74 -0
  672. manigent/web/frontend/src/components/publicRequests/PublicRequestsPage.tsx +57 -0
  673. manigent/web/frontend/src/components/publicRequests/RequestHome.tsx +99 -0
  674. manigent/web/frontend/src/components/publicRequests/RequestRoom.tsx +126 -0
  675. manigent/web/frontend/src/components/publicRequests/agentView.ts +65 -0
  676. manigent/web/frontend/src/components/publicRequests/publicRequests.css +209 -0
  677. manigent/web/frontend/src/components/publicRequests/publicRequests.test.tsx +172 -0
  678. manigent/web/frontend/src/components/publicRequests/requestCopy.ts +121 -0
  679. manigent/web/frontend/src/components/sample-request/SampleIdeaList.test.tsx +190 -0
  680. manigent/web/frontend/src/components/sample-request/SampleIdeaList.tsx +180 -0
  681. manigent/web/frontend/src/components/sample-request/SampleRequestBody.tsx +276 -0
  682. manigent/web/frontend/src/components/sample-request/assistantStarterSamples.ts +62 -0
  683. manigent/web/frontend/src/components/sample-request/sampleIdeaList.css +106 -0
  684. manigent/web/frontend/src/components/scheduled/CronPicker.tsx +80 -0
  685. manigent/web/frontend/src/components/scheduled/ScheduleMessageModal.tsx +341 -0
  686. manigent/web/frontend/src/components/scheduled/ScheduledMessagesList.tsx +157 -0
  687. manigent/web/frontend/src/components/settings/AgentSettingsPage.tsx +366 -0
  688. manigent/web/frontend/src/components/settings/ConnectedAccounts.tsx +277 -0
  689. manigent/web/frontend/src/components/settings/DefaultModelSection.test.tsx +96 -0
  690. manigent/web/frontend/src/components/settings/DefaultModelSection.tsx +212 -0
  691. manigent/web/frontend/src/components/settings/DisconnectConfirmModal.tsx +93 -0
  692. manigent/web/frontend/src/components/settings/FrontDeskSettingsPage.isolation.test.tsx +66 -0
  693. manigent/web/frontend/src/components/settings/FrontDeskSettingsPage.tsx +134 -0
  694. manigent/web/frontend/src/components/settings/InvitableAgentsPicker.tsx +182 -0
  695. manigent/web/frontend/src/components/settings/KnowledgePacksSection.tsx +867 -0
  696. manigent/web/frontend/src/components/settings/ModelConfigDialog.catalogDriven.test.tsx +79 -0
  697. manigent/web/frontend/src/components/settings/ModelConfigDialog.tsx +343 -0
  698. manigent/web/frontend/src/components/settings/ModelConfigSection.smartRouting.test.tsx +109 -0
  699. manigent/web/frontend/src/components/settings/ModelConfigSection.tsx +599 -0
  700. manigent/web/frontend/src/components/settings/ReflectionScheduleSection.tsx +170 -0
  701. manigent/web/frontend/src/components/settings/UserSettingsPage.tsx +168 -0
  702. manigent/web/frontend/src/components/settings/agent/AgentStatusStrip.tsx +71 -0
  703. manigent/web/frontend/src/components/settings/agent/BlockedDeploymentsCard.test.tsx +81 -0
  704. manigent/web/frontend/src/components/settings/agent/BlockedDeploymentsCard.tsx +62 -0
  705. manigent/web/frontend/src/components/settings/agent/ChannelIsolationLine.tsx +21 -0
  706. manigent/web/frontend/src/components/settings/agent/ChannelsSection.test.tsx +879 -0
  707. manigent/web/frontend/src/components/settings/agent/ChannelsSection.tsx +1124 -0
  708. manigent/web/frontend/src/components/settings/agent/EnvVarsCard.test.ts +15 -0
  709. manigent/web/frontend/src/components/settings/agent/EnvVarsCard.tsx +301 -0
  710. manigent/web/frontend/src/components/settings/agent/ExtraDirsList.tsx +85 -0
  711. manigent/web/frontend/src/components/settings/agent/GeneralSection.tsx +88 -0
  712. manigent/web/frontend/src/components/settings/agent/HostDisclaimerSlot.test.tsx +32 -0
  713. manigent/web/frontend/src/components/settings/agent/HostDisclaimerSlot.tsx +30 -0
  714. manigent/web/frontend/src/components/settings/agent/HubChannelCard.test.tsx +449 -0
  715. manigent/web/frontend/src/components/settings/agent/HubChannelCard.tsx +312 -0
  716. manigent/web/frontend/src/components/settings/agent/HubInvitableAgentsCard.test.tsx +83 -0
  717. manigent/web/frontend/src/components/settings/agent/HubInvitableAgentsCard.tsx +64 -0
  718. manigent/web/frontend/src/components/settings/agent/HubSendUsageCard.test.tsx +69 -0
  719. manigent/web/frontend/src/components/settings/agent/HubSendUsageCard.tsx +42 -0
  720. manigent/web/frontend/src/components/settings/agent/HubTurnOffDialog.tsx +88 -0
  721. manigent/web/frontend/src/components/settings/agent/IsolationRecheckButton.test.tsx +26 -0
  722. manigent/web/frontend/src/components/settings/agent/IsolationRecheckButton.tsx +68 -0
  723. manigent/web/frontend/src/components/settings/agent/KnowledgeSection.tsx +68 -0
  724. manigent/web/frontend/src/components/settings/agent/LineChannelCard.test.tsx +428 -0
  725. manigent/web/frontend/src/components/settings/agent/LineChannelCard.tsx +343 -0
  726. manigent/web/frontend/src/components/settings/agent/RuntimeSection.test.tsx +164 -0
  727. manigent/web/frontend/src/components/settings/agent/RuntimeSection.tsx +118 -0
  728. manigent/web/frontend/src/components/settings/agent/StorageSection.tsx +78 -0
  729. manigent/web/frontend/src/components/settings/agent/WebChatChannelCard.test.tsx +253 -0
  730. manigent/web/frontend/src/components/settings/agent/WebChatChannelCard.tsx +401 -0
  731. manigent/web/frontend/src/components/settings/agent/WhatsAppChannelCard.test.tsx +462 -0
  732. manigent/web/frontend/src/components/settings/agent/WhatsAppChannelCard.tsx +289 -0
  733. manigent/web/frontend/src/components/settings/agent/agentSettings.test.tsx +109 -0
  734. manigent/web/frontend/src/components/settings/agent/channelSetupGuides.test.tsx +162 -0
  735. manigent/web/frontend/src/components/settings/agent/channelSetupGuides.tsx +119 -0
  736. manigent/web/frontend/src/components/settings/agent/channelsShared.ts +83 -0
  737. manigent/web/frontend/src/components/settings/agent/fields.tsx +67 -0
  738. manigent/web/frontend/src/components/settings/agent/sections.ts +36 -0
  739. manigent/web/frontend/src/components/settings/agent/useAgentSettingsDraft.ts +129 -0
  740. manigent/web/frontend/src/components/settings/agent/useInstallSelection.ts +38 -0
  741. manigent/web/frontend/src/components/settings/primitives.tsx +98 -0
  742. manigent/web/frontend/src/components/shared/ExpandToggle.tsx +45 -0
  743. manigent/web/frontend/src/components/shared/MarkdownBody.tsx +218 -0
  744. manigent/web/frontend/src/components/shared/MarkdownBody.untrusted.test.tsx +97 -0
  745. manigent/web/frontend/src/components/shared/RenderBody.tsx +90 -0
  746. manigent/web/frontend/src/components/shared/rehypeHideComments.test.tsx +115 -0
  747. manigent/web/frontend/src/components/shared/rehypeHideComments.ts +124 -0
  748. manigent/web/frontend/src/components/sharing/AcceptSharePage.tsx +141 -0
  749. manigent/web/frontend/src/components/sharing/ShareLandingPage.tsx +89 -0
  750. manigent/web/frontend/src/components/sidebar/AccountChip.test.tsx +337 -0
  751. manigent/web/frontend/src/components/sidebar/AccountChip.tsx +407 -0
  752. manigent/web/frontend/src/components/sidebar/ActiveWorkDot.tsx +9 -0
  753. manigent/web/frontend/src/components/sidebar/AgentProfile.tsx +44 -0
  754. manigent/web/frontend/src/components/sidebar/AgentRow.tsx +71 -0
  755. manigent/web/frontend/src/components/sidebar/AgentsPanel.retained.test.tsx +105 -0
  756. manigent/web/frontend/src/components/sidebar/AgentsPanel.tsx +231 -0
  757. manigent/web/frontend/src/components/sidebar/AssistantHero.tsx +48 -0
  758. manigent/web/frontend/src/components/sidebar/ChatroomItem.tsx +48 -0
  759. manigent/web/frontend/src/components/sidebar/CommandPalette.channelThreads.test.tsx +76 -0
  760. manigent/web/frontend/src/components/sidebar/CommandPalette.reopen.test.tsx +206 -0
  761. manigent/web/frontend/src/components/sidebar/CommandPalette.test.tsx +725 -0
  762. manigent/web/frontend/src/components/sidebar/CommandPalette.tsx +774 -0
  763. manigent/web/frontend/src/components/sidebar/ComputersPanel.tsx +144 -0
  764. manigent/web/frontend/src/components/sidebar/DmIcon.tsx +46 -0
  765. manigent/web/frontend/src/components/sidebar/DmsPanel.tsx +226 -0
  766. manigent/web/frontend/src/components/sidebar/FrontDeskPanel.channels.test.tsx +582 -0
  767. manigent/web/frontend/src/components/sidebar/FrontDeskPanel.test.tsx +151 -0
  768. manigent/web/frontend/src/components/sidebar/FrontDeskPanel.tsx +454 -0
  769. manigent/web/frontend/src/components/sidebar/HotkeysHelp.tsx +101 -0
  770. manigent/web/frontend/src/components/sidebar/MessageHitRow.test.tsx +84 -0
  771. manigent/web/frontend/src/components/sidebar/MessageHitRow.tsx +57 -0
  772. manigent/web/frontend/src/components/sidebar/MyDeskRailPanel.crossHighlight.test.tsx +247 -0
  773. manigent/web/frontend/src/components/sidebar/MyDeskRailPanel.delete.test.tsx +273 -0
  774. manigent/web/frontend/src/components/sidebar/MyDeskRailPanel.tsx +351 -0
  775. manigent/web/frontend/src/components/sidebar/PanelShell.tsx +48 -0
  776. manigent/web/frontend/src/components/sidebar/ProjectItem.tsx +161 -0
  777. manigent/web/frontend/src/components/sidebar/ProjectRow.tsx +151 -0
  778. manigent/web/frontend/src/components/sidebar/ProjectsPanel.tsx +80 -0
  779. manigent/web/frontend/src/components/sidebar/PublicPanel.counterpart.test.tsx +85 -0
  780. manigent/web/frontend/src/components/sidebar/PublicPanel.hub.test.tsx +147 -0
  781. manigent/web/frontend/src/components/sidebar/PublicPanel.tsx +271 -0
  782. manigent/web/frontend/src/components/sidebar/RecencyHeader.tsx +17 -0
  783. manigent/web/frontend/src/components/sidebar/ScheduledPanel.tsx +58 -0
  784. manigent/web/frontend/src/components/sidebar/SidebarAvatar.tsx +128 -0
  785. manigent/web/frontend/src/components/sidebar/SidebarRail.tsx +163 -0
  786. manigent/web/frontend/src/components/sidebar/TeamGroup.tsx +67 -0
  787. manigent/web/frontend/src/components/sidebar/dmPrefill.test.ts +69 -0
  788. manigent/web/frontend/src/components/sidebar/dmPrefill.ts +80 -0
  789. manigent/web/frontend/src/components/sidebar/index.ts +18 -0
  790. manigent/web/frontend/src/components/sidebar/railShortcuts.ts +18 -0
  791. manigent/web/frontend/src/components/today/lib.test.ts +70 -0
  792. manigent/web/frontend/src/components/today/lib.ts +296 -0
  793. manigent/web/frontend/src/components/today/today.css +113 -0
  794. manigent/web/frontend/src/components/welcome/RunnerSetup.css +253 -0
  795. manigent/web/frontend/src/components/welcome/RunnerSetup.tsx +148 -0
  796. manigent/web/frontend/src/components/welcome/WelcomePage.css +383 -0
  797. manigent/web/frontend/src/components/welcome/WelcomePage.tsx +329 -0
  798. manigent/web/frontend/src/components/welcome/welcomeCopy.test.ts +382 -0
  799. manigent/web/frontend/src/components/welcome/welcomeCopy.ts +313 -0
  800. manigent/web/frontend/src/config/llmProviders.test.ts +161 -0
  801. manigent/web/frontend/src/config/llmProviders.ts +109 -0
  802. manigent/web/frontend/src/config/providerIds.fixture.json +13 -0
  803. manigent/web/frontend/src/hooks/accountNavigation.test.tsx +138 -0
  804. manigent/web/frontend/src/hooks/index.ts +10 -0
  805. manigent/web/frontend/src/hooks/useActiveWorkBootstrap.ts +86 -0
  806. manigent/web/frontend/src/hooks/useAgentAccess.test.tsx +30 -0
  807. manigent/web/frontend/src/hooks/useAgentAccess.ts +204 -0
  808. manigent/web/frontend/src/hooks/useAgentSampleRequests.ts +27 -0
  809. manigent/web/frontend/src/hooks/useAgentSearch.ts +10 -0
  810. manigent/web/frontend/src/hooks/useAgents.ts +46 -0
  811. manigent/web/frontend/src/hooks/useAnnouncements.test.ts +112 -0
  812. manigent/web/frontend/src/hooks/useAnnouncements.ts +100 -0
  813. manigent/web/frontend/src/hooks/useAssistantAgent.ts +24 -0
  814. manigent/web/frontend/src/hooks/useAutoResizeTextarea.ts +18 -0
  815. manigent/web/frontend/src/hooks/useBridgeDeliveries.test.tsx +79 -0
  816. manigent/web/frontend/src/hooks/useBriefTabs.ts +122 -0
  817. manigent/web/frontend/src/hooks/useChannelThreads.ts +69 -0
  818. manigent/web/frontend/src/hooks/useComposeForm.ts +80 -0
  819. manigent/web/frontend/src/hooks/useComputers.ts +123 -0
  820. manigent/web/frontend/src/hooks/useContextFirstItem.ts +118 -0
  821. manigent/web/frontend/src/hooks/useCreateProject.ts +78 -0
  822. manigent/web/frontend/src/hooks/useDebouncedValue.ts +16 -0
  823. manigent/web/frontend/src/hooks/useDeleteProject.ts +27 -0
  824. manigent/web/frontend/src/hooks/useDeskDispatch.test.tsx +170 -0
  825. manigent/web/frontend/src/hooks/useDeskDispatch.ts +135 -0
  826. manigent/web/frontend/src/hooks/useDeskReadState.test.tsx +85 -0
  827. manigent/web/frontend/src/hooks/useDeskReadState.ts +150 -0
  828. manigent/web/frontend/src/hooks/useDeskRecentlyDone.test.tsx +222 -0
  829. manigent/web/frontend/src/hooks/useDeskRecentlyDone.ts +225 -0
  830. manigent/web/frontend/src/hooks/useDeskTour.ts +37 -0
  831. manigent/web/frontend/src/hooks/useDeskUpdates.channelThreads.test.tsx +91 -0
  832. manigent/web/frontend/src/hooks/useDeskUpdates.hubThreads.test.tsx +93 -0
  833. manigent/web/frontend/src/hooks/useDeskUpdates.ts +353 -0
  834. manigent/web/frontend/src/hooks/useDisconnectIntegration.ts +23 -0
  835. manigent/web/frontend/src/hooks/useDmThreads.ts +99 -0
  836. manigent/web/frontend/src/hooks/useFileDropzone.ts +93 -0
  837. manigent/web/frontend/src/hooks/useFileUpload.ts +38 -0
  838. manigent/web/frontend/src/hooks/useFiles.ts +98 -0
  839. manigent/web/frontend/src/hooks/useHub.test.tsx +237 -0
  840. manigent/web/frontend/src/hooks/useHub.ts +502 -0
  841. manigent/web/frontend/src/hooks/useInstall.ts +82 -0
  842. manigent/web/frontend/src/hooks/useLabels.test.tsx +294 -0
  843. manigent/web/frontend/src/hooks/useLabels.ts +287 -0
  844. manigent/web/frontend/src/hooks/useLogout.ts +49 -0
  845. manigent/web/frontend/src/hooks/useMentionDetection.ts +90 -0
  846. manigent/web/frontend/src/hooks/useMessageAttachments.deskLimit.test.ts +264 -0
  847. manigent/web/frontend/src/hooks/useMessageAttachments.ts +283 -0
  848. manigent/web/frontend/src/hooks/useMessageDeepLink.test.tsx +70 -0
  849. manigent/web/frontend/src/hooks/useMessageDeepLink.ts +32 -0
  850. manigent/web/frontend/src/hooks/useMessageSearch.ts +78 -0
  851. manigent/web/frontend/src/hooks/useModelConfigs.ts +178 -0
  852. manigent/web/frontend/src/hooks/useMyAgents.ts +18 -0
  853. manigent/web/frontend/src/hooks/usePointerReorder.ts +270 -0
  854. manigent/web/frontend/src/hooks/usePrefersReducedMotion.ts +18 -0
  855. manigent/web/frontend/src/hooks/useProjectPlan.ts +36 -0
  856. manigent/web/frontend/src/hooks/useProjectReport.ts +19 -0
  857. manigent/web/frontend/src/hooks/useProjects.ts +104 -0
  858. manigent/web/frontend/src/hooks/useProjectsWithChatrooms.ts +97 -0
  859. manigent/web/frontend/src/hooks/usePublicRequests.ts +89 -0
  860. manigent/web/frontend/src/hooks/useRecentAgents.ts +38 -0
  861. manigent/web/frontend/src/hooks/useRenameProject.test.tsx +185 -0
  862. manigent/web/frontend/src/hooks/useRenameProject.ts +192 -0
  863. manigent/web/frontend/src/hooks/useRoomTrust.ts +56 -0
  864. manigent/web/frontend/src/hooks/useSharing.ts +46 -0
  865. manigent/web/frontend/src/hooks/useSopRecents.ts +55 -0
  866. manigent/web/frontend/src/hooks/useSopSchedules.ts +25 -0
  867. manigent/web/frontend/src/hooks/useSops.ts +99 -0
  868. manigent/web/frontend/src/hooks/useSwitchAccount.ts +41 -0
  869. manigent/web/frontend/src/hooks/useTeamGroups.ts +48 -0
  870. manigent/web/frontend/src/hooks/useThreadOutbox.ts +68 -0
  871. manigent/web/frontend/src/hooks/useTodos.add.test.tsx +89 -0
  872. manigent/web/frontend/src/hooks/useTodos.linkProject.test.tsx +115 -0
  873. manigent/web/frontend/src/hooks/useTodos.saveDraft.test.tsx +188 -0
  874. manigent/web/frontend/src/hooks/useTodos.ts +396 -0
  875. manigent/web/frontend/src/hooks/useUntrustedBodies.test.tsx +87 -0
  876. manigent/web/frontend/src/hooks/useUntrustedBodies.ts +51 -0
  877. manigent/web/frontend/src/hooks/useWebSocket.briefTab.test.tsx +148 -0
  878. manigent/web/frontend/src/hooks/useWebSocket.channelThreads.test.tsx +82 -0
  879. manigent/web/frontend/src/hooks/useWebSocket.hostSync.test.tsx +92 -0
  880. manigent/web/frontend/src/hooks/useWebSocket.hub.test.tsx +114 -0
  881. manigent/web/frontend/src/hooks/useWebSocket.labelSync.test.tsx +168 -0
  882. manigent/web/frontend/src/hooks/useWebSocket.plan.test.tsx +168 -0
  883. manigent/web/frontend/src/hooks/useWebSocket.reconnect.test.tsx +166 -0
  884. manigent/web/frontend/src/hooks/useWebSocket.runnerVersions.test.tsx +167 -0
  885. manigent/web/frontend/src/hooks/useWebSocket.ts +495 -0
  886. manigent/web/frontend/src/hooks/useWindowFileDropGuard.ts +33 -0
  887. manigent/web/frontend/src/landing.tsx +11 -0
  888. manigent/web/frontend/src/main.tsx +17 -0
  889. manigent/web/frontend/src/private-beta.tsx +10 -0
  890. manigent/web/frontend/src/public.tsx +42 -0
  891. manigent/web/frontend/src/queryClient.ts +16 -0
  892. manigent/web/frontend/src/stores/activeWorkStore.ts +88 -0
  893. manigent/web/frontend/src/stores/announcementStore.test.ts +217 -0
  894. manigent/web/frontend/src/stores/announcementStore.ts +169 -0
  895. manigent/web/frontend/src/stores/authStore.multiSession.test.ts +304 -0
  896. manigent/web/frontend/src/stores/authStore.test.ts +235 -0
  897. manigent/web/frontend/src/stores/authStore.ts +693 -0
  898. manigent/web/frontend/src/stores/deskLabelViewStore.test.ts +162 -0
  899. manigent/web/frontend/src/stores/deskLabelViewStore.ts +234 -0
  900. manigent/web/frontend/src/stores/deskLinkStore.ts +54 -0
  901. manigent/web/frontend/src/stores/deskStore.test.ts +53 -0
  902. manigent/web/frontend/src/stores/deskStore.ts +93 -0
  903. manigent/web/frontend/src/stores/heldPostsStore.ts +78 -0
  904. manigent/web/frontend/src/stores/mcpAuthStore.ts +52 -0
  905. manigent/web/frontend/src/stores/modelSelectionStore.ts +33 -0
  906. manigent/web/frontend/src/stores/perUserStores.ts +79 -0
  907. manigent/web/frontend/src/stores/runnerVersionStore.test.ts +81 -0
  908. manigent/web/frontend/src/stores/runnerVersionStore.ts +60 -0
  909. manigent/web/frontend/src/stores/skillAuthStore.ts +54 -0
  910. manigent/web/frontend/src/stores/switchReset.test.ts +236 -0
  911. manigent/web/frontend/src/stores/tokenExp.ts +32 -0
  912. manigent/web/frontend/src/stores/uiStore.ts +186 -0
  913. manigent/web/frontend/src/stores/unreadStore.ts +204 -0
  914. manigent/web/frontend/src/stores/unsavedGuard.ts +22 -0
  915. manigent/web/frontend/src/styles/design/fonts/JetBrainsMono-Latin.woff2 +0 -0
  916. manigent/web/frontend/src/styles/design/fonts/Nunito-Latin.woff2 +0 -0
  917. manigent/web/frontend/src/styles/design/fonts/Nunito-LatinExt.woff2 +0 -0
  918. manigent/web/frontend/src/styles/design/tokens.css +160 -0
  919. manigent/web/frontend/src/styles/homepage.css +242 -0
  920. manigent/web/frontend/src/styles/index.css +126 -0
  921. manigent/web/frontend/src/styles/landing.css +52 -0
  922. manigent/web/frontend/src/test/authFixtures.ts +154 -0
  923. manigent/web/frontend/src/test/fetchHarness.ts +203 -0
  924. manigent/web/frontend/src/test/fixtures/channelThreads.smoke.json +42 -0
  925. manigent/web/frontend/src/test/hubFixtures.ts +212 -0
  926. manigent/web/frontend/src/test/queryWrapper.tsx +9 -0
  927. manigent/web/frontend/src/test/suiteHygiene.test.ts +212 -0
  928. manigent/web/frontend/src/types/channelThreadName.test.ts +16 -0
  929. manigent/web/frontend/src/types/index.ts +903 -0
  930. manigent/web/frontend/src/utils/agentAccessCopy.test.ts +53 -0
  931. manigent/web/frontend/src/utils/agentAccessCopy.ts +231 -0
  932. manigent/web/frontend/src/utils/agentDisplay.ts +81 -0
  933. manigent/web/frontend/src/utils/briefSchedule.ts +25 -0
  934. manigent/web/frontend/src/utils/channelThreads.test.ts +109 -0
  935. manigent/web/frontend/src/utils/channelThreads.ts +179 -0
  936. manigent/web/frontend/src/utils/chatroomDisplay.ts +11 -0
  937. manigent/web/frontend/src/utils/deskLinks.test.ts +155 -0
  938. manigent/web/frontend/src/utils/deskLinks.ts +115 -0
  939. manigent/web/frontend/src/utils/deskRecipient.ts +29 -0
  940. manigent/web/frontend/src/utils/formatDate.ts +102 -0
  941. manigent/web/frontend/src/utils/frontDesk.ts +65 -0
  942. manigent/web/frontend/src/utils/hubChannelView.test.ts +178 -0
  943. manigent/web/frontend/src/utils/hubChannelView.ts +307 -0
  944. manigent/web/frontend/src/utils/hubDelivery.test.ts +105 -0
  945. manigent/web/frontend/src/utils/hubDelivery.ts +137 -0
  946. manigent/web/frontend/src/utils/hubDisplay.test.ts +157 -0
  947. manigent/web/frontend/src/utils/hubDisplay.ts +221 -0
  948. manigent/web/frontend/src/utils/hubThread.test.ts +184 -0
  949. manigent/web/frontend/src/utils/hubThread.ts +247 -0
  950. manigent/web/frontend/src/utils/labelErrors.test.ts +127 -0
  951. manigent/web/frontend/src/utils/labelErrors.ts +123 -0
  952. manigent/web/frontend/src/utils/labelView.test.ts +846 -0
  953. manigent/web/frontend/src/utils/labelView.ts +760 -0
  954. manigent/web/frontend/src/utils/legalUrls.ts +11 -0
  955. manigent/web/frontend/src/utils/messageSender.test.ts +32 -0
  956. manigent/web/frontend/src/utils/messageSender.ts +29 -0
  957. manigent/web/frontend/src/utils/oauthScopeCopy.ts +73 -0
  958. manigent/web/frontend/src/utils/personalize.ts +49 -0
  959. manigent/web/frontend/src/utils/reachableAgents.test.ts +36 -0
  960. manigent/web/frontend/src/utils/reachableAgents.ts +24 -0
  961. manigent/web/frontend/src/utils/recencyBuckets.ts +61 -0
  962. manigent/web/frontend/src/utils/roomTrust.test.ts +94 -0
  963. manigent/web/frontend/src/utils/roomTrust.ts +111 -0
  964. manigent/web/frontend/src/utils/snippetHighlight.test.ts +63 -0
  965. manigent/web/frontend/src/utils/snippetHighlight.ts +42 -0
  966. manigent/web/frontend/src/utils/sopRecipient.ts +37 -0
  967. manigent/web/frontend/src/utils/sopRequest.test.ts +91 -0
  968. manigent/web/frontend/src/utils/sopRequest.ts +68 -0
  969. manigent/web/frontend/src/utils/sopSchedule.ts +26 -0
  970. manigent/web/frontend/src/utils/sopSection.test.ts +148 -0
  971. manigent/web/frontend/src/utils/sopSection.ts +143 -0
  972. manigent/web/frontend/src/utils/textSearch.test.ts +60 -0
  973. manigent/web/frontend/src/utils/textSearch.ts +43 -0
  974. manigent/web/frontend/src/utils/thoughtPartner.ts +26 -0
  975. manigent/web/frontend/src/utils/threadName.test.ts +131 -0
  976. manigent/web/frontend/src/utils/threadName.ts +122 -0
  977. manigent/web/frontend/src/utils/ticketView.test.ts +296 -0
  978. manigent/web/frontend/src/utils/ticketView.ts +175 -0
  979. manigent/web/frontend/src/utils/todoAttachments.desiredState.test.ts +180 -0
  980. manigent/web/frontend/src/utils/todoAttachments.ts +126 -0
  981. manigent/web/frontend/src/utils/todoDraft.test.ts +84 -0
  982. manigent/web/frontend/src/utils/todoDraft.ts +46 -0
  983. manigent/web/frontend/src/utils/todoSearch.test.ts +82 -0
  984. manigent/web/frontend/src/utils/todoSearch.ts +43 -0
  985. manigent/web/frontend/src/utils/versionMatch.test.ts +295 -0
  986. manigent/web/frontend/src/utils/versionMatch.ts +245 -0
  987. manigent/web/frontend/src/vite-env.d.ts +1 -0
  988. manigent/web/frontend/tailwind.config.js +86 -0
  989. manigent/web/frontend/tos.html +188 -0
  990. manigent/web/frontend/tsconfig.dts.json +25 -0
  991. manigent/web/frontend/tsconfig.json +26 -0
  992. manigent/web/frontend/tsconfig.node.json +11 -0
  993. manigent/web/frontend/vite.config.ts +41 -0
  994. manigent/web/frontend/vitest.config.ts +16 -0
  995. manigent/ws_edge/app.py +514 -0
  996. manigent/ws_edge/auth.py +104 -0
  997. manigent/ws_edge/link.py +282 -0
  998. manigent/ws_edge/registry.py +206 -0
  999. manigent/ws_edge/terminal_relay.py +269 -0
  1000. manigent_server-0.1.0.dist-info/METADATA +138 -0
  1001. manigent_server-0.1.0.dist-info/RECORD +1004 -0
  1002. manigent_server-0.1.0.dist-info/WHEEL +4 -0
  1003. manigent_server-0.1.0.dist-info/entry_points.txt +2 -0
  1004. manigent_server-0.1.0.dist-info/licenses/LICENSE +68 -0
@@ -0,0 +1,943 @@
1
+ import {
2
+ forwardRef,
3
+ useEffect,
4
+ useImperativeHandle,
5
+ useRef,
6
+ useState,
7
+ type ChangeEvent,
8
+ } from 'react'
9
+ import { createPortal } from 'react-dom'
10
+ import { useMutation, useQueryClient } from '@tanstack/react-query'
11
+ import { ArrowLeft, ArrowRight, ArrowUp, Check, CornerUpRight, FileText, Paperclip, Send } from 'lucide-react'
12
+ import Avatar from '../common/Avatar'
13
+ import AgentNameLink from '../agent/AgentNameLink'
14
+ import MarkdownBody from '../shared/MarkdownBody'
15
+ import { useRoomTrust } from '../../hooks/useRoomTrust'
16
+ import { RenderBody } from '../shared/RenderBody'
17
+ import FileViewerModal from '../files/FileViewerModal'
18
+ import DropOverlay from '../common/DropOverlay'
19
+ import StagedAttachments from '../chatroom/StagedAttachments'
20
+ import ForwardModal from '../chatroom/ForwardModal'
21
+ import PromptEditor, { type PromptEditorHandle } from '../composer/PromptEditor'
22
+ import { useMessageAttachments } from '../../hooks/useMessageAttachments'
23
+ import { useFileDropzone } from '../../hooks/useFileDropzone'
24
+ import { sendUserMessage } from '../../api/endpoints/messages'
25
+ import { sendHubDraft } from '../../hooks/useHub'
26
+ import { completeProject } from '../../api/endpoints/projects'
27
+ import { useProjectReport } from '../../hooks/useProjectReport'
28
+ import { PlanBand } from './PlanBand'
29
+ import { type PlanSendState } from '../project/plan/PlanEditor'
30
+ import { PhaseRail } from './PhaseRail'
31
+ import { CardBand } from './CardBand'
32
+ import {
33
+ bandsPresent,
34
+ defaultOpenBands,
35
+ isGated,
36
+ planFoldMeta,
37
+ type BandId,
38
+ } from './deskBands'
39
+ import { useMarkDeskRead } from '../../hooks/useDeskReadState'
40
+ import { type DeskFile, type DeskUpdate } from '../../hooks/useDeskUpdates'
41
+ import { AgentStatus } from '../../types'
42
+
43
+ // The card leads with WHAT (the title) and follows with WHO (the agent byline):
44
+ // scanning the desk is a scan of subjects, not of names. No lane pill and no
45
+ // source subtitle — the desk section a card sits in already says its lane
46
+ // (Review / Working / Recently done), and the source repeated the title.
47
+
48
+ // Imperative surface so DeskPage's keyboard shortcuts can drive a card's own
49
+ // behaviors without hoisting all of its local state.
50
+ export interface UpdateCardHandle {
51
+ toggleExpand: () => void
52
+ toggleReply: () => void
53
+ dismiss: () => void
54
+ /** Opens the Forward dialog; no-op on a card with nothing forwardable. */
55
+ forward: () => void
56
+ open: () => void
57
+ scrollIntoView: () => void
58
+ }
59
+
60
+ // One update: review + reply-in-place, mark done, or open the thread. A card is
61
+ // a DM or Project; "Send Back" posts straight to its user-communication room.
62
+ const UpdateCard = forwardRef<
63
+ UpdateCardHandle,
64
+ {
65
+ item: DeskUpdate
66
+ /** `tab` is the destination surface within the project — omitted for the
67
+ * ordinary "Open project", `'plan'` for the PLAN band's release control,
68
+ * which must land the user on the document their answer is owed to. */
69
+ onOpen: (item: DeskUpdate, tab?: 'plan') => void
70
+ onToast: (msg: string) => void
71
+ // Fired synchronously the instant the one removing action (Done) actually
72
+ // REMOVES the card — after the complete POST has succeeded, before the
73
+ // read-state write or any refetch. Lets the desk snapshot the current card
74
+ // order for cursor recovery. No-op unless this card is selected.
75
+ //
76
+ // It moved behind the POST when Done started completing the project: a
77
+ // failed complete leaves the card on the desk, and a snapshot taken for a
78
+ // removal that never happened would sit stale until some later action
79
+ // consumed it and recovered the cursor to the wrong row.
80
+ onActStart?: (projectId: string) => void
81
+ // Done (read-lane) cards only: hand this card up to the desk, which
82
+ // drafts a "turn this into an SOP" request to the owner's assistant in the
83
+ // top composer. Omitted ⇒ the button is not rendered.
84
+ onSaveAsSop?: (item: DeskUpdate) => void
85
+ selected?: boolean
86
+ /** A hovered to-do on the rail lists this card's `projectId` in its
87
+ * `project_ids`. Paints the cross-highlight. Orthogonal to `selected` —
88
+ * both can be true at once, and they paint on different CSS channels
89
+ * (outline vs border+ring) so they stack instead of fighting. */
90
+ linked?: boolean
91
+ /** Pointer entered / left this card. Hands up `item.projectId` on enter and
92
+ * `null` on leave; the DESK decides which side of the pair lights, because
93
+ * it is the only place holding both the cards and the to-dos.
94
+ *
95
+ * Wired to mouseenter/mouseleave, NOT the pointer events: this is a mouse
96
+ * affordance by design. Touch fires an enter with no matching leave, which
97
+ * would strand the highlight lit with nothing left to clear it. */
98
+ onHover?: (projectId: string | null) => void
99
+ }
100
+ >(function UpdateCard(
101
+ { item, onOpen, onToast, onActStart, onSaveAsSop, selected, linked, onHover },
102
+ ref,
103
+ ) {
104
+ const { markRead } = useMarkDeskRead()
105
+ const queryClient = useQueryClient()
106
+ const [replying, setReplying] = useState(false)
107
+ const [text, setText] = useState('')
108
+ const [acted, setActed] = useState(false)
109
+ const [expanded, setExpanded] = useState(false)
110
+ const [forwarding, setForwarding] = useState(false)
111
+ // Same trust rule as the room: a Hub thread whole, and the quote's author
112
+ // per author (a Telegram or email contact, a requester, another account's
113
+ // agent). The quote's files rode in with it, so they share its answer.
114
+ const trust = useRoomTrust(item.projectId, 'user-communication')
115
+ const untrusted = item.quoteOutsideIsolated || (item.quoteAuthorId === null
116
+ ? trust.unknownAuthorUntrusted
117
+ : trust.isUntrusted(item.quoteAuthorId))
118
+ // Forwardable = the card quotes an agent's message. The user's own pending
119
+ // request is not something to hand on, and an empty thread has nothing.
120
+ // Forwarding is additive — it never marks the card done — so every lane
121
+ // carries it, history included. Never for untrusted content: the server
122
+ // quotes the body word for word into the owner's DM, which draws it trusted.
123
+ const canForward = !!item.latestMessageId && !item.quoteIsYours && !untrusted
124
+ // Index into item.files of the file open in the viewer; null = closed.
125
+ // Owned by the card (not per pill) so ← / → can step across its files.
126
+ const [openFileIdx, setOpenFileIdx] = useState<number | null>(null)
127
+ const openFile = openFileIdx !== null ? item.files[openFileIdx] : undefined
128
+ const editorRef = useRef<PromptEditorHandle | null>(null)
129
+ const rootRef = useRef<HTMLDivElement | null>(null)
130
+ const fileInputRef = useRef<HTMLInputElement | null>(null)
131
+
132
+ // Attachment staging for the inline reply — same primitives (and 20 MB cap)
133
+ // as the top-of-desk DeskComposer, so a dropped/picked file rides the reply's
134
+ // atomic send. Each open card owns its own instance; staged files never leak
135
+ // between cards.
136
+ const { stagedFiles, files, addFiles, removeStagedFile, clearStagedFiles, attachmentError } =
137
+ useMessageAttachments()
138
+
139
+ const isProgress = item.lane === 'progress'
140
+ // "Recently done" cards are historical — Open-only. Re-marking a card done
141
+ // is nonsense, so Done / Send Back are suppressed (M1 ruling 3); the card
142
+ // keeps the same layout as its live siblings, minus the removing actions.
143
+ const isRead = item.lane === 'read'
144
+ const actionable = !isProgress && !isRead
145
+
146
+ // THE THREE BANDS, in the user's fixed order: REPORT, PLAN, RESPONSE. Absent
147
+ // bands are OMITTED ROWS, never placeholders.
148
+ //
149
+ // `opened` is seeded on mount from `defaultOpenBands`, which opens at most
150
+ // ONE fetching band (report or plan) — two would be two fetches per card —
151
+ // plus RESPONSE alongside a report, which costs nothing. Folding is never
152
+ // destructive and every band is reachable by click.
153
+ const present = bandsPresent(item)
154
+ const [opened, setOpened] = useState<Partial<Record<BandId, boolean>>>(() =>
155
+ Object.fromEntries(defaultOpenBands(item).map((id) => [id, true])),
156
+ )
157
+ const toggleBand = (id: BandId) => setOpened((o) => ({ ...o, [id]: !o[id] }))
158
+
159
+ // A REPORT THAT ARRIVES AFTER MOUNT re-opens REPORT and RESPONSE. The seed
160
+ // above runs once, so without this a card mounted while the project was
161
+ // still running keeps its earlier choice and the new report stays folded
162
+ // until a reload. Keyed on the publish stamp, so a republished report opens
163
+ // again too; any other fold the user made is left alone.
164
+ const reportAt = item.reportPublishedAt
165
+ const prevReportAt = useRef(reportAt)
166
+ useEffect(() => {
167
+ if (reportAt && reportAt !== prevReportAt.current) {
168
+ setOpened((o) => ({ ...o, report: true, response: true }))
169
+ }
170
+ prevReportAt.current = reportAt
171
+ }, [reportAt])
172
+
173
+ // THE PLAN EDITOR'S OPEN SIGNAL — A COUNTER, NOT A BOOLEAN. The compact
174
+ // editor is UNMOUNTED while the PLAN band is folded (that is the request-cost
175
+ // invariant two comments down), so it reads this ON MOUNT; a boolean would
176
+ // need someone to reset it and could not fire a second time, and unfolding
177
+ // the band a second time is an ordinary thing to do.
178
+ //
179
+ // TWO MECHANISMS IN ONE ORDER, and they cannot race because the second only
180
+ // ever runs inside the first: UNFOLDING the PLAN band MOUNTS the editor (the
181
+ // precondition), and the counter SELECTS the deviation's section (the
182
+ // action). Incrementing on the unfold is what makes that ordering structural
183
+ // rather than a convention two call sites have to keep.
184
+ const [planOpenSignal, setPlanOpenSignal] = useState(0)
185
+ const togglePlanBand = () => {
186
+ if (!opened.plan) setPlanOpenSignal((n) => n + 1)
187
+ toggleBand('plan')
188
+ }
189
+
190
+ /** THE BATCH THE PLAN EDITOR IS HOLDING, reported up from inside the PLAN
191
+ * band so this card's own action row can send it. `null` whenever there is
192
+ * no batch — including while the band is folded, because the editor is
193
+ * genuinely unmounted then and says so on the way out.
194
+ *
195
+ * THIS IS THE ONE PIECE OF REAL PLUMBING IN THE CHANGE, and it buys the rule
196
+ * the row below depends on: **exactly one primary action per card, and it is
197
+ * always in the same place.** `Send review` used to live four inches up,
198
+ * inside a box that flows in the card's scroll, while the row that never
199
+ * moves carried `Done` and `Send Back` — so on any card with a real review
200
+ * the two terminal verbs were the visible ones and the review's own button
201
+ * was the one you had to go find.
202
+ *
203
+ * It holds no rows and no notes. See `PlanSendState`: a card that could read
204
+ * the batch could start deciding it, and the editor is the one write funnel. */
205
+ const [planSend, setPlanSend] = useState<PlanSendState | null>(null)
206
+
207
+ /** ARMED = `Mark Done` has been pressed once while a review was staged, and
208
+ * the button is now asking whether to throw that review away.
209
+ *
210
+ * THE ONE STATE WHERE MARK DONE DESTROYS SOMETHING THE USER TYPED, and so
211
+ * the one place it asks twice. Everything else it clears is recoverable:
212
+ * completing is undone by replying into the thread, and an unanswered plan
213
+ * note is still there when the project reopens. Staged rows are not — they
214
+ * live in the editor and nothing has posted them.
215
+ *
216
+ * It is not a dialog and not a second surface: the button relabels itself in
217
+ * place, and one more press does what the label now says. */
218
+ const [armedDiscard, setArmedDiscard] = useState(false)
219
+
220
+ // The question expires with its subject. If the batch empties — sent,
221
+ // discarded in the tray, or the PLAN band folded and unmounted the editor —
222
+ // an armed button would be asking about a review that no longer exists.
223
+ useEffect(() => {
224
+ if (!planSend) setArmedDiscard(false)
225
+ }, [planSend])
226
+
227
+ // THE DEVIATION PREDICATE, WRITTEN ONCE PER CARD and passed down from here.
228
+ // All five carriers that replace the reference design's promotion-to-focus
229
+ // fire off this and nothing else: the card's urgent left rule, the rail's
230
+ // reddened hint, the PLAN band's warn tone, the PLAN band being
231
+ // default-expanded, and the `.plan-dev` alarm itself — which the editor now
232
+ // renders off its own notes, so this card no longer feeds it anything.
233
+ const offContract = !!item.plan?.deviation
234
+
235
+ // THE HALTED CONJUNCTION, computed here — at its ONE display site — rather
236
+ // than carried as a flag. There is no `halted` member on the wire and none is
237
+ // invented here or anywhere else.
238
+ const gated = isGated(item.plan)
239
+
240
+ /** HOW MANY PLAN NOTES ARE WAITING ON THE USER. Read off the summary the card
241
+ * already carries — `plan.open_notes_for_you`, the same integer the server's
242
+ * execution gate refuses on — and nothing is added to it. A DM has no plan
243
+ * and answers 0, which is right: there is nothing to review, so `Mark Done`
244
+ * is the primary.
245
+ *
246
+ * IT DECIDES WHICH BUTTON IS PRIMARY, NOT WHICH BUTTONS EXIST. `Mark Done`
247
+ * is on every actionable card; while a review is waiting it simply is not
248
+ * the loudest thing in the row. There is no `accepted` check anywhere here,
249
+ * deliberately: a second condition that happened to agree with the count is
250
+ * what the whole change removed from the server, and re-deriving it here
251
+ * would put it back. */
252
+ const planOpenForYou = item.plan?.open_notes_for_you ?? 0
253
+
254
+ // REQUEST COST — this is the design, not an optimisation. A folded band
255
+ // issues NO request: the folded row renders entirely from `PlanSummary` and
256
+ // the report stamp, both already on the card off the project list. The desk
257
+ // makes zero plan requests today and at most ONE report request per card
258
+ // here, and only once the user opens that band.
259
+ //
260
+ // WAVE 2 DEPENDS ON THIS INVARIANT HOLDING: the compact `PlanEditor` mounts
261
+ // only while the PLAN band is expanded, so a folded PLAN band costs zero
262
+ // `GET …/plan`. If that is ever removed, a desk of twelve plan-bearing cards
263
+ // fires twelve plan requests on load. It is also why the editor's open signal
264
+ // must be a COUNTER rather than a boolean — an unmounted editor reads it on
265
+ // mount.
266
+ const reportBand = present.includes('report')
267
+ const { data: report } = useProjectReport(
268
+ reportBand && opened.report ? item.projectId : undefined,
269
+ )
270
+
271
+ useEffect(() => {
272
+ if (replying) editorRef.current?.focusEnd()
273
+ }, [replying])
274
+
275
+ const dest = item.kind === 'project' ? `#${item.sourceName}` : `@${item.agentName}`
276
+
277
+ // Play the card's leave animation, then mark the card done: optimistically
278
+ // write the watermark to the store (so useDeskUpdates drops the card) and PUT
279
+ // it to the server, which broadcasts to the owner's other sessions. On a
280
+ // failed PUT the store rolls back — the card reappears — and we toast the
281
+ // error.
282
+ //
283
+ // `dismiss()` is the ONLY caller. That is what makes "Recently done"
284
+ // honest: every row in the read-state store is a Done, never a Send Back.
285
+ const animateOut = (toastMsg: string) => {
286
+ setActed(true)
287
+ onToast(toastMsg)
288
+ window.setTimeout(() => markRead(item, onToast), 340)
289
+ }
290
+
291
+ // Send Back does NOT mark the card done and does not animate it away. The POST
292
+ // synchronously creates pending work for the coordinator (a desk reply carries
293
+ // no @mention, so messages.py fills in the project's coordinator as the
294
+ // implicit responder), and the resulting `active_participants` broadcast flips
295
+ // the card's lane to `progress` — the first branch in useDeskUpdates, so it
296
+ // beats every other lane. The card stays on the desk and re-lanes from
297
+ // *Review* to *Working*, which is where the work actually is.
298
+ //
299
+ // A hub thread is written only through the hub send route (the user-message
300
+ // route answers `use_hub_send`), so its Send Back goes there.
301
+ const sendMutation = useMutation({
302
+ mutationFn: async ({ content, files }: { content: string; files: File[] }) => {
303
+ if (item.hubThread) await sendHubDraft(queryClient, item.projectId, content, files)
304
+ else await sendUserMessage(item.projectId, 'user-communication', { content, files })
305
+ },
306
+ onSuccess: () => {
307
+ setReplying(false)
308
+ setText('')
309
+ editorRef.current?.clear()
310
+ clearStagedFiles()
311
+ onToast(`Sent back to ${dest} — @${item.agentName} is on it.`)
312
+ },
313
+ onError: (err: Error) => onToast(err.message || 'Failed to send back'),
314
+ })
315
+
316
+ // Drag files onto the reply block → same staging path as the paperclip.
317
+ // Disabled while a send is in flight.
318
+ const { isDragging, dragHandlers } = useFileDropzone({
319
+ onFiles: addFiles,
320
+ disabled: sendMutation.isPending,
321
+ })
322
+
323
+ // Send text + staged files atomically. A file-only reply (no text) is allowed,
324
+ // so the guard passes when EITHER text OR at least one file is present.
325
+ const send = () => {
326
+ const content = text.trim()
327
+ if ((!content && files.length === 0) || sendMutation.isPending) return
328
+ // Never ship a half-filled template: pulse the empty blanks and open the
329
+ // first instead of sending "[Deadline]" to an agent.
330
+ if (editorRef.current?.nudgeEmpty()) return
331
+ // No onActStart here: Send Back no longer removes the card, so the cursor
332
+ // never has to recover from it. Freezing the order now would leave a stale
333
+ // snapshot to be consumed by whatever removes the card later.
334
+ sendMutation.mutate({ content, files })
335
+ }
336
+
337
+ // <input type=file> → stage picked files, then reset the value so the same
338
+ // file can be re-picked immediately.
339
+ const handleFileChange = (e: ChangeEvent<HTMLInputElement>) => {
340
+ if (e.target.files && e.target.files.length > 0) addFiles(e.target.files)
341
+ e.target.value = ''
342
+ }
343
+
344
+ // Cancel: drop the draft AND any staged files for this card.
345
+ const cancelReply = () => {
346
+ setReplying(false)
347
+ setText('')
348
+ editorRef.current?.clear()
349
+ clearStagedFiles()
350
+ }
351
+
352
+ /**
353
+ * DONE = COMPLETE THE PROJECT, THEN CLEAR THE CARD. Two writes, in that
354
+ * order, and the order is the whole design.
355
+ *
356
+ * The button used to write only the read watermark. The card left the desk,
357
+ * but the project stayed `active` and the ticket chip on any to-do linked to
358
+ * it still read Working — two surfaces on one screen disagreeing about
359
+ * whether the thing was finished. The completion is the missing half.
360
+ *
361
+ * COMPLETE FIRST. `markRead` is what makes the card disappear, and it already
362
+ * rolls back and restores the card if its PUT fails. If the completion ran
363
+ * second and failed, the card would be gone with the chip still reading
364
+ * Working and nothing left on screen to say so. Failing first means the card
365
+ * stays put and the user sees the error.
366
+ *
367
+ * NO KIND GATE. Both `dm` and `project` cards complete — a card is exactly
368
+ * one project (the desk keys them `${projectId}|user-communication`), and the
369
+ * lane gate above already withholds Done from cards whose agents are
370
+ * mid-turn (`progress`) and from history (`read`).
371
+ *
372
+ * Done is not terminal: replying into the thread reactivates the project
373
+ * server-side, which is what the toast promises.
374
+ */
375
+ const completeMutation = useMutation({
376
+ mutationFn: () => completeProject(item.projectId),
377
+ onSuccess: () => {
378
+ // THE 200 IS PROOF, AND IT USED TO BE THROWN AWAY. The response says
379
+ // this project is now `completed`, which is exactly the input the
380
+ // to-do's ticket state is derived from — yet the plate was refreshed
381
+ // ONLY by the `desk_todo_sync` frame the server pushes a moment later.
382
+ // One transport, no fallback: if that frame lands on a socket that is
383
+ // down or reconnecting, the chip keeps reading Working with nothing
384
+ // scheduled to correct it. Invalidating here makes the plate converge on
385
+ // the round trip the client ALREADY made, whatever the socket is doing.
386
+ //
387
+ // Deliberately an invalidate and not a local write of `state`: the field
388
+ // is derived from ALL of a to-do's associations (useTodos: "no verb
389
+ // writes `state`, and none can"), so the browser cannot compute it — a
390
+ // to-do linked to this project AND another still-running one is still
391
+ // Working. Only the server knows that; we just ask it again.
392
+ queryClient.invalidateQueries({ queryKey: ['desk-todos'] })
393
+ onActStart?.(item.projectId) // parity with send() — snapshot before removal
394
+ animateOut('Marked done — cleared from your desk. Reply to reopen.')
395
+ },
396
+ onError: (err: Error) =>
397
+ onToast(err.message || 'Couldn’t mark this done — try again.'),
398
+ })
399
+
400
+ /** MARK DONE IS ALWAYS AVAILABLE, and the one thing standing in front of it
401
+ * is the staged batch.
402
+ *
403
+ * Open plan notes do NOT hold it back. "I'm finished with this thread" is a
404
+ * decision the user is entitled to make over the top of unanswered
405
+ * questions — the coordinator asking something does not oblige an answer,
406
+ * and the questions survive the completion: replying reopens the project
407
+ * with every note still open. Withholding the button in that state left a
408
+ * card with an unconfirmed plan and no way off the desk at all.
409
+ *
410
+ * A STAGED BATCH IS THE EXCEPTION because it is the only thing here that
411
+ * cannot come back. See `armedDiscard`: the first press relabels the button
412
+ * to say what the second one will throw away.
413
+ *
414
+ * THE RULE LIVES HERE RATHER THAN ONLY IN THE JSX. `DeskPage`'s `x` shortcut
415
+ * calls straight through the imperative handle, so a question asked only by
416
+ * a button's label would be skipped entirely by the keyboard — which is why
417
+ * the arming press also toasts. */
418
+ const dismiss = () => {
419
+ if (!actionable || acted || completeMutation.isPending) return
420
+ if (planSend && !armedDiscard) {
421
+ setArmedDiscard(true)
422
+ const n = planSend.count
423
+ onToast(
424
+ `${n} ${n === 1 ? 'reply is' : 'replies are'} staged and unsent — Mark Done again to discard ${n === 1 ? 'it' : 'them'} and complete the project.`,
425
+ )
426
+ return
427
+ }
428
+ completeMutation.mutate()
429
+ }
430
+
431
+ const toggleReply = () => {
432
+ if (!actionable || acted) return
433
+ setReplying((r) => !r)
434
+ }
435
+
436
+ /**
437
+ * START REVIEW — NAVIGATION, AND IT CANNOT SEND.
438
+ *
439
+ * The state it exists for is *comments are open and you have decided none of
440
+ * them*: there is nothing to send, so a Send button would be disabled chrome,
441
+ * and `Mark Done` would offer to clear a card whose whole content is
442
+ * unanswered questions. What the user actually needs is to get to the list.
443
+ *
444
+ * It unfolds the PLAN band if it is folded and scrolls the band to the top of
445
+ * the viewport. The comment list is the first thing the editor renders, so
446
+ * landing on the band lands on the list — which is why this does not reach
447
+ * inside the editor for a target it does not own.
448
+ *
449
+ * `setTimeout` rather than a layout effect: the band mounts the editor on
450
+ * this same click, so at call time the element either does not exist or is
451
+ * about to change height under us. One frame is enough and a wrong scroll
452
+ * position is the entire cost of being early.
453
+ */
454
+ const startReview = () => {
455
+ if (!opened.plan) togglePlanBand()
456
+ window.setTimeout(() => {
457
+ rootRef.current
458
+ ?.querySelector('[data-testid="plan-band"]')
459
+ ?.scrollIntoView({ block: 'start', behavior: 'smooth' })
460
+ }, 0)
461
+ }
462
+
463
+ useImperativeHandle(
464
+ ref,
465
+ () => ({
466
+ toggleExpand: () => setExpanded((e) => !e),
467
+ toggleReply,
468
+ dismiss,
469
+ forward: () => canForward && setForwarding(true),
470
+ open: () => onOpen(item),
471
+ scrollIntoView: () =>
472
+ rootRef.current?.scrollIntoView({ block: 'nearest', behavior: 'smooth' }),
473
+ }),
474
+ // `completeMutation.isPending` is in the deps because `dismiss` now reads
475
+ // it as its re-entrancy guard: without it the keyboard shortcut would hold
476
+ // a closure that still sees `false` and could fire a second POST.
477
+ // `planSend` and `armedDiscard` join the deps because `dismiss` now reads
478
+ // both: a stale closure would let the keyboard shortcut discard a staged
479
+ // review on the first press, which is the one thing the arming step exists
480
+ // to stop.
481
+ // eslint-disable-next-line react-hooks/exhaustive-deps
482
+ [item, isProgress, acted, completeMutation.isPending, planSend, armedDiscard, canForward],
483
+ )
484
+
485
+ /** `Mark Done` IS ONE BUTTON WITH TWO HOMES IN THE ROW, not two buttons. It
486
+ * leads when there is nothing else to do and trails `Reply Back` otherwise,
487
+ * so it is never the neighbour of the primary it used to be mistaken for —
488
+ * written once here and placed twice below, because a row is an order and
489
+ * order is not something a className can express. */
490
+ const donePrimary = !planSend && planOpenForYou === 0
491
+ const markDoneButton = (
492
+ <button
493
+ className={donePrimary ? 'md-btn md-btn--primary' : 'md-btn'}
494
+ data-testid="card-mark-done"
495
+ onClick={dismiss}
496
+ disabled={completeMutation.isPending}
497
+ >
498
+ <Check size={14} />
499
+ {armedDiscard ? `Discard ${planSend?.count ?? 0} & mark done` : 'Mark Done'}
500
+ </button>
501
+ )
502
+
503
+ return (
504
+ <div
505
+ ref={rootRef}
506
+ className={
507
+ 'md-card md-card--' +
508
+ item.lane +
509
+ // Carrier 1 of 5. The red left rule is what replaces the reference
510
+ // design's promotion-to-focus, scanning-wise: it is visible in the feed
511
+ // before the card is opened.
512
+ (offContract ? ' md-card--urgent' : '') +
513
+ (acted ? ' md-card--acted' : '') +
514
+ (selected ? ' md-card--selected' : '') +
515
+ // Never set by this card for ITSELF: hovering card A lights A's
516
+ // to-dos, not A's neighbours. The desk only ever hands `linked` back
517
+ // down to the OTHER side of the pair.
518
+ (linked ? ' md-card--linked' : '')
519
+ }
520
+ onMouseEnter={onHover ? () => onHover(item.projectId) : undefined}
521
+ onMouseLeave={onHover ? () => onHover(null) : undefined}
522
+ >
523
+ <div className="md-card-h">
524
+ <div className="md-card-title">{item.title}</div>
525
+ <span className="md-when">{relativeShort(item.ts)}</span>
526
+ </div>
527
+
528
+ <div className="md-card-by">
529
+ <Avatar
530
+ name={item.agentName}
531
+ status={(item.agent?.status as AgentStatus) ?? undefined}
532
+ size="xs"
533
+ />
534
+ {item.agent ? (
535
+ <AgentNameLink agentName={item.agent.name} className="md-card-agent">
536
+ @{item.agentName}
537
+ </AgentNameLink>
538
+ ) : (
539
+ <span className="md-card-agent">@{item.agentName}</span>
540
+ )}
541
+ </div>
542
+
543
+ {/* Whenever the rail renders it is the first element under the byline and
544
+ it never moves. The rail being the fixed thing is what makes an omitted
545
+ band read as an omission rather than as a missing card — and its third
546
+ step, `is-next` and un-filled, is already the "no report yet"
547
+ affordance, which is why an absent REPORT band needs no placeholder.
548
+ A DM (`plan === null`) renders no rail at all: it has no plan to be
549
+ missing, so the card starts straight at its response band. */}
550
+ <PhaseRail plan={item.plan} hostName={item.agentName} offContract={offContract} />
551
+
552
+ {item.files.length > 0 && item.lane !== 'report' && (
553
+ <div className="md-card-files">
554
+ {item.files.map((f, i) => (
555
+ <DeskFilePill
556
+ key={f.filename}
557
+ filename={f.filename}
558
+ entryType={f.entryType}
559
+ onOpen={() => setOpenFileIdx(i)}
560
+ />
561
+ ))}
562
+ </div>
563
+ )}
564
+ {/* Portaled over the desk, for the card's user-communication room — the
565
+ room every desk card is anchored to (deskRoomKey). */}
566
+ {openFile && openFileIdx !== null &&
567
+ createPortal(
568
+ <FileViewerModal
569
+ projectId={item.projectId}
570
+ chatroomName="user-communication"
571
+ filename={openFile.filename}
572
+ untrusted={untrusted}
573
+ onClose={() => setOpenFileIdx(null)}
574
+ nav={{
575
+ index: openFileIdx,
576
+ total: item.files.length,
577
+ onPrev: () => setOpenFileIdx(openFileIdx - 1),
578
+ onNext: () => setOpenFileIdx(openFileIdx + 1),
579
+ }}
580
+ />,
581
+ document.body,
582
+ )}
583
+
584
+ {/* THE THREE BANDS, FIXED ORDER, EVERY CARD: report, plan, response.
585
+ `bandsPresent` is the only thing that decides which of them render, so
586
+ the order cannot be re-derived per lane or per phase. */}
587
+ {present.map((id) => {
588
+ if (id === 'report') {
589
+ return (
590
+ <CardBand
591
+ key="report"
592
+ id="report"
593
+ expanded={!!opened.report}
594
+ onToggle={() => toggleBand('report')}
595
+ label="Report"
596
+ definition="the outcome, measured against the Plan — no action needed, just your read"
597
+ rev={<span className="md-cs-rev mono">{shortStamp(item.reportPublishedAt)}</span>}
598
+ foldMeta={report?.title || 'the completion report'}
599
+ foldCta="Read"
600
+ >
601
+ {report ? (
602
+ <div className="max-h-[420px] overflow-y-auto scrollbar-thin rounded-md border border-[var(--line-2)] px-3 py-2.5">
603
+ <RenderBody
604
+ renderCodeJs={report.render_code_js}
605
+ data={report.data}
606
+ runKey={`${item.projectId}::${report.generated_at}`}
607
+ active
608
+ />
609
+ </div>
610
+ ) : (
611
+ <div className="md-cs-def">Loading the report…</div>
612
+ )}
613
+ </CardBand>
614
+ )
615
+ }
616
+
617
+ if (id === 'plan') {
618
+ const fold = planFoldMeta(item.plan!)
619
+ return (
620
+ <CardBand
621
+ key="plan"
622
+ id="plan"
623
+ expanded={!!opened.plan}
624
+ onToggle={togglePlanBand}
625
+ // Carrier 3 of 5 — and `tone` is set from the deviation predicate
626
+ // and from nothing else. A merely-waiting card is `attn`, never
627
+ // red; nothing missing is ever red.
628
+ tone={fold.tone}
629
+ label="Plan · the contract"
630
+ definition="what you and the agents agreed to build — every deviation comes back here for your call"
631
+ foldMeta={fold.meta}
632
+ foldCta={fold.cta}
633
+ >
634
+ <PlanBand
635
+ plan={item.plan}
636
+ projectId={item.projectId}
637
+ // The editor mounts ONLY here. `CardBand` renders no children
638
+ // while folded, so this is belt-and-braces at one call site
639
+ // rather than a second source of truth — and it is what keeps
640
+ // a twelve-card desk at one `GET …/plan` instead of twelve.
641
+ expanded={!!opened.plan}
642
+ openSignal={planOpenSignal}
643
+ // THE CARD DRAWS THE SEND. Passing this is also what tells the
644
+ // editor to stop drawing its own — one primary per card.
645
+ onSendState={setPlanSend}
646
+ // NO `onOpenEditor`. The band's `Open the full Plan` button is
647
+ // gone: it NAVIGATED to `…?tab=plan`, unmounting this card and
648
+ // taking the user out of the desk, on cards where the in-band
649
+ // editor was already mounted directly below it. The one thing
650
+ // the tab still had that the band did not was the acceptance
651
+ // strip, which now renders inside `PlanEditor` at both densities.
652
+ />
653
+ </CardBand>
654
+ )
655
+ }
656
+
657
+ return (
658
+ <CardBand
659
+ key="response"
660
+ id="response"
661
+ expanded={!!opened.response}
662
+ onToggle={() => toggleBand('response')}
663
+ // Until the agent answers, the newest message is the user's own —
664
+ // calling that a "Response" misreads it as the agent's reply.
665
+ label={item.quoteIsYours || item.quoteFromContact ? 'Request' : 'Response'}
666
+ definition={
667
+ item.quoteIsYours
668
+ ? `what you asked @${item.agentName} — no response yet`
669
+ : item.quoteFromContact
670
+ ? `what ${item.quoteFromContact} asked @${item.agentName} — no response yet`
671
+ : `what @${item.agentName} is telling you right now`
672
+ }
673
+ foldMeta={item.quote || 'no message yet'}
674
+ foldCta={item.lane === 'reply' ? 'Answer' : 'Read'}
675
+ >
676
+ {item.quote && (
677
+ <div
678
+ className={
679
+ 'md-card-quote' +
680
+ (isProgress ? ' md-card-quote--flat' : '') +
681
+ (!expanded ? ' md-card-quote--clickable' : '')
682
+ }
683
+ onClick={!expanded ? () => setExpanded(true) : undefined}
684
+ >
685
+ <MarkdownBody
686
+ content={item.quote}
687
+ untrusted={untrusted}
688
+ clampable
689
+ collapsed={!expanded}
690
+ onToggle={() => setExpanded((e) => !e)}
691
+ collapsedHeightPx={120}
692
+ />
693
+ </div>
694
+ )}
695
+ </CardBand>
696
+ )
697
+ })}
698
+
699
+ {/* THE LIVE METER. `.md-prog` is a SIBLING of the card body, not a child
700
+ of the Response band, so band fold state suppresses nothing — which is
701
+ why the gate is the halted conjunction itself rather than a layout
702
+ outcome. A gated card draws the static held line in the PLAN band
703
+ instead, and this is a stronger guarantee than suppression-by-fold:
704
+ it is one boolean, at one display site. */}
705
+ {isProgress && !gated && (
706
+ <div className="md-prog">
707
+ <span className="md-prog-dot" />
708
+ <span className="md-prog-lbl">Your team is on it</span>
709
+ </div>
710
+ )}
711
+
712
+ {/* THE ACTION ROW — EVERY ACTION IS ALWAYS THERE; WHAT MOVES IS WHICH ONE
713
+ IS PRIMARY, and the rule that decides is a single sentence: **the
714
+ primary is the next thing to do.**
715
+
716
+ rows staged -> Send review · N | Reply Back · Mark Done
717
+ comments open -> Start review | Reply Back · Mark Done
718
+ nothing open -> Mark Done | Reply Back
719
+
720
+ WHY PROMOTION RATHER THAN WITHHOLDING. The complaint this answers is
721
+ that a user who has just replied to four agent comments reads `Done`
722
+ as the next step — a reasonable reading, because `Done` was where the
723
+ eye ended up: it was the primary, it never moved, and `Send review`
724
+ was inside a box that scrolls with the card. Both halves of that are
725
+ fixed by the review's own button being the primary in this row. Taking
726
+ `Mark Done` off the card as well solved the same complaint twice, and
727
+ charged for it: a user who wants a thread off their desk while the
728
+ coordinator has questions open had no button at all, on the one card
729
+ type that always has a question open.
730
+
731
+ So it stays, demoted, and it sits AFTER `Reply Back` — the far end of
732
+ the row from the primary — so the two are never adjacent under a
733
+ thumb. When nothing is open it takes the primary slot, which is the
734
+ first render site below rather than a style swap in place.
735
+
736
+ The one press it does not honour immediately is with a batch staged:
737
+ see `armedDiscard`. Nothing else here is lossy — completing is undone
738
+ by replying, and an unanswered note is still open when the project
739
+ reopens.
740
+
741
+ `Done` IS NOW `Mark Done` and `Send Back` IS NOW `Reply Back`. The
742
+ first because the button removes the card and does not complete the
743
+ work, and "Done" is a review word on a screen full of review; the
744
+ second because it opens a free-text reply, so "send back" oversold it
745
+ as a routing decision competing with the user's actual review — and at
746
+ 22 characters `Reply to @some-coordinator` broke the row on a phone.
747
+ `Reply Back` holds the line beside `Send review · 4` at 330px. */}
748
+ <div className="md-actions">
749
+ {actionable && !acted && donePrimary && markDoneButton}
750
+ {actionable && !acted && planSend && (
751
+ <button
752
+ className="md-btn md-btn--primary"
753
+ data-testid="card-send-review"
754
+ disabled={planSend.submitting}
755
+ onClick={planSend.submit}
756
+ >
757
+ <Send size={14} />
758
+ {planSend.submitting ? 'Sending…' : `Send review · ${planSend.count}`}
759
+ </button>
760
+ )}
761
+ {actionable && !acted && !planSend && planOpenForYou > 0 && (
762
+ <button
763
+ className="md-btn md-btn--primary"
764
+ data-testid="card-start-review"
765
+ onClick={startReview}
766
+ >
767
+ <ArrowUp size={14} />
768
+ Start review
769
+ </button>
770
+ )}
771
+ {actionable && !acted && (
772
+ <button className="md-btn" data-testid="card-reply-back" onClick={toggleReply}>
773
+ <ArrowLeft size={14} />
774
+ Reply Back
775
+ </button>
776
+ )}
777
+ {actionable && !acted && !donePrimary && markDoneButton}
778
+ {/* History cards are Open-only, with one exception: distilling a
779
+ finished project into a reusable SOP is additive — it removes
780
+ nothing from the desk and re-marks nothing done — so it is the one
781
+ action the read lane carries. Rendered before "Open project" so the
782
+ row reads [Save as SOP] [Open project]. */}
783
+ {isRead && onSaveAsSop && (
784
+ <button className="md-btn" onClick={() => onSaveAsSop(item)}>
785
+ <FileText size={14} />
786
+ Save as SOP
787
+ </button>
788
+ )}
789
+ {canForward && (
790
+ <button className="md-btn" data-testid="card-forward" onClick={() => setForwarding(true)}>
791
+ <CornerUpRight size={14} />
792
+ Forward
793
+ </button>
794
+ )}
795
+ <button className="md-btn md-btn--ghost" onClick={() => onOpen(item)}>
796
+ Open {item.kind === 'project' ? 'project' : 'thread'}
797
+ <ArrowRight size={13} />
798
+ </button>
799
+ </div>
800
+
801
+ {forwarding && canForward && item.latestMessageId && (
802
+ <ForwardModal
803
+ target={{
804
+ projectId: item.projectId,
805
+ chatroomName: 'user-communication',
806
+ messageIds: [item.latestMessageId],
807
+ // An outside contact's words are forwarded under their name, never
808
+ // re-attributed to the agent fielding the thread.
809
+ senderName: item.quoteFromContact ?? item.agentName,
810
+ senderId: item.quoteFromContact ? undefined : item.agent?.id,
811
+ content: item.quote,
812
+ files: item.files.map((f) => f.filename),
813
+ untrusted,
814
+ }}
815
+ onClose={() => setForwarding(false)}
816
+ onSent={(names) => onToast(`Forwarded to ${names.join(', ')}`)}
817
+ />
818
+ )}
819
+
820
+ {replying && (
821
+ <div className="md-reply relative" {...dragHandlers}>
822
+ <DropOverlay visible={isDragging} />
823
+ <div className="md-reply-to">
824
+ <Avatar name="You" size="xs" />
825
+ <span>you</span>
826
+ <ArrowRight size={11} />
827
+ <span className="md-reply-dest">{dest}</span>
828
+ </div>
829
+ {/* No `agents` prop: a desk reply has no roster context, so an
830
+ `{{X|agent}}` blank degrades to free-text entry — right here. */}
831
+ <PromptEditor
832
+ ref={editorRef}
833
+ className="md-reply-in"
834
+ minHeight={56}
835
+ maxHeight={160}
836
+ placeholder={`Reply to @${item.agentName}…`}
837
+ disabled={sendMutation.isPending}
838
+ onChange={(t) => setText(t)}
839
+ onSubmit={send}
840
+ />
841
+ <StagedAttachments
842
+ files={stagedFiles}
843
+ onRemove={removeStagedFile}
844
+ disabled={sendMutation.isPending}
845
+ />
846
+ {attachmentError && <p className="md-reply-err">{attachmentError}</p>}
847
+ <div className="md-reply-bar">
848
+ <span className="md-reply-hint">
849
+ Drop files or attach · <kbd>⌘</kbd>
850
+ <kbd>↵</kbd>
851
+ </span>
852
+ <div className="md-reply-actions">
853
+ <input
854
+ ref={fileInputRef}
855
+ type="file"
856
+ multiple
857
+ className="hidden"
858
+ onChange={handleFileChange}
859
+ />
860
+ <button
861
+ className="md-reply-attach"
862
+ title="Attach file"
863
+ disabled={sendMutation.isPending}
864
+ onClick={() => fileInputRef.current?.click()}
865
+ >
866
+ <Paperclip size={14} />
867
+ </button>
868
+ <button className="md-btn md-btn--quiet" onClick={cancelReply}>
869
+ Cancel
870
+ </button>
871
+ <button
872
+ className="md-btn md-btn--primary"
873
+ disabled={(!text.trim() && stagedFiles.length === 0) || sendMutation.isPending}
874
+ onClick={send}
875
+ >
876
+ <Send size={13} />
877
+ command
878
+ </button>
879
+ </div>
880
+ </div>
881
+ </div>
882
+ )}
883
+ </div>
884
+ )
885
+ })
886
+
887
+ export default UpdateCard
888
+
889
+ // Desk-styled file pill; the card owns the FileViewerModal it opens. Click
890
+ // stops propagation so it doesn't also toggle the card's expand-on-click quote.
891
+ function DeskFilePill({
892
+ filename,
893
+ entryType,
894
+ onOpen,
895
+ }: {
896
+ filename: string
897
+ entryType: DeskFile['entryType']
898
+ onOpen: () => void
899
+ }) {
900
+ const action = entryType === 'file_created' ? 'created' : 'updated'
901
+ return (
902
+ <button
903
+ type="button"
904
+ className="md-file-pill"
905
+ title={`${action}: ${filename} — click to view`}
906
+ onClick={(e) => {
907
+ e.stopPropagation()
908
+ onOpen()
909
+ }}
910
+ >
911
+ <FileText size={12} />
912
+ <span className="truncate">{filename}</span>
913
+ </button>
914
+ )
915
+ }
916
+
917
+ // The REPORT band's mono trailer: when the report was published. A pass-through
918
+ // of the stamp the project list already carries — the band is present because
919
+ // that stamp is non-null, so printing it says nothing the presence did not
920
+ // already earn.
921
+ function shortStamp(iso: string | null): string {
922
+ if (!iso) return ''
923
+ const d = new Date(iso)
924
+ if (isNaN(d.getTime())) return ''
925
+ return d.toLocaleDateString(undefined, { month: 'short', day: 'numeric' })
926
+ }
927
+
928
+ // Terse relative label (mirrors formatRelativeTime but always includes the "ago"
929
+ // framing the design uses on cards).
930
+ function relativeShort(iso: string): string {
931
+ if (!iso) return ''
932
+ const then = new Date(iso).getTime()
933
+ if (isNaN(then)) return ''
934
+ const sec = Math.floor((Date.now() - then) / 1000)
935
+ if (sec < 60) return 'just now'
936
+ const min = Math.floor(sec / 60)
937
+ if (min < 60) return `${min}m ago`
938
+ const hr = Math.floor(min / 60)
939
+ if (hr < 24) return `${hr}h ago`
940
+ const day = Math.floor(hr / 24)
941
+ if (day < 7) return `${day}d ago`
942
+ return new Date(iso).toLocaleDateString(undefined, { month: 'short', day: 'numeric' })
943
+ }