Skip to content

[luv-legion-654] Fix broken images in all 14 translated READMEs - #654

Merged
NiveditJain merged 3 commits into
mainfrom
luv-legion-654
Aug 5, 2026
Merged

[luv-legion-654] Fix broken images in all 14 translated READMEs#654
NiveditJain merged 3 commits into
mainfrom
luv-legion-654

Conversation

@NiveditJain

@NiveditJain NiveditJain commented Aug 5, 2026

Copy link
Copy Markdown
Member

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.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 (translator.ts:67 — "Preserve all URLs and paths"), so every copy inherited them verbatim into docs/i18n/, two directories deeper, where they resolve to nothing.

Verified broken on both consumers:

Surface Requested Result
GitHub docs/i18n/assets/logos/claude.svg 404
Mintlify (serves these at /i18n/README.<lang>) s3://…/exosphere/i18n/assets/logos/claude.svg 403

Every CLI logo — light and dark — plus the architecture GIF were missing in all 14 languages. The docs/<lang>/*.mdx pages were never affected: they use site-absolute /agenteye/images/…, which rewriteInternalLinks already exempts from locale prefixing.

Fix

rebaseReadmePaths in scripts/translate-docs/readme-translator.ts, applied to the model output before the wrapper is assembled:

  • Images (src, srcset, Markdown ![…](…)) → absolute raw.githubusercontent.com URLs. A ../../ path fixes GitHub but leaves Mintlify with no assets/ tree above the page to walk into; the absolute URL is the only form that renders on both.
  • Document links (./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.
  • srcset gets 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 only src left the table half-broken, and only for dark-theme readers.
  • Paths inside fenced code blocks stay literal. findFenceRanges follows CommonMark on closing fences (info string on the opener only), so a nested ```ts no longer ends a block early.
  • The rebase runs on the model output only — the wrapper's language selector already points at docs/i18n/ siblings, and rewriting those would break every selector link. A translateReadme call-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

findBrokenAssetRefs in scripts/validate-mdx.ts resolves every local image reference on every docs page — src, href, Markdown images, and each srcset candidate — site-absolute /… against docs/, 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, so mintlify validate and 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:

  • the CI docs job, blocking the PR
  • each per-language auto-translate job, before its artifact uploads — so the language that actually broke is named, rather than surfacing across 14 logs after every language finishes
  • twice in consolidate, where validate:mdx already ran

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.

Verification

  • Reverting a translated README to its broken state makes the guard report every broken image with the path it resolved to; teaching the guard srcset first surfaced the remaining 112 (8 × 14) before the repair cleared them.
  • All rewritten raw.githubusercontent.com URLs return 200; all remaining local refs resolve on disk. ✓ 720 MDX page(s) parsed cleanly with no broken images.
  • 26 new unit tests; 226 script tests pass. Lint 0 errors, tsc clean.
  • Pre-existing, unrelated: 15 local failures in __tests__/components/project-list.test.tsx (window.localStorage undefined) reproduce on a clean tree and pass in CI.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Hnj5rFHSw86gGXZTP6JsPU

Summary by CodeRabbit

  • Bug Fixes

    • Fixed broken links and image references across translated README pages.
    • Improved rendering for repository links, images, and contribution guidance.
  • New Features

    • Added validation for broken local images and asset references, including srcset entries and line locations.
    • Translation checks now run before publishing to prevent invalid pages from being released.
  • Documentation

    • Updated the changelog with documentation link repairs and expanded validation coverage.

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
@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 5800d516-1203-4fe4-ab5a-061bfeec4262

📥 Commits

Reviewing files that changed from the base of the PR and between de8bfdb and 1f6ee04.

📒 Files selected for processing (2)
  • __tests__/scripts/translate-docs/readme-translator.test.ts
  • scripts/translate-docs/readme-translator.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • tests/scripts/translate-docs/readme-translator.test.ts
  • scripts/translate-docs/readme-translator.ts

📝 Walkthrough

Walkthrough

This PR rebases translated README links and asset URLs, updates translated README files, adds broken-asset detection to validate:mdx, expands related tests, and updates documentation workflows to run the stronger validation.

Changes

Documentation path repair and validation

Layer / File(s) Summary
README path rebasing
scripts/translate-docs/readme-translator.ts, __tests__/scripts/translate-docs/readme-translator.test.ts
Adds rebaseReadmePaths for translated README links and asset references. It skips fenced code blocks and runs before wrapper assembly. Tests cover rewrite rules and integration behavior.
Translated README reference updates
docs/i18n/README.*.md, CHANGELOG.md
Updates 14 translated READMEs to use repository-root-relative document links and raw GitHub asset URLs. The changelog records the changes.
Broken asset detection and CI wiring
scripts/validate-mdx.ts, __tests__/scripts/validate-mdx.test.ts, .github/workflows/translate-docs.yml, .github/workflows/ci.yml
Adds broken-asset detection, independent asset checks, root README scanning, detailed failure output, and earlier translated-output 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
Loading

Possibly related PRs

Suggested labels: bug

Suggested reviewers: hermes-exosphere

Poem

🐇 I traced each link from root to page,
and fixed the paths in every language.
The images now resolve with care,
and broken links no longer hide there.
Hop, hop — the docs are clean!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 62.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the primary change: fixing broken images in all 14 translated READMEs.
Description check ✅ Passed The description thoroughly explains the problem, fix, validation changes, affected files, and verification, but omits the template's Type of Change and Checklist sections.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch

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

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

__tests__/scripts/translate-docs/readme-translator.test.ts

ESLint 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.ts

ESLint 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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot added the bug Something isn't working label Aug 5, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 win

Rebase dark-mode logo sources.

The <img> paths use raw GitHub URLs, but the dark-mode <source srcset> paths still resolve under docs/i18n/assets/logos/. Browsers that select dark mode will show broken logos.

  • docs/i18n/README.ru.md#L45-L121: replace every local dark-mode srcset path with its raw GitHub URL.
  • docs/i18n/README.tr.md#L45-L121: replace every local dark-mode srcset path with its raw GitHub URL.
  • docs/i18n/README.vi.md#L45-L121: replace every local dark-mode srcset path with its raw GitHub URL.
  • docs/i18n/README.zh.md#L44-L120: replace every local dark-mode srcset path with its raw GitHub URL.
  • docs/i18n/README.pt-br.md#L45-L121: replace every local dark-mode srcset path 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

📥 Commits

Reviewing files that changed from the base of the PR and between aab657a and 9e47a11.

📒 Files selected for processing (21)
  • .github/workflows/ci.yml
  • .github/workflows/translate-docs.yml
  • CHANGELOG.md
  • __tests__/scripts/translate-docs/readme-translator.test.ts
  • __tests__/scripts/validate-mdx.test.ts
  • docs/i18n/README.ar.md
  • docs/i18n/README.de.md
  • docs/i18n/README.es.md
  • docs/i18n/README.fr.md
  • docs/i18n/README.he.md
  • docs/i18n/README.hi.md
  • docs/i18n/README.it.md
  • docs/i18n/README.ja.md
  • docs/i18n/README.ko.md
  • docs/i18n/README.pt-br.md
  • docs/i18n/README.ru.md
  • docs/i18n/README.tr.md
  • docs/i18n/README.vi.md
  • docs/i18n/README.zh.md
  • scripts/translate-docs/readme-translator.ts
  • scripts/validate-mdx.ts

Comment thread __tests__/scripts/translate-docs/readme-translator.test.ts
Comment thread CHANGELOG.md Outdated
Comment thread scripts/translate-docs/readme-translator.ts
Comment thread scripts/translate-docs/readme-translator.ts
Comment thread scripts/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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 win

Recompute fence ranges after each rewrite pass.

fenceRanges uses offsets from content. The Markdown pass can lengthen text before a fenced block. The src and srcset passes then use offsets from the longer out string. insideFence() can return false for an attribute that remains inside the fence. This rewrites literal sample paths.

Recalculate fenceRanges from out after 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-mode

Also 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

📥 Commits

Reviewing files that changed from the base of the PR and between 9e47a11 and de8bfdb.

📒 Files selected for processing (19)
  • CHANGELOG.md
  • __tests__/scripts/translate-docs/readme-translator.test.ts
  • __tests__/scripts/validate-mdx.test.ts
  • docs/i18n/README.ar.md
  • docs/i18n/README.de.md
  • docs/i18n/README.es.md
  • docs/i18n/README.fr.md
  • docs/i18n/README.he.md
  • docs/i18n/README.hi.md
  • docs/i18n/README.it.md
  • docs/i18n/README.ja.md
  • docs/i18n/README.ko.md
  • docs/i18n/README.pt-br.md
  • docs/i18n/README.ru.md
  • docs/i18n/README.tr.md
  • docs/i18n/README.vi.md
  • docs/i18n/README.zh.md
  • scripts/translate-docs/readme-translator.ts
  • scripts/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
@NiveditJain
NiveditJain merged commit 981f99c into main Aug 5, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants