claude-session-dashboard 0.4.3 → 0.4.5

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 (38) hide show
  1. package/dist/client/assets/_dashboard-Job6DOd1.js +1 -0
  2. package/dist/client/assets/_sessionId-CIFXqjVa.js +12 -0
  3. package/dist/client/assets/app-CkRVT69z.css +1 -0
  4. package/dist/client/assets/{createServerFn-BYTDoNe-.js → createServerFn-6SJrpRCO.js} +1 -1
  5. package/dist/client/assets/index-DDr2smLW.js +1 -0
  6. package/dist/client/assets/main-B6FohBFQ.js +69 -0
  7. package/dist/client/assets/sessions.queries-ClgzoOt4.js +1 -0
  8. package/dist/client/assets/settings-DQD2EaIq.js +1 -0
  9. package/dist/client/assets/{settings.types-CMYAW0cQ.js → settings.types-LYd9Z8Y1.js} +1 -1
  10. package/dist/client/assets/stats-CrWQ_y2R.js +4 -0
  11. package/dist/client/assets/{useSessionCost-BBu3AmcX.js → useSessionCost-Drt0v6oj.js} +1 -1
  12. package/dist/server/assets/{_dashboard-TUzgwLqB.js → _dashboard-61fpMMMe.js} +59 -33
  13. package/dist/server/assets/{_sessionId-DyFxvcBN.js → _sessionId-B_O50OfN.js} +17 -17
  14. package/dist/server/assets/_tanstack-start-manifest_v-Dva5sIpS.js +4 -0
  15. package/dist/server/assets/app-info.api-CdaWsxHl.js +40 -0
  16. package/dist/server/assets/{index-Bx7vBs4O.js → index-BYcFI9Ho.js} +6 -6
  17. package/dist/server/assets/{project-analytics.server-kTooOGl-.js → project-analytics.api-QnhRRs7T.js} +2 -2
  18. package/dist/server/assets/{router-Cd4jLk4T.js → router-BvYNknMb.js} +80 -12
  19. package/dist/server/assets/{session-detail.server-CO5XwLSv.js → session-detail.api-8plxSeB0.js} +2 -2
  20. package/dist/server/assets/{sessions.server-CTeIkXCV.js → sessions.api-DRmOjipJ.js} +6 -6
  21. package/dist/server/assets/{sessions.queries-B5ZBiVJy.js → sessions.queries-CvAnVbE8.js} +3 -3
  22. package/dist/server/assets/{settings-KKaz1ty7.js → settings-Ct2BZGxb.js} +33 -5
  23. package/dist/server/assets/{settings.server-6B2PvLgf.js → settings.api-C9L2GoIE.js} +4 -4
  24. package/dist/server/assets/{settings.queries-DSQd324O.js → settings.queries-BVEZA-1G.js} +2 -2
  25. package/dist/server/assets/{stats-Bsrkajci.js → stats-CZEpvzmd.js} +51 -37
  26. package/dist/server/assets/{stats.server-iJ_z7eUN.js → stats.api-CH-wTCGE.js} +2 -2
  27. package/dist/server/assets/{useSessionCost-CYs5UOX-.js → useSessionCost-CyWBuljV.js} +1 -1
  28. package/dist/server/server.js +39 -25
  29. package/package.json +4 -1
  30. package/dist/client/assets/_dashboard-t702m22X.js +0 -1
  31. package/dist/client/assets/_sessionId-D4Tpmmb5.js +0 -12
  32. package/dist/client/assets/app-DREGBD44.css +0 -1
  33. package/dist/client/assets/index-DnK_zh3s.js +0 -1
  34. package/dist/client/assets/main-CV28H4XG.js +0 -56
  35. package/dist/client/assets/sessions.queries-tzrs5GhP.js +0 -1
  36. package/dist/client/assets/settings-D8yv1q93.js +0 -1
  37. package/dist/client/assets/stats-C_6E4jyb.js +0 -4
  38. package/dist/server/assets/_tanstack-start-manifest_v-Bzp9kpGN.js +0 -4
@@ -1,9 +1,9 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
2
  import { useState } from "react";
3
3
  import { useQuery } from "@tanstack/react-query";
4
- import { s as settingsQuery, u as useSettingsMutation } from "./settings.queries-DSQd324O.js";
4
+ import { s as settingsQuery, u as useSettingsMutation } from "./settings.queries-BVEZA-1G.js";
5
5
  import { a as SUBSCRIPTION_TIERS, b as DEFAULT_PRICING, D as DEFAULT_SETTINGS } from "./settings.types-DntadCHo.js";
6
- import { u as usePrivacy } from "./router-Cd4jLk4T.js";
6
+ import { u as usePrivacy, a as useTheme } from "./router-BvYNknMb.js";
7
7
  import "./createSsrRpc-CVg2UDl0.js";
8
8
  import "../server.js";
9
9
  import "@tanstack/history";
