claude-session-dashboard 0.2.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/README.md +156 -14
  2. package/dist/client/assets/_dashboard-I7m6D7BE.js +1 -0
  3. package/dist/client/assets/_sessionId-DEliIff6.js +12 -0
  4. package/dist/client/assets/app-D7yorIIh.css +1 -0
  5. package/dist/client/assets/{createServerFn-B0pEGqTk.js → createServerFn-Bn6_ISOt.js} +1 -1
  6. package/dist/client/assets/index-BkqRvnEf.js +1 -0
  7. package/dist/client/assets/{main-CM5g2n-_.js → main-CfJIADCp.js} +7 -7
  8. package/dist/client/assets/{sessions.queries-AUVV0tJj.js → sessions.queries-CrJg4dYU.js} +1 -1
  9. package/dist/client/assets/settings-C4_lsEzl.js +1 -0
  10. package/dist/client/assets/{settings.types-BRNIMHGJ.js → settings.types-9Qf5WcRY.js} +1 -1
  11. package/dist/client/assets/stats-_r1gmaTe.js +4 -0
  12. package/dist/client/assets/{useSessionCost-DgFKglaG.js → useSessionCost-DPZ-ubM1.js} +1 -1
  13. package/dist/client/favicon.svg +3 -0
  14. package/dist/server/assets/_dashboard-TUzgwLqB.js +112 -0
  15. package/dist/server/assets/{_sessionId-BZf2Aqy5.js → _sessionId-C-XZIPqn.js} +31 -32
  16. package/dist/server/assets/_tanstack-start-manifest_v-B51mSkGz.js +4 -0
  17. package/dist/server/assets/{index-Do0HxVmM.js → index-CKfH7HpA.js} +22 -21
  18. package/dist/server/assets/{router-ChxlsPNU.js → router-Cb_hBXHI.js} +55 -29
  19. package/dist/server/assets/{settings-ko61yfVs.js → settings-C0_KyVQQ.js} +66 -20
  20. package/dist/server/assets/{stats-C9cZXTP5.js → stats-BtgVene-.js} +261 -24
  21. package/dist/server/assets/{stats.server-52mNk2Yw.js → stats.server-qTOvID9-.js} +61 -2
  22. package/dist/server/server.js +12 -12
  23. package/package.json +7 -1
  24. package/dist/client/assets/_dashboard-Bxw4OxIS.js +0 -1
  25. package/dist/client/assets/_sessionId-CNR4Ln7m.js +0 -12
  26. package/dist/client/assets/app-u2nTs9ny.css +0 -1
  27. package/dist/client/assets/index-BbdJ1jMA.js +0 -1
  28. package/dist/client/assets/settings-CIwZDakc.js +0 -1
  29. package/dist/client/assets/stats-CjWSMX3y.js +0 -4
  30. package/dist/server/assets/_dashboard-CAO6-qAS.js +0 -116
  31. package/dist/server/assets/_tanstack-start-manifest_v-C5hwNzs-.js +0 -4
@@ -0,0 +1,112 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { useMatches, Link, Outlet } from "@tanstack/react-router";
3
+ import { useQuery } from "@tanstack/react-query";
4
+ import { a as activeSessionsQuery } from "./sessions.queries-B5ZBiVJy.js";
5
+ import "./createSsrRpc-CVg2UDl0.js";
6
+ import "../server.js";
7
+ import "@tanstack/history";
8
+ import "@tanstack/router-core/ssr/client";
9
+ import "@tanstack/router-core";
10
+ import "node:async_hooks";
11
+ import "@tanstack/router-core/ssr/server";
12
+ import "h3-v2";
13
+ import "tiny-invariant";
14
+ import "seroval";
15
+ import "@tanstack/react-router/ssr/server";
16
+ import "zod";
17
+ function ActiveSessionsBadge() {
18
+ const { data: activeSessions } = useQuery(activeSessionsQuery);
19
+ const count = activeSessions?.length ?? 0;
20
+ if (count === 0) return null;
21
+ return /* @__PURE__ */ jsx("span", { className: "ml-auto rounded-full bg-emerald-500/20 px-1.5 py-0.5 text-[10px] font-medium text-emerald-400", children: count });
22
+ }
23
+ const NAV_ITEMS = [
24
+ {
25
+ to: "/sessions",
26
+ label: "Sessions",
27
+ icon: /* @__PURE__ */ jsxs("svg", { className: "h-4 w-4", viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "1.5", children: [
28
+ /* @__PURE__ */ jsx("line", { x1: "2", y1: "4", x2: "14", y2: "4" }),
29
+ /* @__PURE__ */ jsx("line", { x1: "2", y1: "8", x2: "14", y2: "8" }),
30
+ /* @__PURE__ */ jsx("line", { x1: "2", y1: "12", x2: "14", y2: "12" })
31
+ ] })
32
+ },
33
+ {
34
+ to: "/stats",
35
+ label: "Stats",
36
+ icon: /* @__PURE__ */ jsxs("svg", { className: "h-4 w-4", viewBox: "0 0 16 16", fill: "currentColor", children: [
37
+ /* @__PURE__ */ jsx("rect", { x: "1", y: "8", width: "3", height: "7", rx: "0.5" }),
38
+ /* @__PURE__ */ jsx("rect", { x: "6.5", y: "4", width: "3", height: "11", rx: "0.5" }),
39
+ /* @__PURE__ */ jsx("rect", { x: "12", y: "1", width: "3", height: "14", rx: "0.5" })
40
+ ] })
41
+ },
42
+ {
43
+ to: "/settings",
44
+ label: "Settings",
45
+ icon: /* @__PURE__ */ jsxs("svg", { className: "h-4 w-4", viewBox: "0 0 16 16", fill: "currentColor", children: [
46
+ /* @__PURE__ */ jsx("path", { d: "M8 10a2 2 0 100-4 2 2 0 000 4z" }),
47
+ /* @__PURE__ */ jsx("path", { fillRule: "evenodd", clipRule: "evenodd", d: "M6.5.8a1 1 0 011-.8h1a1 1 0 011 .8l.15.9a5.5 5.5 0 011.1.64l.86-.36a1 1 0 011.17.36l.5.86a1 1 0 01-.18 1.17l-.7.55a5.5 5.5 0 010 1.27l.7.55a1 1 0 01.18 1.17l-.5.86a1 1 0 01-1.17.36l-.86-.36a5.5 5.5 0 01-1.1.64l-.14.9a1 1 0 01-1 .8h-1a1 1 0 01-1-.8l-.14-.9a5.5 5.5 0 01-1.1-.64l-.87.36a1 1 0 01-1.17-.36l-.5-.86a1 1 0 01.18-1.17l.7-.55a5.5 5.5 0 010-1.27l-.7-.55a1 1 0 01-.18-1.17l.5-.86a1 1 0 011.17-.36l.87.36a5.5 5.5 0 011.1-.64L6.5.8zM8 11a3 3 0 100-6 3 3 0 000 6z" })
48
+ ] })
49
+ }
50
+ ];
51
+ function AppShell({ children }) {
52
+ const matches = useMatches();
53
+ const currentPath = matches[matches.length - 1]?.pathname ?? "";
54
+ return /* @__PURE__ */ jsxs("div", { className: "flex min-h-screen", children: [
55
+ /* @__PURE__ */ jsxs("aside", { className: "flex w-56 shrink-0 flex-col border-r border-gray-800 bg-gray-950", children: [
56
+ /* @__PURE__ */ jsx("div", { className: "flex h-14 items-center border-b border-gray-800 px-4", children: /* @__PURE__ */ jsxs(Link, { to: "/sessions", className: "text-sm font-bold text-white", children: [
57
+ /* @__PURE__ */ jsx("span", { className: "text-brand-500", children: "Claude" }),
58
+ " Dashboard"
59
+ ] }) }),
60
+ /* @__PURE__ */ jsx("nav", { className: "flex-1 p-3", children: NAV_ITEMS.map((item) => {
61
+ const isActive = currentPath.startsWith(item.to);
62
+ return /* @__PURE__ */ jsxs(
63
+ Link,
64
+ {
65
+ to: item.to,
66
+ className: `flex items-center gap-2.5 rounded-lg px-3 py-2 text-sm transition-colors ${isActive ? "bg-gray-800 text-white" : "text-gray-400 hover:bg-gray-800/50 hover:text-gray-200"}`,
67
+ children: [
68
+ /* @__PURE__ */ jsx("span", { className: "text-gray-500", children: item.icon }),
69
+ item.label,
70
+ item.to === "/sessions" && /* @__PURE__ */ jsx(ActiveSessionsBadge, {})
71
+ ]
72
+ },
73
+ item.to
74
+ );
75
+ }) }),
76
+ /* @__PURE__ */ jsx("div", { className: "border-t border-gray-800 p-3", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
77
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
78
+ /* @__PURE__ */ jsx(
79
+ "a",
80
+ {
81
+ href: "https://github.com/dlupiak/claude-session-dashboard",
82
+ target: "_blank",
83
+ rel: "noopener noreferrer",
84
+ className: "text-gray-500 hover:text-gray-300 transition-colors",
85
+ title: "GitHub Repository",
86
+ children: /* @__PURE__ */ jsx("svg", { className: "w-4 h-4", viewBox: "0 0 16 16", fill: "currentColor", children: /* @__PURE__ */ jsx("path", { d: "M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z" }) })
87
+ }
88
+ ),
89
+ /* @__PURE__ */ jsx(
90
+ "a",
91
+ {
92
+ href: "https://www.npmjs.com/package/claude-session-dashboard",
93
+ target: "_blank",
94
+ rel: "noopener noreferrer",
95
+ className: "text-gray-500 hover:text-gray-300 transition-colors",
96
+ title: "npm Package",
97
+ children: /* @__PURE__ */ jsx("svg", { className: "w-4 h-4", viewBox: "0 0 16 16", fill: "currentColor", children: /* @__PURE__ */ jsx("path", { d: "M0 0v16h16V0H0zm13 13H8V5H5v8H3V3h10v10z" }) })
98
+ }
99
+ )
100
+ ] }),
101
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-600", children: "Read-only" })
102
+ ] }) })
103
+ ] }),
104
+ /* @__PURE__ */ jsx("main", { className: "flex-1 overflow-auto", children: /* @__PURE__ */ jsx("div", { className: "mx-auto max-w-5xl px-6 py-6", children }) })
105
+ ] });
106
+ }
107
+ function DashboardLayout() {
108
+ return /* @__PURE__ */ jsx(AppShell, { children: /* @__PURE__ */ jsx(Outlet, {}) });
109
+ }
110
+ export {
111
+ DashboardLayout as component
112
+ };
@@ -10,7 +10,7 @@ import { ResponsiveContainer, AreaChart, YAxis, Tooltip, ReferenceLine, Area } f
10
10
  import { s as settingsQuery } from "./settings.queries-DSQd324O.js";
11
11
  import { g as getMergedPricing, c as calculateSessionCost, u as useSessionCost, E as ExportDropdown, d as downloadFile, e as sessionToJSON } from "./useSessionCost-CYs5UOX-.js";
12
12
  import { a as activeSessionsQuery } from "./sessions.queries-B5ZBiVJy.js";
