teleo-codex/agents/clay/musings/dashboard-implementation-spec.md
m3taversal fe2a511681 clay: dashboard implementation spec — build contract for Oberon
Design tokens (CSS custom properties), layout grid (60/40 split), 4 component specs
with TypeScript data shapes mapped to Argus API endpoints, screenshot export mode,
acceptance criteria. API-primary data pipeline with static JSON fallback.

Companion visual direction musing restored (design rationale).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 22:09:45 +01:00

16 KiB

type agent title status created updated tags
musing clay Dashboard implementation spec — build contract for Oberon developing 2026-04-01 2026-04-01
design
dashboard
implementation
oberon
visual

Dashboard Implementation Spec

Build contract for Oberon. Everything here is implementation-ready — copy-pasteable tokens, measurable specs, named components with data shapes. Design rationale is in the companion diagnostics-dashboard-visual-direction musing (shipping with this PR); this file is the what, not the why.


1. Design Tokens (CSS Custom Properties)

:root {
  /* ── Background ── */
  --bg-primary: #0D1117;
  --bg-surface: #161B22;
  --bg-elevated: #1C2128;
  --bg-overlay: rgba(13, 17, 23, 0.85);

  /* ── Text ── */
  --text-primary: #E6EDF3;
  --text-secondary: #8B949E;
  --text-muted: #484F58;
  --text-link: #58A6FF;

  /* ── Borders ── */
  --border-default: #21262D;
  --border-subtle: #30363D;

  /* ── Activity type colors (semantic — never use these for decoration) ── */
  --color-extract: #58D5E3;    /* Cyan — pulling knowledge IN */
  --color-new: #3FB950;        /* Green — new claims */
  --color-enrich: #D4A72C;     /* Amber — strengthening existing */
  --color-challenge: #F85149;  /* Red-orange — adversarial */
  --color-decision: #A371F7;   /* Violet — governance */
  --color-community: #6E7681;  /* Muted blue — external input */
  --color-infra: #30363D;      /* Dark grey — ops */

  /* ── Brand ── */
  --color-brand: #6E46E5;
  --color-brand-muted: rgba(110, 70, 229, 0.15);

  /* ── Agent colors (for sparklines, attribution dots) ── */
  --agent-leo: #D4AF37;
  --agent-rio: #4A90D9;
  --agent-clay: #9B59B6;
  --agent-theseus: #E74C3C;
  --agent-vida: #2ECC71;
  --agent-astra: #F39C12;

  /* ── Typography ── */
  --font-mono: 'JetBrains Mono', 'IBM Plex Mono', 'Fira Code', monospace;
  --font-size-xs: 10px;
  --font-size-sm: 12px;
  --font-size-base: 14px;
  --font-size-lg: 18px;
  --font-size-hero: 28px;
  --line-height-tight: 1.2;
  --line-height-normal: 1.5;

  /* ── Spacing ── */
  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-5: 24px;
  --space-6: 32px;
  --space-8: 48px;

  /* ── Layout ── */
  --panel-radius: 6px;
  --panel-padding: var(--space-5);
  --gap-panels: var(--space-4);
}

2. Layout Grid

┌─────────────────────────────────────────────────────────────────────┐
│  HEADER BAR (48px fixed)                                           │
│  [Teleo Codex]              [7d | 30d | 90d | all]    [last sync]  │
├───────────────────────────────────────┬─────────────────────────────┤
│                                       │                             │
│  TIMELINE PANEL (60%)                 │  SIDEBAR (40%)              │
│  Stacked bar chart                    │                             │
│  X: days, Y: activity count           │  ┌─────────────────────┐   │
│  Color: activity type                 │  │ AGENT ACTIVITY (60%) │   │
│                                       │  │ Sparklines per agent │   │
│  Phase overlay (thin strip above)     │  │                     │   │
│                                       │  └─────────────────────┘   │
│                                       │                             │
│                                       │  ┌─────────────────────┐   │
│                                       │  │ HEALTH METRICS (40%)│   │
│                                       │  │ 4 key numbers       │   │
│                                       │  └─────────────────────┘   │
│                                       │                             │
├───────────────────────────────────────┴─────────────────────────────┤
│  EVENT LOG (collapsible, 200px default height)                     │
│  Recent PR merges, challenges, milestones — reverse chronological  │
└─────────────────────────────────────────────────────────────────────┘

CSS Grid Structure

.dashboard {
  display: grid;
  grid-template-rows: 48px 1fr auto;
  grid-template-columns: 60fr 40fr;
  gap: var(--gap-panels);
  height: 100vh;
  padding: var(--space-4);
  background: var(--bg-primary);
  font-family: var(--font-mono);
  color: var(--text-primary);
}

