Skip to content

Latest commit

 

History

History
213 lines (166 loc) · 9.41 KB

File metadata and controls

213 lines (166 loc) · 9.41 KB

Person D — Frontend Dashboard

Branch: feat/frontend

Goal

Make this demo-able. Judges won't read code — they'll watch the dashboard. Make the agentic loop visible: questions stream in, builders complete, games play out on real boards, the Elo curve climbs.

Scope

Files owned:

frontend/
├── package.json                    # done
├── vite.config.ts                  # done — proxies /api and /ws to backend
├── tsconfig.json                   # done
├── tailwind.config.js              # done
├── postcss.config.js               # done
├── index.html                      # done
└── src/
    ├── main.tsx                    # done — imports index.css
    ├── index.css                   # done — tailwind base + dark body
    ├── App.tsx                     # IMPLEMENT — top-level layout
    ├── api/
    │   ├── events.ts               # FROZEN — do not edit
    │   └── client.ts               # IMPLEMENT — extend the stub
    ├── components/
    │   ├── LiveBoard.tsx           # CREATE
    │   ├── EloChart.tsx            # CREATE
    │   ├── Bracket.tsx             # CREATE
    │   ├── StrategistFeed.tsx      # CREATE
    │   └── GenerationTimeline.tsx  # CREATE
    └── hooks/
        ├── useEventStream.ts       # CREATE
        └── mockEvents.ts           # CREATE — for offline dev

Read first:

  1. docs/proposal.pdf §11 — the demo plan, your North Star.
  2. plans/README.md — merge order.
  3. frontend/src/api/events.ts — the frozen event types you consume. Do not edit this file without team sync.
  4. backend/darwin/api/websocket.py — the Python source of truth that mirrors events.ts.

Already done for you:

  • Vite + React + TypeScript skeleton with HMR working.
  • Tailwind preconfigured with dark mode and a dark body background.
  • vite.config.ts proxies /api (REST) and /ws (WebSocket) to 127.0.0.1:8000.
  • events.ts has all WS event types — import from here, don't redefine.
  • client.ts has connectEvents(onEvent) to start with.

Frozen contracts touched

  • WebSocket events (frontend mirror)frontend/src/api/events.ts. Consumed; do not modify without team sync.

Deliverables

Step 1 — Branch, install, run dev server

git checkout -b feat/frontend
cd frontend && npm install
npm run dev   # opens http://localhost:5173

You should see the placeholder <h1>Darwin</h1> page on a dark background.

Step 2 — Build a mock event stream

Create src/hooks/mockEvents.ts. This is what unblocks you from the backend entirely:

import type { DarwinEvent } from "../api/events";

export function startMockStream(onEvent: (e: DarwinEvent) => void): () => void {
  const seq: { delay: number; event: DarwinEvent }[] = [
    { delay: 0,    event: { type: "generation.started", number: 1, champion: "baseline-v0" } },
    { delay: 800,  event: { type: "strategist.question", index: 0, category: "book", text: "Would adding a 6-move opening book reduce early blunders?" } },
    { delay: 800,  event: { type: "strategist.question", index: 1, category: "prompt", text: "Would prompting for threat detection first improve tactics?" } },
    { delay: 800,  event: { type: "strategist.question", index: 2, category: "search", text: "Would 1-ply lookahead with LLM eval beat raw selection?" } },
    { delay: 800,  event: { type: "strategist.question", index: 3, category: "evaluation", text: "Would scoring positions before moving improve endgames?" } },
    { delay: 800,  event: { type: "strategist.question", index: 4, category: "sampling", text: "Would majority vote across 3 samples reduce variance?" } },
    { delay: 2000, event: { type: "builder.completed", question_index: 0, engine_name: "gen1-book-a3f", ok: true,  error: null } },
    { delay: 500,  event: { type: "builder.completed", question_index: 1, engine_name: "gen1-prompt-b1d", ok: true,  error: null } },
    { delay: 500,  event: { type: "builder.completed", question_index: 2, engine_name: "gen1-search-c2e", ok: false, error: "smoke game timeout" } },
    // ... add a few game.move and game.finished events
    { delay: 8000, event: { type: "generation.finished", number: 1, new_champion: "gen1-book-a3f", elo_delta: 28.5, promoted: true } },
  ];
  const timers: number[] = [];
  let t = 0;
  for (const { delay, event } of seq) {
    t += delay;
    timers.push(window.setTimeout(() => onEvent(event), t));
  }
  return () => timers.forEach(clearTimeout);
}

Step 3 — useEventStream hook

src/hooks/useEventStream.ts:

import { useEffect, useState } from "react";
import { connectEvents } from "../api/client";
import { startMockStream } from "./mockEvents";
import type { DarwinEvent } from "../api/events";

export function useEventStream(): DarwinEvent[] {
  const [events, setEvents] = useState<DarwinEvent[]>([]);
  useEffect(() => {
    const useMock = new URLSearchParams(location.search).has("mock");
    const push = (e: DarwinEvent) => setEvents((prev) => [...prev, e]);
    if (useMock) return startMockStream(push);
    const ws = connectEvents((e) => push(e));
    return () => ws.close();
  }, []);
  return events;
}

Use ?mock=1 in the URL to develop offline.

Step 4 — Components, in order of demo importance

Build these one at a time and drop them into App.tsx as you go. Use useEventStream() and filter the array client-side — don't add complex state management.

4a. StrategistFeed.tsx — highest demo value. Renders one card per strategist.question event with a category-color badge and a status indicator that flips on the matching builder.completed event.

4b. EloChart.tsx — Recharts LineChart. Build an Elo history from generation.finished events: [{gen: 0, elo: 1500}, {gen: 1, elo: 1528}, ...]. Animated entry on each new point.

4c. LiveBoard.tsx — wraps react-chessboard. Find the most recent game.move, render its FEN. Show White/Black names and a "thinking..." indicator after 2s of no new move event for that game.

4d. Bracket.tsx — for the active generation, a 6×6 grid of pairings. Cells fill in W/L/D as game.finished events arrive. Champion column highlighted. Final column = total points.

4e. GenerationTimeline.tsx — full history. Each row: gen number, 5 questions, winner, Elo delta, promoted/persisted badge.

Step 5 — App.tsx layout

Tailwind grid:

import { useEventStream } from "./hooks/useEventStream";
import LiveBoard from "./components/LiveBoard";
import EloChart from "./components/EloChart";
import StrategistFeed from "./components/StrategistFeed";
import Bracket from "./components/Bracket";
import GenerationTimeline from "./components/GenerationTimeline";

export default function App() {
  const events = useEventStream();
  return (
    <div className="min-h-screen p-6">
      <header className="flex items-center justify-between mb-6">
        <h1 className="text-2xl font-bold">DARWIN</h1>
        <button
          onClick={() => fetch("/api/generations/run", { method: "POST" })}
          className="px-4 py-2 bg-blue-600 rounded hover:bg-blue-500"
        >
          Run Generation
        </button>
      </header>
      <div className="grid grid-cols-3 gap-6 mb-6">
        <LiveBoard events={events} />
        <StrategistFeed events={events} />
        <EloChart events={events} />
      </div>
      <div className="grid grid-cols-2 gap-6 mb-6">
        <Bracket events={events} />
        <GenerationTimeline events={events} />
      </div>
    </div>
  );
}

Step 6 — Switch to live backend (when E ships)

When Person E pings you that the WS is up: drop the ?mock=1 from the URL. If anything breaks, the gap is in events.ts vs websocket.py — page E.

Step 7 — Open the PR

git add -A && git commit -m "feat: dashboard with live board, elo chart, strategist feed, bracket"
git push -u origin feat/frontend
gh pr create --title "Frontend dashboard" --body "Closes plan D."

Integration points

  • Person E runs the backend you connect to. Coordinate only on the WS URL — event shapes are frozen.
  • Person C's questions appear verbatim in your strategist feed. If anything is illegible, ping them.
  • Person B's play_game events drive LiveBoard — the fen field in game.move is your source of truth.

Acceptance criteria

  • Step 5 layout renders end-to-end with ?mock=1.
  • Real WS events drive the dashboard once E ships.
  • All 5 components render meaningfully during a real generation.
  • One-click "Run Generation" button hitting POST /api/generations/run.
  • PR opened, then merged after review.

Risks & mitigations

  • Don't edit events.ts without paging Person E — it must stay aligned with the backend.
  • Volume. A real generation may emit ~2400 game.move events. Don't render every single one. LiveBoard should track only the most recently active game; drop the rest into a counter.
  • Animations matter. The Elo line climbing is the demo's hero shot — make sure it animates on each new point, doesn't snap.
  • Pre-record a backup video before demo day. The Procfile + Vite + WS pipeline can flake live. A 30-second screen recording is your insurance policy.

Status

Merged. Note: post-hackathon the single LiveBoard was generalized to a LiveBoards grid with per-game termination badges (see followup-4-frontend-polish.md).