fleetlens 0.2.9 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (237) hide show
  1. package/app/apps/web/.next/BUILD_ID +1 -1
  2. package/app/apps/web/.next/app-path-routes-manifest.json +9 -0
  3. package/app/apps/web/.next/build-manifest.json +6 -6
  4. package/app/apps/web/.next/prerender-manifest.json +3 -3
  5. package/app/apps/web/.next/routes-manifest.json +62 -0
  6. package/app/apps/web/.next/server/app/_global-error/page/build-manifest.json +3 -3
  7. package/app/apps/web/.next/server/app/_global-error.html +1 -1
  8. package/app/apps/web/.next/server/app/_global-error.rsc +1 -1
  9. package/app/apps/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  10. package/app/apps/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  11. package/app/apps/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  12. package/app/apps/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  13. package/app/apps/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  14. package/app/apps/web/.next/server/app/_not-found/page/build-manifest.json +3 -3
  15. package/app/apps/web/.next/server/app/_not-found/page.js +1 -1
  16. package/app/apps/web/.next/server/app/_not-found/page.js.nft.json +1 -1
  17. package/app/apps/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  18. package/app/apps/web/.next/server/app/api/ask/route.js +3 -2
  19. package/app/apps/web/.next/server/app/api/ask/route.js.nft.json +1 -1
  20. package/app/apps/web/.next/server/app/api/events/route.js +1 -0
  21. package/app/apps/web/.next/server/app/api/events/route.js.nft.json +1 -1
  22. package/app/apps/web/.next/server/app/api/insights/months-index/route/app-paths-manifest.json +3 -0
  23. package/app/apps/web/.next/server/app/api/insights/months-index/route/build-manifest.json +9 -0
  24. package/app/apps/web/.next/server/app/api/insights/months-index/route/server-reference-manifest.json +4 -0
  25. package/app/apps/web/.next/server/app/api/insights/months-index/route.js +7 -0
  26. package/app/apps/web/.next/server/app/api/insights/months-index/route.js.map +5 -0
  27. package/app/apps/web/.next/server/app/api/insights/months-index/route.js.nft.json +1 -0
  28. package/app/apps/web/.next/server/app/api/insights/months-index/route_client-reference-manifest.js +3 -0
  29. package/app/apps/web/.next/server/app/api/insights/pdf/[key]/route/app-paths-manifest.json +3 -0
  30. package/app/apps/web/.next/server/app/api/insights/pdf/[key]/route/build-manifest.json +9 -0
  31. package/app/apps/web/.next/server/app/api/insights/pdf/[key]/route/server-reference-manifest.json +4 -0
  32. package/app/apps/web/.next/server/app/api/insights/pdf/[key]/route.js +6 -0
  33. package/app/apps/web/.next/server/app/api/insights/pdf/[key]/route.js.map +5 -0
  34. package/app/apps/web/.next/server/app/api/insights/pdf/[key]/route.js.nft.json +1 -0
  35. package/app/apps/web/.next/server/app/api/insights/pdf/[key]/route_client-reference-manifest.js +3 -0
  36. package/app/apps/web/.next/server/app/api/insights/route/app-paths-manifest.json +3 -0
  37. package/app/apps/web/.next/server/app/api/insights/route/build-manifest.json +9 -0
  38. package/app/apps/web/.next/server/app/api/insights/route/server-reference-manifest.json +4 -0
  39. package/app/apps/web/.next/server/app/api/insights/route.js +8 -0
  40. package/app/apps/web/.next/server/app/api/insights/route.js.map +5 -0
  41. package/app/apps/web/.next/server/app/api/insights/route.js.nft.json +1 -0
  42. package/app/apps/web/.next/server/app/api/insights/route_client-reference-manifest.js +3 -0
  43. package/app/apps/web/.next/server/app/api/insights/saved/[key]/route/app-paths-manifest.json +3 -0
  44. package/app/apps/web/.next/server/app/api/insights/saved/[key]/route/build-manifest.json +9 -0
  45. package/app/apps/web/.next/server/app/api/insights/saved/[key]/route/server-reference-manifest.json +4 -0
  46. package/app/apps/web/.next/server/app/api/insights/saved/[key]/route.js +6 -0
  47. package/app/apps/web/.next/server/app/api/insights/saved/[key]/route.js.map +5 -0
  48. package/app/apps/web/.next/server/app/api/insights/saved/[key]/route.js.nft.json +1 -0
  49. package/app/apps/web/.next/server/app/api/insights/saved/[key]/route_client-reference-manifest.js +3 -0
  50. package/app/apps/web/.next/server/app/api/insights/saved/route/app-paths-manifest.json +3 -0
  51. package/app/apps/web/.next/server/app/api/insights/saved/route/build-manifest.json +9 -0
  52. package/app/apps/web/.next/server/app/api/insights/saved/route/server-reference-manifest.json +4 -0
  53. package/app/apps/web/.next/server/app/api/insights/saved/route.js +6 -0
  54. package/app/apps/web/.next/server/app/api/insights/saved/route.js.map +5 -0
  55. package/app/apps/web/.next/server/app/api/insights/saved/route.js.nft.json +1 -0
  56. package/app/apps/web/.next/server/app/api/insights/saved/route_client-reference-manifest.js +3 -0
  57. package/app/apps/web/.next/server/app/api/insights/weeks-index/route/app-paths-manifest.json +3 -0
  58. package/app/apps/web/.next/server/app/api/insights/weeks-index/route/build-manifest.json +9 -0
  59. package/app/apps/web/.next/server/app/api/insights/weeks-index/route/server-reference-manifest.json +4 -0
  60. package/app/apps/web/.next/server/app/api/insights/weeks-index/route.js +7 -0
  61. package/app/apps/web/.next/server/app/api/insights/weeks-index/route.js.map +5 -0
  62. package/app/apps/web/.next/server/app/api/insights/weeks-index/route.js.nft.json +1 -0
  63. package/app/apps/web/.next/server/app/api/insights/weeks-index/route_client-reference-manifest.js +3 -0
  64. package/app/apps/web/.next/server/app/insights/[key]/page/app-paths-manifest.json +3 -0
  65. package/app/apps/web/.next/server/app/insights/[key]/page/build-manifest.json +17 -0
  66. package/app/apps/web/.next/server/app/insights/[key]/page/next-font-manifest.json +6 -0
  67. package/app/apps/web/.next/server/app/insights/[key]/page/react-loadable-manifest.json +1 -0
  68. package/app/apps/web/.next/server/app/insights/[key]/page/server-reference-manifest.json +4 -0
  69. package/app/apps/web/.next/server/app/insights/[key]/page.js +16 -0
  70. package/app/apps/web/.next/server/app/insights/[key]/page.js.map +5 -0
  71. package/app/apps/web/.next/server/app/insights/[key]/page.js.nft.json +1 -0
  72. package/app/apps/web/.next/server/app/insights/[key]/page_client-reference-manifest.js +3 -0
  73. package/app/apps/web/.next/server/app/insights/page/app-paths-manifest.json +3 -0
  74. package/app/apps/web/.next/server/app/insights/page/build-manifest.json +17 -0
  75. package/app/apps/web/.next/server/app/insights/page/next-font-manifest.json +6 -0
  76. package/app/apps/web/.next/server/app/insights/page/react-loadable-manifest.json +1 -0
  77. package/app/apps/web/.next/server/app/insights/page/server-reference-manifest.json +4 -0
  78. package/app/apps/web/.next/server/app/insights/page.js +16 -0
  79. package/app/apps/web/.next/server/app/insights/page.js.map +5 -0
  80. package/app/apps/web/.next/server/app/insights/page.js.nft.json +1 -0
  81. package/app/apps/web/.next/server/app/insights/page_client-reference-manifest.js +3 -0
  82. package/app/apps/web/.next/server/app/insights/print/[key]/page/app-paths-manifest.json +3 -0
  83. package/app/apps/web/.next/server/app/insights/print/[key]/page/build-manifest.json +17 -0
  84. package/app/apps/web/.next/server/app/insights/print/[key]/page/next-font-manifest.json +6 -0
  85. package/app/apps/web/.next/server/app/insights/print/[key]/page/react-loadable-manifest.json +1 -0
  86. package/app/apps/web/.next/server/app/insights/print/[key]/page/server-reference-manifest.json +4 -0
  87. package/app/apps/web/.next/server/app/insights/print/[key]/page.js +16 -0
  88. package/app/apps/web/.next/server/app/insights/print/[key]/page.js.map +5 -0
  89. package/app/apps/web/.next/server/app/insights/print/[key]/page.js.nft.json +1 -0
  90. package/app/apps/web/.next/server/app/insights/print/[key]/page_client-reference-manifest.js +3 -0
  91. package/app/apps/web/.next/server/app/page/build-manifest.json +3 -3
  92. package/app/apps/web/.next/server/app/page.js +3 -3
  93. package/app/apps/web/.next/server/app/page.js.nft.json +1 -1
  94. package/app/apps/web/.next/server/app/page_client-reference-manifest.js +1 -1
  95. package/app/apps/web/.next/server/app/parallelism/page/build-manifest.json +3 -3
  96. package/app/apps/web/.next/server/app/parallelism/page.js +1 -1
  97. package/app/apps/web/.next/server/app/parallelism/page.js.nft.json +1 -1
  98. package/app/apps/web/.next/server/app/parallelism/page_client-reference-manifest.js +1 -1
  99. package/app/apps/web/.next/server/app/projects/[slug]/page/build-manifest.json +3 -3
  100. package/app/apps/web/.next/server/app/projects/[slug]/page.js +2 -3
  101. package/app/apps/web/.next/server/app/projects/[slug]/page.js.nft.json +1 -1
  102. package/app/apps/web/.next/server/app/projects/[slug]/page_client-reference-manifest.js +1 -1
  103. package/app/apps/web/.next/server/app/projects/page/build-manifest.json +3 -3
  104. package/app/apps/web/.next/server/app/projects/page.js +1 -1
  105. package/app/apps/web/.next/server/app/projects/page.js.nft.json +1 -1
  106. package/app/apps/web/.next/server/app/projects/page_client-reference-manifest.js +1 -1
  107. package/app/apps/web/.next/server/app/sessions/[id]/page/build-manifest.json +3 -3
  108. package/app/apps/web/.next/server/app/sessions/[id]/page.js +2 -2
  109. package/app/apps/web/.next/server/app/sessions/[id]/page.js.nft.json +1 -1
  110. package/app/apps/web/.next/server/app/sessions/[id]/page_client-reference-manifest.js +1 -1
  111. package/app/apps/web/.next/server/app/sessions/page/build-manifest.json +3 -3
  112. package/app/apps/web/.next/server/app/sessions/page.js +1 -1
  113. package/app/apps/web/.next/server/app/sessions/page.js.nft.json +1 -1
  114. package/app/apps/web/.next/server/app/sessions/page_client-reference-manifest.js +1 -1
  115. package/app/apps/web/.next/server/app/usage/page/build-manifest.json +3 -3
  116. package/app/apps/web/.next/server/app/usage/page.js +1 -1
  117. package/app/apps/web/.next/server/app/usage/page.js.nft.json +1 -1
  118. package/app/apps/web/.next/server/app/usage/page_client-reference-manifest.js +1 -1
  119. package/app/apps/web/.next/server/app-paths-manifest.json +9 -0
  120. package/app/apps/web/.next/server/chunks/02dz_web__next-internal_server_app_api_insights_months-index_route_actions_0~h~zci.js +3 -0
  121. package/app/apps/web/.next/server/chunks/02dz_web__next-internal_server_app_api_insights_saved_[key]_route_actions_0a5j9yw.js +3 -0
  122. package/app/apps/web/.next/server/chunks/02dz_web__next-internal_server_app_api_insights_weeks-index_route_actions_0hh-1c7.js +3 -0
  123. package/app/apps/web/.next/server/chunks/{[externals]__0rv9ct5._.js → [externals]__0_xztrf._.js} +2 -2
  124. package/app/apps/web/.next/server/chunks/[root-of-the-server]__0.hmj0z._.js +3 -0
  125. package/app/apps/web/.next/server/chunks/[root-of-the-server]__01h_g-p._.js +3 -0
  126. package/app/apps/web/.next/server/chunks/[root-of-the-server]__0nh-z10._.js +3 -0
  127. package/app/apps/web/.next/server/chunks/[root-of-the-server]__0op9af~._.js +1 -1
  128. package/app/apps/web/.next/server/chunks/[root-of-the-server]__0ri7q~r._.js +3 -0
  129. package/app/apps/web/.next/server/chunks/[root-of-the-server]__0rr1~~x._.js +3 -0
  130. package/app/apps/web/.next/server/chunks/[root-of-the-server]__0s0gv.0._.js +3 -0
  131. package/app/apps/web/.next/server/chunks/apps_web__next-internal_server_app_api_insights_pdf_[key]_route_actions_0lu-lnx.js +3 -0
  132. package/app/apps/web/.next/server/chunks/apps_web__next-internal_server_app_api_insights_route_actions_0h8l5lz.js +3 -0
  133. package/app/apps/web/.next/server/chunks/apps_web__next-internal_server_app_api_insights_saved_route_actions_13rlblw.js +3 -0
  134. package/app/apps/web/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_00t2xj6.js +68 -0
  135. package/app/apps/web/.next/server/chunks/{[root-of-the-server]__0sgyieh._.js → node_modules_next_dist_esm_build_templates_app-route_0ck.78s.js} +5 -5
  136. package/app/apps/web/.next/server/chunks/packages_parser_dist_fs_0bc7ptm.js +3 -0
  137. package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__00z7zlm._.js +3 -0
  138. package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__08.d5ia._.js +12 -0
  139. package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0b323~k._.js +8 -0
  140. package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0euu8sa._.js +3 -0
  141. package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0gio_ck._.js +3 -0
  142. package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0gx-4qa._.js +3 -0
  143. package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0w52fbc._.js +3 -3
  144. package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0z45d0g._.js +49 -0
  145. package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__13itnvx._.js +3 -0
  146. package/app/apps/web/.next/server/chunks/ssr/_09n75_5._.js +1 -1
  147. package/app/apps/web/.next/server/chunks/ssr/_0r~__0v._.js +12 -0
  148. package/app/apps/web/.next/server/chunks/ssr/_0xies90._.js +1 -1
  149. package/app/apps/web/.next/server/chunks/ssr/apps_web__next-internal_server_app_insights_[key]_page_actions_0_z2~e_.js +3 -0
  150. package/app/apps/web/.next/server/chunks/ssr/apps_web__next-internal_server_app_insights_page_actions_0zjdm~n.js +3 -0
  151. package/app/apps/web/.next/server/chunks/ssr/apps_web__next-internal_server_app_insights_print_[key]_page_actions_0a853fw.js +3 -0
  152. package/app/apps/web/.next/server/chunks/ssr/apps_web_components_06j3c.0._.js +1 -1
  153. package/app/apps/web/.next/server/chunks/ssr/apps_web_components_insight-report_tsx_13q2a-c._.js +12 -0
  154. package/app/apps/web/.next/server/chunks/ssr/apps_web_components_insights-view_tsx_11730ta._.js +6 -0
  155. package/app/apps/web/.next/server/chunks/ssr/node_modules_lucide-react_dist_esm_icons_0-lmm4p._.js +3 -0
  156. package/app/apps/web/.next/server/chunks/ssr/node_modules_lucide-react_dist_esm_icons_0nqz8dw._.js +3 -0
  157. package/app/apps/web/.next/server/chunks/ssr/node_modules_next_dist_0t-hj0x._.js +3 -0
  158. package/app/apps/web/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_0vz7_px.js +4 -0
  159. package/app/apps/web/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_10cdrh2.js +4 -0
  160. package/app/apps/web/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_12n1lw_.js +4 -0
  161. package/app/apps/web/.next/server/chunks/ssr/packages_parser_dist_0-jvt~a._.js +1 -1
  162. package/app/apps/web/.next/server/chunks/ssr/packages_parser_dist_analytics_0k4dgst.js +1 -1
  163. package/app/apps/web/.next/server/functions-config-manifest.json +7 -1
  164. package/app/apps/web/.next/server/middleware-build-manifest.js +6 -6
  165. package/app/apps/web/.next/server/pages/500.html +1 -1
  166. package/app/apps/web/.next/server/server-reference-manifest.js +1 -1
  167. package/app/apps/web/.next/server/server-reference-manifest.json +1 -1
  168. package/app/apps/web/.next/static/chunks/03.mv4y4.w-vb.js +6 -0
  169. package/app/apps/web/.next/static/chunks/03_7xtpi9sv4g.js +4 -0
  170. package/app/apps/web/.next/static/chunks/03g5dw5eskotd.js +6 -0
  171. package/app/apps/web/.next/static/chunks/063oimt30jeo9.js +10 -0
  172. package/app/apps/web/.next/static/chunks/08c1pcrocqq5s.js +6 -0
  173. package/app/apps/web/.next/static/chunks/0e-wrhqh4h3r5.js +2 -0
  174. package/app/apps/web/.next/static/chunks/0fc6dk2qtokwh.js +1 -0
  175. package/app/apps/web/.next/static/chunks/0i7er7_bfab36.js +10 -0
  176. package/app/apps/web/.next/static/chunks/0ipytyi01h0ta.js +1 -0
  177. package/app/apps/web/.next/static/chunks/137uw2-qbro07.js +31 -0
  178. package/app/apps/web/.next/static/chunks/{turbopack-17n9y8y0h4q9f.js → turbopack-169w-1._23ra_.js} +1 -1
  179. package/app/apps/web/app/api/insights/months-index/route.ts +80 -0
  180. package/app/apps/web/app/api/insights/pdf/[key]/route.ts +45 -0
  181. package/app/apps/web/app/api/insights/route.ts +508 -0
  182. package/app/apps/web/app/api/insights/saved/[key]/route.ts +11 -0
  183. package/app/apps/web/app/api/insights/saved/route.ts +9 -0
  184. package/app/apps/web/app/api/insights/weeks-index/route.ts +95 -0
  185. package/app/apps/web/app/insights/[key]/page.tsx +59 -0
  186. package/app/apps/web/app/insights/page.tsx +7 -0
  187. package/app/apps/web/app/insights/print/[key]/page.tsx +73 -0
  188. package/app/apps/web/app/layout.tsx +2 -0
  189. package/app/apps/web/app/projects/[slug]/page.tsx +2 -0
  190. package/app/apps/web/app/sessions/[id]/page.tsx +27 -1
  191. package/app/apps/web/app/sessions/[id]/session-view.tsx +372 -793
  192. package/app/apps/web/app/sessions/[id]/team-tab/adapter.ts +633 -0
  193. package/app/apps/web/app/sessions/[id]/team-tab/format.ts +20 -0
  194. package/app/apps/web/app/sessions/[id]/team-tab/team-minimap.tsx +682 -0
  195. package/app/apps/web/app/sessions/[id]/team-tab/team-tab-client.tsx +78 -0
  196. package/app/apps/web/app/sessions/[id]/team-tab/team-table.tsx +796 -0
  197. package/app/apps/web/app/sessions/[id]/team-tab/turn-drawer.tsx +805 -0
  198. package/app/apps/web/app/sessions/[id]/tool-cards.tsx +461 -0
  199. package/app/apps/web/app/sessions/[id]/turn-steps.tsx +306 -0
  200. package/app/apps/web/app/sessions/sessions-grid.tsx +36 -5
  201. package/app/apps/web/components/insight-report.tsx +1031 -0
  202. package/app/apps/web/components/insights-view.tsx +647 -0
  203. package/app/apps/web/components/live-refresher.tsx +11 -2
  204. package/app/apps/web/components/live-sessions-widget.tsx +20 -6
  205. package/app/apps/web/components/sidebar.tsx +8 -0
  206. package/app/apps/web/components/team-badge.tsx +90 -0
  207. package/app/apps/web/eslint.config.mjs +2 -0
  208. package/app/apps/web/lib/ai/chrome-pdf.ts +64 -0
  209. package/app/apps/web/lib/ai/insights-prompt.ts +71 -0
  210. package/app/apps/web/lib/ai/saved-reports.ts +83 -0
  211. package/app/apps/web/lib/date-range.ts +11 -2
  212. package/app/apps/web/lib/use-live-events.ts +7 -2
  213. package/app/apps/web/package.json +3 -2
  214. package/app/apps/web/tsconfig.tsbuildinfo +1 -1
  215. package/dist/daemon-worker.js +2765 -11
  216. package/dist/index.js +3047 -253
  217. package/package.json +5 -3
  218. package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__08cr2u9._.js +0 -3
  219. package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0b..h_8._.js +0 -3
  220. package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0cdd~8n._.js +0 -3
  221. package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0ms7h0~._.js +0 -8
  222. package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0~a~v00._.js +0 -3
  223. package/app/apps/web/.next/server/chunks/ssr/_0zv~jv-._.js +0 -12
  224. package/app/apps/web/.next/server/chunks/ssr/_12dtryh._.js +0 -12
  225. package/app/apps/web/.next/server/chunks/ssr/node_modules_0i2xw~e._.js +0 -3
  226. package/app/apps/web/.next/server/chunks/ssr/node_modules_lucide-react_dist_esm_icons_0.knjn.._.js +0 -3
  227. package/app/apps/web/.next/static/chunks/0257pdz1-imal.js +0 -2
  228. package/app/apps/web/.next/static/chunks/03fw7f2obcvq~.js +0 -6
  229. package/app/apps/web/.next/static/chunks/07oyxajlp3dvb.js +0 -6
  230. package/app/apps/web/.next/static/chunks/0a24elrqoqqep.js +0 -10
  231. package/app/apps/web/.next/static/chunks/0i4zodv86~~~i.js +0 -1
  232. package/app/apps/web/.next/static/chunks/0qmh881w772ef.js +0 -31
  233. package/app/apps/web/.next/static/chunks/0rbm60-qyshdm.js +0 -6
  234. package/app/apps/web/.next/static/chunks/0wdga7zbwvg70.js +0 -1
  235. /package/app/apps/web/.next/static/{2rEsiJssG30QsuNT0Lp9S → G_4ofKI9v9ARC_lb9JILZ}/_buildManifest.js +0 -0
  236. /package/app/apps/web/.next/static/{2rEsiJssG30QsuNT0Lp9S → G_4ofKI9v9ARC_lb9JILZ}/_clientMiddlewareManifest.js +0 -0
  237. /package/app/apps/web/.next/static/{2rEsiJssG30QsuNT0Lp9S → G_4ofKI9v9ARC_lb9JILZ}/_ssgManifest.js +0 -0