13
- import { b as Route, u as usePrivacy } from "./router-ChxlsPNU.js";
13
+ import { b as Route, u as usePrivacy } from "./router-Cb_hBXHI.js";
14
14
  import "@tanstack/history";
15
15
  import "@tanstack/router-core/ssr/client";
16
16
  import "@tanstack/router-core";
@@ -161,10 +161,10 @@ function shortenToolName(name) {
161
161
  }
162
162
  const TOOL_COLORS = {
163
163
  // File reading
164
- Read: "#60a5fa",
165
- // blue-400
166
- Grep: "#60a5fa",
167
- Glob: "#60a5fa",
164
+ Read: "#e09070",
165
+ // brand-400
166
+ Grep: "#e09070",
167
+ Glob: "#e09070",
168
168
  // File writing
169
169
  Write: "#34d399",
170
170
  // emerald-400
@@ -382,7 +382,6 @@ function TimelineChart({ data, width, onHover }) {
382
382
  y: agentLaneYs[i],
383
383
  toX,
384
384
  laneHeight: LANE_HEIGHT,
385
- leftMargin: LEFT_MARGIN,
386
385
  onHover,
387
386
  getPosition
388
387
  },
@@ -526,7 +525,6 @@ function AgentLaneSVG({
526
525
  y,
527
526
  toX,
528
527
  laneHeight,
529
- leftMargin,
530
528
  onHover,
531
529
  getPosition
532
530
  }) {
@@ -827,7 +825,7 @@ function StatBadge({
827
825
  color
828
826
  }) {
829
827
  const colorMap = {
830
- blue: "bg-blue-500/15 text-blue-400",
828
+ blue: "bg-brand-500/15 text-brand-400",
831
829
  purple: "bg-purple-500/15 text-purple-400",
832
830
  gray: "bg-gray-800 text-gray-300",
833
831
  indigo: "bg-indigo-500/15 text-indigo-400",
@@ -900,7 +898,7 @@ function ContextWindowPanel({ contextWindow, tokens }) {
900
898
  /* @__PURE__ */ jsx(
901
899
  "div",
902
900
  {
903
- className: "bg-blue-500",
901
+ className: "bg-brand-500",
904
902
  style: { width: `${messagesPct}%` },
905
903
  title: `Messages: ~${formatTokenCount(messagesEstimate)}`
906
904
  }
@@ -934,7 +932,7 @@ function ContextWindowPanel({ contextWindow, tokens }) {
934
932
  "system"
935
933
  ] }),
936
934
  /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1", children: [
937
- /* @__PURE__ */ jsx("span", { className: "inline-block h-2 w-2 rounded-sm bg-blue-500" }),
935
+ /* @__PURE__ */ jsx("span", { className: "inline-block h-2 w-2 rounded-sm bg-brand-500" }),
938
936
  "messages"
939
937
  ] }),
940
938
  /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1", children: [
@@ -959,7 +957,7 @@ function ContextWindowPanel({ contextWindow, tokens }) {
959
957
  label: "Messages",
960
958
  value: messagesEstimate,
961
959
  total: contextLimit,
962
- color: "bg-blue-500",
960
+ color: "bg-brand-500",
963
961
  prefix: "~"
964
962
  }
965
963
  ),
