From 70fe2b80b252aa81fc3fabf70f35ad544cd99865 Mon Sep 17 00:00:00 2001 From: gramps Date: Fri, 28 Aug 2026 11:16:46 -0700 Subject: [PATCH] =?UTF-8?q?Web=20source:=20render=20at=20canvas=20size=201?= =?UTF-8?q?920x1080=20=E2=80=94=20full-bleed=20widgets=20no=20longer=20tru?= =?UTF-8?q?ncated=20to=20upper-left?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- HANDOFF.md | 39 ++++++++++++++-------------- Services/WebView2Manager.cs | 16 ++++++------ TASKS.md | 21 +++++++-------- ytLive.Tests/WebView2ManagerTests.cs | 16 ++++++------ 4 files changed, 46 insertions(+), 46 deletions(-) diff --git a/HANDOFF.md b/HANDOFF.md index c63fc69..1742004 100644 --- a/HANDOFF.md +++ b/HANDOFF.md @@ -6,32 +6,33 @@ working). **NOT pushed — user rule: never push without explicit instruction (2026-08-24).** No feature branches pre-1.0: all work lands on `main` per work unit. -## The web-source rendering work (2026-08-28) — CURRENT MODEL: stable viewport + content-bounds crop +## The web-source rendering work (2026-08-28) — CURRENT MODEL: canvas-size viewport + content crop -**Background:** viewport==box made dragging the box reflow the page (truncation, scrollbars, -unrecoverable image, "janky at stressful geometries"). Fixed-viewport + `Uniform` left letterbox -dead space. CSS reset enlarged the gap. Scrollbars were called "super fail". The right/bottom -dead-space + "not anchored at (0,0)" report persisted across every model — dead space was -page-internal (widget content smaller than the viewport), which is now cropped out. +**Root cause finally isolated:** hosting the page at the ELEMENT-box size clips full-bleed widgets +(designed for ~1920×1080) to their upper-left chunk inside a small viewport — "image truncated to +the upper-left corner regardless of scaling." That truncation persisted across every earlier +model because all of them rendered the page at the box (or a mix); e917c41's full 1920×1080 +render was the only one that showed full content, and its letterbox dead space was purely the +display `Uniform` + the box aspect mismatch — not the render. -**The model that satisfies invariants (implemented, visual verification pending):** -- `Services/WebView2Manager.cs` — stable viewport = the element's size at `Register` (snapshot, - never tracked → no reflow/truncation during resize). `overflow:hidden` (html+body) + - transparent-bg injection on every nav (nothing else). After each navigation, +**The model (implemented, visual verification pending):** +- `Services/WebView2Manager.cs` — page renders at the MASTER CANVAS size (1920×1080), stable, + never tracked: full-bleed widgets render FULL (no upper-left truncation); dragging the box never + reflows the page; `overflow:hidden` (html+body) + transparent-bg injection. After each nav `QueryContentBoundsAsync` reads the page's real content rect - (`scrollWidth/scrollHeight` vs `innerWidth/innerHeight` via `ExecuteScriptAsync` + JSON) and - `CaptureFrame` **crops the capture to the actual content bounds, anchored at (0,0)** — dead - space inside the viewport no longer reaches the box. Immediate capture on `NavigationCompleted`. -- `MainWindow.xaml` — web `` uses `Stretch="Fill"`: the cropped frame maps edge-to-edge, - so the selection box always bounds the real widget. `FramePump` unchanged. -- Invariants: **page viewport stable; display Fill-crops to content and fills the box.** + (`scrollWidth/scrollHeight` vs `innerWidth/innerHeight`) and `CaptureFrame` **crops to the + actual content bounds anchored at (0,0)**. Immediate capture on `NavigationCompleted`. +- `MainWindow.xaml` — web `` `Stretch="Fill"`: the cropped 1920×1080 frame maps flush under + the box (Box == widget bounds at every size, pinned (0,0), no dead space). +- Invariants: **render at canvas size; crop to content; Fill to box.** **Property panel / sliders (2026-08-28):** web URI ✕ clears the textbox; web ✓/✕ icon buttons `IsTabStop="False"` so Tab walks X→Y→W→H→URI; Slider style `IsMoveToPointEnabled="True"` -restores click-anywhere-on-bar. +restores click-anywhere-on-bar. All three confirmed fixed by the creator. -Build: 0 warnings; tests 4/4. Rejected for the record: fixed 1920×1080+`Uniform` (letterbox), -CSS margin/100% reset (enlarged gap), viewport==box (reflow/truncation/scrollbars). +Build: 0 warnings; tests 4/4. Rejected for the record: box-sized viewport (upper-left truncation), +fixed 1920×1080 + `Uniform` (letterbox dead space), CSS margin/100% reset (enlarged gap), +viewport==box (reflow/truncation/scrollbars). ## Key facts / landmines diff --git a/Services/WebView2Manager.cs b/Services/WebView2Manager.cs index f112229..7a2ec62 100644 --- a/Services/WebView2Manager.cs +++ b/Services/WebView2Manager.cs @@ -45,16 +45,16 @@ public sealed class WebView2Manager : IDisposable { if (_sessions.ContainsKey(source.Id)) return; - // OBS model: the page renders ONCE at a stable viewport — the size the - // element was created at (snapshot, never tracked, so dragging the box - // never reflows/truncates the page and no scrollbars appear). The - // captured texture is then stretched by the display (Stretch=Fill) into - // whatever box the user draws, so the frame always bounds content with - // zero dead space. Geometry-agnostic: any widget, any box. + // OBS model: the page renders at the MASTER CANVAS size (1920×1080), + // stable, never tracked. Full-bleed widgets (designed for the canvas) + // render fully instead of being clipped to a small box-sized viewport; + // dragging the box never reflows the page. The capture is cropped to + // the widget's actual content bounds (QueryContentBoundsAsync) and the + // display (Stretch=Fill) maps it flush under the box, anchored at (0,0). var webView = new WebView2 { - Width = Math.Max(1, source.Width), - Height = Math.Max(1, source.Height), + Width = 1920, + Height = 1080, DefaultBackgroundColor = System.Drawing.Color.Transparent, Visibility = System.Windows.Visibility.Visible, }; diff --git a/TASKS.md b/TASKS.md index bc040b1..e49b8bf 100644 --- a/TASKS.md +++ b/TASKS.md @@ -917,17 +917,16 @@ capturing the primary display (which causes the infinity mirror effect). 8. ✅ Preview shows live web content (not just a blank rectangle) 9. ☐ Handle navigation errors, invalid URIs, timeout gracefully -**Rendering model (2026-08-28, stable viewport + content-bounds crop):** -the page renders ONCE at the size the element was created at (snapshot in `Register`, never -tracked) — dragging the box never reflows the page; scrollbars suppressed via -`overflow:hidden` html/body injection (background-transparent only otherwise). After each -navigation the manager queries the page's real content rect (`scrollWidth/scrollHeight` vs -`innerWidth/innerHeight`) and **crops the capture to the actual content bounds, anchored at -(0,0)** — widgets that don't fill their viewport no longer stretch dead space across the box. -`Stretch="Fill"` maps the cropped frame edge-to-edge (box is covered; the fill/adornment rect -snaps to it). Graceful-error handling (item 9) is a follow-up. Rejected for the record: -fixed-viewport 1920×1080 + `Uniform` (letterbox), CSS margin/100% reset (enlarged gaps), -viewport==box (page reflow → truncation/scrollbars). +**Rendering model (2026-08-28, canvas-size viewport + content-bounds crop):** +the page renders at the MASTER CANVAS size (1920×1080), stable, never tracked. Two earlier bugs +are both dead: (a) box-sized viewports clip full-bleed widgets to their upper-left chunk +("truncated to the upper-left corner regardless of scaling" — the image is desktop-line space), +(b) dragging the box reflowed the page (truncation/scrollbars). Scrollbars suppressed via +`overflow:hidden` html/body injection. After each navigation the manager queries the page's real +content rect (`scrollWidth/scrollHeight` vs `innerWidth/innerHeight`) and **crops the capture to +the actual content bounds, anchored at (0,0)**. `Stretch="Fill"` maps the frame flush under the +box — selection/adornment box always bounds the widget, pinned to (0,0), for any widget geometry. +Graceful-error handling (item 9) is a follow-up. **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). diff --git a/ytLive.Tests/WebView2ManagerTests.cs b/ytLive.Tests/WebView2ManagerTests.cs index 018fd98..783dec4 100644 --- a/ytLive.Tests/WebView2ManagerTests.cs +++ b/ytLive.Tests/WebView2ManagerTests.cs @@ -20,7 +20,7 @@ public sealed class WebView2ManagerTests } [Fact] - public void WebView2_Control_Size_Snapshots_Source_At_Register() + public void WebView2_Control_Size_Is_Canvas_Resolution_Regardless_Of_Source() { _app.Run(() => { @@ -36,8 +36,8 @@ public sealed class WebView2ManagerTests manager.Register(source); var webView = panel.Children.OfType().First(); - Assert.Equal(640.0, webView.Width); - Assert.Equal(480.0, webView.Height); + Assert.Equal(1920.0, webView.Width); + Assert.Equal(1080.0, webView.Height); }); } @@ -62,13 +62,13 @@ public sealed class WebView2ManagerTests source.Width = 1024; source.Height = 768; - Assert.Equal(640.0, webView.Width); - Assert.Equal(480.0, webView.Height); + Assert.Equal(1920.0, webView.Width); + Assert.Equal(1080.0, webView.Height); }); } [Fact] - public void WebView2_Control_Size_Minimum_Is_One() + public void WebView2_Control_Size_Ignores_Zero_Sized_Source() { _app.Run(() => { @@ -84,8 +84,8 @@ public sealed class WebView2ManagerTests manager.Register(source); var webView = panel.Children.OfType().First(); - Assert.Equal(1.0, webView.Width); - Assert.Equal(1.0, webView.Height); + Assert.Equal(1920.0, webView.Width); + Assert.Equal(1080.0, webView.Height); }); }