The creator (2026-10-21), on Live -> Layers -> webcam -> Properties: "the Y and H boxes under webcam properties should be the same width as the X and W boxes and those widths should be fixed to accomodate the max number of possible digits providing the geometry values" "also the border color drop drown box content should also include the name of the color next to the hex value as they in the drop-down list box" TASK 61 - one fixed width for the four geometry fields. The row is a DockPanel, and WPF sizes an element to its CONTENT unless it is told otherwise, while the LAST child of a DockPanel fills the remaining space. So X/W (not last) were as wide as their text and Y/H (last) stretched across the row - two widths for the same kind of field. Now all four carry Width="44" and the rows set LastChildFill="False" (without it the last field ignores its own Width and fills anyway). 44px is the measured width of "-1920" in the app's font at 11px (the largest geometry value - the master frame is 1920x1080 - plus a sign) plus the field's padding and border. Docs: https://learn.microsoft.com/en-us/dotnet/api/system.windows.controls.dockpanel.lastchildfill The guard MEASURES the widest value with WPF's FormattedText instead of asserting a magic number, so a font change cannot silently make the field too narrow: https://learn.microsoft.com/en-us/dotnet/api/system.windows.media.formattedtext TASK 62 - the border field names its colour. SceneElement.BorderColorName carries the palette's name for the current colour ("Crimson") and is raised when the colour changes; it is EMPTY for a custom hex the creator typed, because "no name" is the honest answer. BorderColorPalette.NameOf changed to match (it used to fall back to the hex itself; the TASK 60 test assertion was updated with it). The hex field keeps a fixed width (60) so the name sits right next to the hex instead of drifting to the row's far edge - the same trap as TASK 61. Guards: ytLive.Tests/GeometryFieldWidthTests.cs, ytLive.Tests/BorderColorNameTests.cs Full suite: 435/435 passed, 0 warnings.
21 KiB
HANDOFF — 2026-10-21 (the creator's three properties-panel asks shipped; the vv webcam Units 1+2 state below)
At a glance
- Branch main, tracking
origin/main. origin/main is still3a77662, so five commits are waiting to be pushed (TASK 58 the theme sweep, TASK 59 the properties hierarchy, TASK 60 the colour names, a docs commit, and TASK 61+62 the geometry field widths and the colour name beside the hex — the creator's properties-panel asks, done in one batch). Not pushed yet: the push happens at a sub-milestone/milestone or at the creator's word ("push it") — the creator has not said so for this batch.- 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:
originis nowhttps://llgit.llamachile.tube/gramps/LlamaCasty.git(HTTPS + the credential helper/home/gramps/bin/gitea-credential, which reads the password from the gitignoredCREDENTIALS.md; plaingit pushworks). The old SSH remote was broken twice over: the repo was renamedgramps/ytLlive→gramps/LlamaCasty, andllgit:2214refuses 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 inMyMistakes.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) andytLive.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/Foregroundinstead 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, BasedOnYtTextBox). 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|Circleas 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/IsRoundShapeonSceneElement) — a WPFRadioButtongroup 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 intoModels/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'sFormattedText— 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.
- 61: the X/Y/W/H fields share ONE fixed width (44px — the largest geometry value, 1920,
plus a sign) and their rows set
- 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). SeeMyMistakes.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 inMyMistakes.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.csApplyStreamQuality→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.xaml144–230) and compositor (Services/Compositor/SceneCompositor.cs389–397) both read the element's ownX/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:
WebcamSceneConfigtable; new columns viaMigrateWebcamConfigTable()as idempotentALTER TABLE … ADD COLUMNguarded onPRAGMA table_info+LayoutStore.Save.csINSERT andLayoutStore.Load.csSELECT. - Precedent for remembered geometry:
RectWidth/RectHeight+ToggleClipShape(Models/SceneElement.cs124–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+ClipShapeexactly 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 transientLandscape*fields while the vertical tier is live.BeginVerticalLayout/EndVerticalLayout/CommitLiveLayoutToVerticaldo the swap.Models/SceneElement.cs:X/Y/Width/Heightare nowvirtualso 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.csApplyStreamQualityis 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'sIsWebcamSelected(NOTSelectedElement.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 theClipShapeproperty → a locked camera cannot become Round by any writer (the toggle, the Shape button, the placement swap).SceneElement.ClipShapeandSceneElement.ToggleClipShape()arevirtualfor this. The lock is cleared before the 16:9 layout comes back, so the desktop shape is restored. - The circle is not OFFERED:
MainViewModel.CanToggleClipShapeis false for a Chat camera in the vertical tier → the Shape button in the webcam's properties disappears (not just disabled).MainViewModel.csraises it on the selection change;MainViewModel.Streaming.cson 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).
ResolveVerticalCameraLayoutnow 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 throughMoveWebcamInVerticalTier(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)
- ✅ 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. - ✅ 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
- 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(theSelectedElement.IsWebSourcepanel, ~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. - Follow-up spotted while doing TASK 60 (not fixed — out of scope for that change): the border
colour swatches in the drop-down are a plain
Borderwith the colour asBackgroundand no border, so the black (#000000) and dark swatches are nearly invisible against the dark menu (#16213e). A 1pxBorderBrush="#2a3a5e"on the swatch would fix it. - GOG Galaxy launcher shows "offline" (2026-10-21) — GOG's own overnight outage, NOT Windows. GOG
put
www.gog.combehind a maintenance page (503) and their Fastly services forauth.gog.com/api.gog.com/remote-config.gog.comreturn500 "Fastly error: unknown domain"; the page itself says "Galaxy Launcher will also be down." Window: 00:00–08:11 local on 2026-10-21 — the Wayback snapshot ofwww.gog.com/en/at 06:56 UTC is HTTP 200 with the full live storefront.status.gog.comis NXDOMAIN globally. Local exoneration chain: genuineGOG sp. z o.o → DigiCertcert (no MITM), local DNS == Google's authoritative answers, real Fastly POP headers, and GOG's own non-Fastly servers still answering. Nothing to fix locally — wait for GOG. Galaxy's logs (incl. the rotated ones) show no API-status failures between Oct 10 and Oct 20. Diagnosis recipe + thewebfetch-is-not-an- independent-egress trap:MyMistakes.md→ 2026-10-21 GOG Galaxy "offline". Galaxy 2.0 logs live inC:\ProgramData\GOG.com\Galaxy\logs\(NOT%LOCALAPPDATA%). ⚠️ that folder also holds a 2.7 GBcef.log(since Jul 13) — disk hygiene, see TASK 55. - verify.sh's known-failure whitelist is too loose (confirmed twice 2026-10-16): require EVERY failure
line to be whitelisted, and add
MasterLimiter_CapsTheLiveMix_OnThePipe+RealMouseDrag_OnTheLayerList_PersistsTheReorderto the list. - A pre-existing stash is still sitting there:
stash@{0}= "WIP on main:0464593Compositor: fast-path opaque 1:1 when source fills canvas" (NOT this session's; do not drop blindly). - The judder reproduction take in the FAILING geometry (vertical 1080p60 first) is still outstanding — no fix until a take names the stage.
- ⛔ Sudo password rotation pending (user; then update both
CREDENTIALS.mdpassword lines). - TASK 56 secret scan before
gramps/LlamaCastygoes public. - TASK 55 disk hygiene (
startup.loguncapped — 23MB and growing every 5s).
Tests
- Last full run: 435/435 passed, 0 warnings (2026-10-21, this session — after TASK 61's
GeometryFieldWidthTestsand TASK 62'sBorderColorNameTests; the runs before were 433/433 after TASK 60, 432/432 after TASK 59 and 431/431 after TASK 58). The one failure seen earlier in the session wasLayerReorderPersistenceTests.RealMouseDrag_OnTheLayerList_PersistsTheReorder, which passes in isolation (re-run: 3/3) — the known real-mouse flake, not a regression. - Known flaky-by-environment (all pass in isolation):
AudioPipelineTests.Mix_HonorsProviderGains…,MasterLimiter_CapsTheLiveMix_OnThePipe,LayerReorderPersistenceTests.RealMouseDrag…,Round_Clip_Corner….- ⚠️
LayerReorderPersistenceTests.RealMouseDrag_OnTheLayerList_PersistsTheReorderis NOT "passes in isolation" any more. During this session it FAILED ALONE on a clean tree at58b9a7d(the vv work stashed) with the same "Expected: ImgC / Actual: ImgA" — i.e. it is an environment/real-mouse flake, not a regression from the vv work — and it PASSES in the full-suite run (429/429). Treat it as environment-dependent, and don't chase it as a regression without stashing first.
- ⚠️
Secrets / paths (do not re-hunt)
- Layout DB:
%APPDATA%\ytLlive\ytLlive.db(SQLite, full-rewrite per save). Auth:%APPDATA%\ytLlive\ytLlive.auth(DPAPI). Startup log:%APPDATA%\ytLlive\startup.log(23MB). - Recordings land in
C:\Users\gramp\Downloads\recordings\(ty-<date>-<time>-<ms>-<seq>.mp4; a duplicate name gets a-2suffix). - Conversation history:
~/.local/share/opencode/opencode.db, tablesession_message— creator turns aretype='user'with text indata.text; my turns aretype='assistant'with reasoning AND the rendered reply concatenated intocontent[0].text. The reply marker's verbatim string is recorded inMyMistakes.md. - LAN credentials:
CREDENTIALS.md(gitignored) — ⚠️ stale for HA: it says192.168.50.144, the real Home Assistant is192.168.50.145(homeassistant.local, HAOS 18.2, Core 2026.10.1, SSHgramps/einstein,/configroot-owned).gorditois.144. Fix this file when next editing it. - Google Cloud OAuth project number
198374416003, owned byllamachile.shop@gmail.com— verify athttps://console.cloud.google.com/apis/credentials?project=198374416003.
Open (operational) — officerFriendly / PS5 "DNS errors" (2026-10-17, off-repo)
- Root cause: a ~43 h-wrong system clock on officerFriendly ×
dnssec = true. Pi 400, no RTC hardware at all, healthy oscillator (−7.39 ppm). Journal shows +54 min, +7 h, +42.9 h steps.pihole-FTL.db: 1,688,396dnssec=3rows on 10-11, 839,276 on 10-12 vs ≤23 other days, hitting PSN endpoints = the PS5's "DNS errors". KSK-rollover theory retracted. - Pending creator decision (do not apply unilaterally): tighten
/etc/systemd/timesyncd.conf.d/lan.conftoPollIntervalMinSec=16/PollIntervalMaxSec=64/ConnectionRetrySec=8/SaveIntervalSec=30, and/or add a DS3231 RTC (i2c-toolsalready installed). - LAN DNS routing vs creator intent: DHCP still hands out
.26+.1(clients query Pi-hole directly) whereas the creator wants everything →.1with BBR forwarding. Alsodnssec = trueon Pi-hole duplicates dnscrypt-proxy'srequire_dnssec = true. Both offered-for-veto, not done. - Router is the DHCP server (ASUS ROG GT-BE98 Pro
.1) — Pi-hole.26is DNS only, itsdhcp.leasesis 0 bytes.