Files
echo-core/dashboard/DESIGN.md
Marius Mutu 5e930ade02 feat(dashboard): unified workspace hub — cookie auth, 9-state projects, planning chat
Merges workspace.html + ralph.html into a single unified project hub with:
- Cookie-based auth (DASHBOARD_TOKEN, HttpOnly, SameSite=Strict)
- 9-state project badge system (running-ralph/manual, planning, approved,
  pending, blocked, failed, complete, idle) with BUTTONS_FOR_STATE matrix
- SSE realtime + polling fallback, version-based optimistic concurrency (If-Match)
- Planning chat modal (phase stepper, markdown bubbles, 50s+ wait state, auto-resume)
- Propose modal (Variant B: inline Plan-with-Echo checkbox)
- 5-type toast taxonomy (success/info/warning/busy/error, 3px colored left-bar)
- Inter font self-hosted + shared tokens.css design system + DESIGN.md
- src/jsonlock.py (flock helper, sidecar .lock for stable inode)
- src/approved_tasks_cli.py (shell-safe wrapper for cron/ralph.sh)
- 55 new tests (T#1–T#30) + real jsonlock bug fix caught by T#16/T#28
- No emoji anywhere (enforced by test_dashboard_no_emoji.py)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-28 07:26:19 +00:00

10 KiB

Echo Dashboard — Design System

This document is the source of truth for visual decisions across the Echo Dashboard (port 8088, served at /echo/). Tokens live in dashboard/static/tokens.css. Page-level CSS is in common.css and per-page <style> blocks. Pages must include tokens.css before common.css.


Theme

  • Default: dark. Background --bg-base: #13131a (near-black neutral).
  • Light theme: opt-in via <html data-theme="light">. Light tokens override the dark palette in the same :root-equivalent block.
  • Toggle: header .theme-toggle button — persisted in localStorage.

Surfaces are translucent overlays on --bg-base, never solid greys, so elevation reads consistently against future backgrounds.


Color tokens

Surfaces (dark)

Token Value Use
--bg-base #13131a App background
--bg-surface rgba(255,255,255,0.12) Cards, panels, inputs
--bg-surface-hover rgba(255,255,255,0.16) Hover state on surfaces
--bg-surface-active rgba(255,255,255,0.20) Pressed / active surfaces
--bg-elevated rgba(255,255,255,0.14) Selects, popovers
--header-bg rgba(19,19,26,0.95) Sticky header backdrop

Text

Token Value Use
--text-primary #ffffff Headings, key labels
--text-secondary #f5f5f5 Body copy
--text-muted #e5e5e5 Meta, timestamps, captions

Accent + borders

Token Value Use
--accent #3b82f6 Primary buttons, focus, links
--accent-hover #2563eb Hover on --accent
--accent-subtle rgba(59,130,246,0.2) Active nav background
--border rgba(255,255,255,0.3) Card / input outline
--border-focus rgba(59,130,246,0.7) Card hover, input focus

Semantic state

Token Value Meaning
--success #22c55e OK, saved, healthy
--warning #eab308 Caution, soft fail
--error #ef4444 Hard fail, destructive

Status palette (workflow states)

These drive the .status-pill[data-status] system on workspace cards.

Token Value State name
--status-running rgb(34, 197, 94) running-ralph, running-manual
--status-blocked rgb(245, 158, 11) blocked
--status-failed rgb(239, 68, 68) failed
--status-complete rgb(156, 163, 175) complete
--status-idle var(--text-muted) idle
--status-planning rgb(167, 139, 250) planning (new)
--status-pending rgb(96, 165, 250) pending (new)
--status-approved rgb(234, 179, 8) approved (new)

Typography

  • Sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif — self-hosted woff2 at /echo/static/fonts/inter-{400,500,600,700}.woff2.
  • Mono: 'JetBrains Mono', 'Fira Code', ui-monospace, monospace — for logs, code blocks, slugs, IDs. Loaded by browser if present (not bundled).

Size scale

Token rem px @ 16px
--text-xs 0.75 12
--text-sm 0.875 14
--text-base 1 16
--text-lg 1.125 18
--text-xl 1.25 20

Weights

400 (body), 500 (medium emphasis), 600 (headings, button labels), 700 (rare — page titles only). No 800/900.


Spacing — 8px grid

All padding, margin, and gap values use these tokens. No hard-coded pixels.

Token px
--space-1 4
--space-2 8
--space-3 12
--space-4 16
--space-5 20
--space-6 24
--space-8 32
--space-10 40

Border radius

Token px Use
--radius-sm 4 Tags, micro-pills
--radius-md 8 Buttons, inputs
--radius-lg 12 Cards, modals, panels
--radius-full 9999 Status pills, badges, avatars

Buttons

All buttons share .btn (8px radius, 14px font, 8/16 padding, --transition-fast).

Variant Class Surface Text Use
Primary .btn-primary --accent white The one CTA per row
Secondary .btn-secondary --bg-surface + --border --text-secondary Side actions
Ghost .btn-ghost transparent --text-secondary Tertiary, destructive-soft
Danger .btn-danger --error white Stop, delete, irreversible

Disabled state: opacity: 0.5; cursor: not-allowed;. Never grey out by swapping colors — keep variant identity.


Card component (.project-card)

  • border-radius: var(--radius-lg) (12px)
  • background: var(--bg-surface)
  • border: 1px solid var(--border)
  • padding: var(--space-5)
  • transition: border-color var(--transition-base)
  • Hover: border-color: var(--border-focus) (blue glow). No surface brightening — border-only hover keeps the grid calm.

Status pill system

A .status-pill is a --radius-full chip placed on every project card. It encodes the current workflow state via data-status="<state>".

Visual recipe

  • Background: state color at 18% alpha (color-mix(in srgb, var(--status-X) 18%, transparent) or precomputed rgba(...)).
  • Text: solid state color (full alpha).
  • Border: 1px state color at 30% alpha.
  • Padding: var(--space-1) var(--space-3) — slim.
  • Font: var(--text-xs), weight 500.

Pulse-dot

Active states render a 6px CSS-shape circle that pulses (no SVG, no emoji).

.status-pill::before {
  content: ""; width: 6px; height: 6px; border-radius: 50%;
  background: currentColor; margin-right: var(--space-2);
}
.status-pill[data-status="running-ralph"]::before,
.status-pill[data-status="running-manual"]::before,
.status-pill[data-status="planning"]::before {
  animation: pulse-dot 1.6s ease-in-out infinite;
}
@keyframes pulse-dot { 0%,100% { opacity: 1; } 50% { opacity: 0.35; } }

State matrix

data-status Color token Pulse Label
running-ralph --status-running yes Ralph running
running-manual --status-running yes Manual run
planning --status-planning yes Planning
approved --status-approved no Approved
pending --status-pending no Pending
blocked --status-blocked no Blocked
failed --status-failed no Failed
complete --status-complete no Complete
idle --status-idle no Idle

BUTTONS_FOR_STATE matrix

Each project card surfaces ≤3 actions, ordered Primary / Secondary / Ghost. The renderer picks the row matching data-status.

State Primary Secondary Ghost
running-ralph Stop Ralph (danger) Logs PRD
running-manual Stop (danger) Open server Logs
planning Continue chat Cancel
approved Unapprove Plan
pending Approve Plan with Echo Cancel
blocked View logs Resume
failed View logs Retry Rollback
complete View plan Run again
idle Run Ralph Delete

Rules:

  • Stop / Delete are always .btn-danger, never primary blue.
  • A dash () means render nothing (no placeholder, no greyed-out slot).
  • The Primary slot is the default action when the card is keyboard-focused and Enter is pressed.

Toast taxonomy

Toasts appear top-right, stack vertically, dismiss after 4s (errors: 8s). Five types, distinguished by a 3px colored left bar — no emoji, no icon fill. Body uses --text-primary on --bg-surface.

Type Bar color Use
success --success Saved, approved, deployed
info --accent Neutral confirmation
warning --warning Soft fail, retried
busy --status-planning Long-running op started
error --error Hard fail, action required

Toast renderer is shared across pages and reads from a single global window.showToast(type, msg) helper.


SSE indicator

Top-right of pages with a live stream (workspace, ralph). Three states indicated via a CSS-shape pulse-dot — never an emoji.

State Dot color Label Pulse
Live --success "Live" yes
Polling --warning "Polling" no
Offline --error "Offline" no

Uses the same .pulse-dot 6px CSS shape as .status-pill::before. The dot sits before the label, both inside a tiny .sse-indicator chip on --bg-surface.


Modal pattern

Used for the planning chat, PRD viewer, log tail, propose-feature form.

  • Overlay: full viewport, background: rgba(0,0,0,0.6), backdrop-filter: blur(4px), display: flex centered.
  • Container (.modal): --radius-lg, --bg-base, --border, max-width 720px, max-height 80vh, scroll on overflow.
  • Header / Footer: 1px border separators using --border.
  • Focus trap: first focusable element gets focus on open; Tab cycles inside the modal.
  • ESC: closes — but if the modal has unsaved input, prompt "Discard changes?" before closing. Click on overlay = same behavior.
  • Mobile (≤640px): full-screen takeover. Header / footer stick; body scrolls. Implemented in tokens.css via the shared @media (max-width:640px) block.

No-emoji rule

No emoji anywhere in the dashboard. This is a hard rule, not a preference.

  • Buttons are text-only. No leading/trailing emoji decoration.
  • Status indicators use CSS-shape colored dots (.pulse-dot, .status-pill::before) — never 🟢 ⏱ 🛑 ✅ etc.
  • The login monogram is the letter E rendered in Inter 700 inside a square with --accent background. Not an emoji, not an SVG logo.
  • Where icons are needed (nav, action buttons), use Lucide-style stroke SVGs inlinedstroke: currentColor, fill: none, stroke-width: 2, stroke-linecap: round, stroke-linejoin: round. Never use emoji as a substitute for an icon.

This rule keeps the UI legible across themes, scales correctly at all sizes, and avoids OS-dependent rendering (Apple, Twemoji, Noto all draw the same emoji differently).


Pages that include this system

Every dashboard page (index.html, workspace.html, ralph.html, notes.html, habits.html, files.html, login.html) must include in <head>:

<link rel="stylesheet" href="/echo/static/tokens.css">
<link rel="stylesheet" href="/echo/common.css">

In that order — tokens first so common.css and per-page styles can resolve the variables.