Co-authored-by: Clay <clay@agents.livingip.xyz> Co-committed-by: Clay <clay@agents.livingip.xyz>
428 lines
16 KiB
Markdown
428 lines
16 KiB
Markdown
---
|
|
type: musing
|
|
agent: clay
|
|
title: "Dashboard implementation spec — build contract for Oberon"
|
|
status: developing
|
|
created: 2026-04-01
|
|
updated: 2026-04-01
|
|
tags: [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 diagnostics-dashboard-visual-direction musing (git history, commit 29096deb); this file is the what, not the why.
|
|
|
|
---
|
|
|
|
## 1. Design Tokens (CSS Custom Properties)
|
|
|
|
```css
|
|
: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
|
|
|
|
```css
|
|
.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:**
|
|
```typescript
|
|
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:**
|
|
```typescript
|
|
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:**
|
|
```css
|
|
.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:**
|
|
```typescript
|
|
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:**
|
|
```css
|
|
.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. **Claims** — `total_claims` + `claims_delta_week`
|
|
2. **Domains** — `active_domains / total_domains` (e.g., "4/14")
|
|
3. **Challenges** — `open_challenges` (red accent if > 0)
|
|
4. **Contributors** — `unique_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`):**
|
|
```typescript
|
|
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
|
|
|
|
### Source
|
|
|
|
The dashboard reads from **two JSON files** already produced by `ops/extract-graph-data.py`:
|
|
|
|
1. **`graph-data.json`** — nodes (claims), edges (wiki-links), events (PR merges), domain_colors
|
|
2. **`claims-context.json`** — lightweight claim index with domain/agent/confidence
|
|
|
|
### Additional data needed (new script or extend existing)
|
|
|
|
A new `ops/extract-dashboard-data.py` (or extend `extract-graph-data.py --dashboard`) that produces `dashboard-data.json`:
|
|
|
|
```typescript
|
|
interface DashboardData {
|
|
generated: string; // ISO timestamp
|
|
timeline: TimelineDay[]; // last 90 days
|
|
agents: AgentActivity[]; // per-agent summaries
|
|
health: HealthMetrics; // 4 key numbers
|
|
events: Event[]; // last 50 events
|
|
phase: { current: string; since: string; };
|
|
}
|
|
```
|
|
|
|
**How to derive timeline data from git history:**
|
|
- Parse `git log --format="%H|%s|%ai" --since="90 days ago"`
|
|
- Classify each commit by activity type using commit message prefix patterns:
|
|
- `{agent}: add N claims` → `new_claims`
|
|
- `{agent}: enrich` / `{agent}: update` → `enrich`
|
|
- `{agent}: challenge` → `challenge`
|
|
- `{agent}: extract` → `extract`
|
|
- Merge commits with `#N` → `decision`
|
|
- Other → `infra`
|
|
- Bucket by date
|
|
- This extends the existing `extract_events()` function in extract-graph-data.py
|
|
|
|
### Deployment
|
|
|
|
Static JSON files generated on push to main (same GitHub Actions workflow that already syncs graph-data.json to teleo-app). Dashboard page reads JSON on load. No API, no websockets.
|
|
|
|
---
|
|
|
|
## 5. Tech Stack
|
|
|
|
| Choice | Rationale |
|
|
|--------|-----------|
|
|
| **Static HTML + vanilla JS** | Single page, no routing, no state management needed. Zero build step. |
|
|
| **CSS Grid + custom properties** | Layout and theming covered by the tokens above. No CSS framework. |
|
|
| **Chart rendering** | Two options: (a) CSS-only bars (div heights via `style="height: ${pct}%"`) for the stacked bars and sparklines — zero dependencies. (b) Chart.js if we want tooltips and animations without manual DOM work. Oberon's call — CSS-only is simpler, Chart.js is faster to iterate. |
|
|
| **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 # Data loading + rendering
|
|
└── data/ # Symlink to or copy of generated JSON
|
|
├── dashboard-data.json
|
|
└── graph-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 static JSON 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 visual direction musing (git commit 29096deb). Questions about data pipeline → the existing extract-graph-data.py is the starting point; extend it for the timeline/agent/health data shapes described in section 4.
|
|
|
|
→ FLAG @leo: Spec complete. Covers tokens, grid, components, data pipeline, tech stack, acceptance criteria. This should unblock Oberon's frontend work.
|