diff --git a/website/src/components/doc-page.js b/website/src/components/doc-page.js index d86dd50..4569f47 100644 --- a/website/src/components/doc-page.js +++ b/website/src/components/doc-page.js @@ -78,6 +78,18 @@ export async function loadDocContent(docPath) { link.setAttribute('rel', 'noopener noreferrer') }) + // Re-root relative image paths. Rendered AsciiDoc emits paths like + // "docs/workflow-diagram.svg" relative to the document, but doc pages are + // served under clean URLs (e.g. /spec-driven-development/), so the browser + // resolves the relative path against the route and 404s. Prefix the site + // base so it points at the asset's real location. Absolute, protocol and + // data URLs are left untouched. + contentEl.querySelectorAll('img[src]').forEach((img) => { + const src = img.getAttribute('src') + if (!src || /^(https?:|data:|\/|#)/.test(src)) return + img.setAttribute('src', `${import.meta.env.BASE_URL}${src}`) + }) + // Attach click-to-load handlers for any YouTube placeholders in the doc. // Keeps us DSGVO-compliant: YouTube is only contacted after user consent. hydrateYouTubeFacades(contentEl) diff --git a/website/src/components/doc-page.test.js b/website/src/components/doc-page.test.js index 2b41edd..917480d 100644 --- a/website/src/components/doc-page.test.js +++ b/website/src/components/doc-page.test.js @@ -42,4 +42,22 @@ describe('doc-page', () => { 'Failed to Load Documentation' ) }) + + it('re-roots relative image paths to the site base, leaving absolute ones', async () => { + global.fetch.mockResolvedValue({ + ok: true, + text: async () => + '

Spec

dext', + }) + + await loadDocContent('docs/spec-driven-workflow.adoc') + + const base = import.meta.env.BASE_URL + const rel = document.querySelector('#doc-content img[alt="d"]') + const ext = document.querySelector('#doc-content img[alt="ext"]') + // relative AsciiDoc image path must be re-rooted (clean-URL routes break it otherwise) + expect(rel.getAttribute('src')).toBe(`${base}docs/workflow-diagram.svg`) + // absolute/external sources are left untouched + expect(ext.getAttribute('src')).toBe('https://x.test/a.png') + }) })