fleetlens 0.13.0 → 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 (205) 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.nft.json +1 -1
  27. package/app/apps/web/.next/server/app/api/jobs/route.js.nft.json +1 -1
  28. package/app/apps/web/.next/server/app/api/pdf/[key]/route.js.nft.json +1 -1
  29. package/app/apps/web/.next/server/app/api/runs/[runId]/route.js.nft.json +1 -1
  30. package/app/apps/web/.next/server/app/api/runs/[runId]/stream/route.js.nft.json +1 -1
  31. package/app/apps/web/.next/server/app/api/runs/route.js.nft.json +1 -1
  32. package/app/apps/web/.next/server/app/api/settings/route.js.nft.json +1 -1
  33. package/app/apps/web/.next/server/app/api/team-config/route.js.nft.json +1 -1
  34. package/app/apps/web/.next/server/app/api/workflow-agent/route.js.nft.json +1 -1
  35. package/app/apps/web/.next/server/app/changelog/page/build-manifest.json +5 -5
  36. package/app/apps/web/.next/server/app/changelog/page.js +1 -1
  37. package/app/apps/web/.next/server/app/changelog/page.js.nft.json +1 -1
  38. package/app/apps/web/.next/server/app/changelog/page_client-reference-manifest.js +1 -1
  39. package/app/apps/web/.next/server/app/day/[date]/page/build-manifest.json +5 -5
  40. package/app/apps/web/.next/server/app/day/[date]/page.js +1 -1
  41. package/app/apps/web/.next/server/app/day/[date]/page.js.nft.json +1 -1
  42. package/app/apps/web/.next/server/app/day/[date]/page_client-reference-manifest.js +1 -1
  43. package/app/apps/web/.next/server/app/day/page/build-manifest.json +5 -5
  44. package/app/apps/web/.next/server/app/day/page.js +1 -1
  45. package/app/apps/web/.next/server/app/day/page.js.nft.json +1 -1
  46. package/app/apps/web/.next/server/app/day/page_client-reference-manifest.js +1 -1
  47. package/app/apps/web/.next/server/app/digest/[date]/page/build-manifest.json +5 -5
  48. package/app/apps/web/.next/server/app/digest/[date]/page.js +1 -1
  49. package/app/apps/web/.next/server/app/digest/[date]/page.js.nft.json +1 -1
  50. package/app/apps/web/.next/server/app/digest/[date]/page_client-reference-manifest.js +1 -1
  51. package/app/apps/web/.next/server/app/digest/page/build-manifest.json +5 -5
  52. package/app/apps/web/.next/server/app/digest/page.js +1 -1
  53. package/app/apps/web/.next/server/app/digest/page.js.nft.json +1 -1
  54. package/app/apps/web/.next/server/app/digest/page_client-reference-manifest.js +1 -1
  55. package/app/apps/web/.next/server/app/insights/[key]/page/build-manifest.json +5 -5
  56. package/app/apps/web/.next/server/app/insights/[key]/page.js +1 -1
  57. package/app/apps/web/.next/server/app/insights/[key]/page.js.nft.json +1 -1
  58. package/app/apps/web/.next/server/app/insights/[key]/page_client-reference-manifest.js +1 -1
  59. package/app/apps/web/.next/server/app/insights/page/build-manifest.json +5 -5
  60. package/app/apps/web/.next/server/app/insights/page.js +3 -2
  61. package/app/apps/web/.next/server/app/insights/page.js.nft.json +1 -1
  62. package/app/apps/web/.next/server/app/insights/page_client-reference-manifest.js +1 -1
  63. package/app/apps/web/.next/server/app/page/build-manifest.json +5 -5
  64. package/app/apps/web/.next/server/app/page.js +1 -1
  65. package/app/apps/web/.next/server/app/page.js.nft.json +1 -1
  66. package/app/apps/web/.next/server/app/page_client-reference-manifest.js +1 -1
  67. package/app/apps/web/.next/server/app/parallelism/page/build-manifest.json +5 -5
  68. package/app/apps/web/.next/server/app/parallelism/page.js +1 -1
  69. package/app/apps/web/.next/server/app/parallelism/page.js.nft.json +1 -1
  70. package/app/apps/web/.next/server/app/parallelism/page_client-reference-manifest.js +1 -1
  71. package/app/apps/web/.next/server/app/print/[key]/page/build-manifest.json +5 -5
  72. package/app/apps/web/.next/server/app/print/[key]/page.js +1 -1
  73. package/app/apps/web/.next/server/app/print/[key]/page.js.nft.json +1 -1
  74. package/app/apps/web/.next/server/app/print/[key]/page_client-reference-manifest.js +1 -1
  75. package/app/apps/web/.next/server/app/projects/[slug]/page/build-manifest.json +5 -5
  76. package/app/apps/web/.next/server/app/projects/[slug]/page.js +1 -1
  77. package/app/apps/web/.next/server/app/projects/[slug]/page.js.nft.json +1 -1
  78. package/app/apps/web/.next/server/app/projects/[slug]/page_client-reference-manifest.js +1 -1
  79. package/app/apps/web/.next/server/app/projects/page/build-manifest.json +5 -5
  80. package/app/apps/web/.next/server/app/projects/page.js +1 -1
  81. package/app/apps/web/.next/server/app/projects/page.js.nft.json +1 -1
  82. package/app/apps/web/.next/server/app/projects/page_client-reference-manifest.js +1 -1
  83. package/app/apps/web/.next/server/app/runs/page/build-manifest.json +5 -5
  84. package/app/apps/web/.next/server/app/runs/page.js +1 -1
  85. package/app/apps/web/.next/server/app/runs/page.js.nft.json +1 -1
  86. package/app/apps/web/.next/server/app/runs/page_client-reference-manifest.js +1 -1
  87. package/app/apps/web/.next/server/app/sessions/[id]/page/build-manifest.json +5 -5
  88. package/app/apps/web/.next/server/app/sessions/[id]/page.js +1 -1
  89. package/app/apps/web/.next/server/app/sessions/[id]/page.js.nft.json +1 -1
  90. package/app/apps/web/.next/server/app/sessions/[id]/page_client-reference-manifest.js +1 -1
  91. package/app/apps/web/.next/server/app/sessions/page/build-manifest.json +5 -5
  92. package/app/apps/web/.next/server/app/sessions/page.js +1 -1
  93. package/app/apps/web/.next/server/app/sessions/page.js.nft.json +1 -1
  94. package/app/apps/web/.next/server/app/sessions/page_client-reference-manifest.js +1 -1
  95. package/app/apps/web/.next/server/app/settings/page/build-manifest.json +5 -5
  96. package/app/apps/web/.next/server/app/settings/page.js +1 -1
  97. package/app/apps/web/.next/server/app/settings/page.js.nft.json +1 -1
  98. package/app/apps/web/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  99. package/app/apps/web/.next/server/app/team/page/build-manifest.json +5 -5
  100. package/app/apps/web/.next/server/app/team/page.js +1 -1
  101. package/app/apps/web/.next/server/app/team/page.js.nft.json +1 -1
  102. package/app/apps/web/.next/server/app/team/page_client-reference-manifest.js +1 -1
  103. package/app/apps/web/.next/server/app/usage/page/build-manifest.json +5 -5
  104. package/app/apps/web/.next/server/app/usage/page.js +1 -1
  105. package/app/apps/web/.next/server/app/usage/page.js.nft.json +1 -1
  106. package/app/apps/web/.next/server/app/usage/page_client-reference-manifest.js +1 -1
  107. package/app/apps/web/.next/server/chunks/[root-of-the-server]__0gril0u._.js +9 -0
  108. package/app/apps/web/.next/server/chunks/[root-of-the-server]__0n79hpm._.js +9 -0
  109. package/app/apps/web/.next/server/chunks/[root-of-the-server]__0~utv~v._.js +9 -0
  110. package/app/apps/web/.next/server/chunks/packages_parser_dist_06d9se1._.js +1 -1
  111. package/app/apps/web/.next/server/chunks/packages_parser_dist_10ihc3~._.js +1 -1
  112. package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0exckd4._.js +1 -1
  113. 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
  114. package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0lhwp-k._.js +8 -0
  115. package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0~.f_3z._.js +3 -3
  116. package/app/apps/web/.next/server/chunks/ssr/_0kajk31._.js +1 -1
  117. package/app/apps/web/.next/server/chunks/ssr/_0s3n5mz._.js +8 -0
  118. package/app/apps/web/.next/server/chunks/ssr/_0xu2pvc._.js +1 -1
  119. package/app/apps/web/.next/server/chunks/ssr/{_0jw2sm7._.js → _12mbgs5._.js} +2 -2
  120. package/app/apps/web/.next/server/chunks/ssr/apps_web_app_day_[date]_day-view_tsx_0wjoe1u._.js +1 -1
  121. package/app/apps/web/.next/server/chunks/ssr/apps_web_app_insights_page_tsx_071kwyw._.js +3 -0
  122. package/app/apps/web/.next/server/chunks/ssr/apps_web_app_sessions_sessions-grid_tsx_0bri5x.._.js +3 -0
  123. package/app/apps/web/.next/server/chunks/ssr/apps_web_components_0l6hsch._.js +6 -0
  124. package/app/apps/web/.next/server/chunks/ssr/apps_web_lib_changelog_ts_0n7_p1d._.js +1 -1
  125. package/app/apps/web/.next/server/chunks/ssr/apps_web_lib_changelog_ts_0nb5_-s._.js +1 -1
  126. package/app/apps/web/.next/server/chunks/ssr/node_modules_lucide-react_dist_esm_icons_0cepfi8._.js +3 -0
  127. package/app/apps/web/.next/server/chunks/ssr/packages_entries_dist_node_0_271yr.js +1 -1
  128. package/app/apps/web/.next/server/chunks/ssr/packages_parser_dist_0mx_509._.js +1 -1
  129. package/app/apps/web/.next/server/middleware-build-manifest.js +8 -8
  130. package/app/apps/web/.next/server/middleware-manifest.json +5 -5
  131. package/app/apps/web/.next/server/pages/500.html +1 -1
  132. package/app/apps/web/.next/server/server-reference-manifest.js +1 -1
  133. package/app/apps/web/.next/server/server-reference-manifest.json +1 -1
  134. package/app/apps/web/.next/static/chunks/{0pzt9vemtzct8.js → 02bv_ejzhhzvp.js} +3 -3
  135. package/app/apps/web/.next/static/chunks/{07lbm-sdh9c2j.js → 0389wb_f0j78j.js} +1 -1
  136. package/app/apps/web/.next/static/chunks/03gy1evbn94qu.js +6 -0
  137. package/app/apps/web/.next/static/chunks/03rd.3cc_t~yf.js +4 -0
  138. package/app/apps/web/.next/static/chunks/{0l4e3as18eexy.js → 0g223u9amib0q.js} +1 -1
  139. package/app/apps/web/.next/static/chunks/{0h3.-_s-k0mj3.js → 0i.hc~2bj0_3d.js} +1 -1
  140. package/app/apps/web/.next/static/chunks/0m7dd-2zd9vl2.js +4 -0
  141. package/app/apps/web/.next/static/chunks/0nqklmbs5s3ax.js +6 -0
  142. package/app/apps/web/.next/static/chunks/{073081qv4uyn8.js → 0p--hzrway8~3.js} +1 -1
  143. package/app/apps/web/.next/static/chunks/0q8dg4txlzno0.js +1 -0
  144. package/app/apps/web/.next/static/chunks/{0gblp_v.kiu-..js → 0qv-gw888_5nu.js} +1 -1
  145. package/app/apps/web/.next/static/chunks/{14bjditmmrn7l.js → 0tirv_vo6q8uc.js} +1 -1
  146. package/app/apps/web/.next/static/chunks/{0qmh5da6gfm56.js → 0vz4~a~~hq399.js} +1 -1
  147. package/app/apps/web/.next/static/chunks/{0n0n.0279ot6c.js → 14vx702c32kn6.js} +1 -1
  148. package/app/apps/web/.next/static/chunks/{turbopack-0lb9w090w5ezx.js → turbopack-0mxu3~9vk.brh.js} +1 -1
  149. package/app/apps/web/app/api/digest/_shared/digest-route-helpers.ts +178 -0
  150. package/app/apps/web/app/api/digest/day/[date]/route.ts +34 -128
  151. package/app/apps/web/app/api/digest/month/[yearMonth]/route.ts +36 -121
  152. package/app/apps/web/app/api/digest/week/[startDate]/route.ts +36 -123
  153. package/app/apps/web/app/insights/page.tsx +20 -5
  154. package/app/apps/web/app/parallelism/burst-detail-modal.tsx +637 -0
  155. package/app/apps/web/app/parallelism/concurrency-info-modal.tsx +228 -0
  156. package/app/apps/web/app/parallelism/gantt-chart-utils.ts +49 -0
  157. package/app/apps/web/app/parallelism/gantt-chart.tsx +16 -902
  158. package/app/apps/web/app/sessions/[id]/session-view/colors.ts +31 -0
  159. package/app/apps/web/app/sessions/[id]/session-view/debug-list.tsx +78 -0
  160. package/app/apps/web/app/sessions/[id]/session-view/drawer.tsx +414 -0
  161. package/app/apps/web/app/sessions/[id]/session-view/entry-day-strip.tsx +124 -0
  162. package/app/apps/web/app/sessions/[id]/session-view/header-stats.tsx +217 -0
  163. package/app/apps/web/app/sessions/[id]/session-view/helpers.ts +135 -0
  164. package/app/apps/web/app/sessions/[id]/session-view/subagent-drawer.tsx +339 -0
  165. package/app/apps/web/app/sessions/[id]/session-view/token-stats.tsx +111 -0
  166. package/app/apps/web/app/sessions/[id]/session-view/tooltip.tsx +63 -0
  167. package/app/apps/web/app/sessions/[id]/session-view/types.ts +46 -0
  168. package/app/apps/web/app/sessions/[id]/session-view/workflows-shared.tsx +104 -0
  169. package/app/apps/web/app/sessions/[id]/session-view.tsx +448 -1639
  170. package/app/apps/web/app/sessions/[id]/team-tab/minimap-hover-card.tsx +201 -0
  171. package/app/apps/web/app/sessions/[id]/team-tab/minimap-idle-band.tsx +53 -0
  172. package/app/apps/web/app/sessions/[id]/team-tab/minimap-shared.ts +20 -0
  173. package/app/apps/web/app/sessions/[id]/team-tab/team-minimap.tsx +10 -265
  174. package/app/apps/web/app/sessions/sessions-grid.tsx +10 -10
  175. package/app/apps/web/components/digest-actions.tsx +103 -0
  176. package/app/apps/web/components/job-queue-widget.tsx +1 -1
  177. package/app/apps/web/components/month-digest-view.tsx +19 -64
  178. package/app/apps/web/components/searchable-select.tsx +215 -0
  179. package/app/apps/web/components/tail-mode.tsx +39 -8
  180. package/app/apps/web/components/week-digest-view.tsx +16 -71
  181. package/app/apps/web/lib/changelog-data.generated.ts +1 -1
  182. package/app/apps/web/lib/entries-index.ts +7 -8
  183. package/app/apps/web/lib/entries.ts +0 -9
  184. package/app/apps/web/lib/format.test.ts +221 -0
  185. package/app/apps/web/lib/utilization-tone.test.ts +56 -0
  186. package/app/apps/web/package.json +1 -1
  187. package/app/apps/web/tsconfig.tsbuildinfo +1 -1
  188. package/dist/daemon-worker.js +35 -43
  189. package/dist/index.js +37 -49
  190. package/package.json +1 -1
  191. package/app/apps/web/.next/server/chunks/[root-of-the-server]__04j3tng._.js +0 -9
  192. package/app/apps/web/.next/server/chunks/[root-of-the-server]__0cnh3k4._.js +0 -9
  193. package/app/apps/web/.next/server/chunks/[root-of-the-server]__132eosx._.js +0 -9
  194. package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__136-0me._.js +0 -8
  195. package/app/apps/web/.next/server/chunks/ssr/_0w.cv64._.js +0 -8
  196. package/app/apps/web/.next/server/chunks/ssr/apps_web_components_01d-iwx._.js +0 -6
  197. package/app/apps/web/.next/server/chunks/ssr/node_modules_lucide-react_dist_esm_icons_0-lmm4p._.js +0 -3
  198. package/app/apps/web/.next/static/chunks/05k84gif3ucxm.js +0 -6
  199. package/app/apps/web/.next/static/chunks/0nnjldasalwdu.js +0 -4
  200. package/app/apps/web/.next/static/chunks/0okg13phslr1..js +0 -4
  201. package/app/apps/web/.next/static/chunks/0rnay-a.-jr8-.js +0 -1
  202. package/app/apps/web/.next/static/chunks/0snv3-2ux2xvv.js +0 -6
  203. /package/app/apps/web/.next/static/{rbp9x3iPbzrz-wxS-DAU5 → VEfemGVAQ0xCHjj15orZU}/_buildManifest.js +0 -0
  204. /package/app/apps/web/.next/static/{rbp9x3iPbzrz-wxS-DAU5 → VEfemGVAQ0xCHjj15orZU}/_clientMiddlewareManifest.js +0 -0
  205. /package/app/apps/web/.next/static/{rbp9x3iPbzrz-wxS-DAU5 → 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
+ }