feat(backdrop): Capture Window… in-app window pin (TASK 38, Good Dog ONE test)
Click-to-pin an open window as the full-bleed Live backdrop, via the existing
window:<hwnd> capture path (same render layer as game/desktop). Session-scoped:
window:/picker: keys heal to auto on reload (HWND-recycle hazard); pin wins
while the window enumerates, then auto-fallbacks (game → desktop → static); a
dead window's capture session heals to auto, no dead ends.
Fix: ScreenCaptureSourceFactory.Resolve hex-parsed "window:0x<Hwnd>" WITHOUT
stripping the 0x prefix — NumberStyles.HexNumber rejects it, so every pin
resolved null ("No capture target for this key") and the heal silently rolled
back. Same flaw in IsAliveWindowPin (pin-wins guard was dead). Both fixed.
Commit also carries an out-of-scope prerequisite: PillRadioTests.cs:105 had a
committed stray token ("...PrimaryStartButtonLabel soil;", CS1003) blocking the
whole test-project compile — token removed.
Plus the previous session's uncommitted pricing docs (one-time $29/$49) that
share TASKS.md/ai.md/HANDOFF.md.
This commit is contained in:
+92
-40
@@ -1,58 +1,110 @@
|
||||
# HANDOFF — 2026-09-20 (ON-AIR armable signed-out shipped + pushed; Good Dog ONE-test ruling)
|
||||
# HANDOFF — 2026-09-21 (pricing docs UNCOMMITTED + TASK 38 Capture Window… implemented, UNCOMMITTED)
|
||||
|
||||
## Branch / Commit State
|
||||
|
||||
`main` HEAD = **`0c55645` — feat(pills): ON-AIR armable signed-out; Start face reads "Sign In"
|
||||
while armed signed-out (Good Dog 2026-09-20 ruling)** — **PUSHED** to `origin/main`
|
||||
(`5a1993b..0c55645`, non-force). Working tree **clean** (all 6 units of the change committed).
|
||||
`main` HEAD = **`523c17b` — docs(handoff): rewrite for pushed 0c55645** (last code commit is
|
||||
`0c55645`, ON-AIR armable signed-out — PUSHED). **Working tree DIRTY — TWO uncommitted work units:**
|
||||
|
||||
This is a continuation of the 2026-09-18 drag-reorder + 2026-09-20 radio-exclusive-pill work: the
|
||||
ON-AIR setter could only light while `IsConnected`, so a signed-out creator found a **greyed pill
|
||||
= dead end**. The ruling ships the armable signed-out pill.
|
||||
**Unit A — pricing docs session (started 2026-09-21, still uncommitted):**
|
||||
```
|
||||
M Distribution.md
|
||||
M README.md
|
||||
M TASKS.md ← now also carries this session's TASK 38 row
|
||||
M TASKS/task-10-monetization.md
|
||||
M ai.md ← now also carries this session's Capture Window… bullet
|
||||
```
|
||||
`MARCOM.md` and `MONETIZATION.md` were also edited but are **gitignored (confidential)** — never
|
||||
committed.
|
||||
|
||||
## ✅ What shipped this session (commit 0c55645, pushed)
|
||||
**Unit B — TASK 38 "Capture Window…" backdrop (this session, code + docs, uncommitted):**
|
||||
```
|
||||
M Controls/LeftPanel.xaml
|
||||
M Controls/LeftPanel.xaml.cs
|
||||
M Controls/PreviewPane.xaml
|
||||
M ViewModels/MainViewModel.Background.cs
|
||||
M ViewModels/MainViewModel.cs
|
||||
?? Services/Win32WindowEnumerator.cs
|
||||
?? ViewModels/MainViewModel.WindowBackdrop.cs
|
||||
?? ytLive.Tests/WindowBackdropIntegrationTests.cs
|
||||
M ytLive.Tests/PillRadioTests.cs ← out-of-scope prerequisite fix (below)
|
||||
?? Models/WindowInfo.cs
|
||||
?? Services/IWindowEnumerator.cs
|
||||
?? TASKS/task-38-window-backdrop.md
|
||||
```
|
||||
NOTE: `Models/WindowInfo.cs` and `Services/IWindowEnumerator.cs` show as `??` yet were already in
|
||||
the working tree before this session (referenced by ai.md) — they are new/untracked, so the seams
|
||||
grew out of existing code expecting them.
|
||||
|
||||
- **ON-AIR pill is intent, not account reality** — `OnAirPillOn` setter is **armable signed-out**
|
||||
(greyed/disabled-until-`IsConnected` gate REMOVED; a pill that could not light offline was the
|
||||
dead end the guard existed to forbid). REC-OR-live radio exclusivity is untouched — arming one
|
||||
clears the other; the radio constraint lives at the pills.
|
||||
- **Start button face is reality-aware**: `PrimaryStartButtonLabel` = `"Start"` while
|
||||
`IsConnected || !OnAirPillOn`, else **`"Sign In"`** when an ON-AIR pill is armed signed-out.
|
||||
Clicking Start while the ON-AIR pill is armed signed-out routes to **sign-in**, not a dead-end
|
||||
go-live (no dead ends — creator ruling; see ai.md §Streaming pills).
|
||||
- **One Good Dog test** (`ytLive.Tests/PillRadioTests.cs`): `PillRadioTests.Arming_One_Output_
|
||||
Pill_Clears_The_Other` (radio-exclusive) **+** the signed-out armable-pill headless test
|
||||
(`RealAppHost` + temp DB, no browser).
|
||||
- **ai.md** updated in the SAME commit: tooltip wording + the armable-signed-out ruling
|
||||
(2026-09-20) recorded; stale "greyed/disabled until `IsConnected`" fact replaced.
|
||||
## ⚠️ Open action items / landmines
|
||||
|
||||
## Deferred / queued (recorded in TASKS.md, NOT folded into this change)
|
||||
- **Polar product `d105dfa1-497e-423b-8cd4-e0ee2e3abbc0` is still a `$99/yr` subscription** — must be
|
||||
re-created as **one-time** ($29 founder / $49 list) before launch. Flagged in `ai.md`,
|
||||
`TASKS/task-10-monetization.md`, `Distribution.md`. `LLAMA50` discount changed 12mo→`once`;
|
||||
`LLAMAFOUNDER` unchanged (100% off, 50 uses, `once`).
|
||||
- **PillRadioTests corruption fixed out-of-scope:** `ytLive.Tests/PillRadioTests.cs:105` had a
|
||||
COMMITTED stray token (`…PrimaryStartButtonLabel soil;` CS1003) that blocked the ENTIRE test
|
||||
project compile (no tests could run at all). Fixed by deleting the stray word — ONLY change to
|
||||
that file. It's committed upstream in `0c55645`; upstream history should get a fix commit, the
|
||||
working-tree file is clean of it now. Flagged so no one re-hunts it.
|
||||
- **Stale "behind the picker" fact**: ai.md §Screen backdrop capture previously claimed window
|
||||
capture (`window:<hwnd>`) is picker-only; corrected to note the in-app submenu + session-scoped
|
||||
transience (2026-09-21).
|
||||
|
||||
- **TASK 37 — defaults vs current layout split**: DB holds static brandable `default` layout; the
|
||||
`current` layout carries the build-id and only loads when it matches `BuildStamp.Id` — enables
|
||||
one-click revert to factory defaults. Files: `TASKS/task-37-defaults-vs-current.md`.
|
||||
- **Manual take on webcam-between-sources order** (cross-type z-order fix `94e114b` already
|
||||
committed/pushed): relaunch, live view, drag Web Resource-0 below the webcam, restart, expect it
|
||||
pinned. Closes the bug with the builder before any further streaming work.
|
||||
- **Build-id-on-exit** demand: still queued for a later slice (do not fold in).
|
||||
## What this session did (TASK 38 — "Capture Window…", creator ruling 2026-09-21)
|
||||
|
||||
## Critical working rules (failure cap)
|
||||
In-app window-picker submenu on the Live backdrop context menus (PreviewPane + layer-row). Picking a
|
||||
window pins it as the full-bleed layer-0 backdrop via `RedesignateBackgroundAsync("window:0x…")` —
|
||||
same capture path as game/desktop, `window:` already resolves in `ScreenCaptureSourceFactory`. Full
|
||||
rulings + shipped items in `TASKS/task-38-window-backdrop.md`.
|
||||
|
||||
- **Good Dog = ONE integration test per change.** Do not pile up multiple tests for one guard.
|
||||
- New: `Win32WindowEnumerator` (EnumWindows, filtered like Win32FullScreenDetector),
|
||||
`ViewModels/MainViewModel.WindowBackdrop.cs` (`OpenWindows`, `RefreshWindowsCommand`,
|
||||
`CaptureWindowCommand`, `IsTransientCaptureKey`=`window:`+`picker:`, `IsAliveWindowPin`,
|
||||
`OnScreenCaptureFailed`).
|
||||
- Heals: `ReacquireScreenCaptures` strips transient keys before auto-detection (**session-scoped,
|
||||
HWND-recycle hazard**); `OnFullscreenMonitorChanged` respects a live window pin (**pin wins while
|
||||
alive**); `CaptureFailed` → auto re-acquire for transient keys (window death = no dead end).
|
||||
- Ctor order matters: `_windowEnumerator = WindowEnumeratorOverride ?? new Win32WindowEnumerator()`
|
||||
is assigned BEFORE `RefreshWindowsCommand`/`CaptureWindowCommand`/`RefreshOpenWindows()`.
|
||||
- LeftPanel row-menu bindings use Window-anchored `RelativeSource` (ContextMenu DataContext is the
|
||||
row Source, not the VM) — mirrors the existing PlacementTarget.Tag pattern.
|
||||
- **ONE Good Dog integration test** (`WindowBackdropIntegrationTests`): seam feeds the submenu; a
|
||||
persisted `window:` key heals away on reload; pinning an un-capturable window falls back to auto.
|
||||
NOTE: an earlier draft asserted the auto-fallback is non-empty — WRONG in the full suite, where
|
||||
ambient desktop state can legitimately yield `null` (static art). Final test accepts null as valid.
|
||||
- **Builds 0 warnings / 0 errors.** Full suite: **312/312 pass.** One wild-lasting detail: tests use
|
||||
the Windows dotnet host and MUST be rebuilt before vstest (stale-dll false failures).
|
||||
|
||||
## Pricing decision (2026-09-21) — record, do not re-derive
|
||||
|
||||
One-time perpetual license: **$29 founder** (launch→first 90 days) → **$49 list** at GA. Buy once,
|
||||
own forever; `IsPremium` never lapses (renewal/lapse code path dead). Free tier = full app,
|
||||
watermark-only. Driver = accessibility. Full reasoning in `MONETIZATION.md` (§Pricing, "Why these
|
||||
numbers"). $49 anchor, **$29 floor — never below**. Docs cascaded (unit A above).
|
||||
|
||||
## Next step
|
||||
|
||||
1. User reviews/commits this work unit (code + `ai.md`/`TASKS.md`/`TASKS/task-38…`/`HANDOFF.md` in
|
||||
the SAME commit, per rules). **No push unless it's a milestone.**
|
||||
2. Then the pricing docs unit (5 dirty files) per `TASKS/task-10-monetization.md`.
|
||||
3. Then TASK 10 implementation (perpetual-key model — renewal/lapse removal).
|
||||
|
||||
## Critical working rules (unchanged, still binding)
|
||||
|
||||
- **Good Dog = ONE integration test per change.**
|
||||
- **Scope lock:** declare the exact file list BEFORE editing; run `scripts/scope-check.sh` with the
|
||||
declared list before commit. No "while I'm here" edits.
|
||||
declared list before commit. No "while I'm here" edits. (Exception this session: the PillRadioTests
|
||||
syntax-error fix — required before ANY test could compile; declared at the time.)
|
||||
- **Run from WSL** always uses the Windows dotnet host (`/mnt/c/Program Files/dotnet/dotnet.exe`)
|
||||
with **quoted** paths — a path containing a space left unquoted was the #1 tool-spin cause this
|
||||
session. Test filter runs go through `dotnet vstest <dll> --filter …` (Windows DLL path), NOT
|
||||
`dotnet test` (WSL re-downloads the windowsdesktop packs + double-restores).
|
||||
- **Never type the username in paths.** Canonical root is `$(pwd)`. Byte-verify every edit anchor
|
||||
(node `fs` on `path.join(R, p)`) — the phantom-tree reads wasted this whole session.
|
||||
with **quoted** paths; test filter runs via `dotnet vstest <dll> --filter …`.
|
||||
- **ai.md + code in the SAME commit**; rewrite HANDOFF.md at session end or interruption.
|
||||
|
||||
## Where the machine facts live
|
||||
|
||||
- `%APPDATA%\ytLive\ytLive.db` = real layout DB (`layout.db` is a 0-byte legacy file). sqlite3 at
|
||||
`/root/android-sdk/platform-tools/sqlite3` (or the SDK copy) for DB checks.
|
||||
`/root/android-sdk/platform-tools/sqlite3`.
|
||||
- ffmpeg/ffprobe: `/mnt/c/Program Files/Krita (x64)/bin/` with Windows paths.
|
||||
- `ai.md` §Local recording (TASK 18, ≈lines 605-640) = the authoritative pill/state-model doc
|
||||
(record-OR-live, armable signed-out, Start-button-face).
|
||||
- `ai.md` §Local recording (TASK 18, ≈lines 605-640) = authoritative pill/state-model doc.
|
||||
- `ai.md` §Screen backdrop capture = authoritative backdrop/capture-key/heal doc (incl. the new
|
||||
TASK 38 bullet).
|
||||
- Business/marcom facts: `MONETIZATION.md` + `MARCOM.md` (both gitignored).
|
||||
Reference in New Issue
Block a user