@@ -25,7 +25,7 @@ function TierSelector({ value, onChange }) {
25
25
  {
26
26
  type: "button",
27
27
  onClick: () => onChange(tier.id),
28
- className: `rounded-lg border px-3 py-2 text-left transition-colors ${isSelected ? "border-brand-500 bg-brand-500/10 text-white" : "border-gray-800 bg-gray-900/50 text-gray-400 hover:border-gray-700 hover:text-gray-300"}`,
28
+ className: `rounded-lg border px-3 py-2 text-left transition-colors ${isSelected ? "border-brand-500 bg-brand-500/10 text-gray-100" : "border-gray-800 bg-gray-900/50 text-gray-400 hover:border-gray-700 hover:text-gray-300"}`,
29
29
  children: [
30
30
  /* @__PURE__ */ jsx("div", { className: "text-sm font-medium", children: tier.displayName }),
31
31
  /* @__PURE__ */ jsx("div", { className: "mt-0.5 font-mono text-[10px] text-gray-500", children: tier.monthlyUSD !== null ? `$${tier.monthlyUSD}/mo` : "Custom" })
@@ -115,6 +115,8 @@ function SettingsPage() {
115
115
  function SettingsForm({ settings }) {
116
116
  const mutation = useSettingsMutation();
117
117
  const { privacyMode, togglePrivacyMode } = usePrivacy();
118
+ const { theme, toggleTheme } = useTheme();
119
+ const isDark = theme === "dark";
118
120
  const [tier, setTier] = useState(settings.subscriptionTier);
119
121
  const [overrides, setOverrides] = useState(settings.pricingOverrides);
120
122
  const [isDirty, setIsDirty] = useState(false);
@@ -144,7 +146,7 @@ function SettingsForm({ settings }) {
144
146
  });
145
147
  }
146
148
  return /* @__PURE__ */ jsxs("div", { children: [
147
- /* @__PURE__ */ jsx("h1", { className: "text-xl font-bold text-white", children: "Settings" }),
149
+ /* @__PURE__ */ jsx("h1", { className: "text-xl font-bold text-gray-100", children: "Settings" }),
148
150
  /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-gray-500", children: "Configure your subscription tier and API pricing for cost estimation." }),
149
151
  /* @__PURE__ */ jsxs("div", { className: "mt-6", children: [
150
152
  /* @__PURE__ */ jsx("h2", { className: "text-sm font-semibold text-gray-300", children: "Privacy Mode" }),
@@ -194,6 +196,32 @@ function SettingsForm({ settings }) {
194
196
  ] })
195
197
  ] })
196
198
  ] }),
199
+ /* @__PURE__ */ jsxs("div", { className: "mt-6", children: [
200
+ /* @__PURE__ */ jsx("h2", { className: "text-sm font-semibold text-gray-300", children: "Theme" }),
201
+ /* @__PURE__ */ jsx("p", { className: "mt-1 text-[10px] text-gray-500", children: "Switch between dark and light mode. Defaults to your system preference." }),
202
+ /* @__PURE__ */ jsx("div", { className: "mt-3 rounded-xl border border-gray-800 bg-gray-900/50 p-4", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
203
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-gray-300", children: isDark ? "Dark mode" : "Light mode" }),
204
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
205
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-gray-500", children: isDark ? "🌙" : "☀️" }),
206
+ /* @__PURE__ */ jsx(
207
+ "button",
208
+ {
209
+ type: "button",
210
+ role: "switch",
211
+ "aria-checked": !isDark,
212
+ onClick: toggleTheme,
213
+ className: `relative inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full transition-colors ${!isDark ? "bg-brand-600" : "bg-gray-800"}`,
214
+ children: /* @__PURE__ */ jsx(
215
+ "span",
216
+ {
217
+ className: `inline-block h-3.5 w-3.5 rounded-full bg-white transition-transform ${!isDark ? "translate-x-[18px]" : "translate-x-[3px]"}`
218
+ }
219
+ )
220
+ }
221
+ )
222
+ ] })
223
+ ] }) })
224
+ ] }),
197
225
  /* @__PURE__ */ jsxs("div", { className: "mt-6", children: [
198
226
  /* @__PURE__ */ jsx("h2", { className: "text-sm font-semibold text-gray-300", children: "Subscription Tier" }),
199
227
  /* @__PURE__ */ jsx("p", { className: "mt-1 text-[10px] text-gray-500", children: "Select your Claude subscription plan. This is informational only and does not affect cost calculations." }),
@@ -232,7 +260,7 @@ function SettingsForm({ settings }) {
232
260
  type: "button",
233
261
  onClick: handleSave,
234
262
  disabled: !isDirty || mutation.isPending,
235
- className: `rounded-lg px-4 py-1.5 text-xs font-medium transition-colors ${isDirty && !mutation.isPending ? "bg-brand-600 text-white hover:bg-brand-500" : "cursor-not-allowed bg-gray-800 text-gray-500"}`,
263
+ className: `rounded-lg px-4 py-1.5 text-xs font-medium transition-colors ${isDirty && !mutation.isPending ? "bg-brand-600 text-gray-100 hover:bg-brand-500" : "cursor-not-allowed bg-gray-800 text-gray-500"}`,
236
264
  children: mutation.isPending ? "Saving..." : "Save"
237
265
  }
238
266
  )
@@ -25,9 +25,9 @@ function getSettingsDir() {
25
25
  return path.join(os.homedir(), SETTINGS_DIR);
26
26
  }
27
27
  const getSettings_createServerFn_handler = createServerRpc({
28
- id: "810657681a273df5b4e58f0d8fcc6a5451598b489431b9bcaa98eea0ad815da8",
28
+ id: "72f81ef9e8fa751bab60a8bdabd7e77816e2a6723a5e6e26e03712c01b3a249c",
29
29
  name: "getSettings",
30
- filename: "src/features/settings/settings.server.ts"
30
+ filename: "src/features/settings/settings.api.ts"
31
31
  }, (opts) => getSettings.__executeServer(opts));
32
32
  const getSettings = createServerFn({
33
33
  method: "GET"
@@ -47,9 +47,9 @@ const getSettings = createServerFn({
47
47
  }
48
48
  });
49
49
  const saveSettings_createServerFn_handler = createServerRpc({
50
- id: "3050115d92ca91ab1fd8fd698e33076328aae80dc64ca27c088eee16cebccc1a",
50
+ id: "7fe8c2b131c4fc81aa9a2570aec79640ff84603fe0060d13c24928b7329cb236",
51
51
  name: "saveSettings",
52
- filename: "src/features/settings/settings.server.ts"
52
+ filename: "src/features/settings/settings.api.ts"
53
53
  }, (opts) => saveSettings.__executeServer(opts));
54
54
  const saveSettings = createServerFn({
55
55
  method: "POST"
@@ -4,13 +4,13 @@ import { S as SettingsSchema } from "./settings.types-DntadCHo.js";
4
4
  import { c as createServerFn } from "../server.js";
5
5
  const getSettings = createServerFn({
6
6
  method: "GET"
7
- }).handler(createSsrRpc("810657681a273df5b4e58f0d8fcc6a5451598b489431b9bcaa98eea0ad815da8"));
7
+ }).handler(createSsrRpc("72f81ef9e8fa751bab60a8bdabd7e77816e2a6723a5e6e26e03712c01b3a249c"));
8
8
  const saveSettings = createServerFn({
9
9
  method: "POST"
10
10
  }).inputValidator((input) => {
11
11
  const result = SettingsSchema.parse(input);
12
12
  return result;
13
- }).handler(createSsrRpc("3050115d92ca91ab1fd8fd698e33076328aae80dc64ca27c088eee16cebccc1a"));
13
+ }).handler(createSsrRpc("7fe8c2b131c4fc81aa9a2570aec79640ff84603fe0060d13c24928b7329cb236"));
14
14
  const settingsQuery = queryOptions({
15
15
  queryKey: ["settings"],
16
16
  queryFn: () => getSettings(),
@@ -8,8 +8,8 @@ import { format, addDays, getDay, parseISO, startOfISOWeek } from "date-fns";
8
8
  import { createPortal } from "react-dom";
9
9
  import { f as formatTokenCount, a as formatDuration, b as formatRelativeTime, c as formatUSD } from "./format-DIZHV7IJ.js";
10
10
  import { Link } from "@tanstack/react-router";
11
- import { u as usePrivacy, R as Route } from "./router-Cd4jLk4T.js";
12
- import { u as useSessionCost, E as ExportDropdown, d as downloadFile, a as dailyActivityToCSV, b as dailyTokensToCSV, m as modelUsageToCSV, s as statsToJSON } from "./useSessionCost-CYs5UOX-.js";
11
+ import { u as usePrivacy, R as Route } from "./router-BvYNknMb.js";
12
+ import { u as useSessionCost, E as ExportDropdown, d as downloadFile, a as dailyActivityToCSV, b as dailyTokensToCSV, m as modelUsageToCSV, s as statsToJSON } from "./useSessionCost-CyWBuljV.js";
13
13
  import "@tanstack/history";
14
14
  import "@tanstack/router-core/ssr/client";
15
15
  import "@tanstack/router-core";
@@ -20,16 +20,24 @@ import "tiny-invariant";
20
20
  import "seroval";
21
21
  import "@tanstack/react-router/ssr/server";
22
22
  import "zod";
23
- import "./settings.queries-DSQd324O.js";
23
+ import "./settings.queries-BVEZA-1G.js";
24
24
  import "./settings.types-DntadCHo.js";
25
25
  const getStats = createServerFn({
26
26
  method: "GET"
27
- }).handler(createSsrRpc("4b9a58c176f487b49800a372100037cdf33cf048f3592a449f115c7e3f5ea799"));
27
+ }).handler(createSsrRpc("44af69d3bfcf3ec46fffb3f297d2b12cd7fe4db36c654b8a322df34d549c6493"));
28
28
  const statsQuery = queryOptions({
29
29
  queryKey: ["stats"],
30
30
  queryFn: () => getStats(),
31
31
  refetchInterval: 6e4
32
32
  });
33
+ const getProjectAnalytics = createServerFn({
34
+ method: "GET"
35
+ }).handler(createSsrRpc("39e65590d2bc41f653f54a9b6a9e0a72f185da275304c0a4a595d811cf185572"));
36
+ const projectAnalyticsQuery = queryOptions({
37
+ queryKey: ["projects", "analytics"],
38
+ queryFn: () => getProjectAnalytics(),
39
+ refetchInterval: 6e4
40
+ });
33
41
  function ActivityChart({ data }) {
34
42
  const chartData = data.map((d) => ({
35
43
  ...d,
@@ -39,19 +47,19 @@ function ActivityChart({ data }) {
39
47
  /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold text-gray-300", children: "Daily Activity" }),
40
48
  /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-gray-500", children: "Messages, sessions, and tool calls per day" }),
41
49
  /* @__PURE__ */ jsx("div", { className: "mt-4 h-64", children: /* @__PURE__ */ jsx(ResponsiveContainer, { width: "100%", height: "100%", children: /* @__PURE__ */ jsxs(BarChart, { data: chartData, children: [
42
- /* @__PURE__ */ jsx(CartesianGrid, { strokeDasharray: "3 3", stroke: "#2a2926" }),
50
+ /* @__PURE__ */ jsx(CartesianGrid, { strokeDasharray: "3 3", stroke: "var(--color-gray-800)" }),
43
51
  /* @__PURE__ */ jsx(
44
52
  XAxis,
45
53
  {
46
54
  dataKey: "dateLabel",
47
- tick: { fill: "#7a7668", fontSize: 10 },
55
+ tick: { fill: "var(--color-gray-500)", fontSize: 10 },
48
56
  tickLine: false
49
57
  }
50
58
  ),
51
59
  /* @__PURE__ */ jsx(
52
60
  YAxis,
53
61
  {
54
- tick: { fill: "#7a7668", fontSize: 10 },
62
+ tick: { fill: "var(--color-gray-500)", fontSize: 10 },
55
63
  tickLine: false,
56
64
  axisLine: false
57
65
  }
@@ -60,8 +68,8 @@ function ActivityChart({ data }) {
60
68
  Tooltip,
61
69
  {
62
70
  contentStyle: {
63
- backgroundColor: "#1c1c1a",
64
- border: "1px solid #3d3b36",
71
+ backgroundColor: "var(--color-gray-900)",
72
+ border: "1px solid var(--color-gray-700)",
65
73
  borderRadius: "8px",
66
74
  fontSize: "12px"
67
75
  }
@@ -98,7 +106,7 @@ function ActivityChart({ data }) {
98
106
  ] });
99
107
  }
100
108
  const INTENSITY_COLORS = [
101
- "#2a2926",
109
+ "var(--color-gray-800)",
102
110
  // Level 0: warm gray-800 (no activity)
103
111
  "#3d2a1e",
104
112
  // Level 1: dark terracotta
@@ -422,7 +430,7 @@ function CustomTooltip({ active, payload, label }) {
422
430
  ] }, entry.name)),
423
431
  /* @__PURE__ */ jsxs("div", { className: "mt-1.5 border-t border-gray-700 pt-1.5 flex justify-between", children: [
424
432
  /* @__PURE__ */ jsx("span", { className: "text-gray-400", children: "Total" }),
425
- /* @__PURE__ */ jsx("span", { className: "font-mono font-medium text-white", children: formatTokenCount(total) })
433
+ /* @__PURE__ */ jsx("span", { className: "font-mono font-medium text-gray-100", children: formatTokenCount(total) })
426
434
  ] })
427
435
  ] });
428
436
  }
@@ -462,7 +470,7 @@ function TokenTrendChart({ data }) {
462
470
  {
463
471
  type: "button",
464
472
  onClick: () => setGranularity("daily"),
465
- className: `rounded-l-lg px-3 py-1 ${granularity === "daily" ? "bg-gray-700 text-white" : "text-gray-400 hover:text-gray-300"}`,
473
+ className: `rounded-l-lg px-3 py-1 ${granularity === "daily" ? "bg-gray-700 text-gray-100" : "text-gray-400 hover:text-gray-300"}`,
466
474
  children: "Daily"
467
475
  }
468
476
  ),
@@ -471,26 +479,26 @@ function TokenTrendChart({ data }) {
471
479
  {
472
480
  type: "button",
473
481
  onClick: () => setGranularity("weekly"),
474
- className: `rounded-r-lg px-3 py-1 ${granularity === "weekly" ? "bg-gray-700 text-white" : "text-gray-400 hover:text-gray-300"}`,
482
+ className: `rounded-r-lg px-3 py-1 ${granularity === "weekly" ? "bg-gray-700 text-gray-100" : "text-gray-400 hover:text-gray-300"}`,
475
483
  children: "Weekly"
476
484
  }
477
485
  )
478
486
  ] })
479
487
  ] }),
480
488
  /* @__PURE__ */ jsx("div", { className: "mt-4 h-72", children: /* @__PURE__ */ jsx(ResponsiveContainer, { width: "100%", height: "100%", children: /* @__PURE__ */ jsxs(AreaChart, { data: chartData, children: [
481
- /* @__PURE__ */ jsx(CartesianGrid, { strokeDasharray: "3 3", stroke: "#2a2926" }),
489
+ /* @__PURE__ */ jsx(CartesianGrid, { strokeDasharray: "3 3", stroke: "var(--color-gray-800)" }),
482
490
  /* @__PURE__ */ jsx(
483
491
  XAxis,
484
492
  {
485
493
  dataKey: "dateLabel",
486
- tick: { fill: "#7a7668", fontSize: 10 },
494
+ tick: { fill: "var(--color-gray-500)", fontSize: 10 },
487
495
  tickLine: false
488
496
  }
489
497
  ),
490
498
  /* @__PURE__ */ jsx(
491
499
  YAxis,
492
500
  {
493
- tick: { fill: "#7a7668", fontSize: 10 },
501
+ tick: { fill: "var(--color-gray-500)", fontSize: 10 },
494
502
  tickLine: false,
495
503
  axisLine: false,
496
504
  tickFormatter: (value) => formatTokenCount(value)
@@ -544,8 +552,8 @@ function ModelUsageChart({ data }) {
544
552
  {
545
553
  formatter: (value) => formatTokenCount(value),
546
554
  contentStyle: {
547
- backgroundColor: "#1c1c1a",
548
- border: "1px solid #3d3b36",
555
+ backgroundColor: "var(--color-gray-900)",
556
+ border: "1px solid var(--color-gray-700)",
549
557
  borderRadius: "8px",
550
558
  fontSize: "12px"
551
559
  }
@@ -610,14 +618,6 @@ function HourlyDistribution({
610
618
  ] })
611
619
  ] });
612
620
  }
613
- const getProjectAnalytics = createServerFn({
614
- method: "GET"
615
- }).handler(createSsrRpc("64052f224a1d6696436e5d3deeee2b798f0742e1292ffabd038c3a7bf75e6fcb"));
616
- const projectAnalyticsQuery = queryOptions({
617
- queryKey: ["projects", "analytics"],
618
- queryFn: () => getProjectAnalytics(),
619
- refetchInterval: 6e4
620
- });
621
621
  const COLUMNS = [
622
622
  { key: "projectName", label: "Project" },
623
623
  { key: "totalSessions", label: "Sessions", align: "right" },
@@ -690,7 +690,7 @@ function ProjectTable({ projects }) {
690
690
  {
691
691
  to: "/sessions",
692
692
  search: { project: project.projectName },
693
- className: "text-sm text-brand-300 hover:underline",
693
+ className: "text-sm text-brand-500 hover:underline",
694
694
  children: anonymizeProjectName(project.projectName)
695
695
  }
696
696
  ),
@@ -769,7 +769,7 @@ function SummaryCard({
769
769
  }) {
770
770
  return /* @__PURE__ */ jsxs("div", { className: "rounded-xl border border-gray-800 bg-gray-900/50 p-4", children: [
771
771
  /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-400", children: label }),
772
- /* @__PURE__ */ jsx("p", { className: "mt-1 truncate text-xl font-bold text-white", children: value }),
772
+ /* @__PURE__ */ jsx("p", { className: "mt-1 truncate text-xl font-bold text-gray-100", children: value }),
773
773
  sub && /* @__PURE__ */ jsx("p", { className: "mt-0.5 text-xs text-gray-500", children: sub })
774
774
  ] });
775
775
  }
@@ -802,7 +802,7 @@ function StatsPage() {
802
802
  return /* @__PURE__ */ jsxs("div", { children: [
803
803
  /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
804
804
  /* @__PURE__ */ jsxs("div", { children: [
805
- /* @__PURE__ */ jsx("h1", { className: "text-2xl font-bold text-white", children: "Stats" }),
805
+ /* @__PURE__ */ jsx("h1", { className: "text-2xl font-bold text-gray-100", children: "Stats" }),
806
806
  /* @__PURE__ */ jsx("p", { className: "mt-1 text-sm text-gray-400", children: "Usage analytics and project insights" })
807
807
  ] }),
808
808
  tab === "overview" && stats && /* @__PURE__ */ jsx(ExportDropdown, { options: [{
@@ -824,12 +824,12 @@ function StatsPage() {
824
824
  search: {
825
825
  tab: "overview"
826
826
  }
827
- }), className: `px-4 py-2 text-sm border-b-2 transition-colors ${tab === "overview" ? "border-brand-500 text-white" : "border-transparent text-gray-400 hover:text-gray-200"}`, children: "Overview" }),
827
+ }), className: `px-4 py-2 text-sm border-b-2 transition-colors ${tab === "overview" ? "border-brand-500 text-gray-100" : "border-transparent text-gray-400 hover:text-gray-200"}`, children: "Overview" }),
828
828
  /* @__PURE__ */ jsx("button", { onClick: () => navigate({
829
829
  search: {
830
830
  tab: "projects"
831
831
  }
832
- }), className: `px-4 py-2 text-sm border-b-2 transition-colors ${tab === "projects" ? "border-brand-500 text-white" : "border-transparent text-gray-400 hover:text-gray-200"}`, children: "Projects" })
832
+ }), className: `px-4 py-2 text-sm border-b-2 transition-colors ${tab === "projects" ? "border-brand-500 text-gray-100" : "border-transparent text-gray-400 hover:text-gray-200"}`, children: "Projects" })
833
833
  ] }),
834
834
  tab === "overview" ? /* @__PURE__ */ jsx(StatsOverview, { stats, isLoading, cost }) : /* @__PURE__ */ jsx("div", { className: "mt-6", children: /* @__PURE__ */ jsx(ProjectAnalytics, {}) })
835
835
  ] });
