feat(frontend): v2 rewrite — Vite + React + TS readonly SPA

Replaces the v1 CRA app (which targeted the obsolete Python Dialectic
backend) with a fresh Vite + React 18 + TypeScript scaffold that talks
to Dialectic.Backend Go v2.

Pages (all readonly — propose/signup/post are agent-only by design):
  - /                       TopicList — filter by status, paginated
  - /topics/:id             TopicDetail — meta + camps + transcript
                            (polling every 8s)
  - /topics/:id/verdict     Verdict permalink (shareable)
  - /agents/:id             AgentActivity — admin diagnostics card

Stack:
  - Vite 5 + React 18 + react-router-dom 6
  - Pure ESM, NodeNext-style imports, .tsx
  - Style: ~/STYLE.md tokens (IBM Plex Mono + Major Mono Display +
    --acid #d8ff3e on --ink #080a0d, with subtle blueprint grid wash)

Auth:
  - v1 dev-bypass only — VITE_OIDC_DEV_BYPASS auto-attaches
    x-dev-bypass header. Real Keycloak OIDC redirect ships as v2.
  - Admin endpoints (x-dialectic-admin-key) prompt on first visit
    and store key in localStorage. Never baked into bundle. Never
    sent to non-admin endpoints.

Backend pairing:
  - Dialectic.Backend@0b16b52 adds GET /api/admin/agents/{id} for the
    AgentActivity page. AgentActivity calls it via the admin-key
    branch in api.ts.

Deploy:
  - Multi-stage Dockerfile (node:22-alpine build → nginx:1.27-alpine
    serve). nginx.conf reverse-proxies /api/ → dialectic-backend:8090
    so the browser sees one origin (no CORS).

Reuses the existing hzhang/Dialectic.Frontend repo — old CRA contents
nuked in this commit. History preserved on master.
This commit is contained in:
h z
2026-05-24 00:15:35 +01:00
parent f7c4ed9e3b
commit 3dbb5abaf6
38 changed files with 3497 additions and 2826 deletions

85
src/types.ts Normal file
View File

@@ -0,0 +1,85 @@
// Mirrors the Dialectic.Backend models — kept in sync by hand. If a
// field is added on the backend (models/topic.go / store responses),
// also add it here so the UI can use it.
export type TopicStatus =
| 'created'
| 'signup_open'
| 'signup_closed'
| 'debating'
| 'completed'
| 'cancelled';
export type Visibility = 'public' | 'private';
export type Camp = 'pro' | 'con' | 'judge';
export interface Topic {
id: string;
title: string;
summary: string;
visibility: Visibility;
verdict_schema_id: string;
status: TopicStatus;
signup_open_at: string;
signup_close_at: string;
debate_start_at: string;
debate_end_at: string;
creator_user_id: string;
visibility_changed_by?: string | null;
visibility_changed_at?: string | null;
cancelled_reason?: string | null;
created_at: string;
updated_at: string;
}
export interface CampAllocation {
id: string;
topic_id: string;
camp: Camp;
agent_id: string;
allocated_at: string;
}
// GET /api/topics/{id} returns the full Topic spread + `camps` sibling.
export interface TopicDetail extends Topic {
camps: CampAllocation[] | null;
}
export interface Argument {
id: string;
topic_id: string;
round_id: string;
camp: Camp;
agent_id: string;
content: string;
posted_at: string;
}
export interface Verdict {
id: string;
topic_id: string;
judge_agent_id: string;
// verdict shape depends on the topic's verdict_schema_id; UI shows raw JSON.
verdict: Record<string, unknown>;
rationale: string;
tokens_input: number;
tokens_output: number;
produced_at: string;
}
// Admin endpoint (next commit) shape — declare now so frontend compiles.
export interface AgentSummary {
agent_id: string;
key_provisioned: boolean;
signups_count: number;
arguments_count: number;
verdicts_count: number;
recent_topics: Array<{
topic_id: string;
title: string;
status: TopicStatus;
role: Camp | 'volunteer';
last_action_at: string;
}>;
}