SIGN IN SIGN UP

fix(annotate): render embedded local HTML documents instead of the editor app (#1561)

* fix(annotate): anchor raw-HTML documents at their own asset directory

A srcdoc document has no URL of its own, so every relative URL in it resolves
against the PARENT page — the Plannotator server — and an embedded sibling
(<iframe src="prototype.html">) hit the catch-all and rendered the editor app
inside the embed. A serve-time attribute rewrite cannot fix that: the src is
often assigned by script at runtime from data-src, and carries a query string.

rewriteHtmlAssetReferences gains a baseHref option that installs a <base href>
first in <head>, so RESOLUTION lands in the document's own token'd directory
whatever writes the URL and whenever. resolveHtmlAssetRoute is the one place
both runtimes decide what an /api/html-assets request means: .html/.htm now
serve as real documents under the 2MB annotate cap with a CSP sandbox that
grants scripting but never allow-same-origin, framed and document requests get
a small HTML error page naming the missing file, and the traversal guard is
unchanged. The share/inline path takes inertBase instead, so a portable export's
embeds render empty rather than resolving onto the host's own catch-all.

* fix(annotate): serve embedded local documents from the Bun asset route

rewriteHtml installs the root-relative <base href> (a srcdoc resolves its own
base against the parent's URL, so the port need not be known), the asset route
serves .html siblings with the sandbox CSP + nosniff, and the annotate
catch-all refuses a Sec-Fetch-Dest of iframe/frame/embed/object with a 404
document instead of handing a nested frame the editor app.

* fix(annotate): mirror embedded local documents in the Pi server

Same shared decision (resolveHtmlAssetRoute), Node transport: the asset handler
now takes the request so it can read Sec-Fetch-Dest, HTML responses carry the
sandbox CSP and nosniff, and the SPA fallback answers a framed request with the
404 document.

* feat(annotate): pin an embedded document as one element while armed

The bridge is never injected into a nested frame, so a click inside an embed
lands in another document. While pinpoint is armed (srcdoc sessions only, never
live-app) frames become pointer-transparent, so the click pins the <iframe>
itself; Interact hands the embed back for native use.

* fix(annotate): an armed click inside an embed pins the frame, not its wrapper

Pointer-transparent frames make hit-testing pass THROUGH the embed to the
container painted behind it, so the pin would name the wrapper div. A point
inside a frame's own rect now resolves to that frame, bounded to the frames
inside the element already resolved.

* docs: embedded local documents in raw-HTML annotate sessions
M
Michael Ramos committed
1954a19a5b7d350ad3f156478ff4f6af6d53cc17
Parent: 2a51b26
Committed by GitHub <noreply@github.com> on 9/17/2026, 8:29:47 PM