diff --git a/packages/app/e2e/browser/plan-card-markdown.spec.ts b/packages/app/e2e/browser/plan-card-markdown.spec.ts index 08052e081e..f9bb212c4a 100644 --- a/packages/app/e2e/browser/plan-card-markdown.spec.ts +++ b/packages/app/e2e/browser/plan-card-markdown.spec.ts @@ -36,7 +36,7 @@ test("chat and file preview share the library renderer and resolve file links", }, testInfo) => { const { writeFile, mkdir } = await import("node:fs/promises"); const source = - "First paragraph.\n\nSecond paragraph.\n\n```ts\nconst answer = 42;\n```\n\n> A quote\n\n| Key || Value |\n| --- | --- | --- |\n| Answer || 42 |\n\nOpen `README.md` or `src/说明.md:2-3`.\n\n[Space file](my%20file.ts) and `my%20file.ts`."; + "First paragraph.\n\nSecond paragraph.\n\n```ts\nconst answer = 42;\n```\n\n> A quote\n\n| Key || Value |\n| --- | --- | --- |\n| Answer || 42 |\n\nOpen `README.md` or `src/说明.md:2-3`, or packages/app/src/说明.md:2-3.\n\n[Space file](my%20file.ts) and `my%20file.ts`."; const session = await seedMockAgentWorkspace({ repoPrefix: "shared-markdown-", title: "Shared Markdown", @@ -73,10 +73,13 @@ test("chat and file preview share the library renderer and resolve file links", await page.getByRole("button", { name: "Shared Markdown", exact: true }).click(); await expectFileLinkTooltip( page, - message.getByRole("link", { name: "src/说明.md:2-3" }), + message.getByRole("link", { name: "src/说明.md:2-3", exact: true }), "packages/app/src/说明.md:2-3", ); - await message.getByRole("link", { name: "src/说明.md:2-3" }).click(); + await message.getByRole("link", { name: "src/说明.md:2-3", exact: true }).click(); + await expect(page.getByText("Unicode suffix lookup succeeded.", { exact: true })).toBeVisible(); + await page.getByRole("button", { name: "Shared Markdown", exact: true }).click(); + await message.getByRole("link", { name: "packages/app/src/说明.md:2-3", exact: true }).click(); await expect(page.getByText("Unicode suffix lookup succeeded.", { exact: true })).toBeVisible(); await page.getByRole("button", { name: "Shared Markdown", exact: true }).click(); await message.getByRole("link", { name: "Space file", exact: true }).click(); 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 8ad9a2dc0e..1e9ecafe69 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 @@ -1597,15 +1597,6 @@ index aaeab3f..6c2cbe8 100644 \ No newline at end of file +{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.tsx"],"names":[],"mappings":"AAAA,YAAY,EACV,iBAAiB,EACjB,sBAAsB,GACvB,MAAM,2BAA2B,CAAC;AACnC,YAAY,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC7E,YAAY,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AACjE,YAAY,EACV,mBAAmB,EACnB,eAAe,GAChB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,OAAO,IAAI,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AAChF,YAAY,EACV,yBAAyB,EACzB,eAAe,EACf,aAAa,EACb,SAAS,EACT,eAAe,IAAI,mBAAmB,EACtC,mBAAmB,IAAI,uBAAuB,EAC9C,yBAAyB,IAAI,6BAA6B,GAC3D,MAAM,+BAA+B,CAAC;AACvC,YAAY,EACV,cAAc,EACd,kBAAkB,EAClB,eAAe,EACf,sBAAsB,EACtB,cAAc,EACd,eAAe,EACf,mBAAmB,GACpB,MAAM,gBAAgB,CAAC;AACxB,YAAY,EACV,mBAAmB,EACnB,2BAA2B,GAC5B,MAAM,6BAA6B,CAAC;AACrC,OAAO,EACL,4BAA4B,EAC5B,0BAA0B,GAC3B,MAAM,8BAA8B,CAAC;AAEtC,OAAO,EAAE,yBAAyB,EAAE,MAAM,6BAA6B,CAAC;AACxE,YAAY,EACV,8BAA8B,EAC9B,iCAAiC,EACjC,sBAAsB,EACtB,UAAU,EACV,eAAe,EACf,wBAAwB,EACxB,gBAAgB,EAChB,uBAAuB,EACvB,eAAe,EACf,cAAc,EACd,mBAAmB,EACnB,oBAAoB,EACpB,iBAAiB,EACjB,SAAS,EACT,YAAY,EACZ,YAAY,GACb,MAAM,6BAA6B,CAAC"} \ No newline at end of file -diff --git a/node_modules/react-native-enriched-markdown/lib/typescript/src/index.web.d.ts b/node_modules/react-native-enriched-markdown/lib/typescript/src/index.web.d.ts -index 17def30..eae0a3a 100644 ---- a/node_modules/react-native-enriched-markdown/lib/typescript/src/index.web.d.ts -+++ b/node_modules/react-native-enriched-markdown/lib/typescript/src/index.web.d.ts -@@ -1,3 +1,4 @@ -+export type { MarkdownRenderers, CodeBlockRendererProps, } from './types/MarkdownRenderers'; - export { EnrichedMarkdownText, default } from './web/EnrichedMarkdownText'; - export type { EnrichedMarkdownTextProps } from './types/MarkdownTextProps.web'; - export type { MarkdownStyle, Md4cFlags } from './types/MarkdownStyle'; diff --git a/node_modules/react-native-enriched-markdown/lib/typescript/src/index.web.d.ts.map b/node_modules/react-native-enriched-markdown/lib/typescript/src/index.web.d.ts.map index ad425ad..c3403c8 100644 --- a/node_modules/react-native-enriched-markdown/lib/typescript/src/index.web.d.ts.map @@ -1838,18 +1829,6 @@ index 6f31ccc..7b08b62 100644 } export interface LinkLongPressEvent { -diff --git a/node_modules/react-native-enriched-markdown/src/index.web.tsx b/node_modules/react-native-enriched-markdown/src/index.web.tsx -index 8628b37..de167b7 100644 ---- a/node_modules/react-native-enriched-markdown/src/index.web.tsx -+++ b/node_modules/react-native-enriched-markdown/src/index.web.tsx -@@ -1,3 +1,7 @@ -+export type { -+ MarkdownRenderers, -+ CodeBlockRendererProps, -+} from './types/MarkdownRenderers'; - export { EnrichedMarkdownText, default } from './web/EnrichedMarkdownText'; - export type { EnrichedMarkdownTextProps } from './types/MarkdownTextProps.web'; - export type { MarkdownStyle, Md4cFlags } from './types/MarkdownStyle'; diff --git a/node_modules/react-native-enriched-markdown/src/native/EnrichedMarkdownText.tsx b/node_modules/react-native-enriched-markdown/src/native/EnrichedMarkdownText.tsx index ca7157e..40581c1 100644 --- a/node_modules/react-native-enriched-markdown/src/native/EnrichedMarkdownText.tsx @@ -2251,10 +2230,47 @@ diff --git a/node_modules/react-native-enriched-markdown/lib/typescript/src/inde export type { LinkVariantStyle, LinkPillStyle } from './types/MarkdownStyle'; export type { LinkPillContent } from './types/MarkdownTextProps'; export type { LinkContextMenuItem, LinkRecognition, } from './types/MarkdownTextProps'; +diff --git a/node_modules/react-native-enriched-markdown/src/types/MarkdownTextProps.ts b/node_modules/react-native-enriched-markdown/src/types/MarkdownTextProps.ts +--- a/node_modules/react-native-enriched-markdown/src/types/MarkdownTextProps.ts ++++ b/node_modules/react-native-enriched-markdown/src/types/MarkdownTextProps.ts +@@ -1,3 +1,4 @@ ++import type { MarkdownRenderers } from './MarkdownRenderers'; + import type { ColorValue, ViewProps, ViewStyle, TextStyle } from 'react-native'; + import type { MarkdownStyle, Md4cFlags } from './MarkdownStyle'; + import type { AccessibilityLabels } from './AccessibilityLabels'; +@@ -154,6 +155,8 @@ + * @platform ios, android, web + */ + markdown: string; ++ /** Custom block components. Ignored on native. @platform web */ ++ renderers?: MarkdownRenderers; + /** + * Style configuration for markdown elements. + * @platform ios, android, web +diff --git a/node_modules/react-native-enriched-markdown/src/index.web.tsx b/node_modules/react-native-enriched-markdown/src/index.web.tsx +--- a/node_modules/react-native-enriched-markdown/src/index.web.tsx ++++ b/node_modules/react-native-enriched-markdown/src/index.web.tsx +@@ -1,3 +1,8 @@ ++export type { ++ MarkdownRenderers, ++ CodeBlockRendererProps, ++ LinkRendererProps, ++} from './types/MarkdownRenderers'; + export { EnrichedMarkdownText, default } from './web/EnrichedMarkdownText'; + export type { EnrichedMarkdownTextProps } from './types/MarkdownTextProps.web'; + export type { MarkdownStyle, Md4cFlags } from './types/MarkdownStyle'; +diff --git a/node_modules/react-native-enriched-markdown/lib/typescript/src/index.web.d.ts b/node_modules/react-native-enriched-markdown/lib/typescript/src/index.web.d.ts +--- a/node_modules/react-native-enriched-markdown/lib/typescript/src/index.web.d.ts ++++ b/node_modules/react-native-enriched-markdown/lib/typescript/src/index.web.d.ts +@@ -1,3 +1,4 @@ ++export type { MarkdownRenderers, CodeBlockRendererProps, LinkRendererProps, } from './types/MarkdownRenderers'; + export { EnrichedMarkdownText, default } from './web/EnrichedMarkdownText'; + export type { EnrichedMarkdownTextProps } from './types/MarkdownTextProps.web'; + export type { MarkdownStyle, Md4cFlags } from './types/MarkdownStyle'; diff --git a/node_modules/react-native-enriched-markdown/src/web/renderers/InlineRenderers.tsx b/node_modules/react-native-enriched-markdown/src/web/renderers/InlineRenderers.tsx --- a/node_modules/react-native-enriched-markdown/src/web/renderers/InlineRenderers.tsx +++ b/node_modules/react-native-enriched-markdown/src/web/renderers/InlineRenderers.tsx -@@ -58,6 +58,7 @@ +@@ -58,16 +58,25 @@ node, style, callbacks, @@ -2262,24 +2278,26 @@ diff --git a/node_modules/react-native-enriched-markdown/src/web/renderers/Inlin renderChildren, }: RendererProps) { const url = node.attributes?.url; -@@ -67,7 +68,15 @@ + + if (!url) return <>{renderChildren(node)}; + ++ const isRecognizedLink = node.attributes?.recognizedLink === 'true'; ++ const containsInlineCode = node.children?.some((child) => child.type === 'Code'); ++ let sourceType: 'markdown' | 'text' | 'inline-code' = 'markdown'; ++ if (isRecognizedLink) { ++ sourceType = containsInlineCode ? 'inline-code' : 'text'; ++ } ++ const link = { url, sourceType }; ++ const handleClick = (event: MouseEvent) => { if (callbacks.onLinkPress) { event.preventDefault(); - callbacks.onLinkPress({ url }); -+ callbacks.onLinkPress({ -+ url, -+ sourceType: -+ node.attributes?.recognizedLink === 'true' -+ ? node.children?.some((child) => child.type === 'Code') -+ ? 'inline-code' -+ : 'text' -+ : 'markdown', -+ }); ++ callbacks.onLinkPress(link); } }; -@@ -78,18 +87,32 @@ +@@ -78,18 +87,27 @@ } }; @@ -2308,12 +2326,7 @@ diff --git a/node_modules/react-native-enriched-markdown/src/web/renderers/Inlin + if (CustomLink) { + return ( + child.type === 'Code') ? 'inline-code' : 'text' -+ : 'markdown', -+ }} ++ link={link} + anchorProps={anchorProps} + > + {renderChildren(node)} @@ -2327,7 +2340,7 @@ diff --git a/node_modules/react-native-enriched-markdown/src/web/renderers/Inlin diff --git a/node_modules/react-native-enriched-markdown/lib/module/web/renderers/InlineRenderers.js b/node_modules/react-native-enriched-markdown/lib/module/web/renderers/InlineRenderers.js --- a/node_modules/react-native-enriched-markdown/lib/module/web/renderers/InlineRenderers.js +++ b/node_modules/react-native-enriched-markdown/lib/module/web/renderers/InlineRenderers.js -@@ -103,6 +103,7 @@ +@@ -103,18 +103,24 @@ node, style, callbacks, @@ -2335,17 +2348,27 @@ diff --git a/node_modules/react-native-enriched-markdown/lib/module/web/renderer renderChildren }) { const url = node.attributes?.url; -@@ -113,7 +114,8 @@ + if (!url) return /*#__PURE__*/_jsx(_Fragment, { + children: renderChildren(node) + }); ++ const isRecognizedLink = node.attributes?.recognizedLink === 'true'; ++ const containsInlineCode = node.children?.some(child => child.type === 'Code'); ++ let sourceType = 'markdown'; ++ if (isRecognizedLink) { ++ sourceType = containsInlineCode ? 'inline-code' : 'text'; ++ } ++ const link = { url, sourceType }; + const handleClick = event => { if (callbacks.onLinkPress) { event.preventDefault(); - callbacks.onLinkPress({ +- callbacks.onLinkPress({ - url -+ url, -+ sourceType: node.attributes?.recognizedLink === 'true' ? node.children?.some(child => child.type === 'Code') ? 'inline-code' : 'text' : 'markdown' - }); +- }); ++ callbacks.onLinkPress(link); } }; -@@ -125,15 +127,27 @@ + const handleContextMenu = event => { +@@ -125,15 +131,24 @@ }); } }; @@ -2367,10 +2390,7 @@ diff --git a/node_modules/react-native-enriched-markdown/lib/module/web/renderer + const CustomLink = capabilities.renderers?.link; + if (CustomLink) { + return /*#__PURE__*/_jsx(CustomLink, { -+ link: { -+ url, -+ sourceType: node.attributes?.recognizedLink === 'true' ? node.children?.some(child => child.type === 'Code') ? 'inline-code' : 'text' : 'markdown' -+ }, ++ link, + anchorProps, + children: renderChildren(node) + });