Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,14 @@ function SourceReference({ index, children }: SourceReferenceProps) {
)
}

/**
* Placeholder URLs Streamdown's streaming parser substitutes for a link or image
* whose URL is still arriving. Neither resolves, so a pending link renders as
* its text and a pending image is withheld until the URL completes.
*/
const INCOMPLETE_LINK_HREF = 'streamdown:incomplete-link'
const INCOMPLETE_IMAGE_SRC = 'streamdown:incomplete-image'

const WORKSPACE_LINK_PREFIX = '#sim-workspace-ref-'
const WorkspaceRefsContext = createContext<{
resources: WorkspaceResourceTagData[]
Expand Down Expand Up @@ -375,6 +383,7 @@ const MARKDOWN_COMPONENTS = {
href?: string
'data-footnote-ref'?: boolean
}) {
if (href === INCOMPLETE_LINK_HREF) return <>{children}</>
if (href?.startsWith(SOURCE_LINK_PREFIX)) {
return (
<SourceReference index={Number(href.slice(SOURCE_LINK_PREFIX.length))}>
Expand Down Expand Up @@ -488,7 +497,7 @@ const MARKDOWN_COMPONENTS = {
return <del className='text-[var(--text-tertiary)] line-through'>{children}</del>
},
img({ src, alt }: ComponentPropsWithoutRef<'img'>) {
if (typeof src !== 'string' || !src) return null
if (typeof src !== 'string' || !src || src === INCOMPLETE_IMAGE_SRC) return null
return (
<Lightbox src={src} alt={alt ?? ''}>
<button
Expand Down Expand Up @@ -606,14 +615,12 @@ function ChatContentInner({
* trades identical pixels, unlike flipping at `isRevealing`'s edge, which cut
* running fades short (the old completion flash).
*
* The swap must REMOUNT Streamdown (via `key`), not just flip its props:
* Streamdown's default element components are memoized on className + source
* position (`E`/`qe` in streamdown 2.5), so a re-parse of unchanged content
* without the animate plugin bails at every unoverridden element (`p`,
* `strong`, `tr`, headings, …) and leaves the stale per-char span DOM in
* place. The settled instance keeps the streaming parser (`parserTree`
* below) so the remount only sheds the spans, never re-interprets the
* markdown.
* The swap REMOUNTS Streamdown (via `key`) rather than flipping its props,
* so the settled tree is built from scratch without the animate plugin and
* no memoized element (`p`, `strong`, `tr`, headings, …) can carry the
* per-word span DOM over. The settled instance keeps the streaming parser
* (`parserTree` below) so the remount only sheds the spans, never
* re-interprets the markdown.
*
* The drain is deliberately one-way: a stream that resumes afterwards
* (reconnect/continuation) reveals paced but unfaded, because re-arming
Expand Down
2 changes: 1 addition & 1 deletion apps/sim/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,7 @@
"sim": "workspace:*",
"socket.io-client": "4.8.4",
"ssh2": "^1.17.0",
"streamdown": "2.5.0",
"streamdown": "2.7.1",
"stripe": "18.5.0",
"tldts": "7.4.19",
"twilio": "6.2.0",
Expand Down
10 changes: 4 additions & 6 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/workflow-renderer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@
"react-dom": "19.2.4",
"remark-breaks": "^4.0.0",
"remark-gfm": "^4.0.1",
"streamdown": "2.5.0",
"streamdown": "2.7.1",
"typescript": "^7.0.2",
"vitest": "^5.1.0"
}
Expand Down
Loading