Improves how reviewer gates render their content, and adds two small
hooks that make the frontend cooperate with the standalone replay server
(also useful as generic primitives).
Gate body rendering:
- IntroBody: gate intros now render paragraphs plus real bullet lists
with a hanging indent and emphasised bullet lead term, filling the
card width (no narrow measure cap). Backed by introFormat (parser) +
IntroBody (renderer), with tests.
- GateArtifactBody: artifact-review gates whose artifact is a bare file
reference (e.g. {kind:"phase", data:{file:"question.md"}}) now fetch
the referenced session document and render its full content, instead
of showing the file reference as text. Falls back to ArtifactView for
inline-content artifacts and unmapped files. Backed by gateArtifact
(resolver) with tests. ArtifactGateWidget passes sessionId through.
- SelectWidget renders intros via IntroBody.
Replay-friendly session exit:
- AppShell: a system_event {event:"session_exit"} now triggers
stopSession(), returning to the landing view. Generic primitive (the
real backend can emit it too); used by the replay server's "Esci".
- SessionMenu: the ⋮ trigger is now always visible (was opacity-0 /
group-hover only), so Resume is reachable without hovering.
Verified offline against the replay server (no VPN needed); tsc clean.
43 lines
1.3 KiB
TypeScript
43 lines
1.3 KiB
TypeScript
import { parseIntroBlocks, splitLead } from "./introFormat";
|
|
|
|
/**
|
|
* Renders the body of a gate widget: paragraphs plus real bullet lists with a
|
|
* hanging indent, and the leading term of each bullet emphasised. Fills the
|
|
* card width (no narrow measure cap) so the text runs close to the border.
|
|
*/
|
|
export function IntroBody({ text }: { text: string }) {
|
|
const blocks = parseIntroBlocks(text);
|
|
return (
|
|
<div className="space-y-2.5 text-sm leading-relaxed text-muted-foreground">
|
|
{blocks.map((b, i) =>
|
|
b.kind === "para" ? (
|
|
<p key={i}>{b.text}</p>
|
|
) : (
|
|
<ul
|
|
key={i}
|
|
className="list-disc space-y-1.5 pl-5 marker:text-muted-foreground/50"
|
|
>
|
|
{b.items.map((item, j) => {
|
|
const lead = splitLead(item);
|
|
return (
|
|
<li key={j} className="pl-1">
|
|
{lead ? (
|
|
<>
|
|
<strong className="font-semibold text-foreground">
|
|
{lead.lead}
|
|
</strong>
|
|
{lead.rest}
|
|
</>
|
|
) : (
|
|
item
|
|
)}
|
|
</li>
|
|
);
|
|
})}
|
|
</ul>
|
|
)
|
|
)}
|
|
</div>
|
|
);
|
|
}
|