@flanksource/clicky-ui 0.3.4 → 0.3.6

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 (77) hide show
  1. package/dist/ai.cjs +15 -0
  2. package/dist/ai.cjs.map +1 -1
  3. package/dist/ai.js +15 -0
  4. package/dist/ai.js.map +1 -1
  5. package/dist/data/ProgressBars.cjs +194 -0
  6. package/dist/data/ProgressBars.cjs.map +1 -0
  7. package/dist/data/ProgressBars.d.ts +85 -0
  8. package/dist/data/ProgressBars.d.ts.map +1 -0
  9. package/dist/data/ProgressBars.js +194 -0
  10. package/dist/data/ProgressBars.js.map +1 -0
  11. package/dist/data/ProgressBars.model.cjs +27 -0
  12. package/dist/data/ProgressBars.model.cjs.map +1 -0
  13. package/dist/data/ProgressBars.model.d.ts +20 -0
  14. package/dist/data/ProgressBars.model.d.ts.map +1 -0
  15. package/dist/data/ProgressBars.model.js +27 -0
  16. package/dist/data/ProgressBars.model.js.map +1 -0
  17. package/dist/data/TimeseriesCoreBars.cjs +19 -173
  18. package/dist/data/TimeseriesCoreBars.cjs.map +1 -1
  19. package/dist/data/TimeseriesCoreBars.d.ts +9 -26
  20. package/dist/data/TimeseriesCoreBars.d.ts.map +1 -1
  21. package/dist/data/TimeseriesCoreBars.js +20 -174
  22. package/dist/data/TimeseriesCoreBars.js.map +1 -1
  23. package/dist/data/WorkloadCard.model.cjs.map +1 -1
  24. package/dist/data/WorkloadCard.model.d.ts +2 -2
  25. package/dist/data/WorkloadCard.model.d.ts.map +1 -1
  26. package/dist/data/WorkloadCard.model.js.map +1 -1
  27. package/dist/data/ai/SessionViewer.cjs +275 -0
  28. package/dist/data/ai/SessionViewer.cjs.map +1 -0
  29. package/dist/data/ai/SessionViewer.d.ts +33 -0
  30. package/dist/data/ai/SessionViewer.d.ts.map +1 -0
  31. package/dist/data/ai/SessionViewer.fixtures.d.ts +4 -0
  32. package/dist/data/ai/SessionViewer.fixtures.d.ts.map +1 -0
  33. package/dist/data/ai/SessionViewer.js +275 -0
  34. package/dist/data/ai/SessionViewer.js.map +1 -0
  35. package/dist/data/ai/SessionViewer.model.cjs +275 -0
  36. package/dist/data/ai/SessionViewer.model.cjs.map +1 -0
  37. package/dist/data/ai/SessionViewer.model.d.ts +87 -0
  38. package/dist/data/ai/SessionViewer.model.d.ts.map +1 -0
  39. package/dist/data/ai/SessionViewer.model.js +275 -0
  40. package/dist/data/ai/SessionViewer.model.js.map +1 -0
  41. package/dist/data/ai/SessionViewerMenu.cjs +190 -0
  42. package/dist/data/ai/SessionViewerMenu.cjs.map +1 -0
  43. package/dist/data/ai/SessionViewerMenu.d.ts +24 -0
  44. package/dist/data/ai/SessionViewerMenu.d.ts.map +1 -0
  45. package/dist/data/ai/SessionViewerMenu.js +190 -0
  46. package/dist/data/ai/SessionViewerMenu.js.map +1 -0
  47. package/dist/data/ai/index.d.ts +3 -0
  48. package/dist/data/ai/index.d.ts.map +1 -1
  49. package/dist/data/ai/session-categories.cjs +219 -0
  50. package/dist/data/ai/session-categories.cjs.map +1 -0
  51. package/dist/data/ai/session-categories.d.ts +36 -0
  52. package/dist/data/ai/session-categories.d.ts.map +1 -0
  53. package/dist/data/ai/session-categories.js +219 -0
  54. package/dist/data/ai/session-categories.js.map +1 -0
  55. package/dist/data/test-runner/TestRunner.fixtures.d.ts.map +1 -1
  56. package/dist/data/version-info.cjs +3 -3
  57. package/dist/data/version-info.cjs.map +1 -1
  58. package/dist/data/version-info.js +3 -3
  59. package/dist/data/version-info.js.map +1 -1
  60. package/dist/data.cjs +4 -2
  61. package/dist/data.cjs.map +1 -1
  62. package/dist/data.d.ts +3 -2
  63. package/dist/data.d.ts.map +1 -1
  64. package/dist/data.js +4 -2
  65. package/dist/data.js.map +1 -1
  66. package/dist/index.cjs +4 -2
  67. package/dist/index.cjs.map +1 -1
  68. package/dist/index.js +4 -2
  69. package/dist/index.js.map +1 -1
  70. package/dist/styles.css +1 -1
  71. package/package.json +1 -1
  72. package/dist/data/TimeseriesCoreBars.model.cjs +0 -27
  73. package/dist/data/TimeseriesCoreBars.model.cjs.map +0 -1
  74. package/dist/data/TimeseriesCoreBars.model.d.ts +0 -20
  75. package/dist/data/TimeseriesCoreBars.model.d.ts.map +0 -1
  76. package/dist/data/TimeseriesCoreBars.model.js +0 -27
  77. package/dist/data/TimeseriesCoreBars.model.js.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SessionViewer.cjs","sources":["../../../src/data/ai/SessionViewer.tsx"],"sourcesContent":["import { useMemo, useState, type ReactNode } from \"react\";\nimport { cn } from \"../../lib/utils\";\nimport { Icon, type StaticIconComponent } from \"../Icon\";\nimport { CodeBlock } from \"../CodeBlock\";\nimport { useDensityValue, type Density } from \"../../hooks/use-density\";\nimport { DensityValueProvider } from \"../../hooks/density-provider\";\nimport {\n UiBrain,\n UiChevronDown,\n UiRobotAi,\n UiSparkles,\n UiUserCircle,\n UiWarningTriangle,\n} from \"../../icons\";\nimport {\n getSessionAction,\n normalizeSession,\n summarizeSession,\n summarizeToolInput,\n type SessionEvent,\n type SessionInput,\n type SessionTone,\n} from \"./SessionViewer.model\";\nimport {\n collectSessionFilters,\n isEventVisible,\n type SessionCategory,\n} from \"./session-categories\";\nimport { SessionViewerMenu, type SessionThemeOverride } from \"./SessionViewerMenu\";\n\nexport type {\n SessionEntry,\n SessionEvent,\n SessionInput,\n SessionActionMeta,\n} from \"./SessionViewer.model\";\nexport type { SessionThemeOverride } from \"./SessionViewerMenu\";\n\nexport interface SessionViewerProps {\n /** A captain session: parsed `SessionEntry[]` or raw log text (JSON / JSONL). */\n session: SessionInput;\n className?: string;\n /** Expand each tool call's input/output by default. Defaults to false. */\n defaultExpanded?: boolean;\n /** Render assistant reasoning (\"thinking\") blocks. Defaults to true. */\n showThinking?: boolean;\n /** Show the summary header (model + action counts). Defaults to true. */\n showHeader?: boolean;\n /** Show the 3-dot menu (density + category/tool/source filters). Defaults to true. */\n showMenu?: boolean;\n /** Initial density override; undefined inherits the page/document density. */\n defaultDensity?: Density;\n /** Initial theme override; undefined inherits the page/document `data-theme`. */\n defaultTheme?: SessionThemeOverride;\n}\n\n// Disc colors per tone. The dark variants key off a `[data-theme=\"dark\"]`\n// ancestor (the document attribute set by ThemeProvider, or the component-level\n// override painted on this viewer's root) rather than Tailwind's `dark:` —\n// which this library compiles to `prefers-color-scheme` and so would ignore the\n// `data-theme` attribute. Written as literal class strings so Tailwind scans them.\nconst DISC_TONE: Record<SessionTone, string> = {\n sky: \"bg-sky-100 text-sky-700 [[data-theme=dark]_&]:bg-sky-500/15 [[data-theme=dark]_&]:text-sky-300\",\n amber: \"bg-amber-100 text-amber-700 [[data-theme=dark]_&]:bg-amber-500/15 [[data-theme=dark]_&]:text-amber-300\",\n violet: \"bg-violet-100 text-violet-700 [[data-theme=dark]_&]:bg-violet-500/15 [[data-theme=dark]_&]:text-violet-300\",\n emerald: \"bg-emerald-100 text-emerald-700 [[data-theme=dark]_&]:bg-emerald-500/15 [[data-theme=dark]_&]:text-emerald-300\",\n rose: \"bg-rose-100 text-rose-700 [[data-theme=dark]_&]:bg-rose-500/15 [[data-theme=dark]_&]:text-rose-300\",\n indigo: \"bg-indigo-100 text-indigo-700 [[data-theme=dark]_&]:bg-indigo-500/15 [[data-theme=dark]_&]:text-indigo-300\",\n fuchsia: \"bg-fuchsia-100 text-fuchsia-700 [[data-theme=dark]_&]:bg-fuchsia-500/15 [[data-theme=dark]_&]:text-fuchsia-300\",\n pink: \"bg-pink-100 text-pink-700 [[data-theme=dark]_&]:bg-pink-500/15 [[data-theme=dark]_&]:text-pink-300\",\n slate: \"bg-muted text-muted-foreground\",\n};\n\nfunction toggleInSet<T>(set: ReadonlySet<T>, value: T): Set<T> {\n const next = new Set(set);\n if (next.has(value)) next.delete(value);\n else next.add(value);\n return next;\n}\n\n/**\n * SessionViewer renders a recorded AI coding-agent session (the captain\n * `pkg/ai/history` JSON schema — Claude Code / Codex transcripts) as a vertical\n * action log. Each entry sits on a tone-colored icon disc from the Flanksource\n * \"Agent Action Icons\" set; user prompts/selections are right-aligned like a\n * chat. Tool calls expand to their input and response. The \"3-dot\" menu mirrors\n * `captain history` filtering — toggle density and hide categories, tools or\n * sources.\n */\nexport function SessionViewer({\n session,\n className,\n defaultExpanded = false,\n showThinking = true,\n showHeader = true,\n showMenu = true,\n defaultDensity,\n defaultTheme,\n}: SessionViewerProps) {\n const allEvents = useMemo(() => normalizeSession(session), [session]);\n\n const pageDensity = useDensityValue();\n const [densityOverride, setDensityOverride] = useState<Density | undefined>(defaultDensity);\n const [themeOverride, setThemeOverride] = useState<SessionThemeOverride | undefined>(defaultTheme);\n const [hiddenCategories, setHiddenCategories] = useState<ReadonlySet<SessionCategory>>(\n () => new Set(),\n );\n const [hiddenTools, setHiddenTools] = useState<ReadonlySet<string>>(() => new Set());\n const [hiddenSources, setHiddenSources] = useState<ReadonlySet<string>>(() => new Set());\n // Undefined = follow the `showThinking` prop; the menu toggle sets an override.\n const [showThinkingOverride, setShowThinkingOverride] = useState<boolean | undefined>(undefined);\n const effectiveShowThinking = showThinkingOverride ?? showThinking;\n\n const filters = useMemo(() => collectSessionFilters(allEvents), [allEvents]);\n const hasThinking = useMemo(() => allEvents.some((e) => e.kind === \"thinking\"), [allEvents]);\n\n const visibility = { hiddenCategories, hiddenTools, hiddenSources, showThinking: effectiveShowThinking };\n const events = allEvents.filter((event) => isEventVisible(event, visibility));\n\n if (allEvents.length === 0) {\n return (\n <div\n className={cn(\n \"rounded-md border border-dashed border-border p-density-4 text-center text-sm text-muted-foreground\",\n className,\n )}\n >\n No session activity.\n </div>\n );\n }\n\n const summary = summarizeSession(allEvents);\n const effectiveDensity = densityOverride ?? pageDensity;\n const dataAttrs: Record<string, string> = {\n ...(densityOverride ? { \"data-density\": densityOverride } : {}),\n ...(themeOverride ? { \"data-theme\": themeOverride } : {}),\n };\n\n return (\n <div className={cn(\"text-sm\", className)} {...dataAttrs}>\n <DensityValueProvider density={effectiveDensity}>\n {(showHeader || showMenu) && (\n <div className=\"mb-density-3 flex items-center justify-between gap-density-3\">\n <div className=\"flex flex-wrap items-center gap-x-density-3 gap-y-1 text-xs text-muted-foreground\">\n {showHeader && summary.model && (\n <span className=\"inline-flex items-center gap-1 font-medium text-foreground\">\n <Icon icon={UiRobotAi} className=\"size-3.5\" />\n {summary.model}\n </span>\n )}\n {showHeader && <span>{summary.toolCount} actions</span>}\n {showHeader && <span>{summary.messageCount} messages</span>}\n </div>\n {showMenu && (\n <SessionViewerMenu\n density={densityOverride}\n onDensityChange={setDensityOverride}\n theme={themeOverride}\n onThemeChange={setThemeOverride}\n filters={filters}\n hiddenCategories={hiddenCategories}\n hiddenTools={hiddenTools}\n hiddenSources={hiddenSources}\n onToggleCategory={(category) =>\n setHiddenCategories((set) => toggleInSet(set, category))\n }\n onToggleTool={(tool) => setHiddenTools((set) => toggleInSet(set, tool))}\n onToggleSource={(source) => setHiddenSources((set) => toggleInSet(set, source))}\n showThinking={effectiveShowThinking}\n onToggleThinking={() => setShowThinkingOverride(!effectiveShowThinking)}\n hasThinking={hasThinking}\n />\n )}\n </div>\n )}\n\n {events.length === 0 ? (\n <div className=\"rounded-md border border-dashed border-border p-density-4 text-center text-sm text-muted-foreground\">\n All actions are hidden by the active filters.\n </div>\n ) : (\n <ol className=\"relative\">\n {events.map((event, index) => (\n <SessionRow\n key={event.id}\n event={event}\n last={index === events.length - 1}\n defaultExpanded={defaultExpanded}\n />\n ))}\n </ol>\n )}\n </DensityValueProvider>\n </div>\n );\n}\n\nfunction SessionRow({\n event,\n last,\n defaultExpanded,\n}: {\n event: SessionEvent;\n last: boolean;\n defaultExpanded: boolean;\n}) {\n if (event.kind === \"user\") return <UserRow event={event} />;\n\n const visual = eventVisual(event);\n return (\n <li data-event-kind={event.kind} className=\"relative flex gap-density-3 pb-density-4 last:pb-0\">\n {!last && <span aria-hidden className=\"absolute bottom-0 left-[10px] top-[22px] w-px bg-border\" />}\n <span\n className={cn(\n \"relative z-[1] flex h-[21px] w-[21px] shrink-0 items-center justify-center rounded-full\",\n DISC_TONE[visual.tone],\n )}\n >\n <Icon icon={visual.icon} className=\"h-3 w-3\" />\n </span>\n <div className=\"min-w-0 flex-1 pt-px\">\n <EventBody event={event} visual={visual} defaultExpanded={defaultExpanded} />\n </div>\n </li>\n );\n}\n\n// User prompts and selections sit on the right, like a chat composer turn.\nfunction UserRow({ event }: { event: SessionEvent }) {\n return (\n <li data-event-kind=\"user\" className=\"relative flex justify-end pb-density-4 last:pb-0\">\n <div className=\"flex max-w-[85%] items-start gap-density-3\">\n <div className=\"min-w-0\">\n <div className=\"mb-0.5 text-right text-xs font-medium text-muted-foreground\">You</div>\n <div className=\"whitespace-pre-wrap break-words rounded-lg bg-accent px-density-3 py-density-2 text-right leading-relaxed text-accent-foreground\">\n {event.text}\n </div>\n </div>\n <span\n className={cn(\n \"relative z-[1] flex h-[21px] w-[21px] shrink-0 items-center justify-center rounded-full\",\n DISC_TONE.slate,\n )}\n >\n <Icon icon={UiUserCircle} className=\"h-3 w-3\" />\n </span>\n </div>\n </li>\n );\n}\n\ninterface EventVisual {\n icon: StaticIconComponent;\n tone: SessionTone;\n label: string;\n}\n\nfunction eventVisual(event: SessionEvent): EventVisual {\n switch (event.kind) {\n case \"tool\": {\n const action = getSessionAction(event.tool ?? \"\");\n return { icon: action.icon, tone: action.tone, label: action.label };\n }\n case \"user\":\n return { icon: UiUserCircle, tone: \"slate\", label: \"User\" };\n case \"assistant\":\n return { icon: UiSparkles, tone: \"indigo\", label: \"Assistant\" };\n case \"thinking\":\n return { icon: UiBrain, tone: \"slate\", label: \"Thinking\" };\n case \"error\":\n return { icon: UiWarningTriangle, tone: \"rose\", label: \"Error\" };\n }\n}\n\nfunction EventBody({\n event,\n visual,\n defaultExpanded,\n}: {\n event: SessionEvent;\n visual: EventVisual;\n defaultExpanded: boolean;\n}) {\n if (event.kind === \"tool\") return <ToolBody event={event} label={visual.label} defaultExpanded={defaultExpanded} />;\n if (event.kind === \"thinking\") return <ThinkingBody event={event} />;\n if (event.kind === \"error\") return <ErrorBody event={event} />;\n return <MessageBody event={event} label={visual.label} />;\n}\n\nfunction ToolBody({\n event,\n label,\n defaultExpanded,\n}: {\n event: SessionEvent;\n label: string;\n defaultExpanded: boolean;\n}) {\n const summary = summarizeToolInput(event.tool ?? \"\", event.toolInput);\n const hasDetail = event.toolInput !== undefined || event.toolResponse !== undefined;\n const [open, setOpen] = useState(defaultExpanded);\n\n const header = (\n <>\n <span className=\"font-medium text-foreground\">{label}</span>\n {summary && <span className=\"truncate font-mono text-xs text-muted-foreground\">{summary}</span>}\n </>\n );\n\n return (\n <div className=\"not-prose\">\n {hasDetail ? (\n <button\n type=\"button\"\n aria-expanded={open}\n onClick={() => setOpen((value) => !value)}\n className=\"flex w-full items-center gap-1.5 text-left hover:text-foreground\"\n >\n {header}\n <Icon\n icon={UiChevronDown}\n className={cn(\"ml-auto size-3 shrink-0 text-muted-foreground transition-transform\", open && \"rotate-180\")}\n />\n </button>\n ) : (\n <div className=\"flex items-center gap-1.5\">{header}</div>\n )}\n\n {(event.model || event.source) && (\n <div className=\"mt-0.5 flex gap-density-2 text-[11px] text-muted-foreground\">\n {event.source && <span>{event.source}</span>}\n {event.model && <span>{event.model}</span>}\n {event.reasoningEffort && <span>effort: {event.reasoningEffort}</span>}\n </div>\n )}\n\n {open && hasDetail && (\n <div className=\"mt-1.5 space-y-1.5\">\n {event.toolInput !== undefined && (\n <DetailBlock language=\"json\" source={JSON.stringify(event.toolInput, null, 2)} />\n )}\n {event.toolResponse !== undefined && <ResponseBlock response={event.toolResponse} />}\n </div>\n )}\n </div>\n );\n}\n\nfunction ResponseBlock({ response }: { response: string }) {\n const trimmed = response.trim();\n const isJson = trimmed.startsWith(\"{\") || trimmed.startsWith(\"[\");\n return <DetailBlock language={isJson ? \"json\" : \"text\"} source={response} />;\n}\n\nfunction DetailBlock({ language, source }: { language: string; source: string }): ReactNode {\n return (\n <div className=\"overflow-x-auto text-xs\">\n <CodeBlock language={language} source={source} />\n </div>\n );\n}\n\nfunction MessageBody({ event, label }: { event: SessionEvent; label: string }) {\n return (\n <div>\n <div className=\"mb-0.5 text-xs font-medium text-muted-foreground\">{label}</div>\n <div className=\"whitespace-pre-wrap break-words leading-relaxed text-foreground\">{event.text}</div>\n </div>\n );\n}\n\nfunction ThinkingBody({ event }: { event: SessionEvent }) {\n const [open, setOpen] = useState(false);\n return (\n <div className=\"not-prose\">\n <button\n type=\"button\"\n aria-expanded={open}\n onClick={() => setOpen((value) => !value)}\n className=\"flex items-center gap-1.5 text-xs text-muted-foreground hover:text-foreground\"\n >\n <span className=\"italic\">Reasoning</span>\n <Icon icon={UiChevronDown} className={cn(\"size-3 transition-transform\", open && \"rotate-180\")} />\n </button>\n {open && (\n <div className=\"mt-1 whitespace-pre-wrap break-words border-l-2 border-border pl-density-3 text-xs italic leading-relaxed text-muted-foreground\">\n {event.text}\n </div>\n )}\n </div>\n );\n}\n\nfunction ErrorBody({ event }: { event: SessionEvent }) {\n return (\n <div className=\"rounded-md border border-rose-200 bg-rose-50 px-density-3 py-1.5 text-xs text-rose-700 [[data-theme=dark]_&]:border-rose-500/30 [[data-theme=dark]_&]:bg-rose-500/10 [[data-theme=dark]_&]:text-rose-300\">\n {event.text}\n </div>\n );\n}\n"],"names":["useMemo","normalizeSession","useDensityValue","useState","collectSessionFilters","isEventVisible","jsx","cn","summarizeSession","jsxs","DensityValueProvider","Icon","UiRobotAi","SessionViewerMenu","UiUserCircle","getSessionAction","UiSparkles","UiBrain","UiWarningTriangle","summarizeToolInput","Fragment","UiChevronDown","CodeBlock"],"mappings":"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"}
