fleetlens 0.12.3 → 0.13.1

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 (228) hide show
  1. package/app/apps/web/.next/BUILD_ID +1 -1
  2. package/app/apps/web/.next/build-manifest.json +8 -8
  3. package/app/apps/web/.next/prerender-manifest.json +3 -3
  4. package/app/apps/web/.next/server/app/_global-error/page/build-manifest.json +5 -5
  5. package/app/apps/web/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  6. package/app/apps/web/.next/server/app/_global-error.html +1 -1
  7. package/app/apps/web/.next/server/app/_global-error.rsc +7 -7
  8. package/app/apps/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +2 -2
  9. package/app/apps/web/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
  10. package/app/apps/web/.next/server/app/_global-error.segments/_head.segment.rsc +3 -3
  11. package/app/apps/web/.next/server/app/_global-error.segments/_index.segment.rsc +3 -3
  12. package/app/apps/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  13. package/app/apps/web/.next/server/app/_not-found/page/build-manifest.json +5 -5
  14. package/app/apps/web/.next/server/app/_not-found/page.js +1 -1
  15. package/app/apps/web/.next/server/app/_not-found/page.js.nft.json +1 -1
  16. package/app/apps/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  17. package/app/apps/web/.next/server/app/api/ask/route.js.nft.json +1 -1
  18. package/app/apps/web/.next/server/app/api/digest/day/[date]/route.js +1 -1
  19. package/app/apps/web/.next/server/app/api/digest/day/[date]/route.js.nft.json +1 -1
  20. package/app/apps/web/.next/server/app/api/digest/month/[yearMonth]/route.js +1 -1
  21. package/app/apps/web/.next/server/app/api/digest/month/[yearMonth]/route.js.nft.json +1 -1
  22. package/app/apps/web/.next/server/app/api/digest/month-index/route.js.nft.json +1 -1
  23. package/app/apps/web/.next/server/app/api/digest/week/[startDate]/route.js +1 -1
  24. package/app/apps/web/.next/server/app/api/digest/week/[startDate]/route.js.nft.json +1 -1
  25. package/app/apps/web/.next/server/app/api/digest/week-index/route.js.nft.json +1 -1
  26. package/app/apps/web/.next/server/app/api/events/route.js +2 -1
  27. package/app/apps/web/.next/server/app/api/events/route.js.nft.json +1 -1
  28. package/app/apps/web/.next/server/app/api/jobs/route.js.nft.json +1 -1
  29. package/app/apps/web/.next/server/app/api/pdf/[key]/route.js.nft.json +1 -1
  30. package/app/apps/web/.next/server/app/api/runs/[runId]/route.js.nft.json +1 -1
  31. package/app/apps/web/.next/server/app/api/runs/[runId]/stream/route.js.nft.json +1 -1
  32. package/app/apps/web/.next/server/app/api/runs/route.js.nft.json +1 -1
  33. package/app/apps/web/.next/server/app/api/settings/route.js.nft.json +1 -1
  34. package/app/apps/web/.next/server/app/api/team-config/route.js.nft.json +1 -1
  35. package/app/apps/web/.next/server/app/api/workflow-agent/route.js.nft.json +1 -1
  36. package/app/apps/web/.next/server/app/changelog/page/build-manifest.json +5 -5
  37. package/app/apps/web/.next/server/app/changelog/page.js +1 -1
  38. package/app/apps/web/.next/server/app/changelog/page.js.nft.json +1 -1
  39. package/app/apps/web/.next/server/app/changelog/page_client-reference-manifest.js +1 -1
  40. package/app/apps/web/.next/server/app/day/[date]/page/build-manifest.json +5 -5
  41. package/app/apps/web/.next/server/app/day/[date]/page.js +1 -1
  42. package/app/apps/web/.next/server/app/day/[date]/page.js.nft.json +1 -1
  43. package/app/apps/web/.next/server/app/day/[date]/page_client-reference-manifest.js +1 -1
  44. package/app/apps/web/.next/server/app/day/page/build-manifest.json +5 -5
  45. package/app/apps/web/.next/server/app/day/page.js +1 -1
  46. package/app/apps/web/.next/server/app/day/page.js.nft.json +1 -1
  47. package/app/apps/web/.next/server/app/day/page_client-reference-manifest.js +1 -1
  48. package/app/apps/web/.next/server/app/digest/[date]/page/build-manifest.json +5 -5
  49. package/app/apps/web/.next/server/app/digest/[date]/page.js +1 -1
  50. package/app/apps/web/.next/server/app/digest/[date]/page.js.nft.json +1 -1
  51. package/app/apps/web/.next/server/app/digest/[date]/page_client-reference-manifest.js +1 -1
  52. package/app/apps/web/.next/server/app/digest/page/build-manifest.json +5 -5
  53. package/app/apps/web/.next/server/app/digest/page.js +1 -1
  54. package/app/apps/web/.next/server/app/digest/page.js.nft.json +1 -1
  55. package/app/apps/web/.next/server/app/digest/page_client-reference-manifest.js +1 -1
  56. package/app/apps/web/.next/server/app/insights/[key]/page/build-manifest.json +5 -5
  57. package/app/apps/web/.next/server/app/insights/[key]/page.js +1 -1
  58. package/app/apps/web/.next/server/app/insights/[key]/page.js.nft.json +1 -1
  59. package/app/apps/web/.next/server/app/insights/[key]/page_client-reference-manifest.js +1 -1
  60. package/app/apps/web/.next/server/app/insights/page/build-manifest.json +5 -5
  61. package/app/apps/web/.next/server/app/insights/page.js +3 -2
  62. package/app/apps/web/.next/server/app/insights/page.js.nft.json +1 -1
  63. package/app/apps/web/.next/server/app/insights/page_client-reference-manifest.js +1 -1
  64. package/app/apps/web/.next/server/app/page/build-manifest.json +5 -5
  65. package/app/apps/web/.next/server/app/page.js +1 -1
  66. package/app/apps/web/.next/server/app/page.js.nft.json +1 -1
  67. package/app/apps/web/.next/server/app/page_client-reference-manifest.js +1 -1
  68. package/app/apps/web/.next/server/app/parallelism/page/build-manifest.json +5 -5
  69. package/app/apps/web/.next/server/app/parallelism/page.js +1 -1
  70. package/app/apps/web/.next/server/app/parallelism/page.js.nft.json +1 -1
  71. package/app/apps/web/.next/server/app/parallelism/page_client-reference-manifest.js +1 -1
  72. package/app/apps/web/.next/server/app/print/[key]/page/build-manifest.json +5 -5
  73. package/app/apps/web/.next/server/app/print/[key]/page.js +1 -1
  74. package/app/apps/web/.next/server/app/print/[key]/page.js.nft.json +1 -1
  75. package/app/apps/web/.next/server/app/print/[key]/page_client-reference-manifest.js +1 -1
  76. package/app/apps/web/.next/server/app/projects/[slug]/page/build-manifest.json +5 -5
  77. package/app/apps/web/.next/server/app/projects/[slug]/page.js +1 -1
  78. package/app/apps/web/.next/server/app/projects/[slug]/page.js.nft.json +1 -1
  79. package/app/apps/web/.next/server/app/projects/[slug]/page_client-reference-manifest.js +1 -1
  80. package/app/apps/web/.next/server/app/projects/page/build-manifest.json +5 -5
  81. package/app/apps/web/.next/server/app/projects/page.js +1 -1
  82. package/app/apps/web/.next/server/app/projects/page.js.nft.json +1 -1
  83. package/app/apps/web/.next/server/app/projects/page_client-reference-manifest.js +1 -1
  84. package/app/apps/web/.next/server/app/runs/page/build-manifest.json +5 -5
  85. package/app/apps/web/.next/server/app/runs/page.js +1 -1
  86. package/app/apps/web/.next/server/app/runs/page.js.nft.json +1 -1
  87. package/app/apps/web/.next/server/app/runs/page_client-reference-manifest.js +1 -1
  88. package/app/apps/web/.next/server/app/sessions/[id]/page/build-manifest.json +5 -5
  89. package/app/apps/web/.next/server/app/sessions/[id]/page.js +1 -1
  90. package/app/apps/web/.next/server/app/sessions/[id]/page.js.nft.json +1 -1
  91. package/app/apps/web/.next/server/app/sessions/[id]/page_client-reference-manifest.js +1 -1
  92. package/app/apps/web/.next/server/app/sessions/page/build-manifest.json +5 -5
  93. package/app/apps/web/.next/server/app/sessions/page.js +1 -1
  94. package/app/apps/web/.next/server/app/sessions/page.js.nft.json +1 -1
  95. package/app/apps/web/.next/server/app/sessions/page_client-reference-manifest.js +1 -1
  96. package/app/apps/web/.next/server/app/settings/page/build-manifest.json +5 -5
  97. package/app/apps/web/.next/server/app/settings/page.js +1 -1
  98. package/app/apps/web/.next/server/app/settings/page.js.nft.json +1 -1
  99. package/app/apps/web/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  100. package/app/apps/web/.next/server/app/team/page/build-manifest.json +5 -5
  101. package/app/apps/web/.next/server/app/team/page.js +1 -1
  102. package/app/apps/web/.next/server/app/team/page.js.nft.json +1 -1
  103. package/app/apps/web/.next/server/app/team/page_client-reference-manifest.js +1 -1
  104. package/app/apps/web/.next/server/app/usage/page/build-manifest.json +5 -5
  105. package/app/apps/web/.next/server/app/usage/page.js +1 -1
  106. package/app/apps/web/.next/server/app/usage/page.js.nft.json +1 -1
  107. package/app/apps/web/.next/server/app/usage/page_client-reference-manifest.js +1 -1
  108. package/app/apps/web/.next/server/chunks/[externals]__0rv9ct5._.js +3 -0
  109. package/app/apps/web/.next/server/chunks/[root-of-the-server]__0gril0u._.js +9 -0
  110. package/app/apps/web/.next/server/chunks/[root-of-the-server]__0n79hpm._.js +9 -0
  111. package/app/apps/web/.next/server/chunks/[root-of-the-server]__0~utv~v._.js +9 -0
  112. package/app/apps/web/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_02bw8.n.js +5 -0
  113. package/app/apps/web/.next/server/chunks/packages_parser_dist_06d9se1._.js +1 -1
  114. package/app/apps/web/.next/server/chunks/packages_parser_dist_10ihc3~._.js +1 -1
  115. package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0exckd4._.js +1 -1
  116. package/app/apps/web/.next/server/chunks/ssr/{[root-of-the-server]__0-1_njm._.js → [root-of-the-server]__0k1x-c9._.js} +2 -2
  117. package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0lhwp-k._.js +8 -0
  118. package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0n-ax-5._.js +1 -1
  119. package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0o03ymy._.js +1 -1
  120. package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0~.f_3z._.js +3 -3
  121. package/app/apps/web/.next/server/chunks/ssr/_0kajk31._.js +1 -1
  122. package/app/apps/web/.next/server/chunks/ssr/_0s3n5mz._.js +8 -0
  123. package/app/apps/web/.next/server/chunks/ssr/_0xu2pvc._.js +1 -1
  124. package/app/apps/web/.next/server/chunks/ssr/{_0jw2sm7._.js → _12mbgs5._.js} +2 -2
  125. package/app/apps/web/.next/server/chunks/ssr/apps_web_0.mu7c7._.js +1 -1
  126. package/app/apps/web/.next/server/chunks/ssr/apps_web_app_day_[date]_day-view_tsx_0wjoe1u._.js +1 -1
  127. package/app/apps/web/.next/server/chunks/ssr/apps_web_app_insights_page_tsx_071kwyw._.js +3 -0
  128. package/app/apps/web/.next/server/chunks/ssr/apps_web_app_sessions_sessions-grid_tsx_0bri5x.._.js +3 -0
  129. package/app/apps/web/.next/server/chunks/ssr/apps_web_components_0b-4-4j._.js +1 -1
  130. package/app/apps/web/.next/server/chunks/ssr/apps_web_components_0c-tpft._.js +1 -1
  131. package/app/apps/web/.next/server/chunks/ssr/apps_web_components_0l6hsch._.js +6 -0
  132. package/app/apps/web/.next/server/chunks/ssr/apps_web_components_0txtsv5._.js +1 -1
  133. package/app/apps/web/.next/server/chunks/ssr/apps_web_lib_changelog_ts_0n7_p1d._.js +1 -1
  134. package/app/apps/web/.next/server/chunks/ssr/apps_web_lib_changelog_ts_0nb5_-s._.js +1 -1
  135. package/app/apps/web/.next/server/chunks/ssr/node_modules_lucide-react_dist_esm_icons_0cepfi8._.js +3 -0
  136. package/app/apps/web/.next/server/chunks/ssr/packages_entries_dist_node_0_271yr.js +1 -1
  137. package/app/apps/web/.next/server/chunks/ssr/packages_parser_dist_0mx_509._.js +1 -1
  138. package/app/apps/web/.next/server/middleware-build-manifest.js +8 -8
  139. package/app/apps/web/.next/server/middleware-manifest.json +5 -5
  140. package/app/apps/web/.next/server/pages/500.html +1 -1
  141. package/app/apps/web/.next/server/server-reference-manifest.js +1 -1
  142. package/app/apps/web/.next/server/server-reference-manifest.json +1 -1
  143. package/app/apps/web/.next/static/chunks/02bv_ejzhhzvp.js +10 -0
  144. package/app/apps/web/.next/static/chunks/{07lbm-sdh9c2j.js → 0389wb_f0j78j.js} +1 -1
  145. package/app/apps/web/.next/static/chunks/03gy1evbn94qu.js +6 -0
  146. package/app/apps/web/.next/static/chunks/03rd.3cc_t~yf.js +4 -0
  147. package/app/apps/web/.next/static/chunks/{12jvvc_w.cb45.css → 083~hzkv577.5.css} +1 -1
  148. package/app/apps/web/.next/static/chunks/{0osdgl2.9cl1q.js → 0d2x-wfu.m0-z.js} +1 -1
  149. package/app/apps/web/.next/static/chunks/{0lziv8y_6yp5w.js → 0e_fzy0n8ij-o.js} +1 -1
  150. package/app/apps/web/.next/static/chunks/{0l4e3as18eexy.js → 0g223u9amib0q.js} +1 -1
  151. package/app/apps/web/.next/static/chunks/{0h3.-_s-k0mj3.js → 0i.hc~2bj0_3d.js} +1 -1
  152. package/app/apps/web/.next/static/chunks/0m7dd-2zd9vl2.js +4 -0
  153. package/app/apps/web/.next/static/chunks/0nqklmbs5s3ax.js +6 -0
  154. package/app/apps/web/.next/static/chunks/{073081qv4uyn8.js → 0p--hzrway8~3.js} +1 -1
  155. package/app/apps/web/.next/static/chunks/0q8dg4txlzno0.js +1 -0
  156. package/app/apps/web/.next/static/chunks/{0gblp_v.kiu-..js → 0qv-gw888_5nu.js} +1 -1
  157. package/app/apps/web/.next/static/chunks/{14bjditmmrn7l.js → 0tirv_vo6q8uc.js} +1 -1
  158. package/app/apps/web/.next/static/chunks/{0qmh5da6gfm56.js → 0vz4~a~~hq399.js} +1 -1
  159. package/app/apps/web/.next/static/chunks/{13po66tt.68f3.js → 0wwcx42ng8i2e.js} +3 -3
  160. package/app/apps/web/.next/static/chunks/{0n0n.0279ot6c.js → 14vx702c32kn6.js} +1 -1
  161. package/app/apps/web/.next/static/chunks/{turbopack-0lb9w090w5ezx.js → turbopack-0mxu3~9vk.brh.js} +1 -1
  162. package/app/apps/web/app/api/digest/_shared/digest-route-helpers.ts +178 -0
  163. package/app/apps/web/app/api/digest/day/[date]/route.ts +34 -128
  164. package/app/apps/web/app/api/digest/month/[yearMonth]/route.ts +36 -121
  165. package/app/apps/web/app/api/digest/week/[startDate]/route.ts +36 -123
  166. package/app/apps/web/app/api/events/route.ts +47 -4
  167. package/app/apps/web/app/globals.css +23 -0
  168. package/app/apps/web/app/insights/page.tsx +20 -5
  169. package/app/apps/web/app/layout.tsx +1 -0
  170. package/app/apps/web/app/page.tsx +1 -1
  171. package/app/apps/web/app/parallelism/burst-detail-modal.tsx +637 -0
  172. package/app/apps/web/app/parallelism/concurrency-info-modal.tsx +228 -0
  173. package/app/apps/web/app/parallelism/gantt-chart-utils.ts +49 -0
  174. package/app/apps/web/app/parallelism/gantt-chart.tsx +16 -902
  175. package/app/apps/web/app/projects/[slug]/page.tsx +1 -1
  176. package/app/apps/web/app/sessions/[id]/session-view/colors.ts +31 -0
  177. package/app/apps/web/app/sessions/[id]/session-view/debug-list.tsx +78 -0
  178. package/app/apps/web/app/sessions/[id]/session-view/drawer.tsx +414 -0
  179. package/app/apps/web/app/sessions/[id]/session-view/entry-day-strip.tsx +124 -0
  180. package/app/apps/web/app/sessions/[id]/session-view/header-stats.tsx +217 -0
  181. package/app/apps/web/app/sessions/[id]/session-view/helpers.ts +135 -0
  182. package/app/apps/web/app/sessions/[id]/session-view/subagent-drawer.tsx +339 -0
  183. package/app/apps/web/app/sessions/[id]/session-view/token-stats.tsx +111 -0
  184. package/app/apps/web/app/sessions/[id]/session-view/tooltip.tsx +63 -0
  185. package/app/apps/web/app/sessions/[id]/session-view/types.ts +46 -0
  186. package/app/apps/web/app/sessions/[id]/session-view/workflows-shared.tsx +104 -0
  187. package/app/apps/web/app/sessions/[id]/session-view.tsx +701 -1577
  188. package/app/apps/web/app/sessions/[id]/team-tab/minimap-hover-card.tsx +201 -0
  189. package/app/apps/web/app/sessions/[id]/team-tab/minimap-idle-band.tsx +53 -0
  190. package/app/apps/web/app/sessions/[id]/team-tab/minimap-shared.ts +20 -0
  191. package/app/apps/web/app/sessions/[id]/team-tab/team-minimap.tsx +10 -265
  192. package/app/apps/web/app/sessions/sessions-grid.tsx +43 -19
  193. package/app/apps/web/components/digest-actions.tsx +103 -0
  194. package/app/apps/web/components/job-queue-widget.tsx +1 -1
  195. package/app/apps/web/components/live-badge.tsx +11 -3
  196. package/app/apps/web/components/live-sessions-widget.tsx +9 -3
  197. package/app/apps/web/components/month-digest-view.tsx +19 -64
  198. package/app/apps/web/components/outcome-pill.tsx +24 -4
  199. package/app/apps/web/components/searchable-select.tsx +215 -0
  200. package/app/apps/web/components/tail-mode.tsx +39 -8
  201. package/app/apps/web/components/week-digest-view.tsx +16 -71
  202. package/app/apps/web/lib/changelog-data.generated.ts +1 -1
  203. package/app/apps/web/lib/entries-index.ts +7 -8
  204. package/app/apps/web/lib/entries.ts +0 -9
  205. package/app/apps/web/lib/format.test.ts +221 -0
  206. package/app/apps/web/lib/utilization-tone.test.ts +56 -0
  207. package/app/apps/web/package.json +1 -1
  208. package/app/apps/web/tsconfig.tsbuildinfo +1 -1
  209. package/dist/daemon-worker.js +83 -45
  210. package/dist/index.js +452 -198
  211. package/package.json +1 -1
  212. package/app/apps/web/.next/server/chunks/[root-of-the-server]__0455vu3._.js +0 -5
  213. package/app/apps/web/.next/server/chunks/[root-of-the-server]__04j3tng._.js +0 -9
  214. package/app/apps/web/.next/server/chunks/[root-of-the-server]__0cnh3k4._.js +0 -9
  215. package/app/apps/web/.next/server/chunks/[root-of-the-server]__132eosx._.js +0 -9
  216. package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__136-0me._.js +0 -8
  217. package/app/apps/web/.next/server/chunks/ssr/_0w.cv64._.js +0 -8
  218. package/app/apps/web/.next/server/chunks/ssr/apps_web_components_01d-iwx._.js +0 -6
  219. package/app/apps/web/.next/server/chunks/ssr/node_modules_lucide-react_dist_esm_icons_0-lmm4p._.js +0 -3
  220. package/app/apps/web/.next/static/chunks/00xkqnvk4-d1-.js +0 -10
  221. package/app/apps/web/.next/static/chunks/0htgc_hr1bw67.js +0 -1
  222. package/app/apps/web/.next/static/chunks/0nnjldasalwdu.js +0 -4
  223. package/app/apps/web/.next/static/chunks/0okg13phslr1..js +0 -4
  224. package/app/apps/web/.next/static/chunks/0vf-3rauafbek.js +0 -6
  225. package/app/apps/web/.next/static/chunks/0vl27-vjob2_9.js +0 -6
  226. /package/app/apps/web/.next/static/{7kQ9mNICnVHhf4-LmSGcy → VEfemGVAQ0xCHjj15orZU}/_buildManifest.js +0 -0
  227. /package/app/apps/web/.next/static/{7kQ9mNICnVHhf4-LmSGcy → VEfemGVAQ0xCHjj15orZU}/_clientMiddlewareManifest.js +0 -0
  228. /package/app/apps/web/.next/static/{7kQ9mNICnVHhf4-LmSGcy → VEfemGVAQ0xCHjj15orZU}/_ssgManifest.js +0 -0