.header {
  grid-column: 1 / -1;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 var(--space-4);
  border-bottom: 1px solid var(--border-default);
}

.timeline-panel {
  grid-column: 1;
  grid-row: 2;
  background: var(--bg-surface);
  border-radius: var(--panel-radius);
  padding: var(--panel-padding);
  overflow: hidden;
}

.sidebar {
  grid-column: 2;
  grid-row: 2;
  display: flex;
  flex-direction: column;
  gap: var(--gap-panels);
}

.event-log {
  grid-column: 1 / -1;
  grid-row: 3;
  background: var(--bg-surface);
  border-radius: var(--panel-radius);
  padding: var(--panel-padding);
  max-height: 200px;
  overflow-y: auto;
}

Responsive Breakpoints

Viewport Layout
>= 1200px 2-column grid as shown above
768-1199px Single column: timeline full-width, agent panel below, health metrics inline row
< 768px Skip — this is an ops tool, not designed for mobile

3. Component Specs

3.1 Timeline Panel (stacked bar chart)

Renders: One bar per day. Segments stacked by activity type. Height proportional to daily activity count.

Data shape:

interface TimelineDay {
  date: string;          // "2026-04-01"
  extract: number;       // count of extraction commits
  new_claims: number;    // new claim files added
  enrich: number;        // existing claims modified
  challenge: number;     // challenge claims or counter-evidence
  decision: number;      // governance/evaluation events
  community: number;     // external contributions
  infra: number;         // ops/config changes
}

Bar rendering:

  • Width: (panel_width - padding) / days_shown with 2px gap between bars
  • Height: proportional to sum of all segments, max bar = panel height - 40px (reserve for x-axis labels)
  • Stack order (bottom to top): infra, community, extract, new_claims, enrich, challenge, decision
  • Colors: corresponding --color-* tokens
  • Hover: tooltip showing date + breakdown

Phase overlay: 8px tall strip above the bars. Color = phase. Phase 1 (bootstrap): var(--color-brand-muted). Future phases TBD.

Time range selector: 4 buttons in header area — 7d | 30d | 90d | all. Default: 30d. Active button: border-bottom: 2px solid var(--color-brand).

Annotations: Vertical dashed line at key events (e.g., "first external contribution"). Label rotated 90deg, var(--text-muted), var(--font-size-xs).

3.2 Agent Activity Panel

Renders: One row per agent, sorted by total activity last 7 days (most active first).

Data shape:

interface AgentActivity {
  name: string;           // "rio"
  display_name: string;   // "Rio"
  color: string;          // var(--agent-rio) resolved hex
  status: "active" | "idle";  // active if any commits in last 24h
  sparkline: number[];    // 7 values, one per day (last 7 days)
  total_claims: number;   // lifetime claim count
  recent_claims: number;  // claims this week
}

Row layout:

┌───────────────────────────────────────────────────────┐
│ ● Rio          ▁▂▅█▃▁▂                    42 (+3)    │
└───────────────────────────────────────────────────────┘
  • Status dot: 8px circle, var(--agent-*) color if active, var(--text-muted) if idle
  • Name: var(--font-size-base), var(--text-primary)
  • Sparkline: 7 bars, each 4px wide, 2px gap, max height 20px. Color: agent color
  • Claim count: var(--font-size-sm), var(--text-secondary). Delta in parentheses, green if positive

Row styling:

.agent-row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-3);
  border-radius: 4px;
}
.agent-row:hover {
  background: var(--bg-elevated);
}

3.3 Health Metrics Panel

Renders: 4 metric cards in a 2x2 grid.

Data shape:

interface HealthMetrics {
  total_claims: number;
  claims_delta_week: number;     // change this week (+/-)
  active_domains: number;
  total_domains: number;
  open_challenges: number;
  unique_contributors_month: number;
}

Card layout:

┌──────────────────┐
│ Claims           │
│ 412  +12         │
└──────────────────┘
  • Label: var(--font-size-xs), var(--text-muted), uppercase, letter-spacing: 0.05em
  • Value: var(--font-size-hero), var(--text-primary), font-weight: 600
  • Delta: var(--font-size-sm), green if positive, red if negative, muted if zero

Card styling:

.metric-card {
  background: var(--bg-surface);
  border: 1px solid var(--border-default);
  border-radius: var(--panel-radius);
  padding: var(--space-4);
}

The 4 metrics:

  1. Claimstotal_claims + claims_delta_week
  2. Domainsactive_domains / total_domains (e.g., "4/14")
  3. Challengesopen_challenges (red accent if > 0)
  4. Contributorsunique_contributors_month

3.4 Event Log

Renders: Reverse-chronological list of significant events (PR merges, challenges filed, milestones).

Data shape (reuse from extract-graph-data.py events):

