@roastcodes/ttdash 6.1.5 → 6.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.html CHANGED
@@ -1,4 +1,4 @@
1
- <!DOCTYPE html>
1
+ <!doctype html>
2
2
  <html lang="de" class="dark">
3
3
  <head>
4
4
  <meta charset="UTF-8" />
@@ -8,7 +8,7 @@
8
8
  <link rel="icon" type="image/png" sizes="256x256" href="/favicon.png" />
9
9
  <link rel="shortcut icon" href="/favicon.png" />
10
10
  <link rel="apple-touch-icon" href="/favicon.png" />
11
- <script type="module" crossorigin src="/assets/index-_318nw_j.js"></script>
11
+ <script type="module" crossorigin src="/assets/index-D8uaHhGW.js"></script>
12
12
  <link rel="modulepreload" crossorigin href="/assets/rolldown-runtime-COnpUsM8.js">
13
13
  <link rel="modulepreload" crossorigin href="/assets/charts-vendor-CiBqdKXh.js">
14
14
  <link rel="modulepreload" crossorigin href="/assets/react-vendor-0R1rd57Z.js">
@@ -17,7 +17,7 @@
17
17
  <link rel="modulepreload" crossorigin href="/assets/icons-vendor-DFoaijFJ.js">
18
18
  <link rel="modulepreload" crossorigin href="/assets/dialog-Cn1m7WhC.js">
19
19
  <link rel="modulepreload" crossorigin href="/assets/button-D7Ib8H7t.js">
20
- <link rel="modulepreload" crossorigin href="/assets/CustomTooltip-Be-rHcDB.js">
20
+ <link rel="modulepreload" crossorigin href="/assets/CustomTooltip-DBPq6A_5.js">
21
21
  <link rel="stylesheet" crossorigin href="/assets/index-TppJ6Iqj.css">
22
22
  </head>
23
23
  <body>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@roastcodes/ttdash",
3
- "version": "6.1.5",
3
+ "version": "6.1.6",
4
4
  "description": "Local-first dashboard and CLI for toktrack usage data",
5
5
  "main": "server.js",
6
6
  "repository": {
@@ -16,7 +16,12 @@
16
16
  },
17
17
  "scripts": {
18
18
  "dev": "vite",
19
- "build": "vite build",
19
+ "build:app": "vite build",
20
+ "build": "npm run format:check && npm run lint && npm run build:app",
21
+ "format": "prettier . --write",
22
+ "format:check": "prettier . --check",
23
+ "lint": "eslint .",
24
+ "lint:fix": "eslint . --fix",
20
25
  "preview": "vite preview",
21
26
  "start": "node server.js",
22
27
  "start:test-server": "node scripts/start-test-server.js",
@@ -24,14 +29,15 @@
24
29
  "test:unit": "vitest run",
25
30
  "test:unit:watch": "vitest",
26
31
  "test:unit:coverage": "vitest run --coverage",
27
- "test:e2e": "npm run build && playwright test",
32
+ "test:e2e": "npm run build:app && playwright test",
28
33
  "test:e2e:ci": "playwright test",
29
34
  "test:all": "npm run test:unit && npm run test:e2e",
30
35
  "pack:dry-run": "npm pack --dry-run",
36
+ "verify": "npm run format:check && npm run lint && tsc --noEmit && npm run test:unit && npm run build:app && npm run verify:package",
31
37
  "verify:package": "node scripts/verify-package.js",
32
38
  "verify:registry-install": "node scripts/verify-registry-install.js",
33
- "verify:release": "npm run test:unit:coverage && npm run build && npm run verify:package",
34
- "prepare": "npm run build"
39
+ "verify:release": "npm run format:check && npm run lint && tsc --noEmit && npm run test:unit:coverage && npm run build:app && npm run verify:package",
40
+ "prepare": "npm run build:app"
35
41
  },
36
42
  "files": [
37
43
  "server.js",
@@ -59,6 +65,7 @@
59
65
  "node": ">=20"
60
66
  },
