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 inlined — stroke: 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.