# TASK 17 — Web source rendering (WebView2) > Catalog: [`TASKS.md`](../TASKS.md) — status and requirements live here. **Goal:** make the web source actually render URLs into the preview and stream output. ### Status: ✅ Done — required for v1 (2026-08-28) 1. ✅ Add `Microsoft.Web.WebView2` NuGet package 2. ✅ Schema v10: `WebUri TEXT` column on `Source` table + migration in `LayoutStore.cs` 3. ✅ Persist `Source.WebUri` on save/load (currently in-memory only — lost on restart) 4. ✅ Hidden off-screen `WebView2` control per web source — navigates to `WebUri`, renders in-app 5. ✅ Frame capture from WebView2 (**superseded 2026-09-14:** was `CoreWebView2.CapturePreviewAsync` → `WriteableBitmap` (BGRA8); now composition-capture via `CoreWebView2CompositionController` → `Windows.Graphics.Capture` — see the rendering-model update below. PNG polling is deleted) 6. ✅ Wire into `FramePump` resolver — `Source { Type: WebSource }` → latest WebView2 frame 7. ✅ Wire into `SceneCompositor` — render web source as an image element at its position/size 8. ✅ Preview shows live web content (not just a blank rectangle) 9. ☐ Handle navigation errors, invalid URIs, timeout gracefully **Rendering model (2026-08-28, canvas-size viewport + ALPHA-BBOX crop — DONE, creator-verified):** the page renders at the MASTER CANVAS size (1920×1080), stable, never tracked (no reflow/truncation; scrollbars suppressed via `overflow:hidden`). `FindContentBounds` scans the Bgra32 capture and crops to the bounding box of non-transparent pixels — the widget's true rendered extent, measured from the frame itself (NO DOM query, immune to layout timing; can never truncate content; full-canvas widgets fall through to the full frame = prior verified-good image). `Stretch="Fill"` maps the cropped frame flush under the selection box → box hugs the widget on all four sides. `QueryContentBoundsAsync` + the JS content-bounds script are DELETED. **RESULT: bounding verified PERFECT by the creator with two widgets.** Root-cause note on the earlier "remaining defect/gap": that was a WIDGET GLOW EFFECT (the widget's own CSS glow pushes out its perceived borders), not a code bug — the alpha-bbox was already hugging the glow halo. Lesson: test with a second, plain widget before changing code. ⚠️ Lesson bank: never return `JSON.stringify` from `ExecuteScriptAsync` (double-encodes); never measure DOM stuff on NavigationCompleted (unsettled layout broke the image — `a5b9952`); avoid sizing the container to the crop (`ab29ec8` reverted — made it worse); a widget glow effect can fake a gap — verify with a plain widget first; measure rendered pixels instead. **Rendering model update (2026-09-14, COMPOSITION capture — the PNG poll is gone):** frame capture is no longer `CapturePreviewAsync` → PNG (35–165ms per full-HD encode+decode capped real cadence at ~20Hz; item 5 below is superseded). Each session now renders through a `CoreWebView2CompositionController` visual into `Windows.Graphics.Capture` (`GraphicsCaptureItem.CreateFromVisual` of the root 1920×1080 `ContainerVisual`), pulled frame-driven from a free-threaded `Direct3D11CaptureFramePool` at the renderer's own rate (~60fps, parity with the browser). Straight-alpha readback, alpha-bbox crop + `CropBounds` per frame, epoch'd ring reuse, one shared crop-sized preview bitmap. The old `SetCaptureInterval` cadence hooks and `CaptureScheduler` are deleted; the hidden XAML `WebViewHostPanel` overlay and `CapturePreviewAsync` telemetry are gone. `TransparentBackgroundScript` is unchanged (a test pins it). Full design + the CoreMessaging DQ recipe in `../ai.md` → Slice 14. **Open: device verification of ~60fps web animation.** **Properties panel (2026-08-28):** web URI ✓/✕ icon buttons are `IsTabStop="False"` so Tab flows X→Y→W→H→URI; the ✕ button now clears the URI textbox (was reverting to the pre-accept snapshot). Slider style gained `IsMoveToPointEnabled="True"` — click-anywhere-on-bar jumps the thumb to the click (volume sliders keep their manual `SetSliderValueFromClick`, harmless duplication). ### Design decisions - WebView2 is the only option for Windows — it's pre-installed on Windows 10 20H2+ and Windows 11 - The web source is a standard element — positioned/sized/opacitied like any image source - Frame capture is drive-by-renderer (composition capture, ~60fps parity with the browser since 2026-09-14; the old "5-10 fps is fine" poll model is deleted) - This enables Streamlabs/StreamElements overlays via web URLs