fix: address markdown preview follow-ups

This commit is contained in:
Val Alexander
2026-04-25 02:35:49 -05:00
parent 1ae9505492
commit ebbe96fc88
10 changed files with 85 additions and 6 deletions

View File

@@ -1,5 +1,6 @@
export { export {
DEFAULT_AI_SNAPSHOT_MAX_CHARS, DEFAULT_AI_SNAPSHOT_MAX_CHARS,
DEFAULT_BROWSER_ACTION_TIMEOUT_MS,
DEFAULT_BROWSER_DEFAULT_PROFILE_NAME, DEFAULT_BROWSER_DEFAULT_PROFILE_NAME,
DEFAULT_BROWSER_EVALUATE_ENABLED, DEFAULT_BROWSER_EVALUATE_ENABLED,
DEFAULT_OPENCLAW_BROWSER_COLOR, DEFAULT_OPENCLAW_BROWSER_COLOR,

View File

@@ -35,6 +35,7 @@ import { DEFAULT_UPLOAD_DIR } from "./paths.js";
export { export {
DEFAULT_AI_SNAPSHOT_MAX_CHARS, DEFAULT_AI_SNAPSHOT_MAX_CHARS,
DEFAULT_BROWSER_ACTION_TIMEOUT_MS,
DEFAULT_BROWSER_DEFAULT_PROFILE_NAME, DEFAULT_BROWSER_DEFAULT_PROFILE_NAME,
DEFAULT_BROWSER_EVALUATE_ENABLED, DEFAULT_BROWSER_EVALUATE_ENABLED,
DEFAULT_OPENCLAW_BROWSER_COLOR, DEFAULT_OPENCLAW_BROWSER_COLOR,

View File

@@ -263,7 +263,9 @@ describe("gateway update.run", () => {
); );
const res = await onceMessage(ws, (o) => o.type === "res" && o.id === id); const res = await onceMessage(ws, (o) => o.type === "res" && o.id === id);
expect(res.ok).toBe(true); expect(res.ok).toBe(true);
expect(updateMock).toHaveBeenCalledOnce(); await vi.waitFor(() => {
expect(updateMock).toHaveBeenCalledOnce();
}, FAST_WAIT_OPTS);
} finally { } finally {
process.off("SIGUSR1", sigusr1); process.off("SIGUSR1", sigusr1);
} }

View File

@@ -128,6 +128,7 @@ const BROWSER_HELPER_EXPORT_PARITY_CONTRACTS: readonly BrowserHelperExportParity
extensionPath: "extensions/browser/browser-profiles.ts", extensionPath: "extensions/browser/browser-profiles.ts",
expectedExports: [ expectedExports: [
"DEFAULT_AI_SNAPSHOT_MAX_CHARS", "DEFAULT_AI_SNAPSHOT_MAX_CHARS",
"DEFAULT_BROWSER_ACTION_TIMEOUT_MS",
"DEFAULT_BROWSER_DEFAULT_PROFILE_NAME", "DEFAULT_BROWSER_DEFAULT_PROFILE_NAME",
"DEFAULT_BROWSER_EVALUATE_ENABLED", "DEFAULT_BROWSER_EVALUATE_ENABLED",
"DEFAULT_OPENCLAW_BROWSER_COLOR", "DEFAULT_OPENCLAW_BROWSER_COLOR",

View File

@@ -303,10 +303,14 @@ export function renderToolPreview(
`; `;
} }
export function buildSidebarContent(value: string): SidebarContent { export function buildSidebarContent(
value: string,
options?: { rawText?: string | null },
): SidebarContent {
return { return {
kind: "markdown", kind: "markdown",
content: value, content: value,
...(options?.rawText ? { rawText: options.rawText } : {}),
}; };
} }

View File

@@ -1,6 +1,7 @@
export type MarkdownSidebarContent = { export type MarkdownSidebarContent = {
kind: "markdown"; kind: "markdown";
content: string; content: string;
rawText?: string | null;
}; };
export type CanvasSidebarContent = { export type CanvasSidebarContent = {

View File

@@ -47,6 +47,28 @@ function getExtensionLabel(fileName: string) {
return ext ? `${ext.toUpperCase()} Preview` : "Preview"; return ext ? `${ext.toUpperCase()} Preview` : "Preview";
} }
function formatWorkspaceRelativePath(filePath: string, workspace: string | null | undefined) {
const normalizedPath = filePath.trim();
const normalizedWorkspace = workspace?.trim();
if (!normalizedPath) {
return "";
}
if (normalizedWorkspace && normalizedPath === normalizedWorkspace) {
return ".";
}
if (normalizedWorkspace && normalizedPath.startsWith(`${normalizedWorkspace}/`)) {
return normalizedPath.slice(normalizedWorkspace.length + 1) || ".";
}
const pathParts = normalizedPath.split(/[\\/]+/);
for (let index = pathParts.length - 1; index >= 0; index -= 1) {
const pathPart = pathParts[index];
if (pathPart) {
return pathPart;
}
}
return normalizedPath;
}
function toDomId(value: string) { function toDomId(value: string) {
const normalized = value.toLowerCase().replace(/[^a-z0-9]+/g, "-"); const normalized = value.toLowerCase().replace(/[^a-z0-9]+/g, "-");
return normalized.replace(/^-+|-+$/g, "") || "preview"; return normalized.replace(/^-+|-+$/g, "") || "preview";
@@ -413,6 +435,9 @@ export function renderAgentFiles(params: {
const draftByteSize = formatBytes(new TextEncoder().encode(draft).length); const draftByteSize = formatBytes(new TextEncoder().encode(draft).length);
const draftWordCount = countWords(draft); const draftWordCount = countWords(draft);
const draftLineCount = countLines(draft); const draftLineCount = countLines(draft);
const activePathLabel = activeEntry
? formatWorkspaceRelativePath(activeEntry.path, list?.workspace)
: "";
const previewTitleId = activeEntry ? `agent-file-preview-title-${toDomId(activeEntry.name)}` : ""; const previewTitleId = activeEntry ? `agent-file-preview-title-${toDomId(activeEntry.name)}` : "";
const previewStatusLabel = activeEntry?.missing const previewStatusLabel = activeEntry?.missing
? "Will Create on Save" ? "Will Create on Save"
@@ -574,7 +599,7 @@ export function renderAgentFiles(params: {
${activeEntry.name} ${activeEntry.name}
</div> </div>
<div class="md-preview-dialog__path mono" translate="no"> <div class="md-preview-dialog__path mono" translate="no">
${activeEntry.path} ${activePathLabel}
</div> </div>
</div> </div>
</div> </div>

View File

@@ -220,7 +220,7 @@ describe("renderAgentFiles", () => {
expect(container.querySelector(".md-preview-dialog__reader.sidebar-markdown")).not.toBeNull(); expect(container.querySelector(".md-preview-dialog__reader.sidebar-markdown")).not.toBeNull();
expect(container.querySelector(".md-preview-dialog__path")?.textContent?.trim()).toBe( expect(container.querySelector(".md-preview-dialog__path")?.textContent?.trim()).toBe(
"/tmp/workspace/USER.md", "USER.md",
); );
expect(container.querySelector(".md-preview-dialog__chip strong")?.textContent).toBe( expect(container.querySelector(".md-preview-dialog__chip strong")?.textContent).toBe(
"Saved Preview", "Saved Preview",

View File

@@ -111,6 +111,42 @@ describe("renderChat", () => {
cleanupChatModuleState(); cleanupChatModuleState();
}); });
it("keeps markdown raw text toggles idempotent", () => {
const container = document.createElement("div");
const onOpenSidebar = vi.fn();
const rawMarkdown = "```ts\nconst value = 1;\n```";
render(
renderChat(
createProps({
sidebarOpen: true,
sidebarContent: {
kind: "markdown",
content: `\`\`\`\n${rawMarkdown}\n\`\`\``,
rawText: rawMarkdown,
},
stream: null,
streamStartedAt: null,
onCloseSidebar: () => undefined,
onOpenSidebar,
}),
),
container,
);
const rawButton = Array.from(container.querySelectorAll<HTMLButtonElement>("button")).find(
(button) => button.textContent?.includes("View Raw Text"),
);
rawButton?.dispatchEvent(new MouseEvent("click", { bubbles: true }));
expect(rawButton).not.toBeNull();
expect(onOpenSidebar).toHaveBeenCalledWith({
kind: "markdown",
content: `\`\`\`\n${rawMarkdown}\n\`\`\``,
rawText: rawMarkdown,
});
});
it("renders configured assistant text avatars in transcript groups", () => { it("renders configured assistant text avatars in transcript groups", () => {
const container = document.createElement("div"); const container = document.createElement("div");

View File

@@ -141,6 +141,11 @@ function getPinnedMessages(sessionKey: string): PinnedMessages {
); );
} }
function toPlainTextCodeFence(value: string, language = ""): string {
const fenceHeader = language ? `\`\`\`${language}` : "```";
return `${fenceHeader}\n${value}\n\`\`\``;
}
function getDeletedMessages(sessionKey: string): DeletedMessages { function getDeletedMessages(sessionKey: string): DeletedMessages {
return getOrCreateSessionCacheValue( return getOrCreateSessionCacheValue(
deletedMessagesMap, deletedMessagesMap,
@@ -1129,14 +1134,17 @@ export function renderChat(props: ChatProps) {
return; return;
} }
if (props.sidebarContent.kind === "markdown") { if (props.sidebarContent.kind === "markdown") {
const rawText = props.sidebarContent.rawText ?? props.sidebarContent.content;
props.onOpenSidebar( props.onOpenSidebar(
buildSidebarContent(`\`\`\`\n${props.sidebarContent.content}\n\`\`\``), buildSidebarContent(toPlainTextCodeFence(rawText), { rawText }),
); );
return; return;
} }
if (props.sidebarContent.rawText?.trim()) { if (props.sidebarContent.rawText?.trim()) {
props.onOpenSidebar( props.onOpenSidebar(
buildSidebarContent(`\`\`\`json\n${props.sidebarContent.rawText}\n\`\`\``), buildSidebarContent(
toPlainTextCodeFence(props.sidebarContent.rawText, "json"),
),
); );
} }
}, },