Files
learnharness/web/src/views/Home.tsx
T
Amos e127a9bfbd fix: assistant text rendering, toolResult plumbing leak; add render smoke tests
- Markdown.tsx never passed text as children to ReactMarkdown (valid JSX,
  compiled clean, rendered nothing) — the 'thinking shows, text doesn't' bug
- toolResult entries (quiz/ask dispatch instructions for the LLM) leaked
  into the transcript as visible text; now never rendered
- render smoke tests (renderToStaticMarkup, no DOM): pin markdown content,
  KaTeX, button-based quiz options, ask answers not graded, toolResult and
  [Quiz answer] hiding — negative-tested against the actual bugs
- React default import in all .tsx: tsx test runner uses classic JSX
  transform (ignores tsconfig jsx option), Vite uses automatic runtime;
  explicit import works under both
- katex css moved to main.tsx (tests can't import css)
- verified live in browser: all assistant messages render, ask card has no
  grading marks, pending quiz re-arms on resume
2026-08-22 01:20:14 +03:00

86 lines
2.2 KiB
TypeScript

import React, { useState } from "react";
import type { SessionSummary } from "../../../src/shared/protocol.ts";
import { GraphPlaceholder } from "../components/GraphPlaceholder.tsx";
interface HomeProps {
sessions: SessionSummary[];
onStart: (topic: string) => Promise<void>;
onOpen: (file: string) => void;
onRefresh: () => void;
error: string | null;
}
function formatDate(iso: string): string {
if (!iso) return "";
try {
return new Date(iso).toLocaleString(undefined, {
month: "short",
day: "numeric",
hour: "2-digit",
minute: "2-digit",
});
} catch {
return iso;
}
}
export function Home({ sessions, onStart, onOpen, onRefresh, error }: HomeProps) {
const [topic, setTopic] = useState("");
const [busy, setBusy] = useState(false);
const submit = async () => {
const t = topic.trim();
if (!t || busy) return;
setBusy(true);
try {
await onStart(t);
} finally {
setBusy(false);
setTopic("");
}
};
return (
<div className="home">
<aside className="sidebar">
<div className="sidebar-head">
<span>Sessions</span>
<button className="icon-btn" title="Refresh" onClick={onRefresh}>
↻
</button>
</div>
<div className="session-list">
{sessions.length === 0 && <div className="empty">No sessions yet</div>}
{sessions.map((s) => (
<button key={s.id} className="session-item" onClick={() => onOpen(s.file)}>
<div className="session-name">{s.name || s.preview || "Untitled"}</div>
<div className="session-meta">
{formatDate(s.updatedAt)} · {s.messageCount} msg{s.messageCount === 1 ? "" : "s"}
</div>
</button>
))}
</div>
</aside>
<main className="home-main">
<GraphPlaceholder />
<div className="new-session">
<input
type="text"
placeholder="What do you want to learn?"
value={topic}
disabled={busy}
onChange={(e) => setTopic(e.target.value)}
onKeyDown={(e) => {
if (e.key === "Enter") void submit();
}}
/>
<button onClick={() => void submit()} disabled={busy || !topic.trim()}>
{busy ? "Starting…" : "Start"}
</button>
</div>
{error && <div className="error-banner">{error}</div>}
</main>
</div>
);
}