fix(web): harden the transparency injection to the OBS-standard element-wide !important wipe

The real-widget dumps (12:54/13:50) proved the OLD inline
element.style.background='transparent' injection holds only while the page has
nothing to paint: the capture was alpha-transparent, yet the recording showed a
black opaque box over the whole element rect (1231,679 705x396) once the widget
connected and repainted a container background-COLOR — CapturePreviewAsync always
honors page CSS (MicrosoftEdge/WebView2Feedback specs/BackgroundColor.md), so any
page-painted background wins over DefaultBackgroundColor.

This is the OBS-solved class (all web-uri resources paint their own background):
browser sources use a Custom CSS override, and the decade-validated formula for
arbitrary pages is a pre-parse <style> with 'background-color: transparent
!important' — https://obsproject.com/forum/threads/translucent-transparent-browser-source.59549/
('body { background-color: rgba(0,0,0,0) !important }') plus the div-level variant
for stubborn widgets (woahtech.com OBS custom-CSS guide).

Injection is now an idempotent pre-parse style element wiping background-color on
html,body,html * with !important (outranks every page rule, runs before page parse
via AddScriptToExecuteOnDocumentCreatedAsync). Only background-COLOR is targeted —
background images and widget art survive. Regression test asserts the element-wide
!important form and that the losing inline form is gone.

