tokentrace 0.15.0 → 0.15.2

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 (41) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/app/discovery/page.tsx +2 -2
  3. package/app/evidence/page.tsx +3 -3
  4. package/app/globals.css +51 -3
  5. package/app/guide/page.tsx +4 -4
  6. package/app/parser-debug/page.tsx +3 -3
  7. package/app/sessions/[id]/page.tsx +1 -1
  8. package/app/sessions/page.tsx +1 -1
  9. package/components/charts/rank-bar-chart.tsx +3 -3
  10. package/components/charts/trend-chart.tsx +7 -5
  11. package/components/charts/trend-section.tsx +5 -3
  12. package/components/diagnostics/parser-panels.tsx +2 -2
  13. package/components/overview/recommendations-card.tsx +1 -1
  14. package/components/overview/review-status-strip.tsx +1 -1
  15. package/components/overview/summary-cards.tsx +6 -6
  16. package/components/period-filter.tsx +37 -39
  17. package/components/pricing/model-alias-suggestions-table.tsx +1 -1
  18. package/components/pricing/model-rates-table.tsx +1 -1
  19. package/components/pricing-settings.tsx +1 -1
  20. package/components/repair/repair-guidance.tsx +1 -1
  21. package/components/repair/repair-items-table.tsx +2 -2
  22. package/components/repair-state-control.tsx +2 -2
  23. package/components/scan-health-summary.tsx +1 -1
  24. package/components/scan-now-button.tsx +2 -2
  25. package/components/session-explorer.tsx +9 -8
  26. package/components/settings/import-profiles-section.tsx +2 -2
  27. package/components/settings/section-nav.tsx +2 -2
  28. package/components/sidebar.tsx +2 -2
  29. package/components/ui/badge.tsx +1 -1
  30. package/components/ui/button.tsx +1 -1
  31. package/components/ui/help-tooltip.tsx +1 -1
  32. package/components/ui/input.tsx +1 -1
  33. package/components/ui/textarea.tsx +1 -1
  34. package/components/ui/typography.tsx +1 -1
  35. package/package.json +11 -11
  36. package/postcss.config.mjs +1 -2
  37. package/scripts/package-inspect.mjs +2 -2
  38. package/server.json +2 -2
  39. package/src/cli/runtime.js +1 -2
  40. package/tsconfig.json +20 -5
  41. package/tailwind.config.ts +0 -53
package/CHANGELOG.md CHANGED
@@ -4,6 +4,35 @@ All notable changes to TokenTrace are documented here.
4
4
 
5
5
  ## Unreleased
6
6
 
