codexmeter 1.0.1 → 1.0.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/bin/codexmeter.js +1 -0
- package/dist/assets/index-C0qLYGUY.css +1 -0
- package/dist/assets/index-M87czq50.js +113 -0
- package/dist/index.html +2 -2
- package/package.json +5 -2
- package/server/export-replay.js +80 -0
- package/server/export-video.js +313 -0
- package/server/index.js +68 -1
- package/server/ingest.js +125 -41
- package/server/rollout-reader.js +4 -0
- package/src/utils/animationsDefault.js +244 -0
- package/dist/assets/index-DJWqyRDh.css +0 -1
- package/dist/assets/index-DZKogILW.js +0 -113
package/server/ingest.js
CHANGED
|
@@ -9,17 +9,14 @@ import { buildAggregates, buildSessionView } from './aggregator.js';
|
|
|
9
9
|
import { createDayKeyFormatter } from './day-key.js';
|
|
10
10
|
import { createLiveAggregateState, createEmptyLivePatch, applySessionToLiveState, buildLiveBootstrap, buildLivePatch } from './live-state.js';
|
|
11
11
|
import { createRolloutWorkerPool } from './rollout-worker-pool.js';
|
|
12
|
+
import { beginReplayCapture, createReplayCaptureState, failReplayCapture, getReplaySnapshot, recordReplayEvent, resetReplayCapture } from './export-replay.js';
|
|
13
|
+
import { OVERVIEW_INGEST_ANIMATION } from '../src/utils/animationsDefault.js';
|
|
12
14
|
|
|
13
15
|
const LIVE_FRAME_INTERVAL_MS = 50;
|
|
14
16
|
const LIVE_DAYS_PER_SECOND = 6;
|
|
15
|
-
const
|
|
17
|
+
const LIVE_OVERVIEW_CADENCE_MS = Math.round(1000 / 10);
|
|
16
18
|
const LIVE_DAY_KEYS_PER_EMIT = 1;
|
|
17
|
-
const
|
|
18
|
-
overview: LIVE_FRAME_INTERVAL_MS * 2,
|
|
19
|
-
rankings: LIVE_FRAME_INTERVAL_MS * 3,
|
|
20
|
-
daily: LIVE_DAY_CADENCE_MS,
|
|
21
|
-
heatmap: LIVE_DAY_CADENCE_MS,
|
|
22
|
-
};
|
|
19
|
+
const LIVE_SESSION_REORDER_BUFFER = 160;
|
|
23
20
|
|
|
24
21
|
export function createIngestState() {
|
|
25
22
|
return {
|
|
@@ -43,9 +40,11 @@ export function createIngestState() {
|
|
|
43
40
|
live_subscribers: new Set(),
|
|
44
41
|
live_pump_timer: null,
|
|
45
42
|
live_pending_patch: createEmptyLivePatch(),
|
|
43
|
+
live_session_buffer: [],
|
|
46
44
|
live_progress_dirty: false,
|
|
47
45
|
live_last_emit_at: 0,
|
|
48
|
-
|
|
46
|
+
live_last_overview_emit_at: 0,
|
|
47
|
+
replay_capture: createReplayCaptureState(),
|
|
49
48
|
};
|
|
50
49
|
}
|
|
51
50
|
|
|
@@ -60,6 +59,12 @@ export async function runIngest(codexHome, state, opts = {}) {
|
|
|
60
59
|
state.live_state = createLiveAggregateState(tz);
|
|
61
60
|
state.phase = 'inventory';
|
|
62
61
|
state.percent = 0;
|
|
62
|
+
beginReplayCapture(state.replay_capture, state.ingest_id, {
|
|
63
|
+
ingest_id: state.ingest_id,
|
|
64
|
+
seq: 0,
|
|
65
|
+
progress: progressPayload(state),
|
|
66
|
+
data: buildLiveBootstrap(state.live_state),
|
|
67
|
+
});
|
|
63
68
|
queueLiveProgress(state);
|
|
64
69
|
broadcastBootstrap(state);
|
|
65
70
|
|
|
@@ -100,6 +105,7 @@ export async function runIngest(codexHome, state, opts = {}) {
|
|
|
100
105
|
usage_total: null,
|
|
101
106
|
usage_by_day: null,
|
|
102
107
|
has_usage_by_day: false,
|
|
108
|
+
live_sort_ts: null,
|
|
103
109
|
active_by_day: null,
|
|
104
110
|
agent_role: t.agent_role,
|
|
105
111
|
agent_nickname: t.agent_nickname,
|
|
@@ -134,7 +140,7 @@ export async function runIngest(codexHome, state, opts = {}) {
|
|
|
134
140
|
|
|
135
141
|
state.needs_enrichment = candidates.length;
|
|
136
142
|
state.percent = candidates.length > 0 ? 0.08 : 0.90;
|
|
137
|
-
const BATCH_SIZE = opts.batchSize ||
|
|
143
|
+
const BATCH_SIZE = opts.batchSize || 40;
|
|
138
144
|
const ROOT_REFRESH_EVERY = opts.rootRefreshEvery || 1000;
|
|
139
145
|
let lastRootRefreshCount = 0;
|
|
140
146
|
|
|
@@ -166,12 +172,16 @@ export async function runIngest(codexHome, state, opts = {}) {
|
|
|
166
172
|
s.usage_by_day = buildUsageByDayMetrics(s.model_name, data.usage_by_day);
|
|
167
173
|
s.has_usage_by_day = s.usage_by_day.length > 0;
|
|
168
174
|
}
|
|
175
|
+
if (data.first_usage_timestamp) {
|
|
176
|
+
s.live_sort_ts = Math.floor(data.first_usage_timestamp / 1000);
|
|
177
|
+
}
|
|
169
178
|
if (data.active_seconds && data.active_seconds > 0) {
|
|
170
179
|
s.elapsed_seconds = data.active_seconds;
|
|
171
180
|
s.active_by_day = data.active_by_day || null;
|
|
172
181
|
}
|
|
173
182
|
}
|
|
174
183
|
finalizeSessionMetrics(s, toDayKey);
|
|
184
|
+
s.live_sort_day = deriveLiveSortDay(s, toDayKey);
|
|
175
185
|
s.materialized = true;
|
|
176
186
|
}
|
|
177
187
|
|
|
@@ -183,8 +193,14 @@ export async function runIngest(codexHome, state, opts = {}) {
|
|
|
183
193
|
assignRootThreadIds(sessions);
|
|
184
194
|
lastRootRefreshCount = state.enriched;
|
|
185
195
|
}
|
|
196
|
+
const liveReady = bufferLiveSessionsForPresentation(state, batch);
|
|
197
|
+
if (state.live_session_buffer[0]?.live_sort_day) {
|
|
198
|
+
state.current_date_bucket = state.live_session_buffer[0].live_sort_day;
|
|
199
|
+
} else if (liveReady[0]?.live_sort_day) {
|
|
200
|
+
state.current_date_bucket = liveReady[0].live_sort_day;
|
|
201
|
+
}
|
|
186
202
|
const livePatch = createEmptyLivePatch();
|
|
187
|
-
for (const session of
|
|
203
|
+
for (const session of liveReady) {
|
|
188
204
|
applySessionToLiveState(state.live_state, session, livePatch);
|
|
189
205
|
}
|
|
190
206
|
queueLivePatch(state, livePatch);
|
|
@@ -203,6 +219,11 @@ export async function runIngest(codexHome, state, opts = {}) {
|
|
|
203
219
|
}
|
|
204
220
|
|
|
205
221
|
assignRootThreadIds(sessions);
|
|
222
|
+
const finalLivePatch = createEmptyLivePatch();
|
|
223
|
+
for (const session of flushBufferedLiveSessions(state)) {
|
|
224
|
+
applySessionToLiveState(state.live_state, session, finalLivePatch);
|
|
225
|
+
}
|
|
226
|
+
queueLivePatch(state, finalLivePatch);
|
|
206
227
|
|
|
207
228
|
state.phase = 'aggregation';
|
|
208
229
|
state.percent = state.needs_enrichment > 0 ? 0.95 : 0.90;
|
|
@@ -222,6 +243,7 @@ export async function runIngest(codexHome, state, opts = {}) {
|
|
|
222
243
|
if (!isCurrentRun()) return;
|
|
223
244
|
state.error = err.message;
|
|
224
245
|
state.phase = 'error';
|
|
246
|
+
failReplayCapture(state.replay_capture);
|
|
225
247
|
console.error('Ingest error:', err);
|
|
226
248
|
flushLive(state, 'ingest-error');
|
|
227
249
|
} finally {
|
|
@@ -253,9 +275,11 @@ export function restartIngest(codexHome, state, opts = {}) {
|
|
|
253
275
|
state.live_state = null;
|
|
254
276
|
state.live_seq = 0;
|
|
255
277
|
state.live_pending_patch = createEmptyLivePatch();
|
|
278
|
+
state.live_session_buffer = [];
|
|
256
279
|
state.live_progress_dirty = false;
|
|
257
280
|
state.live_last_emit_at = 0;
|
|
258
|
-
state.
|
|
281
|
+
state.live_last_overview_emit_at = 0;
|
|
282
|
+
resetReplayCapture(state.replay_capture);
|
|
259
283
|
|
|
260
284
|
return runIngest(codexHome, state, opts);
|
|
261
285
|
}
|
|
@@ -373,6 +397,49 @@ function buildUsageByDayMetrics(modelName, usageByDay) {
|
|
|
373
397
|
return entries;
|
|
374
398
|
}
|
|
375
399
|
|
|
400
|
+
function deriveLiveSortDay(session, toDayKey) {
|
|
401
|
+
if (session.has_usage_by_day && session.usage_by_day?.length) {
|
|
402
|
+
let best = session.usage_by_day[0];
|
|
403
|
+
for (const entry of session.usage_by_day) {
|
|
404
|
+
if ((entry.tokens || 0) > (best.tokens || 0)) {
|
|
405
|
+
best = entry;
|
|
406
|
+
continue;
|
|
407
|
+
}
|
|
408
|
+
if ((entry.tokens || 0) === (best.tokens || 0) && String(entry.day) > String(best.day)) {
|
|
409
|
+
best = entry;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
return best?.day || null;
|
|
413
|
+
}
|
|
414
|
+
if (session.started_at) {
|
|
415
|
+
return toDayKey(session.started_at * 1000);
|
|
416
|
+
}
|
|
417
|
+
return null;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
function compareLiveSessionOrder(a, b) {
|
|
421
|
+
const dayCompare = String(a.live_sort_day || '9999-12-31').localeCompare(String(b.live_sort_day || '9999-12-31'));
|
|
422
|
+
if (dayCompare !== 0) return dayCompare;
|
|
423
|
+
const liveTsCompare = (a.live_sort_ts || a.started_at || 0) - (b.live_sort_ts || b.started_at || 0);
|
|
424
|
+
if (liveTsCompare !== 0) return liveTsCompare;
|
|
425
|
+
return String(a.thread_id || '').localeCompare(String(b.thread_id || ''));
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
function bufferLiveSessionsForPresentation(state, sessions) {
|
|
429
|
+
if (!sessions.length) return [];
|
|
430
|
+
state.live_session_buffer.push(...sessions);
|
|
431
|
+
state.live_session_buffer.sort(compareLiveSessionOrder);
|
|
432
|
+
const flushCount = Math.max(0, state.live_session_buffer.length - LIVE_SESSION_REORDER_BUFFER);
|
|
433
|
+
if (flushCount <= 0) return [];
|
|
434
|
+
return state.live_session_buffer.splice(0, flushCount);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
function flushBufferedLiveSessions(state) {
|
|
438
|
+
if (!state.live_session_buffer.length) return [];
|
|
439
|
+
state.live_session_buffer.sort(compareLiveSessionOrder);
|
|
440
|
+
return state.live_session_buffer.splice(0, state.live_session_buffer.length);
|
|
441
|
+
}
|
|
442
|
+
|
|
376
443
|
function queueLiveProgress(state) {
|
|
377
444
|
state.live_progress_dirty = true;
|
|
378
445
|
ensureLivePump(state);
|
|
@@ -385,13 +452,14 @@ function queueLivePatch(state, patch) {
|
|
|
385
452
|
}
|
|
386
453
|
|
|
387
454
|
function ensureLivePump(state) {
|
|
388
|
-
if (state.live_pump_timer
|
|
455
|
+
if (state.live_pump_timer) return;
|
|
456
|
+
if (!state.live_subscribers.size && !state.replay_capture.active) return;
|
|
389
457
|
state.live_pump_timer = setInterval(() => flushLive(state), LIVE_FRAME_INTERVAL_MS);
|
|
390
458
|
}
|
|
391
459
|
|
|
392
460
|
function stopLivePumpIfIdle(state) {
|
|
393
461
|
if (!state.live_pump_timer) return;
|
|
394
|
-
if (state.live_subscribers.size) return;
|
|
462
|
+
if (state.live_subscribers.size || state.replay_capture.active) return;
|
|
395
463
|
clearInterval(state.live_pump_timer);
|
|
396
464
|
state.live_pump_timer = null;
|
|
397
465
|
finalizeWithoutSubscribers(state);
|
|
@@ -399,6 +467,7 @@ function stopLivePumpIfIdle(state) {
|
|
|
399
467
|
|
|
400
468
|
function finalizeWithoutSubscribers(state) {
|
|
401
469
|
if (state.live_subscribers.size) return;
|
|
470
|
+
if (state.replay_capture.active) return;
|
|
402
471
|
if (!state.presentation_complete_pending) return;
|
|
403
472
|
state.phase = 'complete';
|
|
404
473
|
state.percent = 1;
|
|
@@ -409,7 +478,7 @@ function finalizeWithoutSubscribers(state) {
|
|
|
409
478
|
}
|
|
410
479
|
|
|
411
480
|
function flushLive(state, forcedEvent = null) {
|
|
412
|
-
if (!state.live_subscribers.size) {
|
|
481
|
+
if (!state.live_subscribers.size && !state.replay_capture.active) {
|
|
413
482
|
state.live_pending_patch = createEmptyLivePatch();
|
|
414
483
|
state.live_progress_dirty = false;
|
|
415
484
|
stopLivePumpIfIdle(state);
|
|
@@ -449,12 +518,16 @@ function flushLive(state, forcedEvent = null) {
|
|
|
449
518
|
payload.data = buildLiveBootstrap(state.live_state);
|
|
450
519
|
}
|
|
451
520
|
|
|
452
|
-
|
|
521
|
+
recordReplayEvent(state.replay_capture, event, payload);
|
|
522
|
+
if (state.live_subscribers.size) {
|
|
523
|
+
broadcastLive(state, event, payload);
|
|
524
|
+
}
|
|
453
525
|
state.live_last_emit_at = Date.now();
|
|
454
526
|
state.live_progress_dirty = shouldEmitComplete ? false : (event === 'progress' ? false : state.live_progress_dirty);
|
|
455
527
|
if (forcedEvent) {
|
|
456
528
|
state.live_pending_patch = createEmptyLivePatch();
|
|
457
529
|
}
|
|
530
|
+
stopLivePumpIfIdle(state);
|
|
458
531
|
}
|
|
459
532
|
|
|
460
533
|
function broadcastLive(state, event, payload) {
|
|
@@ -496,41 +569,48 @@ function takeFlushablePatch(state) {
|
|
|
496
569
|
const now = Date.now();
|
|
497
570
|
const sent = createEmptyLivePatch();
|
|
498
571
|
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
state.live_last_surface_emit_at.overview = now;
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
const rankingsDirty =
|
|
572
|
+
const overviewDirty =
|
|
573
|
+
state.live_pending_patch.overview.size > 0 ||
|
|
505
574
|
state.live_pending_patch.repos.total.size > 0 || state.live_pending_patch.repos.d7.size > 0 || state.live_pending_patch.repos.d30.size > 0 ||
|
|
506
575
|
state.live_pending_patch.models.total.size > 0 || state.live_pending_patch.models.d7.size > 0 || state.live_pending_patch.models.d30.size > 0 ||
|
|
507
|
-
state.live_pending_patch.families.total.size > 0 || state.live_pending_patch.families.d7.size > 0 || state.live_pending_patch.families.d30.size > 0
|
|
576
|
+
state.live_pending_patch.families.total.size > 0 || state.live_pending_patch.families.d7.size > 0 || state.live_pending_patch.families.d30.size > 0 ||
|
|
577
|
+
state.live_pending_patch.daily.size > 0 || state.live_pending_patch.heatmap.size > 0;
|
|
508
578
|
|
|
509
|
-
if (
|
|
510
|
-
|
|
511
|
-
moveRangeSets(state.live_pending_patch.models, sent.models);
|
|
512
|
-
moveRangeSets(state.live_pending_patch.families, sent.families);
|
|
513
|
-
state.live_last_surface_emit_at.rankings = now;
|
|
579
|
+
if (!overviewDirty || !readyForOverview(state, now)) {
|
|
580
|
+
return sent;
|
|
514
581
|
}
|
|
515
582
|
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
583
|
+
moveSet(state.live_pending_patch.overview, sent.overview);
|
|
584
|
+
moveRangeSets(state.live_pending_patch.repos, sent.repos);
|
|
585
|
+
moveRangeSets(state.live_pending_patch.models, sent.models);
|
|
586
|
+
moveRangeSets(state.live_pending_patch.families, sent.families);
|
|
587
|
+
|
|
588
|
+
const nextDayKeys = takeNextChronologicalDayKeys(
|
|
589
|
+
state.live_pending_patch.daily,
|
|
590
|
+
state.live_pending_patch.heatmap,
|
|
591
|
+
LIVE_DAY_KEYS_PER_EMIT
|
|
592
|
+
);
|
|
593
|
+
moveSpecificKeys(state.live_pending_patch.daily, sent.daily, nextDayKeys);
|
|
594
|
+
moveSpecificKeys(state.live_pending_patch.heatmap, sent.heatmap, nextDayKeys);
|
|
595
|
+
state.live_last_overview_emit_at = now;
|
|
528
596
|
|
|
529
597
|
return sent;
|
|
530
598
|
}
|
|
531
599
|
|
|
532
|
-
function
|
|
533
|
-
return (now - state.
|
|
600
|
+
function readyForOverview(state, now) {
|
|
601
|
+
return (now - state.live_last_overview_emit_at) >= getOverviewCadenceMs(state);
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
function getOverviewCadenceMs(state) {
|
|
605
|
+
const tail = OVERVIEW_INGEST_ANIMATION.tail;
|
|
606
|
+
const tailStartPercent = Math.min(Math.max(tail?.startPercent ?? 0.95, 0), 0.999);
|
|
607
|
+
const tailHz = Number.isFinite(tail?.overviewHz) && tail.overviewHz > 0 ? tail.overviewHz : 5;
|
|
608
|
+
const tailCadenceMs = Math.round(1000 / tailHz);
|
|
609
|
+
|
|
610
|
+
if (tail?.enabled && (state.presentation_complete_pending || (state.percent || 0) >= tailStartPercent)) {
|
|
611
|
+
return tailCadenceMs;
|
|
612
|
+
}
|
|
613
|
+
return LIVE_OVERVIEW_CADENCE_MS;
|
|
534
614
|
}
|
|
535
615
|
|
|
536
616
|
function moveSet(from, to) {
|
|
@@ -602,6 +682,10 @@ export function detachLiveSubscriber(state, res) {
|
|
|
602
682
|
stopLivePumpIfIdle(state);
|
|
603
683
|
}
|
|
604
684
|
|
|
685
|
+
export function getLatestReplay(state) {
|
|
686
|
+
return getReplaySnapshot(state.replay_capture);
|
|
687
|
+
}
|
|
688
|
+
|
|
605
689
|
function broadcastBootstrap(state) {
|
|
606
690
|
if (!state.live_subscribers.size) return;
|
|
607
691
|
const payload = {
|
package/server/rollout-reader.js
CHANGED
|
@@ -15,6 +15,7 @@ export async function enrichFromRollout(rolloutPath, opts = {}) {
|
|
|
15
15
|
model_name: null,
|
|
16
16
|
reasoning_effort: null,
|
|
17
17
|
first_timestamp: null,
|
|
18
|
+
first_usage_timestamp: null,
|
|
18
19
|
last_timestamp: null,
|
|
19
20
|
active_seconds: null,
|
|
20
21
|
active_by_day: null,
|
|
@@ -97,6 +98,9 @@ export async function enrichFromRollout(rolloutPath, opts = {}) {
|
|
|
97
98
|
if (obj.timestamp && hasUsage(usageDelta)) {
|
|
98
99
|
const ts = new Date(obj.timestamp).getTime();
|
|
99
100
|
if (!isNaN(ts)) {
|
|
101
|
+
if (!result.first_usage_timestamp || ts < result.first_usage_timestamp) {
|
|
102
|
+
result.first_usage_timestamp = ts;
|
|
103
|
+
}
|
|
100
104
|
const dayKey = toDayKey(ts);
|
|
101
105
|
mergeUsageTotals(usageByDay, dayKey, usageDelta);
|
|
102
106
|
}
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
const AUTO = 'auto';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Overview ingest animation control panel.
|
|
5
|
+
*
|
|
6
|
+
* This is the single source of truth for:
|
|
7
|
+
* - Overview client-side presentation timing
|
|
8
|
+
* - Overview ECharts timing defaults
|
|
9
|
+
* - Overview server-side tail pacing
|
|
10
|
+
*
|
|
11
|
+
* `speed` scales the main timings:
|
|
12
|
+
* - `1` = baseline
|
|
13
|
+
* - `2` = 2x faster
|
|
14
|
+
* - `0.5` = 2x slower
|
|
15
|
+
*
|
|
16
|
+
* Per-chart overrides use:
|
|
17
|
+
* - `'auto'` to inherit the scaled main value
|
|
18
|
+
* - a number to force an explicit value in ms
|
|
19
|
+
*/
|
|
20
|
+
export const OVERVIEW_INGEST_ANIMATION = {
|
|
21
|
+
speed: 1,
|
|
22
|
+
main: {
|
|
23
|
+
presentationDurationMs: 190,
|
|
24
|
+
chartAppearDurationMs: 0,
|
|
25
|
+
chartUpdateDurationMs: 200,
|
|
26
|
+
easing: 'linear',
|
|
27
|
+
easingUpdate: 'linear',
|
|
28
|
+
},
|
|
29
|
+
tail: {
|
|
30
|
+
// Master switch for the end-of-ingest slowdown behavior.
|
|
31
|
+
enabled: true,
|
|
32
|
+
// Progress threshold where the tail mode starts, expressed from 0..1.
|
|
33
|
+
startPercent: 0.85,
|
|
34
|
+
// Fixed presentation duration in ms during the tail; use AUTO to derive it from main.durationScale.
|
|
35
|
+
durationMs: AUTO,
|
|
36
|
+
// Multiplier applied to the normal presentation duration when durationMs is AUTO.
|
|
37
|
+
durationScale: 100,
|
|
38
|
+
// Shared easing applied by the Overview presentation animator during the tail.
|
|
39
|
+
easing: 'cubicOut',
|
|
40
|
+
// Backend Overview live-update cadence during the tail, in patches per second.
|
|
41
|
+
overviewHz: 5,
|
|
42
|
+
},
|
|
43
|
+
daily: {
|
|
44
|
+
chartAppearDurationMs: AUTO,
|
|
45
|
+
chartUpdateDurationMs: 30,
|
|
46
|
+
easing: AUTO,
|
|
47
|
+
easingUpdate: AUTO,
|
|
48
|
+
},
|
|
49
|
+
bars: {
|
|
50
|
+
chartAppearDurationMs: AUTO,
|
|
51
|
+
chartUpdateDurationMs: 20,
|
|
52
|
+
easing: AUTO,
|
|
53
|
+
easingUpdate: AUTO,
|
|
54
|
+
},
|
|
55
|
+
donuts: {
|
|
56
|
+
chartAppearDurationMs: AUTO,
|
|
57
|
+
chartUpdateDurationMs: AUTO,
|
|
58
|
+
easing: AUTO,
|
|
59
|
+
easingUpdate: 'linear',
|
|
60
|
+
seriesAnimation: false,
|
|
61
|
+
},
|
|
62
|
+
heatmap: {
|
|
63
|
+
/** Duration (ms) of the pop animation when a cell gets new data during ingest */
|
|
64
|
+
popDurationMs: 380,
|
|
65
|
+
/** Don't trigger pop when ingest progress is above this (avoids burst at settle) */
|
|
66
|
+
settleThreshold: 0.998,
|
|
67
|
+
/** Min relative intensity rise (0–1) to trigger pop – e.g. 0.08 = cell turning white */
|
|
68
|
+
intensityRiseThreshold: 0.08,
|
|
69
|
+
/** Relative intensity (0–1) that counts as "white" – crossing this triggers pop */
|
|
70
|
+
whiteThreshold: 0.88,
|
|
71
|
+
/** Cell is "new winner" if val >= maxVal * this when max increased */
|
|
72
|
+
nearMaxRatio: 0.985,
|
|
73
|
+
},
|
|
74
|
+
videoExport: {
|
|
75
|
+
width: 1080,
|
|
76
|
+
height: 864,
|
|
77
|
+
fps: 60,
|
|
78
|
+
introDurationMs: 900,
|
|
79
|
+
replayDurationMs: 8000,
|
|
80
|
+
tailDurationMs: 5000,
|
|
81
|
+
tailReplayFraction: 0.72,
|
|
82
|
+
captureFormat: 'png',
|
|
83
|
+
jpegQuality: 92,
|
|
84
|
+
crf: 10,
|
|
85
|
+
encoderPreset: 'veryfast',
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
/** Shared ECharts animation defaults for non-Overview charts */
|
|
90
|
+
export const ECHARTS_ANIMATION = {
|
|
91
|
+
animationDuration: 750,
|
|
92
|
+
animationDurationUpdate: 220,
|
|
93
|
+
animationEasing: 'cubicOut',
|
|
94
|
+
animationEasingUpdate: 'cubicOut',
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
/** Label animation - fade in after chart finishes */
|
|
98
|
+
export const ECHARTS_LABEL_ANIMATION = {
|
|
99
|
+
show: true,
|
|
100
|
+
animationDuration: 250,
|
|
101
|
+
animationDurationUpdate: 220,
|
|
102
|
+
animationDelay: 250,
|
|
103
|
+
animationDelayUpdate: 180,
|
|
104
|
+
animationEasing: 'cubicOut',
|
|
105
|
+
animationEasingUpdate: 'cubicOut',
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
/** Detail donut charts (Repos/Models/Daily) */
|
|
109
|
+
export const ECHARTS_DONUT_ANIMATION = {
|
|
110
|
+
...ECHARTS_ANIMATION,
|
|
111
|
+
animationDurationUpdate: 200,
|
|
112
|
+
animationDelayUpdate: 0,
|
|
113
|
+
animationEasingUpdate: 'cubicOut',
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
/** Detail bar charts (Repos/Models/Daily) */
|
|
117
|
+
export const ECHARTS_DETAIL_BAR_ANIMATION = {
|
|
118
|
+
animationDuration: 700,
|
|
119
|
+
animationDurationUpdate: 220,
|
|
120
|
+
animationDelay: 250,
|
|
121
|
+
animationDelayUpdate: 140,
|
|
122
|
+
animationEasing: 'cubicOut',
|
|
123
|
+
animationEasingUpdate: 'cubicOut',
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
/** Detail bar labels */
|
|
127
|
+
export const ECHARTS_DETAIL_BAR_LABEL_ANIMATION = {
|
|
128
|
+
...ECHARTS_LABEL_ANIMATION,
|
|
129
|
+
animationDelay: 950,
|
|
130
|
+
animationDelayUpdate: 360,
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
function safeSpeed(value) {
|
|
134
|
+
return Number.isFinite(value) && value > 0 ? value : 1;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function scaledMainDuration(ms) {
|
|
138
|
+
return Math.max(0, Math.round(ms / safeSpeed(OVERVIEW_INGEST_ANIMATION.speed)));
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function resolveOverviewValue(overrideValue, mainValue) {
|
|
142
|
+
return overrideValue === AUTO ? scaledMainDuration(mainValue) : overrideValue;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function resolveOverviewString(overrideValue, mainValue) {
|
|
146
|
+
return overrideValue === AUTO ? mainValue : overrideValue;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function buildOverviewChartAnimation(overrides) {
|
|
150
|
+
return {
|
|
151
|
+
animation: true,
|
|
152
|
+
animationDuration: resolveOverviewValue(
|
|
153
|
+
overrides.chartAppearDurationMs,
|
|
154
|
+
OVERVIEW_INGEST_ANIMATION.main.chartAppearDurationMs
|
|
155
|
+
),
|
|
156
|
+
animationDurationUpdate: resolveOverviewValue(
|
|
157
|
+
overrides.chartUpdateDurationMs,
|
|
158
|
+
OVERVIEW_INGEST_ANIMATION.main.chartUpdateDurationMs
|
|
159
|
+
),
|
|
160
|
+
animationEasing: resolveOverviewString(
|
|
161
|
+
overrides.easing,
|
|
162
|
+
OVERVIEW_INGEST_ANIMATION.main.easing
|
|
163
|
+
),
|
|
164
|
+
animationEasingUpdate: resolveOverviewString(
|
|
165
|
+
overrides.easingUpdate,
|
|
166
|
+
OVERVIEW_INGEST_ANIMATION.main.easingUpdate
|
|
167
|
+
),
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export const OVERVIEW_PRESENTATION_DURATION_MS = scaledMainDuration(
|
|
172
|
+
OVERVIEW_INGEST_ANIMATION.main.presentationDurationMs
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
export function resolveOverviewPresentationDuration(progress = 0, isIngestActive = false) {
|
|
176
|
+
const base = OVERVIEW_PRESENTATION_DURATION_MS;
|
|
177
|
+
const tail = OVERVIEW_INGEST_ANIMATION.tail;
|
|
178
|
+
|
|
179
|
+
if (!isIngestActive || !tail?.enabled) return base;
|
|
180
|
+
|
|
181
|
+
const start = Math.min(Math.max(tail.startPercent ?? 0.9, 0), 0.999);
|
|
182
|
+
const clampedProgress = Math.min(Math.max(progress || 0, 0), 1);
|
|
183
|
+
if (clampedProgress <= start) return base;
|
|
184
|
+
|
|
185
|
+
const normalized = (clampedProgress - start) / Math.max(1 - start, 0.001);
|
|
186
|
+
const durationTarget = tail.durationMs === AUTO
|
|
187
|
+
? Math.round(base * Math.max(tail.durationScale || 1, 1))
|
|
188
|
+
: tail.durationMs;
|
|
189
|
+
|
|
190
|
+
return Math.round(base + (durationTarget - base) * cubicOut(normalized));
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export function resolveOverviewPresentationEasing(progress = 0, isIngestActive = false) {
|
|
194
|
+
const tail = OVERVIEW_INGEST_ANIMATION.tail;
|
|
195
|
+
if (!isIngestActive || !tail?.enabled) return 'linear';
|
|
196
|
+
|
|
197
|
+
const start = Math.min(Math.max(tail.startPercent ?? 0.9, 0), 0.999);
|
|
198
|
+
const clampedProgress = Math.min(Math.max(progress || 0, 0), 1);
|
|
199
|
+
if (clampedProgress <= start) return 'linear';
|
|
200
|
+
|
|
201
|
+
return tail.easing || 'cubicOut';
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export function isOverviewTailActive(progress = 0, isIngestActive = false) {
|
|
205
|
+
const tail = OVERVIEW_INGEST_ANIMATION.tail;
|
|
206
|
+
if (!isIngestActive || !tail?.enabled) return false;
|
|
207
|
+
|
|
208
|
+
const start = Math.min(Math.max(tail.startPercent ?? 0.9, 0), 0.999);
|
|
209
|
+
const clampedProgress = Math.min(Math.max(progress || 0, 0), 1);
|
|
210
|
+
return clampedProgress > start;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/** Overview page - resolved main animation config */
|
|
214
|
+
export const ECHARTS_OVERVIEW_ANIMATION = buildOverviewChartAnimation({
|
|
215
|
+
chartAppearDurationMs: AUTO,
|
|
216
|
+
chartUpdateDurationMs: AUTO,
|
|
217
|
+
easing: AUTO,
|
|
218
|
+
easingUpdate: AUTO,
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
/** Overview DailySpark - compact stacked bar */
|
|
222
|
+
export const ECHARTS_OVERVIEW_DAILY = buildOverviewChartAnimation(
|
|
223
|
+
OVERVIEW_INGEST_ANIMATION.daily
|
|
224
|
+
);
|
|
225
|
+
|
|
226
|
+
/** Overview Top Repos - horizontal bar chart */
|
|
227
|
+
export const ECHARTS_OVERVIEW_BARS = buildOverviewChartAnimation(
|
|
228
|
+
OVERVIEW_INGEST_ANIMATION.bars
|
|
229
|
+
);
|
|
230
|
+
|
|
231
|
+
/** Overview Work Type & Models - donut charts */
|
|
232
|
+
export const ECHARTS_OVERVIEW_DONUTS = buildOverviewChartAnimation(
|
|
233
|
+
OVERVIEW_INGEST_ANIMATION.donuts
|
|
234
|
+
);
|
|
235
|
+
|
|
236
|
+
export const ECHARTS_OVERVIEW_DONUT_SERIES_ANIMATION =
|
|
237
|
+
OVERVIEW_INGEST_ANIMATION.donuts.seriesAnimation;
|
|
238
|
+
|
|
239
|
+
export { AUTO as OVERVIEW_ANIMATION_AUTO };
|
|
240
|
+
|
|
241
|
+
function cubicOut(t) {
|
|
242
|
+
const x = Math.min(Math.max(t, 0), 1);
|
|
243
|
+
return 1 - Math.pow(1 - x, 3);
|
|
244
|
+
}
|
|
@@ -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: rgba(99, 102, 241, .25);--text-primary: #e6edf3;--text-secondary: #8b949e;--text-muted: #484f58;--accent: #6366f1;--accent-dim: rgba(99, 102, 241, .12);--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);line-height:1.6;-webkit-font-smoothing:antialiased}.app{min-height:100vh;display:flex;flex-direction:column}.app-content{opacity:0;display:flex;flex-direction:column;min-height:100vh}.app-content-revealed{opacity:1;animation:contentReveal .5s ease forwards}@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);position:sticky;top:0;z-index:100;-webkit-backdrop-filter:blur(12px);backdrop-filter:blur(12px)}.navbar-inner{display:flex;align-items:center;gap:1.5rem;padding:0 2rem;height:48px;max-width:1400px;margin:0 auto;width:100%;box-sizing:border-box}.navbar-brand{font-weight:700;font-size:.95rem;letter-spacing:-.03em;background:linear-gradient(135deg,#818cf8,#6366f1);-webkit-background-clip:text;-webkit-text-fill-color:transparent}.navbar-tabs{display:flex;gap:2px}.navbar-tab{padding:.35rem .85rem;border-radius:6px;cursor:pointer;font-size:.8rem;font-weight:500;color:var(--text-secondary);background:none;border:none;transition:all .15s}.navbar-tab:hover{color:var(--text-primary);background:var(--bg-card)}.navbar-tab.active{color:#fff;background:var(--accent)}.navbar-meta{margin-left:auto;display:flex;align-items:center;gap:.75rem;flex:1;min-width:0}.navbar-ingest-wrap{display:flex;align-items:center;gap:.75rem;flex:1;min-width:0;transition:opacity .3s ease}.navbar-ingest-wrap.navbar-ingest-fade-out{opacity:0;pointer-events:none}.navbar-date-wrap{display:flex;align-items:center;gap:.75rem;margin-left:auto;flex-shrink:0;transition:opacity .3s ease}.navbar-date-wrap-dimmed{opacity:.5;pointer-events:none}.navbar-date{display:inline-block;width:22ch;min-width:22ch;font-variant-numeric:tabular-nums;text-align:right;overflow:hidden;text-overflow:ellipsis}.navbar-progress-wrap{flex:1;height:4px;background:var(--bg-elevated);border-radius:2px;overflow:hidden}.navbar-progress-bar{height:100%;background:linear-gradient(90deg,var(--accent),#818cf8);border-radius:2px;transition:width .4s ease}.navbar-status{font-size:.7rem;color:var(--text-muted);font-family:var(--font-mono)}.main-content{flex:1;padding:1.5rem 2rem 3rem;max-width:1400px;margin:0 auto;width:100%;min-width:0;container-type:inline-size;container-name:main}.app-footer{padding:.75rem 2rem 1rem;font-size:.7rem;color:var(--text-muted);text-align:center;max-width:1400px;margin:auto auto 0;width:100%;box-sizing:border-box}.section-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:1.25rem}.section-title{font-size:1rem;font-weight:600;letter-spacing:-.01em}.stat-row{display:flex;flex-wrap:wrap;gap:1rem;align-items:stretch;margin-bottom:1.5rem}.stat-cards{display:grid;grid-template-columns:repeat(4,minmax(160px,1fr));gap:.75rem;flex:0 0 auto;min-width:0}@media(max-width:920px){.stat-cards{flex:1 1 auto;grid-template-columns:repeat(2,minmax(160px,1fr))}}@media(max-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{font-size:.65rem;font-weight:600;text-transform:uppercase;letter-spacing:.06em;color:var(--text-muted);margin-bottom:.35rem}.stat-value{font-size:1.6rem;font-weight:700;font-family:var(--font-mono);letter-spacing:-.03em;line-height:1.1}.stat-sub{font-size:.72rem;color:var(--text-muted);margin-top:.2rem}.stat-per-day{font-size:.72rem;color:var(--text-muted);margin-top:.25rem;font-family:var(--font-mono)}.stat-per-day .stat-per-day-value{font-weight:600;color:var(--text-secondary)}.range-toggle{display:inline-flex;background:var(--bg-card);border:1px solid var(--border);border-radius:6px;overflow:hidden}.range-btn{padding:.3rem .7rem;font-size:.72rem;font-weight:600;color:var(--text-muted);background:none;border:none;cursor:pointer;transition:all .15s}.range-btn:hover{color:var(--text-secondary)}.range-btn.active{color:#fff;background:var(--accent)}.heatmap-wrap{background:var(--bg-card);border:1px solid var(--border);border-radius:var(--radius-lg);padding:1.25rem;margin-bottom:1.5rem;overflow-x:auto}.heatmap-grid{display:grid;grid-auto-flow:column;grid-template-rows:repeat(7,1fr);gap:3px}.heatmap-cell{width:13px;height:13px;border-radius:2px;background:var(--bg-elevated);opacity:.4;transform:scale(.82);transition:background .18s ease,opacity .22s ease,transform .22s ease}.heatmap-cell.active{opacity:1;transform:scale(1)}.heatmap-cell:hover{outline:1px solid var(--text-muted)}.heatmap-labels{display:flex;justify-content:space-between;margin-top:.5rem;font-size:.65rem;color:var(--text-muted)}.chart-card{background:var(--bg-card);border:1px solid var(--border);border-radius:var(--radius-lg);padding:1.25rem;margin-bottom:1.5rem;position:relative}.chart-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:.75rem}.chart-title{font-size:.85rem;font-weight:600}.chart-badge{font-size:.62rem;color:var(--yellow);background:#d299221a;padding:.15rem .5rem;border-radius:4px}.export-btn{position:absolute;top:.75rem;right:.75rem;padding:.25rem .5rem;border-radius:4px;border:1px solid var(--border);background:var(--bg-surface);color:var(--text-muted);font-size:.65rem;cursor:pointer;opacity:0;transition:all .15s;z-index:2}.chart-card:hover .export-btn{opacity:.7}.export-btn:hover{opacity:1!important;color:var(--text-primary);border-color:var(--accent)}.grid-2{display:grid;grid-template-columns:1fr 1fr;gap:1.25rem;margin-bottom:1.5rem;min-width:0}.grid-3{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:1.25rem;margin-bottom:1.5rem;min-width:0}.grid-3>.chart-card{min-width:0;overflow:hidden}@media(max-width:900px){.grid-2,.grid-3{grid-template-columns:1fr}}.btn-group{display:flex;gap:2px}.btn{padding:.3rem .65rem;border-radius:5px;border:1px solid var(--border);background:var(--bg-surface);color:var(--text-muted);font-size:.72rem;font-weight:500;cursor:pointer;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{width:28px;height:28px;display:inline-flex;align-items:center;justify-content:center;border-radius:6px;border:1px solid var(--border);background:var(--bg-card);color:var(--text-muted);font-size:.9rem;line-height:1;cursor:pointer;transition:color .12s,border-color .12s,background .12s,transform .12s}.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{flex:1 1 140px;min-width:140px;min-height:0;position:relative;background:var(--bg-card);border:1px solid var(--border);border-radius:var(--radius-lg);overflow:visible}.overview-daily-spark-title{position:absolute;top:1rem;left:1.1rem;font-size:.65rem;font-weight:600;text-transform:uppercase;letter-spacing:.06em;color:var(--text-muted);z-index:1;pointer-events:none}.overview-daily-spark-chart{position:absolute;top:0;right:0;bottom:0;left:0;overflow:hidden}.overview-daily-spark-empty{display:flex;align-items:center;justify-content:center}.overview-daily-spark-empty-text{font-size:.7rem;color:var(--text-muted)}@media(max-width:920px){.overview-daily-spark{flex:1 1 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{display:flex;flex-direction:column;gap:.85rem}.model-detail-note{color:var(--text-muted);font-size:.74rem;line-height:1.4}.model-detail-footer{display:flex;justify-content:flex-start;margin-top:.25rem}.model-detail-toggle{display:inline-flex;align-items:center;gap:.35rem;width:fit-content;padding:.35rem .6rem;font-size:.72rem;font-weight:500;color:var(--text-muted);background:#ffffff0a;border:1px solid var(--border);border-radius:6px;cursor:pointer;transition:color .2s,background .2s,border-color .2s}.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{font-size:.6rem;opacity:.8;transition:transform .25s ease}.model-detail-toggle-arrow.expanded{transform:rotate(180deg)}.model-detail-charts{display:flex;gap:.25rem;flex-wrap:wrap;align-items:flex-start;width:100%;overflow:visible;position:relative;z-index:10}.model-detail-donut{flex:0 0 200px;width:200px;min-width:200px;height:200px;overflow:visible;z-index:10;display:flex;flex-direction:column}.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:1 1 300px;min-width:300px;height:200px;overflow:visible;display:flex;flex-direction:column}.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;overflow:hidden;transition:max-height .2s ease-out}.model-detail-summary-wrap.expanded{max-height:400px}.model-detail-summary{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:.6rem;padding-top:.5rem}.model-detail-summary-card{background:#ffffff05;border:1px solid var(--border);border-radius:10px;padding:.65rem .75rem}.model-detail-summary-head{display:flex;align-items:center;gap:.45rem;margin-bottom:.45rem;font-size:.76rem;font-weight:600;color:var(--text-primary);text-transform:capitalize;min-width:0}.model-detail-summary-head .model-detail-summary-name{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.model-detail-swatch{width:9px;height:9px;border-radius:999px;flex:0 0 auto}.model-detail-summary-line{font-size:.72rem;color:var(--text-muted);line-height:1.45}.detail-summary-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:.75rem;margin-bottom:1rem}.detail-summary-stat{background:#ffffff08;border:1px solid var(--border);border-radius:10px;padding:.8rem .9rem}.detail-summary-label{font-size:.68rem;text-transform:uppercase;letter-spacing:.05em;color:var(--text-muted);margin-bottom:.25rem}.detail-summary-value{font-size:1.1rem;font-family:var(--font-mono);color:var(--text-primary)}.table-search{padding:.75rem 1rem;border-bottom:1px solid var(--border)}.table-search input{width:100%;padding:.5rem .85rem;border-radius:6px;border:1px solid var(--border);background:var(--bg-surface);color:var(--text-primary);font-size:.82rem;font-family:var(--font-sans);outline:none}.table-search input:focus{border-color:var(--accent)}.table-search input::placeholder{color:var(--text-muted)}table{width:100%;border-collapse:collapse;font-size:.78rem}th{text-align:left;padding:.55rem .85rem;font-weight:600;color:var(--text-muted);border-bottom:1px solid var(--border);font-size:.68rem;text-transform:uppercase;letter-spacing:.05em;cursor:pointer;-webkit-user-select:none;user-select:none;white-space:nowrap}td{padding:.5rem .85rem;border-bottom:1px solid rgba(48,54,61,.5);color:var(--text-secondary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:240px}tr:hover td{color:var(--text-primary);background:#6366f108}.tag{display:inline-block;padding:.1rem .4rem;border-radius:4px;font-size:.66rem;font-family:var(--font-mono);font-weight:500}.tag-review{background:#06b6d41f;color:var(--cyan)}.tag-exploration{background:#f973161f;color:var(--orange)}.tag-planning{background:#eab3081f;color:var(--yellow)}.tag-memory{background:#ec48991f;color:var(--pink)}.tag-generic{background:#64748b1f;color:var(--text-secondary)}.loading-overlay{position:fixed;top:0;right:0;bottom:0;left:0;background:#06080feb;-webkit-backdrop-filter:blur(20px);backdrop-filter:blur(20px);display:flex;flex-direction:column;align-items:center;justify-content:center;z-index:999;transition:opacity .6s ease}.loading-overlay.fading{opacity:0;pointer-events:none}.loading-logo{font-size:1.5rem;font-weight:700;background:linear-gradient(135deg,#818cf8,#6366f1);-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent;margin-bottom:1.5rem}.loading-bar-wrap{width:260px;height:4px;background:var(--bg-elevated);border-radius:2px;overflow:hidden;margin-bottom:1rem}.loading-bar{height:100%;background:linear-gradient(90deg,var(--accent),#818cf8);border-radius:2px;transition:width .4s ease}.loading-phase{font-size:.82rem;font-weight:500;color:var(--text-secondary);margin-bottom:.2rem}.loading-detail{font-family:var(--font-mono);font-size:.72rem;color:var(--text-muted)}.loading-footer{position:absolute;bottom:1.5rem;font-size:.7rem;color:var(--text-muted)}.loading-heart{color:#f43f5e}.coverage-subtle{display:flex;align-items:center;gap:1.5rem;font-size:.68rem;color:var(--text-muted);padding:.5rem 0;margin-bottom:1rem}.coverage-item{display:flex;align-items:center;gap:.35rem;font-variant-numeric:tabular-nums}.coverage-nums{display:inline-block;min-width:9ch;text-align:right}.coverage-nums-single{min-width:4ch}.coverage-dot{width:6px;height:6px;border-radius:50%}html{scrollbar-width:none}::-webkit-scrollbar{display:none;width:0;height:0}@keyframes fadeUp{0%{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}.animate-in{animation:fadeUp .35s ease forwards}@keyframes pulse{0%,to{opacity:1}50%{opacity:.5}}.incomplete-badge{font-size:.65rem;color:var(--yellow);background:#d299221a;padding:.12rem .45rem;border-radius:3px;animation:pulse 2s infinite}.ingesting-badge .ingesting-pct{display:inline-block;min-width:4ch;text-align:right;font-variant-numeric:tabular-nums}.echarts-tooltip,[class*=ec-component-tooltip]{max-width:90vw!important;max-height:80vh!important;overflow:auto!important}
|