diff --git a/frontend-web/src/core/utils/markdown.ts b/frontend-web/src/core/utils/markdown.ts index 4ab819ce..c5b80575 100644 --- a/frontend-web/src/core/utils/markdown.ts +++ b/frontend-web/src/core/utils/markdown.ts @@ -102,14 +102,18 @@ function candidateTextFromHref(href: string): string { * Injects a small runtime script into generated HTML so that clicking an * in-page anchor (``) works inside the preview iframe. * - * The hash MUST really change on click: multi-page documents (the - * html-multipage skill) show/hide their sub-page `
` - * blocks with CSS `:target` rules, which only fire on a fragment navigation. - * Intercepting with preventDefault + scrollIntoView alone leaves the hash - * untouched, so sub-pages stay `display:none` and clicks look dead. The - * handler therefore syncs `location.hash` (a same-document navigation — - * safe in the sandboxed blob: frame) and only smooth-scrolls afterwards when - * the target is actually rendered. + * The preview loads from a sandboxed `blob:` URL (opaque origin). Such a frame + * is FORBIDDEN from navigating to its own `blob:` URL, so changing the hash + * throws "Not allowed to load local resource: blob:…#page-x". And because CSS + * `:target` only updates on a real fragment navigation, the html-multipage + * skill's `:target`-based sub-page switching can never fire here. + * + * So we never touch the hash. Instead the handler emulates the skill's + * show/hide in JS: a "page" is a `.page` inside a `.pages` container; clicking + * a link to one shows it and hides its siblings (mirroring the + * `.page:target` + `~ #page-home` rules via inline `display`, which outranks + * the skill's class-level CSS). Plain in-page anchors (TOC headings, etc.) just + * smooth-scroll as before. */ export function injectInPageAnchorHandler(html: string): string { const script = ``; if (/<\/body>/i.test(html)) {