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,682 @@
1
+ "use client";
2
+
3
+ import Link from "next/link";
4
+ import * as React from "react";
5
+ import { useEffect, useState } from "react";
6
+ import { ChevronLeft, ChevronRight } from "lucide-react";
7
+ import { formatGap } from "@/lib/format";
8
+ import type { TimelineData, TeamTrack, TeamTurn, XAnchor, MinimapIdleBand } from "./adapter";
9
+ import { xOfMs, msOfXFrac } from "./adapter";
10
+ import { formatTeammatePreview } from "./format";
11
+
12
+ const LANE_HEIGHT = 24;
13
+ const LANE_GAP = 2;
14
+ const LABEL_WIDTH = 100;
15
+ export const DEFAULT_VISIBLE_LANES = 4;
16
+
17
+ type Props = {
18
+ data: TimelineData;
19
+ playheadMs: number | null;
20
+ onSeek: (tsMs: number, trackId?: string) => void;
21
+ expanded: boolean;
22
+ onToggleExpanded: () => void;
23
+ /** Member track ids currently visible in the body table (mid-point inside
24
+ * the horizontal viewport). When the minimap is collapsed, the lane set
25
+ * mirrors this — lead first, then whichever members are currently in the
26
+ * table's viewport. Falls back to "first N members by start time" when
27
+ * empty, so the initial render is sane. */
28
+ visibleTrackIds: string[];
29
+ };
30
+
31
+ type HoverState = {
32
+ turn: TeamTurn;
33
+ track: TeamTrack;
34
+ clientX: number;
35
+ clientY: number;
36
+ };
37
+
38
+ export function TeamMinimap({
39
+ data,
40
+ playheadMs,
41
+ onSeek,
42
+ expanded,
43
+ onToggleExpanded,
44
+ visibleTrackIds,
45
+ }: Props) {
46
+ const [hover, setHover] = useState<HoverState | null>(null);
47
+ const hasOverflow = data.tracks.length > DEFAULT_VISIBLE_LANES;
48
+
49
+ // Day-page mode: when the team span exceeds 24h, the adapter pre-computes
50
+ // per-day x-scales and we render one page at a time. The minimap's
51
+ // rangeStart/rangeEnd, xAnchors, and idle bands all come from the active
52
+ // page instead of the global ones.
53
+ const dayPages = data.dayPages ?? null;
54
+ const [pageIndex, setPageIndex] = useState(0);
55
+ // Follow the playhead: if the table scrolls into a different day, advance
56
+ // the minimap page to match so the user's focus stays in view.
57
+ useEffect(() => {
58
+ if (!dayPages || playheadMs == null) return;
59
+ const match = dayPages.findIndex(
60
+ (p) => playheadMs >= p.startMs && playheadMs <= p.endMs,
61
+ );
62
+ if (match !== -1 && match !== pageIndex) setPageIndex(match);
63
+ }, [dayPages, playheadMs, pageIndex]);
64
+ // Clamp pageIndex in case the dataset shrinks between renders.
65
+ const safePageIndex = dayPages
66
+ ? Math.min(Math.max(0, pageIndex), dayPages.length - 1)
67
+ : 0;
68
+ const activePage = dayPages ? dayPages[safePageIndex] : null;
69
+
70
+ const activeXAnchors: XAnchor[] = activePage ? activePage.xAnchors : data.xAnchors;
71
+ const activeIdleBands: MinimapIdleBand[] = activePage
72
+ ? activePage.minimapIdleBands
73
+ : data.minimapIdleBands;
74
+ const rangeStartMs = activePage ? activePage.startMs : data.firstEventMs;
75
+ const rangeEndMs = activePage ? activePage.endMs : data.lastEventMs;
76
+
77
+ // Compute which tracks are "active" (shown at full height). The minimap
78
+ // always renders every track so CSS transitions can animate lanes in and
79
+ // out when the active set changes; non-active lanes collapse to height 0
80
+ // via transition instead of unmounting.
81
+ // - expanded → every track active
82
+ // - collapsed + table has reported visible members → lead + those
83
+ // - collapsed + no report yet → lead + first N-1 members (initial)
84
+ let activeIds: Set<string>;
85
+ if (expanded || !hasOverflow) {
86
+ activeIds = new Set(data.tracks.map((t) => t.id));
87
+ } else {
88
+ const lead = data.tracks[0];
89
+ const members = data.tracks.slice(1);
90
+ const pickMembers =
91
+ visibleTrackIds.length > 0
92
+ ? members
93
+ .filter((t) => visibleTrackIds.includes(t.id))
94
+ .slice(0, DEFAULT_VISIBLE_LANES - 1)
95
+ .map((t) => t.id)
96
+ : members.slice(0, DEFAULT_VISIBLE_LANES - 1).map((t) => t.id);
97
+ activeIds = new Set(pickMembers);
98
+ if (lead) activeIds.add(lead.id);
99
+ }
100
+ const hiddenCount = data.tracks.length - activeIds.size;
101
+
102
+ // Event-anchored x-scale — active intervals stay proportional with a floor,
103
+ // all-idle intervals (including multi-day overnight gaps) collapse into
104
+ // compact bands. Shared across lanes so cross-agent alignment holds.
105
+ // In day-page mode, xOf uses the current page's anchors so each day
106
+ // fills the minimap width instead of sharing a cramped global strip.
107
+ const xOf = (ms: number) => xOfMs(activeXAnchors, ms);
108
+
109
+ const onLaneClick = (
110
+ track: TeamTrack,
111
+ e: React.MouseEvent<HTMLDivElement>,
112
+ ) => {
113
+ const rect = e.currentTarget.getBoundingClientRect();
114
+ const x = Math.max(0, e.clientX - rect.left);
115
+ const frac = Math.min(1, x / Math.max(1, rect.width));
116
+ onSeek(msOfXFrac(activeXAnchors, frac), track.id);
117
+ };
118
+
119
+ // Playhead only renders when it's inside the current page's window.
120
+ const playheadFrac =
121
+ playheadMs != null && playheadMs >= rangeStartMs && playheadMs <= rangeEndMs
122
+ ? Math.max(0, Math.min(1, xOf(playheadMs)))
123
+ : null;
124
+
125
+ // Turn bars are clipped to the active page — bars whose entire range is
126
+ // outside the current day window are skipped, bars that straddle the
127
+ // boundary get clamped to the window's edges.
128
+ const clipTurn = (turn: TeamTurn) => {
129
+ if (turn.endMs < rangeStartMs || turn.startMs > rangeEndMs) return null;
130
+ return {
131
+ id: turn.id,
132
+ startMs: Math.max(turn.startMs, rangeStartMs),
133
+ endMs: Math.min(turn.endMs, rangeEndMs),
134
+ original: turn,
135
+ };
136
+ };
137
+
138
+ return (
139
+ <div
140
+ style={{
141
+ position: "sticky",
142
+ top: 0,
143
+ zIndex: 10,
144
+ background: "var(--af-surface-elevated)",
145
+ border: "1px solid var(--af-border-subtle)",
146
+ borderRadius: 6,
147
+ padding: 8,
148
+ marginBottom: 8,
149
+ }}
150
+ >
151
+ <div
152
+ style={{
153
+ display: "flex",
154
+ justifyContent: "space-between",
155
+ alignItems: "center",
156
+ fontSize: 9,
157
+ color: "var(--af-text-tertiary)",
158
+ fontFamily: "ui-monospace, monospace",
159
+ marginBottom: 6,
160
+ paddingLeft: LABEL_WIDTH,
161
+ gap: 8,
162
+ }}
163
+ >
164
+ <span>{formatEdge(rangeStartMs, data.multiDay)}</span>
165
+ {dayPages && dayPages.length > 1 && (
166
+ <div
167
+ style={{
168
+ display: "flex",
169
+ alignItems: "center",
170
+ gap: 4,
171
+ fontSize: 10,
172
+ fontWeight: 600,
173
+ color: "var(--af-text-secondary)",
174
+ }}
175
+ >
176
+ <button
177
+ onClick={() => setPageIndex((i) => Math.max(0, i - 1))}
178
+ disabled={safePageIndex === 0}
179
+ aria-label="Previous day"
180
+ style={pageBtnStyle(safePageIndex === 0)}
181
+ >
182
+ <ChevronLeft size={11} />
183
+ </button>
184
+ <span
185
+ style={{
186
+ minWidth: 64,
187
+ textAlign: "center",
188
+ fontFamily: "ui-monospace, monospace",
189
+ }}
190
+ >
191
+ {activePage?.label ?? ""}{" "}
192
+ <span style={{ opacity: 0.55 }}>
193
+ ({safePageIndex + 1}/{dayPages.length})
194
+ </span>
195
+ </span>
196
+ <button
197
+ onClick={() =>
198
+ setPageIndex((i) => Math.min(dayPages.length - 1, i + 1))
199
+ }
200
+ disabled={safePageIndex === dayPages.length - 1}
201
+ aria-label="Next day"
202
+ style={pageBtnStyle(safePageIndex === dayPages.length - 1)}
203
+ >
204
+ <ChevronRight size={11} />
205
+ </button>
206
+ </div>
207
+ )}
208
+ <span>{formatEdge(rangeEndMs, data.multiDay)}</span>
209
+ </div>
210
+
211
+ <div
212
+ style={{
213
+ position: "relative",
214
+ display: "flex",
215
+ flexDirection: "column",
216
+ }}
217
+ >
218
+ {/* Hatched idle bands spanning all lanes. Positioned as an overlay
219
+ so they line up exactly with the lane strips' x axis (both use
220
+ the shared xOfMs scale). Uses the active page's idle bands in
221
+ day-page mode. */}
222
+ {activeIdleBands.map((band, i) => (
223
+ <IdleBandOverlay key={i} band={band} />
224
+ ))}
225
+ {data.tracks.map((t) => {
226
+ const isActive = activeIds.has(t.id);
227
+ return (
228
+ <div
229
+ key={t.id}
230
+ style={{
231
+ display: "flex",
232
+ alignItems: "center",
233
+ gap: 6,
234
+ // The collapsed lanes animate to zero height with opacity fade
235
+ // so the minimap visually slides when its active set changes
236
+ // (e.g. in response to the table's horizontal scroll or the
237
+ // "Show all" toggle).
238
+ maxHeight: isActive ? LANE_HEIGHT : 0,
239
+ opacity: isActive ? 1 : 0,
240
+ marginBottom: isActive ? LANE_GAP : 0,
241
+ overflow: "hidden",
242
+ transition:
243
+ "max-height 280ms ease, opacity 280ms ease, margin-bottom 280ms ease",
244
+ pointerEvents: isActive ? "auto" : "none",
245
+ }}
246
+ >
247
+ <Link
248
+ href={`/sessions/${t.id}`}
249
+ style={{
250
+ width: LABEL_WIDTH - 6,
251
+ fontSize: 10,
252
+ fontFamily: "ui-monospace, monospace",
253
+ color: t.color,
254
+ fontWeight: t.isLead ? 700 : 500,
255
+ overflow: "hidden",
256
+ textOverflow: "ellipsis",
257
+ whiteSpace: "nowrap",
258
+ textDecoration: "none",
259
+ }}
260
+ title={`${t.label} — open session`}
261
+ >
262
+ {t.isLead ? "LEAD" : t.label}
263
+ </Link>
264
+ <div
265
+ style={{
266
+ flex: 1,
267
+ position: "relative",
268
+ height: LANE_HEIGHT,
269
+ background: "var(--af-surface-hover)",
270
+ borderRadius: 3,
271
+ overflow: "hidden",
272
+ cursor: "pointer",
273
+ padding: "3px 2px",
274
+ boxSizing: "border-box",
275
+ }}
276
+ onClick={(e) => onLaneClick(t, e)}
277
+ >
278
+ {t.turns.map((turn) => {
279
+ const clipped = clipTurn(turn);
280
+ if (!clipped) return null;
281
+ const leftFrac = xOf(clipped.startMs);
282
+ const rightFrac = xOf(clipped.endMs);
283
+ const left = leftFrac * 100;
284
+ const width = Math.max(0.4, (rightFrac - leftFrac) * 100);
285
+ return (
286
+ <div
287
+ key={turn.id}
288
+ style={{
289
+ position: "absolute",
290
+ left: `calc(${left}% + 1px)`,
291
+ top: 3,
292
+ width: `calc(${width}% - 2px)`,
293
+ minWidth: 4,
294
+ height: LANE_HEIGHT - 10,
295
+ background: t.color,
296
+ opacity: 0.88,
297
+ borderRadius: 3,
298
+ border: `1px solid ${t.color}`,
299
+ boxSizing: "border-box",
300
+ }}
301
+ onMouseEnter={(e) =>
302
+ setHover({
303
+ turn: clipped.original,
304
+ track: t,
305
+ clientX: e.clientX,
306
+ clientY: e.clientY,
307
+ })
308
+ }
309
+ onMouseMove={(e) =>
310
+ setHover((h) =>
311
+ h && h.turn.id === turn.id
312
+ ? { ...h, clientX: e.clientX, clientY: e.clientY }
313
+ : h,
314
+ )
315
+ }
316
+ onMouseLeave={() => setHover(null)}
317
+ />
318
+ );
319
+ })}
320
+ {t.subagents.map((sa, i) => {
321
+ if (sa.startMs == null) return null;
322
+ const saEnd = sa.startMs + (sa.durationMs ?? 0);
323
+ if (saEnd < rangeStartMs || sa.startMs > rangeEndMs) return null;
324
+ const clampedStart = Math.max(sa.startMs, rangeStartMs);
325
+ const clampedEnd = Math.min(saEnd, rangeEndMs);
326
+ const startFrac = xOf(clampedStart);
327
+ const endFrac = xOf(clampedEnd);
328
+ const left = startFrac * 100;
329
+ const w = Math.max(0.2, (endFrac - startFrac) * 100) || 0.4;
330
+ return (
331
+ <div
332
+ key={i}
333
+ style={{
334
+ position: "absolute",
335
+ left: `${left}%`,
336
+ bottom: 2,
337
+ width: `${w}%`,
338
+ height: 3,
339
+ background: t.color,
340
+ opacity: 0.5,
341
+ borderRadius: 1,
342
+ }}
343
+ title={`subagent: ${sa.agentType}`}
344
+ />
345
+ );
346
+ })}
347
+ </div>
348
+ </div>
349
+ );
350
+ })}
351
+ </div>
352
+
353
+ {hasOverflow && (
354
+ <div
355
+ style={{
356
+ marginTop: 6,
357
+ paddingLeft: LABEL_WIDTH + 6,
358
+ display: "flex",
359
+ }}
360
+ >
361
+ <button
362
+ onClick={onToggleExpanded}
363
+ style={{
364
+ background: "var(--af-surface-hover)",
365
+ border: "1px solid var(--af-border-subtle)",
366
+ color: "var(--af-text-secondary)",
367
+ fontSize: 10,
368
+ fontFamily: "ui-monospace, monospace",
369
+ padding: "2px 8px",
370
+ borderRadius: 10,
371
+ cursor: "pointer",
372
+ lineHeight: 1.4,
373
+ }}
374
+ >
375
+ {expanded
376
+ ? "Show fewer"
377
+ : `+ ${hiddenCount} more agent${hiddenCount === 1 ? "" : "s"} — show all`}
378
+ </button>
379
+ </div>
380
+ )}
381
+
382
+ {/* Playhead lives in an overlay that starts AFTER the label column so
383
+ the % coordinate matches the strips exactly. */}
384
+ {playheadFrac != null && (
385
+ <div
386
+ style={{
387
+ position: "absolute",
388
+ top: 24,
389
+ bottom: 8,
390
+ left: LABEL_WIDTH + 6,
391
+ right: 8,
392
+ pointerEvents: "none",
393
+ }}
394
+ >
395
+ <div
396
+ style={{
397
+ position: "absolute",
398
+ top: 0,
399
+ bottom: 0,
400
+ left: `${playheadFrac * 100}%`,
401
+ width: 1,
402
+ background: "var(--af-text)",
403
+ opacity: 0.7,
404
+ }}
405
+ />
406
+ </div>
407
+ )}
408
+
409
+ {hover && <HoverCard hover={hover} multiDay={data.multiDay} />}
410
+ </div>
411
+ );
412
+ }
413
+
414
+ function HoverCard({
415
+ hover,
416
+ multiDay,
417
+ }: {
418
+ hover: HoverState;
419
+ multiDay: boolean;
420
+ }) {
421
+ const { turn, track } = hover;
422
+ const summary = turn.megaRow.summary;
423
+ const tmMsg =
424
+ turn.userPrompt && "event" in turn.userPrompt
425
+ ? turn.userPrompt.event.teammateMessage
426
+ : undefined;
427
+ const userLabel = tmMsg ? `FROM ${tmMsg.teammateId}` : "HUMAN";
428
+ const userText = tmMsg
429
+ ? formatTeammatePreview(tmMsg)
430
+ : turn.userPrompt && turn.userPrompt.kind === "user"
431
+ ? (turn.userPrompt.displayPreview ?? turn.userPrompt.event.preview ?? "")
432
+ : "";
433
+ const firstAgent = summary.firstAgentPreview ?? "";
434
+ const finalAgent = summary.finalAgentPreview ?? "";
435
+ const showFinal = finalAgent && finalAgent !== firstAgent;
436
+ const tools = summary.toolNames.slice(0, 4);
437
+
438
+ // Position card near the cursor, biased above-and-right to avoid covering
439
+ // the bar itself. Clamp to viewport via simple offsets.
440
+ const left = Math.min(window.innerWidth - 360, hover.clientX + 12);
441
+ const top = Math.max(8, hover.clientY - 12);
442
+
443
+ return (
444
+ <div
445
+ style={{
446
+ position: "fixed",
447
+ left,
448
+ top,
449
+ width: 340,
450
+ background: "var(--af-surface)",
451
+ border: "1px solid var(--af-border-subtle)",
452
+ borderLeft: `3px solid ${track.color}`,
453
+ borderRadius: 4,
454
+ padding: "8px 10px",
455
+ fontSize: 10,
456
+ lineHeight: 1.4,
457
+ boxShadow: "0 8px 24px rgba(0,0,0,0.25)",
458
+ zIndex: 100,
459
+ pointerEvents: "none",
460
+ }}
461
+ >
462
+ <div
463
+ style={{
464
+ display: "flex",
465
+ justifyContent: "space-between",
466
+ fontSize: 9,
467
+ fontFamily: "ui-monospace, monospace",
468
+ color: track.color,
469
+ fontWeight: 600,
470
+ textTransform: "uppercase",
471
+ letterSpacing: "0.04em",
472
+ marginBottom: 4,
473
+ }}
474
+ >
475
+ <span>{track.isLead ? "LEAD" : track.label}</span>
476
+ <span style={{ color: "var(--af-text-tertiary)", fontWeight: 500 }}>
477
+ {formatEdge(turn.startMs, multiDay)} → {formatEdge(turn.endMs, multiDay)} · {formatGap(turn.durationMs)}
478
+ </span>
479
+ </div>
480
+ <div
481
+ style={{
482
+ fontSize: 9,
483
+ color: "var(--af-text-tertiary)",
484
+ marginBottom: 6,
485
+ }}
486
+ >
487
+ {summary.agentMessages} msg · {summary.toolCalls} tools
488
+ {summary.errors > 0 ? ` · ${summary.errors} err` : ""}
489
+ </div>
490
+ {userText && (
491
+ <div style={{ marginBottom: 5 }}>
492
+ <div
493
+ style={{
494
+ fontSize: 8,
495
+ color: tmMsg ? "var(--af-text-tertiary)" : track.color,
496
+ fontWeight: 600,
497
+ textTransform: "uppercase",
498
+ letterSpacing: "0.04em",
499
+ marginBottom: 1,
500
+ fontStyle: tmMsg ? "italic" : undefined,
501
+ }}
502
+ >
503
+ {userLabel}
504
+ </div>
505
+ <div
506
+ style={{
507
+ color: tmMsg ? "var(--af-text-tertiary)" : "var(--af-text)",
508
+ display: "-webkit-box",
509
+ WebkitLineClamp: 2,
510
+ WebkitBoxOrient: "vertical",
511
+ overflow: "hidden",
512
+ }}
513
+ >
514
+ {userText}
515
+ </div>
516
+ </div>
517
+ )}
518
+ {firstAgent && (
519
+ <div style={{ marginBottom: 5 }}>
520
+ <div
521
+ style={{
522
+ fontSize: 8,
523
+ color: track.color,
524
+ fontWeight: 600,
525
+ textTransform: "uppercase",
526
+ letterSpacing: "0.04em",
527
+ marginBottom: 1,
528
+ }}
529
+ >
530
+ AGENT
531
+ </div>
532
+ <div
533
+ style={{
534
+ color: "var(--af-text)",
535
+ display: "-webkit-box",
536
+ WebkitLineClamp: 3,
537
+ WebkitBoxOrient: "vertical",
538
+ overflow: "hidden",
539
+ }}
540
+ >
541
+ {firstAgent}
542
+ </div>
543
+ </div>
544
+ )}
545
+ {tools.length > 0 && (
546
+ <div
547
+ style={{
548
+ display: "flex",
549
+ gap: 3,
550
+ flexWrap: "wrap",
551
+ marginBottom: showFinal ? 5 : 0,
552
+ }}
553
+ >
554
+ {tools.map((t) => (
555
+ <span
556
+ key={t.name}
557
+ style={{
558
+ fontSize: 8,
559
+ padding: "1px 5px",
560
+ background: "var(--af-surface-hover)",
561
+ borderRadius: 2,
562
+ color: "var(--af-text-tertiary)",
563
+ }}
564
+ >
565
+ {t.name}
566
+ {t.count > 1 ? ` ×${t.count}` : ""}
567
+ </span>
568
+ ))}
569
+ </div>
570
+ )}
571
+ {showFinal && (
572
+ <div>
573
+ <div
574
+ style={{
575
+ fontSize: 8,
576
+ color: track.color,
577
+ fontWeight: 600,
578
+ textTransform: "uppercase",
579
+ letterSpacing: "0.04em",
580
+ marginBottom: 1,
581
+ }}
582
+ >
583
+ RESULT
584
+ </div>
585
+ <div
586
+ style={{
587
+ color: "var(--af-text)",
588
+ display: "-webkit-box",
589
+ WebkitLineClamp: 2,
590
+ WebkitBoxOrient: "vertical",
591
+ overflow: "hidden",
592
+ }}
593
+ >
594
+ {finalAgent}
595
+ </div>
596
+ </div>
597
+ )}
598
+ </div>
599
+ );
600
+ }
601
+
602
+ function pageBtnStyle(disabled: boolean): React.CSSProperties {
603
+ return {
604
+ background: "transparent",
605
+ border: "1px solid var(--af-border-subtle)",
606
+ color: disabled ? "var(--af-text-tertiary)" : "var(--af-text-secondary)",
607
+ width: 18,
608
+ height: 18,
609
+ borderRadius: 3,
610
+ display: "flex",
611
+ alignItems: "center",
612
+ justifyContent: "center",
613
+ cursor: disabled ? "default" : "pointer",
614
+ opacity: disabled ? 0.4 : 1,
615
+ padding: 0,
616
+ };
617
+ }
618
+
619
+ function formatEdge(ms: number, multiDay: boolean): string {
620
+ const d = new Date(ms);
621
+ if (!multiDay) {
622
+ return d.toLocaleTimeString([], {
623
+ hour: "2-digit",
624
+ minute: "2-digit",
625
+ });
626
+ }
627
+ const date = d.toLocaleDateString([], { month: "short", day: "numeric" });
628
+ const time = d.toLocaleTimeString([], {
629
+ hour: "2-digit",
630
+ minute: "2-digit",
631
+ });
632
+ return `${date} ${time}`;
633
+ }
634
+
635
+ function IdleBandOverlay({ band }: { band: MinimapIdleBand }) {
636
+ const [hovered, setHovered] = useState(false);
637
+ const left = band.xFracStart * 100;
638
+ const width = Math.max(0.5, (band.xFracEnd - band.xFracStart) * 100);
639
+ return (
640
+ <div
641
+ onMouseEnter={() => setHovered(true)}
642
+ onMouseLeave={() => setHovered(false)}
643
+ style={{
644
+ position: "absolute",
645
+ top: 0,
646
+ bottom: 0,
647
+ left: `calc(${LABEL_WIDTH}px + ${left}% * (100% - ${LABEL_WIDTH}px) / 100%)`,
648
+ width: `calc(${width}% * (100% - ${LABEL_WIDTH}px) / 100%)`,
649
+ background:
650
+ "repeating-linear-gradient(135deg, transparent 0, transparent 4px, var(--af-border-subtle) 4px, var(--af-border-subtle) 6px)",
651
+ zIndex: 1,
652
+ cursor: "help",
653
+ }}
654
+ >
655
+ {hovered && (
656
+ <div
657
+ style={{
658
+ position: "absolute",
659
+ left: "50%",
660
+ bottom: "calc(100% + 6px)",
661
+ transform: "translateX(-50%)",
662
+ background: "#0F172A",
663
+ color: "#F1F5F9",
664
+ borderRadius: 8,
665
+ padding: "6px 10px",
666
+ fontSize: 11,
667
+ pointerEvents: "none",
668
+ boxShadow: "0 4px 16px rgba(15,23,42,0.22)",
669
+ zIndex: 20,
670
+ whiteSpace: "nowrap",
671
+ }}
672
+ >
673
+ <div style={{ fontWeight: 600, marginBottom: 1 }}>Idle</div>
674
+ <div style={{ opacity: 0.78, fontFamily: "ui-monospace, monospace", fontSize: 10 }}>
675
+ {band.label}
676
+ </div>
677
+ </div>
678
+ )}
679
+ </div>
680
+ );
681
+ }
682
+