codexmeter 1.0.17 → 1.0.19

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.
@@ -79,6 +79,17 @@ export function buildLiveBootstrap(live) {
79
79
  };
80
80
  }
81
81
 
82
+ export function buildLiveSnapshot(live) {
83
+ return {
84
+ overview: serializeOverview(live),
85
+ repos: serializeTopRanges(live.repos, live.repoTopKeys, serializeRepoSummary),
86
+ models: serializeTopRanges(live.models, live.modelTopKeys, serializeModelSummary),
87
+ families: serializeTopRanges(live.families, live.familyTopKeys, serializeFamilySummary),
88
+ daily: serializeDailySnapshot(live.daily),
89
+ heatmap: serializeHeatmap(live.heatmap),
90
+ };
91
+ }
92
+
82
93
  export function buildLivePatch(live, patch) {
83
94
  return {
84
95
  overview: Object.fromEntries(
@@ -464,6 +475,10 @@ function serializeDaily(dayMap) {
464
475
  return Object.fromEntries([...dayMap.entries()].map(([dayKey, value]) => [dayKey, serializeDailyEntry(value)]));
465
476
  }
466
477
 
478
+ function serializeDailySnapshot(dayMap) {
479
+ return Object.fromEntries([...dayMap.entries()].map(([dayKey, value]) => [dayKey, serializeDailySnapshotEntry(value)]));
480
+ }
481
+
467
482
  function serializeDailyEntry(value) {
468
483
  return {
469
484
  tokens: Math.round(value?.tokens || 0),
@@ -477,6 +492,10 @@ function serializeDailyEntry(value) {
477
492
  };
478
493
  }
479
494
 
495
+ function serializeDailySnapshotEntry(value) {
496
+ return serializeDailyEntry(value);
497
+ }
498
+
480
499
  function serializeHeatmap(dayMap) {
481
500
  return Object.fromEntries([...dayMap.entries()].map(([dayKey, value]) => [dayKey, serializeHeatmapEntry(value)]));
482
501
  }
@@ -126,14 +126,29 @@ const MODEL_ALIASES = {
126
126
  'codex-mini-latest': 'o4-mini',
127
127
  'gpt 5.5': 'gpt-5.5',
128
128
  'gpt-5.5': 'gpt-5.5',
129
+ 'gpt 5 mini': 'gpt-5-mini',
129
130
  'gpt-5 mini': 'gpt-5-mini',
131
+ 'gpt 5.4 mini': 'gpt-5.4-mini',
130
132
  'gpt-5.4 mini': 'gpt-5.4-mini',
133
+ 'gpt 5 nano': 'gpt-5-nano',
131
134
  'gpt-5 nano': 'gpt-5-nano',
135
+ 'gpt 5.4 nano': 'gpt-5.4-nano',
132
136
  'gpt-5.4 nano': 'gpt-5.4-nano',
133
137
  };
134
138
 
139
+ const MODEL_HYPHEN_CHARS = /[\u2010-\u2015\u2212\uFE58\uFE63\uFF0D]/g;
140
+
141
+ function normalizeModelLookupKey(rawModel) {
142
+ return String(rawModel)
143
+ .normalize('NFKC')
144
+ .replace(MODEL_HYPHEN_CHARS, '-')
145
+ .toLowerCase()
146
+ .trim()
147
+ .replace(/\s+/g, ' ');
148
+ }
149
+
135
150
  export function normalizeModelName(rawModel) {
136
151
  if (!rawModel) return null;
137
- const lower = rawModel.toLowerCase().trim();
138
- return MODEL_ALIASES[lower] || lower;
152
+ const lookup = normalizeModelLookupKey(rawModel);
153
+ return MODEL_ALIASES[lookup] || lookup;
139
154
  }
@@ -1,8 +1,18 @@
1
- import { existsSync } from 'fs';
1
+ import { existsSync, statSync } from 'fs';
2
2
  import { readFile } from 'fs/promises';
3
+ import { spawn } from 'child_process';
3
4
  import { createDayKeyFormatter } from './day-key.js';
4
5
 
5
6
  const ACTIVE_GAP_CAP_MS = 15 * 60 * 1000;
7
+ const LINE_HEADER_SCAN_CHARS = 512;
8
+ const TIMESTAMP_RE = /"timestamp"\s*:\s*"([^"]+)"/;
9
+ const DEFAULT_RG_MIN_BYTES = 10 * 1024 * 1024;
10
+ const RG_RELEVANT_PATTERN =
11
+ '"type"\\s*:\\s*"session_meta"|' +
12
+ '"type"\\s*:\\s*"turn_context"|' +
13
+ '"type"\\s*:\\s*"token_count"';
14
+ const RG_TOKEN_COUNT_PATTERN = '"type"\\s*:\\s*"token_count"';
15
+ const RG_TIMESTAMP_PATTERN = '^\\{[^{}]*"timestamp"\\s*:\\s*"[^"]+"';
6
16
 
7
17
  export async function enrichFromRollout(rolloutPath, opts = {}) {
8
18
  if (!rolloutPath || !existsSync(rolloutPath)) {
@@ -11,6 +21,9 @@ export async function enrichFromRollout(rolloutPath, opts = {}) {
11
21
 
12
22
  const tz = opts.timezone || Intl.DateTimeFormat().resolvedOptions().timeZone;
13
23
  const toDayKey = opts.toDayKey || createDayKeyFormatter(tz);
24
+ const fastScan = opts.fastScan === true;
25
+ const rgScan = opts.rgScan === true;
26
+ const rgMinBytes = Math.max(0, Number(opts.rgMinBytes ?? DEFAULT_RG_MIN_BYTES) || 0);
14
27
  const result = {
15
28
  model_name: null,
16
29
  reasoning_effort: null,
@@ -27,7 +40,20 @@ export async function enrichFromRollout(rolloutPath, opts = {}) {
27
40
  };
28
41
 
29
42
  try {
30
- const content = await readFile(rolloutPath, 'utf8');
43
+ let lines = null;
44
+ let activeFromRgTimestamps = false;
45
+ if (rgScan && shouldUseRipgrep(rolloutPath, rgMinBytes)) {
46
+ const rgResult = await readRolloutLinesWithRipgrep(rolloutPath);
47
+ if (rgResult) {
48
+ lines = rgResult.relevantLines;
49
+ applyTimestampMatches(result, rgResult.timestampMatches, toDayKey);
50
+ activeFromRgTimestamps = true;
51
+ }
52
+ }
53
+ if (!lines) {
54
+ const content = await readFile(rolloutPath, 'utf8');
55
+ lines = content.split(/\r?\n/);
56
+ }
31
57
 
32
58
  let prevTimestamp = null;
33
59
  let activeMs = 0;
@@ -39,21 +65,17 @@ export async function enrichFromRollout(rolloutPath, opts = {}) {
39
65
  let lastReasoningOutputTokens = 0;
40
66
  let lastTotalTokens = 0;
41
67
  let hasSeenUsage = false;
42
- for (const line of content.split(/\r?\n/)) {
68
+ for (const line of lines) {
43
69
  if (!line.trim()) continue;
44
70
 
45
71
  try {
46
- const obj = JSON.parse(line);
72
+ let obj = null;
73
+ let ts = null;
47
74
 
48
- if (obj.timestamp) {
49
- const ts = new Date(obj.timestamp).getTime();
50
- if (!isNaN(ts)) {
51
- if (!result.first_timestamp || ts < result.first_timestamp) {
52
- result.first_timestamp = ts;
53
- }
54
- if (!result.last_timestamp || ts > result.last_timestamp) {
55
- result.last_timestamp = ts;
56
- }
75
+ if (fastScan || activeFromRgTimestamps) {
76
+ ts = extractTimestampMs(line);
77
+ if (ts !== null && !activeFromRgTimestamps) {
78
+ updateTimestampBounds(result, ts);
57
79
  if (prevTimestamp !== null && ts >= prevTimestamp) {
58
80
  const deltaMs = Math.min(ts - prevTimestamp, ACTIVE_GAP_CAP_MS);
59
81
  activeMs += deltaMs;
@@ -64,6 +86,25 @@ export async function enrichFromRollout(rolloutPath, opts = {}) {
64
86
  }
65
87
  prevTimestamp = ts;
66
88
  }
89
+ if (!activeFromRgTimestamps && !isRolloutLineWorthParsing(line)) continue;
90
+ obj = JSON.parse(line);
91
+ } else {
92
+ obj = JSON.parse(line);
93
+ if (obj.timestamp) {
94
+ ts = new Date(obj.timestamp).getTime();
95
+ if (!isNaN(ts)) {
96
+ updateTimestampBounds(result, ts);
97
+ if (prevTimestamp !== null && ts >= prevTimestamp) {
98
+ const deltaMs = Math.min(ts - prevTimestamp, ACTIVE_GAP_CAP_MS);
99
+ activeMs += deltaMs;
100
+ if (deltaMs > 0) {
101
+ const dayKey = toDayKey(prevTimestamp);
102
+ activeByDay.set(dayKey, (activeByDay.get(dayKey) || 0) + deltaMs);
103
+ }
104
+ }
105
+ prevTimestamp = ts;
106
+ }
107
+ }
67
108
  }
68
109
 
69
110
  if (obj.type === 'event_msg' && obj.payload?.type === 'token_count') {
@@ -121,16 +162,13 @@ export async function enrichFromRollout(rolloutPath, opts = {}) {
121
162
  lastOutputTokens = outputTokens;
122
163
  lastReasoningOutputTokens = reasoningOutputTokens;
123
164
  lastTotalTokens = totalTokens;
124
- if (obj.timestamp && hasUsageBoundarySignal(usageDelta)) {
125
- const ts = new Date(obj.timestamp).getTime();
126
- if (!isNaN(ts)) {
127
- if (!result.first_usage_timestamp || ts < result.first_usage_timestamp) {
128
- result.first_usage_timestamp = ts;
129
- }
130
- if (hasUsage(usageDelta)) {
131
- const dayKey = toDayKey(ts);
132
- mergeUsageTotals(usageByDay, dayKey, usageDelta);
133
- }
165
+ if (ts !== null && hasUsageBoundarySignal(usageDelta)) {
166
+ if (!result.first_usage_timestamp || ts < result.first_usage_timestamp) {
167
+ result.first_usage_timestamp = ts;
168
+ }
169
+ if (hasUsage(usageDelta)) {
170
+ const dayKey = toDayKey(ts);
171
+ mergeUsageTotals(usageByDay, dayKey, usageDelta);
134
172
  }
135
173
  }
136
174
  }
@@ -179,19 +217,30 @@ export async function enrichFromRollout(rolloutPath, opts = {}) {
179
217
  return result;
180
218
  }
181
219
 
182
- export async function readUsageTimeline(rolloutPath) {
220
+ export async function readUsageTimeline(rolloutPath, opts = {}) {
183
221
  if (!rolloutPath || !existsSync(rolloutPath)) {
184
222
  return [];
185
223
  }
186
224
 
187
225
  try {
188
- const content = await readFile(rolloutPath, 'utf8');
226
+ const fastScan = opts.fastScan === true;
227
+ const rgScan = opts.rgScan === true;
228
+ const rgMinBytes = Math.max(0, Number(opts.rgMinBytes ?? DEFAULT_RG_MIN_BYTES) || 0);
229
+ let lines = null;
230
+ if (rgScan && shouldUseRipgrep(rolloutPath, rgMinBytes)) {
231
+ lines = await readMatchingLinesWithRipgrep(RG_TOKEN_COUNT_PATTERN, rolloutPath);
232
+ }
233
+ if (!lines) {
234
+ const content = await readFile(rolloutPath, 'utf8');
235
+ lines = content.split(/\r?\n/);
236
+ }
189
237
  const timeline = [];
190
238
  let segmentId = 0;
191
239
  let lastTotalTokens = 0;
192
240
  let hasSeenUsage = false;
193
- for (const line of content.split(/\r?\n/)) {
241
+ for (const line of lines) {
194
242
  if (!line.trim()) continue;
243
+ if (fastScan && !isTokenCountEventLine(line)) continue;
195
244
  try {
196
245
  const obj = JSON.parse(line);
197
246
  if (obj.type !== 'event_msg' || obj.payload?.type !== 'token_count') continue;
@@ -241,6 +290,116 @@ export function findUsageAtOrBefore(timeline, timestampMs) {
241
290
  return findUsageEntryAtOrBefore(timeline, timestampMs)?.usage || null;
242
291
  }
243
292
 
293
+ function shouldUseRipgrep(rolloutPath, minBytes) {
294
+ try {
295
+ return statSync(rolloutPath).size >= minBytes;
296
+ } catch {
297
+ return false;
298
+ }
299
+ }
300
+
301
+ async function readRolloutLinesWithRipgrep(rolloutPath) {
302
+ const [relevantLines, timestampMatches] = await Promise.all([
303
+ readMatchingLinesWithRipgrep(RG_RELEVANT_PATTERN, rolloutPath),
304
+ readMatchingLinesWithRipgrep(RG_TIMESTAMP_PATTERN, rolloutPath, ['--only-matching']),
305
+ ]);
306
+ if (!relevantLines || !timestampMatches) return null;
307
+ return { relevantLines, timestampMatches };
308
+ }
309
+
310
+ function readMatchingLinesWithRipgrep(pattern, rolloutPath, extraArgs = []) {
311
+ return new Promise((resolve) => {
312
+ const args = [
313
+ '--no-heading',
314
+ '--no-line-number',
315
+ '--color',
316
+ 'never',
317
+ ...extraArgs,
318
+ pattern,
319
+ rolloutPath,
320
+ ];
321
+ const child = spawn('rg', args, { stdio: ['ignore', 'pipe', 'pipe'] });
322
+ let stdout = '';
323
+
324
+ child.stdout.setEncoding('utf8');
325
+ child.stdout.on('data', (chunk) => {
326
+ stdout += chunk;
327
+ });
328
+ child.on('error', () => resolve(null));
329
+ child.on('close', (code) => {
330
+ if (code !== 0 && code !== 1) {
331
+ resolve(null);
332
+ return;
333
+ }
334
+ resolve(stdout ? stdout.split(/\r?\n/).filter(Boolean) : []);
335
+ });
336
+ });
337
+ }
338
+
339
+ function applyTimestampMatches(result, timestampMatches, toDayKey) {
340
+ let prevTimestamp = null;
341
+ let activeMs = 0;
342
+ const activeByDay = new Map();
343
+
344
+ for (const line of timestampMatches || []) {
345
+ const ts = extractTimestampMs(line);
346
+ if (ts === null) continue;
347
+ updateTimestampBounds(result, ts);
348
+ if (prevTimestamp !== null && ts >= prevTimestamp) {
349
+ const deltaMs = Math.min(ts - prevTimestamp, ACTIVE_GAP_CAP_MS);
350
+ activeMs += deltaMs;
351
+ if (deltaMs > 0) {
352
+ const dayKey = toDayKey(prevTimestamp);
353
+ activeByDay.set(dayKey, (activeByDay.get(dayKey) || 0) + deltaMs);
354
+ }
355
+ }
356
+ prevTimestamp = ts;
357
+ }
358
+
359
+ if (activeMs > 0) {
360
+ const activeByDaySeconds = Object.fromEntries(
361
+ [...activeByDay.entries()].map(([dayKey, ms]) => [dayKey, Math.round(ms / 1000)])
362
+ );
363
+ result.active_by_day = activeByDaySeconds;
364
+ result.active_seconds = Object.values(activeByDaySeconds).reduce((sum, seconds) => sum + seconds, 0);
365
+ }
366
+ }
367
+
368
+ function updateTimestampBounds(result, ts) {
369
+ if (!result.first_timestamp || ts < result.first_timestamp) {
370
+ result.first_timestamp = ts;
371
+ }
372
+ if (!result.last_timestamp || ts > result.last_timestamp) {
373
+ result.last_timestamp = ts;
374
+ }
375
+ }
376
+
377
+ function extractTimestampMs(line) {
378
+ const head = line.length > LINE_HEADER_SCAN_CHARS
379
+ ? line.slice(0, LINE_HEADER_SCAN_CHARS)
380
+ : line;
381
+ const match = TIMESTAMP_RE.exec(head);
382
+ if (!match) return null;
383
+ const ts = Date.parse(match[1]);
384
+ return Number.isNaN(ts) ? null : ts;
385
+ }
386
+
387
+ function isRolloutLineWorthParsing(line) {
388
+ const head = line.length > LINE_HEADER_SCAN_CHARS
389
+ ? line.slice(0, LINE_HEADER_SCAN_CHARS)
390
+ : line;
391
+ return head.includes('"type":"token_count"') ||
392
+ head.includes('"type":"turn_context"') ||
393
+ head.includes('"type":"session_meta"');
394
+ }
395
+
396
+ function isTokenCountEventLine(line) {
397
+ const head = line.length > LINE_HEADER_SCAN_CHARS
398
+ ? line.slice(0, LINE_HEADER_SCAN_CHARS)
399
+ : line;
400
+ return head.includes('"type":"token_count"');
401
+ }
402
+
244
403
  function normalizeUsageTotals(usage) {
245
404
  return {
246
405
  input_tokens: usage.input_tokens || 0,
@@ -3,8 +3,9 @@ import { Worker } from 'worker_threads';
3
3
 
4
4
  export function createRolloutWorkerPool(opts = {}) {
5
5
  const size = normalizePoolSize(opts.size);
6
+ const readerOptions = opts.readerOptions || {};
6
7
  if (size <= 1) {
7
- return createInlinePool();
8
+ return createInlinePool({ readerOptions });
8
9
  }
9
10
 
10
11
  const workers = new Set();
@@ -89,6 +90,7 @@ export function createRolloutWorkerPool(opts = {}) {
89
90
  id,
90
91
  rolloutPath: task.rolloutPath,
91
92
  timezone: task.timezone,
93
+ readerOptions,
92
94
  });
93
95
  }
94
96
  }
@@ -107,6 +109,70 @@ export function createRolloutWorkerPool(opts = {}) {
107
109
  return Promise.all(rolloutPaths.map((rolloutPath) => runTask(rolloutPath, timezone)));
108
110
  }
109
111
 
112
+ async function mapRolloutsInChunks(rolloutPaths, timezone, { chunkSize = 100, onChunk } = {}) {
113
+ const results = new Array(rolloutPaths.length);
114
+ const completed = new Array(rolloutPaths.length).fill(false);
115
+ const safeChunkSize = Math.max(1, Number(chunkSize) || 1);
116
+ const maxInFlight = Math.max(safeChunkSize, size * 4);
117
+ let nextStartIndex = 0;
118
+ let nextFlushIndex = 0;
119
+ let activeCount = 0;
120
+ let completedCount = 0;
121
+ let flushChain = Promise.resolve();
122
+ let rejected = false;
123
+
124
+ const scheduleFlush = (force = false) => {
125
+ if (typeof onChunk !== 'function') return;
126
+ const chunk = [];
127
+ while (nextFlushIndex < results.length && completed[nextFlushIndex]) {
128
+ chunk.push({ index: nextFlushIndex, result: results[nextFlushIndex] });
129
+ nextFlushIndex += 1;
130
+ if (!force && chunk.length >= safeChunkSize) break;
131
+ }
132
+ if (!chunk.length) return;
133
+ flushChain = flushChain.then(() => onChunk(chunk));
134
+ };
135
+
136
+ await new Promise((resolve, reject) => {
137
+ const launchNext = () => {
138
+ if (rejected) return;
139
+ while (activeCount < maxInFlight && nextStartIndex < rolloutPaths.length) {
140
+ const index = nextStartIndex;
141
+ const rolloutPath = rolloutPaths[index];
142
+ nextStartIndex += 1;
143
+ activeCount += 1;
144
+ runTask(rolloutPath, timezone)
145
+ .then((result) => {
146
+ results[index] = result;
147
+ completed[index] = true;
148
+ completedCount += 1;
149
+ activeCount -= 1;
150
+ scheduleFlush(false);
151
+ if (completedCount >= rolloutPaths.length && activeCount === 0) {
152
+ resolve();
153
+ return;
154
+ }
155
+ launchNext();
156
+ })
157
+ .catch((error) => {
158
+ rejected = true;
159
+ reject(error);
160
+ });
161
+ }
162
+
163
+ if (completedCount >= rolloutPaths.length && activeCount === 0) {
164
+ resolve();
165
+ }
166
+ };
167
+
168
+ launchNext();
169
+ });
170
+
171
+ scheduleFlush(true);
172
+ await flushChain;
173
+ return results;
174
+ }
175
+
110
176
  async function close() {
111
177
  closed = true;
112
178
  while (queuedTasks.length > 0) {
@@ -122,10 +188,10 @@ export function createRolloutWorkerPool(opts = {}) {
122
188
  idleWorkers.length = 0;
123
189
  }
124
190
 
125
- return { mapRollouts, close, size };
191
+ return { mapRollouts, mapRolloutsInChunks, close, size };
126
192
  }
127
193
 
128
- function createInlinePool() {
194
+ function createInlinePool({ readerOptions = {} } = {}) {
129
195
  return {
130
196
  size: 1,
131
197
  async mapRollouts(rolloutPaths, timezone) {
@@ -133,7 +199,7 @@ function createInlinePool() {
133
199
  return Promise.all(
134
200
  rolloutPaths.map(async (rolloutPath) => {
135
201
  try {
136
- const data = await enrichFromRollout(rolloutPath, { timezone });
202
+ const data = await enrichFromRollout(rolloutPath, { timezone, ...readerOptions });
137
203
  return { ok: true, data, error: null };
138
204
  } catch (error) {
139
205
  return {
@@ -145,6 +211,39 @@ function createInlinePool() {
145
211
  })
146
212
  );
147
213
  },
214
+ async mapRolloutsInChunks(rolloutPaths, timezone, { chunkSize = 100, onChunk } = {}) {
215
+ const { enrichFromRollout } = await import('./rollout-reader.js');
216
+ const results = [];
217
+ const completed = [];
218
+ const safeChunkSize = Math.max(1, Number(chunkSize) || 1);
219
+
220
+ for (let index = 0; index < rolloutPaths.length; index += 1) {
221
+ try {
222
+ const data = await enrichFromRollout(rolloutPaths[index], { timezone, ...readerOptions });
223
+ const result = { ok: true, data, error: null };
224
+ results[index] = result;
225
+ completed.push({ index, result });
226
+ } catch (error) {
227
+ const result = {
228
+ ok: false,
229
+ data: null,
230
+ error: error instanceof Error ? error.message : String(error),
231
+ };
232
+ results[index] = result;
233
+ completed.push({ index, result });
234
+ }
235
+
236
+ if (typeof onChunk === 'function' && completed.length >= safeChunkSize) {
237
+ await onChunk(completed.splice(0, completed.length));
238
+ }
239
+ }
240
+
241
+ if (typeof onChunk === 'function' && completed.length) {
242
+ await onChunk(completed.splice(0, completed.length));
243
+ }
244
+
245
+ return results;
246
+ },
148
247
  async close() {},
149
248
  };
150
249
  }
@@ -6,10 +6,10 @@ if (!parentPort) {
6
6
  }
7
7
 
8
8
  parentPort.on('message', async (message) => {
9
- const { id, rolloutPath, timezone } = message || {};
9
+ const { id, rolloutPath, timezone, readerOptions } = message || {};
10
10
 
11
11
  try {
12
- const result = await enrichFromRollout(rolloutPath, { timezone });
12
+ const result = await enrichFromRollout(rolloutPath, { timezone, ...(readerOptions || {}) });
13
13
  parentPort.postMessage({ id, ok: true, data: result });
14
14
  } catch (error) {
15
15
  parentPort.postMessage({
@@ -6,7 +6,7 @@ const AUTO = 'auto';
6
6
  * This is the single source of truth for:
7
7
  * - Overview client-side presentation timing
8
8
  * - Overview ECharts timing defaults
9
- * - Overview server-side tail pacing
9
+ * - Overview live snapshot transport cadence
10
10
  *
11
11
  * `speed` scales the main timings:
12
12
  * - `1` = baseline
@@ -21,8 +21,7 @@ export const OVERVIEW_INGEST_ANIMATION = {
21
21
  speed: 1,
22
22
  live: {
23
23
  frameIntervalMs: 33,
24
- overviewHz: 20,
25
- dayKeysPerEmit: 1,
24
+ snapshotHz: 12,
26
25
  },
27
26
  main: {
28
27
  presentationDurationMs: 220,
@@ -35,15 +34,13 @@ export const OVERVIEW_INGEST_ANIMATION = {
35
34
  // Master switch for the end-of-ingest slowdown behavior.
36
35
  enabled: true,
37
36
  // Progress threshold where the tail mode starts, expressed from 0..1.
38
- startPercent: 0.5,
37
+ startPercent: 0.95,
39
38
  // Fixed presentation duration in ms during the tail; use AUTO to derive it from main.durationScale.
40
39
  durationMs: AUTO,
41
40
  // Multiplier applied to the normal presentation duration when durationMs is AUTO.
42
41
  durationScale: 10,
43
42
  // Shared easing applied by the Overview presentation animator during the tail.
44
43
  easing: 'cubicOut',
45
- // Backend Overview live-update cadence during the tail, in patches per second.
46
- overviewHz: 10,
47
44
  },
48
45
  daily: {
49
46
  chartAppearDurationMs: AUTO,
@@ -1 +0,0 @@
1
- :root{--bg-base:#06080f;--bg-surface:#0d1117;--bg-card:#161b22;--bg-card-hover:#1c2333;--bg-elevated:#21262d;--border:#30363d;--border-accent:#6366f140;--text-primary:#e6edf3;--text-secondary:#8b949e;--text-muted:#484f58;--accent:#6366f1;--accent-dim:#6366f11f;--green:#3fb950;--yellow:#d29922;--red:#f85149;--orange:#f97316;--cyan:#39d2f5;--pink:#f472b6;--font-sans:"Inter", -apple-system, BlinkMacSystemFont, sans-serif;--font-mono:"JetBrains Mono", "Fira Code", monospace;--radius:8px;--radius-lg:12px}*{box-sizing:border-box;margin:0;padding:0}body{font-family:var(--font-sans);background:var(--bg-base);color:var(--text-primary);-webkit-font-smoothing:antialiased;line-height:1.6}.app{flex-direction:column;min-height:100vh;display:flex}.app-content{opacity:0;flex-direction:column;min-height:100vh;display:flex}.app-content-revealed{opacity:1;animation:.5s forwards contentReveal}@keyframes contentReveal{0%{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}.navbar{background:var(--bg-surface);border-bottom:1px solid var(--border);z-index:100;-webkit-backdrop-filter:blur(12px);backdrop-filter:blur(12px);position:sticky;top:0}.navbar-inner{box-sizing:border-box;align-items:center;gap:1.5rem;width:100%;max-width:1400px;height:48px;margin:0 auto;padding:0 2rem;display:flex}.navbar-brand{letter-spacing:-.03em;background:linear-gradient(135deg,#818cf8,#6366f1);-webkit-text-fill-color:transparent;-webkit-background-clip:text;font-size:.95rem;font-weight:700}.navbar-tabs{gap:2px;display:flex}.navbar-tab{cursor:pointer;color:var(--text-secondary);background:0 0;border:none;border-radius:6px;padding:.35rem .85rem;font-size:.8rem;font-weight:500;transition:all .15s,opacity .35s}.navbar-tab:hover{color:var(--text-primary);background:var(--bg-card)}.navbar-tab.active{color:#fff;background:var(--accent)}.navbar-tab.navbar-tab-dimmed,.navbar-tab.navbar-tab-dimmed:hover{opacity:.4;color:var(--text-muted)}.navbar-meta{flex:1;align-items:center;gap:.75rem;min-width:0;margin-left:auto;display:flex}.navbar-ingest-wrap{flex:1;align-items:center;gap:.75rem;min-width:0;transition:opacity .3s;display:flex}.navbar-ingest-wrap.navbar-ingest-fade-out{opacity:0;pointer-events:none}.navbar-date-wrap{flex-shrink:0;align-items:center;gap:.75rem;margin-left:auto;transition:opacity .3s;display:flex}.navbar-date-wrap-dimmed{opacity:.5;pointer-events:none}.navbar-date{font-variant-numeric:tabular-nums;text-align:right;text-overflow:ellipsis;width:22ch;min-width:22ch;display:inline-block;overflow:hidden}.navbar-progress-wrap{background:var(--bg-elevated);border-radius:2px;flex:1;height:4px;overflow:hidden}.navbar-progress-bar{background:linear-gradient(90deg, var(--accent), #818cf8);border-radius:2px;height:100%;transition:width .4s}.navbar-status{color:var(--text-muted);font-size:.7rem;font-family:var(--font-mono)}.main-content{flex:1;width:100%;min-width:0;max-width:1400px;margin:0 auto;padding:1.5rem 2rem 1.25rem;container:main/inline-size}.app-footer{color:var(--text-muted);text-align:center;box-sizing:border-box;width:100%;max-width:1400px;margin:auto auto 0;padding:.5rem 2rem .75rem;font-size:.7rem}.section-header{justify-content:space-between;align-items:center;margin-bottom:1.25rem;display:flex}.section-title{letter-spacing:-.01em;font-size:1rem;font-weight:600}.stat-row{flex-wrap:wrap;align-items:stretch;gap:1rem;margin-bottom:1.5rem;display:flex}.stat-cards{flex:none;grid-template-columns:repeat(4,minmax(160px,1fr));gap:.75rem;min-width:0;display:grid}@media (width<=920px){.stat-cards{flex:auto;grid-template-columns:repeat(2,minmax(160px,1fr))}}@media (width<=500px){.stat-cards{grid-template-columns:1fr}}.stat-card{background:var(--bg-card);border:1px solid var(--border);border-radius:var(--radius);padding:1rem 1.1rem;transition:border-color .2s}.stat-card:hover{border-color:var(--border-accent)}.stat-label{text-transform:uppercase;letter-spacing:.06em;color:var(--text-muted);margin-bottom:.35rem;font-size:.65rem;font-weight:600}.stat-value{font-size:1.6rem;font-weight:700;font-family:var(--font-mono);letter-spacing:-.03em;line-height:1.1}.stat-sub{color:var(--text-muted);margin-top:.2rem;font-size:.72rem}.stat-per-day{color:var(--text-muted);font-size:.72rem;font-family:var(--font-mono);margin-top:.25rem}.stat-per-day .stat-per-day-value{color:var(--text-secondary);font-weight:600}.range-toggle{background:var(--bg-card);border:1px solid var(--border);border-radius:6px;display:inline-flex;overflow:hidden}.range-btn{color:var(--text-muted);cursor:pointer;background:0 0;border:none;padding:.3rem .7rem;font-size:.72rem;font-weight:600;transition:all .15s}.range-btn:hover{color:var(--text-secondary)}.range-btn.active{color:#fff;background:var(--accent)}.range-btn:disabled{cursor:not-allowed}.export-video-btn{min-width:110px}.export-video-btn:disabled{cursor:not-allowed}.range-btn-unsupported{color:#ffffffeb;background:var(--accent);border-left:1px solid #f0b90b38;position:relative}.range-btn-unsupported:before{content:"";pointer-events:none;background:repeating-linear-gradient(-45deg,#f0b90b0d 0 6px,#ffffff03 6px 12px);position:absolute;inset:0}.range-btn-unsupported:disabled{opacity:.78}.heatmap-wrap{background:var(--bg-card);border:1px solid var(--border);border-radius:var(--radius-lg);margin-bottom:1.5rem;padding:1.25rem;overflow-x:auto}.heatmap-grid{grid-template-rows:repeat(7,1fr);grid-auto-flow:column;gap:3px;display:grid}.heatmap-cell{background:var(--bg-elevated);opacity:.4;border-radius:2px;width:13px;height:13px;transition:background .18s,opacity .22s,transform .22s;transform:scale(.82)}.heatmap-cell.active{opacity:1;transform:scale(1)}.heatmap-cell:hover{outline:1px solid var(--text-muted)}.heatmap-cell.heatmap-cell-pop{animation:.38s cubic-bezier(.34,1.56,.64,1) heatmap-cell-pop}@keyframes heatmap-cell-pop{0%{box-shadow:none;transform:scale(1)}40%{transform:scale(1.4);box-shadow:0 0 10px #6366f1b3,0 0 0 1.5px #0003}70%{transform:scale(1.1);box-shadow:0 0 4px #6366f159,0 0 0 .5px #0000001a}to{box-shadow:none;transform:scale(1)}}.heatmap-cell.heatmap-cell-pop-dim{animation:.32s cubic-bezier(.34,1.56,.64,1) heatmap-cell-pop-dim}@keyframes heatmap-cell-pop-dim{0%{transform:scale(1)}50%{transform:scale(.85)}to{transform:scale(1)}}.heatmap-labels{color:var(--text-muted);justify-content:space-between;margin-top:.5rem;font-size:.65rem;display:flex}.chart-card{background:var(--bg-card);border:1px solid var(--border);border-radius:var(--radius-lg);margin-bottom:1.5rem;padding:1.25rem;position:relative}.chart-header{justify-content:space-between;align-items:center;margin-bottom:.75rem;display:flex}.chart-title{font-size:.85rem;font-weight:600}.chart-badge{color:var(--yellow);background:#d299221a;border-radius:4px;padding:.15rem .5rem;font-size:.62rem}.export-btn{border:1px solid var(--border);background:var(--bg-surface);color:var(--text-muted);cursor:pointer;opacity:0;z-index:2;border-radius:4px;padding:.25rem .5rem;font-size:.65rem;transition:all .15s;position:absolute;top:.75rem;right:.75rem}.chart-card:hover .export-btn{opacity:.7}.export-btn:hover{color:var(--text-primary);border-color:var(--accent);opacity:1!important}.grid-2{grid-template-columns:1fr 1fr;gap:1.25rem;min-width:0;margin-bottom:1.5rem;display:grid}.grid-3{grid-template-columns:repeat(3,minmax(0,1fr));gap:1.25rem;min-width:0;margin-bottom:1.5rem;display:grid}.grid-3>.chart-card{min-width:0;overflow:hidden}.grid-3>.overview-donut-card{overflow:visible}.overview-donut-card .echarts-for-react,.overview-donut-card .echarts-for-react>div{overflow:visible!important}@media (width<=900px){.grid-2,.grid-3{grid-template-columns:1fr}}.btn-group{gap:2px;display:flex}.btn{border:1px solid var(--border);background:var(--bg-surface);color:var(--text-muted);cursor:pointer;border-radius:5px;padding:.3rem .65rem;font-size:.72rem;font-weight:500;transition:all .12s}.btn:hover{color:var(--text-secondary);border-color:var(--text-muted)}.btn.active{color:#fff;background:var(--accent);border-color:var(--accent)}.icon-btn{border:1px solid var(--border);background:var(--bg-card);width:28px;height:28px;color:var(--text-muted);cursor:pointer;border-radius:6px;justify-content:center;align-items:center;font-size:.9rem;line-height:1;transition:color .12s,border-color .12s,background .12s,transform .12s;display:inline-flex}.icon-btn:hover:not(:disabled){color:var(--text-primary);border-color:var(--accent);background:var(--bg-elevated);transform:rotate(20deg)}.icon-btn:disabled{opacity:.55;cursor:wait}.overview-daily-spark{background:var(--bg-card);border:1px solid var(--border);border-radius:var(--radius-lg);flex:140px;min-width:140px;min-height:0;position:relative;overflow:visible}.overview-daily-spark-title{text-transform:uppercase;letter-spacing:.06em;color:var(--text-muted);z-index:1;pointer-events:none;font-size:.65rem;font-weight:600;position:absolute;top:1rem;left:1.1rem}.overview-daily-spark-chart{position:absolute;inset:0;overflow:hidden}.overview-daily-spark-empty{justify-content:center;align-items:center;display:flex}.overview-daily-spark-empty-text{color:var(--text-muted);font-size:.7rem}@media (width<=920px){.overview-daily-spark{flex:100%;min-width:100%;min-height:140px}}.table-wrap{background:var(--bg-card);border:1px solid var(--border);border-radius:var(--radius-lg);overflow:hidden}.model-detail-wrap{flex-direction:column;gap:.85rem;display:flex}.model-detail-note{color:var(--text-muted);font-size:.74rem;line-height:1.4}.model-detail-footer{justify-content:flex-start;margin-top:.25rem;display:flex}.model-detail-toggle{width:fit-content;color:var(--text-muted);border:1px solid var(--border);cursor:pointer;background:#ffffff0a;border-radius:6px;align-items:center;gap:.35rem;padding:.35rem .6rem;font-size:.72rem;font-weight:500;transition:color .2s,background .2s,border-color .2s;display:inline-flex}.model-detail-toggle:hover{color:var(--text-secondary);background:#ffffff0f;border-color:#6366f14d}.model-detail-toggle-text{font-family:var(--font-sans)}.model-detail-toggle-arrow{opacity:.8;font-size:.6rem;transition:transform .25s}.model-detail-toggle-arrow.expanded{transform:rotate(180deg)}.model-detail-charts{z-index:10;flex-wrap:wrap;align-items:flex-start;gap:.25rem;width:100%;display:flex;position:relative;overflow:visible}.model-detail-donut{z-index:10;flex-direction:column;flex:0 0 200px;width:200px;min-width:200px;height:200px;display:flex;overflow:visible}.model-detail-donut .chart-title{flex-shrink:0}.model-detail-donut .echarts-for-react{flex:1;min-height:0}.model-detail-wrap .model-detail-donut{flex:0 0 340px;width:340px;min-width:340px;height:220px}.model-detail-donut .echarts-for-react,.model-detail-donut .echarts-for-react>div,.model-detail-bar .echarts-for-react,.model-detail-bar .echarts-for-react>div{overflow:visible!important}.model-detail-bar{flex-direction:column;flex:300px;min-width:300px;height:200px;display:flex;overflow:visible}.model-detail-bar .chart-title{flex-shrink:0}.model-detail-bar .echarts-for-react{flex:1;min-height:0}.model-detail-summary-wrap{max-height:0;transition:max-height .2s ease-out;overflow:hidden}.model-detail-summary-wrap.expanded{max-height:400px}.model-detail-summary{grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:.6rem;padding-top:.5rem;display:grid}.model-detail-summary-card{border:1px solid var(--border);background:#ffffff05;border-radius:10px;padding:.65rem .75rem}.model-detail-summary-head{color:var(--text-primary);text-transform:capitalize;align-items:center;gap:.45rem;min-width:0;margin-bottom:.45rem;font-size:.76rem;font-weight:600;display:flex}.model-detail-summary-head .model-detail-summary-name{text-overflow:ellipsis;white-space:nowrap;min-width:0;overflow:hidden}.model-detail-swatch{border-radius:999px;flex:none;width:9px;height:9px}.model-detail-summary-line{color:var(--text-muted);font-size:.72rem;line-height:1.45}.detail-summary-grid{grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:.75rem;margin-bottom:1rem;display:grid}.detail-summary-stat{border:1px solid var(--border);background:#ffffff08;border-radius:10px;padding:.8rem .9rem}.detail-summary-label{text-transform:uppercase;letter-spacing:.05em;color:var(--text-muted);margin-bottom:.25rem;font-size:.68rem}.detail-summary-value{font-size:1.1rem;font-family:var(--font-mono);color:var(--text-primary)}.table-search{border-bottom:1px solid var(--border);padding:.75rem 1rem}.table-search input{border:1px solid var(--border);background:var(--bg-surface);width:100%;color:var(--text-primary);font-size:.82rem;font-family:var(--font-sans);border-radius:6px;outline:none;padding:.5rem .85rem}.table-search input:focus{border-color:var(--accent)}.table-search input::placeholder{color:var(--text-muted)}table{border-collapse:collapse;width:100%;font-size:.78rem}th{text-align:left;color:var(--text-muted);border-bottom:1px solid var(--border);text-transform:uppercase;letter-spacing:.05em;cursor:pointer;-webkit-user-select:none;user-select:none;white-space:nowrap;padding:.55rem .85rem;font-size:.68rem;font-weight:600}td{color:var(--text-secondary);white-space:nowrap;text-overflow:ellipsis;border-bottom:1px solid #30363d80;max-width:240px;padding:.5rem .85rem;overflow:hidden}tr:hover td{color:var(--text-primary);background:#6366f108}.tag{font-size:.66rem;font-family:var(--font-mono);border-radius:4px;padding:.1rem .4rem;font-weight:500;display:inline-block}.tag-review{color:var(--cyan);background:#06b6d41f}.tag-exploration{color:var(--orange);background:#f973161f}.tag-planning{color:var(--yellow);background:#eab3081f}.tag-memory{color:var(--pink);background:#ec48991f}.tag-generic{color:var(--text-secondary);background:#64748b1f}.loading-overlay{-webkit-backdrop-filter:blur(20px);backdrop-filter:blur(20px);z-index:999;background:#06080feb;flex-direction:column;justify-content:center;align-items:center;transition:opacity .6s;display:flex;position:fixed;inset:0}.loading-overlay.fading{opacity:0;pointer-events:none}.loading-logo{background:linear-gradient(135deg,#818cf8,#6366f1);-webkit-text-fill-color:transparent;-webkit-background-clip:text;background-clip:text;margin-bottom:1.5rem;font-size:1.5rem;font-weight:700}.loading-bar-wrap{background:var(--bg-elevated);border-radius:2px;width:260px;height:4px;margin-bottom:1rem;overflow:hidden}.loading-bar{background:linear-gradient(90deg, var(--accent), #818cf8);border-radius:2px;height:100%;transition:width .4s}.loading-phase{color:var(--text-secondary);margin-bottom:.2rem;font-size:.82rem;font-weight:500}.loading-detail{font-family:var(--font-mono);color:var(--text-muted);font-size:.72rem}.loading-footer{color:var(--text-muted);font-size:.7rem;position:absolute;bottom:1.5rem}.loading-heart{color:#f43f5e}.coverage-subtle{color:var(--text-muted);align-items:center;gap:1.5rem;margin-top:-.5rem;margin-bottom:.5rem;padding:.25rem 0;font-size:.68rem;display:flex}.coverage-item{font-variant-numeric:tabular-nums;align-items:center;gap:.35rem;display:flex}.coverage-nums{text-align:right;min-width:9ch;display:inline-block}.coverage-nums-single{min-width:4ch}.coverage-dot{border-radius:50%;width:6px;height:6px}html{scrollbar-width:none}::-webkit-scrollbar{width:0;height:0;display:none}@keyframes fadeUp{0%{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}.animate-in{animation:.35s forwards fadeUp}@keyframes pulse{0%,to{opacity:1}50%{opacity:.5}}.incomplete-badge{color:var(--yellow);background:#d299221a;border-radius:3px;padding:.12rem .45rem;font-size:.65rem;animation:2s infinite pulse}.ingesting-badge .ingesting-pct{text-align:right;font-variant-numeric:tabular-nums;min-width:4ch;display:inline-block}.echarts-tooltip,[class*=ec-component-tooltip]{max-width:90vw!important;max-height:80vh!important;overflow:auto!important}