@@ -839,10 +839,16 @@ function StatsOverview({
839
839
  isLoading,
840
840
  cost
841
841
  }) {
842
+ const {
843
+ data: projectData
844
+ } = useQuery(projectAnalyticsQuery);
842
845
  if (isLoading) {
843
846
  return /* @__PURE__ */ jsxs("div", { className: "mt-6 space-y-4", children: [
844
- /* @__PURE__ */ jsx("div", { className: "grid grid-cols-2 gap-3 md:grid-cols-5", children: Array.from({
845
- length: 5
847
+ /* @__PURE__ */ jsx("div", { className: "grid grid-cols-2 gap-3 md:grid-cols-4", children: Array.from({
848
+ length: 4
849
+ }).map((_, i) => /* @__PURE__ */ jsx("div", { className: "h-20 animate-pulse rounded-xl bg-gray-900/50" }, i)) }),
850
+ /* @__PURE__ */ jsx("div", { className: "grid grid-cols-2 gap-3 md:grid-cols-4", children: Array.from({
851
+ length: 4
846
852
  }).map((_, i) => /* @__PURE__ */ jsx("div", { className: "h-20 animate-pulse rounded-xl bg-gray-900/50" }, i)) }),
847
853
  Array.from({
848
854
  length: 3
@@ -853,12 +859,20 @@ function StatsOverview({
853
859
  return /* @__PURE__ */ jsx("div", { className: "py-12 text-center text-sm text-gray-500", children: "No stats data found. Check ~/.claude/stats-cache.json" });
854
860
  }
855
861
  const totalTokens = Object.values(stats.modelUsage).reduce((sum, m) => sum + m.inputTokens + m.outputTokens, 0);
862
+ const totalToolCalls = stats.dailyActivity.reduce((sum, d) => sum + d.toolCallCount, 0);
863
+ const totalDurationMs = projectData?.projects.reduce((sum, p) => sum + p.totalDurationMs, 0) ?? 0;
864
+ const projectCount = projectData?.projects.length ?? 0;
856
865
  return /* @__PURE__ */ jsxs(Fragment, { children: [
857
- /* @__PURE__ */ jsxs("div", { className: "mt-6 grid grid-cols-2 gap-3 md:grid-cols-5", children: [
866
+ /* @__PURE__ */ jsxs("div", { className: "mt-6 grid grid-cols-2 gap-3 md:grid-cols-4", children: [
858
867
  /* @__PURE__ */ jsx(StatCard, { label: "Total Sessions", value: String(stats.totalSessions) }),
859
868
  /* @__PURE__ */ jsx(StatCard, { label: "Total Messages", value: stats.totalMessages.toLocaleString() }),
860
- /* @__PURE__ */ jsx(StatCard, { label: "Total Tokens", value: formatTokenCount(totalTokens), sub: cost ? `~${formatUSD(cost.totalUSD)}` : void 0 }),
861
- /* @__PURE__ */ jsx(StatCard, { label: "Total Estimated Cost", value: cost ? `~${formatUSD(cost.totalUSD)}` : "N/A" }),
869
+ /* @__PURE__ */ jsx(StatCard, { label: "Total Time", value: formatDuration(totalDurationMs) }),
870
+ /* @__PURE__ */ jsx(StatCard, { label: "Projects", value: String(projectCount) })
871
+ ] }),
872
+ /* @__PURE__ */ jsxs("div", { className: "mt-3 grid grid-cols-2 gap-3 md:grid-cols-4", children: [
873
+ /* @__PURE__ */ jsx(StatCard, { label: "Total Tokens", value: formatTokenCount(totalTokens) }),
874
+ /* @__PURE__ */ jsx(StatCard, { label: "Estimated Cost", value: cost ? `~${formatUSD(cost.totalUSD)}` : "N/A" }),
875
+ /* @__PURE__ */ jsx(StatCard, { label: "Tool Calls", value: totalToolCalls.toLocaleString() }),
862
876
  /* @__PURE__ */ jsx(StatCard, { label: "Longest Session", value: formatDuration(stats.longestSession.duration), sub: `${stats.longestSession.messageCount} messages` })
863
877
  ] }),
864
878
  /* @__PURE__ */ jsx("div", { className: "mt-6", children: /* @__PURE__ */ jsx(ContributionHeatmap, { dailyActivity: stats.dailyActivity, dailyModelTokens: stats.dailyModelTokens }) }),
@@ -877,7 +891,7 @@ function StatCard({
877
891
  }) {
878
892
  return /* @__PURE__ */ jsxs("div", { className: "rounded-xl border border-gray-800 bg-gray-900/50 p-4", children: [
879
893
  /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-400", children: label }),
880
- /* @__PURE__ */ jsx("p", { className: "mt-1 text-xl font-bold text-white", children: value }),
894
+ /* @__PURE__ */ jsx("p", { className: "mt-1 text-xl font-bold text-gray-100", children: value }),
881
895
  sub && /* @__PURE__ */ jsx("p", { className: "mt-0.5 text-xs text-gray-500", children: sub })
882
896
  ] });
883
897
  }
@@ -384,9 +384,9 @@ async function computeStatsFromSessions() {
384
384
  }
385
385
  }
386
386
  const getStats_createServerFn_handler = createServerRpc({
387
- id: "4b9a58c176f487b49800a372100037cdf33cf048f3592a449f115c7e3f5ea799",
387
+ id: "44af69d3bfcf3ec46fffb3f297d2b12cd7fe4db36c654b8a322df34d549c6493",
388
388
  name: "getStats",
389
- filename: "src/features/stats/stats.server.ts"
389
+ filename: "src/features/stats/stats.api.ts"
390
390
  }, (opts) => getStats.__executeServer(opts));
391
391
  const getStats = createServerFn({
392
392
  method: "GET"
@@ -1,7 +1,7 @@
1
1
  import { jsxs, jsx } from "react/jsx-runtime";
2
2
  import { useState, useRef, useEffect, useMemo } from "react";
3
3
  import { useQuery } from "@tanstack/react-query";
4
- import { s as settingsQuery } from "./settings.queries-DSQd324O.js";
4
+ import { s as settingsQuery } from "./settings.queries-BVEZA-1G.js";
5
5
  import { b as DEFAULT_PRICING, n as normalizeModelId } from "./settings.types-DntadCHo.js";
6
6
  function escapeCSVField(value) {
7
7
  if (value.includes(",") || value.includes('"') || value.includes("\n")) {
@@ -123,6 +123,7 @@ const createServerFn = (options, __opts) => {
123
123
  ...newOptions.middleware || [],
124
124
  serverFnBaseToMiddleware(newOptions)
125
125
  ];
126
+ extractedFn.method = resolvedOptions.method;
126
127
  return Object.assign(
127
128
  async (opts) => {
128
129
  const result = await executeMiddleware$1(resolvedMiddleware, "client", {
@@ -144,6 +145,9 @@ const createServerFn = (options, __opts) => {
144
145
  {
145
146
  // This copies over the URL, function ID
146
147
  ...extractedFn,
148
+ // Expose the declared HTTP method so the server handler
149
+ // can reject mismatched methods before parsing payloads
150
+ method: resolvedOptions.method,
147
151
  // The extracted function on the server-side calls
148
152
  // this function
149
153
  __executeServer: async (opts) => {
@@ -423,7 +427,7 @@ function getResponse() {
423
427
  return event.res;
424
428
  }
425
429
  async function getStartManifest(matchedRoutes) {
426
- const { tsrStartManifest } = await import("./assets/_tanstack-start-manifest_v-Bzp9kpGN.js");
430
+ const { tsrStartManifest } = await import("./assets/_tanstack-start-manifest_v-Dva5sIpS.js");
427
431
  const startManifest = tsrStartManifest();
428
432
  const rootRoute = startManifest.routes[rootRouteId] = startManifest.routes[rootRouteId] || {};
429
433
  rootRoute.assets = rootRoute.assets || [];
@@ -577,30 +581,33 @@ function createMultiplexedStream(jsonStream, rawStreams) {
577
581
  }
578
582
  });
579
583
  }
580
- const manifest = { "4b9a58c176f487b49800a372100037cdf33cf048f3592a449f115c7e3f5ea799": {
584
+ const manifest = { "44af69d3bfcf3ec46fffb3f297d2b12cd7fe4db36c654b8a322df34d549c6493": {
581
585
  functionName: "getStats_createServerFn_handler",
582
- importer: () => import("./assets/stats.server-iJ_z7eUN.js")
583
- }, "ff8a3161afdfa175e9c519e4146a56ab5bce6e80745e99cfc2191ebbb7a859bb": {
586
+ importer: () => import("./assets/stats.api-CH-wTCGE.js")
587
+ }, "71794080473579a94431392ab409ebd02772f6a9f6a08386cadbb8c0d3cf804a": {
584
588
  functionName: "getSessionDetail_createServerFn_handler",
585
- importer: () => import("./assets/session-detail.server-CO5XwLSv.js")
586
- }, "810657681a273df5b4e58f0d8fcc6a5451598b489431b9bcaa98eea0ad815da8": {
589
+ importer: () => import("./assets/session-detail.api-8plxSeB0.js")
590
+ }, "39e65590d2bc41f653f54a9b6a9e0a72f185da275304c0a4a595d811cf185572": {
591
+ functionName: "getProjectAnalytics_createServerFn_handler",
592
+ importer: () => import("./assets/project-analytics.api-QnhRRs7T.js")
593
+ }, "04ac41a7e3e644815167d098c2d6c3375d00a72a11e5af0d37033ba771081ba9": {
594
+ functionName: "getAppInfo_createServerFn_handler",
595
+ importer: () => import("./assets/app-info.api-CdaWsxHl.js")
596
+ }, "72f81ef9e8fa751bab60a8bdabd7e77816e2a6723a5e6e26e03712c01b3a249c": {
587
597
  functionName: "getSettings_createServerFn_handler",
588
- importer: () => import("./assets/settings.server-6B2PvLgf.js")
589
- }, "3050115d92ca91ab1fd8fd698e33076328aae80dc64ca27c088eee16cebccc1a": {
598
+ importer: () => import("./assets/settings.api-C9L2GoIE.js")
599
+ }, "7fe8c2b131c4fc81aa9a2570aec79640ff84603fe0060d13c24928b7329cb236": {
590
600
  functionName: "saveSettings_createServerFn_handler",
591
- importer: () => import("./assets/settings.server-6B2PvLgf.js")
592
- }, "bf8e4a7901f1843bdc9c46be1ad5ad59c615b8bbe611b73eb3ff28f20e43ee0d": {
601
+ importer: () => import("./assets/settings.api-C9L2GoIE.js")
602
+ }, "8fd6c4e5b4d5590acf1ec73da75f249978e8aced6dd2be23de06ade8431033be": {
593
603
  functionName: "getSessionList_createServerFn_handler",
594
- importer: () => import("./assets/sessions.server-CTeIkXCV.js")
595
- }, "839d29fe93dfa2a6d506af7b48ca25197190a5ff4c796e970ddfdc6e8c98827f": {
604
+ importer: () => import("./assets/sessions.api-DRmOjipJ.js")
605
+ }, "946cc550946f64ee7985dc35913a690eb13183d7ba83cffe398e424e697b4265": {
596
606
  functionName: "getActiveSessionList_createServerFn_handler",
597
- importer: () => import("./assets/sessions.server-CTeIkXCV.js")
598
- }, "a3f42f9012fd83586787da8f7cb90649da739dd947d867eb67572f68735ff495": {
607
+ importer: () => import("./assets/sessions.api-DRmOjipJ.js")
608
+ }, "e574977967ea9b3387e72d70704b6ca87230e72becaf69f0b98cbc91c9cd1339": {
599
609
  functionName: "getPaginatedSessions_createServerFn_handler",
600
- importer: () => import("./assets/sessions.server-CTeIkXCV.js")
601
- }, "64052f224a1d6696436e5d3deeee2b798f0742e1292ffabd038c3a7bf75e6fcb": {
602
- functionName: "getProjectAnalytics_createServerFn_handler",
603
- importer: () => import("./assets/project-analytics.server-kTooOGl-.js")
610
+ importer: () => import("./assets/sessions.api-DRmOjipJ.js")
604
611
  } };
605
612
  async function getServerFnById(id) {
606
613
  const serverFnInfo = manifest[id];
@@ -636,9 +643,19 @@ const handleServerAction = async ({
636
643
  }) => {
637
644
  const method = request.method;
638
645
  const methodUpper = method.toUpperCase();
639
- const methodLower = method.toLowerCase();
640
646
  const url = new URL(request.url);
641
647
  const action = await getServerFnById(serverFnId);
648
+ if (action.method && methodUpper !== action.method) {
649
+ return new Response(
650
+ `expected ${action.method} method. Got ${methodUpper}`,
651
+ {
652
+ status: 405,
653
+ headers: {
654
+ Allow: action.method
655
+ }
656
+ }
657
+ );
658
+ }
642
659
  const isServerFn = request.headers.get("x-tsr-serverFn") === "true";
643
660
  if (!serovalPlugins) {
644
661
  serovalPlugins = getDefaultSerovalPlugins();
@@ -767,7 +784,7 @@ const handleServerAction = async ({
767
784
  (type) => contentType && contentType.includes(type)
768
785
  )) {
769
786
  invariant(
770
- methodLower !== "get",
787
+ methodUpper !== "GET",
771
788
  "GET requests with FormData payloads are not supported"
772
789
  );
773
790
  const formData = await request.formData();
@@ -796,7 +813,7 @@ const handleServerAction = async ({
796
813
  }
797
814
  return await action(params);
798
815
  }
799
- if (methodLower === "get") {
816
+ if (methodUpper === "GET") {
800
817
  const payloadParam = url.searchParams.get("payload");
801
818
  if (payloadParam && payloadParam.length > MAX_PAYLOAD_SIZE) {
802
819
  throw new Error("Payload too large");
@@ -806,9 +823,6 @@ const handleServerAction = async ({
806
823
  payload2.method = methodUpper;
807
824
  return await action(payload2);
808
825
  }
809
- if (methodLower !== "post") {
810
- throw new Error("expected POST method");
811
- }
812
826
  let jsonPayload;
813
827
  if (contentType?.includes("application/json")) {
814
828
  jsonPayload = await request.json();
@@ -1016,7 +1030,7 @@ let entriesPromise;
1016
1030
  let baseManifestPromise;
1017
1031
  let cachedFinalManifestPromise;
1018
1032
  async function loadEntries() {
1019
- const routerEntry = await import("./assets/router-Cd4jLk4T.js").then((n) => n.r);
1033
+ const routerEntry = await import("./assets/router-BvYNknMb.js").then((n) => n.r);
1020
1034
  const startEntry = await import("./assets/start-HYkvq4Ni.js");
1021
1035
  return { startEntry, routerEntry };
1022
1036
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-session-dashboard",
3
- "version": "0.4.3",
3
+ "version": "0.4.5",
4
4
  "description": "Local observability dashboard for Claude Code sessions",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -55,6 +55,9 @@
55
55
  "recharts": "^3.7.0",
56
56
  "zod": "^4.3.6"
57
57
  },
58
+ "overrides": {
59
+ "picomatch": "^4.0.0"
60
+ },
58
61
  "devDependencies": {
59
62
  "@eslint/js": "^9.39.2",
60
63
  "@playwright/test": "^1.58.2",
@@ -1 +0,0 @@
1
- import{j as e,u as o,L as r,O as i}from"./main-CV28H4XG.js";import{u as c}from"./createServerFn-BYTDoNe-.js";import{a as x}from"./sessions.queries-tzrs5GhP.js";function h(){const{data:t}=c(x),a=t?.length??0;return a===0?null:e.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:a})}const d=[{to:"/sessions",label:"Sessions",icon:e.jsxs("svg",{className:"h-4 w-4",viewBox:"0 0 16 16",fill:"none",stroke:"currentColor",strokeWidth:"1.5",children:[e.jsx("line",{x1:"2",y1:"4",x2:"14",y2:"4"}),e.jsx("line",{x1:"2",y1:"8",x2:"14",y2:"8"}),e.jsx("line",{x1:"2",y1:"12",x2:"14",y2:"12"})]})},{to:"/stats",label:"Stats",icon:e.jsxs("svg",{className:"h-4 w-4",viewBox:"0 0 16 16",fill:"currentColor",children:[e.jsx("rect",{x:"1",y:"8",width:"3",height:"7",rx:"0.5"}),e.jsx("rect",{x:"6.5",y:"4",width:"3",height:"11",rx:"0.5"}),e.jsx("rect",{x:"12",y:"1",width:"3",height:"14",rx:"0.5"})]})},{to:"/settings",label:"Settings",icon:e.jsxs("svg",{className:"h-4 w-4",viewBox:"0 0 16 16",fill:"currentColor",children:[e.jsx("path",{d:"M8 10a2 2 0 100-4 2 2 0 000 4z"}),e.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"})]})}];function m({children:t}){const a=o(),l=a[a.length-1]?.pathname??"";return e.jsxs("div",{className:"flex min-h-screen",children:[e.jsxs("aside",{className:"flex w-56 shrink-0 flex-col border-r border-gray-800 bg-gray-950",children:[e.jsx("div",{className:"flex h-14 items-center border-b border-gray-800 px-4",children:e.jsxs(r,{to:"/sessions",className:"text-sm font-bold text-white",children:[e.jsx("span",{className:"text-brand-500",children:"Claude"})," Dashboard"]})}),e.jsx("nav",{className:"flex-1 p-3",children:d.map(s=>{const n=l.startsWith(s.to);return e.jsxs(r,{to:s.to,className:`flex items-center gap-2.5 rounded-lg px-3 py-2 text-sm transition-colors ${n?"bg-gray-800 text-white":"text-gray-400 hover:bg-gray-800/50 hover:text-gray-200"}`,children:[e.jsx("span",{className:"text-gray-500",children:s.icon}),s.label,s.to==="/sessions"&&e.jsx(h,{})]},s.to)})}),e.jsx("div",{className:"border-t border-gray-800 p-3",children:e.jsxs("div",{className:"flex items-center justify-between",children:[e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx("a",{href:"https://github.com/dlupiak/claude-session-dashboard",target:"_blank",rel:"noopener noreferrer",className:"text-gray-500 hover:text-gray-300 transition-colors",title:"GitHub Repository",children:e.jsx("svg",{className:"w-4 h-4",viewBox:"0 0 16 16",fill:"currentColor",children:e.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"})})}),e.jsx("a",{href:"https://www.npmjs.com/package/claude-session-dashboard",target:"_blank",rel:"noopener noreferrer",className:"text-gray-500 hover:text-gray-300 transition-colors",title:"npm Package",children:e.jsx("svg",{className:"w-4 h-4",viewBox:"0 0 16 16",fill:"currentColor",children:e.jsx("path",{d:"M0 0v16h16V0H0zm13 13H8V5H5v8H3V3h10v10z"})})})]}),e.jsx("p",{className:"text-xs text-gray-600",children:"Read-only"})]})})]}),e.jsx("main",{className:"flex-1 overflow-auto",children:e.jsx("div",{className:"mx-auto max-w-5xl px-6 py-6",children:t})})]})}function j(){return e.jsx(m,{children:e.jsx(i,{})})}export{j as component};