[luv-legion-654] Fix broken images in all 14 translated READMEs - #654
Conversation
The root README.md sits at the repo root, so it writes repo-root-relative image paths (assets/logos/claude.svg, readme-arch-hq.gif). The translator is prompt-forbidden from rewriting paths, so every copy inherited them verbatim into docs/i18n/, two directories down, where they resolved to nothing: GitHub 404'd on docs/i18n/assets/..., and Mintlify — which also serves these pages at /i18n/README.<lang> — 403'd from S3. Every CLI logo and the architecture GIF were missing in all 14 languages. rebaseReadmePaths now re-points them at generation time. Images become absolute raw.githubusercontent.com URLs, the only form that renders on both surfaces: a ../../ path fixes GitHub but leaves Mintlify with no assets/ tree to walk into. Document links (./LICENSE, ./CONTRIBUTING.md) get ../../ instead, since GitHub is the only place a link to a repo file resolves and a raw URL there would serve unrendered plaintext. Paths inside fenced code blocks stay literal, and the rebase runs on the model output only — the wrapper's language selector already points at docs/i18n/ siblings. The 14 committed files are repaired in place with the same function. Add a broken-image check to validate:mdx so the class cannot ship again. A bad image path is valid MDX and valid YAML, so mintlify validate and the existing MDX parse both passed it straight through to a reader's browser — nothing in CI was watching. findBrokenAssetRefs resolves every local image reference on every docs page (site-absolute against docs/, everything else against the page's own directory) and fails with the path it resolved to. It runs in the CI docs job, in each per-language auto-translation job before its artifact is uploaded so the failing language is named rather than surfacing across 14 logs, and twice more in consolidate. The root README.md is checked too, since a bad path there propagates into 14 files as an absolute URL the check would no longer follow. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Hnj5rFHSw86gGXZTP6JsPU
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (2)
📝 WalkthroughWalkthroughThis PR rebases translated README links and asset URLs, updates translated README files, adds broken-asset detection to ChangesDocumentation path repair and validation
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant translate-docs.yml as translate-docs job
participant readme-translator.ts as readme-translator.ts
participant validate-mdx.ts as validate-mdx.ts
participant translated README files as translated README files
translate-docs.yml->>readme-translator.ts: generate translated README content
readme-translator.ts->>readme-translator.ts: sanitize output and rebase paths
readme-translator.ts->>translated README files: write translated README files
translate-docs.yml->>validate-mdx.ts: run bun run validate:mdx
validate-mdx.ts->>translated README files: parse pages and resolve asset references
validate-mdx.ts-->>translate-docs.yml: report parse or broken-image failures
Possibly related PRs
Suggested labels: Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
__tests__/scripts/translate-docs/readme-translator.test.tsESLint skipped: missing config or dependency (missing-dependency). The ESLint configuration references a package that is not available in the sandbox. scripts/translate-docs/readme-translator.tsESLint skipped: the ESLint configuration for this file references a package that is not available in the sandbox. 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 |
There was a problem hiding this comment.
Actionable comments posted: 5
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
docs/i18n/README.ru.md (1)
45-121: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winRebase dark-mode logo sources.
The
<img>paths use raw GitHub URLs, but the dark-mode<source srcset>paths still resolve underdocs/i18n/assets/logos/. Browsers that select dark mode will show broken logos.
docs/i18n/README.ru.md#L45-L121: replace every local dark-modesrcsetpath with its raw GitHub URL.docs/i18n/README.tr.md#L45-L121: replace every local dark-modesrcsetpath with its raw GitHub URL.docs/i18n/README.vi.md#L45-L121: replace every local dark-modesrcsetpath with its raw GitHub URL.docs/i18n/README.zh.md#L44-L120: replace every local dark-modesrcsetpath with its raw GitHub URL.docs/i18n/README.pt-br.md#L45-L121: replace every local dark-modesrcsetpath with its raw GitHub URL.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@docs/i18n/README.ru.md` around lines 45 - 121, Replace every local dark-mode source path in the logo <picture> blocks with the corresponding raw GitHub URL, matching the existing <img> URL pattern. Apply this consistently in docs/i18n/README.ru.md lines 45-121, docs/i18n/README.tr.md lines 45-121, docs/i18n/README.vi.md lines 45-121, docs/i18n/README.zh.md lines 44-120, and docs/i18n/README.pt-br.md lines 45-121; preserve the logo filenames and light-mode sources.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@__tests__/scripts/translate-docs/readme-translator.test.ts`:
- Around line 76-86: Extend the tests around translateReadme to exercise the
full render path rather than only rebaseReadmePaths. Mock or provide model
output containing a relative asset link, invoke translateReadme, and assert that
the asset is rebased while the assembled language selector still contains
README.zh.md unchanged.
In `@CHANGELOG.md`:
- Around line 9-10: Combine the two new CHANGELOG entries under ### Fixes into
one single-line entry describing both README image-path repair and broken-image
validation, preserving the existing details and PR reference.
In `@scripts/translate-docs/readme-translator.ts`:
- Around line 106-121: Update findFenceRanges so a candidate closing fence is
accepted only when the remainder of its line contains whitespace, not an info
string such as “ts”; preserve support for opening fences with info strings. Add
a regression test covering a fenced example containing a ```ts line and verify
later literal code remains protected from path rewriting.
- Around line 88-94: Update the README translation rewrite flow around rebase
and insideFence to parse each srcset candidate, rebase its URL, and preserve
descriptors and fencing behavior; add a regression test covering srcset values.
Regenerate docs/i18n/README.ar.md (47-50), docs/i18n/README.de.md (45-48),
docs/i18n/README.es.md (45-48), docs/i18n/README.fr.md (45-48),
docs/i18n/README.he.md (47-50), docs/i18n/README.hi.md (45-48),
docs/i18n/README.it.md (45-48), docs/i18n/README.ja.md (45-48), and
docs/i18n/README.ko.md (45-48) so their dark-mode srcset URLs use rebased paths;
each listed README requires regeneration only.
In `@scripts/validate-mdx.ts`:
- Around line 237-240: Update the reference extraction in the validation logic
around the refs array to also parse every URL candidate from srcset attributes,
including comma-separated candidates and their descriptors, while preserving
existing src, href, and Markdown-link handling. Add a regression test covering a
missing local srcset asset and ensure validation reports it as an unresolved
reference.
---
Outside diff comments:
In `@docs/i18n/README.ru.md`:
- Around line 45-121: Replace every local dark-mode source path in the logo
<picture> blocks with the corresponding raw GitHub URL, matching the existing
<img> URL pattern. Apply this consistently in docs/i18n/README.ru.md lines
45-121, docs/i18n/README.tr.md lines 45-121, docs/i18n/README.vi.md lines
45-121, docs/i18n/README.zh.md lines 44-120, and docs/i18n/README.pt-br.md lines
45-121; preserve the logo filenames and light-mode sources.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: a90675e7-0645-48bb-b145-817287e07d37
📒 Files selected for processing (21)
.github/workflows/ci.yml.github/workflows/translate-docs.ymlCHANGELOG.md__tests__/scripts/translate-docs/readme-translator.test.ts__tests__/scripts/validate-mdx.test.tsdocs/i18n/README.ar.mddocs/i18n/README.de.mddocs/i18n/README.es.mddocs/i18n/README.fr.mddocs/i18n/README.he.mddocs/i18n/README.hi.mddocs/i18n/README.it.mddocs/i18n/README.ja.mddocs/i18n/README.ko.mddocs/i18n/README.pt-br.mddocs/i18n/README.ru.mddocs/i18n/README.tr.mddocs/i18n/README.vi.mddocs/i18n/README.zh.mdscripts/translate-docs/readme-translator.tsscripts/validate-mdx.ts
CodeRabbit caught a real gap in the previous commit. Each logo cell in the README is a <picture> pairing an <img src> with a dark-mode <source srcset="assets/logos/*-dark.svg">. The rebase handled src and href only, so 8 dark-mode logos per file — 112 across the 14 translated READMEs — stayed broken, and stayed broken for dark-theme readers only, the half least likely to be caught by eye. findBrokenAssetRefs had the same blind spot, which is worse: the guard added to stop exactly this class from shipping would have gone green over it. Both now parse srcset, splitting the comma-separated candidate list and stripping each density/width descriptor. Adding it to the guard first turned the 112 broken paths into failures, which the repair then cleared. Also correct the closing-fence rule in findFenceRanges: CommonMark permits an info string on an opening fence only, so treating ```ts as a close ended a nested block early and exposed the sample paths after it to rewriting. Add a translateReadme call-site test. rebaseReadmePaths is only correct if it runs on the model output BEFORE the wrapper is attached — rebasing the assembled bytes would rewrite the language selector's sibling README.<lang>.md links to one directory too high. The unit tests pinned the function; this pins the ordering. Verified non-vacuous by mutation: moving the rebase to the assembled bytes fails it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Hnj5rFHSw86gGXZTP6JsPU
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
scripts/translate-docs/readme-translator.ts (1)
62-64: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winRecompute fence ranges after each rewrite pass.
fenceRangesuses offsets fromcontent. The Markdown pass can lengthen text before a fenced block. Thesrcandsrcsetpasses then use offsets from the longeroutstring.insideFence()can returnfalsefor an attribute that remains inside the fence. This rewrites literal sample paths.Recalculate
fenceRangesfromoutafter each replacement pass. Add a regression case with a rebased Markdown asset before a fence and an HTML asset inside the fence.As per coding guidelines, “Add unit tests for every new or changed behavior.”
Proposed fix
- const fenceRanges = findFenceRanges(content); + let fenceRanges = findFenceRanges(content); @@ let out = content.replace( @@ ); + fenceRanges = findFenceRanges(out); // HTML/JSX attributes: the README's logo table is a raw <table> of <img>. @@ ); + fenceRanges = findFenceRanges(out); // `srcset` needs its own pass: each logo cell is a <picture> whose dark-modeAlso applies to: 78-105
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@scripts/translate-docs/readme-translator.ts` around lines 62 - 64, Update the translation flow around insideFence and the Markdown, src, and srcset replacement passes so fenceRanges is recalculated from the current out string after each rewrite before subsequent fence checks. Preserve fenced content, and add a regression test covering a rebased Markdown asset before a fence and an HTML asset inside that fence.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@scripts/translate-docs/readme-translator.ts`:
- Around line 62-64: Update the translation flow around insideFence and the
Markdown, src, and srcset replacement passes so fenceRanges is recalculated from
the current out string after each rewrite before subsequent fence checks.
Preserve fenced content, and add a regression test covering a rebased Markdown
asset before a fence and an HTML asset inside that fence.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 5653e359-91f7-4acf-a01d-294931ac3b36
📒 Files selected for processing (19)
CHANGELOG.md__tests__/scripts/translate-docs/readme-translator.test.ts__tests__/scripts/validate-mdx.test.tsdocs/i18n/README.ar.mddocs/i18n/README.de.mddocs/i18n/README.es.mddocs/i18n/README.fr.mddocs/i18n/README.he.mddocs/i18n/README.hi.mddocs/i18n/README.it.mddocs/i18n/README.ja.mddocs/i18n/README.ko.mddocs/i18n/README.pt-br.mddocs/i18n/README.ru.mddocs/i18n/README.tr.mddocs/i18n/README.vi.mddocs/i18n/README.zh.mdscripts/translate-docs/readme-translator.tsscripts/validate-mdx.ts
🚧 Files skipped from review as they are similar to previous changes (17)
- CHANGELOG.md
- docs/i18n/README.zh.md
- docs/i18n/README.ja.md
- docs/i18n/README.he.md
- docs/i18n/README.ar.md
- docs/i18n/README.es.md
- docs/i18n/README.de.md
- docs/i18n/README.hi.md
- docs/i18n/README.ko.md
- docs/i18n/README.ru.md
- tests/scripts/validate-mdx.test.ts
- docs/i18n/README.vi.md
- docs/i18n/README.it.md
- docs/i18n/README.tr.md
- docs/i18n/README.fr.md
- scripts/validate-mdx.ts
- docs/i18n/README.pt-br.md
Third real find from CodeRabbit. rebaseReadmePaths computed fence ranges once from the input, but String.replace reports offsets into the string it is scanning — so once the markdown pass rewrote a path AHEAD of a fence, the text lengthened and that fence moved. The later src and srcset passes then tested offsets in the longer string against a map built from the shorter one, concluded a literal `<img src="assets/logos/claude.svg" />` inside a ```html block was ordinary markup, and rewrote the sample path the fence guard exists to protect. Reproduced with a rebased GIF link above an html fence: the fenced src was rewritten. The map is now rebuilt from the current string before each pass — within a pass it stays valid, since every offset comes from the same input. The 14 committed READMEs are unaffected (verified stable under the fixed function); this guards the sample-bearing pages a future English README will have. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Hnj5rFHSw86gGXZTP6JsPU
Summary
Every image in all 14 translated READMEs has been broken since they were first generated. This fixes them and adds a CI check so the class cannot ship again.
Root cause
README.mdsits at the repo root, so it writes repo-root-relative image paths (assets/logos/claude.svg,readme-arch-hq.gif). The translator is prompt-forbidden from rewriting paths (translator.ts:67— "Preserve all URLs and paths"), so every copy inherited them verbatim intodocs/i18n/, two directories deeper, where they resolve to nothing.Verified broken on both consumers:
docs/i18n/assets/logos/claude.svg/i18n/README.<lang>)s3://…/exosphere/i18n/assets/logos/claude.svgEvery CLI logo — light and dark — plus the architecture GIF were missing in all 14 languages. The
docs/<lang>/*.mdxpages were never affected: they use site-absolute/agenteye/images/…, whichrewriteInternalLinksalready exempts from locale prefixing.Fix
rebaseReadmePathsinscripts/translate-docs/readme-translator.ts, applied to the model output before the wrapper is assembled:src,srcset, Markdown) → absoluteraw.githubusercontent.comURLs. A../../path fixes GitHub but leaves Mintlify with noassets/tree above the page to walk into; the absolute URL is the only form that renders on both../LICENSE,./CONTRIBUTING.md) →../../. GitHub is the only surface where a link to a repo file resolves at all, and a raw URL there would serve unrendered plaintext.srcsetgets its own pass, splitting the comma-separated candidate list and preserving each density/width descriptor. Each logo cell is a<picture>pairing an<img src>with a dark-mode<source srcset>— handling onlysrcleft the table half-broken, and only for dark-theme readers.findFenceRangesfollows CommonMark on closing fences (info string on the opener only), so a nested```tsno longer ends a block early.docs/i18n/siblings, and rewriting those would break every selector link. AtranslateReadmecall-site test pins that ordering; it was verified non-vacuous by mutation.The 14 committed files are repaired in place with the same function (the content-hash cache would otherwise have skipped them, since the English source has not changed).
Guard
findBrokenAssetRefsinscripts/validate-mdx.tsresolves every local image reference on every docs page —src,href, Markdown images, and eachsrcsetcandidate — site-absolute/…againstdocs/, everything else against the page's own directory, failing with the path it resolved to. A broken image path is valid MDX and valid YAML, somintlify validateand the existing MDX parse both passed it straight through to a reader's browser; nothing in CI was watching.It now runs in three places:
docsjob, blocking the PRconsolidate, wherevalidate:mdxalready ranThe root
README.mdis checked too, since a bad path there propagates into 14 files as an absolute URL the check would no longer follow.Verification
srcsetfirst surfaced the remaining 112 (8 × 14) before the repair cleared them.raw.githubusercontent.comURLs return 200; all remaining local refs resolve on disk.✓ 720 MDX page(s) parsed cleanly with no broken images.tscclean.__tests__/components/project-list.test.tsx(window.localStorageundefined) reproduce on a clean tree and pass in CI.🤖 Generated with Claude Code
https://claude.ai/code/session_01Hnj5rFHSw86gGXZTP6JsPU
Summary by CodeRabbit
Bug Fixes
New Features
srcsetentries and line locations.Documentation