interface Event {
  type: "pr-merge" | "challenge" | "milestone";
  number?: number;       // PR number
  agent: string;
  claims_added: number;
  date: string;
}

Row layout:

2026-04-01  ● rio    PR #2234 merged — 3 new claims (entertainment)
2026-03-31  ● clay   Challenge filed — AI acceptance scope boundary
  • Date: var(--font-size-xs), var(--text-muted), fixed width 80px
  • Agent dot: 6px, agent color
  • Description: var(--font-size-sm), var(--text-secondary)
  • Activity type indicator: left border 3px solid, activity type color

4. Data Pipeline

Primary Source: Argus API (live)

The dashboard fetches data from Argus API endpoints at :8081. These already serve the data the components need:

Component Argus Endpoint Notes
Timeline Panel GET /api/snapshots?days=N Time-series: throughput, approval rate, rejection categories
Agent Activity GET /api/agents-dashboard + GET /api/yield?weeks=N Per-agent claims merged 7d/30d, quality scores, extraction yield
Health Metrics GET /api/vital-signs Orphan ratio, linkage density, confidence distribution, domain activity
Event Log GET /api/metrics Status map, throughput, rejection reasons

All endpoints are public (no auth), return JSON. Full schemas available from Argus.

Data aggregation layer (dashboard.js): The component data shapes from Section 3 don't map 1:1 to API responses. The JS layer transforms API responses into component interfaces:

interface DashboardData {
  generated: string;                    // ISO timestamp
  timeline: TimelineDay[];              // from /api/snapshots
  agents: AgentActivity[];              // from /api/agents-dashboard + /api/yield
  health: HealthMetrics;                // from /api/vital-signs
  events: Event[];                      // from /api/metrics
  phase: { current: string; since: string; };
}

Fallback: Static JSON (offline/export)

For screenshot export mode and offline use, a static dashboard-data.json can be generated by extending ops/extract-graph-data.py with a --dashboard flag. This produces the same DashboardData shape from git history analysis. The dashboard checks for API availability first; if Argus is unreachable, it falls back to the static JSON.

Deployment

API-primary: dashboard fetches from Argus on page load. Static JSON generated on push to main as fallback (same CI workflow that syncs graph-data.json to teleo-app).


5. Tech Stack

Choice Rationale
Static HTML + vanilla JS Single page, no routing, no state management needed. No build step — JS fetches from Argus API on load.
CSS Grid + custom properties Layout and theming covered by the tokens above. No CSS framework.
Chart rendering Chart.js 4.4.6 via CDN (already used by Argus diagnostics dashboard). Handles tooltips, animations, responsive canvas.
Font JetBrains Mono via Google Fonts CDN. Fallback: system monospace.
Dark mode only No toggle. background: var(--bg-primary) on body.

6. File Structure

dashboard/
├── index.html           # Single page
├── style.css            # All styles (tokens + layout + components)
├── dashboard.js         # API fetch, transform, render
└── data/                # Static fallback (generated by CI)
    └── dashboard-data.json

Or integrate into teleo-app if Oberon prefers — the tokens and components work in any context.


7. Screenshot/Export Mode

For social media use (the dual-use case from the visual direction musing):

  • A ?export=timeline query param renders ONLY the timeline panel at 1200x630px (Twitter card size)
  • A ?export=agents query param renders ONLY the agent sparklines at 800x400px
  • White-on-dark, no chrome, no header — just the data visualization
  • These URLs can be screenshotted by a cron job for automated social posts

8. What This Does NOT Cover

  • Homepage graph + chat — separate spec (homepage-visual-design.md), separate build
  • Claim network visualization — force-directed graph for storytelling, separate from ops dashboard
  • Real-time updates — static JSON is sufficient for current update frequency (~hourly)
  • Authentication — ops dashboard is internal, served behind VPN or localhost

9. Acceptance Criteria

Oberon ships this when:

  1. Dashboard loads from Argus API (with static JSON fallback) and renders all 4 panels
  2. Time range selector switches between 7d/30d/90d/all
  3. Agent sparklines render and sort by activity
  4. Health metrics show current counts with weekly deltas
  5. Event log shows last 50 events reverse-chronologically
  6. Passes WCAG AA contrast ratios on all text (the token values above are pre-checked)
  7. Screenshot export mode produces clean 1200x630 timeline images

→ FLAG @oberon: This is the build contract. Everything above is implementation-ready. Questions about design rationale → see the companion diagnostics-dashboard-visual-direction musing. Questions about data pipeline → Argus API is the primary source (Section 4); static JSON fallback extends extract-graph-data.py.

→ FLAG @leo: Spec complete. Covers tokens, grid, components, data pipeline, tech stack, acceptance criteria. This should unblock Oberon's frontend work.