@@ -0,0 +1,33 @@
1
+ import { Density } from '../../hooks/use-density';
2
+ import { SessionInput } from './SessionViewer.model';
3
+ import { SessionThemeOverride } from './SessionViewerMenu';
4
+ export type { SessionEntry, SessionEvent, SessionInput, SessionActionMeta, } from './SessionViewer.model';
5
+ export type { SessionThemeOverride } from './SessionViewerMenu';
6
+ export interface SessionViewerProps {
7
+ /** A captain session: parsed `SessionEntry[]` or raw log text (JSON / JSONL). */
8
+ session: SessionInput;
9
+ className?: string;
10
+ /** Expand each tool call's input/output by default. Defaults to false. */
11
+ defaultExpanded?: boolean;
12
+ /** Render assistant reasoning ("thinking") blocks. Defaults to true. */
13
+ showThinking?: boolean;
14
+ /** Show the summary header (model + action counts). Defaults to true. */
15
+ showHeader?: boolean;
16
+ /** Show the 3-dot menu (density + category/tool/source filters). Defaults to true. */
17
+ showMenu?: boolean;
18
+ /** Initial density override; undefined inherits the page/document density. */
19
+ defaultDensity?: Density;
20
+ /** Initial theme override; undefined inherits the page/document `data-theme`. */
21
+ defaultTheme?: SessionThemeOverride;
22
+ }
23
+ /**
24
+ * SessionViewer renders a recorded AI coding-agent session (the captain
25
+ * `pkg/ai/history` JSON schema — Claude Code / Codex transcripts) as a vertical
26
+ * action log. Each entry sits on a tone-colored icon disc from the Flanksource
27
+ * "Agent Action Icons" set; user prompts/selections are right-aligned like a
28
+ * chat. Tool calls expand to their input and response. The "3-dot" menu mirrors
29
+ * `captain history` filtering — toggle density and hide categories, tools or
30
+ * sources.
31
+ */
32
+ export declare function SessionViewer({ session, className, defaultExpanded, showThinking, showHeader, showMenu, defaultDensity, defaultTheme, }: SessionViewerProps): import("react/jsx-runtime").JSX.Element;
33
+ //# sourceMappingURL=SessionViewer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SessionViewer.d.ts","sourceRoot":"","sources":["../../../src/data/ai/SessionViewer.tsx"],"names":[],"mappings":"AAIA,OAAO,EAAmB,KAAK,OAAO,EAAE,MAAM,yBAAyB,CAAC;AAUxE,OAAO,EAML,KAAK,YAAY,EAElB,MAAM,uBAAuB,CAAC;AAM/B,OAAO,EAAqB,KAAK,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAEnF,YAAY,EACV,YAAY,EACZ,YAAY,EACZ,YAAY,EACZ,iBAAiB,GAClB,MAAM,uBAAuB,CAAC;AAC/B,YAAY,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAEhE,MAAM,WAAW,kBAAkB;IACjC,iFAAiF;IACjF,OAAO,EAAE,YAAY,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,0EAA0E;IAC1E,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,wEAAwE;IACxE,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,yEAAyE;IACzE,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,sFAAsF;IACtF,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,8EAA8E;IAC9E,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,iFAAiF;IACjF,YAAY,CAAC,EAAE,oBAAoB,CAAC;CACrC;AA0BD;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,EAC5B,OAAO,EACP,SAAS,EACT,eAAuB,EACvB,YAAmB,EACnB,UAAiB,EACjB,QAAe,EACf,cAAc,EACd,YAAY,GACb,EAAE,kBAAkB,2CAkGpB"}
@@ -0,0 +1,4 @@
1
+ import { SessionEntry } from './SessionViewer.model';
2
+ export declare const SAMPLE_SESSION: SessionEntry[];
3
+ export declare const SAMPLE_SESSION_JSONL: string;
4
+ //# sourceMappingURL=SessionViewer.fixtures.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SessionViewer.fixtures.d.ts","sourceRoot":"","sources":["../../../src/data/ai/SessionViewer.fixtures.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAK1D,eAAO,MAAM,cAAc,EAAE,YAAY,EAyFxC,CAAC;AAIF,eAAO,MAAM,oBAAoB,QAIrB,CAAC"}
@@ -0,0 +1,275 @@
1
+ import { jsx, jsxs, Fragment } from "react/jsx-runtime";
2
+ import { useMemo, useState } from "react";
3
+ import { cn } from "../../lib/utils.js";
4
+ import { Icon } from "../Icon.js";
5
+ import { CodeBlock } from "../CodeBlock.js";
6
+ import { useDensityValue } from "../../hooks/use-density.js";
7
+ import { DensityValueProvider } from "../../hooks/density-provider.js";
8
+ import { normalizeSession, summarizeSession, getSessionAction, summarizeToolInput } from "./SessionViewer.model.js";
9
+ import { collectSessionFilters, isEventVisible } from "./session-categories.js";
10
+ import { SessionViewerMenu } from "./SessionViewerMenu.js";
11
+ import { UiRobotAi } from "../../icons/components/UiRobotAi.js";
12
+ import { UiUserCircle } from "../../icons/components/UiUserCircle.js";
13
+ import { UiWarningTriangle } from "../../icons/components/UiWarningTriangle.js";
14
+ import { UiBrain } from "../../icons/components/UiBrain.js";
15
+ import { UiSparkles } from "../../icons/components/UiSparkles.js";
16
+ import { UiChevronDown } from "../../icons/components/UiChevronDown.js";
17
+ const DISC_TONE = {
18
+ sky: "bg-sky-100 text-sky-700 [[data-theme=dark]_&]:bg-sky-500/15 [[data-theme=dark]_&]:text-sky-300",
19
+ amber: "bg-amber-100 text-amber-700 [[data-theme=dark]_&]:bg-amber-500/15 [[data-theme=dark]_&]:text-amber-300",
20
+ violet: "bg-violet-100 text-violet-700 [[data-theme=dark]_&]:bg-violet-500/15 [[data-theme=dark]_&]:text-violet-300",
21
+ emerald: "bg-emerald-100 text-emerald-700 [[data-theme=dark]_&]:bg-emerald-500/15 [[data-theme=dark]_&]:text-emerald-300",
22
+ rose: "bg-rose-100 text-rose-700 [[data-theme=dark]_&]:bg-rose-500/15 [[data-theme=dark]_&]:text-rose-300",
23
+ indigo: "bg-indigo-100 text-indigo-700 [[data-theme=dark]_&]:bg-indigo-500/15 [[data-theme=dark]_&]:text-indigo-300",
24
+ fuchsia: "bg-fuchsia-100 text-fuchsia-700 [[data-theme=dark]_&]:bg-fuchsia-500/15 [[data-theme=dark]_&]:text-fuchsia-300",
25
+ pink: "bg-pink-100 text-pink-700 [[data-theme=dark]_&]:bg-pink-500/15 [[data-theme=dark]_&]:text-pink-300",
26
+ slate: "bg-muted text-muted-foreground"
27
+ };
28
+ function toggleInSet(set, value) {
29
+ const next = new Set(set);
30
+ if (next.has(value)) next.delete(value);
31
+ else next.add(value);
32
+ return next;
33
+ }
34
+ function SessionViewer({
35
+ session,
36
+ className,
37
+ defaultExpanded = false,
38
+ showThinking = true,
39
+ showHeader = true,
40
+ showMenu = true,
41
+ defaultDensity,
42
+ defaultTheme
43
+ }) {
44
+ const allEvents = useMemo(() => normalizeSession(session), [session]);
45
+ const pageDensity = useDensityValue();
46
+ const [densityOverride, setDensityOverride] = useState(defaultDensity);
47
+ const [themeOverride, setThemeOverride] = useState(defaultTheme);
48
+ const [hiddenCategories, setHiddenCategories] = useState(
49
+ () => /* @__PURE__ */ new Set()
50
+ );
51
+ const [hiddenTools, setHiddenTools] = useState(() => /* @__PURE__ */ new Set());
52
+ const [hiddenSources, setHiddenSources] = useState(() => /* @__PURE__ */ new Set());
53
+ const [showThinkingOverride, setShowThinkingOverride] = useState(void 0);
54
+ const effectiveShowThinking = showThinkingOverride ?? showThinking;
55
+ const filters = useMemo(() => collectSessionFilters(allEvents), [allEvents]);
56
+ const hasThinking = useMemo(() => allEvents.some((e) => e.kind === "thinking"), [allEvents]);
57
+ const visibility = { hiddenCategories, hiddenTools, hiddenSources, showThinking: effectiveShowThinking };
58
+ const events = allEvents.filter((event) => isEventVisible(event, visibility));
59
+ if (allEvents.length === 0) {
60
+ return /* @__PURE__ */ jsx(
61
+ "div",
62
+ {
63
+ className: cn(
64
+ "rounded-md border border-dashed border-border p-density-4 text-center text-sm text-muted-foreground",
65
+ className
66
+ ),
67
+ children: "No session activity."
68
+ }
69
+ );
70
+ }
71
+ const summary = summarizeSession(allEvents);
72
+ const effectiveDensity = densityOverride ?? pageDensity;
73
+ const dataAttrs = {
74
+ ...densityOverride ? { "data-density": densityOverride } : {},
75
+ ...themeOverride ? { "data-theme": themeOverride } : {}
76
+ };
77
+ return /* @__PURE__ */ jsx("div", { className: cn("text-sm", className), ...dataAttrs, children: /* @__PURE__ */ jsxs(DensityValueProvider, { density: effectiveDensity, children: [
78
+ (showHeader || showMenu) && /* @__PURE__ */ jsxs("div", { className: "mb-density-3 flex items-center justify-between gap-density-3", children: [
79
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-x-density-3 gap-y-1 text-xs text-muted-foreground", children: [
80
+ showHeader && summary.model && /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1 font-medium text-foreground", children: [
81
+ /* @__PURE__ */ jsx(Icon, { icon: UiRobotAi, className: "size-3.5" }),
82
+ summary.model
83
+ ] }),
84
+ showHeader && /* @__PURE__ */ jsxs("span", { children: [
85
+ summary.toolCount,
86
+ " actions"
87
+ ] }),
88
+ showHeader && /* @__PURE__ */ jsxs("span", { children: [
89
+ summary.messageCount,
90
+ " messages"
91
+ ] })
92
+ ] }),
93
+ showMenu && /* @__PURE__ */ jsx(
94
+ SessionViewerMenu,
95
+ {
96
+ density: densityOverride,
97
+ onDensityChange: setDensityOverride,
98
+ theme: themeOverride,
99
+ onThemeChange: setThemeOverride,
100
+ filters,
101
+ hiddenCategories,
102
+ hiddenTools,
103
+ hiddenSources,
104
+ onToggleCategory: (category) => setHiddenCategories((set) => toggleInSet(set, category)),
105
+ onToggleTool: (tool) => setHiddenTools((set) => toggleInSet(set, tool)),
106
+ onToggleSource: (source) => setHiddenSources((set) => toggleInSet(set, source)),
107
+ showThinking: effectiveShowThinking,
108
+ onToggleThinking: () => setShowThinkingOverride(!effectiveShowThinking),
109
+ hasThinking
110
+ }
111
+ )
112
+ ] }),
113
+ events.length === 0 ? /* @__PURE__ */ jsx("div", { className: "rounded-md border border-dashed border-border p-density-4 text-center text-sm text-muted-foreground", children: "All actions are hidden by the active filters." }) : /* @__PURE__ */ jsx("ol", { className: "relative", children: events.map((event, index) => /* @__PURE__ */ jsx(
114
+ SessionRow,
115
+ {
116
+ event,
117
+ last: index === events.length - 1,
118
+ defaultExpanded
119
+ },
120
+ event.id
121
+ )) })
122
+ ] }) });
123
+ }
124
+ function SessionRow({
125
+ event,
126
+ last,
127
+ defaultExpanded
128
+ }) {
129
+ if (event.kind === "user") return /* @__PURE__ */ jsx(UserRow, { event });
130
+ const visual = eventVisual(event);
131
+ return /* @__PURE__ */ jsxs("li", { "data-event-kind": event.kind, className: "relative flex gap-density-3 pb-density-4 last:pb-0", children: [
132
+ !last && /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: "absolute bottom-0 left-[10px] top-[22px] w-px bg-border" }),
133
+ /* @__PURE__ */ jsx(
134
+ "span",
135
+ {
136
+ className: cn(
137
+ "relative z-[1] flex h-[21px] w-[21px] shrink-0 items-center justify-center rounded-full",
138
+ DISC_TONE[visual.tone]
139
+ ),
140
+ children: /* @__PURE__ */ jsx(Icon, { icon: visual.icon, className: "h-3 w-3" })
141
+ }
142
+ ),
143
+ /* @__PURE__ */ jsx("div", { className: "min-w-0 flex-1 pt-px", children: /* @__PURE__ */ jsx(EventBody, { event, visual, defaultExpanded }) })
144
+ ] });
145
+ }
146
+ function UserRow({ event }) {
147
+ return /* @__PURE__ */ jsx("li", { "data-event-kind": "user", className: "relative flex justify-end pb-density-4 last:pb-0", children: /* @__PURE__ */ jsxs("div", { className: "flex max-w-[85%] items-start gap-density-3", children: [
148
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
149
+ /* @__PURE__ */ jsx("div", { className: "mb-0.5 text-right text-xs font-medium text-muted-foreground", children: "You" }),
150
+ /* @__PURE__ */ jsx("div", { className: "whitespace-pre-wrap break-words rounded-lg bg-accent px-density-3 py-density-2 text-right leading-relaxed text-accent-foreground", children: event.text })
151
+ ] }),
152
+ /* @__PURE__ */ jsx(
153
+ "span",
154
+ {
155
+ className: cn(
156
+ "relative z-[1] flex h-[21px] w-[21px] shrink-0 items-center justify-center rounded-full",
157
+ DISC_TONE.slate
158
+ ),
159
+ children: /* @__PURE__ */ jsx(Icon, { icon: UiUserCircle, className: "h-3 w-3" })
160
+ }
161
+ )
162
+ ] }) });
163
+ }
164
+ function eventVisual(event) {
165
+ switch (event.kind) {
166
+ case "tool": {
167
+ const action = getSessionAction(event.tool ?? "");
168
+ return { icon: action.icon, tone: action.tone, label: action.label };
169
+ }
170
+ case "user":
171
+ return { icon: UiUserCircle, tone: "slate", label: "User" };
172
+ case "assistant":
173
+ return { icon: UiSparkles, tone: "indigo", label: "Assistant" };
174
+ case "thinking":
175
+ return { icon: UiBrain, tone: "slate", label: "Thinking" };
176
+ case "error":
177
+ return { icon: UiWarningTriangle, tone: "rose", label: "Error" };
178
+ }
179
+ }
180
+ function EventBody({
181
+ event,
182
+ visual,
183
+ defaultExpanded
184
+ }) {
185
+ if (event.kind === "tool") return /* @__PURE__ */ jsx(ToolBody, { event, label: visual.label, defaultExpanded });
186
+ if (event.kind === "thinking") return /* @__PURE__ */ jsx(ThinkingBody, { event });
187
+ if (event.kind === "error") return /* @__PURE__ */ jsx(ErrorBody, { event });
188
+ return /* @__PURE__ */ jsx(MessageBody, { event, label: visual.label });
189
+ }
190
+ function ToolBody({
191
+ event,
192
+ label,
193
+ defaultExpanded
194
+ }) {
195
+ const summary = summarizeToolInput(event.tool ?? "", event.toolInput);
196
+ const hasDetail = event.toolInput !== void 0 || event.toolResponse !== void 0;
197
+ const [open, setOpen] = useState(defaultExpanded);
198
+ const header = /* @__PURE__ */ jsxs(Fragment, { children: [
199
+ /* @__PURE__ */ jsx("span", { className: "font-medium text-foreground", children: label }),
200
+ summary && /* @__PURE__ */ jsx("span", { className: "truncate font-mono text-xs text-muted-foreground", children: summary })
201
+ ] });
202
+ return /* @__PURE__ */ jsxs("div", { className: "not-prose", children: [
203
+ hasDetail ? /* @__PURE__ */ jsxs(
204
+ "button",
205
+ {
206
+ type: "button",
207
+ "aria-expanded": open,
208
+ onClick: () => setOpen((value) => !value),
209
+ className: "flex w-full items-center gap-1.5 text-left hover:text-foreground",
210
+ children: [
211
+ header,
212
+ /* @__PURE__ */ jsx(
213
+ Icon,
214
+ {
215
+ icon: UiChevronDown,
216
+ className: cn("ml-auto size-3 shrink-0 text-muted-foreground transition-transform", open && "rotate-180")
217
+ }
218
+ )
219
+ ]
220
+ }
221
+ ) : /* @__PURE__ */ jsx("div", { className: "flex items-center gap-1.5", children: header }),
222
+ (event.model || event.source) && /* @__PURE__ */ jsxs("div", { className: "mt-0.5 flex gap-density-2 text-[11px] text-muted-foreground", children: [
223
+ event.source && /* @__PURE__ */ jsx("span", { children: event.source }),
224
+ event.model && /* @__PURE__ */ jsx("span", { children: event.model }),
225
+ event.reasoningEffort && /* @__PURE__ */ jsxs("span", { children: [
226
+ "effort: ",
227
+ event.reasoningEffort
228
+ ] })
229
+ ] }),
230
+ open && hasDetail && /* @__PURE__ */ jsxs("div", { className: "mt-1.5 space-y-1.5", children: [
231
+ event.toolInput !== void 0 && /* @__PURE__ */ jsx(DetailBlock, { language: "json", source: JSON.stringify(event.toolInput, null, 2) }),
232
+ event.toolResponse !== void 0 && /* @__PURE__ */ jsx(ResponseBlock, { response: event.toolResponse })
233
+ ] })
234
+ ] });
235
+ }
236
+ function ResponseBlock({ response }) {
237
+ const trimmed = response.trim();
238
+ const isJson = trimmed.startsWith("{") || trimmed.startsWith("[");
239
+ return /* @__PURE__ */ jsx(DetailBlock, { language: isJson ? "json" : "text", source: response });
240
+ }
241
+ function DetailBlock({ language, source }) {
242
+ return /* @__PURE__ */ jsx("div", { className: "overflow-x-auto text-xs", children: /* @__PURE__ */ jsx(CodeBlock, { language, source }) });
243
+ }
244
+ function MessageBody({ event, label }) {
245
+ return /* @__PURE__ */ jsxs("div", { children: [
246
+ /* @__PURE__ */ jsx("div", { className: "mb-0.5 text-xs font-medium text-muted-foreground", children: label }),
247
+ /* @__PURE__ */ jsx("div", { className: "whitespace-pre-wrap break-words leading-relaxed text-foreground", children: event.text })
248
+ ] });
249
+ }
250
+ function ThinkingBody({ event }) {
251
+ const [open, setOpen] = useState(false);
252
+ return /* @__PURE__ */ jsxs("div", { className: "not-prose", children: [
253
+ /* @__PURE__ */ jsxs(
254
+ "button",
255
+ {
256
+ type: "button",
257
+ "aria-expanded": open,
258
+ onClick: () => setOpen((value) => !value),
259
+ className: "flex items-center gap-1.5 text-xs text-muted-foreground hover:text-foreground",
260
+ children: [
261
+ /* @__PURE__ */ jsx("span", { className: "italic", children: "Reasoning" }),
262
+ /* @__PURE__ */ jsx(Icon, { icon: UiChevronDown, className: cn("size-3 transition-transform", open && "rotate-180") })
263
+ ]
264
+ }
265
+ ),
266
+ open && /* @__PURE__ */ jsx("div", { className: "mt-1 whitespace-pre-wrap break-words border-l-2 border-border pl-density-3 text-xs italic leading-relaxed text-muted-foreground", children: event.text })
267
+ ] });
268
+ }
269
+ function ErrorBody({ event }) {
270
+ return /* @__PURE__ */ jsx("div", { className: "rounded-md border border-rose-200 bg-rose-50 px-density-3 py-1.5 text-xs text-rose-700 [[data-theme=dark]_&]:border-rose-500/30 [[data-theme=dark]_&]:bg-rose-500/10 [[data-theme=dark]_&]:text-rose-300", children: event.text });
271
+ }
272
+ export {
273
+ SessionViewer
274
+ };
275
+ //# sourceMappingURL=SessionViewer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SessionViewer.js","sources":["../../../src/data/ai/SessionViewer.tsx"],"sourcesContent":["import { useMemo, useState, type ReactNode } from \"react\";\nimport { cn } from \"../../lib/utils\";\nimport { Icon, type StaticIconComponent } from \"../Icon\";\nimport { CodeBlock } from \"../CodeBlock\";\nimport { useDensityValue, type Density } from \"../../hooks/use-density\";\nimport { DensityValueProvider } from \"../../hooks/density-provider\";\nimport {\n UiBrain,\n UiChevronDown,\n UiRobotAi,\n UiSparkles,\n UiUserCircle,\n UiWarningTriangle,\n} from \"../../icons\";\nimport {\n getSessionAction,\n normalizeSession,\n summarizeSession,\n summarizeToolInput,\n type SessionEvent,\n type SessionInput,\n type SessionTone,\n} from \"./SessionViewer.model\";\nimport {\n collectSessionFilters,\n isEventVisible,\n type SessionCategory,\n} from \"./session-categories\";\nimport { SessionViewerMenu, type SessionThemeOverride } from \"./SessionViewerMenu\";\n\nexport type {\n SessionEntry,\n SessionEvent,\n SessionInput,\n SessionActionMeta,\n} from \"./SessionViewer.model\";\nexport type { SessionThemeOverride } from \"./SessionViewerMenu\";\n\nexport interface SessionViewerProps {\n /** A captain session: parsed `SessionEntry[]` or raw log text (JSON / JSONL). */\n session: SessionInput;\n className?: string;\n /** Expand each tool call's input/output by default. Defaults to false. */\n defaultExpanded?: boolean;\n /** Render assistant reasoning (\"thinking\") blocks. Defaults to true. */\n showThinking?: boolean;\n /** Show the summary header (model + action counts). Defaults to true. */\n showHeader?: boolean;\n /** Show the 3-dot menu (density + category/tool/source filters). Defaults to true. */\n showMenu?: boolean;\n /** Initial density override; undefined inherits the page/document density. */\n defaultDensity?: Density;\n /** Initial theme override; undefined inherits the page/document `data-theme`. */\n defaultTheme?: SessionThemeOverride;\n}\n\n// Disc colors per tone. The dark variants key off a `[data-theme=\"dark\"]`\n// ancestor (the document attribute set by ThemeProvider, or the component-level\n// override painted on this viewer's root) rather than Tailwind's `dark:` —\n// which this library compiles to `prefers-color-scheme` and so would ignore the\n// `data-theme` attribute. Written as literal class strings so Tailwind scans them.\nconst DISC_TONE: Record<SessionTone, string> = {\n sky: \"bg-sky-100 text-sky-700 [[data-theme=dark]_&]:bg-sky-500/15 [[data-theme=dark]_&]:text-sky-300\",\n amber: \"bg-amber-100 text-amber-700 [[data-theme=dark]_&]:bg-amber-500/15 [[data-theme=dark]_&]:text-amber-300\",\n violet: \"bg-violet-100 text-violet-700 [[data-theme=dark]_&]:bg-violet-500/15 [[data-theme=dark]_&]:text-violet-300\",\n emerald: \"bg-emerald-100 text-emerald-700 [[data-theme=dark]_&]:bg-emerald-500/15 [[data-theme=dark]_&]:text-emerald-300\",\n rose: \"bg-rose-100 text-rose-700 [[data-theme=dark]_&]:bg-rose-500/15 [[data-theme=dark]_&]:text-rose-300\",\n indigo: \"bg-indigo-100 text-indigo-700 [[data-theme=dark]_&]:bg-indigo-500/15 [[data-theme=dark]_&]:text-indigo-300\",\n fuchsia: \"bg-fuchsia-100 text-fuchsia-700 [[data-theme=dark]_&]:bg-fuchsia-500/15 [[data-theme=dark]_&]:text-fuchsia-300\",\n pink: \"bg-pink-100 text-pink-700 [[data-theme=dark]_&]:bg-pink-500/15 [[data-theme=dark]_&]:text-pink-300\",\n slate: \"bg-muted text-muted-foreground\",\n};\n\nfunction toggleInSet<T>(set: ReadonlySet<T>, value: T): Set<T> {\n const next = new Set(set);\n if (next.has(value)) next.delete(value);\n else next.add(value);\n return next;\n}\n\n/**\n * SessionViewer renders a recorded AI coding-agent session (the captain\n * `pkg/ai/history` JSON schema — Claude Code / Codex transcripts) as a vertical\n * action log. Each entry sits on a tone-colored icon disc from the Flanksource\n * \"Agent Action Icons\" set; user prompts/selections are right-aligned like a\n * chat. Tool calls expand to their input and response. The \"3-dot\" menu mirrors\n * `captain history` filtering — toggle density and hide categories, tools or\n * sources.\n */\nexport function SessionViewer({\n session,\n className,\n defaultExpanded = false,\n showThinking = true,\n showHeader = true,\n showMenu = true,\n defaultDensity,\n defaultTheme,\n}: SessionViewerProps) {\n const allEvents = useMemo(() => normalizeSession(session), [session]);\n\n const pageDensity = useDensityValue();\n const [densityOverride, setDensityOverride] = useState<Density | undefined>(defaultDensity);\n const [themeOverride, setThemeOverride] = useState<SessionThemeOverride | undefined>(defaultTheme);\n const [hiddenCategories, setHiddenCategories] = useState<ReadonlySet<SessionCategory>>(\n () => new Set(),\n );\n const [hiddenTools, setHiddenTools] = useState<ReadonlySet<string>>(() => new Set());\n const [hiddenSources, setHiddenSources] = useState<ReadonlySet<string>>(() => new Set());\n // Undefined = follow the `showThinking` prop; the menu toggle sets an override.\n const [showThinkingOverride, setShowThinkingOverride] = useState<boolean | undefined>(undefined);\n const effectiveShowThinking = showThinkingOverride ?? showThinking;\n\n const filters = useMemo(() => collectSessionFilters(allEvents), [allEvents]);\n const hasThinking = useMemo(() => allEvents.some((e) => e.kind === \"thinking\"), [allEvents]);\n\n const visibility = { hiddenCategories, hiddenTools, hiddenSources, showThinking: effectiveShowThinking };\n const events = allEvents.filter((event) => isEventVisible(event, visibility));\n\n if (allEvents.length === 0) {\n return (\n <div\n className={cn(\n \"rounded-md border border-dashed border-border p-density-4 text-center text-sm text-muted-foreground\",\n className,\n )}\n >\n No session activity.\n </div>\n );\n }\n\n const summary = summarizeSession(allEvents);\n const effectiveDensity = densityOverride ?? pageDensity;\n const dataAttrs: Record<string, string> = {\n ...(densityOverride ? { \"data-density\": densityOverride } : {}),\n ...(themeOverride ? { \"data-theme\": themeOverride } : {}),\n };\n\n return (\n <div className={cn(\"text-sm\", className)} {...dataAttrs}>\n <DensityValueProvider density={effectiveDensity}>\n {(showHeader || showMenu) && (\n <div className=\"mb-density-3 flex items-center justify-between gap-density-3\">\n <div className=\"flex flex-wrap items-center gap-x-density-3 gap-y-1 text-xs text-muted-foreground\">\n {showHeader && summary.model && (\n <span className=\"inline-flex items-center gap-1 font-medium text-foreground\">\n <Icon icon={UiRobotAi} className=\"size-3.5\" />\n {summary.model}\n </span>\n )}\n {showHeader && <span>{summary.toolCount} actions</span>}\n {showHeader && <span>{summary.messageCount} messages</span>}\n </div>\n {showMenu && (\n <SessionViewerMenu\n density={densityOverride}\n onDensityChange={setDensityOverride}\n theme={themeOverride}\n onThemeChange={setThemeOverride}\n filters={filters}\n hiddenCategories={hiddenCategories}\n hiddenTools={hiddenTools}\n hiddenSources={hiddenSources}\n onToggleCategory={(category) =>\n setHiddenCategories((set) => toggleInSet(set, category))\n }\n onToggleTool={(tool) => setHiddenTools((set) => toggleInSet(set, tool))}\n onToggleSource={(source) => setHiddenSources((set) => toggleInSet(set, source))}\n showThinking={effectiveShowThinking}\n onToggleThinking={() => setShowThinkingOverride(!effectiveShowThinking)}\n hasThinking={hasThinking}\n />\n )}\n </div>\n )}\n\n {events.length === 0 ? (\n <div className=\"rounded-md border border-dashed border-border p-density-4 text-center text-sm text-muted-foreground\">\n All actions are hidden by the active filters.\n </div>\n ) : (\n <ol className=\"relative\">\n {events.map((event, index) => (\n <SessionRow\n key={event.id}\n event={event}\n last={index === events.length - 1}\n defaultExpanded={defaultExpanded}\n />\n ))}\n </ol>\n )}\n </DensityValueProvider>\n </div>\n );\n}\n\nfunction SessionRow({\n event,\n last,\n defaultExpanded,\n}: {\n event: SessionEvent;\n last: boolean;\n defaultExpanded: boolean;\n}) {\n if (event.kind === \"user\") return <UserRow event={event} />;\n\n const visual = eventVisual(event);\n return (\n <li data-event-kind={event.kind} className=\"relative flex gap-density-3 pb-density-4 last:pb-0\">\n {!last && <span aria-hidden className=\"absolute bottom-0 left-[10px] top-[22px] w-px bg-border\" />}\n <span\n className={cn(\n \"relative z-[1] flex h-[21px] w-[21px] shrink-0 items-center justify-center rounded-full\",\n DISC_TONE[visual.tone],\n )}\n >\n <Icon icon={visual.icon} className=\"h-3 w-3\" />\n </span>\n <div className=\"min-w-0 flex-1 pt-px\">\n <EventBody event={event} visual={visual} defaultExpanded={defaultExpanded} />\n </div>\n </li>\n );\n}\n\n// User prompts and selections sit on the right, like a chat composer turn.\nfunction UserRow({ event }: { event: SessionEvent }) {\n return (\n <li data-event-kind=\"user\" className=\"relative flex justify-end pb-density-4 last:pb-0\">\n <div className=\"flex max-w-[85%] items-start gap-density-3\">\n <div className=\"min-w-0\">\n <div className=\"mb-0.5 text-right text-xs font-medium text-muted-foreground\">You</div>\n <div className=\"whitespace-pre-wrap break-words rounded-lg bg-accent px-density-3 py-density-2 text-right leading-relaxed text-accent-foreground\">\n {event.text}\n </div>\n </div>\n <span\n className={cn(\n \"relative z-[1] flex h-[21px] w-[21px] shrink-0 items-center justify-center rounded-full\",\n DISC_TONE.slate,\n )}\n >\n <Icon icon={UiUserCircle} className=\"h-3 w-3\" />\n </span>\n </div>\n </li>\n );\n}\n\ninterface EventVisual {\n icon: StaticIconComponent;\n tone: SessionTone;\n label: string;\n}\n\nfunction eventVisual(event: SessionEvent): EventVisual {\n switch (event.kind) {\n case \"tool\": {\n const action = getSessionAction(event.tool ?? \"\");\n return { icon: action.icon, tone: action.tone, label: action.label };\n }\n case \"user\":\n return { icon: UiUserCircle, tone: \"slate\", label: \"User\" };\n case \"assistant\":\n return { icon: UiSparkles, tone: \"indigo\", label: \"Assistant\" };\n case \"thinking\":\n return { icon: UiBrain, tone: \"slate\", label: \"Thinking\" };\n case \"error\":\n return { icon: UiWarningTriangle, tone: \"rose\", label: \"Error\" };\n }\n}\n\nfunction EventBody({\n event,\n visual,\n defaultExpanded,\n}: {\n event: SessionEvent;\n visual: EventVisual;\n defaultExpanded: boolean;\n}) {\n if (event.kind === \"tool\") return <ToolBody event={event} label={visual.label} defaultExpanded={defaultExpanded} />;\n if (event.kind === \"thinking\") return <ThinkingBody event={event} />;\n if (event.kind === \"error\") return <ErrorBody event={event} />;\n return <MessageBody event={event} label={visual.label} />;\n}\n\nfunction ToolBody({\n event,\n label,\n defaultExpanded,\n}: {\n event: SessionEvent;\n label: string;\n defaultExpanded: boolean;\n}) {\n const summary = summarizeToolInput(event.tool ?? \"\", event.toolInput);\n const hasDetail = event.toolInput !== undefined || event.toolResponse !== undefined;\n const [open, setOpen] = useState(defaultExpanded);\n\n const header = (\n <>\n <span className=\"font-medium text-foreground\">{label}</span>\n {summary && <span className=\"truncate font-mono text-xs text-muted-foreground\">{summary}</span>}\n </>\n );\n\n return (\n <div className=\"not-prose\">\n {hasDetail ? (\n <button\n type=\"button\"\n aria-expanded={open}\n onClick={() => setOpen((value) => !value)}\n className=\"flex w-full items-center gap-1.5 text-left hover:text-foreground\"\n >\n {header}\n <Icon\n icon={UiChevronDown}\n className={cn(\"ml-auto size-3 shrink-0 text-muted-foreground transition-transform\", open && \"rotate-180\")}\n />\n </button>\n ) : (\n <div className=\"flex items-center gap-1.5\">{header}</div>\n )}\n\n {(event.model || event.source) && (\n <div className=\"mt-0.5 flex gap-density-2 text-[11px] text-muted-foreground\">\n {event.source && <span>{event.source}</span>}\n {event.model && <span>{event.model}</span>}\n {event.reasoningEffort && <span>effort: {event.reasoningEffort}</span>}\n </div>\n )}\n\n {open && hasDetail && (\n <div className=\"mt-1.5 space-y-1.5\">\n {event.toolInput !== undefined && (\n <DetailBlock language=\"json\" source={JSON.stringify(event.toolInput, null, 2)} />\n )}\n {event.toolResponse !== undefined && <ResponseBlock response={event.toolResponse} />}\n </div>\n )}\n </div>\n );\n}\n\nfunction ResponseBlock({ response }: { response: string }) {\n const trimmed = response.trim();\n const isJson = trimmed.startsWith(\"{\") || trimmed.startsWith(\"[\");\n return <DetailBlock language={isJson ? \"json\" : \"text\"} source={response} />;\n}\n\nfunction DetailBlock({ language, source }: { language: string; source: string }): ReactNode {\n return (\n <div className=\"overflow-x-auto text-xs\">\n <CodeBlock language={language} source={source} />\n </div>\n );\n}\n\nfunction MessageBody({ event, label }: { event: SessionEvent; label: string }) {\n return (\n <div>\n <div className=\"mb-0.5 text-xs font-medium text-muted-foreground\">{label}</div>\n <div className=\"whitespace-pre-wrap break-words leading-relaxed text-foreground\">{event.text}</div>\n </div>\n );\n}\n\nfunction ThinkingBody({ event }: { event: SessionEvent }) {\n const [open, setOpen] = useState(false);\n return (\n <div className=\"not-prose\">\n <button\n type=\"button\"\n aria-expanded={open}\n onClick={() => setOpen((value) => !value)}\n className=\"flex items-center gap-1.5 text-xs text-muted-foreground hover:text-foreground\"\n >\n <span className=\"italic\">Reasoning</span>\n <Icon icon={UiChevronDown} className={cn(\"size-3 transition-transform\", open && \"rotate-180\")} />\n </button>\n {open && (\n <div className=\"mt-1 whitespace-pre-wrap break-words border-l-2 border-border pl-density-3 text-xs italic leading-relaxed text-muted-foreground\">\n {event.text}\n </div>\n )}\n </div>\n );\n}\n\nfunction ErrorBody({ event }: { event: SessionEvent }) {\n return (\n <div className=\"rounded-md border border-rose-200 bg-rose-50 px-density-3 py-1.5 text-xs text-rose-700 [[data-theme=dark]_&]:border-rose-500/30 [[data-theme=dark]_&]:bg-rose-500/10 [[data-theme=dark]_&]:text-rose-300\">\n {event.text}\n </div>\n );\n}\n"],"names":[],"mappings":"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"}