D601-F103-V2<\/code>/u);
+ assert.match(html, /\| devicePodId \| debug \|/u);
+ assert.match(html, /`D601-F103-V2`/u);
assert.doesNotMatch(html, / {
+ const source = Bun.file(new URL("./MessageTracePanel.tsx", import.meta.url));
+ return source.text().then((text) => {
+ assert.match(text, /markdownUpgradeDelayMs\?: number/u);
+ assert.match(text, /upgradeDelayMs=\{markdownUpgradeDelayMs\}/u);
+ });
+});
+
test("message trace panel exposes running status for live trace animation", () => {
const trace: RunnerTrace = {
traceId: "trc_running_row",
diff --git a/web/hwlab-cloud-web/src/components/conversation/MessageTracePanel.tsx b/web/hwlab-cloud-web/src/components/conversation/MessageTracePanel.tsx
index 686244a1..379f3f11 100644
--- a/web/hwlab-cloud-web/src/components/conversation/MessageTracePanel.tsx
+++ b/web/hwlab-cloud-web/src/components/conversation/MessageTracePanel.tsx
@@ -11,9 +11,10 @@ interface MessageTracePanelProps {
trace: RunnerTrace;
defaultOpen?: boolean;
storageKey?: string;
+ markdownUpgradeDelayMs?: number;
}
-export function MessageTracePanel({ trace, defaultOpen, storageKey }: MessageTracePanelProps): ReactElement {
+export function MessageTracePanel({ trace, defaultOpen, storageKey, markdownUpgradeDelayMs }: MessageTracePanelProps): ReactElement {
const traceRunning = isRunningTrace(trace);
const storedOpen = readStoredOpen(storageKey, defaultOpen ?? traceRunning);
const [open, setOpen] = useState(storedOpen);
@@ -74,7 +75,7 @@ export function MessageTracePanel({ trace, defaultOpen, storageKey }: MessageTra
●
{row.header}
- {row.body ? : null}
+ {row.body ? : null}
))}
@@ -84,11 +85,11 @@ export function MessageTracePanel({ trace, defaultOpen, storageKey }: MessageTra
);
}
-function TraceRowBody({ row }: { row: TraceEventRow }): ReactElement | null {
+function TraceRowBody({ row, markdownUpgradeDelayMs }: { row: TraceEventRow; markdownUpgradeDelayMs?: number }): ReactElement | null {
if (!row.body) return null;
if (row.bodyFormat === "markdown") {
return (
-
+
{row.body}
);
diff --git a/web/hwlab-cloud-web/src/components/shared/MarkdownRenderer.tsx b/web/hwlab-cloud-web/src/components/shared/MarkdownRenderer.tsx
new file mode 100644
index 00000000..3692ba9c
--- /dev/null
+++ b/web/hwlab-cloud-web/src/components/shared/MarkdownRenderer.tsx
@@ -0,0 +1,18 @@
+import type { ReactElement } from "react";
+import ReactMarkdown from "react-markdown";
+import remarkGfm from "remark-gfm";
+
+export function MarkdownRenderer({ children }: { children: string }): ReactElement {
+ return (
+ (
+ {linkChildren}
+ )
+ }}
+ >
+ {children}
+
+ );
+}
diff --git a/web/hwlab-cloud-web/src/components/shared/MessageMarkdown.test.tsx b/web/hwlab-cloud-web/src/components/shared/MessageMarkdown.test.tsx
index c9bc05b7..33cda92e 100644
--- a/web/hwlab-cloud-web/src/components/shared/MessageMarkdown.test.tsx
+++ b/web/hwlab-cloud-web/src/components/shared/MessageMarkdown.test.tsx
@@ -2,11 +2,20 @@ import assert from "node:assert/strict";
import { test } from "bun:test";
import { renderToStaticMarkup } from "react-dom/server";
-import { MessageMarkdown } from "./MessageMarkdown";
+import { MarkdownRenderer } from "./MarkdownRenderer";
+import { MessageMarkdown, PlainMessageText } from "./MessageMarkdown";
-test("message markdown renders GFM tables and inline code through the shared third-party component", () => {
+test("message markdown module keeps the third-party renderer lazy for first screen", async () => {
+ const source = await Bun.file(new URL("./MessageMarkdown.tsx", import.meta.url)).text();
+
+ assert.doesNotMatch(source, /from "react-markdown"/u);
+ assert.match(source, /lazy\(\(\) => import\("\.\/MarkdownRenderer"\)/u);
+ assert.match(source, /upgradeDelayMs = 1800/u);
+});
+
+test("markdown renderer renders GFM tables and inline code through the shared third-party component", () => {
const html = renderToStaticMarkup(
- {"| Key | Value |\n| --- | --- |\n| **status** | `ok` |"}
+ {"| Key | Value |\n| --- | --- |\n| **status** | `ok` |"}
);
assert.match(html, //u);
@@ -15,7 +24,7 @@ test("message markdown renders GFM tables and inline code through the shared thi
});
test("message markdown escapes raw HTML instead of injecting handwritten HTML", () => {
- const html = renderToStaticMarkup({""});
+ const html = renderToStaticMarkup({""});
assert.doesNotMatch(html, /"});
+
+ assert.doesNotMatch(html, /