opencastle 0.3.1 → 0.4.0
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/cli.mjs +0 -0
- package/dist/cli/dashboard.d.ts.map +1 -1
- package/dist/cli/dashboard.js +2 -1
- package/dist/cli/dashboard.js.map +1 -1
- package/dist/cli/init.d.ts.map +1 -1
- package/dist/cli/init.js +6 -2
- package/dist/cli/init.js.map +1 -1
- package/dist/cli/mcp.d.ts.map +1 -1
- package/dist/cli/mcp.js +21 -0
- package/dist/cli/mcp.js.map +1 -1
- package/dist/cli/stack-config.js +4 -4
- package/dist/cli/stack-config.js.map +1 -1
- package/package.json +2 -2
- package/src/cli/dashboard.ts +2 -1
- package/src/cli/init.ts +7 -2
- package/src/cli/mcp.ts +30 -1
- package/src/cli/stack-config.ts +4 -4
- package/src/dashboard/package.json +1 -0
- package/src/dashboard/src/pages/index.astro +163 -59
- package/src/dashboard/src/styles/dashboard.css +261 -0
- package/src/dashboard/tsconfig.json +1 -1
- package/src/orchestrator/agents/release-manager.agent.md +1 -1
- package/src/orchestrator/agents/team-lead.agent.md +13 -1
- package/src/orchestrator/customizations/stack/notifications-config.md +57 -0
- package/src/orchestrator/instructions/general.instructions.md +31 -2
- package/src/orchestrator/mcp.json +12 -6
- package/src/orchestrator/skills/agent-hooks/SKILL.md +13 -7
- package/src/orchestrator/skills/session-checkpoints/SKILL.md +12 -0
- package/src/orchestrator/skills/slack-notifications/SKILL.md +139 -39
- /package/src/dashboard/{public/data → seed-data}/delegations.ndjson +0 -0
- /package/src/dashboard/{public/data → seed-data}/panels.ndjson +0 -0
- /package/src/dashboard/{public/data → seed-data}/sessions.ndjson +0 -0
|
@@ -246,10 +246,84 @@ const base = import.meta.env.BASE_URL;
|
|
|
246
246
|
return div.innerHTML;
|
|
247
247
|
}
|
|
248
248
|
|
|
249
|
+
// ── SVG Icon Library (empty states) ────────────────────
|
|
250
|
+
|
|
251
|
+
const EMPTY_ICONS = {
|
|
252
|
+
welcome: '<svg width="48" height="48" viewBox="0 0 48 48" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M24 4L6 14v20l18 10 18-10V14L24 4z" opacity="0.3"/><path d="M24 4L6 14v20l18 10 18-10V14L24 4z"/><path d="M24 24v20"/><path d="M6 14l18 10 18-10"/><rect x="18" y="18" width="12" height="14" rx="1" opacity="0.5"/><path d="M21 32v-6h6v6"/></svg>',
|
|
253
|
+
agents: '<svg width="40" height="40" viewBox="0 0 40 40" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="20" cy="14" r="6"/><path d="M8 34c0-6.627 5.373-12 12-12s12 5.373 12 12"/><circle cx="32" cy="12" r="4" opacity="0.4"/><path d="M36 26c0-3.5-2-6-4-7" opacity="0.4"/></svg>',
|
|
254
|
+
tiers: '<svg width="40" height="40" viewBox="0 0 40 40" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><ellipse cx="20" cy="10" rx="14" ry="5"/><path d="M6 10v8c0 2.761 6.268 5 14 5s14-2.239 14-5v-8"/><path d="M6 18v8c0 2.761 6.268 5 14 5s14-2.239 14-5v-8" opacity="0.5"/></svg>',
|
|
255
|
+
mechanism: '<svg width="40" height="40" viewBox="0 0 40 40" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="20" cy="20" r="6"/><path d="M20 6v6M20 28v6M6 20h6M28 20h6"/><path d="M10.1 10.1l4.2 4.2M25.7 25.7l4.2 4.2M10.1 29.9l4.2-4.2M25.7 14.3l4.2-4.2" opacity="0.5"/></svg>',
|
|
256
|
+
outcomes: '<svg width="40" height="40" viewBox="0 0 40 40" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="6" y="6" width="28" height="28" rx="4"/><polyline points="12 22 18 28 28 14" opacity="0.5"/><line x1="12" y1="16" x2="18" y2="16" opacity="0.3"/><line x1="12" y1="12" x2="24" y2="12" opacity="0.3"/></svg>',
|
|
257
|
+
timeline: '<svg width="40" height="40" viewBox="0 0 40 40" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="6" y="8" width="28" height="26" rx="3"/><line x1="6" y1="16" x2="34" y2="16"/><line x1="14" y1="8" x2="14" y2="12"/><line x1="26" y1="8" x2="26" y2="12"/><circle cx="14" cy="24" r="2" opacity="0.4"/><circle cx="20" cy="28" r="2" opacity="0.4"/><circle cx="26" cy="22" r="2" opacity="0.4"/></svg>',
|
|
258
|
+
models: '<svg width="40" height="40" viewBox="0 0 40 40" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="8" y="8" width="24" height="24" rx="4"/><circle cx="20" cy="20" r="4"/><path d="M20 12v4M20 24v4M12 20h4M24 20h4" opacity="0.5"/><circle cx="14" cy="14" r="1.5" opacity="0.3"/><circle cx="26" cy="14" r="1.5" opacity="0.3"/><circle cx="14" cy="26" r="1.5" opacity="0.3"/><circle cx="26" cy="26" r="1.5" opacity="0.3"/></svg>',
|
|
259
|
+
execLog: '<svg width="40" height="40" viewBox="0 0 40 40" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M14 8h18a2 2 0 0 1 2 2v20a2 2 0 0 1-2 2H14"/><circle cx="10" cy="14" r="3"/><circle cx="10" cy="24" r="3" opacity="0.4"/><line x1="10" y1="17" x2="10" y2="21" opacity="0.3"/><line x1="18" y1="14" x2="28" y2="14" opacity="0.5"/><line x1="18" y1="24" x2="26" y2="24" opacity="0.3"/><line x1="18" y1="19" x2="24" y2="19" opacity="0.2"/></svg>',
|
|
260
|
+
panels: '<svg width="40" height="40" viewBox="0 0 40 40" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6l2 4h4l-3 3 1 5-4-2.5L16 18l1-5-3-3h4l2-4z"/><rect x="8" y="22" width="24" height="12" rx="3" opacity="0.4"/><line x1="14" y1="28" x2="26" y2="28" opacity="0.3"/><line x1="14" y1="31" x2="22" y2="31" opacity="0.2"/></svg>',
|
|
261
|
+
sessions: '<svg width="40" height="40" viewBox="0 0 40 40" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="6" y="6" width="28" height="28" rx="3"/><line x1="6" y1="14" x2="34" y2="14"/><line x1="14" y1="6" x2="14" y2="34" opacity="0.3"/><line x1="6" y1="22" x2="34" y2="22" opacity="0.2"/><line x1="6" y1="30" x2="34" y2="30" opacity="0.2"/></svg>',
|
|
262
|
+
pipeline: '<svg width="40" height="40" viewBox="0 0 40 40" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="4" y="14" width="8" height="12" rx="2" opacity="0.3"/><rect x="16" y="14" width="8" height="12" rx="2" opacity="0.3"/><rect x="28" y="14" width="8" height="12" rx="2" opacity="0.3"/><path d="M12 20h4M24 20h4" opacity="0.4"/></svg>',
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
function emptyStateHtml(iconKey, title, description) {
|
|
266
|
+
return '<div class="empty-state empty-state--enhanced">' +
|
|
267
|
+
'<div class="empty-state__icon-wrap">' + EMPTY_ICONS[iconKey] + '</div>' +
|
|
268
|
+
'<p class="empty-state__title">' + title + '</p>' +
|
|
269
|
+
'<p class="empty-state__desc">' + description + '</p>' +
|
|
270
|
+
'</div>';
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// ── Welcome Banner (all data empty) ────────────────────
|
|
274
|
+
|
|
275
|
+
function renderWelcomeBanner() {
|
|
276
|
+
const existing = document.getElementById('welcome-banner');
|
|
277
|
+
if (existing) existing.remove();
|
|
278
|
+
|
|
279
|
+
const banner = document.createElement('section');
|
|
280
|
+
banner.id = 'welcome-banner';
|
|
281
|
+
banner.className = 'welcome-banner';
|
|
282
|
+
banner.innerHTML =
|
|
283
|
+
'<div class="welcome-banner__glow"></div>' +
|
|
284
|
+
'<div class="welcome-banner__content">' +
|
|
285
|
+
'<div class="welcome-banner__icon">' + EMPTY_ICONS.welcome + '</div>' +
|
|
286
|
+
'<h2 class="welcome-banner__title">Welcome to your Observability Dashboard</h2>' +
|
|
287
|
+
'<p class="welcome-banner__subtitle">Agent sessions, delegations, and quality reviews will appear here as your team completes work.</p>' +
|
|
288
|
+
'<div class="welcome-banner__steps">' +
|
|
289
|
+
'<div class="welcome-step">' +
|
|
290
|
+
'<span class="welcome-step__num">1</span>' +
|
|
291
|
+
'<div class="welcome-step__text">' +
|
|
292
|
+
'<strong>Configure agents</strong>' +
|
|
293
|
+
'<span>Set up your orchestrator and specialist agents</span>' +
|
|
294
|
+
'</div>' +
|
|
295
|
+
'</div>' +
|
|
296
|
+
'<div class="welcome-step">' +
|
|
297
|
+
'<span class="welcome-step__num">2</span>' +
|
|
298
|
+
'<div class="welcome-step__text">' +
|
|
299
|
+
'<strong>Run a session</strong>' +
|
|
300
|
+
'<span>Delegate tasks — session logs are captured automatically</span>' +
|
|
301
|
+
'</div>' +
|
|
302
|
+
'</div>' +
|
|
303
|
+
'<div class="welcome-step">' +
|
|
304
|
+
'<span class="welcome-step__num">3</span>' +
|
|
305
|
+
'<div class="welcome-step__text">' +
|
|
306
|
+
'<strong>Watch insights flow in</strong>' +
|
|
307
|
+
'<span>Charts, metrics, and trends populate in real time</span>' +
|
|
308
|
+
'</div>' +
|
|
309
|
+
'</div>' +
|
|
310
|
+
'</div>' +
|
|
311
|
+
'</div>';
|
|
312
|
+
|
|
313
|
+
const main = document.querySelector('.dash-main');
|
|
314
|
+
if (main) main.prepend(banner);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function removeWelcomeBanner() {
|
|
318
|
+
const existing = document.getElementById('welcome-banner');
|
|
319
|
+
if (existing) existing.remove();
|
|
320
|
+
}
|
|
321
|
+
|
|
249
322
|
// ── KPI Rendering ────────────────────────────────────────
|
|
250
323
|
|
|
251
324
|
function renderKpis(sessions, delegations) {
|
|
252
325
|
const total = sessions.length;
|
|
326
|
+
const isEmpty = total === 0;
|
|
253
327
|
const successCount = sessions.filter((s) => s.outcome === 'success').length;
|
|
254
328
|
const rate = total > 0 ? Math.round((successCount / total) * 100) : 0;
|
|
255
329
|
const durSessions = sessions.filter((s) => s.duration_min != null);
|
|
@@ -262,50 +336,61 @@ const base = import.meta.env.BASE_URL;
|
|
|
262
336
|
: 0;
|
|
263
337
|
const uniqueAgents = new Set(delegations.map((d) => d.agent)).size;
|
|
264
338
|
|
|
339
|
+
// Toggle ghost class on KPI row
|
|
340
|
+
const kpiRow = document.querySelector('.kpi-row');
|
|
341
|
+
if (kpiRow) kpiRow.classList.toggle('kpi-row--empty', isEmpty);
|
|
342
|
+
|
|
265
343
|
const kpiSessions = document.getElementById('kpi-sessions');
|
|
266
344
|
const kpiSuccess = document.getElementById('kpi-success');
|
|
267
345
|
const kpiDelegations = document.getElementById('kpi-delegations');
|
|
268
346
|
const kpiDuration = document.getElementById('kpi-duration');
|
|
269
347
|
|
|
270
348
|
if (kpiSessions) {
|
|
271
|
-
kpiSessions.querySelector('.kpi-card__value').textContent = total;
|
|
272
|
-
kpiSessions.querySelector('.kpi-card__sub').innerHTML =
|
|
273
|
-
'<span class="kpi-
|
|
274
|
-
successCount +
|
|
275
|
-
' successful';
|
|
349
|
+
kpiSessions.querySelector('.kpi-card__value').textContent = isEmpty ? '0' : total;
|
|
350
|
+
kpiSessions.querySelector('.kpi-card__sub').innerHTML = isEmpty
|
|
351
|
+
? '<span class="kpi-card__hint">No sessions yet</span>'
|
|
352
|
+
: '<span class="kpi-trend kpi-trend--up">\u2191</span> ' + successCount + ' successful';
|
|
276
353
|
}
|
|
277
354
|
if (kpiSuccess) {
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
trendClass
|
|
284
|
-
|
|
285
|
-
(
|
|
286
|
-
'
|
|
355
|
+
if (isEmpty) {
|
|
356
|
+
kpiSuccess.querySelector('.kpi-card__value').textContent = '\u2014';
|
|
357
|
+
kpiSuccess.querySelector('.kpi-card__sub').innerHTML =
|
|
358
|
+
'<span class="kpi-card__hint">No sessions yet</span>';
|
|
359
|
+
} else {
|
|
360
|
+
const trendClass =
|
|
361
|
+
rate >= 80 ? 'up' : rate >= 60 ? 'neutral' : 'down';
|
|
362
|
+
kpiSuccess.querySelector('.kpi-card__value').textContent = rate + '%';
|
|
363
|
+
kpiSuccess.querySelector('.kpi-card__sub').innerHTML =
|
|
364
|
+
'<span class="kpi-trend kpi-trend--' +
|
|
365
|
+
trendClass +
|
|
366
|
+
'">' +
|
|
367
|
+
(trendClass === 'up' ? '\u2191' : trendClass === 'down' ? '\u2193' : '\u2192') +
|
|
368
|
+
'</span> across all sessions';
|
|
369
|
+
}
|
|
287
370
|
}
|
|
288
371
|
if (kpiDelegations) {
|
|
289
372
|
kpiDelegations.querySelector('.kpi-card__value').textContent =
|
|
290
|
-
delegations.length;
|
|
291
|
-
kpiDelegations.querySelector('.kpi-card__sub').
|
|
292
|
-
|
|
373
|
+
delegations.length === 0 ? '0' : delegations.length;
|
|
374
|
+
kpiDelegations.querySelector('.kpi-card__sub').innerHTML = isEmpty
|
|
375
|
+
? '<span class="kpi-card__hint">No delegations yet</span>'
|
|
376
|
+
: uniqueAgents + ' unique agents';
|
|
293
377
|
}
|
|
294
378
|
if (kpiDuration) {
|
|
295
|
-
kpiDuration.querySelector('.kpi-card__value').textContent =
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
'<span class="kpi-trend kpi-trend--neutral">\u2192</span> per session';
|
|
379
|
+
kpiDuration.querySelector('.kpi-card__value').textContent = isEmpty ? '\u2014' : avgDur + 'm';
|
|
380
|
+
kpiDuration.querySelector('.kpi-card__sub').innerHTML = isEmpty
|
|
381
|
+
? '<span class="kpi-card__hint">No duration yet</span>'
|
|
382
|
+
: '<span class="kpi-trend kpi-trend--neutral">\u2192</span> per session';
|
|
299
383
|
}
|
|
300
384
|
|
|
301
385
|
// Retries KPI
|
|
302
386
|
const totalRetries = sessions.reduce((sum, s) => sum + (s.retries || 0), 0);
|
|
303
387
|
const kpiRetries = document.getElementById('kpi-retries');
|
|
304
388
|
if (kpiRetries) {
|
|
305
|
-
kpiRetries.querySelector('.kpi-card__value').textContent = totalRetries;
|
|
389
|
+
kpiRetries.querySelector('.kpi-card__value').textContent = isEmpty ? '0' : totalRetries;
|
|
306
390
|
const retriedSessions = sessions.filter((s) => (s.retries || 0) > 0).length;
|
|
307
|
-
kpiRetries.querySelector('.kpi-card__sub').
|
|
308
|
-
|
|
391
|
+
kpiRetries.querySelector('.kpi-card__sub').innerHTML = isEmpty
|
|
392
|
+
? '<span class="kpi-card__hint">No retries yet</span>'
|
|
393
|
+
: retriedSessions + ' sessions with retries';
|
|
309
394
|
}
|
|
310
395
|
|
|
311
396
|
// Lessons KPI
|
|
@@ -315,13 +400,14 @@ const base = import.meta.env.BASE_URL;
|
|
|
315
400
|
);
|
|
316
401
|
const kpiLessons = document.getElementById('kpi-lessons');
|
|
317
402
|
if (kpiLessons) {
|
|
318
|
-
kpiLessons.querySelector('.kpi-card__value').textContent = totalLessons;
|
|
403
|
+
kpiLessons.querySelector('.kpi-card__value').textContent = isEmpty ? '0' : totalLessons;
|
|
319
404
|
const discoveryCount = sessions.reduce(
|
|
320
405
|
(sum, s) => sum + (s.discoveries ? s.discoveries.length : 0),
|
|
321
406
|
0
|
|
322
407
|
);
|
|
323
|
-
kpiLessons.querySelector('.kpi-card__sub').
|
|
324
|
-
|
|
408
|
+
kpiLessons.querySelector('.kpi-card__sub').innerHTML = isEmpty
|
|
409
|
+
? '<span class="kpi-card__hint">No lessons yet</span>'
|
|
410
|
+
: discoveryCount + ' issues discovered';
|
|
325
411
|
}
|
|
326
412
|
}
|
|
327
413
|
|
|
@@ -331,6 +417,11 @@ const base = import.meta.env.BASE_URL;
|
|
|
331
417
|
const el = document.getElementById('pipeline-view');
|
|
332
418
|
if (!el) return;
|
|
333
419
|
|
|
420
|
+
if (delegations.length === 0) {
|
|
421
|
+
el.innerHTML = emptyStateHtml('pipeline', 'No pipeline activity yet', 'Delegation phases appear here as tasks flow through Foundation, Integration, Validation, and QA stages.');
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
|
|
334
425
|
const phases = { 1: 0, 2: 0, 3: 0, 4: 0 };
|
|
335
426
|
delegations.forEach((d) => {
|
|
336
427
|
const p = d.phase || 1;
|
|
@@ -395,8 +486,7 @@ const base = import.meta.env.BASE_URL;
|
|
|
395
486
|
if (!el) return;
|
|
396
487
|
|
|
397
488
|
if (sessions.length === 0) {
|
|
398
|
-
el.innerHTML =
|
|
399
|
-
'<div class="empty-state"><div class="empty-state__icon">\uD83D\uDCCA</div><p class="empty-state__text">No session data available</p></div>';
|
|
489
|
+
el.innerHTML = emptyStateHtml('agents', 'No agent sessions yet', 'A breakdown of sessions per agent will appear here — stacked by outcome (success, partial, failed).');
|
|
400
490
|
return;
|
|
401
491
|
}
|
|
402
492
|
|
|
@@ -421,15 +511,18 @@ const base = import.meta.env.BASE_URL;
|
|
|
421
511
|
escapeHtml(name) +
|
|
422
512
|
'</span>' +
|
|
423
513
|
'<div class="bar-track">' +
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
(
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
514
|
+
(data.success > 0
|
|
515
|
+
? '<div class="bar-segment bar--success" style="width: ' +
|
|
516
|
+
((data.success / maxTotal) * 100).toFixed(1) + '%"></div>'
|
|
517
|
+
: '') +
|
|
518
|
+
(data.partial > 0
|
|
519
|
+
? '<div class="bar-segment bar--partial" style="width: ' +
|
|
520
|
+
((data.partial / maxTotal) * 100).toFixed(1) + '%"></div>'
|
|
521
|
+
: '') +
|
|
522
|
+
(data.failed > 0
|
|
523
|
+
? '<div class="bar-segment bar--failed" style="width: ' +
|
|
524
|
+
((data.failed / maxTotal) * 100).toFixed(1) + '%"></div>'
|
|
525
|
+
: '') +
|
|
433
526
|
'</div>' +
|
|
434
527
|
'<span class="bar-value">' +
|
|
435
528
|
data.total +
|
|
@@ -446,8 +539,7 @@ const base = import.meta.env.BASE_URL;
|
|
|
446
539
|
if (!el) return;
|
|
447
540
|
|
|
448
541
|
if (delegations.length === 0) {
|
|
449
|
-
el.innerHTML =
|
|
450
|
-
'<div class="empty-state"><div class="empty-state__icon">\uD83D\uDFE3</div><p class="empty-state__text">No delegation data available</p></div>';
|
|
542
|
+
el.innerHTML = emptyStateHtml('tiers', 'No tier data yet', 'Model tier distribution (Premium, Standard, Utility, Economy) will be visualized as a donut chart.');
|
|
451
543
|
return;
|
|
452
544
|
}
|
|
453
545
|
|
|
@@ -469,6 +561,8 @@ const base = import.meta.env.BASE_URL;
|
|
|
469
561
|
const circles = tiers.map((t) => {
|
|
470
562
|
const pct = t.count / total;
|
|
471
563
|
const dashLen = pct * circumference;
|
|
564
|
+
// Skip round linecap for single-segment donuts to avoid overlap artifact
|
|
565
|
+
const linecap = tiers.length === 1 ? 'butt' : 'round';
|
|
472
566
|
const segment =
|
|
473
567
|
'<circle cx="90" cy="90" r="' +
|
|
474
568
|
r +
|
|
@@ -485,7 +579,7 @@ const base = import.meta.env.BASE_URL;
|
|
|
485
579
|
(-cumOffset).toFixed(2) +
|
|
486
580
|
'" ' +
|
|
487
581
|
'transform="rotate(-90 90 90)" ' +
|
|
488
|
-
'stroke-linecap="
|
|
582
|
+
'stroke-linecap="' + linecap + '"/>';
|
|
489
583
|
cumOffset += dashLen;
|
|
490
584
|
return segment;
|
|
491
585
|
});
|
|
@@ -535,8 +629,7 @@ const base = import.meta.env.BASE_URL;
|
|
|
535
629
|
if (!el) return;
|
|
536
630
|
|
|
537
631
|
if (delegations.length === 0) {
|
|
538
|
-
el.innerHTML =
|
|
539
|
-
'<div class="empty-state"><div class="empty-state__icon">\u2699\uFE0F</div><p class="empty-state__text">No delegation data available</p></div>';
|
|
632
|
+
el.innerHTML = emptyStateHtml('mechanism', 'No delegation data yet', 'The split between sub-agent (inline) and background (worktree) delegations will be shown here.');
|
|
540
633
|
return;
|
|
541
634
|
}
|
|
542
635
|
|
|
@@ -571,12 +664,14 @@ const base = import.meta.env.BASE_URL;
|
|
|
571
664
|
var circles = mechs.map(function (m) {
|
|
572
665
|
var pct = m.count / total;
|
|
573
666
|
var dashLen = pct * circumference;
|
|
667
|
+
// Skip round linecap for single-segment donuts to avoid overlap artifact
|
|
668
|
+
var linecap = mechs.length === 1 ? 'butt' : 'round';
|
|
574
669
|
var segment =
|
|
575
670
|
'<circle cx="90" cy="90" r="' + r + '" fill="none" ' +
|
|
576
671
|
'stroke="' + (MECH_COLORS[m.name] || '#64748b') + '" stroke-width="18" ' +
|
|
577
672
|
'stroke-dasharray="' + dashLen.toFixed(2) + ' ' + (circumference - dashLen).toFixed(2) + '" ' +
|
|
578
673
|
'stroke-dashoffset="' + (-cumOffset).toFixed(2) + '" ' +
|
|
579
|
-
'transform="rotate(-90 90 90)" stroke-linecap="
|
|
674
|
+
'transform="rotate(-90 90 90)" stroke-linecap="' + linecap + '"/>';
|
|
580
675
|
cumOffset += dashLen;
|
|
581
676
|
return segment;
|
|
582
677
|
});
|
|
@@ -615,8 +710,7 @@ const base = import.meta.env.BASE_URL;
|
|
|
615
710
|
if (!el) return;
|
|
616
711
|
|
|
617
712
|
if (delegations.length === 0) {
|
|
618
|
-
el.innerHTML =
|
|
619
|
-
'<div class="empty-state"><div class="empty-state__icon">\uD83D\uDCCA</div><p class="empty-state__text">No delegation data available</p></div>';
|
|
713
|
+
el.innerHTML = emptyStateHtml('outcomes', 'No outcome data yet', 'Delegation results — success, partial, failed, redirected — will be tracked and compared here.');
|
|
620
714
|
return;
|
|
621
715
|
}
|
|
622
716
|
|
|
@@ -674,8 +768,7 @@ const base = import.meta.env.BASE_URL;
|
|
|
674
768
|
);
|
|
675
769
|
|
|
676
770
|
if (dates.length === 0) {
|
|
677
|
-
el.innerHTML =
|
|
678
|
-
'<div class="empty-state"><div class="empty-state__icon">\uD83D\uDCC5</div><p class="empty-state__text">No timeline data</p></div>';
|
|
771
|
+
el.innerHTML = emptyStateHtml('timeline', 'No timeline data yet', 'A daily activity chart will build here as sessions and delegations accumulate over time.');
|
|
679
772
|
return;
|
|
680
773
|
}
|
|
681
774
|
|
|
@@ -687,14 +780,21 @@ const base = import.meta.env.BASE_URL;
|
|
|
687
780
|
const pad = { top: 10, right: 10, bottom: 28, left: 10 };
|
|
688
781
|
const plotW = w - pad.left - pad.right;
|
|
689
782
|
const plotH = h - pad.top - pad.bottom;
|
|
690
|
-
|
|
691
|
-
const
|
|
783
|
+
// Prevent sparse layout when there are very few dates
|
|
784
|
+
const groupWidth = dates.length <= 3
|
|
785
|
+
? Math.min(100, plotW / dates.length)
|
|
786
|
+
: plotW / dates.length;
|
|
787
|
+
const barWidth = Math.min(dates.length <= 3 ? 24 : 16, groupWidth * 0.35);
|
|
788
|
+
// Center the bars when there are few dates
|
|
789
|
+
const timelineStartX = dates.length <= 3
|
|
790
|
+
? pad.left + (plotW - dates.length * groupWidth) / 2
|
|
791
|
+
: pad.left;
|
|
692
792
|
|
|
693
793
|
let rects = '';
|
|
694
794
|
let labels = '';
|
|
695
795
|
|
|
696
796
|
dates.forEach(([date, data], i) => {
|
|
697
|
-
const x =
|
|
797
|
+
const x = timelineStartX + i * groupWidth + groupWidth / 2;
|
|
698
798
|
const sH = maxVal > 0 ? (data.sessions / maxVal) * plotH : 0;
|
|
699
799
|
const dH = maxVal > 0 ? (data.delegations / maxVal) * plotH : 0;
|
|
700
800
|
|
|
@@ -754,8 +854,7 @@ const base = import.meta.env.BASE_URL;
|
|
|
754
854
|
if (!el) return;
|
|
755
855
|
|
|
756
856
|
if (sessions.length === 0) {
|
|
757
|
-
el.innerHTML =
|
|
758
|
-
'<div class="empty-state"><div class="empty-state__icon">\uD83E\uDD16</div><p class="empty-state__text">No model data</p></div>';
|
|
857
|
+
el.innerHTML = emptyStateHtml('models', 'No model data yet', 'Model utilization across sessions — Claude Opus, GPT-5, Gemini, etc. — will be compared here.');
|
|
759
858
|
return;
|
|
760
859
|
}
|
|
761
860
|
|
|
@@ -801,8 +900,7 @@ const base = import.meta.env.BASE_URL;
|
|
|
801
900
|
.slice(0, 10);
|
|
802
901
|
|
|
803
902
|
if (sorted.length === 0) {
|
|
804
|
-
el.innerHTML =
|
|
805
|
-
'<div class="empty-state"><div class="empty-state__icon">\uD83D\uDCDD</div><p class="empty-state__text">No sessions recorded yet</p></div>';
|
|
903
|
+
el.innerHTML = emptyStateHtml('execLog', 'No execution history yet', 'A step-by-step trace of agent activity — with outcomes, durations, and metadata — will appear here.');
|
|
806
904
|
return;
|
|
807
905
|
}
|
|
808
906
|
|
|
@@ -885,8 +983,7 @@ const base = import.meta.env.BASE_URL;
|
|
|
885
983
|
if (!el) return;
|
|
886
984
|
|
|
887
985
|
if (panels.length === 0) {
|
|
888
|
-
el.innerHTML =
|
|
889
|
-
'<div class="empty-state"><div class="empty-state__icon">\uD83D\uDEE1\uFE0F</div><p class="empty-state__text">No panel reviews yet</p></div>';
|
|
986
|
+
el.innerHTML = emptyStateHtml('panels', 'No panel reviews yet', 'Quality gate verdicts from majority-vote panels — with pass/block counts and must-fix items — will be shown here.');
|
|
890
987
|
return;
|
|
891
988
|
}
|
|
892
989
|
|
|
@@ -951,8 +1048,7 @@ const base = import.meta.env.BASE_URL;
|
|
|
951
1048
|
.slice(0, 15);
|
|
952
1049
|
|
|
953
1050
|
if (sorted.length === 0) {
|
|
954
|
-
el.innerHTML =
|
|
955
|
-
'<div class="empty-state"><div class="empty-state__icon">\uD83D\uDCCB</div><p class="empty-state__text">No sessions recorded</p></div>';
|
|
1051
|
+
el.innerHTML = emptyStateHtml('sessions', 'No session records yet', 'A detailed table of recent sessions — with timestamps, agents, tasks, outcomes, and linked issues — will populate here.');
|
|
956
1052
|
return;
|
|
957
1053
|
}
|
|
958
1054
|
|
|
@@ -1014,6 +1110,14 @@ const base = import.meta.env.BASE_URL;
|
|
|
1014
1110
|
loadNdjson(base + 'data/panels.ndjson'),
|
|
1015
1111
|
]);
|
|
1016
1112
|
|
|
1113
|
+
// Show/hide welcome banner
|
|
1114
|
+
const allEmpty = sessions.length === 0 && delegations.length === 0 && panels.length === 0;
|
|
1115
|
+
if (allEmpty) {
|
|
1116
|
+
renderWelcomeBanner();
|
|
1117
|
+
} else {
|
|
1118
|
+
removeWelcomeBanner();
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1017
1121
|
renderKpis(sessions, delegations);
|
|
1018
1122
|
renderPipeline(delegations);
|
|
1019
1123
|
renderAgentChart(sessions);
|
|
@@ -991,6 +991,267 @@ body {
|
|
|
991
991
|
max-width: 320px;
|
|
992
992
|
}
|
|
993
993
|
|
|
994
|
+
/* ---------- Enhanced Empty State ---------- */
|
|
995
|
+
.empty-state--enhanced {
|
|
996
|
+
padding: 56px 32px;
|
|
997
|
+
gap: 16px;
|
|
998
|
+
border: 1px dashed rgba(167, 139, 250, 0.15);
|
|
999
|
+
border-radius: 12px;
|
|
1000
|
+
background:
|
|
1001
|
+
radial-gradient(ellipse 300px 200px at 50% 30%, rgba(99, 102, 241, 0.04) 0%, transparent 70%),
|
|
1002
|
+
var(--bg-tertiary);
|
|
1003
|
+
position: relative;
|
|
1004
|
+
overflow: hidden;
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
.empty-state--enhanced::before {
|
|
1008
|
+
content: '';
|
|
1009
|
+
position: absolute;
|
|
1010
|
+
inset: 0;
|
|
1011
|
+
background:
|
|
1012
|
+
repeating-linear-gradient(
|
|
1013
|
+
0deg,
|
|
1014
|
+
transparent,
|
|
1015
|
+
transparent 23px,
|
|
1016
|
+
rgba(255, 255, 255, 0.015) 23px,
|
|
1017
|
+
rgba(255, 255, 255, 0.015) 24px
|
|
1018
|
+
);
|
|
1019
|
+
pointer-events: none;
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
.empty-state__icon-wrap {
|
|
1023
|
+
width: 64px;
|
|
1024
|
+
height: 64px;
|
|
1025
|
+
display: flex;
|
|
1026
|
+
align-items: center;
|
|
1027
|
+
justify-content: center;
|
|
1028
|
+
border-radius: 16px;
|
|
1029
|
+
background: rgba(167, 139, 250, 0.06);
|
|
1030
|
+
border: 1px solid rgba(167, 139, 250, 0.12);
|
|
1031
|
+
color: var(--text-accent);
|
|
1032
|
+
animation: empty-breathe 4s ease-in-out infinite;
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
@keyframes empty-breathe {
|
|
1036
|
+
0%, 100% {
|
|
1037
|
+
box-shadow: 0 0 0 0 rgba(167, 139, 250, 0.08);
|
|
1038
|
+
transform: scale(1);
|
|
1039
|
+
}
|
|
1040
|
+
50% {
|
|
1041
|
+
box-shadow: 0 0 20px 4px rgba(167, 139, 250, 0.06);
|
|
1042
|
+
transform: scale(1.03);
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
.empty-state__title {
|
|
1047
|
+
font-size: 0.9375rem;
|
|
1048
|
+
font-weight: 600;
|
|
1049
|
+
color: var(--text-secondary);
|
|
1050
|
+
letter-spacing: -0.01em;
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
.empty-state__desc {
|
|
1054
|
+
font-size: 0.8125rem;
|
|
1055
|
+
color: var(--text-tertiary);
|
|
1056
|
+
max-width: 380px;
|
|
1057
|
+
line-height: 1.55;
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
/* ---------- KPI Empty Hints ---------- */
|
|
1061
|
+
.kpi-card__hint {
|
|
1062
|
+
color: var(--text-tertiary);
|
|
1063
|
+
font-style: italic;
|
|
1064
|
+
font-size: 0.6875rem;
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
.kpi-row--empty .kpi-card {
|
|
1068
|
+
border-style: dashed;
|
|
1069
|
+
border-color: rgba(255, 255, 255, 0.04);
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
.kpi-row--empty .kpi-card__value {
|
|
1073
|
+
color: var(--text-tertiary);
|
|
1074
|
+
opacity: 0.5;
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
/* ---------- Welcome Banner ---------- */
|
|
1078
|
+
.welcome-banner {
|
|
1079
|
+
position: relative;
|
|
1080
|
+
background: var(--bg-secondary);
|
|
1081
|
+
border: 1px solid transparent;
|
|
1082
|
+
border-radius: 16px;
|
|
1083
|
+
padding: 48px 40px;
|
|
1084
|
+
overflow: hidden;
|
|
1085
|
+
z-index: 1;
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
.welcome-banner::before {
|
|
1089
|
+
content: '';
|
|
1090
|
+
position: absolute;
|
|
1091
|
+
inset: -1px;
|
|
1092
|
+
border-radius: 16px;
|
|
1093
|
+
padding: 1px;
|
|
1094
|
+
background: linear-gradient(
|
|
1095
|
+
135deg,
|
|
1096
|
+
rgba(167, 139, 250, 0.3) 0%,
|
|
1097
|
+
rgba(99, 102, 241, 0.15) 30%,
|
|
1098
|
+
rgba(59, 130, 246, 0.1) 60%,
|
|
1099
|
+
rgba(167, 139, 250, 0.2) 100%
|
|
1100
|
+
);
|
|
1101
|
+
-webkit-mask:
|
|
1102
|
+
linear-gradient(#fff 0 0) content-box,
|
|
1103
|
+
linear-gradient(#fff 0 0);
|
|
1104
|
+
mask:
|
|
1105
|
+
linear-gradient(#fff 0 0) content-box,
|
|
1106
|
+
linear-gradient(#fff 0 0);
|
|
1107
|
+
-webkit-mask-composite: xor;
|
|
1108
|
+
mask-composite: exclude;
|
|
1109
|
+
pointer-events: none;
|
|
1110
|
+
z-index: 0;
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
.welcome-banner__glow {
|
|
1114
|
+
position: absolute;
|
|
1115
|
+
top: -60px;
|
|
1116
|
+
left: 50%;
|
|
1117
|
+
transform: translateX(-50%);
|
|
1118
|
+
width: 500px;
|
|
1119
|
+
height: 300px;
|
|
1120
|
+
background: radial-gradient(
|
|
1121
|
+
ellipse at center,
|
|
1122
|
+
rgba(167, 139, 250, 0.08) 0%,
|
|
1123
|
+
rgba(99, 102, 241, 0.04) 40%,
|
|
1124
|
+
transparent 70%
|
|
1125
|
+
);
|
|
1126
|
+
pointer-events: none;
|
|
1127
|
+
z-index: 0;
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
.welcome-banner__content {
|
|
1131
|
+
position: relative;
|
|
1132
|
+
z-index: 1;
|
|
1133
|
+
display: flex;
|
|
1134
|
+
flex-direction: column;
|
|
1135
|
+
align-items: center;
|
|
1136
|
+
text-align: center;
|
|
1137
|
+
gap: 20px;
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
.welcome-banner__icon {
|
|
1141
|
+
width: 72px;
|
|
1142
|
+
height: 72px;
|
|
1143
|
+
display: flex;
|
|
1144
|
+
align-items: center;
|
|
1145
|
+
justify-content: center;
|
|
1146
|
+
border-radius: 20px;
|
|
1147
|
+
background: rgba(167, 139, 250, 0.08);
|
|
1148
|
+
border: 1px solid rgba(167, 139, 250, 0.15);
|
|
1149
|
+
color: var(--text-accent);
|
|
1150
|
+
animation: welcome-float 6s ease-in-out infinite;
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
@keyframes welcome-float {
|
|
1154
|
+
0%, 100% {
|
|
1155
|
+
transform: translateY(0);
|
|
1156
|
+
box-shadow: 0 8px 32px rgba(167, 139, 250, 0.08);
|
|
1157
|
+
}
|
|
1158
|
+
50% {
|
|
1159
|
+
transform: translateY(-6px);
|
|
1160
|
+
box-shadow: 0 16px 48px rgba(167, 139, 250, 0.12);
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
.welcome-banner__title {
|
|
1165
|
+
font-size: 1.375rem;
|
|
1166
|
+
font-weight: 700;
|
|
1167
|
+
color: var(--text-primary);
|
|
1168
|
+
letter-spacing: -0.02em;
|
|
1169
|
+
line-height: 1.3;
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
.welcome-banner__subtitle {
|
|
1173
|
+
font-size: 0.9375rem;
|
|
1174
|
+
color: var(--text-secondary);
|
|
1175
|
+
max-width: 480px;
|
|
1176
|
+
line-height: 1.6;
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
.welcome-banner__steps {
|
|
1180
|
+
display: flex;
|
|
1181
|
+
gap: 20px;
|
|
1182
|
+
margin-top: 12px;
|
|
1183
|
+
flex-wrap: wrap;
|
|
1184
|
+
justify-content: center;
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
.welcome-step {
|
|
1188
|
+
display: flex;
|
|
1189
|
+
align-items: flex-start;
|
|
1190
|
+
gap: 12px;
|
|
1191
|
+
text-align: left;
|
|
1192
|
+
padding: 16px 20px;
|
|
1193
|
+
background: rgba(255, 255, 255, 0.02);
|
|
1194
|
+
border: 1px solid rgba(255, 255, 255, 0.05);
|
|
1195
|
+
border-radius: 12px;
|
|
1196
|
+
min-width: 200px;
|
|
1197
|
+
max-width: 220px;
|
|
1198
|
+
transition: border-color var(--transition-fast), background var(--transition-fast);
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
.welcome-step:hover {
|
|
1202
|
+
border-color: rgba(167, 139, 250, 0.15);
|
|
1203
|
+
background: rgba(255, 255, 255, 0.03);
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
.welcome-step__num {
|
|
1207
|
+
width: 28px;
|
|
1208
|
+
height: 28px;
|
|
1209
|
+
border-radius: 8px;
|
|
1210
|
+
display: flex;
|
|
1211
|
+
align-items: center;
|
|
1212
|
+
justify-content: center;
|
|
1213
|
+
font-size: 0.75rem;
|
|
1214
|
+
font-weight: 700;
|
|
1215
|
+
color: var(--text-accent);
|
|
1216
|
+
background: rgba(167, 139, 250, 0.1);
|
|
1217
|
+
border: 1px solid rgba(167, 139, 250, 0.2);
|
|
1218
|
+
flex-shrink: 0;
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
.welcome-step__text {
|
|
1222
|
+
display: flex;
|
|
1223
|
+
flex-direction: column;
|
|
1224
|
+
gap: 3px;
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
.welcome-step__text strong {
|
|
1228
|
+
font-size: 0.8125rem;
|
|
1229
|
+
font-weight: 600;
|
|
1230
|
+
color: var(--text-primary);
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
.welcome-step__text span {
|
|
1234
|
+
font-size: 0.75rem;
|
|
1235
|
+
color: var(--text-tertiary);
|
|
1236
|
+
line-height: 1.4;
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
@media (max-width: 640px) {
|
|
1240
|
+
.welcome-banner {
|
|
1241
|
+
padding: 32px 24px;
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
.welcome-banner__steps {
|
|
1245
|
+
flex-direction: column;
|
|
1246
|
+
align-items: center;
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
.welcome-step {
|
|
1250
|
+
max-width: 100%;
|
|
1251
|
+
width: 100%;
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
|
|
994
1255
|
/* ---------- Animations ---------- */
|
|
995
1256
|
@keyframes slide-up {
|
|
996
1257
|
from {
|