9/9 WebView2Manager tests, 0 warnings.
This commit is contained in:
2026-09-10 14:04:11 -07:00
parent ea347f211c
commit b4bba4bf68
4 changed files with 106 additions and 66 deletions
+44 -48
View File
@@ -1,63 +1,59 @@
# HANDOFF — 2026-09-10 (late afternoon)
# HANDOFF — 2026-09-10 (transparency hardening applied)
## Branch / Commit State
`main` HEAD currently = `f3d578c` (slice 12 audio telemetry). **Slice 13 (web transparency
diagnostic re-point) is uncommitted** in the working tree. Ahead of origin by 18, NOT pushing
(user ruling: no push until web-overlay transparency AND audio-silence are addressed).
`main` HEAD will be = the new transparency-hardening commit (slice 14). Ahead of
origin by 19, NOT pushing (user ruling: no push until web-overlay transparency AND
audio-silence are addressed).
## ACTIVE THREAD (the user's directive: ONE problem at a time)
## ACTIVE THREAD (ONE problem at a time)
**Web-uri transparency: still a black, opaque block** (take `ty-20260910-1202-0000-2.mp4` —
user: "the web-uri resource transparency is still a black, opaque block. The desired animation
runs just fine."). The animation-speed work is done; transparency is now THE web problem.
**Web-uri transparency — branch (a) CONFIRMED and FIX-APPLIED, awaiting the verify take.**
The 12:54/13:50 dumps proved the REAL widget document captures TRANSPARENT
(`%TEMP%\ytLive-web-8d7234ec…-w1..5.png` alpha max 0/logged alpha 100% zero) YET the
recording kept showing a black opaque box over the element rect (1231,679,705,396 — crisp
edges, interior mean RGB ≈(1,6,13), NOT the desktop). Conclusion: the OLD inline
`element.style.background='transparent'` injection only holds while the page has nothing
to paint; once the widget connects and repaints a container background-COLOR, the capture
re-opaques → black box. That is the OBS-solved class ("all web-uri resources paint their
own background").
Institutional memory (MyMistakes, spin-guard entry): WebView2 `CapturePreviewAsync` honors the
page's CSS — the pre-parse injection (`1a39b09`, `AddScriptToExecuteOnDocumentCreatedAsync`)
is the recorded fix and is INTACT in `WebView2Manager.cs` (line ~157). The "? VERIFY" was never
closed because the diagnostic dump + alpha log bound to the FIRST capture ever = the about:blank
placeholder (blind; 5/5 sessions alpha=0 rgb=0).
**Slice 14 change (commit target):** `WebView2Manager.TransparentBackgroundScript` is now a
pre-parse `<style id='ytl-transparent-bg'>` wiping `background-color:transparent !important`
on `html,body,html *` (background-images/art survive — only background-COLOR targeted).
Idempotent by element id; `!important` outranks every page rule (OBS forums 2016 `body {
background-color: rgba(0,0,0,0) !important }` + div variant, woahtech OBS custom-CSS guide —
both cited in the commit message and `MyMistakes.md`).
- `internal const` + regression test `TransparentBackgroundScript_Is_A_Important_Element_Wide_Wipe`
(asserts element-wide `!important`, style-element form, and that the losing inline
`.style.background=` form is gone).
- Build 0 warnings; 9/9 WebView2Manager tests pass.
**Slice 13 change (uncommitted):** re-point the instrument at reality —
- `WebSourceSession.WidgetDumpRemaining`; `NavigationCompleted` arms `= 5` when the newly-loaded
document is NOT about:blank.
- Next 5 captures dump `%TEMP%\ytLive-web-<id>-w1..5.png` + `AlphaStats(...)` + FindContentBounds
to startup.log.
- `AlphaStats` extracted from the old inline first-capture block.
## THE ONE REMAINING STEP (verify, no further analysis)
Build 0 warnings; 8/8 WebView2 tests pass (runtime not instantiable — re-point is log-only, no
new test). Suite otherwise 290/291 (pre-existing compositor pixel).
## THE DECISION LADDER (no more cargo-culting)
1. User launches the app with the widget sources active, waits ~10s, closes. (NO recording needed.)
2. Read the `widget capture [1..5/5]` lines in startup.log:
- **zero% ≈ 0 (opaque):** pre-parse injection did NOT hold for this widget's own CSS →
FIX = stronger transparent-background enforcement (document-level `!important` stylesheet
injected pre-parse + re-asserted, per the OBS user.css precedent) OR chroma-key the known
backdrop color in `CaptureFrame`. Pick after seeing the `-w1..5.png` PNGs.
- **zero% large + tight contentBounds:** the capture HAS transparent margins → the recording's
black block lives downstream → audit the compositor web-layer blend/underlay (PMA-vs-straight
alpha, black pre-fill). Do NOT touch the capture path.
3. Implement the branch's fix with ONE integration test (chroma-key math or compositor blend has
testable seams), docs same-commit, then ONE verification recording.
1. User REPLACES the app (build is current) and records the Live scene, widget animating,
like the 12:54/13:50 takes.
2. Verdict: element rect shows scene bg with widget art over it (NO black box) → transparency
C LOSED, push gate #1 clears. If it still shows a solid black box you can SEE at a glance
inside the widget's 705×396 area, bring it + then (and ONLY then) audit what other layer
paints that rect in the composite — the web capture has been exonerated twice.
3. Then the queued layer-order-save bug (dragging an element over another doesn't persist
`SortOrder`) is the next single use case.
## Other threads (paused)
- **Audio silence** — `f3d578c` added per-5s `Audio live:` telemetry; next take with desktop
audio ACTIVE + those lines names the stage. Push gate #2.
- **Web capture speed** — 30Hz NOT achieved (35-117ms/capture → effective ~10-14Hz). The
animation "runs fine" so the user is satisfied; revisit only if they want more.
- **Webcam missing** in the 10:38/12:02 takes — "MJPG negotiation refused (being used by
another process)" at startup. Queued behind transparency+audio.
- **Audio silence** — `f3d578c` has per-5s `Audio live:` telemetry; next take with desktop
audio ACTIVE names the stage. Push gate #2.
- **Webcam missing** — "MJPG negotiation refused (being used by another process)". Queued.
- **Web capture speed** — ~10-14Hz effective, user satisfied. Revisit only on request.
## Landmines
- testhost shares startup.log with app — filter by time.
- `taskkill //F //IM testhost.exe //IM ytLive.exe` before rebuild.
- testhost shares startup.log with the app — filter by time.
- App was running at commit time; `taskkill //F //IM ytLive.exe` (Windows `taskkill.exe`,
bash-quoted `//F //IM`) before rebuilds, and re-run if `MSB3021` copy-lock appears.
- Build/tests: `/mnt/c/Program Files/dotnet/dotnet.exe build …` / vstest.
- Probing: `/mnt/c/Program Files/Krita (x64)/bin/ffmpeg.exe` / `ffprobe.exe`.
- Do NOT delete the crop/compositor paths while debugging source alpha (MyMistakes rule 3 —
take-25 vandalism).
- The user is frustrated with take-loops; the widget dump needs NO recording — an app launch
suffices. Ask for launch + 10s + close, not a take.
- Probing: `/mnt/c/Program Files/Krita (x64)/bin/ffmpeg.exe` / `ffprobe.exe` — Windows exes
take Windows-style paths.
- Never re-derive the transparency story again — MyMistakes "SPIN GUARD → RESOLVED" is the
record (wash-rinse-repeat cost the user a whole session).