Skip to content

feat(annotate): annotate live localhost design previews#1049

Open
notxcain wants to merge 1 commit into
backnotprop:mainfrom
notxcain:feat/localhost-live-preview-annotation
Open

feat(annotate): annotate live localhost design previews#1049
notxcain wants to merge 1 commit into
backnotprop:mainfrom
notxcain:feat/localhost-live-preview-annotation

Conversation

@notxcain

@notxcain notxcain commented Jul 14, 2026

Copy link
Copy Markdown

Summary

Adds plannotator annotate http://localhost:PORT/path — renders a live frontend dev-server page (as an HTML artifact) for annotation, instead of converting it to markdown.

Partially addresses #642 (annotate a live website/URL in a frame, no extension, works over SSH) — scoped here to localhost/loopback dev previews; arbitrary public URLs still convert to markdown.

  • Per-session reverse proxy forwards to the dev server, proxies its HMR websocket (no reload loop), and injects the annotation bridge into HTML responses. Loopback-only (isLoopbackUrl guard), bound to 127.0.0.1. Bun impl (packages/server/preview-proxy.ts) + node:http mirror for Pi (apps/pi-extension/server/previewProxy.ts).
  • Editor renders an <iframe src> at the proxy origin (additive optional src prop on HtmlViewer) so the real ES-module graph loads with no null-origin CORS problem; annotation works via the existing postMessage bridge. The srcDoc path is byte-for-byte unchanged when src is absent (@plannotator/ui seam rule).
  • Threads livePreviewUrl through the annotate /api/plan (Bun, OpenCode, Pi) and CLI dispatch, carrying the target path onto the iframe URL.
  • Live-preview sessions disable URL sharing, version history, and host-theme token injection.

Why a proxy (not <base> / srcdoc)

A srcDoc iframe is a null origin; a modern Vite dev server returns no Access-Control-Allow-Origin to a null origin, so its module scripts are CORS-blocked → blank page. And its URLs are root-absolute (/@vite/client, /src/main.tsx), which a <base> can't redirect. A proxy that owns the origin root solves both.

Caveat for reviewers — Pi runtime not fully verified

The Pi runtime's live-preview path (apps/pi-extension/server/previewProxy.ts, a node:http mirror of the Bun proxy) is not end-to-end verified. HTTP forwarding + bridge injection were smoke-tested against a real dev server, but the HMR-websocket passthrough was never exercised against a live HMR client — no Pi runtime was available this session. It is a dependency-free raw-socket implementation mirroring the Bun version. One known edge case: the WS passthrough hooks the socket connect event, which is correct for plain TCP but wrong for TLS — an https-loopback dev server with HMR would need secureConnect (plain-http loopback, the common case, is unaffected). The Claude Code path is verified end-to-end in a browser; OpenCode shares the same server/UI code but was only build-checked.

Add `plannotator annotate http://localhost:PORT/path`, which renders a live
frontend dev-server page (as an HTML artifact) for annotation instead of
converting it to markdown.

- Per-session reverse proxy forwards to the dev server, proxies its HMR
  websocket, and injects the annotation bridge into HTML responses. Loopback
  only (isLoopbackUrl guard), bound to 127.0.0.1. Bun impl in
  packages/server/preview-proxy.ts; node:http mirror in
  apps/pi-extension/server/previewProxy.ts.
- The editor renders an <iframe src> at the proxy origin (additive optional
  `src` prop on HtmlViewer) so the real ES-module graph loads with no
  null-origin CORS problem; annotation works via the existing postMessage
  bridge. The srcDoc path is unchanged when `src` is absent.
- The live-preview iframe renders like a real browser tab: an opaque white
  canvas with color-scheme:normal, so an unstyled/transparent page shows
  black-on-white instead of bleeding through to the host surface.
- Thread livePreviewUrl through the annotate /api/plan and CLI dispatch across
  all three runtimes (Claude Code, OpenCode, Pi), carrying the target path onto
  the iframe URL.
- Live-preview sessions disable URL sharing, version history, and host-theme
  token injection.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant