v2 web interface: fix delivery races, real URLs, optimistic echo, thinking stream, button-based quiz UI

Root causes of the v1 browser bugs (all fixed + covered by deterministic tests):
- duplicated messages: per-socket snapshot + global broadcast marking raced;
  replaced with a single append-only entry log (exactly-once per socket,
  structurally) — covered by tests/server.test.ts
- delayed/missing echo of own messages: optimistic append with sending/failed
  status, reconciled by text when the real entry arrives
- no thinking shown: thinking deltas streamed live (kind: thinking) and
  persisted in assistant entries so they survive reloads
- ask answers graded as wrong: AnsweredCard no longer applies quiz grading to
  ask answers (no ✓/✗, no verdict)
- broken option selection: native radio/checkbox inputs fighting React state
  replaced with button-based options (single source of truth, one-click select)
- no URLs: History API routing — / home, /s/<file> per session (deep links,
  refresh, back button); session view resumes its file on mount
- lost connection = dead UI: exponential-backoff reconnect with full resync
- stick-to-bottom transcript with jump-to-latest pill

Server: startServer() with injectable runtime factory (scripted model in
tests); createSession returns file for routing; scripted model gained thinking
turns. 51 deterministic tests green.
This commit is contained in:
Amos
2026-08-22 00:57:16 +03:00
parent 25063b2a3e
commit 296cc1b7b6
14 changed files with 1485 additions and 441 deletions
+3 -2
View File
@@ -16,7 +16,7 @@ export interface ClientToolCall {
export type ClientEntry =
| { type: "user"; id: string; text: string }
| { type: "assistant"; id: string; text: string; toolCalls: ClientToolCall[] }
| { type: "assistant"; id: string; text: string; thinking?: string; toolCalls: ClientToolCall[] }
| { type: "toolResult"; id: string; toolName: string; text: string }
| { type: "question"; id: string; data: ClientQuestion }
| { type: "questionAnswered"; id: string; data: QuestionAnswerData }
@@ -36,6 +36,7 @@ export interface SessionSummary {
export interface NewSessionResponse {
id: string;
file: string;
}
// ─── WS protocol ────────────────────────────────────────────────────────────
@@ -45,7 +46,7 @@ export type ServerMessage =
| { type: "backfill"; entries: ClientEntry[] }
| { type: "entry"; entry: ClientEntry }
| { type: "pending"; question: ClientQuestion | null }
| { type: "delta"; delta: string }
| { type: "delta"; kind: "thinking" | "text"; delta: string }
| { type: "streaming"; value: boolean }
| { type: "error"; message: string };