7
+ ## [0.15.2] - 2026-05-23
8
+
9
+ ### Fixed
10
+
11
+ - Period filter presets (Today / 7d / 30d / 60d / 90d / Month / All time) now navigate correctly when clicked on the production webpack build. Previously the preset `<Link>` elements were rendered inside the custom-date `<form>`, and under Next.js 16 + React 19 the form was eating Link clicks, leaving the URL and rendered data unchanged. The preset links are now siblings of the form rather than children.
12
+
13
+ ## [0.15.1] - 2026-05-22
14
+
15
+ ### Changed
16
+
17
+ - Upgraded Next.js from 15 to 16 (and `eslint-config-next` to 16). Lint config now uses `eslint-config-next`'s native flat config; the `FlatCompat`/`@eslint/eslintrc` bridge is no longer needed.
18
+ - Upgraded Tailwind CSS from 3 to 4 via `@tailwindcss/upgrade`. Tailwind config moved from `tailwind.config.ts` into `@theme` directives in `app/globals.css`; `postcss.config.mjs` now uses `@tailwindcss/postcss`; `autoprefixer` is removed (built into Tailwind 4).
19
+ - Upgraded `tailwind-merge` from 2 to 3 (gated on Tailwind 4).
20
+ - Upgraded Recharts from 2 to 3.
21
+ - Upgraded Vitest from 3 to 4 (added `@vitejs/plugin-react` so React/JSX is parsed by Vite instead of Rolldown's parser).
22
+ - Upgraded TypeScript from 5 to 6. Removed the deprecated `baseUrl` from `tsconfig.json` (path mapping still works under `moduleResolution: "bundler"`).
23
+ - Bumped minimum Node.js engine to `>=20.9.0` (required by Next 16).
24
+
25
+ ### Fixed
26
+
27
+ - Replaced three `useEffect` -> `setState` synchronization patterns with the React-recommended previous-value comparison (in `components/charts/trend-chart.tsx`, `components/charts/trend-section.tsx`, `components/session-explorer.tsx`) to satisfy the new `react-hooks/set-state-in-effect` rule and avoid cascading renders.
28
+ - Reverted the Tailwind 4 codemod's accidental rename of the `"outline"` button/badge `variant` name to `"outline-solid"` (it is an internal variant label, not a Tailwind class).
29
+ - Wrapped Recharts `Tooltip.labelFormatter` to match the new v3 signature.
30
+ - Parallelized 10-spawn CLI subprocess test in `tests/serve-command.test.ts` so it no longer times out under parallel test contention; raised global Vitest timeouts and `spawnSync` timeouts in remaining CLI subprocess tests to match.
31
+
32
+ ### Deferred
33
+
34
+ - ESLint 9 -> 10: blocked on `eslint-plugin-react` (peer-pinned by `eslint-config-next`); the latest 7.37.5 caps its eslint peer at `^9.7`. Staying on ESLint 9.39.4.
35
+
7
36
  ## [0.15.0] - 2026-05-22
8
37
 
9
38
  ### Changed
@@ -70,7 +70,7 @@ export default function DiscoveryPage() {
70
70
  <CardDescription>Latest 500 discovered files. Ignored files are retained so support-file noise stays visible without becoming usage.</CardDescription>
71
71
  </CardHeader>
72
72
  <CardContent className="table-scroll">
73
- <Table className="min-w-[64rem]">
73
+ <Table className="min-w-5xl">
74
74
  <TableHeader>
75
75
  <TableRow>
76
76
  <TableHead>File</TableHead>
@@ -91,7 +91,7 @@ export default function DiscoveryPage() {
91
91
  <TableCell>{file.sizeBytes.toLocaleString()} bytes</TableCell>
92
92
  <TableCell>{formatDate(file.modifiedTime)}</TableCell>
93
93
  <TableCell>{file.parser ?? "None"}</TableCell>
94
- <TableCell className="max-w-sm whitespace-normal break-words text-xs leading-relaxed text-muted-foreground">
94
+ <TableCell className="max-w-sm whitespace-normal wrap-break-word text-xs leading-relaxed text-muted-foreground">
95
95
  {String(file.rawMetadata.ignoreReason ?? file.rawMetadata.reason ?? file.errors[0] ?? file.warnings[0] ?? "None")}
96
96
  </TableCell>
97
97
  </TableRow>
@@ -111,7 +111,7 @@ function EvidenceMetricTabs({
111
111
  key={item.metric}
112
112
  href={mergeHrefParams(`/evidence?metric=${item.metric}`, rangeLinkParams)}
113
113
  className={cn(
114
- "inline-flex h-8 items-center rounded-md border px-3 text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
114
+ "inline-flex h-8 items-center rounded-md border px-3 text-sm font-medium transition-colors focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
115
115
  current === item.metric
116
116
  ? "border-primary bg-primary text-primary-foreground"
117
117
  : "border-border bg-card text-foreground hover:bg-muted"
@@ -141,7 +141,7 @@ function EvidenceDrilldownStrip({
141
141
  key={action.label}
142
142
  href={action.href}
143
143
  className={cn(
144
- "group min-w-0 p-3 transition-colors hover:bg-muted/50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
144
+ "group min-w-0 p-3 transition-colors hover:bg-muted/50 focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
145
145
  index > 0 ? "border-t border-border md:border-l md:border-t-0" : ""
146
146
  )}
147
147
  >
@@ -204,7 +204,7 @@ function EvidenceContextPanel({
204
204
  <Link
205
205
  key={action.label}
206
206
  href={action.href}
207
- className="group rounded-md border bg-card p-3 text-left transition-colors hover:bg-muted/40 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
207
+ className="group rounded-md border bg-card p-3 text-left transition-colors hover:bg-muted/40 focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
208
208
  >
209
209
  <span className="flex items-center gap-1.5 text-xs font-semibold text-foreground">
210
210
  {action.label}
package/app/globals.css CHANGED
@@ -1,6 +1,54 @@
1
- @tailwind base;
2
- @tailwind components;
3
- @tailwind utilities;
1
+ @import 'tailwindcss';
2
+
3
+ @custom-variant dark (&:is(.dark *));
4
+
5
+ @theme {
6
+ --color-border: hsl(var(--border));
7
+ --color-input: hsl(var(--input));
8
+ --color-ring: hsl(var(--ring));
9
+ --color-background: hsl(var(--background));
10
+ --color-foreground: hsl(var(--foreground));
11
+
12
+ --color-primary: hsl(var(--primary));
13
+ --color-primary-foreground: hsl(var(--primary-foreground));
14
+
15
+ --color-secondary: hsl(var(--secondary));
16
+ --color-secondary-foreground: hsl(var(--secondary-foreground));
17
+
18
+ --color-muted: hsl(var(--muted));
19
+ --color-muted-foreground: hsl(var(--muted-foreground));
20
+
21
+ --color-accent: hsl(var(--accent));
22
+ --color-accent-foreground: hsl(var(--accent-foreground));
23
+
24
+ --color-destructive: hsl(var(--destructive));
25
+ --color-destructive-foreground: hsl(var(--destructive-foreground));
26
+
27
+ --color-card: hsl(var(--card));
28
+ --color-card-foreground: hsl(var(--card-foreground));
29
+
30
+ --radius-lg: 0.5rem;
31
+ --radius-md: 0.375rem;
32
+ --radius-sm: 0.25rem;
33
+ }
34
+
35
+ /*
36
+ The default border color has changed to `currentcolor` in Tailwind CSS v4,
37
+ so we've added these compatibility styles to make sure everything still
38
+ looks the same as it did with Tailwind CSS v3.
39
+
40
+ If we ever want to remove these styles, we need to add an explicit border
41
+ color utility to any element that depends on these defaults.
42
+ */
43
+ @layer base {
44
+ *,
45
+ ::after,
46
+ ::before,
47
+ ::backdrop,
48
+ ::file-selector-button {
49
+ border-color: var(--color-gray-200, currentcolor);
50
+ }
51
+ }
4
52
 
5
53
  :root {
6
54
  --background: 40 33% 98%;
@@ -421,7 +421,7 @@ export default function GuidePage() {
421
421
  </div>
422
422
  </div>
423
423
  <div className="table-scroll">
424
- <Table className="min-w-[42rem]">
424
+ <Table className="min-w-2xl">
425
425
  <TableHeader>
426
426
  <TableRow>
427
427
  <TableHead>Label</TableHead>
@@ -527,7 +527,7 @@ export default function GuidePage() {
527
527
  {mcpAgentEntries.map(([label, value, detail]) => (
528
528
  <div key={label} className="rounded-md border bg-muted/30 p-3">
529
529
  <FieldLabel>{label}</FieldLabel>
530
- <div className="mt-2 min-h-10 break-words text-sm font-medium leading-5">
530
+ <div className="mt-2 min-h-10 wrap-break-word text-sm font-medium leading-5">
531
531
  <MonoText>{value}</MonoText>
532
532
  </div>
533
533
  <p className="mt-2 text-xs leading-5 text-muted-foreground">{detail}</p>
@@ -538,7 +538,7 @@ export default function GuidePage() {
538
538
  <div className="border-t p-4">
539
539
  <h3 className="text-sm font-semibold leading-tight">Agent quickstart</h3>
540
540
  <div className="table-scroll mt-3">
541
- <Table className="min-w-[46rem]">
541
+ <Table className="min-w-184">
542
542
  <TableHeader>
543
543
  <TableRow>
544
544
  <TableHead>Step</TableHead>
@@ -570,7 +570,7 @@ export default function GuidePage() {
570
570
  Use <MonoText>tokentrace roadmap --json</MonoText> or <MonoText>/api/roadmap</MonoText> for implementation status and release gates.
571
571
  </p>
572
572
  <div className="table-scroll mt-3">
573
- <Table className="min-w-[44rem]">
573
+ <Table className="min-w-176">
574
574
  <TableHeader>
575
575
  <TableRow>
576
576
  <TableHead>Surface</TableHead>
@@ -92,14 +92,14 @@ export default async function ParserDebugPage({
92
92
  <TableCell>{file.parser ?? "None"}</TableCell>
93
93
  <TableCell><Badge variant={file.errors.length ? "destructive" : file.parser ? "success" : "secondary"}>{file.status}</Badge></TableCell>
94
94
  <TableCell>{file.rawMetadata.confidence == null ? "Unknown" : Number(file.rawMetadata.confidence).toFixed(2)}</TableCell>
95
- <TableCell className="max-w-xs whitespace-normal break-words">
95
+ <TableCell className="max-w-xs whitespace-normal wrap-break-word">
96
96
  <MonoText className="text-muted-foreground">
97
97
  {JSON.stringify(file.rawMetadata.tokenConfidence ?? { unknown: 0 })}
98
98
  </MonoText>
99
99
  </TableCell>
100
100
  <TableCell>{file.recordsImported.toLocaleString()}</TableCell>
101
- <TableCell className="max-w-sm whitespace-normal break-words text-xs leading-relaxed text-muted-foreground">{file.warnings.join("; ") || "None"}</TableCell>
102
- <TableCell className="max-w-sm whitespace-normal break-words text-xs leading-relaxed text-muted-foreground">{file.errors.join("; ") || "None"}</TableCell>
101
+ <TableCell className="max-w-sm whitespace-normal wrap-break-word text-xs leading-relaxed text-muted-foreground">{file.warnings.join("; ") || "None"}</TableCell>
102
+ <TableCell className="max-w-sm whitespace-normal wrap-break-word text-xs leading-relaxed text-muted-foreground">{file.errors.join("; ") || "None"}</TableCell>
103
103
  <TableCell className="max-w-md break-all" title={file.path}>
104
104
  <MonoText>{file.path}</MonoText>
105
105
  </TableCell>
@@ -211,7 +211,7 @@ export default async function SessionTimelinePage({
211
211
  <CardDescription>Interaction rows are expanded with model changes, spikes, cache activity, unknown cost, and tool calls.</CardDescription>
212
212
  </CardHeader>
213
213
  <CardContent className="table-scroll">
214
- <Table className="min-w-[78rem]">
214
+ <Table className="min-w-312">
215
215
  <TableHeader>
216
216
  <TableRow>
217
217
  <TableHead className="w-36">Time</TableHead>
@@ -45,7 +45,7 @@ export default async function SessionsPage({
45
45
  </CardDescription>
46
46
  </CardHeader>
47
47
  <CardContent className="table-scroll overflow-x-auto">
48
- <Table className="min-w-[54rem]">
48
+ <Table className="min-w-216">
49
49
  <TableHeader>
50
50
  <TableRow>
51
51
  <TableHead>Priority</TableHead>
@@ -8,11 +8,11 @@ function ChartSkeleton() {
8
8
  return (
9
9
  <div className="grid h-full grid-cols-[7rem_minmax(0,1fr)]" aria-label="Chart loading">
10
10
  <div className="space-y-10 border-r border-border/70 pt-10">
11
- <div className="h-3 w-20 rounded bg-muted" />
12
- <div className="h-3 w-16 rounded bg-muted" />
11
+ <div className="h-3 w-20 rounded-sm bg-muted" />
12
+ <div className="h-3 w-16 rounded-sm bg-muted" />
13
13
  </div>
14
14
  <div className="relative overflow-hidden p-8">
15
- <div className="absolute inset-0 bg-[linear-gradient(to_right,#e7ded3_1px,transparent_1px),linear-gradient(to_bottom,#e7ded3_1px,transparent_1px)] bg-[size:25%_33%] opacity-80" />
15
+ <div className="absolute inset-0 bg-[linear-gradient(to_right,#e7ded3_1px,transparent_1px),linear-gradient(to_bottom,#e7ded3_1px,transparent_1px)] bg-size-[25%_33%] opacity-80" />
16
16
  <div className="relative mt-8 h-10 w-[82%] rounded-r bg-primary/20" />
17
17
  <div className="relative mt-10 h-10 w-[22%] rounded-r bg-primary/15" />
18
18
  </div>
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
 
3
3
  import * as React from "react";
4
- import { useEffect, useMemo, useState } from "react";
4
+ import { useMemo, useState } from "react";
5
5
  import { Area, AreaChart, CartesianGrid, Tooltip, XAxis, YAxis } from "recharts";
6
6
  import type { TrendPoint } from "@/src/lib/analytics";
7
7
  import { formatCurrency, formatShortDate, formatTokens } from "@/src/lib/format";
@@ -41,7 +41,7 @@ function ChartSkeleton() {
41
41
  <div className="grid h-full grid-cols-[2.5rem_minmax(0,1fr)] grid-rows-[minmax(0,1fr)_1.5rem]" aria-label="Chart loading">
42
42
  <div className="row-span-1 border-r border-border/70" />
43
43
  <div className="relative overflow-hidden">
44
- <div className="absolute inset-0 bg-[linear-gradient(to_right,#e7ded3_1px,transparent_1px),linear-gradient(to_bottom,#e7ded3_1px,transparent_1px)] bg-[size:25%_33%] opacity-80" />
44
+ <div className="absolute inset-0 bg-[linear-gradient(to_right,#e7ded3_1px,transparent_1px),linear-gradient(to_bottom,#e7ded3_1px,transparent_1px)] bg-size-[25%_33%] opacity-80" />
45
45
  <div className="absolute bottom-[18%] left-[8%] h-[38%] w-[84%] rounded-t-full border-t-4 border-primary/25" />
46
46
  <div className="absolute bottom-[12%] left-[8%] h-2 w-[84%] rounded-full bg-muted" />
47
47
  </div>
@@ -137,11 +137,13 @@ export function TrendChart({
137
137
  }) {
138
138
  const [internalPeriod, setInternalPeriod] = useState<TrendBucket>("daily");
139
139
  const [internalTrendWindow, setInternalTrendWindow] = useState<TrendWindow>(defaultWindow);
140
+ const [prevDefaultWindow, setPrevDefaultWindow] = useState<TrendWindow>(defaultWindow);
140
141
  const { ref: chartRef, size } = useChartSize<HTMLDivElement>();
141
142
 
142
- useEffect(() => {
143
+ if (defaultWindow !== prevDefaultWindow) {
144
+ setPrevDefaultWindow(defaultWindow);
143
145
  if (!controlledTrendWindow) setInternalTrendWindow(defaultWindow);
144
- }, [controlledTrendWindow, defaultWindow]);
146
+ }
145
147
 
146
148
  const period = controlledPeriod ?? internalPeriod;
147
149
  const trendWindow = controlledTrendWindow ?? internalTrendWindow;
@@ -217,7 +219,7 @@ export function TrendChart({
217
219
  ? formatCurrency(Number(value))
218
220
  : `${formatTokens(Number(value))} tokens`
219
221
  }
220
- labelFormatter={formatShortDate}
222
+ labelFormatter={(label) => formatShortDate(label as string | number | null | undefined)}
221
223
  />
222
224
  <Area
223
225
  type="monotone"
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
 
3
3
  import * as React from "react";
4
- import { useEffect, useState } from "react";
4
+ import { useState } from "react";
5
5
  import type { TrendPoint } from "@/src/lib/analytics";
6
6
  import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
7
7
  import { Badge } from "@/components/ui/badge";
@@ -23,10 +23,12 @@ export function TrendSection({
23
23
  }) {
24
24
  const [period, setPeriod] = useState<TrendBucket>("daily");
25
25
  const [trendWindow, setTrendWindow] = useState<TrendWindow>(defaultWindow);
26
+ const [prevDefaultWindow, setPrevDefaultWindow] = useState<TrendWindow>(defaultWindow);
26
27
 
27
- useEffect(() => {
28
+ if (defaultWindow !== prevDefaultWindow) {
29
+ setPrevDefaultWindow(defaultWindow);
28
30
  setTrendWindow(defaultWindow);
29
- }, [defaultWindow]);
31
+ }
30
32
 
31
33
  return (
32
34
  <section className="space-y-3">
@@ -19,7 +19,7 @@ export function ParserTrustPanel({ report }: { report: DoctorReport["parserTrust
19
19
  </CardHeader>
20
20
  <CardContent className="table-scroll">
21
21
  {report.parsers.length ? (
22
- <Table className="min-w-[72rem]">
22
+ <Table className="min-w-6xl">
23
23
  <TableHeader>
24
24
  <TableRow>
25
25
  <TableHead>Parser</TableHead>
@@ -249,7 +249,7 @@ export function SourceCoveragePanel({ scanFiles }: { scanFiles: ReturnType<typeo
249
249
  <p className="mt-1 text-xs leading-relaxed text-muted-foreground">{entry.description}</p>
250
250
  <div className="mt-2 flex flex-wrap gap-1">
251
251
  {entry.matchers.map((matcher) => (
252
- <code key={matcher} className="rounded bg-muted px-1.5 py-0.5 text-xs">{matcher}</code>
252
+ <code key={matcher} className="rounded-sm bg-muted px-1.5 py-0.5 text-xs">{matcher}</code>
253
253
  ))}
254
254
  </div>
255
255
  </div>
@@ -23,7 +23,7 @@ export function OverviewRecommendationsCard({
23
23
  <li key={item.id} className="min-w-0">
24
24
  <Link
25
25
  href={item.href}
26
- className="group flex h-full min-w-0 gap-3 px-4 py-4 transition-colors hover:bg-muted/30 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
26
+ className="group flex h-full min-w-0 gap-3 px-4 py-4 transition-colors hover:bg-muted/30 focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
27
27
  >
28
28
  <span className="mt-0.5 flex h-6 w-6 shrink-0 items-center justify-center rounded-full border border-border text-xs font-semibold tabular-nums text-muted-foreground">
29
29
  {index + 1}
@@ -41,7 +41,7 @@ function OverviewReviewStatusTile({
41
41
  <Link
42
42
  href={href}
43
43
  className={cn(
44
- "group flex min-w-0 gap-3 p-3 transition-colors hover:bg-muted/40 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
44
+ "group flex min-w-0 gap-3 p-3 transition-colors hover:bg-muted/40 focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
45
45
  className
46
46
  )}
47
47
  >
@@ -36,7 +36,7 @@ function CostSessionsMetricPane({
36
36
  return (
37
37
  <section
38
38
  className={cn(
39
- "cost-sessions-section grid min-w-0 grid-rows-[auto_auto_auto_auto_1fr_auto] p-4 md:row-span-6 md:[grid-template-rows:subgrid]",
39
+ "cost-sessions-section grid min-w-0 grid-rows-[auto_auto_auto_auto_1fr_auto] p-4 md:row-span-6 md:grid-rows-subgrid",
40
40
  className
41
41
  )}
42
42
  >
@@ -48,7 +48,7 @@ function CostSessionsMetricPane({
48
48
  </div>
49
49
 
50
50
  <div className="mt-3">
51
- <DataValue size="lg" className={cn("break-words leading-none", valueClassName)}>{value}</DataValue>
51
+ <DataValue size="lg" className={cn("wrap-break-word leading-none", valueClassName)}>{value}</DataValue>
52
52
  </div>
53
53
 
54
54
  <div className="mt-3 flex flex-wrap items-start gap-x-2 gap-y-1 text-xs leading-snug text-muted-foreground">
@@ -118,11 +118,11 @@ export function CostSessionsCard({
118
118
  <CardDescription>Model-rate trust and imported activity in one place.</CardDescription>
119
119
  </CardHeader>
120
120
  <CardContent className="flex flex-1 flex-col p-0">
121
- <div className="grid flex-1 border-t md:grid-cols-2 md:[grid-template-rows:auto_auto_auto_auto_1fr_auto]">
121
+ <div className="grid flex-1 border-t md:grid-cols-2 md:grid-rows-[auto_auto_auto_auto_1fr_auto]">
122
122
  <CostSessionsMetricPane
123
123
  label="Cost"
124
124
  value={formatCurrency(summary.totalCost)}
125
- valueClassName="break-words text-2xl"
125
+ valueClassName="wrap-break-word text-2xl"
126
126
  detailItems={[
127
127
  `${formatCurrency(summary.exactCost)} exact`,
128
128
  `${formatCurrency(summary.estimatedCost)} estimated`,
@@ -176,7 +176,7 @@ function TokenAccountingSlice({
176
176
  return (
177
177
  <div className="min-w-0 border-t p-3 first:border-t-0 md:border-l md:border-t-0 md:first:border-l-0">
178
178
  <FieldLabel>{label}</FieldLabel>
179
- <DataValue className="mt-1 break-words leading-none" size="md">{value}</DataValue>
179
+ <DataValue className="mt-1 wrap-break-word leading-none" size="md">{value}</DataValue>
180
180
  <p className="mt-2 text-xs leading-5 text-muted-foreground">{detail}</p>
181
181
  </div>
182
182
  );
@@ -220,7 +220,7 @@ export function TokenAccountingCard({
220
220
  </span>
221
221
  </CardHeader>
222
222
  <CardContent className="flex flex-1 flex-col">
223
- <DataValue size="lg" className="break-words text-3xl leading-none">{formatTokens(summary.totalTokens)}</DataValue>
223
+ <DataValue size="lg" className="wrap-break-word text-3xl leading-none">{formatTokens(summary.totalTokens)}</DataValue>
224
224
  <div className="mt-1 text-sm font-medium text-foreground">processed tokens</div>
225
225
  <div className="mt-3 flex flex-wrap items-center gap-x-2 gap-y-1 text-xs leading-snug text-muted-foreground">
226
226
  {[
@@ -71,48 +71,46 @@ export function PeriodFilter({
71
71
  const statusLabel = range.key === "custom" ? "Custom range" : range.label;
72
72
 
73
73
  return (
74
- <div className="min-w-0 max-w-full rounded-lg bg-card p-3 outline outline-1 outline-border sm:p-4">
75
- <form className="max-w-full" action={basePath}>
76
- <input type="hidden" name="range" value="custom" />
77
- {Object.entries(preserveParams).map(([key, value]) =>
78
- value ? <input key={key} type="hidden" name={key} value={value} /> : null
79
- )}
80
- <div className="flex min-w-0 flex-wrap items-center gap-x-3 gap-y-3 lg:flex-nowrap">
81
- <div className="flex shrink-0 items-center gap-2 text-sm font-semibold">
82
- <CalendarDays className="h-4 w-4 shrink-0 text-muted-foreground" />
83
- <span>Period</span>
84
- <span className="hidden shrink-0 whitespace-nowrap rounded-md bg-muted px-2 py-0.5 text-xs font-medium text-muted-foreground 2xl:inline-flex">
85
- {statusLabel}
86
- </span>
87
- </div>
88
-
89
- <div className="period-preset-scroll -mx-3 min-w-0 flex-1 overflow-x-auto px-3 sm:mx-0 sm:px-0 md:overflow-visible">
90
- <div className="flex w-max items-center gap-1.5 pr-1 md:w-auto md:flex-wrap">
91
- {dateRangeOptions.map((option) => (
92
- <Button
93
- key={option.key}
94
- asChild
95
- size="sm"
96
- variant={range.key === option.key ? "default" : "outline"}
97
- className="px-2.5"
98
- >
99
- <Link href={rangeHref(option.key, basePath, preserveParams)}>
100
- {compactOptionLabel(option.key, option.label)}
101
- </Link>
102
- </Button>
103
- ))}
104
- </div>
105
- </div>
74
+ <div className="min-w-0 max-w-full rounded-lg bg-card p-3 outline-solid outline-1 outline-border sm:p-4">
75
+ <div className="flex min-w-0 flex-wrap items-center gap-x-3 gap-y-3 lg:flex-nowrap">
76
+ <div className="flex shrink-0 items-center gap-2 text-sm font-semibold">
77
+ <CalendarDays className="h-4 w-4 shrink-0 text-muted-foreground" />
78
+ <span>Period</span>
79
+ <span className="hidden shrink-0 whitespace-nowrap rounded-md bg-muted px-2 py-0.5 text-xs font-medium text-muted-foreground 2xl:inline-flex">
80
+ {statusLabel}
81
+ </span>
82
+ </div>
106
83
 
107
- <div className="period-custom-row ml-auto flex min-w-0 flex-wrap items-center gap-2 shrink-0 lg:flex-nowrap">
108
- <PeriodDateField label="From" name="from" defaultValue={range.fromInput} />
109
- <PeriodDateField label="To" name="to" defaultValue={range.toInput} />
110
- <Button size="sm" type="submit" variant={range.key === "custom" ? "default" : "outline"}>
111
- Apply
112
- </Button>
84
+ <div className="period-preset-scroll -mx-3 min-w-0 flex-1 overflow-x-auto px-3 sm:mx-0 sm:px-0 md:overflow-visible">
85
+ <div className="flex w-max items-center gap-1.5 pr-1 md:w-auto md:flex-wrap">
86
+ {dateRangeOptions.map((option) => (
87
+ <Button
88
+ key={option.key}
89
+ asChild
90
+ size="sm"
91
+ variant={range.key === option.key ? "default" : "outline"}
92
+ className="px-2.5"
93
+ >
94
+ <Link href={rangeHref(option.key, basePath, preserveParams)}>
95
+ {compactOptionLabel(option.key, option.label)}
96
+ </Link>
97
+ </Button>
98
+ ))}
113
99
  </div>
114
100
  </div>
115
- </form>
101
+
102
+ <form className="period-custom-row ml-auto flex min-w-0 flex-wrap items-center gap-2 shrink-0 lg:flex-nowrap" action={basePath}>
103
+ <input type="hidden" name="range" value="custom" />
104
+ {Object.entries(preserveParams).map(([key, value]) =>
105
+ value ? <input key={key} type="hidden" name={key} value={value} /> : null
106
+ )}
107
+ <PeriodDateField label="From" name="from" defaultValue={range.fromInput} />
108
+ <PeriodDateField label="To" name="to" defaultValue={range.toInput} />
109
+ <Button size="sm" type="submit" variant={range.key === "custom" ? "default" : "outline"}>
110
+ Apply
111
+ </Button>
112
+ </form>
113
+ </div>
116
114
  </div>
117
115
  );
118
116
  }
@@ -19,7 +19,7 @@ function ModelAliasSuggestionMobileCards({
19
19
  <div className="flex flex-wrap items-start justify-between gap-2">
20
20
  <div className="min-w-0">
21
21
  <div className="text-xs font-medium uppercase text-muted-foreground">Observed model</div>
22
- <div className="mt-1 break-words text-sm font-semibold">{suggestion.model}</div>
22
+ <div className="mt-1 wrap-break-word text-sm font-semibold">{suggestion.model}</div>
23
23
  </div>
24
24
  <Badge variant={suggestion.confidence === "high" ? "success" : suggestion.confidence === "medium" ? "warning" : "secondary"}>
25
25
  {suggestion.confidence}
@@ -40,7 +40,7 @@ function ModelRateMobileCards({
40
40
  <div className="flex flex-wrap items-start justify-between gap-2">
41
41
  <div className="min-w-0">
42
42
  <div className="text-xs font-medium uppercase text-muted-foreground">Provider/model</div>
43
- <div className="mt-1 break-words text-sm font-medium">{row.model || "Unnamed model"}</div>
43
+ <div className="mt-1 wrap-break-word text-sm font-medium">{row.model || "Unnamed model"}</div>
44
44
  <div className="mt-1 text-xs text-muted-foreground">{(row.providerName ?? row.provider) || row.providerId || "No provider"}</div>
45
45
  </div>
46
46
  {duplicate ? <Badge variant="warning">duplicate rate</Badge> : null}
@@ -110,7 +110,7 @@ export function PricingSettings({
110
110
  <CardContent className="space-y-3 text-sm text-muted-foreground">
111
111
  <Label>Formula</Label>
112
112
  <pre className="overflow-x-auto rounded-md bg-muted/40 p-3 whitespace-pre-wrap">
113
- <MonoText className="break-words">
113
+ <MonoText className="wrap-break-word">
114
114
  input * inputPrice + output * outputPrice + cacheRead * cacheReadPrice + cacheWrite * cacheWritePrice
115
115
  </MonoText>
116
116
  </pre>
@@ -77,7 +77,7 @@ export function RepairFlowSteps() {
77
77
  <CardDescription>Move left to right so every unknown-cost item ends with a checked local result.</CardDescription>
78
78
  </CardHeader>
79
79
  <CardContent className="overflow-x-auto p-0">
80
- <div className="grid divide-y border-t sm:min-w-[46rem] sm:grid-cols-5 sm:divide-x sm:divide-y-0">
80
+ <div className="grid divide-y border-t sm:min-w-184 sm:grid-cols-5 sm:divide-x sm:divide-y-0">
81
81
  {steps.map(([label, detail], index) => (
82
82
  <div key={label} className="p-3">
83
83
  <div className="flex items-center gap-2">
@@ -35,7 +35,7 @@ function RepairItemsMobileList({
35
35
  <Badge variant={causeVariant(group.cause)}>{causeLabel(group.cause)}</Badge>
36
36
  <span className="text-xs text-muted-foreground">{group.interactions.toLocaleString()} interactions</span>
37
37
  </div>
38
- <div className="mt-2 break-words text-sm font-medium">{group.model}</div>
38
+ <div className="mt-2 wrap-break-word text-sm font-medium">{group.model}</div>
39
39
  <div className="mt-1 text-xs text-muted-foreground">
40
40
  {group.provider} / {group.tool}
41
41
  </div>
@@ -144,7 +144,7 @@ export function RepairItemsTable({
144
144
  />
145
145
  <RepairItemsMobileList groups={workbench.groups} focusKey={focusKey} rangeLinkParams={rangeLinkParams} />
146
146
  <div className="hidden overflow-x-auto md:block">
147
- <Table className="min-w-[84rem]">
147
+ <Table className="min-w-336">
148
148
  <TableHeader>
149
149
  <TableRow>
150
150
  <TableHead>State</TableHead>
@@ -84,7 +84,7 @@ export function RepairStateControl({
84
84
  <Badge variant={statusVariant(status)}>{status}</Badge>
85
85
  <select
86
86
  aria-label="Repair state"
87
- className="h-8 w-full rounded-md border bg-card px-2 text-xs font-medium focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
87
+ className="h-8 w-full rounded-md border bg-card px-2 text-xs font-medium focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring"
88
88
  value={status}
89
89
  disabled={isPending}
90
90
  onChange={(event) => {
@@ -100,7 +100,7 @@ export function RepairStateControl({
100
100
  </select>
101
101
  <input
102
102
  aria-label="Repair note"
103
- className="h-8 w-full rounded-md border bg-card px-2 text-xs focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
103
+ className="h-8 w-full rounded-md border bg-card px-2 text-xs focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring"
104
104
  value={notes}
105
105
  maxLength={500}
106
106
  disabled={isPending}
@@ -193,7 +193,7 @@ export function ScanHealthSummary({ health }: { health: ScanHealth }) {
193
193
  </div>
194
194
  <ul className="space-y-1 text-xs leading-relaxed text-muted-foreground">
195
195
  {health.latestNoteGroups.map((group) => (
196
- <li key={`${group.severity}-${group.message}`} className="break-words">
196
+ <li key={`${group.severity}-${group.message}`} className="wrap-break-word">
197
197
  <span className="font-medium text-foreground">{group.message}</span>
198
198
  <span className="ml-2 text-muted-foreground">
199
199
  {plural(group.count, "file")}
@@ -114,10 +114,10 @@ export function ScanNowButton({
114
114
  {isPending ? "Scanning..." : label}
115
115
  </Button>
116
116
  {status ? (
117
- <div role="status" aria-live="polite" className={cn("max-w-[26rem] text-xs leading-5", statusClassName)}>
117
+ <div role="status" aria-live="polite" className={cn("max-w-104 text-xs leading-5", statusClassName)}>
118
118
  <p className={scanStatusClassName(status.tone)}>{status.message}</p>
119
119
  {status.result ? (
120
- <div className="mt-2 rounded-md border bg-card p-2 text-left text-muted-foreground shadow-sm">
120
+ <div className="mt-2 rounded-md border bg-card p-2 text-left text-muted-foreground shadow-xs">
121
121
  <div className="grid grid-cols-2 gap-x-3 gap-y-1">
122
122
  <span>{status.result.filesScanned.toLocaleString()} files checked</span>
123
123
  <span>{status.result.recordsImported.toLocaleString()} records imported</span>
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
 
3
- import { useEffect, useMemo, useState } from "react";
3
+ import { useMemo, useState } from "react";
4
4
  import Link from "next/link";
5
5
  import { BookmarkPlus, Download, Filter, RotateCcw, Trash2 } from "lucide-react";
6
6
  import type { SessionRow } from "@/src/lib/analytics";
@@ -93,6 +93,11 @@ export function SessionExplorer({
93
93
  () => ({ query, tool, model, project, exact, cost, from, to, highCost, hasCache }),
94
94
  [cost, exact, from, hasCache, highCost, model, project, query, to, tool]
95
95
  );
96
+ const [prevFilterState, setPrevFilterState] = useState(filterState);
97
+ if (filterState !== prevFilterState) {
98
+ setPrevFilterState(filterState);
99
+ setPage(1);
100
+ }
96
101
  const filtered = useMemo(
97
102
  () => filterSessions(sessions, filterState, highCostThreshold),
98
103
  [filterState, highCostThreshold, sessions]
@@ -106,10 +111,6 @@ export function SessionExplorer({
106
111
  const activeFilters = useMemo(() => getActiveSessionFilters(filterState), [filterState]);
107
112
  const currentFilters = useMemo(() => getCurrentSessionFilters(filterState), [filterState]);
108
113
 
109
- useEffect(() => {
110
- setPage(1);
111
- }, [filterState]);
112
-
113
114
  function clearFilters() {
114
115
  setQuery("");
115
116
  setTool("all");
@@ -431,9 +432,9 @@ export function SessionExplorer({
431
432
  </div>
432
433
  </div>
433
434
  {filtered.length ? (
434
- <div className="table-scroll max-h-[38rem] overflow-x-auto">
435
- <Table className={cn("min-w-[78rem]", tableDensityClass)}>
436
- <TableHeader className="sticky top-0 z-10 bg-background shadow-sm">
435
+ <div className="table-scroll max-h-152 overflow-x-auto">
436
+ <Table className={cn("min-w-312", tableDensityClass)}>
437
+ <TableHeader className="sticky top-0 z-10 bg-background shadow-xs">
437
438
  <TableRow>
438
439
  <TableHead className="w-28">Date</TableHead>
439
440
  <TableHead className="w-28">Tool</TableHead>
@@ -59,7 +59,7 @@ export function ImportProfilesSection({
59
59
  </div>
60
60
  <div className="mt-2 flex flex-wrap gap-1">
61
61
  {profile.matchers.map((matcher) => (
62
- <code key={matcher} className="rounded bg-muted px-1.5 py-0.5 text-xs">{matcher}</code>
62
+ <code key={matcher} className="rounded-sm bg-muted px-1.5 py-0.5 text-xs">{matcher}</code>
63
63
  ))}
64
64
  </div>
65
65
  <div className="mt-3 flex flex-wrap gap-2">
@@ -126,7 +126,7 @@ export function ImportProfilesSection({
126
126
  <FieldLabel>Matchers</FieldLabel>
127
127
  <div className="mt-1 flex flex-wrap gap-1">
128
128
  {previewResult.recommendedMatchers.map((matcher) => (
129
- <code key={matcher} className="rounded bg-muted px-1.5 py-0.5 text-xs">{matcher}</code>
129
+ <code key={matcher} className="rounded-sm bg-muted px-1.5 py-0.5 text-xs">{matcher}</code>
130
130
  ))}
131
131
  </div>
132
132
  </div>
@@ -22,7 +22,7 @@ export function SettingsSectionNav() {
22
22
  return (
23
23
  <nav
24
24
  aria-label="Settings sections"
25
- className="sticky top-2 z-20 rounded-lg border bg-background/95 p-2 shadow-sm backdrop-blur supports-[backdrop-filter]:bg-background/80"
25
+ className="sticky top-2 z-20 rounded-lg border bg-background/95 p-2 shadow-xs backdrop-blur-sm supports-backdrop-filter:bg-background/80"
26
26
  >
27
27
  <div className="flex items-center gap-2 overflow-x-auto pb-1 sm:pb-0">
28
28
  <div className="sticky left-0 shrink-0 bg-background/95 pr-2 text-xs font-semibold text-foreground">
@@ -33,7 +33,7 @@ export function SettingsSectionNav() {
33
33
  key={section.id}
34
34
  href={`#${section.id}`}
35
35
  title={section.detail}
36
- className="inline-flex h-8 shrink-0 items-center rounded-md border bg-card px-3 text-xs font-medium text-muted-foreground transition-colors hover:bg-muted hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
36
+ className="inline-flex h-8 shrink-0 items-center rounded-md border bg-card px-3 text-xs font-medium text-muted-foreground transition-colors hover:bg-muted hover:text-foreground focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
37
37
  >
38
38
  {section.label}
39
39
  </a>
@@ -64,7 +64,7 @@ function NavLink({
64
64
  ? cn(
65
65
  "flex shrink-0 items-center gap-2 rounded-md border px-3 py-2 text-xs transition-colors",
66
66
  isActive
67
- ? "border-primary/30 bg-primary/10 font-medium text-primary shadow-sm"
67
+ ? "border-primary/30 bg-primary/10 font-medium text-primary shadow-xs"
68
68
  : variant === "support"
69
69
  ? "bg-muted/50 font-medium text-foreground"
70
70
  : "text-muted-foreground"
@@ -107,7 +107,7 @@ function MobileIconLink({
107
107
  className={cn(
108
108
  "flex h-10 w-10 shrink-0 items-center justify-center rounded-md border transition-colors",
109
109
  isActive
110
- ? "border-primary/30 bg-primary/10 text-primary shadow-sm"
110
+ ? "border-primary/30 bg-primary/10 text-primary shadow-xs"
111
111
  : "bg-card text-muted-foreground hover:bg-muted hover:text-foreground"
112
112
  )}
113
113
  >
@@ -3,7 +3,7 @@ import { cva, type VariantProps } from "class-variance-authority";
3
3
  import { cn } from "@/src/lib/utils";
4
4
 
5
5
  const badgeVariants = cva(
6
- "inline-flex items-center rounded-md px-2 py-1 text-xs font-medium leading-none outline outline-1 -outline-offset-1",
6
+ "inline-flex items-center rounded-md px-2 py-1 text-xs font-medium leading-none outline-solid outline-1 -outline-offset-1",
7
7
  {
8
8
  variants: {
9
9
  variant: {
@@ -4,7 +4,7 @@ import { cva, type VariantProps } from "class-variance-authority";
4
4
  import { cn } from "@/src/lib/utils";
5
5
 
6
6
  const buttonVariants = cva(
7
- "inline-flex h-9 items-center justify-center gap-2 whitespace-nowrap rounded-md px-3 py-2 text-sm font-medium leading-none transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50",
7
+ "inline-flex h-9 items-center justify-center gap-2 whitespace-nowrap rounded-md px-3 py-2 text-sm font-medium leading-none transition-colors focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50",
8
8
  {
9
9
  variants: {
10
10
  variant: {
@@ -19,7 +19,7 @@ export function HelpTooltip({
19
19
  type="button"
20
20
  aria-label={`${label} details`}
21
21
  aria-describedby={id}
22
- className="inline-flex h-5 w-5 items-center justify-center rounded-sm text-muted-foreground outline-none transition-colors hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
22
+ className="inline-flex h-5 w-5 items-center justify-center rounded-xs text-muted-foreground outline-hidden transition-colors hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
23
23
  >
24
24
  <Info className="h-3.5 w-3.5" aria-hidden="true" />
25
25
  </button>
@@ -7,7 +7,7 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(({ className, type,
7
7
  <input
8
8
  type={type}
9
9
  className={cn(
10
- "flex h-9 w-full rounded-md border bg-card px-3 py-1 text-sm leading-5 shadow-sm tabular-nums transition-colors placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
10
+ "flex h-9 w-full rounded-md border bg-card px-3 py-1 text-sm leading-5 shadow-xs tabular-nums transition-colors placeholder:text-muted-foreground focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
11
11
  className
12
12
  )}
13
13
  ref={ref}
@@ -6,7 +6,7 @@ export type TextareaProps = React.TextareaHTMLAttributes<HTMLTextAreaElement>;
6
6
  const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(({ className, ...props }, ref) => (
7
7
  <textarea
8
8
  className={cn(
9
- "flex min-h-24 w-full rounded-md border bg-card px-3 py-2 text-sm leading-6 shadow-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
9
+ "flex min-h-24 w-full rounded-md border bg-card px-3 py-2 text-sm leading-6 shadow-xs placeholder:text-muted-foreground focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
10
10
  className
11
11
  )}
12
12
  ref={ref}
@@ -16,7 +16,7 @@ export function PageHeader({
16
16
  <div className={cn("flex w-full max-w-full flex-col justify-between gap-4 sm:flex-row sm:items-end", className)}>
17
17
  <div className="min-w-0 max-w-full">
18
18
  <h1 className="text-2xl font-semibold leading-tight tracking-normal text-foreground">{title}</h1>
19
- <p className="mt-1 max-w-full break-words text-sm leading-6 text-muted-foreground sm:max-w-[65ch]">{description}</p>
19
+ <p className="mt-1 max-w-full wrap-break-word text-sm leading-6 text-muted-foreground sm:max-w-[65ch]">{description}</p>
20
20
  </div>
21
21
  {actions ? <div className="flex w-full min-w-0 flex-wrap gap-2 sm:w-auto sm:shrink-0 sm:justify-end">{actions}</div> : null}
22
22
  </div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tokentrace",
3
- "version": "0.15.0",
3
+ "version": "0.15.2",
4
4
  "mcpName": "io.github.abhiyoheswaran1/tokentrace",
5
5
  "description": "Local-first dashboard for AI CLI token, cost, and session analytics.",
6
6
  "author": {
@@ -40,7 +40,6 @@
40
40
  "fixtures",
41
41
  "next.config.mjs",
42
42
  "postcss.config.mjs",
43
- "tailwind.config.ts",
44
43
  "tsconfig.json",
45
44
  "components.json",
46
45
  "README.md"
@@ -77,7 +76,7 @@
77
76
  "postcss": "^8.5.15"
78
77
  },
79
78
  "engines": {
80
- "node": ">=20.0.0"
79
+ "node": ">=20.9.0"
81
80
  },
82
81
  "keywords": [
83
82
  "tokens",
@@ -95,34 +94,35 @@
95
94
  "license": "MIT",
96
95
  "dependencies": {
97
96
  "@radix-ui/react-slot": "^1.1.1",
97
+ "@tailwindcss/postcss": "^4.3.0",
98
98
  "@types/node": "^22.19.19",
99
99
  "@types/react": "^19.2.15",
100
- "autoprefixer": "^10.5.0",
101
100
  "better-sqlite3": "^12.10.0",
102
101
  "class-variance-authority": "^0.7.1",
103
102
  "clsx": "^2.1.1",
104
103
  "drizzle-orm": "^0.45.2",
105
104
  "get-port": "^7.2.0",
106
105
  "lucide-react": "^1.16.0",
107
- "next": "^15.5.18",
106
+ "next": "^16.2.6",
108
107
  "open": "^11.0.0",
109
108
  "postcss": "^8.5.15",
110
109
  "react": "^19.0.0",
111
110
  "react-dom": "^19.0.0",
112
- "recharts": "^2.15.0",
113
- "tailwind-merge": "^2.6.0",
114
- "tailwindcss": "^3.4.17",
115
- "typescript": "^5.7.2"
111
+ "recharts": "^3.8.1",
112
+ "tailwind-merge": "^3.6.0",
113
+ "tailwindcss": "^4.3.0",
114
+ "typescript": "^6.0.3"
116
115
  },
117
116
  "devDependencies": {
118
117
  "@playwright/test": "^1.60.0",
119
118
  "@types/better-sqlite3": "^7.6.12",
120
119
  "@types/react-dom": "^19.0.2",
120
+ "@vitejs/plugin-react": "^5.2.0",
121
121
  "esbuild": "^0.28.0",
122
122
  "eslint": "^9.39.4",
123
- "eslint-config-next": "^15.1.4",
123
+ "eslint-config-next": "^16.2.6",
124
124
  "eslint-plugin-react-hooks": "^5.2.0",
125
125
  "tsx": "^4.22.3",
126
- "vitest": "^3.2.4"
126
+ "vitest": "^4.1.7"
127
127
  }
128
128
  }
@@ -1,7 +1,6 @@
1
1
  const config = {
2
2
  plugins: {
3
- tailwindcss: {},
4
- autoprefixer: {}
3
+ '@tailwindcss/postcss': {},
5
4
  }
6
5
  };
7
6
 
@@ -40,8 +40,8 @@ try {
40
40
  );
41
41
  }
42
42
 
43
- if (packageJson.dependencies?.next !== "^15.5.18") {
44
- fail("Keep the published Next.js dependency floor at ^15.5.18 or newer.");
43
+ if (packageJson.dependencies?.next !== "^16.2.6") {
44
+ fail("Keep the published Next.js dependency floor at ^16.2.6 or newer.");
45
45
  }
46
46
 
47
47
  if (packageJson.dependencies?.["drizzle-orm"] !== "^0.45.2") {
package/server.json CHANGED
@@ -8,12 +8,12 @@
8
8
  "url": "https://github.com/abhiyoheswaran1/tokentrace",
9
9
  "source": "github"
10
10
  },
11
- "version": "0.15.0",
11
+ "version": "0.15.2",
12
12
  "packages": [
13
13
  {
14
14
  "registryType": "npm",
15
15
  "identifier": "tokentrace",
16
- "version": "0.15.0",
16
+ "version": "0.15.2",
17
17
  "runtimeHint": "npx",
18
18
  "packageArguments": [
19
19
  {
@@ -31,7 +31,6 @@ function copyDashboardSource(context, targetRoot) {
31
31
  "next.config.mjs",
32
32
  "package.json",
33
33
  "postcss.config.mjs",
34
- "tailwind.config.ts",
35
34
  "tsconfig.json"
36
35
  ];
37
36
 
@@ -63,7 +62,7 @@ function copyDashboardSource(context, targetRoot) {
63
62
 
64
63
  function runNextBuild(context, cwd) {
65
64
  return new Promise((resolve, reject) => {
66
- const child = spawn(process.execPath, [context.nextBin(), "build"], {
65
+ const child = spawn(process.execPath, [context.nextBin(), "build", "--webpack"], {
67
66
  cwd,
68
67
  env: context.runtimeEnv(),
69
68
  stdio: "inherit"
package/tsconfig.json CHANGED
@@ -1,7 +1,11 @@
1
1
  {
2
2
  "compilerOptions": {
3
3
  "target": "ES2022",
4
- "lib": ["dom", "dom.iterable", "es2022"],
4
+ "lib": [
5
+ "dom",
6
+ "dom.iterable",
7
+ "es2022"
8
+ ],
5
9
  "allowJs": false,
6
10
  "skipLibCheck": true,
7
11
  "strict": true,
@@ -11,11 +15,14 @@
11
15
  "moduleResolution": "bundler",
12
16
  "resolveJsonModule": true,
13
17
  "isolatedModules": true,
14
- "jsx": "preserve",
18
+ "jsx": "react-jsx",
15
19
  "incremental": true,
16
20
  "baseUrl": ".",
21
+ "ignoreDeprecations": "6.0",
17
22
  "paths": {
18
- "@/*": ["./*"]
23
+ "@/*": [
24
+ "./*"
25
+ ]
19
26
  },
20
27
  "plugins": [
21
28
  {
@@ -23,6 +30,14 @@
23
30
  }
24
31
  ]
25
32
  },
26
- "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
27
- "exclude": ["node_modules"]
33
+ "include": [
34
+ "next-env.d.ts",
35
+ "**/*.ts",
36
+ "**/*.tsx",
37
+ ".next/types/**/*.ts",
38
+ ".next/dev/types/**/*.ts"
39
+ ],
40
+ "exclude": [
41
+ "node_modules"
42
+ ]
28
43
  }
@@ -1,53 +0,0 @@
1
- import type { Config } from "tailwindcss";
2
-
3
- const config: Config = {
4
- darkMode: ["class"],
5
- content: [
6
- "./app/**/*.{ts,tsx}",
7
- "./components/**/*.{ts,tsx}",
8
- "./src/**/*.{ts,tsx}"
9
- ],
10
- theme: {
11
- extend: {
12
- colors: {
13
- border: "hsl(var(--border))",
14
- input: "hsl(var(--input))",
15
- ring: "hsl(var(--ring))",
16
- background: "hsl(var(--background))",
17
- foreground: "hsl(var(--foreground))",
18
- primary: {
19
- DEFAULT: "hsl(var(--primary))",
20
- foreground: "hsl(var(--primary-foreground))"
21
- },
22
- secondary: {
23
- DEFAULT: "hsl(var(--secondary))",
24
- foreground: "hsl(var(--secondary-foreground))"
25
- },
26
- muted: {
27
- DEFAULT: "hsl(var(--muted))",
28
- foreground: "hsl(var(--muted-foreground))"
29
- },
30
- accent: {
31
- DEFAULT: "hsl(var(--accent))",
32
- foreground: "hsl(var(--accent-foreground))"
33
- },
34
- destructive: {
35
- DEFAULT: "hsl(var(--destructive))",
36
- foreground: "hsl(var(--destructive-foreground))"
37
- },
38
- card: {
39
- DEFAULT: "hsl(var(--card))",
40
- foreground: "hsl(var(--card-foreground))"
41
- }
42
- },
43
- borderRadius: {
44
- lg: "0.5rem",
45
- md: "0.375rem",
46
- sm: "0.25rem"
47
- }
48
- }
49
- },
50
- plugins: []
51
- };
52
-
53
- export default config;