@@ -0,0 +1,805 @@
1
+ "use client";
2
+
3
+ import { useEffect, useState } from "react";
4
+ import ReactMarkdown from "react-markdown";
5
+ import remarkGfm from "remark-gfm";
6
+ import { ChevronDown, ChevronRight } from "lucide-react";
7
+ import type {
8
+ ContentBlock,
9
+ PresentationRow,
10
+ TurnMegaRow,
11
+ SessionEvent,
12
+ } from "@claude-lens/parser";
13
+ import { formatGap } from "@/lib/format";
14
+ import type { TeamTurn } from "./adapter";
15
+ import { formatTeammatePreview } from "./format";
16
+ import { TurnStepsList, MAX_INLINE_STEPS, shortenToolName } from "../turn-steps";
17
+ import { ToolUseCard, type ToolUseInput } from "../tool-cards";
18
+
19
+ type Props = {
20
+ turn: TeamTurn | null;
21
+ trackLabel: string;
22
+ trackColor: string;
23
+ onClose: () => void;
24
+ };
25
+
26
+ type View =
27
+ | { page: "turn" }
28
+ | { page: "step"; row: PresentationRow; index: number };
29
+
30
+ /** Widened drawer to give the full turn card room to breathe. Reads as
31
+ * roughly the same layout as the transcript's collapsed-turn row — a
32
+ * standalone modal-ish card inside a drawer rather than a grid row. */
33
+ const DRAWER_WIDTH = 620;
34
+
35
+ export function TurnDrawer({ turn, trackLabel, trackColor, onClose }: Props) {
36
+ const [view, setView] = useState<View>({ page: "turn" });
37
+
38
+ useEffect(() => {
39
+ setView({ page: "turn" });
40
+ }, [turn?.id]);
41
+
42
+ useEffect(() => {
43
+ if (!turn) return;
44
+ const onKey = (e: KeyboardEvent) => {
45
+ if (e.key === "Escape") {
46
+ if (view.page === "step") setView({ page: "turn" });
47
+ else onClose();
48
+ }
49
+ };
50
+ window.addEventListener("keydown", onKey);
51
+ return () => window.removeEventListener("keydown", onKey);
52
+ }, [turn, onClose, view.page]);
53
+
54
+ if (!turn) return null;
55
+
56
+ const startStr = new Date(turn.startMs).toLocaleString();
57
+ const endStr = new Date(turn.endMs).toLocaleTimeString();
58
+ const durationStr = formatGap(turn.durationMs);
59
+
60
+ return (
61
+ <>
62
+ <div
63
+ onClick={onClose}
64
+ style={{
65
+ position: "fixed",
66
+ inset: 0,
67
+ background: "rgba(0,0,0,0.4)",
68
+ zIndex: 1000,
69
+ }}
70
+ />
71
+ <div
72
+ style={{
73
+ position: "fixed",
74
+ top: 0,
75
+ right: 0,
76
+ bottom: 0,
77
+ width: DRAWER_WIDTH,
78
+ maxWidth: "96vw",
79
+ background: "var(--af-surface-elevated)",
80
+ borderLeft: "1px solid var(--af-border-subtle)",
81
+ zIndex: 1001,
82
+ display: "flex",
83
+ flexDirection: "column",
84
+ fontSize: 12,
85
+ color: "var(--af-text)",
86
+ }}
87
+ >
88
+ <div
89
+ style={{
90
+ padding: "12px 16px",
91
+ borderBottom: "1px solid var(--af-border-subtle)",
92
+ display: "flex",
93
+ alignItems: "center",
94
+ justifyContent: "space-between",
95
+ gap: 8,
96
+ flexShrink: 0,
97
+ }}
98
+ >
99
+ {view.page === "step" ? (
100
+ <button
101
+ onClick={() => setView({ page: "turn" })}
102
+ style={{
103
+ background: "transparent",
104
+ border: "1px solid var(--af-border-subtle)",
105
+ color: "var(--af-text-secondary)",
106
+ padding: "4px 10px",
107
+ borderRadius: 4,
108
+ cursor: "pointer",
109
+ fontSize: 11,
110
+ fontFamily: "ui-monospace, monospace",
111
+ }}
112
+ >
113
+ ← Back to turn
114
+ </button>
115
+ ) : (
116
+ <div>
117
+ <div
118
+ style={{
119
+ fontSize: 10,
120
+ fontWeight: 700,
121
+ color: trackColor,
122
+ letterSpacing: "0.08em",
123
+ fontFamily: "ui-monospace, monospace",
124
+ }}
125
+ >
126
+ {trackLabel}
127
+ </div>
128
+ <div
129
+ style={{
130
+ fontSize: 11,
131
+ color: "var(--af-text-tertiary)",
132
+ fontFamily: "ui-monospace, monospace",
133
+ marginTop: 2,
134
+ }}
135
+ >
136
+ {startStr} → {endStr} · {durationStr}
137
+ </div>
138
+ </div>
139
+ )}
140
+ <button
141
+ onClick={onClose}
142
+ aria-label="Close"
143
+ style={{
144
+ background: "transparent",
145
+ border: "1px solid var(--af-border-subtle)",
146
+ color: "var(--af-text-secondary)",
147
+ width: 28,
148
+ height: 28,
149
+ borderRadius: 4,
150
+ cursor: "pointer",
151
+ fontSize: 14,
152
+ lineHeight: 1,
153
+ flexShrink: 0,
154
+ }}
155
+ >
156
+ ×
157
+ </button>
158
+ </div>
159
+
160
+ <div
161
+ style={{
162
+ flex: 1,
163
+ overflowY: "auto",
164
+ padding: "14px 18px",
165
+ }}
166
+ >
167
+ {view.page === "turn" ? (
168
+ <FullTurnCard
169
+ turn={turn.megaRow}
170
+ userRow={turn.userPrompt}
171
+ color={trackColor}
172
+ onStepClick={(row, index) => setView({ page: "step", row, index })}
173
+ />
174
+ ) : (
175
+ <StepDetail row={view.row} color={trackColor} />
176
+ )}
177
+ </div>
178
+ </div>
179
+ </>
180
+ );
181
+ }
182
+
183
+ /* ---------------------------------------------------------------- */
184
+ /* FullTurnCard — the HUMAN + AGENT + steps + conclusion layout */
185
+ /* used inside the team drawer. Visually mirrors the transcript's */
186
+ /* collapsed-turn row but as a standalone vertical card so it fits */
187
+ /* the drawer / modal context without the grid columns used by the */
188
+ /* transcript list. */
189
+ /* ---------------------------------------------------------------- */
190
+
191
+ function FullTurnCard({
192
+ turn,
193
+ userRow,
194
+ color,
195
+ onStepClick,
196
+ }: {
197
+ turn: TurnMegaRow;
198
+ userRow?: PresentationRow;
199
+ color: string;
200
+ onStepClick: (row: PresentationRow, index: number) => void;
201
+ }) {
202
+ const s = turn.summary;
203
+ const firstAgentIdx = s.firstAgentIndex ?? -1;
204
+ const finalAgentIdx = s.finalAgentIndex ?? -1;
205
+ const firstAgentRow = firstAgentIdx >= 0 ? turn.rows[firstAgentIdx] : undefined;
206
+ const finalAgentRow =
207
+ finalAgentIdx >= 0 && finalAgentIdx !== firstAgentIdx
208
+ ? turn.rows[finalAgentIdx]
209
+ : undefined;
210
+
211
+ // Middle rows exclude the first and conclusion — they're surfaced as
212
+ // their own sections above and below the steps list.
213
+ const middleRows = turn.rows.filter((_, i) => {
214
+ if (i === firstAgentIdx) return false;
215
+ if (finalAgentRow && i === finalAgentIdx) return false;
216
+ return true;
217
+ });
218
+
219
+ return (
220
+ <div style={{ display: "flex", flexDirection: "column", gap: 12 }}>
221
+ {userRow && userRow.kind === "user" && (() => {
222
+ const tm = userRow.event.teammateMessage;
223
+ const label = tm ? `From ${tm.teammateId}` : "Human";
224
+ // Drawer shows the full body — no truncation. For teammate
225
+ // messages the body is the complete instruction or protocol
226
+ // payload extracted from the <teammate-message> wrapper.
227
+ const text = tm
228
+ ? tm.body
229
+ : (userRow.displayPreview ?? userRow.event.preview ?? "");
230
+ return (
231
+ <ExpandableTextBlock
232
+ label={label}
233
+ color={tm ? "var(--af-text-tertiary)" : color}
234
+ text={text}
235
+ />
236
+ );
237
+ })()}
238
+
239
+ {firstAgentRow && firstAgentRow.kind === "agent" && (
240
+ <ExpandableAgentBlock
241
+ label="First message"
242
+ color={color}
243
+ row={firstAgentRow}
244
+ />
245
+ )}
246
+
247
+ <TurnStatsRow summary={s} rows={turn.rows} durationMs={turn.durationMs} />
248
+
249
+ {middleRows.length > 0 && (
250
+ <StepsSection
251
+ color={color}
252
+ rows={middleRows}
253
+ onStepClick={onStepClick}
254
+ />
255
+ )}
256
+
257
+ {finalAgentRow && finalAgentRow.kind === "agent" && (
258
+ <ExpandableAgentBlock
259
+ label="Conclusion"
260
+ color={color}
261
+ row={finalAgentRow}
262
+ arrow
263
+ />
264
+ )}
265
+ </div>
266
+ );
267
+ }
268
+
269
+ function ExpandableTextBlock({
270
+ label,
271
+ color,
272
+ text,
273
+ }: {
274
+ label: string;
275
+ color: string;
276
+ text: string;
277
+ }) {
278
+ const [expanded, setExpanded] = useState(false);
279
+ return (
280
+ <div
281
+ onClick={() => setExpanded((v) => !v)}
282
+ style={{
283
+ padding: "8px 12px",
284
+ borderRadius: 6,
285
+ background: expanded ? "var(--af-surface-hover)" : "transparent",
286
+ border: `1px solid ${expanded ? "var(--af-border-subtle)" : "transparent"}`,
287
+ cursor: "pointer",
288
+ transition: "all 0.12s",
289
+ }}
290
+ >
291
+ <div
292
+ style={{
293
+ display: "flex",
294
+ alignItems: "center",
295
+ gap: 6,
296
+ fontSize: 10,
297
+ color: "var(--af-text-tertiary)",
298
+ textTransform: "uppercase",
299
+ letterSpacing: "0.05em",
300
+ fontWeight: 600,
301
+ fontFamily: "ui-monospace, monospace",
302
+ }}
303
+ >
304
+ {expanded ? <ChevronDown size={11} /> : <ChevronRight size={11} />}
305
+ <span style={{ color }}>{label}</span>
306
+ </div>
307
+ {expanded ? (
308
+ <div className="sl-prose" style={{ fontSize: 13, marginTop: 6 }}>
309
+ <ReactMarkdown
310
+ remarkPlugins={[remarkGfm]}
311
+ components={{
312
+ a: (props) => (
313
+ <a
314
+ {...props}
315
+ target="_blank"
316
+ rel="noopener noreferrer"
317
+ style={{ color: "var(--af-accent)", textDecoration: "underline" }}
318
+ />
319
+ ),
320
+ }}
321
+ >
322
+ {text}
323
+ </ReactMarkdown>
324
+ </div>
325
+ ) : (
326
+ <div
327
+ style={{
328
+ marginTop: 4,
329
+ fontSize: 13,
330
+ lineHeight: 1.45,
331
+ color: "var(--af-text)",
332
+ display: "-webkit-box",
333
+ WebkitLineClamp: 2,
334
+ WebkitBoxOrient: "vertical",
335
+ overflow: "hidden",
336
+ }}
337
+ >
338
+ {text}
339
+ </div>
340
+ )}
341
+ </div>
342
+ );
343
+ }
344
+
345
+ function StepsSection({
346
+ color,
347
+ rows,
348
+ onStepClick,
349
+ }: {
350
+ color: string;
351
+ rows: PresentationRow[];
352
+ onStepClick: (row: PresentationRow, index: number) => void;
353
+ }) {
354
+ const [showAll, setShowAll] = useState(false);
355
+ const overflow = rows.length - MAX_INLINE_STEPS;
356
+ return (
357
+ <div>
358
+ <SectionLabel color={color}>STEPS</SectionLabel>
359
+ <div style={{ marginTop: 4 }}>
360
+ <TurnStepsList
361
+ rows={rows}
362
+ onStepClick={onStepClick}
363
+ showAll={showAll}
364
+ />
365
+ </div>
366
+ {overflow > 0 && !showAll && (
367
+ <button
368
+ onClick={() => setShowAll(true)}
369
+ style={{
370
+ display: "flex",
371
+ alignItems: "center",
372
+ justifyContent: "center",
373
+ gap: 6,
374
+ width: "100%",
375
+ padding: "6px 10px",
376
+ marginTop: 6,
377
+ background: "var(--af-surface-hover)",
378
+ border: "1px solid var(--af-border-subtle)",
379
+ borderRadius: 6,
380
+ fontSize: 11,
381
+ fontWeight: 500,
382
+ color: "var(--af-text-secondary)",
383
+ fontFamily: "inherit",
384
+ cursor: "pointer",
385
+ }}
386
+ >
387
+ <ChevronDown size={12} />
388
+ Show all {rows.length} steps
389
+ </button>
390
+ )}
391
+ </div>
392
+ );
393
+ }
394
+
395
+ function ExpandableAgentBlock({
396
+ label,
397
+ color,
398
+ row,
399
+ arrow,
400
+ }: {
401
+ label: string;
402
+ color: string;
403
+ row: Extract<PresentationRow, { kind: "agent" }>;
404
+ arrow?: boolean;
405
+ }) {
406
+ const [expanded, setExpanded] = useState(false);
407
+ return (
408
+ <div
409
+ onClick={() => setExpanded((v) => !v)}
410
+ style={{
411
+ padding: "8px 12px",
412
+ borderRadius: 6,
413
+ background: expanded ? "var(--af-surface-hover)" : "transparent",
414
+ border: `1px solid ${expanded ? "var(--af-border-subtle)" : "transparent"}`,
415
+ cursor: "pointer",
416
+ transition: "all 0.12s",
417
+ borderTop:
418
+ arrow && !expanded ? "1px dashed var(--af-border-subtle)" : undefined,
419
+ }}
420
+ >
421
+ <div
422
+ style={{
423
+ display: "flex",
424
+ alignItems: "center",
425
+ gap: 6,
426
+ fontSize: 10,
427
+ color: "var(--af-text-tertiary)",
428
+ textTransform: "uppercase",
429
+ letterSpacing: "0.05em",
430
+ fontWeight: 600,
431
+ fontFamily: "ui-monospace, monospace",
432
+ }}
433
+ >
434
+ {expanded ? <ChevronDown size={11} /> : <ChevronRight size={11} />}
435
+ <span style={{ color }}>{label}</span>
436
+ </div>
437
+ {expanded ? (
438
+ <div
439
+ className="sl-prose"
440
+ style={{
441
+ fontSize: 13,
442
+ marginTop: 6,
443
+ color: "var(--af-text)",
444
+ lineHeight: 1.5,
445
+ }}
446
+ >
447
+ {row.event.blocks
448
+ .filter((b): b is { type: "text"; text: string } => b.type === "text")
449
+ .map((b, i) => (
450
+ <ReactMarkdown
451
+ key={i}
452
+ remarkPlugins={[remarkGfm]}
453
+ components={{
454
+ a: (props) => (
455
+ <a
456
+ {...props}
457
+ target="_blank"
458
+ rel="noopener noreferrer"
459
+ style={{
460
+ color: "var(--af-accent)",
461
+ textDecoration: "underline",
462
+ }}
463
+ />
464
+ ),
465
+ }}
466
+ >
467
+ {b.text}
468
+ </ReactMarkdown>
469
+ ))}
470
+ </div>
471
+ ) : (
472
+ <div
473
+ style={{
474
+ marginTop: 4,
475
+ display: "flex",
476
+ gap: 6,
477
+ alignItems: "flex-start",
478
+ fontSize: 13,
479
+ lineHeight: 1.45,
480
+ color: "var(--af-text)",
481
+ }}
482
+ >
483
+ {arrow && (
484
+ <span
485
+ style={{
486
+ color: "var(--af-text-tertiary)",
487
+ marginTop: 1,
488
+ fontSize: 11,
489
+ }}
490
+ >
491
+
492
+ </span>
493
+ )}
494
+ <span
495
+ style={{
496
+ overflow: "hidden",
497
+ display: "-webkit-box",
498
+ WebkitLineClamp: 3,
499
+ WebkitBoxOrient: "vertical",
500
+ }}
501
+ >
502
+ {row.event.preview}
503
+ </span>
504
+ </div>
505
+ )}
506
+ </div>
507
+ );
508
+ }
509
+
510
+ function SectionLabel({
511
+ children,
512
+ color,
513
+ }: {
514
+ children: React.ReactNode;
515
+ color: string;
516
+ }) {
517
+ return (
518
+ <div
519
+ style={{
520
+ fontSize: 9,
521
+ color,
522
+ fontWeight: 700,
523
+ textTransform: "uppercase",
524
+ letterSpacing: "0.06em",
525
+ fontFamily: "ui-monospace, monospace",
526
+ }}
527
+ >
528
+ {children}
529
+ </div>
530
+ );
531
+ }
532
+
533
+ /** Activity summary line — Ghostty-style aggregation of tool use
534
+ * categories. A slimmer cousin of session-view's TurnStatsLine. */
535
+ function TurnStatsRow({
536
+ summary,
537
+ rows,
538
+ durationMs,
539
+ }: {
540
+ summary: TurnMegaRow["summary"];
541
+ rows: PresentationRow[];
542
+ durationMs?: number;
543
+ }) {
544
+ let editCount = 0;
545
+ let writeCount = 0;
546
+ let readCount = 0;
547
+ let bashCount = 0;
548
+ let searchCount = 0;
549
+ let agentCount = 0;
550
+ let otherToolCount = 0;
551
+
552
+ for (const r of rows) {
553
+ if (r.kind !== "tool-group") continue;
554
+ for (const ev of r.events) {
555
+ const name = ev.toolName ?? "";
556
+ switch (name) {
557
+ case "Edit":
558
+ editCount++;
559
+ break;
560
+ case "Write":
561
+ writeCount++;
562
+ break;
563
+ case "Read":
564
+ readCount++;
565
+ break;
566
+ case "Bash":
567
+ bashCount++;
568
+ break;
569
+ case "Grep":
570
+ case "Glob":
571
+ searchCount++;
572
+ break;
573
+ case "Agent":
574
+ agentCount++;
575
+ break;
576
+ default:
577
+ otherToolCount++;
578
+ break;
579
+ }
580
+ }
581
+ }
582
+
583
+ const phrases: string[] = [];
584
+ if (editCount + writeCount > 0) {
585
+ const n = editCount + writeCount;
586
+ phrases.push(`Edited ${n} file${n === 1 ? "" : "s"}`);
587
+ }
588
+ if (readCount > 0) phrases.push(`read ${readCount} file${readCount === 1 ? "" : "s"}`);
589
+ if (bashCount > 0) phrases.push(`${bashCount} command${bashCount === 1 ? "" : "s"}`);
590
+ if (searchCount > 0) phrases.push(`${searchCount} search${searchCount === 1 ? "" : "es"}`);
591
+ if (agentCount > 0)
592
+ phrases.push(`${agentCount} sub-agent${agentCount === 1 ? "" : "s"}`);
593
+ if (otherToolCount > 0 && phrases.length === 0)
594
+ phrases.push(`${otherToolCount} tool call${otherToolCount === 1 ? "" : "s"}`);
595
+ if (phrases.length === 0 && summary.agentMessages > 0) {
596
+ phrases.push(
597
+ `${summary.agentMessages} message${summary.agentMessages === 1 ? "" : "s"}`,
598
+ );
599
+ }
600
+
601
+ return (
602
+ <div
603
+ style={{
604
+ fontSize: 11,
605
+ color: "var(--af-text-tertiary)",
606
+ lineHeight: 1.5,
607
+ fontFamily: "ui-monospace, monospace",
608
+ }}
609
+ >
610
+ {phrases.join(", ")}
611
+ {summary.errors > 0 && (
612
+ <span style={{ color: "var(--af-danger)" }}>
613
+ {phrases.length > 0 ? ", " : ""}
614
+ {summary.errors} error{summary.errors === 1 ? "" : "s"}
615
+ </span>
616
+ )}
617
+ {durationMs !== undefined && durationMs > 0 && (
618
+ <>
619
+ <span style={{ opacity: 0.5, marginLeft: 6 }}>·</span>
620
+ <span style={{ marginLeft: 6 }}>{formatGap(durationMs)}</span>
621
+ </>
622
+ )}
623
+ </div>
624
+ );
625
+ }
626
+
627
+ /* ---------------------------------------------------------------- */
628
+ /* Step detail page — unchanged from the previous drawer iteration */
629
+ /* ---------------------------------------------------------------- */
630
+
631
+ function StepDetail({ row, color }: { row: PresentationRow; color: string }) {
632
+ if (row.kind === "user") {
633
+ return (
634
+ <DetailCard
635
+ label="HUMAN"
636
+ color={color}
637
+ body={row.displayPreview ?? row.event.preview ?? ""}
638
+ />
639
+ );
640
+ }
641
+ if (row.kind === "agent") {
642
+ const text = blocksText(row.event.blocks) || row.event.preview || "";
643
+ return <DetailCard label="AGENT" color={color} body={text} />;
644
+ }
645
+ if (row.kind === "tool-group") {
646
+ const toolLabel = row.toolNames
647
+ .slice(0, 4)
648
+ .map((t) => {
649
+ const short = shortenToolName(t.name);
650
+ return t.count > 1 ? `${short} ×${t.count}` : short;
651
+ })
652
+ .join(" · ");
653
+ const overflow = row.toolNames.length > 4 ? ` +${row.toolNames.length - 4}` : "";
654
+ return (
655
+ <div style={{ display: "flex", flexDirection: "column", gap: 10 }}>
656
+ <div
657
+ style={{
658
+ display: "flex",
659
+ alignItems: "center",
660
+ gap: 8,
661
+ padding: "8px 0",
662
+ borderBottom: "1px solid var(--af-border-subtle)",
663
+ }}
664
+ >
665
+ <span
666
+ style={{
667
+ fontSize: 11,
668
+ fontWeight: 600,
669
+ padding: "3px 10px",
670
+ borderRadius: 4,
671
+ background: "rgba(138, 133, 128, 0.16)",
672
+ color: "#44403C",
673
+ }}
674
+ >
675
+ Tool
676
+ </span>
677
+ <span
678
+ style={{
679
+ fontSize: 12,
680
+ color: "var(--af-text-secondary)",
681
+ fontWeight: 500,
682
+ }}
683
+ >
684
+ Tool use · {toolLabel}{overflow}
685
+ </span>
686
+ </div>
687
+
688
+ {row.events.map((ev, i) => {
689
+ const toolUse = (ev.blocks ?? []).find(
690
+ (b) => b.type === "tool_use",
691
+ ) as { name?: string; input?: ToolUseInput } | undefined;
692
+ if (!toolUse) return null;
693
+ const name = toolUse.name ?? "?";
694
+ const input = toolUse.input ?? {};
695
+ return (
696
+ <div key={i}>
697
+ <div
698
+ style={{
699
+ fontSize: 10,
700
+ color: "var(--af-text-tertiary)",
701
+ fontFamily: "ui-monospace, monospace",
702
+ marginBottom: 4,
703
+ }}
704
+ >
705
+ #{i + 1}
706
+ </div>
707
+ <ToolUseCard name={name} input={input} />
708
+ </div>
709
+ );
710
+ })}
711
+ </div>
712
+ );
713
+ }
714
+ if (row.kind === "interrupt") {
715
+ return (
716
+ <DetailCard
717
+ label="INTERRUPT"
718
+ color="#f85149"
719
+ body={row.event.preview ?? "[interrupted]"}
720
+ />
721
+ );
722
+ }
723
+ if (row.kind === "error") {
724
+ return <DetailCard label="ERROR" color="#f85149" body={row.message} />;
725
+ }
726
+ if (row.kind === "model") {
727
+ return (
728
+ <DetailCard
729
+ label="MODEL CHANGE"
730
+ color={color}
731
+ body={row.event.preview ?? ""}
732
+ />
733
+ );
734
+ }
735
+ if (row.kind === "task-notification") {
736
+ return (
737
+ <DetailCard
738
+ label={`TASK ${row.status.toUpperCase()}`}
739
+ color={color}
740
+ body={row.summary}
741
+ />
742
+ );
743
+ }
744
+ return null;
745
+ }
746
+
747
+ function DetailCard({
748
+ label,
749
+ color,
750
+ body,
751
+ }: {
752
+ label: string;
753
+ color: string;
754
+ body: string;
755
+ }) {
756
+ return (
757
+ <div
758
+ style={{
759
+ padding: "10px 12px",
760
+ background: "var(--af-surface-hover)",
761
+ borderLeft: `3px solid ${color}`,
762
+ borderRadius: 4,
763
+ marginBottom: 6,
764
+ }}
765
+ >
766
+ <div
767
+ style={{
768
+ fontSize: 9,
769
+ color,
770
+ fontWeight: 600,
771
+ textTransform: "uppercase",
772
+ letterSpacing: "0.04em",
773
+ marginBottom: 4,
774
+ fontFamily: "ui-monospace, monospace",
775
+ }}
776
+ >
777
+ {label}
778
+ </div>
779
+ {body && (
780
+ <div
781
+ style={{
782
+ fontSize: 12,
783
+ color: "var(--af-text)",
784
+ lineHeight: 1.5,
785
+ whiteSpace: "pre-wrap",
786
+ wordBreak: "break-word",
787
+ }}
788
+ >
789
+ {body}
790
+ </div>
791
+ )}
792
+ </div>
793
+ );
794
+ }
795
+
796
+
797
+ function blocksText(blocks: ContentBlock[] | undefined): string {
798
+ if (!blocks) return "";
799
+ const parts: string[] = [];
800
+ for (const b of blocks) {
801
+ if (b.type === "text" && typeof b.text === "string") parts.push(b.text);
802
+ }
803
+ return parts.join("\n\n");
804
+ }
805
+