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.
- package/CHANGELOG.md +29 -0
- package/app/discovery/page.tsx +2 -2
- package/app/evidence/page.tsx +3 -3
- package/app/globals.css +51 -3
- package/app/guide/page.tsx +4 -4
- package/app/parser-debug/page.tsx +3 -3
- package/app/sessions/[id]/page.tsx +1 -1
- package/app/sessions/page.tsx +1 -1
- package/components/charts/rank-bar-chart.tsx +3 -3
- package/components/charts/trend-chart.tsx +7 -5
- package/components/charts/trend-section.tsx +5 -3
- package/components/diagnostics/parser-panels.tsx +2 -2
- package/components/overview/recommendations-card.tsx +1 -1
- package/components/overview/review-status-strip.tsx +1 -1
- package/components/overview/summary-cards.tsx +6 -6
- package/components/period-filter.tsx +37 -39
- package/components/pricing/model-alias-suggestions-table.tsx +1 -1
- package/components/pricing/model-rates-table.tsx +1 -1
- package/components/pricing-settings.tsx +1 -1
- package/components/repair/repair-guidance.tsx +1 -1
- package/components/repair/repair-items-table.tsx +2 -2
- package/components/repair-state-control.tsx +2 -2
- package/components/scan-health-summary.tsx +1 -1
- package/components/scan-now-button.tsx +2 -2
- package/components/session-explorer.tsx +9 -8
- package/components/settings/import-profiles-section.tsx +2 -2
- package/components/settings/section-nav.tsx +2 -2
- package/components/sidebar.tsx +2 -2
- package/components/ui/badge.tsx +1 -1
- package/components/ui/button.tsx +1 -1
- package/components/ui/help-tooltip.tsx +1 -1
- package/components/ui/input.tsx +1 -1
- package/components/ui/textarea.tsx +1 -1
- package/components/ui/typography.tsx +1 -1
- package/package.json +11 -11
- package/postcss.config.mjs +1 -2
- package/scripts/package-inspect.mjs +2 -2
- package/server.json +2 -2
- package/src/cli/runtime.js +1 -2
- package/tsconfig.json +20 -5
- 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
|
package/app/discovery/page.tsx
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-
|
|
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-
|
|
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>
|
package/app/evidence/page.tsx
CHANGED
|
@@ -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-
|
|
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-
|
|
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-
|
|
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
|
-
@
|
|
2
|
-
|
|
3
|
-
@
|
|
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%;
|
package/app/guide/page.tsx
CHANGED
|
@@ -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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
102
|
-
<TableCell className="max-w-sm whitespace-normal break-
|
|
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-
|
|
214
|
+
<Table className="min-w-312">
|
|
215
215
|
<TableHeader>
|
|
216
216
|
<TableRow>
|
|
217
217
|
<TableHead className="w-36">Time</TableHead>
|
package/app/sessions/page.tsx
CHANGED
|
@@ -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-
|
|
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-[
|
|
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 {
|
|
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-[
|
|
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
|
-
|
|
143
|
+
if (defaultWindow !== prevDefaultWindow) {
|
|
144
|
+
setPrevDefaultWindow(defaultWindow);
|
|
143
145
|
if (!controlledTrendWindow) setInternalTrendWindow(defaultWindow);
|
|
144
|
-
}
|
|
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 {
|
|
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
|
-
|
|
28
|
+
if (defaultWindow !== prevDefaultWindow) {
|
|
29
|
+
setPrevDefaultWindow(defaultWindow);
|
|
28
30
|
setTrendWindow(defaultWindow);
|
|
29
|
-
}
|
|
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-
|
|
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-
|
|
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-
|
|
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:
|
|
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-
|
|
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:
|
|
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-
|
|
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-
|
|
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-
|
|
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
|
-
<
|
|
76
|
-
<
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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
|
-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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 {
|
|
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-
|
|
435
|
-
<Table className={cn("min-w-
|
|
436
|
-
<TableHeader className="sticky top-0 z-10 bg-background shadow-
|
|
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-
|
|
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-
|
|
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>
|
package/components/sidebar.tsx
CHANGED
|
@@ -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-
|
|
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-
|
|
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
|
>
|
package/components/ui/badge.tsx
CHANGED
|
@@ -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: {
|
package/components/ui/button.tsx
CHANGED
|
@@ -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-
|
|
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-
|
|
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>
|
package/components/ui/input.tsx
CHANGED
|
@@ -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-
|
|
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-
|
|
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-
|
|
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.
|
|
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.
|
|
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": "^
|
|
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": "^
|
|
113
|
-
"tailwind-merge": "^
|
|
114
|
-
"tailwindcss": "^3.
|
|
115
|
-
"typescript": "^
|
|
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": "^
|
|
123
|
+
"eslint-config-next": "^16.2.6",
|
|
124
124
|
"eslint-plugin-react-hooks": "^5.2.0",
|
|
125
125
|
"tsx": "^4.22.3",
|
|
126
|
-
"vitest": "^
|
|
126
|
+
"vitest": "^4.1.7"
|
|
127
127
|
}
|
|
128
128
|
}
|
package/postcss.config.mjs
CHANGED
|
@@ -40,8 +40,8 @@ try {
|
|
|
40
40
|
);
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
if (packageJson.dependencies?.next !== "^
|
|
44
|
-
fail("Keep the published Next.js dependency floor at ^
|
|
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.
|
|
11
|
+
"version": "0.15.2",
|
|
12
12
|
"packages": [
|
|
13
13
|
{
|
|
14
14
|
"registryType": "npm",
|
|
15
15
|
"identifier": "tokentrace",
|
|
16
|
-
"version": "0.15.
|
|
16
|
+
"version": "0.15.2",
|
|
17
17
|
"runtimeHint": "npx",
|
|
18
18
|
"packageArguments": [
|
|
19
19
|
{
|
package/src/cli/runtime.js
CHANGED
|
@@ -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": [
|
|
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": "
|
|
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": [
|
|
27
|
-
|
|
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
|
}
|
package/tailwind.config.ts
DELETED
|
@@ -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;
|