61
67
  "devDependencies": {
68
+ "@eslint/js": "^9.39.4",
62
69
  "@playwright/test": "^1.59.1",
63
70
  "@radix-ui/react-dialog": "^1.1.14",
64
71
  "@radix-ui/react-select": "^2.2.2",
@@ -76,15 +83,21 @@
76
83
  "class-variance-authority": "^0.7.1",
77
84
  "clsx": "^2.1.1",
78
85
  "cmdk": "^1.1.1",
86
+ "eslint": "^9.39.4",
87
+ "eslint-config-prettier": "^10.1.8",
88
+ "eslint-plugin-react-hooks": "^7.0.1",
79
89
  "framer-motion": "^12.6.5",
90
+ "globals": "^17.5.0",
80
91
  "jsdom": "^29.0.2",
81
92
  "lucide-react": "^1.7.0",
93
+ "prettier": "^3.8.2",
82
94
  "react": "^19.2.4",
83
95
  "react-dom": "^19.2.4",
84
96
  "recharts": "^3.8.1",
85
97
  "tailwind-merge": "^3.0.2",
86
98
  "tailwindcss": "^4.1.3",
87
99
  "typescript": "^6.0.2",
100
+ "typescript-eslint": "^8.58.1",
88
101
  "vite": "^8.0.8",
89
102
  "vitest": "^4.1.3"
90
103
  },
@@ -0,0 +1,28 @@
1
+ {
2
+ "displayAliases": [
3
+ { "pattern": "(^|-)gpt-5-4$", "name": "GPT-5.4" },
4
+ { "pattern": "(^|-)gpt-5$", "name": "GPT-5" },
5
+ { "pattern": "(^|-)opus-4-6$", "name": "Opus 4.6" },
6
+ { "pattern": "(^|-)opus-4-5$", "name": "Opus 4.5" },
7
+ { "pattern": "(^|-)sonnet-4-6$", "name": "Sonnet 4.6" },
8
+ { "pattern": "(^|-)sonnet-4-5$", "name": "Sonnet 4.5" },
9
+ { "pattern": "(^|-)haiku-4-5$", "name": "Haiku 4.5" },
10
+ { "pattern": "(^|-)gemini-3-flash-preview$", "name": "Gemini 3 Flash Preview" },
11
+ { "pattern": "(^|-)opencode$", "name": "OpenCode" }
12
+ ],
13
+ "providerMatchers": [
14
+ { "pattern": "(^|-)opencode($|-)", "provider": "OpenCode" },
15
+ {
16
+ "pattern": "openai-codex|(^|-)codex($|-)|(^|-)gpt($|-)|(^|[^a-z0-9])o\\d(?:$|[^a-z0-9])|openai",
17
+ "provider": "OpenAI"
18
+ },
19
+ { "pattern": "claude|anthropic|opus|sonnet|haiku", "provider": "Anthropic" },
20
+ { "pattern": "gemini|google|vertex", "provider": "Google" },
21
+ { "pattern": "grok|xai", "provider": "xAI" },
22
+ { "pattern": "llama|meta-llama|meta/", "provider": "Meta" },
23
+ { "pattern": "command|cohere", "provider": "Cohere" },
24
+ { "pattern": "mistral", "provider": "Mistral" },
25
+ { "pattern": "deepseek", "provider": "DeepSeek" },
26
+ { "pattern": "qwen|alibaba", "provider": "Alibaba" }
27
+ ]
28
+ }
@@ -25,7 +25,18 @@ function truncateSvgLabel(value, maxLength = 28) {
25
25
  return `${stringValue.slice(0, Math.max(1, maxLength - 1)).trimEnd()}…`;
26
26
  }
27
27
 
