Kolu currently has no webview / iframe capability. Adding one unlocks dev-server preview (the killer use case), doc/dashboard embeds while pairing, and — as a free side-effect — inclusion in tab-capture video recording (#632), since getDisplayMedia() already records the whole tab.
Architectural commitments
- A browser is a first-class canvas tile, not a sub-panel of a terminal. Rationale: Kolu's direction is mode-less canvas workspace (commit 6318813); tying the browser's lifetime to a terminal's
parentId would complect placement with terminal state and force preview to die when the terminal running pnpm dev restarts. Keep tile identity = URL, nothing more.
- Tile identity is just a URL. No repo/branch/port metadata on the tile. Ports are globally unique on the host, so there's nothing to disambiguate.
- Transparent localhost rewrite. User types
http://localhost:5173 in the URL bar; Kolu detects the localhost host and routes through the Hono reverse proxy. Proxy is invisible infrastructure, not a separate mode.
Phase 0 — Browser canvas tile, any URL, no proxy
Drop a tile on the canvas, type any URL, renders in an iframe. Full canvas-tile citizenship: movable, resizable, multi-instance, captured by recording for free. Handle X-Frame-Options / frame-ancestors: none blocks explicitly — detect and show an "open externally" fallback instead of a silent blank tile. Useful standalone for docs, Figma, Grafana, the Kolu website. localhost:* silently fails at this phase (or shows a hint that Phase 1 is needed).
Phase 1 — Transparent localhost rewrite via Hono reverse proxy
Same URL bar; http://localhost:NNNN or 127.0.0.1:NNNN detected and rewritten through a Hono proxy route before loading. Choose subdomain-based proxy from the start (NNNN.preview.<kolu-host>) rather than path-prefix, so Vite HMR WebSockets and absolute asset paths just work. SSRF guard: allowlist ports that some terminal has announced recently (plus user-confirmed manual entries). Handle WebSocket upgrade in the proxy for HMR. Tile model doesn't change — proxy is a URL resolver inside the fetch path.
Phase 2 — Port discovery from terminal stdout
Scrape terminal stdout for Local: http://localhost:NNNN patterns (Vite, Next, Astro, etc.). Ephemeral global suggestion list, labeled by announcing terminal ("5173 — from kolu:savage-touch"). Surface via: command palette entry ("Open preview…"), terminal context menu, or a small chrome-bar indicator. Pill-tree indicator per branch: derived at render time from "any terminal under this branch has a recently-announced URL" — no per-tile storage.
Non-goals for this issue
Three-worktree stress test
Three dev servers on three unique ports → three tiles, each a URL. Canvas composites all three; getDisplayMedia() records all three in one frame. No per-tile repo/branch metadata needed because ports are globally unique.
Kolu currently has no webview / iframe capability. Adding one unlocks dev-server preview (the killer use case), doc/dashboard embeds while pairing, and — as a free side-effect — inclusion in tab-capture video recording (#632), since
getDisplayMedia()already records the whole tab.Architectural commitments
parentIdwould complect placement with terminal state and force preview to die when the terminal runningpnpm devrestarts. Keep tile identity = URL, nothing more.http://localhost:5173in the URL bar; Kolu detects the localhost host and routes through the Hono reverse proxy. Proxy is invisible infrastructure, not a separate mode.Phase 0 — Browser canvas tile, any URL, no proxy
Drop a tile on the canvas, type any URL, renders in an iframe. Full canvas-tile citizenship: movable, resizable, multi-instance, captured by recording for free. Handle
X-Frame-Options/frame-ancestors: noneblocks explicitly — detect and show an "open externally" fallback instead of a silent blank tile. Useful standalone for docs, Figma, Grafana, the Kolu website.localhost:*silently fails at this phase (or shows a hint that Phase 1 is needed).X-Frame-Options/ CSPframe-ancestorsblock detection + fallback UI.webm)Phase 1 — Transparent localhost rewrite via Hono reverse proxy
Same URL bar;
http://localhost:NNNNor127.0.0.1:NNNNdetected and rewritten through a Hono proxy route before loading. Choose subdomain-based proxy from the start (NNNN.preview.<kolu-host>) rather than path-prefix, so Vite HMR WebSockets and absolute asset paths just work. SSRF guard: allowlist ports that some terminal has announced recently (plus user-confirmed manual entries). Handle WebSocket upgrade in the proxy for HMR. Tile model doesn't change — proxy is a URL resolver inside the fetch path.NNNN.preview.<host>→127.0.0.1:NNNN)localhost/127.0.0.1→ proxy subdomain)python -m http.serverPhase 2 — Port discovery from terminal stdout
Scrape terminal stdout for
Local: http://localhost:NNNNpatterns (Vite, Next, Astro, etc.). Ephemeral global suggestion list, labeled by announcing terminal ("5173 — fromkolu:savage-touch"). Surface via: command palette entry ("Open preview…"), terminal context menu, or a small chrome-bar indicator. Pill-tree indicator per branch: derived at render time from "any terminal under this branch has a recently-announced URL" — no per-tile storage.Non-goals for this issue
Three-worktree stress test
Three dev servers on three unique ports → three tiles, each a URL. Canvas composites all three;
getDisplayMedia()records all three in one frame. No per-tile repo/branch metadata needed because ports are globally unique.