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.
34 lines
1.5 KiB
TypeScript
34 lines
1.5 KiB
TypeScript
// A phase-completion gate sometimes arrives with an artifact that is only a file
|
|
// *reference* (e.g. `{ kind: "phase", data: { file: "question.md" } }`) instead of
|
|
// the embedded content. The full content is already served by the session-documents
|
|
// endpoint, so we resolve the referenced file to the document key that carries it.
|
|
|
|
// Filename -> session-document key (see harness build_documents). Only markdown
|
|
// documents are listed: those we can render in full from the reference alone.
|
|
export const DOC_KEY_BY_FILE: Record<string, string> = {
|
|
"question.md": "revised_question",
|
|
"validation_report.md": "validation_report",
|
|
};
|
|
|
|
type ArtifactLike = { data?: unknown; content?: unknown; [k: string]: unknown };
|
|
|
|
/**
|
|
* If the artifact is a bare file reference we can resolve, return the filename and
|
|
* the session-document key holding its content. Returns null when the artifact
|
|
* carries inline content (nothing to resolve) or points at an unmapped file.
|
|
*/
|
|
export function resolveFileArtifact(
|
|
artifact: ArtifactLike
|
|
): { filename: string; docKey: string } | null {
|
|
const payload = artifact.data ?? artifact.content;
|
|
const rec =
|
|
payload && typeof payload === "object" && !Array.isArray(payload)
|
|
? (payload as Record<string, unknown>)
|
|
: null;
|
|
const raw = rec?.file ?? rec?.path ?? artifact.file ?? artifact.path;
|
|
if (typeof raw !== "string") return null;
|
|
const filename = raw.split("/").pop() || raw;
|
|
const docKey = DOC_KEY_BY_FILE[filename];
|
|
return docKey ? { filename, docKey } : null;
|
|
}
|