diff --git a/packages/app/e2e/browser/agent-tab-image-stability.spec.ts b/packages/app/e2e/browser/agent-tab-image-stability.spec.ts index b6fbadb3ae..27ef6d7579 100644 --- a/packages/app/e2e/browser/agent-tab-image-stability.spec.ts +++ b/packages/app/e2e/browser/agent-tab-image-stability.spec.ts @@ -1,3 +1,5 @@ +import type { Page } from "@playwright/test"; +import { readFile } from "node:fs/promises"; import { expect, test as base } from "../support/fixtures"; import { appendSettledTimelineTurns, @@ -323,3 +325,45 @@ test("a later reply reads new bytes at the same workspace image path", async ({ await expectAssistantImageRendered(page, second); await expectAssistantImageRendered(page, first); }); + +test("pending assistant images reserve their decoded height", async ({ + imageWorkspace: workspace, + page, +}) => { + const image = await createSmallAssistantPng(workspace, { + alt: "Delayed remote image", + fileName: "delayed.png", + }); + const agent = await createSettledMockAgent(workspace, "Pending image geometry"); + const url = "https://markdown-image.test/delayed.png"; + const release = await holdRemoteImage( + page, + url, + await readFile(path.join(workspace.repoPath, image.relativePath)), + ); + try { + await emitSettledAssistantImage(workspace.client, agent, image, url); + await openAssistantImageTimeline(page, agent); + const rendered = page.getByRole("button", { name: image.alt }).first(); + await expect(rendered).toHaveAttribute("src", /^data:image\/gif/); + const pendingBox = await rendered.boundingBox(); + expect(pendingBox?.height).toBe(320); + release(); + await expect(rendered).toHaveAttribute("src", url); + expect((await rendered.boundingBox())?.height).toBe(pendingBox?.height); + } finally { + release(); + } +}); + +async function holdRemoteImage(page: Page, url: string, bytes: Buffer): Promise<() => void> { + let release!: () => void; + const ready = new Promise((resolve) => { + release = resolve; + }); + await page.route(url, async (route) => { + await ready; + await route.fulfill({ contentType: "image/png", body: bytes }); + }); + return release; +} diff --git a/packages/app/e2e/browser/plan-card-markdown.spec.ts b/packages/app/e2e/browser/plan-card-markdown.spec.ts index f9bb212c4a..e462ce7cba 100644 --- a/packages/app/e2e/browser/plan-card-markdown.spec.ts +++ b/packages/app/e2e/browser/plan-card-markdown.spec.ts @@ -56,6 +56,8 @@ test("chat and file preview share the library renderer and resolve file links", "# Nested file\n\nUnicode suffix lookup succeeded.", ); await session.client.waitForFinish(session.agentId, 30_000); + // A cold render must work even when a separate parser chunk cannot be fetched. + await page.route("**/wasm/md4c.bundle?**", (route) => route.abort("failed")); await openAgentRoute(page, session); const message = page.getByTestId("assistant-message"); await expect(message).toHaveCount(1); @@ -66,6 +68,7 @@ test("chat and file preview share the library renderer and resolve file links", await expect(message.getByRole("cell").nth(0)).toHaveText("Answer"); await expect(message.getByRole("cell").nth(1)).toHaveText(""); await expect(message.getByRole("cell").nth(2)).toHaveText("42"); + await expect(message.locator("p div")).toHaveCount(0); await message.getByRole("link", { name: "README.md" }).click(); const preview = page.getByTestId("markdown-preview-frame"); await expect(preview.locator(".enrm-text")).toContainText("Second paragraph."); diff --git a/packages/app/src/components/markdown/images.ts b/packages/app/src/components/markdown/images.ts index c0832055a6..a707a0b3c1 100644 --- a/packages/app/src/components/markdown/images.ts +++ b/packages/app/src/components/markdown/images.ts @@ -112,12 +112,26 @@ export function useMarkdownSource(text: string, context: MarkdownImageContext) { })), ); const unavailableImages = new Set( - sources.filter( - (_source, index) => displayUrls[index] !== undefined && decoded[index]?.data !== true, + sources.filter((_source, index) => + isWeb + ? (!acquisitions[index] && displayUrls[index] === undefined) || + results[index]?.isError || + decoded[index]?.isError + : displayUrls[index] !== undefined && decoded[index]?.data !== true, ), ); return { - markdown: prepared.render(resolved, unavailableImages), + markdown: prepared.render( + resolved, + unavailableImages, + new Set( + isWeb + ? sources.filter( + (source, index) => !unavailableImages.has(source) && decoded[index]?.data !== true, + ) + : [], + ), + ), linkRecognition: prepared.linkRecognition, linkSource: prepared.linkSource, imageSources: Object.fromEntries([...resolved].map(([source, url]) => [url, source])), diff --git a/packages/app/src/components/markdown/renderer.test.ts b/packages/app/src/components/markdown/renderer.test.ts index 9d47a2c65d..f6246211b7 100644 --- a/packages/app/src/components/markdown/renderer.test.ts +++ b/packages/app/src/components/markdown/renderer.test.ts @@ -391,3 +391,18 @@ it("keeps the document's first reference definition across every native Mermaid expect(segments[2].source.endsWith(after)).toBe(true); expect(segments[1]).toEqual({ kind: "mermaid", id: "1", source: "graph TD\nA-->B" }); }); + +it("reserves pending image geometry until decode succeeds and collapses only confirmed failures", () => { + const source = "![Local](shot.png)\n\n![Remote](https://example.com/image.png)"; + const prepared = prepareMarkdownSource(source); + const pending = prepared.render( + new Map(), + new Set(), + new Set(prepared.images.map((image) => image.source)), + ); + expect(pending.match(/!\[/g)).toHaveLength(2); + expect(pending).toContain("data:image/gif;base64,"); + expect(prepared.render(new Map(), new Set(prepared.images.map((image) => image.source)))).toBe( + "Local\n\nRemote", + ); +}); diff --git a/packages/app/src/components/markdown/renderer.tsx b/packages/app/src/components/markdown/renderer.tsx index 5852be4877..8e86b2c20e 100644 --- a/packages/app/src/components/markdown/renderer.tsx +++ b/packages/app/src/components/markdown/renderer.tsx @@ -12,6 +12,7 @@ import { isWeb } from "@/constants/platform"; import { MarkdownRendererFrame } from "./frame"; import { WEB_RENDERERS } from "./renderers"; import { createMarkdownStyles } from "./styles"; +import { PENDING_MARKDOWN_IMAGE } from "./source"; import { useMarkdownSource, type MarkdownImageContext } from "./images"; import { MermaidFence, MermaidStreamingContext, splitMermaidFences } from "./fence/mermaid"; @@ -57,9 +58,9 @@ export const MarkdownRenderer = memo(function MarkdownRenderer({ if (onLinkPress && !actions.canResolveFile(source)) onLinkPress(url); else actions.open(source, "preferred"); }); - const pressImage = useStableEvent(({ url }: { url: string }) => - setImage({ type: "uri", uri: url }), - ); + const pressImage = useStableEvent(({ url }: { url: string }) => { + if (url !== PENDING_MARKDOWN_IMAGE) setImage({ type: "uri", uri: url }); + }); const closeImage = useStableEvent(() => setImage(null)); const segments = useMemo( () => diff --git a/packages/app/src/components/markdown/renderers.web.tsx b/packages/app/src/components/markdown/renderers.web.tsx index 7fe706ca17..abf341d051 100644 --- a/packages/app/src/components/markdown/renderers.web.tsx +++ b/packages/app/src/components/markdown/renderers.web.tsx @@ -1,4 +1,5 @@ -import { Text, View, type ViewStyle } from "react-native"; +import { Text } from "react-native"; +import { forwardRef, type ComponentProps } from "react"; import type { CodeBlockRendererProps, LinkRendererProps, @@ -32,7 +33,7 @@ function WebLink({ link, anchorProps, children }: LinkRendererProps) { return ( - {anchor} + {anchor} {tooltipPath ? ( @@ -44,7 +45,23 @@ function WebLink({ link, anchorProps, children }: LinkRendererProps) { ); } -const INLINE_LINK: ViewStyle = { display: "inline-flex" as ViewStyle["display"] }; +// TooltipTrigger also supplies native Pressable handlers; forward only DOM events. +// A View renders a div, which is invalid inside the library's prose paragraphs. +const InlineLinkTrigger = forwardRef>( + function InlineLinkTrigger({ children, onMouseEnter, onMouseLeave, onFocus, onBlur }, ref) { + return ( + + {children} + + ); + }, +); const styles = StyleSheet.create((theme) => ({ tooltipPath: { color: theme.colors.foreground, diff --git a/packages/app/src/components/markdown/source.ts b/packages/app/src/components/markdown/source.ts index 9c61d4f40f..7fe28d1d18 100644 --- a/packages/app/src/components/markdown/source.ts +++ b/packages/app/src/components/markdown/source.ts @@ -32,6 +32,10 @@ export function markdownLinkSource( return { href: url, text: url }; } +// A decoded transparent pixel reserves the library's normal block/inline image box. +export const PENDING_MARKDOWN_IMAGE = + "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"; + export interface MarkdownImage { source: string; alt: string; @@ -142,12 +146,15 @@ export function prepareMarkdownSource( render( urls: ReadonlyMap = new Map(), unavailableImages: ReadonlySet = new Set(), + pendingImages: ReadonlySet = new Set(), ) { const replacements = [...edits]; for (const image of images) { const unavailable = unavailableImages.has(image.source); - if (!unavailable && /^(https?:|data:|blob:)/i.test(image.source)) continue; - const url = unavailable ? undefined : urls.get(image.source); + const pending = pendingImages.has(image.source); + if (!unavailable && !pending && /^(https?:|data:|blob:)/i.test(image.source)) continue; + let url = unavailable ? undefined : urls.get(image.source); + if (pending && !unavailable) url = PENDING_MARKDOWN_IMAGE; replacements.push({ start: image.start, end: image.end, diff --git a/packages/app/src/components/ui/tooltip.tsx b/packages/app/src/components/ui/tooltip.tsx index 22796e7ffe..72803f06d0 100644 --- a/packages/app/src/components/ui/tooltip.tsx +++ b/packages/app/src/components/ui/tooltip.tsx @@ -41,10 +41,12 @@ interface Rect { height: number; } +type TooltipTriggerElement = View | HTMLElement; + interface TooltipContextValue { open: boolean; setOpen: (open: boolean) => void; - triggerRef: React.RefObject; + triggerRef: React.RefObject; enabled: boolean; openOnPress: boolean; delayDuration: number; @@ -120,12 +122,19 @@ function useControllableOpenState({ return [value, setValue]; } -function measureElement(element: View): Promise { - return new Promise((resolve) => { - element.measureInWindow((x, y, width, height) => { - resolve({ x, y, width, height }); +function measureElement(element: TooltipTriggerElement): Promise { + if (isWeb && element instanceof HTMLElement) { + const { x, y, width, height } = element.getBoundingClientRect(); + return Promise.resolve({ x, y, width, height }); + } + if ("measureInWindow" in element) { + return new Promise((resolve) => { + element.measureInWindow((x, y, width, height) => { + resolve({ x, y, width, height }); + }); }); - }); + } + throw new Error("A DOM tooltip trigger requires the web platform"); } function resolveActualSide(args: { @@ -240,7 +249,7 @@ export function Tooltip({ enabledOnDesktop?: boolean; enabledOnMobile?: boolean; }>): ReactElement { - const triggerRef = useRef(null); + const triggerRef = useRef(null); const [isOpen, setIsOpen] = useControllableOpenState({ open, defaultOpen, @@ -282,6 +291,12 @@ export function TooltipTrigger({ triggerRefProp?: string; }): ReactElement { const ctx = useTooltipContext("TooltipTrigger"); + const setTriggerRef = useCallback( + (node: View | null) => { + ctx.triggerRef.current = node; + }, + [ctx.triggerRef], + ); const openTimerRef = useRef | null>(null); const clearOpenTimer = useCallback(() => { @@ -411,7 +426,7 @@ export function TooltipTrigger({ }; const existingRefProp = Reflect.get(rawProps, triggerRefProp); - mergedProps[triggerRefProp] = (node: View) => { + mergedProps[triggerRefProp] = (node: TooltipTriggerElement) => { if (isCallable(existingRefProp)) { existingRefProp(node); } else if (existingRefProp && typeof existingRefProp === "object") { @@ -424,7 +439,7 @@ export function TooltipTrigger({ } return ( - + {children} ); diff --git a/patches/react-native-enriched-markdown+1.2.0-nightly-20261014-766fc0e67.patch b/patches/react-native-enriched-markdown+1.2.0-nightly-20261014-766fc0e67.patch index 1e9ecafe69..cf0c983066 100644 --- a/patches/react-native-enriched-markdown+1.2.0-nightly-20261014-766fc0e67.patch +++ b/patches/react-native-enriched-markdown+1.2.0-nightly-20261014-766fc0e67.patch @@ -1179,10 +1179,11 @@ index 1c49b41..921a946 100644 -{"version":3,"names":["useState","useEffect","useMemo","useRef","normalizeMarkdownStyle","zeroTrailingMargins","parseErrorFallbackStyle","buildStyles","parseMarkdown","recognizeTextLinks","RenderNode","indexTaskItems","markInlineImages","loadKaTeX","ENRM_TEXT_CLASS","ENRM_SELECTION_BG_VAR","filterNativeOnlyProps","jsx","_jsx","EnrichedMarkdownText","markdown","markdownStyle","md4cFlags","linkRecognition","onLinkPress","onLinkLongPress","onImagePress","onTaskListItemPress","onCodeBlockPress","enableTaskListItemToggle","allowTrailingMargin","containerStyle","selectable","dir","selectionColor","testID","rest","normalizedStyle","ast","setAst","katex","setKatex","parseError","setParseError","underline","latexMath","superscript","subscript","highlight","hardSoftBreaks","preserveBlankLines","admonitions","linkRecognitionRef","current","textLinkKey","text","toString","inlineCodeLinkKey","inlineCode","cancelled","katexPromise","Promise","resolve","all","then","result","katexInstance","catch","error","__DEV__","console","callbacks","capabilities","lastChildStyle","styles","lastChildStyles","wrapperStyle","display","flexDirection","undefined","userSelect","domProps","className","style","children","lastIdx","length","map","child","index","node","type"],"sourceRoot":"../../../src","sources":["web/EnrichedMarkdownText.tsx"],"mappings":"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","ignoreList":[]} +{"version":3,"names":["useState","useEffect","useMemo","useRef","normalizeMarkdownStyle","zeroTrailingMargins","parseErrorFallbackStyle","buildStyles","parseMarkdown","parseMarkdownSync","recognizeTextLinks","RenderNode","indexTaskItems","markInlineImages","loadKaTeX","ENRM_TEXT_CLASS","ENRM_SELECTION_BG_VAR","filterNativeOnlyProps","jsx","_jsx","EnrichedMarkdownText","markdown","markdownStyle","md4cFlags","linkRecognition","renderers","onLinkPress","onLinkLongPress","onImagePress","onTaskListItemPress","onCodeBlockPress","enableTaskListItemToggle","allowTrailingMargin","containerStyle","selectable","dir","selectionColor","testID","rest","normalizedStyle","parsed","setParsed","katex","setKatex","parseError","setParseError","underline","latexMath","superscript","subscript","highlight","hardSoftBreaks","preserveBlankLines","admonitions","linkRecognitionRef","current","textLinkKey","text","toString","inlineCodeLinkKey","inlineCode","immediate","result","ast","cancelled","katexPromise","Promise","resolve","all","then","katexInstance","catch","error","__DEV__","console","callbacks","capabilities","lastChildStyle","styles","lastChildStyles","wrapperStyle","display","flexDirection","undefined","userSelect","domProps","visibleParsed","className","style","children","lastIdx","length","map","child","index","node","type"],"sourceRoot":"../../../src","sources":["web/EnrichedMarkdownText.tsx"],"mappings":"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","ignoreList":[]} diff --git a/node_modules/react-native-enriched-markdown/lib/module/web/parseMarkdown.js b/node_modules/react-native-enriched-markdown/lib/module/web/parseMarkdown.js -index 7052cba..cc1ea0f 100644 --- a/node_modules/react-native-enriched-markdown/lib/module/web/parseMarkdown.js +++ b/node_modules/react-native-enriched-markdown/lib/module/web/parseMarkdown.js -@@ -2,13 +2,17 @@ +@@ -1,14 +1,19 @@ ++import createMd4c from './wasm/md4c'; + "use strict"; // Caching the Promise (not the resolved value) means concurrent callers share // a single WASM initialization — no duplicate loading. @@ -1194,14 +1195,14 @@ index 7052cba..cc1ea0f 100644 function initializeParser() { if (!parserPromise) { - parserPromise = import('./wasm/md4c').then(module => module.default()).then(wasmModule => wasmModule.cwrap('parseMarkdown', 'string', ['string', 'number', 'number', 'number', 'number', 'number', 'number', 'number', 'number'])).catch(error => { -+ parserPromise = import('./wasm/md4c').then(module => module.default()).then(wasmModule => wasmModule.cwrap('parseMarkdown', 'string', ['string', 'number', 'number', 'number', 'number', 'number', 'number', 'number', 'number'])).then(parse => { ++ parserPromise = Promise.resolve().then(() => createMd4c()).then(wasmModule => wasmModule.cwrap('parseMarkdown', 'string', ['string', 'number', 'number', 'number', 'number', 'number', 'number', 'number', 'number'])).then(parse => { + readyParser = parse; + return parse; + }).catch(error => { parserPromise = null; throw error; }); -@@ -18,7 +22,7 @@ function initializeParser() { +@@ -18,7 +23,7 @@ function isASTNode(value) { return typeof value === 'object' && value !== null && 'type' in value && typeof value.type === 'string'; } @@ -1210,7 +1211,7 @@ index 7052cba..cc1ea0f 100644 underline = false, latexMath = true, superscript = false, -@@ -28,11 +32,16 @@ export async function parseMarkdown(markdown, { +@@ -28,11 +33,16 @@ preserveBlankLines = false, admonitions = true } = {}) { @@ -2049,10 +2050,14 @@ index 08dc7df..11fbd16 100644 style={wrapperStyle} dir={dir} diff --git a/node_modules/react-native-enriched-markdown/src/web/parseMarkdown.ts b/node_modules/react-native-enriched-markdown/src/web/parseMarkdown.ts -index b94c8d2..73477df 100644 --- a/node_modules/react-native-enriched-markdown/src/web/parseMarkdown.ts +++ b/node_modules/react-native-enriched-markdown/src/web/parseMarkdown.ts -@@ -15,6 +15,7 @@ type ParseFn = ( +@@ -1,3 +1,4 @@ ++import createMd4c from './wasm/md4c'; + import type { ASTNode } from './types'; + import type { Md4cFlags } from '../types/MarkdownStyle'; + +@@ -15,14 +16,15 @@ // Caching the Promise (not the resolved value) means concurrent callers share // a single WASM initialization — no duplicate loading. @@ -2060,7 +2065,17 @@ index b94c8d2..73477df 100644 let parserPromise: Promise | null = null; // SINGLE_FILE=1 inlines the WASM binary as base64 inside md4c.js, so no -@@ -36,6 +37,10 @@ function initializeParser(): Promise { + // network fetch is needed — only a one-time decode + compile on first call. + function initializeParser(): Promise { + if (!parserPromise) { +- parserPromise = import('./wasm/md4c') +- .then((module) => module.default()) ++ parserPromise = Promise.resolve() ++ .then(() => createMd4c()) + .then((wasmModule) => + wasmModule.cwrap('parseMarkdown', 'string', [ + 'string', +@@ -36,6 +38,10 @@ 'number', ]) ) @@ -2071,7 +2086,7 @@ index b94c8d2..73477df 100644 .catch((error) => { parserPromise = null; throw error; -@@ -53,7 +58,7 @@ function isASTNode(value: unknown): value is ASTNode { +@@ -53,7 +59,7 @@ ); } @@ -2080,7 +2095,7 @@ index b94c8d2..73477df 100644 markdown: string, { underline = false, -@@ -65,8 +70,9 @@ export async function parseMarkdown( +@@ -65,8 +71,9 @@ preserveBlankLines = false, admonitions = true, }: Md4cFlags = {} @@ -2092,7 +2107,7 @@ index b94c8d2..73477df 100644 const result: unknown = JSON.parse( parse( -@@ -88,3 +94,11 @@ export async function parseMarkdown( +@@ -88,3 +95,11 @@ return result; }