28
- function lineChart(data, { valueKey, secondaryKey, title, stroke = '#1f6feb', fill = 'rgba(31, 111, 235, 0.14)', formatter = (value) => String(value), fontFamily = DEFAULT_FONT_FAMILY }) {
28
+ function lineChart(
29
+ data,
30
+ {
31
+ valueKey,
32
+ secondaryKey,
33
+ title,
34
+ stroke = '#1f6feb',
35
+ fill = 'rgba(31, 111, 235, 0.14)',
36
+ formatter = (value) => String(value),
37
+ fontFamily = DEFAULT_FONT_FAMILY,
38
+ },
39
+ ) {
29
40
  const width = 980;
30
41
  const height = 360;
31
42
  const margin = { top: 42, right: 28, bottom: 54, left: 74 };
@@ -39,16 +50,18 @@ function lineChart(data, { valueKey, secondaryKey, title, stroke = '#1f6feb', fi
39
50
  const y = (value) => margin.top + plotHeight - (value / maxValue) * plotHeight;
40
51
 
41
52
  const linePoints = values.map((value, index) => `${x(index)},${y(value)}`).join(' ');
42
- const areaPoints = data.length > 1
43
- ? [
44
- `${margin.left},${margin.top + plotHeight}`,
45
- ...values.map((value, index) => `${x(index)},${y(value)}`),
46
- `${margin.left + plotWidth},${margin.top + plotHeight}`,
47
- ].join(' ')
48
- : '';
49
- const secondaryPoints = secondaryValues.length > 0
50
- ? secondaryValues.map((value, index) => `${x(index)},${y(value)}`).join(' ')
51
- : '';
53
+ const areaPoints =
54
+ data.length > 1
55
+ ? [
56
+ `${margin.left},${margin.top + plotHeight}`,
57
+ ...values.map((value, index) => `${x(index)},${y(value)}`),
58
+ `${margin.left + plotWidth},${margin.top + plotHeight}`,
59
+ ].join(' ')
60
+ : '';
61
+ const secondaryPoints =
62
+ secondaryValues.length > 0
63
+ ? secondaryValues.map((value, index) => `${x(index)},${y(value)}`).join(' ')
64
+ : '';
52
65
  const tickCount = 4;
53
66
  const yTicks = Array.from({ length: tickCount + 1 }, (_, index) => {
54
67
  const value = (maxValue / tickCount) * index;
@@ -59,33 +72,66 @@ function lineChart(data, { valueKey, secondaryKey, title, stroke = '#1f6feb', fi
59
72
  });
60
73
  const labelStep = Math.max(1, Math.ceil(data.length / 6));
61
74
 
62
- return svgDoc(width, height, `
75
+ return svgDoc(
76
+ width,
77
+ height,
78
+ `
63
79
  <rect width="${width}" height="${height}" rx="24" fill="#ffffff"/>
64
80
  <text x="${margin.left}" y="26" font-size="18" font-family="${fontFamily}" font-weight="700" fill="#122033">${escapeXml(title)}</text>
65
- ${yTicks.map((tick) => `
81
+ ${yTicks
82
+ .map(
83
+ (tick) => `
66
84
  <line x1="${margin.left}" y1="${tick.y}" x2="${margin.left + plotWidth}" y2="${tick.y}" stroke="#e6edf5" stroke-width="1"/>
67
85
  <text x="${margin.left - 12}" y="${tick.y + 4}" text-anchor="end" font-size="11" font-family="${fontFamily}" fill="#5c6b7e">${escapeXml(formatter(tick.value))}</text>
68
- `).join('')}
86
+ `,
87
+ )
88
+ .join('')}
69
89
  <line x1="${margin.left}" y1="${margin.top}" x2="${margin.left}" y2="${margin.top + plotHeight}" stroke="#98a6b7" stroke-width="1.2"/>
70
90
  <line x1="${margin.left}" y1="${margin.top + plotHeight}" x2="${margin.left + plotWidth}" y2="${margin.top + plotHeight}" stroke="#98a6b7" stroke-width="1.2"/>
71
91
  ${areaPoints ? `<polygon points="${areaPoints}" fill="${fill}"/>` : ''}
72
92
  ${secondaryPoints ? `<polyline points="${secondaryPoints}" stroke="#f59e0b" stroke-width="2.5" stroke-dasharray="8 6" stroke-linecap="round" stroke-linejoin="round"/>` : ''}
73
- ${data.length > 1
74
- ? `<polyline points="${linePoints}" stroke="${stroke}" stroke-width="3.5" stroke-linecap="round" stroke-linejoin="round"/>`
75
- : `<line x1="${x(0)}" y1="${margin.top + plotHeight}" x2="${x(0)}" y2="${y(values[0])}" stroke="${stroke}" stroke-width="3.5" stroke-linecap="round"/>`}
76
- ${values.map((value, index) => `
93
+ ${
94
+ data.length > 1
95
+ ? `<polyline points="${linePoints}" stroke="${stroke}" stroke-width="3.5" stroke-linecap="round" stroke-linejoin="round"/>`
96
+ : `<line x1="${x(0)}" y1="${margin.top + plotHeight}" x2="${x(0)}" y2="${y(values[0])}" stroke="${stroke}" stroke-width="3.5" stroke-linecap="round"/>`
97
+ }
98
+ ${values
99
+ .map(
100
+ (value, index) => `
77
101
  <circle cx="${x(index)}" cy="${y(value)}" r="${data.length > 40 ? 0 : 3.8}" fill="${stroke}"/>
78
- `).join('')}
79
- ${data.map((entry, index) => index % labelStep === 0 || index === data.length - 1 ? `
102
+ `,
103
+ )
104
+ .join('')}
105
+ ${data
106
+ .map((entry, index) =>
107
+ index % labelStep === 0 || index === data.length - 1
108
+ ? `
80
109
  <text x="${x(index)}" y="${height - 18}" text-anchor="middle" font-size="11" font-family="${fontFamily}" fill="#5c6b7e">${escapeXml(entry.label)}</text>
81
- ` : '').join('')}
82
- `);
110
+ `
111
+ : '',
112
+ )
113
+ .join('')}
114
+ `,
115
+ );
83
116
  }
84
117
 
85
- function horizontalBarChart(data, { title, formatter = (value) => String(value), getValue, getLabel, getColor, fontFamily = DEFAULT_FONT_FAMILY }) {
118
+ function horizontalBarChart(
119
+ data,
120
+ {
121
+ title,
122
+ formatter = (value) => String(value),
123
+ getValue,
124
+ getLabel,
125
+ getColor,
126
+ fontFamily = DEFAULT_FONT_FAMILY,
127
+ },
128
+ ) {
86
129
  const width = 980;
87
130
  const height = 360;
88
- const longestLabelLength = data.reduce((max, entry) => Math.max(max, String(getLabel(entry) || '').length), 0);
131
+ const longestLabelLength = data.reduce(
132
+ (max, entry) => Math.max(max, String(getLabel(entry) || '').length),
133
+ 0,
134
+ );
89
135
  const margin = {
90
136
  top: 46,
91
137
  right: 100,
@@ -94,39 +140,56 @@ function horizontalBarChart(data, { title, formatter = (value) => String(value),
94
140
  };
95
141
  const plotWidth = width - margin.left - margin.right;
96
142
  const barGap = 18;
97
- const barHeight = Math.min(28, (height - margin.top - margin.bottom - barGap * (data.length - 1)) / Math.max(data.length, 1));
143
+ const barHeight = Math.min(
144
+ 28,
145
+ (height - margin.top - margin.bottom - barGap * (data.length - 1)) / Math.max(data.length, 1),
146
+ );
98
147
  const maxValue = Math.max(...data.map(getValue), 1);
99
148
 
100
- return svgDoc(width, height, `
149
+ return svgDoc(
150
+ width,
151
+ height,
152
+ `
101
153
  <rect width="${width}" height="${height}" rx="24" fill="#ffffff"/>
102
154
  <text x="${margin.left}" y="28" font-size="18" font-family="${fontFamily}" font-weight="700" fill="#122033">${escapeXml(title)}</text>
103
- ${data.map((entry, index) => {
104
- const y = margin.top + index * (barHeight + barGap);
105
- const value = getValue(entry);
106
- const barWidth = clamp((value / maxValue) * plotWidth, 0, plotWidth);
107
- return `
155
+ ${data
156
+ .map((entry, index) => {
157
+ const y = margin.top + index * (barHeight + barGap);
158
+ const value = getValue(entry);
159
+ const barWidth = clamp((value / maxValue) * plotWidth, 0, plotWidth);
160
+ return `
108
161
  <text x="${margin.left - 18}" y="${y + barHeight / 2 + 4}" text-anchor="end" font-size="13" font-family="${fontFamily}" fill="#122033">${escapeXml(truncateSvgLabel(getLabel(entry), 30))}</text>
109
162
  <rect x="${margin.left}" y="${y}" width="${plotWidth}" height="${barHeight}" rx="12" fill="#eef3f8"/>
110
163
  <rect x="${margin.left}" y="${y}" width="${barWidth}" height="${barHeight}" rx="12" fill="${getColor(entry)}"/>
111
164
  <text x="${margin.left + plotWidth + 12}" y="${y + barHeight / 2 + 4}" font-size="12" font-family="${fontFamily}" fill="#475569">${escapeXml(formatter(value))}</text>
112
165
  `;
113
- }).join('')}
114
- `);
166
+ })
167
+ .join('')}
168
+ `,
169
+ );
115
170
  }
116
171
 
117
- function stackedBarChart(data, { title, segments, formatter = (value) => String(value), fontFamily = DEFAULT_FONT_FAMILY }) {
172
+ function stackedBarChart(
173
+ data,
174
+ { title, segments, formatter = (value) => String(value), fontFamily = DEFAULT_FONT_FAMILY },
175
+ ) {
118
176
  const width = 980;
119
177
  const height = 380;
120
178
  const margin = { top: 52, right: 30, bottom: 56, left: 74 };
121
179
  const plotWidth = width - margin.left - margin.right;
122
180
  const plotHeight = height - margin.top - margin.bottom;
123
- const totals = data.map((entry) => segments.reduce((sum, segment) => sum + (Number(entry[segment.key]) || 0), 0));
181
+ const totals = data.map((entry) =>
182
+ segments.reduce((sum, segment) => sum + (Number(entry[segment.key]) || 0), 0),
183
+ );
124
184
  const maxValue = Math.max(...totals, 1);
125
185
  const barWidth = Math.max(10, plotWidth / Math.max(data.length * 1.8, 1));
126
186
  const gap = data.length > 1 ? (plotWidth - data.length * barWidth) / (data.length - 1) : 0;
127
187
  const labelStep = Math.max(1, Math.ceil(data.length / 7));
128
188
 
129
- return svgDoc(width, height, `
189
+ return svgDoc(
190
+ width,
191
+ height,
192
+ `
130
193
  <rect width="${width}" height="${height}" rx="24" fill="#ffffff"/>
131
194
  <text x="${margin.left}" y="30" font-size="18" font-family="${fontFamily}" font-weight="700" fill="#122033">${escapeXml(title)}</text>
132
195
  ${Array.from({ length: 5 }, (_, index) => {
@@ -137,26 +200,36 @@ function stackedBarChart(data, { title, segments, formatter = (value) => String(
137
200
  <text x="${margin.left - 12}" y="${y + 4}" text-anchor="end" font-size="11" font-family="${fontFamily}" fill="#5c6b7e">${escapeXml(formatter(value))}</text>
138
201
  `;
139
202
  }).join('')}
140
- ${data.map((entry, index) => {
141
- const x = margin.left + index * (barWidth + gap);
142
- let offset = 0;
143
- const rects = segments.map((segment) => {
144
- const value = Number(entry[segment.key]) || 0;
145
- const h = maxValue > 0 ? (value / maxValue) * plotHeight : 0;
146
- const y = margin.top + plotHeight - offset - h;
147
- offset += h;
148
- return `<rect x="${x}" y="${y}" width="${barWidth}" height="${h}" rx="6" fill="${segment.color}"/>`;
149
- }).join('');
150
- const label = index % labelStep === 0 || index === data.length - 1
151
- ? `<text x="${x + barWidth / 2}" y="${height - 18}" text-anchor="middle" font-size="11" font-family="${fontFamily}" fill="#5c6b7e">${escapeXml(entry.label)}</text>`
152
- : '';
153
- return `${rects}${label}`;
154
- }).join('')}
155
- ${segments.map((segment, index) => `
203
+ ${data
204
+ .map((entry, index) => {
205
+ const x = margin.left + index * (barWidth + gap);
206
+ let offset = 0;
207
+ const rects = segments
208
+ .map((segment) => {
209
+ const value = Number(entry[segment.key]) || 0;
210
+ const h = maxValue > 0 ? (value / maxValue) * plotHeight : 0;
211
+ const y = margin.top + plotHeight - offset - h;
212
+ offset += h;
213
+ return `<rect x="${x}" y="${y}" width="${barWidth}" height="${h}" rx="6" fill="${segment.color}"/>`;
214
+ })
215
+ .join('');
216
+ const label =
217
+ index % labelStep === 0 || index === data.length - 1
218
+ ? `<text x="${x + barWidth / 2}" y="${height - 18}" text-anchor="middle" font-size="11" font-family="${fontFamily}" fill="#5c6b7e">${escapeXml(entry.label)}</text>`
219
+ : '';
220
+ return `${rects}${label}`;
221
+ })
222
+ .join('')}
223
+ ${segments
224
+ .map(
225
+ (segment, index) => `
156
226
  <rect x="${margin.left + (index % 3) * 156}" y="${height - 34 - Math.floor(index / 3) * 18}" width="12" height="12" rx="3" fill="${segment.color}"/>
157
227
  <text x="${margin.left + 18 + (index % 3) * 156}" y="${height - 24 - Math.floor(index / 3) * 18}" font-size="11" font-family="${fontFamily}" fill="#334155">${escapeXml(segment.label)}</text>
158
- `).join('')}
159
- `);
228
+ `,
229
+ )
230
+ .join('')}
231
+ `,
232
+ );
160
233
  }
161
234
 
162
235
  module.exports = {
@@ -292,11 +292,31 @@ function createChartAssets(reportData) {
292
292
  title: reportData.text.charts.tokenTrend,
293
293
  formatter: (value) => formatCompactAxis(value, reportData.meta.language),
294
294
  segments: [
295
- { key: 'input', label: translate(reportData.meta.language, 'common.input'), color: '#0f766e' },
296
- { key: 'output', label: translate(reportData.meta.language, 'common.output'), color: '#1d4ed8' },
297
- { key: 'cacheWrite', label: translate(reportData.meta.language, 'common.cacheWrite'), color: '#b45309' },
298
- { key: 'cacheRead', label: translate(reportData.meta.language, 'common.cacheRead'), color: '#7c3aed' },
299
- { key: 'thinking', label: translate(reportData.meta.language, 'common.thinking'), color: '#be185d' },
295
+ {
296
+ key: 'input',
297
+ label: translate(reportData.meta.language, 'common.input'),
298
+ color: '#0f766e',
299
+ },
300
+ {
301
+ key: 'output',
302
+ label: translate(reportData.meta.language, 'common.output'),
303
+ color: '#1d4ed8',
304
+ },
305
+ {
306
+ key: 'cacheWrite',
307
+ label: translate(reportData.meta.language, 'common.cacheWrite'),
308
+ color: '#b45309',
309
+ },
310
+ {
311
+ key: 'cacheRead',
312
+ label: translate(reportData.meta.language, 'common.cacheRead'),
313
+ color: '#7c3aed',
314
+ },
315
+ {
316
+ key: 'thinking',
317
+ label: translate(reportData.meta.language, 'common.thinking'),
318
+ color: '#be185d',
319
+ },
300
320
  ],
301
321
  }),
302
322
  };