@@ -986,8 +984,8 @@ function ContextWindowPanel({ contextWindow, tokens }) {
986
984
  /* @__PURE__ */ jsx("p", { className: "mb-1 text-[10px] text-gray-500", children: "Context growth" }),
987
985
  /* @__PURE__ */ jsx(ResponsiveContainer, { width: "100%", height: 96, children: /* @__PURE__ */ jsxs(AreaChart, { data: chartData, children: [
988
986
  /* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsxs("linearGradient", { id: "contextGrad", x1: "0", y1: "0", x2: "0", y2: "1", children: [
989
- /* @__PURE__ */ jsx("stop", { offset: "0%", stopColor: "#3b82f6", stopOpacity: 0.3 }),
990
- /* @__PURE__ */ jsx("stop", { offset: "100%", stopColor: "#3b82f6", stopOpacity: 0.05 })
987
+ /* @__PURE__ */ jsx("stop", { offset: "0%", stopColor: "#d97757", stopOpacity: 0.3 }),
988
+ /* @__PURE__ */ jsx("stop", { offset: "100%", stopColor: "#d97757", stopOpacity: 0.05 })
991
989
  ] }) }),
992
990
  /* @__PURE__ */ jsx(
993
991
  YAxis,
@@ -1000,8 +998,8 @@ function ContextWindowPanel({ contextWindow, tokens }) {
1000
998
  Tooltip,
1001
999
  {
1002
1000
  contentStyle: {
1003
- background: "#1f2937",
1004
- border: "1px solid #374151",
1001
+ background: "#1c1c1a",
1002
+ border: "1px solid #3d3b36",
1005
1003
  borderRadius: "8px",
1006
1004
  fontSize: "11px"
1007
1005
  },
@@ -1032,7 +1030,7 @@ function ContextWindowPanel({ contextWindow, tokens }) {
1032
1030
  {
1033
1031
  type: "stepAfter",
1034
1032
  dataKey: "context",
1035
- stroke: "#3b82f6",
1033
+ stroke: "#d97757",
1036
1034
  fill: "url(#contextGrad)",
1037
1035
  strokeWidth: 1.5,
1038
1036
  dot: false
@@ -1092,7 +1090,7 @@ function CategoryRow({
1092
1090
  function TokenBreakdown({ tokens }) {
1093
1091
  const allTotal = tokens.inputTokens + tokens.outputTokens + tokens.cacheReadInputTokens + tokens.cacheCreationInputTokens;
1094
1092
  const items = [
1095
- { label: "Input", value: tokens.inputTokens, color: "bg-blue-400" },
1093
+ { label: "Input", value: tokens.inputTokens, color: "bg-brand-400" },
1096
1094
  { label: "Output", value: tokens.outputTokens, color: "bg-emerald-400" },
1097
1095
  { label: "Cache Read", value: tokens.cacheReadInputTokens, color: "bg-amber-400" },
1098
1096
  { label: "Cache Create", value: tokens.cacheCreationInputTokens, color: "bg-purple-400" }
@@ -1116,7 +1114,7 @@ function TokenFallback({ tokens }) {
1116
1114
  const activeTotal = tokens.inputTokens + tokens.outputTokens;
1117
1115
  const allTotal = activeTotal + tokens.cacheReadInputTokens + tokens.cacheCreationInputTokens;
1118
1116
  const items = [
1119
- { label: "Input", value: tokens.inputTokens, color: "text-blue-400" },
1117
+ { label: "Input", value: tokens.inputTokens, color: "text-brand-400" },
1120
1118
  { label: "Output", value: tokens.outputTokens, color: "text-emerald-400" },
1121
1119
  { label: "Cache Read", value: tokens.cacheReadInputTokens, color: "text-amber-400" },
1122
1120
  { label: "Cache Create", value: tokens.cacheCreationInputTokens, color: "text-purple-400" }
@@ -1167,9 +1165,9 @@ function ToolUsagePanel({
1167
1165
  /* @__PURE__ */ jsx("div", { className: "flex-1", children: /* @__PURE__ */ jsx(
1168
1166
  "div",
1169
1167
  {
1170
- className: "h-4 rounded bg-blue-500/20",
1168
+ className: "h-4 rounded bg-brand-500/20",
1171
1169
  style: { width: `${count / maxCount * 100}%` },
1172
- children: /* @__PURE__ */ jsx("span", { className: "px-1.5 text-xs text-blue-300", children: count })
1170
+ children: /* @__PURE__ */ jsx("span", { className: "px-1.5 text-xs text-brand-300", children: count })
1173
1171
  }
1174
1172
  ) })
1175
1173
  ] }, tool)) })
@@ -1183,7 +1181,7 @@ function ErrorPanel({ errors }) {
1183
1181
  errors.length,
1184
1182
  ")"
1185
1183
  ] }),
1186
- /* @__PURE__ */ jsx("div", { className: "mt-3 space-y-2", children: errors.map((error, i) => /* @__PURE__ */ jsxs("div", { className: "rounded-lg bg-red-950/30 p-2.5", children: [
1184
+ /* @__PURE__ */ jsx("div", { className: "mt-3 max-h-64 space-y-2 overflow-y-auto", children: errors.map((error, i) => /* @__PURE__ */ jsxs("div", { className: "rounded-lg bg-red-950/30 p-2.5", children: [
1187
1185
  /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
1188
1186
  /* @__PURE__ */ jsx("span", { className: "text-xs font-mono text-red-300", children: error.type }),
1189
1187
  error.timestamp && /* @__PURE__ */ jsx("span", { className: "text-xs text-gray-500", children: formatDateTime(error.timestamp) })
@@ -1344,8 +1342,8 @@ const statusConfig = {
1344
1342
  pending: { label: "Pending", bg: "bg-gray-500/20", text: "text-gray-400" },
1345
1343
  in_progress: {
1346
1344
  label: "In Progress",
1347
- bg: "bg-blue-500/20",
1348
- text: "text-blue-400"
1345
+ bg: "bg-brand-500/20",
1346
+ text: "text-brand-400"
1349
1347
  },
1350
1348
  completed: {
1351
1349
  label: "Done",
@@ -1439,7 +1437,7 @@ function CostEstimationPanel({ tokensByModel }) {
1439
1437
  }
1440
1438
  function CategoryBreakdown({ cost }) {
1441
1439
  const categories = [
1442
- { label: "Input tokens", value: cost.byCategory.input, color: "text-blue-400" },
1440
+ { label: "Input tokens", value: cost.byCategory.input, color: "text-brand-400" },
1443
1441
  { label: "Output tokens", value: cost.byCategory.output, color: "text-emerald-400" },
1444
1442
  { label: "Cache read", value: cost.byCategory.cacheRead, color: "text-amber-400" },
1445
1443
  { label: "Cache write", value: cost.byCategory.cacheWrite, color: "text-purple-400" }
@@ -1452,7 +1450,7 @@ function CategoryBreakdown({ cost }) {
1452
1450
  function CostBar({ cost }) {
1453
1451
  if (cost.totalUSD === 0) return null;
1454
1452
  const segments = [
1455
- { key: "input", value: cost.byCategory.input, color: "bg-blue-400" },
1453
+ { key: "input", value: cost.byCategory.input, color: "bg-brand-400" },
1456
1454
  { key: "output", value: cost.byCategory.output, color: "bg-emerald-400" },
1457
1455
  { key: "cacheRead", value: cost.byCategory.cacheRead, color: "bg-amber-400" },
1458
1456
  { key: "cacheWrite", value: cost.byCategory.cacheWrite, color: "bg-purple-400" }
@@ -1513,10 +1511,10 @@ function CostSummaryLine({ tokensByModel }) {
1513
1511
  );
1514
1512
  }
1515
1513
  function ActiveSessionBanner() {
1516
- return /* @__PURE__ */ jsxs("div", { className: "mb-4 flex items-center gap-2 rounded-lg border border-green-800 bg-green-900/30 px-4 py-2 text-sm text-green-300", children: [
1514
+ return /* @__PURE__ */ jsxs("div", { className: "mb-4 flex items-center gap-2 rounded-lg border border-emerald-800 bg-emerald-900/30 px-4 py-2 text-sm text-emerald-300", children: [
1517
1515
  /* @__PURE__ */ jsxs("span", { className: "relative flex h-2 w-2", children: [
1518
- /* @__PURE__ */ jsx("span", { className: "absolute inline-flex h-full w-full animate-ping rounded-full bg-green-400 opacity-75" }),
1519
- /* @__PURE__ */ jsx("span", { className: "relative inline-flex h-2 w-2 rounded-full bg-green-500" })
1516
+ /* @__PURE__ */ jsx("span", { className: "absolute inline-flex h-full w-full animate-ping rounded-full bg-emerald-400 opacity-75" }),
1517
+ /* @__PURE__ */ jsx("span", { className: "relative inline-flex h-2 w-2 rounded-full bg-emerald-500" })
1520
1518
  ] }),
1521
1519
  "This session is currently active. Data refreshes automatically."
1522
1520
  ] });
@@ -1534,7 +1532,8 @@ function SessionDetailPage() {
1534
1532
  } = Route.useSearch();
1535
1533
  const {
1536
1534
  privacyMode,
1537
- anonymizeProjectName
1535
+ anonymizeProjectName,
1536
+ anonymizeBranch
1538
1537
  } = usePrivacy();
1539
1538
  const isActive = useIsSessionActive(sessionId);
1540
1539
  const {
@@ -1554,7 +1553,7 @@ function SessionDetailPage() {
1554
1553
  "Failed to load session: ",
1555
1554
  error?.message ?? "Not found"
1556
1555
  ] }),
1557
- /* @__PURE__ */ jsx(Link, { to: "/sessions", className: "mt-2 inline-block text-sm text-blue-400 hover:underline", children: "Back to sessions" })
1556
+ /* @__PURE__ */ jsx(Link, { to: "/sessions", className: "mt-2 inline-block text-sm text-brand-300 hover:underline", children: "Back to sessions" })
1558
1557
  ] });
1559
1558
  }
1560
1559
  const startedAt = detail.turns[0]?.timestamp;
@@ -1567,7 +1566,7 @@ function SessionDetailPage() {
1567
1566
  /* @__PURE__ */ jsx(Link, { to: "/sessions", className: "text-xs text-gray-500 hover:text-gray-300", children: "← Sessions" }),
1568
1567
  /* @__PURE__ */ jsx("h1", { className: "mt-1 text-xl font-bold text-white", children: privacyMode ? anonymizeProjectName(detail.projectName) : detail.projectName }),
1569
1568
  /* @__PURE__ */ jsxs("div", { className: "mt-1 flex items-center gap-3 text-xs text-gray-400", children: [
1570
- detail.branch && /* @__PURE__ */ jsx("span", { className: "font-mono", children: detail.branch }),
1569
+ detail.branch && /* @__PURE__ */ jsx("span", { className: "font-mono", children: anonymizeBranch(detail.branch) }),
1571
1570
  startedAt && /* @__PURE__ */ jsx("span", { children: formatDateTime(startedAt) }),
1572
1571
  /* @__PURE__ */ jsx("span", { children: formatDuration(durationMs) }),
1573
1572
  /* @__PURE__ */ jsxs("span", { children: [
@@ -1593,7 +1592,7 @@ function SessionDetailPage() {
1593
1592
  /* @__PURE__ */ jsx("div", { className: "mt-4", children: /* @__PURE__ */ jsx(CostEstimationPanel, { tokensByModel: detail.tokensByModel }) }),
1594
1593
  (detail.agents.length > 0 || detail.skills.length > 0) && /* @__PURE__ */ jsx("div", { className: "mt-4", children: /* @__PURE__ */ jsx(AgentsSkillsPanel, { agents: detail.agents, skills: detail.skills }) }),
1595
1594
  detail.tasks.length > 0 && /* @__PURE__ */ jsx("div", { className: "mt-4", children: /* @__PURE__ */ jsx(TasksPanel, { tasks: detail.tasks }) }),
1596
- /* @__PURE__ */ jsx(ErrorPanel, { errors: detail.errors }),
1595
+ /* @__PURE__ */ jsx("div", { className: "mt-4", children: /* @__PURE__ */ jsx(ErrorPanel, { errors: detail.errors }) }),
1597
1596
  /* @__PURE__ */ jsxs("div", { className: "mt-6", children: [
1598
1597
  /* @__PURE__ */ jsx("h2", { className: "mb-3 text-sm font-semibold text-gray-300", children: "Timeline" }),
1599
1598
  /* @__PURE__ */ jsx(TimelineEventsChart, { turns: detail.turns, agents: detail.agents, skills: detail.skills, errors: detail.errors })
@@ -0,0 +1,4 @@
1
+ const tsrStartManifest = () => ({ "routes": { "__root__": { "filePath": "/home/runner/work/claude-session-dashboard/claude-session-dashboard/apps/web/src/routes/__root.tsx", "children": ["/", "/_dashboard"], "preloads": ["/assets/main-CfJIADCp.js"], "assets": [] }, "/": { "filePath": "/home/runner/work/claude-session-dashboard/claude-session-dashboard/apps/web/src/routes/index.tsx" }, "/_dashboard": { "filePath": "/home/runner/work/claude-session-dashboard/claude-session-dashboard/apps/web/src/routes/_dashboard.tsx", "children": ["/_dashboard/settings", "/_dashboard/stats", "/_dashboard/sessions/$sessionId", "/_dashboard/sessions/"], "assets": [], "preloads": ["/assets/_dashboard-I7m6D7BE.js", "/assets/createServerFn-Bn6_ISOt.js", "/assets/sessions.queries-CrJg4dYU.js"] }, "/_dashboard/settings": { "filePath": "/home/runner/work/claude-session-dashboard/claude-session-dashboard/apps/web/src/routes/_dashboard/settings.tsx", "assets": [], "preloads": ["/assets/settings-C4_lsEzl.js", "/assets/settings.types-9Qf5WcRY.js"] }, "/_dashboard/stats": { "filePath": "/home/runner/work/claude-session-dashboard/claude-session-dashboard/apps/web/src/routes/_dashboard/stats.tsx", "assets": [], "preloads": ["/assets/stats-_r1gmaTe.js", "/assets/format-Bsprb3az.js", "/assets/useSessionCost-DPZ-ubM1.js", "/assets/settings.types-9Qf5WcRY.js"] }, "/_dashboard/sessions/$sessionId": { "filePath": "/home/runner/work/claude-session-dashboard/claude-session-dashboard/apps/web/src/routes/_dashboard/sessions/$sessionId.tsx", "assets": [], "preloads": ["/assets/_sessionId-DEliIff6.js", "/assets/format-Bsprb3az.js", "/assets/useSessionCost-DPZ-ubM1.js", "/assets/settings.types-9Qf5WcRY.js"] }, "/_dashboard/sessions/": { "filePath": "/home/runner/work/claude-session-dashboard/claude-session-dashboard/apps/web/src/routes/_dashboard/sessions/index.tsx", "assets": [], "preloads": ["/assets/index-BkqRvnEf.js", "/assets/format-Bsprb3az.js"] } }, "clientEntry": "/assets/main-CfJIADCp.js" });
2
+ export {
3
+ tsrStartManifest
4
+ };
@@ -4,7 +4,7 @@ import { useQuery } from "@tanstack/react-query";
4
4
  import { Link, useNavigate } from "@tanstack/react-router";
5
5
  import { p as paginatedSessionListQuery, a as activeSessionsQuery } from "./sessions.queries-B5ZBiVJy.js";
6
6
  import { a as formatDuration, b as formatRelativeTime, d as formatBytes } from "./format-DIZHV7IJ.js";
7
- import { u as usePrivacy, a as Route } from "./router-ChxlsPNU.js";
7
+ import { u as usePrivacy, a as Route } from "./router-Cb_hBXHI.js";
8
8
  import "./createSsrRpc-CVg2UDl0.js";
9
9
  import "../server.js";
10
10
  import "@tanstack/history";
@@ -46,9 +46,10 @@ function RunningTimer({ startedAt }) {
46
46
  return /* @__PURE__ */ jsx("span", { className: "text-emerald-400", children: formatDuration(elapsed) });
47
47
  }
48
48
  function SessionCard({ session }) {
49
- const { privacyMode, anonymizePath, anonymizeProjectName } = usePrivacy();
49
+ const { privacyMode, anonymizePath, anonymizeProjectName, anonymizeBranch } = usePrivacy();
50
50
  const displayName = privacyMode ? anonymizeProjectName(session.projectName) : session.projectName;
51
- const displayCwd = session.cwd ? privacyMode ? anonymizePath(session.cwd) : session.cwd : null;
51
+ const displayCwd = session.cwd ? anonymizePath(session.cwd, session.projectName) : null;
52
+ const displayBranch = session.branch ? anonymizeBranch(session.branch) : null;
52
53
  return /* @__PURE__ */ jsxs(
53
54
  Link,
54
55
  {
@@ -63,7 +64,7 @@ function SessionCard({ session }) {
63
64
  /* @__PURE__ */ jsx("h3", { className: "truncate text-sm font-semibold text-white", children: displayName }),
64
65
  /* @__PURE__ */ jsx(StatusBadge, { isActive: session.isActive })
65
66
  ] }),
66
- session.branch && /* @__PURE__ */ jsx("p", { className: "mt-1 truncate text-xs text-gray-500", children: /* @__PURE__ */ jsx("span", { className: "font-mono", children: session.branch }) })
67
+ displayBranch && /* @__PURE__ */ jsx("p", { className: "mt-1 truncate text-xs text-gray-500", children: /* @__PURE__ */ jsx("span", { className: "font-mono", children: displayBranch }) })
67
68
  ] }),
68
69
  /* @__PURE__ */ jsx("span", { className: "shrink-0 text-xs text-gray-500", children: formatRelativeTime(session.lastActiveAt) })
69
70
  ] }),
@@ -129,7 +130,7 @@ function SessionFilters({ projects, activeCount }) {
129
130
  placeholder: "Search sessions...",
130
131
  value: localSearch,
131
132
  onChange: (e) => handleSearchChange(e.target.value),
132
- className: "rounded-lg border border-gray-700 bg-gray-800/50 px-3 py-1.5 text-sm text-gray-200 placeholder-gray-500 outline-none focus:border-blue-500 focus:ring-1 focus:ring-blue-500"
133
+ className: "rounded-lg border border-gray-700 bg-gray-800/50 px-3 py-1.5 text-sm text-gray-200 placeholder-gray-500 outline-none focus:border-brand-500 focus:ring-1 focus:ring-brand-500"
133
134
  }
134
135
  ),
135
136
  /* @__PURE__ */ jsx("div", { className: "flex rounded-lg border border-gray-700 text-xs", children: ["all", "active", "completed"].map((s) => /* @__PURE__ */ jsxs(
@@ -153,7 +154,7 @@ function SessionFilters({ projects, activeCount }) {
153
154
  {
154
155
  value: project,
155
156
  onChange: (e) => handleProjectChange(e.target.value),
156
- className: "rounded-lg border border-gray-700 bg-gray-800/50 px-3 py-1.5 text-sm text-gray-200 outline-none focus:border-blue-500",
157
+ className: "rounded-lg border border-gray-700 bg-gray-800/50 px-3 py-1.5 text-sm text-gray-200 outline-none focus:border-brand-500",
157
158
  children: [
158
159
  /* @__PURE__ */ jsx("option", { value: "", children: "All projects" }),
159
160
  projects.map((p) => /* @__PURE__ */ jsx("option", { value: p, children: privacyMode ? anonymizeProjectName(p) : p }, p))
@@ -167,20 +168,20 @@ const VALID_SIZES = [5, 10, 25, 50];
167
168
  function isValidSize(value) {
168
169
  return VALID_SIZES.includes(value);
169
170
  }
170
- function usePageSizePreference() {
171
- const [storedPageSize, setStoredPageSize] = useState(null);
172
- useEffect(() => {
173
- try {
174
- const raw = localStorage.getItem(STORAGE_KEY);
175
- if (raw !== null) {
176
- const parsed = Number(raw);
177
- if (isValidSize(parsed)) {
178
- setStoredPageSize(parsed);
179
- }
180
- }
181
- } catch {
171
+ function readStoredPageSize() {
172
+ if (typeof window === "undefined") return null;
173
+ try {
174
+ const raw = localStorage.getItem(STORAGE_KEY);
175
+ if (raw !== null) {
176
+ const parsed = Number(raw);
177
+ if (isValidSize(parsed)) return parsed;
182
178
  }
183
- }, []);
179
+ } catch {
180
+ }
181
+ return null;
182
+ }
183
+ function usePageSizePreference() {
184
+ const [storedPageSize, setStoredPageSize] = useState(readStoredPageSize);
184
185
  const setPageSize = useCallback((size) => {
185
186
  if (!isValidSize(size)) return;
186
187
  try {
@@ -211,7 +212,7 @@ function PaginationControls({
211
212
  {
212
213
  value: pageSize,
213
214
  onChange: (e) => onPageSizeChange(Number(e.target.value)),
214
- className: "rounded-lg border border-gray-700 bg-gray-800/50 px-2 py-1 text-xs text-gray-200 outline-none focus:border-blue-500",
215
+ className: "rounded-lg border border-gray-700 bg-gray-800/50 px-2 py-1 text-xs text-gray-200 outline-none focus:border-brand-500",
215
216
  children: VALID_SIZES.map((size) => /* @__PURE__ */ jsxs("option", { value: size, children: [
216
217
  size,
217
218
  " / page"
@@ -255,7 +256,7 @@ function PaginationControls({
255
256
  "button",
256
257
  {
257
258
  onClick: () => onPageChange(item),
258
- className: `min-w-[2rem] rounded-lg px-2 py-1.5 text-xs font-mono transition-colors ${item === page ? "bg-blue-600 text-white" : "border border-gray-700 bg-gray-800 text-gray-400 hover:bg-gray-700 hover:text-gray-200"}`,
259
+ className: `min-w-[2rem] rounded-lg px-2 py-1.5 text-xs font-mono transition-colors ${item === page ? "bg-brand-600 text-white" : "border border-gray-700 bg-gray-800 text-gray-400 hover:bg-gray-700 hover:text-gray-200"}`,
259
260
  children: item
260
261
  },
261
262
  item
@@ -1,27 +1,31 @@
1
1
  import { createRootRoute, Outlet, HeadContent, Scripts, createFileRoute, lazyRouteComponent, redirect, createRouter } from "@tanstack/react-router";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import { QueryClientProvider, QueryClient } from "@tanstack/react-query";
4
- import { useState, useRef, useEffect, useCallback, createContext, useContext } from "react";
4
+ import { useState, useRef, useCallback, createContext, useContext } from "react";
5
5
  import { z } from "zod";
6
6
  const OS_USERNAME_PATTERN = /^(\/(?:Users|home))\/[^/]+/;
7
- function anonymizePath(path) {
7
+ function anonymizePath(path, anonymizedProjectName) {
8
+ if (anonymizedProjectName) {
9
+ return `.../${anonymizedProjectName}`;
10
+ }
8
11
  return path.replace(OS_USERNAME_PATTERN, "$1/user");
9
12
  }
10
13
  const STORAGE_KEY = "claude-dashboard:privacy-mode";
14
+ function readStoredPrivacyMode() {
15
+ if (typeof window === "undefined") return false;
16
+ try {
17
+ return localStorage.getItem(STORAGE_KEY) === "true";
18
+ } catch {
19
+ return false;
20
+ }
21
+ }
11
22
  const PrivacyContext = createContext(null);
12
23
  function PrivacyProvider({ children }) {
13
- const [privacyMode, setPrivacyMode] = useState(false);
24
+ const [privacyMode, setPrivacyMode] = useState(readStoredPrivacyMode);
14
25
  const projectNameMapRef = useRef(/* @__PURE__ */ new Map());
15
26
  const nextIndexRef = useRef(1);
16
- useEffect(() => {
17
- try {
18
- const stored = localStorage.getItem(STORAGE_KEY);
19
- if (stored === "true") {
20
- setPrivacyMode(true);
21
- }
22
- } catch {
23
- }
24
- }, []);
27
+ const branchNameMapRef = useRef(/* @__PURE__ */ new Map());
28
+ const nextBranchIndexRef = useRef(1);
25
29
  const togglePrivacyMode = useCallback(() => {
26
30
  setPrivacyMode((prev) => {
27
31
  const next = !prev;
@@ -31,16 +35,11 @@ function PrivacyProvider({ children }) {
31
35
  }
32
36
  projectNameMapRef.current = /* @__PURE__ */ new Map();
33
37
  nextIndexRef.current = 1;
38
+ branchNameMapRef.current = /* @__PURE__ */ new Map();
39
+ nextBranchIndexRef.current = 1;
34
40
  return next;
35
41
  });
36
42
  }, []);
37
- const anonymizePath$1 = useCallback(
38
- (path) => {
39
- if (!privacyMode) return path;
40
- return anonymizePath(path);
41
- },
42
- [privacyMode]
43
- );
44
43
  const anonymizeProjectName = useCallback(
45
44
  (name) => {
46
45
  if (!privacyMode) return name;
@@ -53,14 +52,38 @@ function PrivacyProvider({ children }) {
53
52
  },
54
53
  [privacyMode]
55
54
  );
55
+ const anonymizePathFn = useCallback(
56
+ (path, projectName) => {
57
+ if (!privacyMode) return path;
58
+ if (projectName) {
59
+ const anonName = anonymizeProjectName(projectName);
60
+ return anonymizePath(path, anonName);
61
+ }
62
+ return anonymizePath(path);
63
+ },
64
+ [privacyMode, anonymizeProjectName]
65
+ );
66
+ const anonymizeBranch = useCallback(
67
+ (branch) => {
68
+ if (!privacyMode) return branch;
69
+ const existing = branchNameMapRef.current.get(branch);
70
+ if (existing) return existing;
71
+ const anonymized = `branch-${nextBranchIndexRef.current}`;
72
+ nextBranchIndexRef.current += 1;
73
+ branchNameMapRef.current.set(branch, anonymized);
74
+ return anonymized;
75
+ },
76
+ [privacyMode]
77
+ );
56
78
  return /* @__PURE__ */ jsx(
57
79
  PrivacyContext.Provider,
58
80
  {
59
81
  value: {
60
82
  privacyMode,
61
83
  togglePrivacyMode,
62
- anonymizePath: anonymizePath$1,
63
- anonymizeProjectName
84
+ anonymizePath: anonymizePathFn,
85
+ anonymizeProjectName,
86
+ anonymizeBranch
64
87
  },
65
88
  children
66
89
  }
@@ -73,7 +96,7 @@ function usePrivacy() {
73
96
  }
74
97
  return ctx;
75
98
  }
76
- const appCss = "/assets/app-u2nTs9ny.css";
99
+ const appCss = "/assets/app-D7yorIIh.css";
77
100
  const queryClient = new QueryClient({
78
101
  defaultOptions: {
79
102
  queries: {
@@ -87,13 +110,16 @@ const Route$6 = createRootRoute({
87
110
  meta: [
88
111
  { charSet: "utf-8" },
89
112
  { name: "viewport", content: "width=device-width, initial-scale=1" },
90
- { title: "Claude Session Dashboard" }
113
+ { title: "Claude Session Dashboard" },
114
+ { name: "theme-color", content: "#141413" },
115
+ { name: "description", content: "Local observability dashboard for Claude Code sessions" }
91
116
  ],
92
117
  links: [
93
118
  {
94
119
  rel: "stylesheet",
95
120
  href: appCss
96
- }
121
+ },
122
+ { rel: "icon", type: "image/svg+xml", href: "/favicon.svg" }
97
123
  ]
98
124
  }),
99
125
  component: RootComponent
@@ -110,7 +136,7 @@ function RootDocument({ children }) {
110
136
  ] })
111
137
  ] });
112
138
  }
113
- const $$splitComponentImporter$4 = () => import("./_dashboard-CAO6-qAS.js");
139
+ const $$splitComponentImporter$4 = () => import("./_dashboard-TUzgwLqB.js");
114
140
  const Route$5 = createFileRoute("/_dashboard")({
115
141
  component: lazyRouteComponent($$splitComponentImporter$4, "component")
116
142
  });
@@ -119,7 +145,7 @@ const Route$4 = createFileRoute("/")({
119
145
  throw redirect({ to: "/sessions" });
120
146
  }
121
147
  });
122
- const $$splitComponentImporter$3 = () => import("./stats-C9cZXTP5.js");
148
+ const $$splitComponentImporter$3 = () => import("./stats-BtgVene-.js");
123
149
  const statsSearchSchema = z.object({
124
150
  tab: z.enum(["overview", "projects"]).default("overview").catch("overview")
125
151
  });
@@ -127,11 +153,11 @@ const Route$3 = createFileRoute("/_dashboard/stats")({
127
153
  validateSearch: statsSearchSchema,
128
154
  component: lazyRouteComponent($$splitComponentImporter$3, "component")
129
155
  });
130
- const $$splitComponentImporter$2 = () => import("./settings-ko61yfVs.js");
156
+ const $$splitComponentImporter$2 = () => import("./settings-C0_KyVQQ.js");
131
157
  const Route$2 = createFileRoute("/_dashboard/settings")({
132
158
  component: lazyRouteComponent($$splitComponentImporter$2, "component")
133
159
  });
134
- const $$splitComponentImporter$1 = () => import("./index-Do0HxVmM.js");
160
+ const $$splitComponentImporter$1 = () => import("./index-CKfH7HpA.js");
135
161
  const sessionsSearchSchema = z.object({
136
162
  page: z.number().int().min(1).default(1).catch(1),
137
163
  pageSize: z.number().int().min(5).max(100).default(5).catch(5),
@@ -143,7 +169,7 @@ const Route$1 = createFileRoute("/_dashboard/sessions/")({
143
169
  validateSearch: sessionsSearchSchema,
144
170
  component: lazyRouteComponent($$splitComponentImporter$1, "component")
145
171
  });
146
- const $$splitComponentImporter = () => import("./_sessionId-BZf2Aqy5.js");
172
+ const $$splitComponentImporter = () => import("./_sessionId-C-XZIPqn.js");
147
173
  const searchSchema = z.object({
148
174
  project: z.string().optional()
149
175
  });