From fc2c1b88f04445022a31d30c8dafe6ba16f6d6f1 Mon Sep 17 00:00:00 2001 From: pdelfan Date: Wed, 14 Oct 2026 11:42:30 -0700 Subject: [PATCH] A search result's snippet of a reply renders its Markdown through Streamdown, as the reply itself does, so the web app no longer depends on react-markdown or remark-gfm. It uses Streamdown's Block rather than the Streamdown component, which would wrap the snippet's run of text in a div and complete the Markdown the snippet's cut left open, so the snippet renders the same elements as before and the tests pass unchanged. The plugin that marks matches is a named function, since Streamdown caches its parsers by the names of their plugins. --- apps/web/package.json | 2 -- .../chat-list/components/SearchResults.tsx | 29 ++++++++++++------ pnpm-lock.yaml | 30 ------------------- 3 files changed, 20 insertions(+), 41 deletions(-) diff --git a/apps/web/package.json b/apps/web/package.json index a45294e..2a526f3 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -26,8 +26,6 @@ "lucide-react": "1.47.0", "react": "19.3.0", "react-dom": "19.3.0", - "react-markdown": "10.1.0", - "remark-gfm": "4.0.1", "shiki": "4.5.0", "streamdown": "2.7.0", "tailwind-merge": "3.7.0" diff --git a/apps/web/src/features/chat-list/components/SearchResults.tsx b/apps/web/src/features/chat-list/components/SearchResults.tsx index c1d0f4a..4b7c3de 100644 --- a/apps/web/src/features/chat-list/components/SearchResults.tsx +++ b/apps/web/src/features/chat-list/components/SearchResults.tsx @@ -1,6 +1,5 @@ import { Link } from '@tanstack/react-router' -import ReactMarkdown from 'react-markdown' -import remarkGfm from 'remark-gfm' +import { Block, defaultRemarkPlugins } from 'streamdown' import { cn } from '../../../shared/cn.ts' import { ChatIcon } from '../../../shared/icons.tsx' import { plural } from '../../../shared/plural.ts' @@ -101,23 +100,35 @@ function markMatches(node: HtmlNode) { }) } -const remarkPlugins = [remarkGfm] -const rehypePlugins = [() => markMatches] +/** Named, since Streamdown caches its parsers by the names of their plugins. */ +function markSearchMatches() { + return markMatches +} + +const remarkPlugins = Object.values(defaultRemarkPlugins) +const rehypePlugins = [markSearchMatches] // Inline styling only, so the snippet stays one run of text. Other elements give way to their // text, and images, which have none, drop out. const inlineElements = ['strong', 'em', 'del', 'code', 'mark'] -/** A snippet of a reply with its Markdown styled inline, as the reply shows it. */ +/** + * A snippet of a reply with its Markdown styled inline, as the reply shows it. Streamdown's one + * block, rather than all of Streamdown, which would wrap the snippet's run of text in a div and + * complete the Markdown the snippet's cut left open. + */ export function MarkdownSnippet(props: SnippetProps) { return ( - - {dropCutLinks(props.text)} - + /> ) } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 798f375..c99ca18 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -249,12 +249,6 @@ importers: react-dom: specifier: 19.3.0 version: 19.3.0(react@19.3.0) - react-markdown: - specifier: 10.1.0 - version: 10.1.0(@types/react@19.3.0)(react@19.3.0) - remark-gfm: - specifier: 4.0.1 - version: 4.0.1 shiki: specifier: 4.5.0 version: 4.5.0 @@ -3076,12 +3070,6 @@ packages: react-is@17.0.2: resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} - react-markdown@10.1.0: - resolution: {integrity: sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==} - peerDependencies: - '@types/react': '>=18' - react: '>=18' - react-scan@0.5.3: resolution: {integrity: sha512-qde9PupmUf0L3MU1H6bjmoukZNbCXdMyTEwP4Gh8RQ4rZPd2GGNBgEKWszwLm96E8k+sGtMpc0B9P0KyFDP6Bw==} hasBin: true @@ -6500,24 +6488,6 @@ snapshots: react-is@17.0.2: {} - react-markdown@10.1.0(@types/react@19.3.0)(react@19.3.0): - dependencies: - '@types/hast': 3.0.5 - '@types/mdast': 4.0.4 - '@types/react': 19.3.0 - devlop: 1.1.0 - hast-util-to-jsx-runtime: 2.3.6 - html-url-attributes: 3.0.1 - mdast-util-to-hast: 13.2.1 - react: 19.3.0 - remark-parse: 11.0.0 - remark-rehype: 11.1.2 - unified: 11.0.5 - unist-util-visit: 5.1.0 - vfile: 6.0.3 - transitivePeerDependencies: - - supports-color - react-scan@0.5.3(react-dom@19.3.0(react@19.3.0))(react@19.3.0): dependencies: '@babel/core': 7.29.7 -- knotmirror 0.0.1