Uh oh!
There was an error while loading. Please reload this page.
fix(web): render markdown images from workspace files - #4631
fix(web): render markdown images from workspace files#4631colonelpanic8 wants to merge 3 commits into
Conversation
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
ApprovabilityVerdict: Needs human review This PR introduces new user-facing functionality (rendering workspace images in markdown via signed asset URLs) rather than fixing existing behavior. The author is a first-time contributor to this repository. New features warrant human review. You can customize Macroscope's approvability policy. Learn more. |
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
…les) # Conflicts: # apps/web/src/components/ChatMarkdown.tsx
…les) # Conflicts: # apps/web/src/components/ChatMarkdown.tsx
…les) # Conflicts: # apps/web/src/components/ChatMarkdown.tsx
…les) # Conflicts: # apps/web/src/components/ChatMarkdown.tsx
…les) # Conflicts: # apps/web/src/components/ChatMarkdown.tsx
…les) # Conflicts: # apps/web/src/components/ChatMarkdown.tsx
…les) # Conflicts: # apps/web/src/components/ChatMarkdown.tsx
f455c9c to
dcc1ab0CompareMarkdown images whose source is a local path rendered as a broken image. The web origin only serves the client bundle, so `<img src="docs/shot.png">` hit the SPA fallback and decoded as HTML. Resolve local sources — relative to the thread cwd, absolute, or file:// — through the same signed workspace-asset URL the file preview panel already uses. Remote sources still pass through untouched, and a source the environment will not sign degrades to the existing file chip instead of a broken image. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
dcc1ab0 to
5f85c61CompareThere was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 5f85c61. Configure here.
Uh oh!
There was an error while loading. Please reload this page.
… files) # Conflicts: # apps/web/src/components/ChatMarkdown.tsx
… files) # Conflicts: # apps/web/src/components/ChatMarkdown.tsx
t3dotgg
commented
Aug 23, 2026
Note 🤖 GPT-5.6 Sol responding on behalf of Theo Closing this PR after an automated pass over open pull requests. Workspace image rendering already shipped in #6433. |

What Changed
ChatMarkdownhad noimgrenderer, so a markdown image whose source is a local path fell through to a bare<img src="docs/shots/portrait.png">. Local sources now resolve through the same signed workspace-asset URL that the file preview panel already uses.apps/web/src/components/ChatMarkdown.tsx— newimgcomponent. A source that resolves to a workspace file (relative to the threadcwd, absolute, orfile://) is rendered fromassets.createUrl({_tag: "workspace-file", …}). Remotehttp(s)sources pass through untouched.<button aria-label="Preview …">following the pattern already used for annotation crops inMessagesTimeline.apps/web/src/index.css—.chat-markdown-imagesizing (max 100% wide, 32rem tall, contained, bordered) and a loading placeholder. There was no image styling at all before.Also lifted the duplicated file-chip label construction out of the
arenderer intomarkdownFileLinkLabelso the image fallback builds the same label.Why
The web origin only serves the client bundle. An unknown path falls through to the SPA handler in
apps/server/src/http.ts, which answers withindex.html— so every local-path image decoded as HTML and rendered as the broken-image icon. Onlyhttp(s)sources could ever have worked.This matters because agents write these. Ask one to summarize UI work and it produces
pointing at screenshots it just captured in the workspace; every one of them rendered broken.The asset endpoint is the right lever rather than a new file-serving route: it already signs short-lived, claim-scoped URLs, already restricts to files inside the thread's workspace root, and already gates on previewable image types. The fix reuses that boundary instead of widening it, which is why an out-of-workspace path degrades to a chip instead of loading.
UI Changes
Before — a message with four workspace screenshots, all broken:
After — same message, same files:
Verification
New
apps/web/src/components/ChatMarkdown.test.tsxcovers the four cases: relative path, absolute path, refused asset (falls back to the chip), and remote URL (untouched). Confirmed the tests fail against the unmodified component — the baseline output is literally<img src="docs/shots/docked.png">.Integrated web pass through
test-t3-app: isolated dev environment, real project, the message above sent and rendered in the controlled browser. All four images load from/api/assets/…; a missing file renders the chip; a remote URL is passed through unchanged.Not addressed here, to keep this focused: mobile's
NativeMarkdownImagepasses the raw href to<Image source={{uri}}>and has the same defect, but it needs async resolution on a different render path.Checklist
Note
Low Risk
Scoped to chat markdown rendering and reuses existing signed-asset boundaries; adds tests and a small link-parser guard.
Overview
Chat markdown workspace image paths no longer render as broken
<img>tags pointing at filesystem paths. A newimgrenderer resolves relative, absolute, andfile://sources throughuseAssetUrlState(signedworkspace-fileURLs), leaveshttp(s)URLs unchanged, and shows a file chip when signing fails or the path is not a previewable image type.MarkdownWorkspaceImagehandles loading placeholders,onErrorfallback, and optional click-to-open in the file preview panel. File-chip labels are shared viamarkdownFileLinkLabel.resolveMarkdownFileLinkMetanow ignores protocol-relative (//…) URLs so they are not treated as workspace files. CSS adds.chat-markdown-imageand a loading placeholder.ChatMarkdown.test.tsxcovers the four main cases.Reviewed by Cursor Bugbot for commit 9296530. Bugbot is set up for automated code reviews on this repo. Configure here.
Note
Render workspace image files inline in chat markdown
MarkdownWorkspaceImagecomponent in ChatMarkdown.tsx that resolves local workspace image paths to signed asset URLs and renders them as<img>elements with lazy loading.MarkdownFileLink); remotehttp(s)and protocol-relative URLs are left unchanged.//...) from being treated as workspace file paths..chat-markdown-imageand.chat-markdown-image-placeholderto constrain size and apply consistent visual styling.cwdto themarkdownComponentsuseMemodependency array so image/link resolution updates when the working directory changes.Macroscope summarized 9296530.