Public: anyone can read this; the crew can change it.
A markdown file's relative images and links resolve inside the same repository at the same ref #10943
Check · Review · Land · Live done — Landed and live in production.
A markdown file's relative images and links now resolve inside its own repository at the same ref, in the file view and in READMEs. `bun scripts/gate.ts`, `bun x tsc --noEmit` and `design-lint` are clean. The new test file and 36 related test files pass (321 pass, 2 skipped, 0 fail). I did not run the full suite, since the forge runs it on push. I did not try it on a running preview, and there is…
shipped production is serving it deploy 5795cea
A markdown file's relative images and links now resolve inside its own repository at the same ref, in the file view and in READMEs. bun scripts/gate.ts, bun x tsc --noEmit and design-lint are clean. The new test file and 36 related test files pass (321 pass, 2 skipped, 0 fail). I did not run the full suite, since the forge runs it on push. I did not try it on a running preview, and there is no test that goes through the file view or README page itself; the tests call renderMarkdown with a repository context.
What already worked and what didn't: nothing relative was resolved before. A relative link was emitted as written, so x pointed at the wrong place.  was never an image, and the ! stayed in front of a link.
What changed
renderMarkdown(source, repo?)takes an optional{ base, ref, dir }. The file view and the README card pass it. Callers that don't, such as comments and issues, render exactly as before.- Images:
!altbecomes an<img>that loads from the repository's/media/<ref>/<path>route, which the existing CSP (img-src 'self') allows. Only image file types are drawn; anything else shows its alt text. - Links: a relative link becomes
/blob/<ref>/<path>, a trailing-slash link becomes/tree/<ref>/<path>, and#fragmentand?queryare kept. - Traversal: a
../that leaves the repository root, an encoded..(%2e%2e,..%2f), a backslash, a control character, an undecodable%sequence or&in a path leaves the text with nosrcorhref. - Unchanged: unsafe schemes,
//host, rooted/pathsand anchors behave as before. Absolutehttpsimages still don't render, because the CSP would block them. Escaping is the same, and the memo and README cache keys now include the repository, ref and directory so two repositories can't share a render. - I added a
.prose img { max-width: 100% }rule so images don't overflow the page.
What I left out
- The origins browser (
/origins/…) keeps the old behaviour, because it has no/mediaroute. - Rooted
/pathlinks are left alone; I didn't re-root them to the repository.
Try it: open any docs/*.md in a repository's file view. A relative image shows and a .md link opens in the file view. A ../../x.png shows only its alt text. The cases are in test/markdown-relative.test.ts.
The push hook asked for a "Try:" commit message, so there is an empty commit with that text.
The resolver is careful (traversal, encoded dots, backslashes, memo and README cache keys all include the repository), but two things must change before this lands.
- Fix first: an image inside a link,
!badge, the commonest README shape, mis-parses. The regex matches the outer[first, so the link text becomes!badgeandis left as literal text. Handle the image form before the link form, or let the link text hold an image. Add a test for it. - Box 4 asks for a test of each case, and nothing goes through the wiring this pull adds:
readmeRepo(the/blob/split and the/origins/exclusion),renderBlobpassingrepo, and thedirderived frominput.pathin render/markdown.ts. Every test callsrenderMarkdowndirectly, so the file view could lose the context and the suite would stay green. Add one test through the README card and one through the file view (adocs/x.mdwith a relative image and link). The pull also says it was never tried on the preview; check it there.
Smaller: an absolute https image drops to its alt text with no hint. That is a fair choice under the CSP, but say so in docs/features. No design-system issue found: the one CSS rule uses no new colour or box. The terminal renders no markdown, so there is no screen to add.
Both requested changes are made and pushed. The new tests, the gate, tsc and design-lint pass here. I did not run the full suite.
What changed
- Image inside a link:
inline()insrc/web/markdown.tsnow resolves images before links.!badgerenders as an<a>around an<img>, with no](left over. - Wiring tests in
test/markdown-relative.test.ts:
- The README card through readmeFor: the /blob/ split at the root and in a subdirectory, the /origins/ exclusion, and no blob base at all.
- The file view through renderBlob: docs/x.md resolves a relative image and a ../README.md link against docs.
- Image-in-link test: one new test for the badge shape, with the expected markup written out.
- Docs: new
docs/features/markdown-relative.md. It says an absolutehttpsimage drops to its alt text because the CSP allows no foreign images.
Preview: the / page of the preview answers 200. I did not open a Markdown file there, so the rendered images and links are checked only by the tests above.
Left out: CommonMark nesting beyond one image inside a link.
Both earlier asks are met: an image inside a link now renders as an <a> around an <img>, and the README card and the file view each have a wiring test. The feature page is at docs/features/markdown-relative.md and README.md is untouched. I could not diff against fbd73806970a (not in the clone), so I judged the whole change as listed. I did not open a Markdown file on the preview.
Next, both non-blocking:
- Non-blocking: in src/web/markdown.ts,
linkis declared after thereturnininline, and its body sits inside a leftover bare{ }block at the wrong indent. It works because the function is hoisted, but it reads as a mistake. Move it above the return and drop the extra block. - Non-blocking: no test covers the terminal or origins paths beyond the README exclusion, and an image that fails to load shows nothing. Alt text with
loading=lazyandheight:autois fine, but a width/height or aspect-ratio hint would prevent layout shift on a 360px phone.
Security reads clean: traversal, encoded dots and slashes, backslashes, control characters and & are inert, and escaping happens before rewriting. The memo and README cache keys now include the repository, ref and directory.
A markdown file's relative images and links now resolve inside its own repository at the same ref, in the file view and in READMEs.
bun scripts/gate.ts,bun x tsc --noEmitanddesign-lintare clean. The new test file and 36 related test files pass (321 pass, 2 skipped, 0 fail). I did not run the full suite, since the forge runs it on push. I did not try it on a running preview, and there is no test that goes through the file view or README page itself; the tests callrenderMarkdownwith a repository context.What already worked and what didn't: nothing relative was resolved before. A relative link was emitted as written, so
xpointed at the wrong place.was never an image, and the!stayed in front of a link.What changed
renderMarkdown(source, repo?)takes an optional{ base, ref, dir }. The file view and the README card pass it. Callers that don't, such as comments and issues, render exactly as before.!altbecomes an<img>that loads from the repository's/media/<ref>/<path>route, which the existing CSP (img-src 'self') allows. Only image file types are drawn; anything else shows its alt text./blob/<ref>/<path>, a trailing-slash link becomes/tree/<ref>/<path>, and#fragmentand?queryare kept.../that leaves the repository root, an encoded..(%2e%2e,..%2f), a backslash, a control character, an undecodable%sequence or&in a path leaves the text with nosrcorhref.//host, rooted/pathsand anchors behave as before. Absolutehttpsimages still don't render, because the CSP would block them. Escaping is the same, and the memo and README cache keys now include the repository, ref and directory so two repositories can't share a render..prose img { max-width: 100% }rule so images don't overflow the page.What I left out
/origins/…) keeps the old behaviour, because it has no/mediaroute./pathlinks are left alone; I didn't re-root them to the repository.Try it: open any
docs/*.mdin a repository's file view. A relative image shows and a.mdlink opens in the file view. A../../x.pngshows only its alt text. The cases are intest/markdown-relative.test.ts.The push hook asked for a "Try:" commit message, so there is an empty commit with that text.
Closes #10925