@@ -0,0 +1,217 @@
1
+ "use client";
2
+
3
+ /**
4
+ * Header stats + token stat with anchored tooltip.
5
+ *
6
+ * - InlineStat / InlineStatDivider — compact icon+value chips used in the
7
+ * single-line session header, modeled on Claude's Sessions page.
8
+ * - EntrypointBadge — colors `cli` / `claude-desktop` (green, human-driven)
9
+ * vs `sdk-*` (amber, programmatic).
10
+ * - InlineTokenStat — session-header token chip with a tooltip that
11
+ * escapes the header's overflow:hidden ancestor via getBoundingClientRect
12
+ * + position:fixed (`useAnchoredTooltip` + `AnchoredTooltip`).
13
+ *
14
+ * `useAnchoredTooltip` is a hook — it must be defined and consumed inside
15
+ * a component (no hook calls at module load).
16
+ */
17
+ import React, { useRef, useState } from "react";
18
+ import type { SessionEvent } from "@claude-lens/parser";
19
+ import { formatTokens } from "@/lib/format";
20
+ import { TooltipRow } from "./tooltip";
21
+
22
+ export function InlineStat({
23
+ icon,
24
+ value,
25
+ mono,
26
+ truncate,
27
+ }: {
28
+ icon?: React.ReactNode;
29
+ value: string;
30
+ mono?: boolean;
31
+ truncate?: boolean;
32
+ }) {
33
+ return (
34
+ <span
35
+ style={{
36
+ display: "inline-flex",
37
+ alignItems: "center",
38
+ gap: 5,
39
+ fontSize: 12,
40
+ color: "var(--af-text-secondary)",
41
+ maxWidth: truncate ? 260 : undefined,
42
+ overflow: "hidden",
43
+ textOverflow: "ellipsis",
44
+ whiteSpace: "nowrap",
45
+ }}
46
+ >
47
+ {icon && (
48
+ <span
49
+ style={{
50
+ display: "inline-flex",
51
+ color: "var(--af-text-tertiary)",
52
+ }}
53
+ >
54
+ {icon}
55
+ </span>
56
+ )}
57
+ <span
58
+ style={{
59
+ fontFamily: mono ? "var(--font-mono)" : "inherit",
60
+ overflow: "hidden",
61
+ textOverflow: "ellipsis",
62
+ whiteSpace: "nowrap",
63
+ }}
64
+ >
65
+ {value}
66
+ </span>
67
+ </span>
68
+ );
69
+ }
70
+
71
+ export function InlineStatDivider() {
72
+ return (
73
+ <span
74
+ style={{
75
+ color: "var(--af-text-tertiary)",
76
+ fontSize: 11,
77
+ opacity: 0.6,
78
+ }}
79
+ >
80
+ ·
81
+ </span>
82
+ );
83
+ }
84
+
85
+ export function EntrypointBadge({ entrypoint }: { entrypoint: string }) {
86
+ const isSdk = entrypoint.startsWith("sdk-");
87
+ const tone = isSdk
88
+ ? { bg: "rgba(245, 158, 11, 0.16)", fg: "#b45309" }
89
+ : { bg: "rgba(16, 185, 129, 0.16)", fg: "#047857" };
90
+ return (
91
+ <span
92
+ title={`entrypoint: ${entrypoint}`}
93
+ style={{
94
+ fontSize: 10.5,
95
+ padding: "2px 7px",
96
+ borderRadius: 4,
97
+ background: tone.bg,
98
+ color: tone.fg,
99
+ fontWeight: 600,
100
+ fontFamily: "var(--font-mono)",
101
+ letterSpacing: 0.2,
102
+ }}
103
+ >
104
+ {entrypoint}
105
+ </span>
106
+ );
107
+ }
108
+
109
+ export function useAnchoredTooltip() {
110
+ const ref = useRef<HTMLSpanElement>(null);
111
+ const [anchor, setAnchor] = useState<{ top: number; left: number } | null>(null);
112
+ const open = () => {
113
+ if (!ref.current) return;
114
+ const r = ref.current.getBoundingClientRect();
115
+ setAnchor({ top: r.bottom + 6, left: r.left });
116
+ };
117
+ const close = () => setAnchor(null);
118
+ return { ref, anchor, open, close };
119
+ }
120
+
121
+ export function AnchoredTooltip({
122
+ anchor,
123
+ width,
124
+ children,
125
+ }: {
126
+ anchor: { top: number; left: number };
127
+ width: number;
128
+ children: React.ReactNode;
129
+ }) {
130
+ return (
131
+ <div
132
+ style={{
133
+ position: "fixed",
134
+ top: anchor.top,
135
+ left: anchor.left,
136
+ zIndex: 1000,
137
+ background: "#1A1A1A",
138
+ color: "#F5F1EC",
139
+ padding: "8px 12px",
140
+ borderRadius: 6,
141
+ fontSize: 11,
142
+ fontFamily: "var(--font-mono)",
143
+ lineHeight: 1.5,
144
+ pointerEvents: "none",
145
+ boxShadow: "0 4px 16px rgba(15,23,42,0.24)",
146
+ width,
147
+ }}
148
+ >
149
+ {children}
150
+ </div>
151
+ );
152
+ }
153
+
154
+ export function InlineTokenStat({ usage }: { usage: SessionEvent["usage"] }) {
155
+ const { ref, anchor, open, close } = useAnchoredTooltip();
156
+ const u = usage ?? { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
157
+ const totalIn = u.input + u.cacheRead + u.cacheWrite;
158
+ const pctRead = totalIn > 0 ? Math.round((u.cacheRead / totalIn) * 100) : 0;
159
+ const cached = u.cacheRead + u.cacheWrite;
160
+
161
+ return (
162
+ <span
163
+ ref={ref}
164
+ style={{
165
+ display: "inline-flex",
166
+ alignItems: "center",
167
+ gap: 6,
168
+ fontSize: 12,
169
+ color: "var(--af-text-secondary)",
170
+ cursor: "default",
171
+ fontFamily: "var(--font-mono)",
172
+ }}
173
+ onMouseEnter={open}
174
+ onMouseLeave={close}
175
+ >
176
+ <span>
177
+ {formatTokens(u.input)}
178
+ <span style={{ color: "var(--af-text-tertiary)", margin: "0 3px" }}>in</span>
179
+ {formatTokens(u.output)}
180
+ <span style={{ color: "var(--af-text-tertiary)", marginLeft: 3 }}>out</span>
181
+ </span>
182
+ {cached > 0 && (
183
+ <span
184
+ style={{
185
+ fontSize: 10.5,
186
+ color: "var(--af-text-tertiary)",
187
+ paddingLeft: 6,
188
+ borderLeft: "1px solid var(--af-border-subtle)",
189
+ }}
190
+ >
191
+ +{formatTokens(cached)} cached
192
+ </span>
193
+ )}
194
+ {anchor && (
195
+ <AnchoredTooltip anchor={anchor} width={280}>
196
+ <TooltipRow label="Input (fresh)" value={u.input.toLocaleString()} />
197
+ <TooltipRow label="Output" value={u.output.toLocaleString()} />
198
+ <TooltipRow label="Cache read" value={`${u.cacheRead.toLocaleString()} (${pctRead}%)`} />
199
+ <TooltipRow label="Cache write" value={u.cacheWrite.toLocaleString()} />
200
+ <div
201
+ style={{
202
+ marginTop: 6,
203
+ paddingTop: 6,
204
+ borderTop: "1px solid rgba(241,245,249,0.12)",
205
+ opacity: 0.65,
206
+ fontSize: 10,
207
+ whiteSpace: "normal",
208
+ lineHeight: 1.4,
209
+ }}
210
+ >
211
+ Cache reads are cumulative across all API requests and billed at ~10% of regular input.
212
+ </div>
213
+ </AnchoredTooltip>
214
+ )}
215
+ </span>
216
+ );
217
+ }
@@ -0,0 +1,135 @@
1
+ /**
2
+ * Pure (no-React) helpers for the session-view module.
3
+ *
4
+ * - `formatDayKey` — render a `YYYY-MM-DD` day key as a short, locale-free
5
+ * label (e.g. "Jun 16"). Formatted straight from the key — not a Date —
6
+ * so SSR and client output match regardless of timezone.
7
+ * - `flattenMegaRows` — turn the parser's MegaRow stream into a flat
8
+ * DisplayRow list, expanding turns whose firstPrimaryIndex is in the
9
+ * `expanded` set.
10
+ * - `allRowsAsRawRows` — fallback for "All events" filter mode. Wraps each
11
+ * raw event in a synthetic PresentationRow so TranscriptList can render
12
+ * attachments/meta/thinking/tool_result without going through the
13
+ * meaningful-transformation logic.
14
+ * - `formatDurationHeader` — concise ms→string for header stats; rolls up
15
+ * to "h m" once agent time exceeds an hour so workflow-fold totals stay
16
+ * readable.
17
+ */
18
+ import type {
19
+ MegaRow,
20
+ PresentationRow,
21
+ PresentationRowKind,
22
+ SessionEvent,
23
+ } from "@claude-lens/parser";
24
+ import type { DisplayRow } from "./types";
25
+
26
+ const DAY_MONTHS = [
27
+ "Jan",
28
+ "Feb",
29
+ "Mar",
30
+ "Apr",
31
+ "May",
32
+ "Jun",
33
+ "Jul",
34
+ "Aug",
35
+ "Sep",
36
+ "Oct",
37
+ "Nov",
38
+ "Dec",
39
+ ];
40
+
41
+ export function formatDayKey(key: string): string {
42
+ const [, m, d] = key.split("-").map(Number);
43
+ if (!m || !d) return key;
44
+ return `${DAY_MONTHS[m - 1]} ${d}`;
45
+ }
46
+
47
+ export function flattenMegaRows(
48
+ megaRows: MegaRow[],
49
+ expanded: Set<number>,
50
+ ): DisplayRow[] {
51
+ const out: DisplayRow[] = [];
52
+ for (const m of megaRows) {
53
+ if (m.kind === "turn") {
54
+ if (expanded.has(m.firstPrimaryIndex)) {
55
+ out.push({ kind: "turn-expanded-header", turn: m });
56
+ for (const r of m.rows) {
57
+ out.push({ kind: "presentation", row: r, indented: true });
58
+ }
59
+ out.push({ kind: "turn-expanded-footer", turn: m });
60
+ } else {
61
+ out.push({ kind: "turn-collapsed", turn: m });
62
+ }
63
+ } else {
64
+ out.push({ kind: "presentation", row: m });
65
+ }
66
+ }
67
+ return out;
68
+ }
69
+
70
+ export function allRowsAsRawRows(events: SessionEvent[]): PresentationRow[] {
71
+ const out: PresentationRow[] = [];
72
+ for (const e of events) {
73
+ let kind: PresentationRowKind;
74
+ switch (e.role) {
75
+ case "user":
76
+ kind = "user";
77
+ break;
78
+ case "agent":
79
+ case "agent-thinking":
80
+ kind = "agent";
81
+ break;
82
+ case "tool-call":
83
+ case "tool-result":
84
+ kind = "tool-group";
85
+ break;
86
+ case "system":
87
+ case "meta":
88
+ kind = "model";
89
+ break;
90
+ }
91
+ if (kind === "tool-group") {
92
+ out.push({
93
+ kind: "tool-group",
94
+ toolNames: [{ name: e.toolName ?? e.rawType, count: 1 }],
95
+ count: 1,
96
+ events: [e],
97
+ tOffsetMs: e.tOffsetMs,
98
+ gapMs: e.gapMs,
99
+ });
100
+ } else if (kind === "agent") {
101
+ out.push({
102
+ kind: "agent",
103
+ event: e,
104
+ groupedEvents: [e],
105
+ tOffsetMs: e.tOffsetMs,
106
+ gapMs: e.gapMs,
107
+ });
108
+ } else if (kind === "user") {
109
+ out.push({
110
+ kind: "user",
111
+ event: e,
112
+ tOffsetMs: e.tOffsetMs,
113
+ gapMs: e.gapMs,
114
+ });
115
+ } else {
116
+ out.push({
117
+ kind: "model",
118
+ event: e,
119
+ tOffsetMs: e.tOffsetMs,
120
+ gapMs: e.gapMs,
121
+ });
122
+ }
123
+ }
124
+ return out;
125
+ }
126
+
127
+ export function formatDurationHeader(ms: number): string {
128
+ if (ms < 1000) return `${ms}ms`;
129
+ const s = Math.round(ms / 1000);
130
+ if (s < 60) return `${s}s`;
131
+ const m = Math.floor(s / 60);
132
+ if (m < 60) return `${m}m ${s % 60}s`;
133
+ const h = Math.floor(m / 60);
134
+ return `${h}h ${m % 60}m`;
135
+ }
@@ -0,0 +1,339 @@
1
+ "use client";
2
+
3
+ /**
4
+ * SubagentDrawer — right-side panel opened when the user clicks a
5
+ * sub-agent lane bar on the mini-map. Renders the full run profile:
6
+ * type pill, background badge, timing range, duration, model, parent
7
+ * tool-use id, activity stats (events / messages / tool calls), token
8
+ * breakdown, tools-used pills, the prompt the parent dispatched, and
9
+ * the final agent text. A "Jump to parent" button scrolls the
10
+ * transcript to the Agent tool_use row that kicked the subagent off.
11
+ */
12
+ import React from "react";
13
+ import { X } from "lucide-react";
14
+ import ReactMarkdown from "react-markdown";
15
+ import remarkGfm from "remark-gfm";
16
+ import type { SubagentRun } from "@claude-lens/parser";
17
+ import { formatGap, formatOffset } from "@/lib/format";
18
+ import { subagentColor } from "./colors";
19
+ import { StatCell, TokenLine } from "./workflows-shared";
20
+ import { shortenToolName } from "../turn-steps";
21
+
22
+ export function SubagentDrawer({
23
+ subagent,
24
+ onClose,
25
+ onJumpToParent,
26
+ }: {
27
+ subagent: SubagentRun;
28
+ onClose: () => void;
29
+ onJumpToParent: () => void;
30
+ }) {
31
+ const s = subagent;
32
+ const fill = subagentColor(s.agentType, s.runInBackground);
33
+ const startOff = formatOffset(s.startTOffsetMs);
34
+ const endOff = formatOffset(s.endTOffsetMs);
35
+ const dur = s.durationMs !== undefined ? formatGap(s.durationMs) : "—";
36
+ const totalIn = s.totalUsage.input + s.totalUsage.cacheRead + s.totalUsage.cacheWrite;
37
+ const pctRead = totalIn > 0 ? Math.round((s.totalUsage.cacheRead / totalIn) * 100) : 0;
38
+
39
+ return (
40
+ <div>
41
+ {/* Sticky title bar */}
42
+ <div
43
+ style={{
44
+ padding: "14px 20px",
45
+ borderBottom: "1px solid var(--af-border-subtle)",
46
+ display: "flex",
47
+ alignItems: "center",
48
+ gap: 10,
49
+ background: "var(--af-surface)",
50
+ position: "sticky",
51
+ top: 0,
52
+ zIndex: 1,
53
+ flexWrap: "wrap",
54
+ }}
55
+ >
56
+ <span
57
+ style={{
58
+ fontSize: 11,
59
+ fontWeight: 600,
60
+ padding: "3px 10px",
61
+ borderRadius: 4,
62
+ background: fill,
63
+ color: "#fff",
64
+ }}
65
+ >
66
+ {s.agentType}
67
+ </span>
68
+ {s.runInBackground && (
69
+ <span
70
+ style={{
71
+ fontSize: 9,
72
+ fontWeight: 600,
73
+ padding: "2px 8px",
74
+ borderRadius: 4,
75
+ background: "var(--af-warning-subtle)",
76
+ color: "var(--af-warning)",
77
+ textTransform: "uppercase",
78
+ letterSpacing: "0.04em",
79
+ }}
80
+ >
81
+ background
82
+ </span>
83
+ )}
84
+ <span
85
+ style={{
86
+ fontSize: 14,
87
+ fontWeight: 600,
88
+ color: "var(--af-text)",
89
+ flex: 1,
90
+ minWidth: 0,
91
+ overflow: "hidden",
92
+ textOverflow: "ellipsis",
93
+ whiteSpace: "nowrap",
94
+ }}
95
+ title={s.description}
96
+ >
97
+ {s.description}
98
+ </span>
99
+ <button
100
+ onClick={onClose}
101
+ style={{
102
+ background: "transparent",
103
+ border: "none",
104
+ cursor: "pointer",
105
+ color: "var(--af-text-tertiary)",
106
+ padding: 4,
107
+ borderRadius: 4,
108
+ }}
109
+ aria-label="Close"
110
+ >
111
+ <X size={16} />
112
+ </button>
113
+ </div>
114
+
115
+ {/* Meta strip */}
116
+ <div
117
+ style={{
118
+ padding: "10px 20px",
119
+ fontFamily: "var(--font-mono)",
120
+ fontSize: 11,
121
+ color: "var(--af-text-tertiary)",
122
+ borderBottom: "1px solid var(--af-border-subtle)",
123
+ display: "grid",
124
+ gridTemplateColumns: "auto 1fr",
125
+ columnGap: 10,
126
+ rowGap: 3,
127
+ }}
128
+ >
129
+ <span style={{ opacity: 0.7 }}>range</span>
130
+ <span style={{ color: "var(--af-text-secondary)" }}>
131
+ {startOff} → {endOff}
132
+ </span>
133
+ <span style={{ opacity: 0.7 }}>duration</span>
134
+ <span style={{ color: "var(--af-text-secondary)" }}>{dur}</span>
135
+ {s.model && (
136
+ <>
137
+ <span style={{ opacity: 0.7 }}>model</span>
138
+ <span style={{ color: "var(--af-text-secondary)" }}>{s.model}</span>
139
+ </>
140
+ )}
141
+ {s.parentToolUseId && (
142
+ <>
143
+ <span style={{ opacity: 0.7 }}>parent</span>
144
+ <span style={{ color: "var(--af-text-secondary)" }}>
145
+ {s.parentToolUseId.slice(0, 24)}…
146
+ </span>
147
+ </>
148
+ )}
149
+ </div>
150
+
151
+ {/* Activity stats */}
152
+ <div
153
+ style={{
154
+ padding: "14px 20px",
155
+ borderBottom: "1px solid var(--af-border-subtle)",
156
+ display: "grid",
157
+ gridTemplateColumns: "1fr 1fr 1fr",
158
+ gap: 10,
159
+ }}
160
+ >
161
+ <StatCell label="Events" value={String(s.eventCount)} />
162
+ <StatCell label="Messages" value={String(s.assistantMessageCount ?? 0)} />
163
+ <StatCell label="Tool calls" value={String(s.toolCallCount ?? 0)} />
164
+ </div>
165
+
166
+ {/* Token breakdown */}
167
+ <div
168
+ style={{
169
+ padding: "12px 20px",
170
+ borderBottom: "1px solid var(--af-border-subtle)",
171
+ fontSize: 11,
172
+ fontFamily: "var(--font-mono)",
173
+ color: "var(--af-text-secondary)",
174
+ display: "flex",
175
+ flexDirection: "column",
176
+ gap: 3,
177
+ }}
178
+ >
179
+ <TokenLine label="Input (fresh)" value={s.totalUsage.input} />
180
+ <TokenLine label="Output" value={s.totalUsage.output} />
181
+ <TokenLine
182
+ label="Cache read"
183
+ value={s.totalUsage.cacheRead}
184
+ suffix={` (${pctRead}%)`}
185
+ />
186
+ <TokenLine label="Cache write" value={s.totalUsage.cacheWrite} />
187
+ </div>
188
+
189
+ {/* Tool breakdown */}
190
+ {s.toolCalls && s.toolCalls.length > 0 && (
191
+ <div
192
+ style={{
193
+ padding: "14px 20px",
194
+ borderBottom: "1px solid var(--af-border-subtle)",
195
+ }}
196
+ >
197
+ <div
198
+ style={{
199
+ fontSize: 10,
200
+ fontWeight: 600,
201
+ textTransform: "uppercase",
202
+ letterSpacing: "0.04em",
203
+ color: "var(--af-text-tertiary)",
204
+ marginBottom: 8,
205
+ }}
206
+ >
207
+ Tools used
208
+ </div>
209
+ <div style={{ display: "flex", flexWrap: "wrap", gap: 6 }}>
210
+ {s.toolCalls.map((t) => (
211
+ <span
212
+ key={t.name}
213
+ style={{
214
+ display: "inline-flex",
215
+ alignItems: "center",
216
+ gap: 4,
217
+ fontSize: 11,
218
+ padding: "2px 8px",
219
+ borderRadius: 4,
220
+ background: "var(--af-border-subtle)",
221
+ color: "var(--af-text)",
222
+ fontFamily: "var(--font-mono)",
223
+ }}
224
+ >
225
+ <b style={{ fontWeight: 600 }}>{shortenToolName(t.name)}</b>
226
+ <span style={{ color: "var(--af-text-tertiary)" }}>×{t.count}</span>
227
+ </span>
228
+ ))}
229
+ </div>
230
+ </div>
231
+ )}
232
+
233
+ {/* Parent prompt (what the parent asked the subagent to do) */}
234
+ {s.prompt && (
235
+ <div
236
+ style={{
237
+ padding: "14px 20px",
238
+ borderBottom: "1px solid var(--af-border-subtle)",
239
+ }}
240
+ >
241
+ <div
242
+ style={{
243
+ display: "flex",
244
+ alignItems: "center",
245
+ justifyContent: "space-between",
246
+ marginBottom: 8,
247
+ }}
248
+ >
249
+ <div
250
+ style={{
251
+ fontSize: 10,
252
+ fontWeight: 600,
253
+ textTransform: "uppercase",
254
+ letterSpacing: "0.04em",
255
+ color: "var(--af-text-tertiary)",
256
+ }}
257
+ >
258
+ Prompt
259
+ </div>
260
+ {s.parentToolUseId && (
261
+ <button
262
+ type="button"
263
+ onClick={onJumpToParent}
264
+ style={{
265
+ fontSize: 10,
266
+ color: "var(--af-accent)",
267
+ background: "transparent",
268
+ border: "none",
269
+ cursor: "pointer",
270
+ padding: 0,
271
+ }}
272
+ >
273
+ Jump to parent →
274
+ </button>
275
+ )}
276
+ </div>
277
+ <div className="sl-prose" style={{ fontSize: 12.5 }}>
278
+ <ReactMarkdown
279
+ remarkPlugins={[remarkGfm]}
280
+ components={{
281
+ a: (props) => (
282
+ <a
283
+ {...props}
284
+ target="_blank"
285
+ rel="noopener noreferrer"
286
+ style={{
287
+ color: "var(--af-accent)",
288
+ textDecoration: "underline",
289
+ }}
290
+ />
291
+ ),
292
+ }}
293
+ >
294
+ {s.prompt}
295
+ </ReactMarkdown>
296
+ </div>
297
+ </div>
298
+ )}
299
+
300
+ {/* Final text */}
301
+ {s.finalText && (
302
+ <div style={{ padding: "14px 20px" }}>
303
+ <div
304
+ style={{
305
+ fontSize: 10,
306
+ fontWeight: 600,
307
+ textTransform: "uppercase",
308
+ letterSpacing: "0.04em",
309
+ color: "var(--af-text-tertiary)",
310
+ marginBottom: 8,
311
+ }}
312
+ >
313
+ Final result
314
+ </div>
315
+ <div className="sl-prose" style={{ fontSize: 13 }}>
316
+ <ReactMarkdown
317
+ remarkPlugins={[remarkGfm]}
318
+ components={{
319
+ a: (props) => (
320
+ <a
321
+ {...props}
322
+ target="_blank"
323
+ rel="noopener noreferrer"
324
+ style={{
325
+ color: "var(--af-accent)",
326
+ textDecoration: "underline",
327
+ }}
328
+ />
329
+ ),
330
+ }}
331
+ >
332
+ {s.finalText}
333
+ </ReactMarkdown>
334
+ </div>
335
+ </div>
336
+ )}
337
+ </div>
338
+ );
339
+ }