fix(pdf): render the pdf dispatch card (live browser-smoke gap)
The browser e2e check on the live dispatch session found the tool-pdf part rendered NO card: the artifact renderer registry had no pdf entry (the doc comment literally says "add your renderer here"), and the typed TeacherUITools union + the chat.tsx job-part enumerations (poll cadence, dispatch nudge, settle-sync) all missed the new tool kind. - PdfJobCard in job-card.tsx: the research JobCard idiom, kind pdf — registered in artifacts/index.ts. - ui-message.ts: pdf entry in TeacherUITools (fire-and-forget ack output). - chat.tsx: two mechanical "tool-pdf" additions to the research/demo/visual enumerations — UI-stream file, flagged for review at merge (same treatment as the activity-dock type-forced entries). - demos.test.tsx: live/settled dispatch-card renders + registry pin.
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
import type { ComponentType } from "react";
|
import type { ComponentType } from "react";
|
||||||
import type { ArtifactPartProps } from "@/src/core";
|
import type { ArtifactPartProps } from "@/src/core";
|
||||||
import { DemoCard } from "@/components/artifacts/demo-card";
|
import { DemoCard } from "@/components/artifacts/demo-card";
|
||||||
import { JobCard } from "@/components/artifacts/job-card";
|
import { JobCard, PdfJobCard } from "@/components/artifacts/job-card";
|
||||||
import { VisualCard } from "@/components/artifacts/visual-card";
|
import { VisualCard } from "@/components/artifacts/visual-card";
|
||||||
import { ProbeDoneCard } from "@/components/artifacts/probe-done-card";
|
import { ProbeDoneCard } from "@/components/artifacts/probe-done-card";
|
||||||
|
|
||||||
@@ -19,6 +19,7 @@ import { ProbeDoneCard } from "@/components/artifacts/probe-done-card";
|
|||||||
*/
|
*/
|
||||||
export const artifactRenderers: Record<string, ComponentType<ArtifactPartProps>> = {
|
export const artifactRenderers: Record<string, ComponentType<ArtifactPartProps>> = {
|
||||||
research: JobCard,
|
research: JobCard,
|
||||||
|
pdf: PdfJobCard, // sources v0 — live smoke 2026-09-19 caught the gap: without this the tool-pdf dispatch part rendered nothing
|
||||||
// plan_* parts render nothing inline by design: the phase seam is a
|
// plan_* parts render nothing inline by design: the phase seam is a
|
||||||
// message-level boundary, drawn by PhaseDivider in chat.tsx (the old
|
// message-level boundary, drawn by PhaseDivider in chat.tsx (the old
|
||||||
// part-level plan_init divider retired with it).
|
// part-level plan_init divider retired with it).
|
||||||
|
|||||||
@@ -18,6 +18,17 @@ export function JobCard({ part }: ArtifactPartProps) {
|
|||||||
return <DispatchCard toolCallId={part.toolCallId} kind="research" />;
|
return <DispatchCard toolCallId={part.toolCallId} kind="research" />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The pdf dispatch box (sources v0) — the same async-protocol idiom as
|
||||||
|
* JobCard: the ack output drives a DispatchCard (live "PDF reader" status
|
||||||
|
* via the central jobs poll); the brief itself rides the steer message
|
||||||
|
* and renders under the PDF-complete badge in the transcript.
|
||||||
|
*/
|
||||||
|
export function PdfJobCard({ part }: ArtifactPartProps) {
|
||||||
|
if (part.state !== "output-available") return null;
|
||||||
|
return <DispatchCard toolCallId={part.toolCallId} kind="pdf" />;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The PDF reading brief (sources v0) — markdown body + the consulted-pages
|
* The PDF reading brief (sources v0) — markdown body + the consulted-pages
|
||||||
* line. Exported: the steer card renders it under the PDF-complete badge,
|
* line. Exported: the steer card renders it under the PDF-complete badge,
|
||||||
|
|||||||
+12
-2
@@ -549,7 +549,14 @@ export function Chat({
|
|||||||
(n, m) =>
|
(n, m) =>
|
||||||
n +
|
n +
|
||||||
m.parts.filter(
|
m.parts.filter(
|
||||||
(p) => p.type === "tool-research" || p.type === "tool-demo" || p.type === "tool-visual",
|
// sources v0 — "tool-pdf" added to the job-part enumerations
|
||||||
|
// (mechanical, UI-stream review at merge): poll cadence +
|
||||||
|
// dispatch nudge + settle-sync all key on these parts.
|
||||||
|
(p) =>
|
||||||
|
p.type === "tool-research" ||
|
||||||
|
p.type === "tool-demo" ||
|
||||||
|
p.type === "tool-visual" ||
|
||||||
|
p.type === "tool-pdf",
|
||||||
).length,
|
).length,
|
||||||
0,
|
0,
|
||||||
),
|
),
|
||||||
@@ -576,7 +583,10 @@ export function Chat({
|
|||||||
for (const m of messages) {
|
for (const m of messages) {
|
||||||
for (const p of m.parts) {
|
for (const p of m.parts) {
|
||||||
if (
|
if (
|
||||||
(p.type === "tool-research" || p.type === "tool-demo" || p.type === "tool-visual") &&
|
(p.type === "tool-research" ||
|
||||||
|
p.type === "tool-demo" ||
|
||||||
|
p.type === "tool-visual" ||
|
||||||
|
p.type === "tool-pdf") && // sources v0 — see the poll-cadence enumeration above
|
||||||
p.toolCallId &&
|
p.toolCallId &&
|
||||||
p.state !== "input-streaming" &&
|
p.state !== "input-streaming" &&
|
||||||
p.state !== "output-error"
|
p.state !== "output-error"
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import type { UIMessage } from "ai";
|
|||||||
import type {
|
import type {
|
||||||
DemoToolInput,
|
DemoToolInput,
|
||||||
GraphContextData,
|
GraphContextData,
|
||||||
|
PdfToolInput,
|
||||||
PlanVerdictData,
|
PlanVerdictData,
|
||||||
PlanBlockData,
|
PlanBlockData,
|
||||||
ProbeDoneInput,
|
ProbeDoneInput,
|
||||||
@@ -34,6 +35,9 @@ export type TeacherUITools = {
|
|||||||
research: { input: ResearchToolInput; output: string };
|
research: { input: ResearchToolInput; output: string };
|
||||||
demo: { input: DemoToolInput; output: string };
|
demo: { input: DemoToolInput; output: string };
|
||||||
visual: { input: VisualToolInput; output: string };
|
visual: { input: VisualToolInput; output: string };
|
||||||
|
/** The pdf dispatch tool (sources v0): fire-and-forget ack output like
|
||||||
|
* research — the brief arrives later as a steer message. */
|
||||||
|
pdf: { input: PdfToolInput; output: string };
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TeacherUIDataParts = {
|
export type TeacherUIDataParts = {
|
||||||
|
|||||||
+45
-1
@@ -40,7 +40,7 @@ import {
|
|||||||
import { sanitizeSvg, VisualCard } from "../components/artifacts/visual-card.tsx";
|
import { sanitizeSvg, VisualCard } from "../components/artifacts/visual-card.tsx";
|
||||||
import { SubagentSteerCard } from "../components/artifacts/steer-card.tsx";
|
import { SubagentSteerCard } from "../components/artifacts/steer-card.tsx";
|
||||||
import { DispatchCard } from "../components/artifacts/dispatch-card.tsx";
|
import { DispatchCard } from "../components/artifacts/dispatch-card.tsx";
|
||||||
import { JobCard } from "../components/artifacts/job-card.tsx";
|
import { JobCard, PdfJobCard } from "../components/artifacts/job-card.tsx";
|
||||||
import { JobsContext } from "../components/jobs-context.ts";
|
import { JobsContext } from "../components/jobs-context.ts";
|
||||||
import { demoInputSchema, jobTools, pdfInputSchema, visualInputSchema } from "../src/agent/tools/jobs.ts";
|
import { demoInputSchema, jobTools, pdfInputSchema, visualInputSchema } from "../src/agent/tools/jobs.ts";
|
||||||
import { runDemoBuilder } from "../src/agent/subagents/demo-builder.ts";
|
import { runDemoBuilder } from "../src/agent/subagents/demo-builder.ts";
|
||||||
@@ -289,6 +289,48 @@ describe("dispatch card rendering (the tool-call ack box)", () => {
|
|||||||
);
|
);
|
||||||
assert.equal(html, "");
|
assert.equal(html, "");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// sources v0 — the live browser smoke (2026-09-19) caught the pdf dispatch
|
||||||
|
// part rendering nothing: PdfJobCard is the fix.
|
||||||
|
it("pdf dispatch renders the live PDF-reader card while the job runs", () => {
|
||||||
|
const pdfJob: JobRecord = ({
|
||||||
|
id: "jp1",
|
||||||
|
kind: "pdf",
|
||||||
|
chatId: "c",
|
||||||
|
toolCallId: "call-1",
|
||||||
|
status: "running",
|
||||||
|
activity: 'pdf_render: 42-44 @150dpi',
|
||||||
|
createdAt: new Date(Date.now() - 5000).toISOString(),
|
||||||
|
updatedAt: new Date().toISOString(),
|
||||||
|
}) as JobRecord;
|
||||||
|
const html = renderToStaticMarkup(
|
||||||
|
<JobsContext.Provider value={[pdfJob]}>
|
||||||
|
<PdfJobCard part={ackPart as never} />
|
||||||
|
</JobsContext.Provider>,
|
||||||
|
);
|
||||||
|
assert.match(html, /PDF reader/);
|
||||||
|
assert.match(html, /pdf_render/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("pdf dispatch settles to the delivered one-liner", () => {
|
||||||
|
const pdfJob: JobRecord = ({
|
||||||
|
id: "jp1",
|
||||||
|
kind: "pdf",
|
||||||
|
chatId: "c",
|
||||||
|
toolCallId: "call-1",
|
||||||
|
status: "done",
|
||||||
|
activity: "delivered",
|
||||||
|
createdAt: new Date(Date.now() - 5000).toISOString(),
|
||||||
|
updatedAt: new Date().toISOString(),
|
||||||
|
}) as JobRecord;
|
||||||
|
const html = renderToStaticMarkup(
|
||||||
|
<JobsContext.Provider value={[pdfJob]}>
|
||||||
|
<PdfJobCard part={ackPart as never} />
|
||||||
|
</JobsContext.Provider>,
|
||||||
|
);
|
||||||
|
assert.match(html, /PDF reader/);
|
||||||
|
assert.match(html, /delivered/);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("steer card rendering (the delivered artifact)", () => {
|
describe("steer card rendering (the delivered artifact)", () => {
|
||||||
@@ -802,5 +844,7 @@ describe("tool + renderer registry wiring", () => {
|
|||||||
it("the artifact renderer registry maps demo/visual to the W6 cards", () => {
|
it("the artifact renderer registry maps demo/visual to the W6 cards", () => {
|
||||||
assert.equal(artifactRenderers.demo, DemoCard);
|
assert.equal(artifactRenderers.demo, DemoCard);
|
||||||
assert.equal(artifactRenderers.visual, VisualCard);
|
assert.equal(artifactRenderers.visual, VisualCard);
|
||||||
|
// sources v0 — pdf maps to its dispatch card (the live-smoke gap)
|
||||||
|
assert.equal(artifactRenderers.pdf, PdfJobCard);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user