# HANDOFF — 2026-10-22 (the creator's properties-panel asks shipped, incl. TASK 63, and pushed; the vv webcam Units 1+2 state below) ## At a glance - **Branch** main, tracking `origin/main`. **The six-commit properties-panel batch is PUSHED** (2026-10-22, as a sub-milestone): TASK 58 the theme sweep, TASK 59 the properties hierarchy, TASK 60 the colour names, a docs commit, TASK 61+62 the geometry field widths and the colour name beside the hex, and TASK 63 the border-colour picker as the app's own drop-down — the creator's properties-panel asks, done in one batch. `origin/main` is now `987fdda`. - The vv feature (Units 1+2) **was** pushed at its close (2026-10-21 — the creator approved the Unit 1 push after testing it, and the completed feature is a milestone). - **The push path changed:** `origin` is now `https://llgit.llamachile.tube/gramps/LlamaCasty.git` (HTTPS + the credential helper `/home/gramps/bin/gitea-credential`, which reads the password from the gitignored `CREDENTIALS.md`; plain `git push` works). The old SSH remote was broken twice over: the repo was **renamed `gramps/ytLlive` → `gramps/LlamaCasty`**, and `llgit:2214` refuses the publickey of this machine's key even though it IS registered (account key id 6 "Corsair VSC"). Ask the Gitea REST API, don't do key archaeology — recipe in `MyMistakes.md` → 2026-10-21. - **This session shipped vv webcam Units 1+2** (the vertical 9:16 tier keeps its OWN camera placement per scene; 16:9 restores the desktop layout exactly; the Chat scene in vv is rect-only and can be enlarged to fill a whole 607×540 area). Code + memory in ONE commit each; the ONE Good Dog tests are `ytLive.Tests/VerticalWebcamLayoutTests.cs` (Unit 1) and `ytLive.Tests/VerticalChatCameraLayoutTests.cs` (Unit 2). **Only the creator's hands-on check is left** — see the vv section. - **TASK 58 — the properties panel now wears the theme** (shipped 2026-10-21). The creator's "why is the drop-down not in the same style as the others?" was a real defect: those drop-downs (and every check box, and every properties field) had been hand-painted with a `Background`/ `Foreground` instead of a style, so WPF drew the **light system template** on top of the dark panel. Now: `ComboBox` → `YtComboBox`, `CheckBox` → `YtCheckBox` (new), properties fields → `YtTextBoxCompact` (new, BasedOn `YtTextBox`). Guard: `ytLive.Tests/PropertiesStyleTests.cs`. - **TASK 59 — the properties panel is a hierarchy** (shipped 2026-10-21). A title owns its content and the content is indented one level (10px): `Shape` (new title) over the Mirror switch and the shape choice, the drag hint indented under that row, Geometry / Border Color / Border Thickness as titles with their fields indented, Background's Remove + Blur indented, and Strength one level deeper under Blur. **The shape became a CHOICE**: `Rect` | `Circle` as two segment options with **Rect selected by default** (the old single button showed the *next* action, "Round"). The mutual exclusion lives in the model (`IsRectShape`/`IsRoundShape` on `SceneElement`) — a WPF `RadioButton` group would kill the bindings. Guard: `ytLive.Tests/PropertiesHierarchyTests.cs`. - **TASK 60 — the border-colour drop-down names every colour** (shipped 2026-10-21). The creator: *"the #hex values are good, but add a corresponding color name/label next to the value"* — each row now reads `#e94560 Crimson` (the name dimmed, the tooltip "Crimson (#e94560)"). The 14 colours moved out of the click handler into `Models/BorderColorPalette.cs` (`NamedColor(Hex, Name)` + `NameOf(hex)`), so the list is testable and extensible. Guard: `ytLive.Tests/BorderColorPaletteTests.cs`. - **TASK 61 + 62 — the geometry fields and the border-colour field** (shipped 2026-10-21). - **61:** the X/Y/W/H fields share ONE fixed width (44px — the largest geometry value, 1920, plus a sign) and their rows set `LastChildFill="False"`, so Y/H no longer stretch to fill the row while X/W sized themselves to their text. Guard: `ytLive.Tests/GeometryFieldWidthTests.cs` (it MEASURES the widest value with WPF's `FormattedText` — measure, don't guess). - **62:** the border-colour field shows the palette's **name beside the hex** (`SceneElement.BorderColorName`, empty for a custom hex), and the hex field keeps a fixed width so the name stays next to the hex. The field stays editable (a custom hex can still be typed). Guard: `ytLive.Tests/BorderColorNameTests.cs`. - **TASK 63 - the border-colour picker IS the app's drop-down** (shipped 2026-10-21; **popup click deselection fixed 2026-10-22**). The creator: *"why does the drop down under the web cam properties not show the same style for the border color drop down list box?"* and *"we established look-and-feel standards. And yet you keep ignoring these and I'm wondering why."* **Root cause of the miss:** the TASK 58 sweep looked for control **types** (`ComboBox`/`CheckBox`/`TextBox`) without a theme style - and the border picker was NONE of those: it was a `...` `Button` whose `Click` handler built a `ContextMenu` in code. It was never in the sweep's scope, and `PropertiesStyleTests.cs` (a type-based guard) could not see it either. Now: the picker is a **`YtComboBox`** bound to the palette (`SelectedElement.BorderColorOptions` / `BorderColorEntry`), the **closed box shows the swatch + the colour's NAME**, the **rows show swatch + hex + name**, the hex field stays for a custom colour, the swatch is painted by the theme's `HexToBrush` converter, and the hand-built menu + `WebcamBorderColorPicker_Click` are **gone**. The closed box != the rows only through `Helpers/ClosedBoxTemplateSelector.cs` (WPF copies `ItemTemplate` into the read-only `SelectionBoxItemTemplate`; the selector is told them apart by the `ContentPresenter`'s templated parent - `ComboBox` = closed box, `ComboBoxItem` = a row). **2026-10-22 creator bug fix:** clicking a colour in the open `ComboBox` popup registered as an exit from the properties listing because `LeftPanel.IsDescendantOf` used `child is Visual ? VisualTreeHelper.GetParent(child) : LogicalTreeHelper.GetParent(child)` — at `PopupRoot` (which IS a `Visual`), `VisualTreeHelper.GetParent` returns `null`, so `IsDescendantOf` stopped without crossing `PopupRoot`'s logical parent (`PART_Popup` → `ComboBox` → `ElementsScrollViewer`), and `MainWindow.Window_PreviewMouseLeftButtonDown` cleared `SelectedElement = null`. Fixed by falling back to `LogicalTreeHelper.GetParent` / `TemplatedParent` when `VisualTreeHelper.GetParent` returns `null`. Guard: `ytLive.Tests/BorderColorPickerTests.cs` (model + panel + the **real widget**, including `LeftPanel.IsClickInside` on an open popup row and clicking a row to change `BorderColor`). The TASK 60 guard (`BorderColorPaletteTests`) was updated to guard the new wiring (the drop-down is bound to the palette; no second list of colours anywhere). - **TASK 55 slice 1 — the log is capped (shipped 2026-10-22).** `startup.log` was uncapped (23 MB and growing every few seconds — ffmpeg logs every stderr line). Now it rolls to `startup.1.log` at 5 MB (one rolled copy kept), so the growth is bounded. The policy is `Helpers/LogRotation.cs` (a pure function over a path — `AppLog`'s 156-call-site surface is unchanged; the seams are two `internal` properties). Guard: `ytLive.Tests/AppLogRotationTests.cs`. Slices 2 (temp alert-clip cleanup) and 3 (low-disk guard before recording) are still open. - **TASK 48 slice 2 — the app PACKS as a Store MSIX (shipped 2026-10-22).** `Packaging/Package.appxmanifest` (full trust: `Windows.FullTrustApplication` + `uap10:TrustLevel=mediumIL` + `uap10:RuntimeBehavior=packagedClassicApp` + `rescap runFullTrust`, plus webcam/microphone `DeviceCapability`s, x64, en-US only) + `Packaging/Assets/` (four logo tiles generated from the app's own artwork by `scripts/make-store-logos.py`) + `scripts/package-msix.sh` (publish `Release -r win-x64 --self-contained` → stage → `makeappx pack`, optional `MSIX_PFX` signing). **First real package built: `artifacts/LlamaCasty.msix`, 159 MB, 509 files, `Package creation succeeded`** (`artifacts/` is gitignored). Guard: `StorePackagingTests.The_package_manifest_declares_full_trust_camera_and_microphone`. ⚠️ Before submission the manifest's `Identity/@Publisher` and `PublisherDisplayName` must be set to the exact values Partner Center shows — a mismatch is a rejection. - **TASK 48 slice 3 — the self-updater is gone (shipped 2026-10-22).** The Store route (creator ruling 2026-09-27) means the Store updates the app, so the Velopack package + bootstrap were **deleted** (`ytLive.csproj`, `App.xaml.cs`) — a compliance action, not cleanup: the updater downloaded *and executed*, which Store policy 10.2.2 does not permit. The DO update-URL droplet dies with it. Guard: `ytLive.Tests/StorePackagingTests.cs` (no updater package, no updater API in any app source file). The app was smoke-tested after the removal: `MainWindow loaded`, no exceptions. **Still open on TASK 48:** slice 2 (the `Package.appxmanifest` + packaging project), slice 6 (the full-trust recording invariant comment), slice 7 (disqualifier re-check), slice 8 (the pre-submission gates) — and slices 4/5 are the creator's (Certification Kit, Partner Center listing, the demo account). - **The previous session (HA lights) produced NO ytLive code** — it was spent on the creator's Home Assistant light alert system (off-repo, `192.168.50.145`). See `MyMistakes.md` → 2026-10-21: the plan was one tweak to a verified-working script; the script was rewritten twice, the alert path broke, and the day was burned. **The HA work landed OK in the end** (recipes + measurements now in `MyMistakes.md`). Records below so the next session does not re-hunt. ## HA light alert system — current, verified state (2026-10-21) Five Tapo bulbs: `light.br1`, `light.br2`, `light.officeone`, `light.officetwo`, `light.death_star_light`. Purpose: the creator is **deaf** — lights are the alert channel; the phone (`notify.mobile_app_mis_lx9`) is secondary. **The iPhone is gone — never notify it.** | State | br1 / officeone | br2 / officetwo | Death Star | |---|---|---|---| | Idle | off | off | off | | Pump running | off | **off** | **blue only** (creator directive) | | Leak | warm-white 100% | purple | purple | | Alarm | warm-white 100% | red | red | | Doorbell | green, 8s | green, 8s | green, 8s | | Doorbell during an alert | — | suppressed — | — | | Clear | off | off | off | **Architecture:** `script.alerts_arm` / `alerts_clear` write only `input_text.light_alert` (`none|leak|alarm|motion`, restart-surviving). **`script.alerts_render_lights` is the single writer of the bulbs** — flat top-level `if`/`else` blocks (nested `if` and `choose:` both silently lit nothing; do not "improve" the shape). Priority: an alert always beats pump status. **⛔ THE ENTITY-ID TRAP (cost this session hours):** the YAML key is `alerts_render` but the entity id is **`script.alerts_render_lights`**. `script.turn_on` on a non-existent entity returns **HTTP 200 and does nothing** — no error, no log. All callers now use the `_lights` id. Check this first on any "script does nothing" symptom. **Pump status** is a *status*, not an alert (auto-clearing), and is keyed off `sensor.water_pump_current_consumption` — **never** `switch.water_pump_2`, which was observed latched `on` for hours at 0.0A draw and painted the bedroom blue at 5am. **HA files touched** (`/config`, root-owned → writes need `sudo`): `scripts.yaml`, `automations.yaml`, `configuration.yaml` (added `input_text.light_alert` / `_prev`). Backups: `scripts.yaml.bak-alertbuild`, `automations.yaml.bak-alertbuild`, `automations.yaml.bak-notify-20261020`, `configuration.yaml.bak-tplinkfix`. Zombie entities deleted from the registry: `script.leak_strobe`, `script.doorbell_flash` (old one), `automation.leak_strobe_notify_clear_restore`, `script.alerts_render_lights_2`. **Leak sensor is healthy** (`binary_sensor.pump_leak_moisture` = off, battery 100, signal 3) — an earlier "unavailable" claim was a bad read during a reload; `alert_leak_on` now also triggers on the latched `sensor.pump_leak_last_water_leak_alert`. ## vv webcam feature — locked rules (creator, 2026-10-17) — **Units 1+2 shipped 2026-10-21** | mode | shape | size | position | |---|---|---|---| | 16:9 | as today | as today | as today | | **vv · Live** | **circle OR rect, creator's choice** | small tile | one of six spots: top/bottom × L/C/R | | **vv · Chat** | **rect only — no circle option** | normal, or enlarged to fill one area (607×540) | enlarged → top or bottom | | Starting / BRB / Ending | unchanged | unchanged | unchanged | - **Placement is set by tap/click-drag with snap, exactly like the socials today**, plus manual entry in properties. **vv keeps its own camera position, size and shape per scene** — switching back to 16:9 restores the desktop layout exactly. - **No explicit borderless/no-background mode is needed** (round + border/bg options already allow it). - Creator's "square" means the rectangular view (the camera's own aspect), not a literal 1:1 box. ### What the code already gives us (do not re-hunt) - **vv = the active tier's centred band crop.** 1080p60-vertical: master band **x 656.25–1263.75 (607.5 wide), full height**, upscaled to 1080×1920 (`MainViewModel.Streaming.cs` `ApplyStreamQuality` → `OutputRect*`). The band splits into **two 607×540 areas** (top/bottom). - **The Live webcam sits at x≈1425 → entirely outside the band** — in vv it is invisible. That is the whole motivation. - **Geometry only — no render-path change.** Preview (`Controls/PreviewPane.xaml` 144–230) and compositor (`Services/Compositor/SceneCompositor.cs` 389–397) both read the element's own `X/Y/W/H` + `ClipShape`. - **Round / border / background are independent:** `ClipShape.Round`; `HasBorder` = width > 0 AND valid 6-hex colour; `BackgroundEffect` = None/Remove/Blur. - **Per-scene camera size policy exists**: `MaxWebcamWidthFor/HeightFor` (Chat 1358×764, others 960×540) + `ClampWebcamToBounds`. **A 607×540 tile sits inside both → no clamp change.** - **Storage:** `WebcamSceneConfig` table; new columns via `MigrateWebcamConfigTable()` as idempotent `ALTER TABLE … ADD COLUMN` guarded on `PRAGMA table_info` + `LayoutStore.Save.cs` INSERT and `LayoutStore.Load.cs` SELECT. - **Precedent for remembered geometry:** `RectWidth/RectHeight` + `ToggleClipShape` (`Models/SceneElement.cs` 124–152). ### What shipped in Unit 1 (2026-10-21) — do not re-hunt - **Geometry only — NO render-path change.** The tier switch swaps the element's LIVE geometry; the preview and the compositor read `X/Y/W/H` + `ClipShape` exactly as before. - `Models/VerticalCameraLayout.cs` (new): the pure math — band x 656.25, w 607.5, h 1080; two 607×540 areas; six spots; default tile **270×152 master px** (= 480×270 in the 1080×1920 output), **bottom-right** spot; inset 32 master px. - `Models/WebcamSceneConfig.cs`: the vertical placement is persisted in **seven additive columns** (`VerticalX/Y/Width/Height/VerticalClipShape/VerticalRectWidth/VerticalRectHeight`, NULL = "this scene has no vertical placement yet"); the 16:9 layout stays in the old columns and is **parked** in the transient `Landscape*` fields while the vertical tier is live. `BeginVerticalLayout` / `EndVerticalLayout` / `CommitLiveLayoutToVertical` do the swap. - `Models/SceneElement.cs`: `X/Y/Width/Height` are now **`virtual`** so the webcam clamps EVERY write (drag, resize, typed fields) into the band — outside the band = invisible in vv. - `ViewModels/MainViewModel.Webcam.cs`: `IsVerticalTier`/`VerticalBand` + `ResolveVerticalCameraLayouts` (the tier swap; the **Live scene gets its tile automatically** on the first vertical switch) + `MoveWebcamInVerticalTier`/`SnapWebcamInVerticalTier` (the six-spot snap, by the tile's CENTRE). `MainViewModel.Streaming.cs` `ApplyStreamQuality` is the tier-change hook. - `Controls/LeftPanel.xaml`: the webcam properties gained a **Geometry** section (X/Y/W/H) bound to the live placement, gated on the VM's `IsWebcamSelected` (NOT `SelectedElement.IsWebcam` — the TASK 51 test finds that panel by binding text and would match twice). - `Controls/PreviewPane.xaml.cs`: dragging/resizing a webcam under the vertical tier routes through the VM's snap instead of free-floating over the master. - Test seam: the drag lives in a **VM method**, so the test drives it without synthesising mouse input. ### What shipped in Unit 2 (the Chat scene, 2026-10-21) — do not re-hunt - **Rect only, and the lock is on the MODEL, not the UI.** `WebcamSceneConfig.VerticalShapeLocked` (set per scene by the VM) **overrides the `ClipShape` property** → a locked camera cannot become Round by any writer (the toggle, the Shape button, the placement swap). `SceneElement.ClipShape` and `SceneElement.ToggleClipShape()` are `virtual` for this. The lock is cleared **before** the 16:9 layout comes back, so the desktop shape is restored. - **The circle is not OFFERED:** `MainViewModel.CanToggleClipShape` is false for a Chat camera in the vertical tier → the Shape button in the webcam's properties **disappears** (not just disabled). `MainViewModel.cs` raises it on the selection change; `MainViewModel.Streaming.cs` on the tier change. - **Two sizes, snapped by the nearer one:** `VerticalCameraLayout.SizeFor(band, requestedWidth)` → the small tile (270×152) or one whole area (607.5×540). The resize handle therefore has two detents instead of any size in between. - **An enlarged camera fills the area its centre is in** — top or bottom (`VerticalCameraLayout.AreaFor`). - **The Chat camera is auto-tiled** like the Live one when the vertical tier is picked (otherwise the vertical chat stream has no camera at all). `ResolveVerticalCameraLayout` now auto-tiles Live **and** Chat; Starting/BRB/Ending keep their 16:9 placement. - The preview's resize routes through `MainViewModel.ResizeWebcamInVerticalTier` (the Chat camera's discrete size + the snap); the drag routes through `MoveWebcamInVerticalTier` (assign → clamp → snap, so the enlarged camera can be dragged from the top area to the bottom one). - *Test:* `ytLive.Tests/VerticalChatCameraLayoutTests.cs` — **PASSES** (in Chat under the vertical tier, enlarging fills the full top area and the circle option is not offered). ### Units (each: ONE Good Dog integration test, own commit, memory in the same commit) 1. ✅ **vv camera layout (Unit 1)** — shipped 2026-10-21. Stored separately per scene, resolved when the tier changes. Live: small tile (circle or rect) snapped to one of six spots; drag like the socials + properties entry. *Test:* `ytLive.Tests/VerticalWebcamLayoutTests.cs` — with the vertical tier active, dragging the Live camera to top-right places it top-right inside the band; switching back to 16:9 restores the original layout exactly; both placements survive a save + reload. **PASSES.** 2. ✅ **vv · Chat (Unit 2)** — shipped 2026-10-21. Rect enforced with the circle option unavailable, plus enlarge-to-one-area (607×540) filling the top or the bottom area by where the camera is. *Test:* `ytLive.Tests/VerticalChatCameraLayoutTests.cs` — in Chat under the vertical tier, enlarge sets the full top area and the circle option is not offered. **PASSES.** ## Next step - **✅ TASK 63 + popup-click fix verified by creator (2026-10-22)** — clicking a colour in the Border Color `YtComboBox` dropdown now stays inside `LeftPanel` (`IsDescendantOf` crosses `PopupRoot` → `PART_Popup` via `LogicalTreeHelper.GetParent`), updates the webcam border colour, and does not exit the properties listing. - **Creator check (needs the creator's hands)** — the last piece of the vv feature: switch the resolution dropdown to "Vertical 1080p60", confirm the Live camera tile lands inside the band and the **Circle** option (TASK 59 turned the Shape button into a Rect | Circle choice) turns the tile into a circle there, open the **Chat** scene and confirm the **Circle** option is **gone** (rect only) and that dragging the tile up and enlarging it fills a whole 607×540 area, then switch back and confirm the desktop layout is untouched. The unit tests cover the geometry, not the eyeball. - Nothing else is queued for llamacasty — the creator queues work. ## Open (operational) - **Follow-up (not fixed — out of scope for TASK 61):** the **web source** block's geometry rows in `Controls/LeftPanel.xaml` (the `SelectedElement.IsWebSource` panel, ~lines 387-402) have the identical shape and the identical defect as the webcam's did — the Y/H fields stretch to fill the row. Same one-line-per-field fix as TASK 61, when the creator wants it. - **✅ FIXED in TASK 63:** the border-colour swatches now carry a 1px `BorderBrush="#2a3a5e"`, so the black (`#000000`) and dark swatches are visible against the dark menu (`#16213e`). - **Follow-up spotted while doing TASK 63 (not fixed — out of scope for that change):** the theme has **no implicit `Button` style**, so every plain `