Docs backfill for social bar v2 (9873eed): schema v6->v8 in TASKS.md/ai.md, social bar architecture section + Simple Icons CC0 notice, README structure rows, HANDOFF rewritten (committed+pushed)

This commit is contained in:
2026-08-12 18:39:08 -07:00
parent 9873eeda7d
commit 7c456042a6
5 changed files with 71 additions and 24 deletions
+8 -6
View File
@@ -7,8 +7,10 @@
## Session state (last updated: 2026-08-12)
- **Branch:** `main` (work UNCOMMITTED — see dirty list). Includes the previous
session's socials v2 fixes + this session's fediverse-service-icon work.
- **Branch:** `main` — the social bar v2 push is **committed + pushed** (`9873eed`,
18 files, +1878/321, 112 tests passing, 0 warnings). Working tree is **dirty
with the post-push docs backfill only** (ai.md, README.md, TASKS.md schema
section, THIRD-PARTY-NOTICES.txt) — ready to commit when the user says so.
- **Finished this session (round 3):** user's live-test report said the mastodon
icon still didn't show for `@gramps@llamachile.tube`. Root cause: the identity
domain `llamachile.tube` is YunoHost-SSO-gated — `/.well-known/nodeinfo`,
@@ -20,7 +22,7 @@
identity domain fails, follows the root redirect (`ResolveInstanceHostAsync`,
reads `resp.RequestMessage.RequestUri.Host`) and re-runs the nodeinfo lookup
on the resolved host. New test `FediverseHandle_RootRedirectToSubdomain_ResolvesSoftware`.
Build: **0 warnings**. Tests: **111 passing**.
Build: **0 warnings**. Tests: **112 passing**.
- **What landed (all rounds this session):**
1. **Fediverse icon resolution**`DetectService` maps `@user@domain` to the
new `SocialService.Fediverse` (was `Link`/chain icon). On validate,
@@ -61,9 +63,9 @@
- Round-3 redirect fallback fires only when identity-domain nodeinfo fails;
the root follow happens automatically (HttpClient default auto-redirect),
and `RequestMessage.RequestUri.Host` is read from the final response.
- **Next step:** show the user the fixes and let them decide whether to commit.
Nothing queued beyond that — do not expand the task queue on your own.
(`ytLive.Tests`/`bin` + `/obj` are dirty build artifacts, not hand-authored.)
- **Next step:** commit + push the docs backfill (ai.md / README.md / TASKS.md
schema section / THIRD-PARTY-NOTICES.txt) once the user confirms. Nothing
else queued — do not expand the task queue on your own.
- **Secret/DB/port facts live:** OAuth client id/secret in `Helpers/OAuthCredentials.cs`;
OAuth session token in `Helpers/TokenStore.cs` (DPAPI → `%APPDATA%\ytLlive\ytLlive.auth`);
+4 -3
View File
@@ -45,12 +45,13 @@ dotnet run
| Path | Role |
|------|------|
| `Models/` | Scene, Source, QualityOption, StreamConfig, StreamHealth, YouTube channel/chat |
| `ViewModels/` | MainViewModel — scenes, stream controls, chat; GoLiveViewModel, ReuseImageViewModel |
| `Services/` | YouTubeAuthService (OAuth2), YouTubeStreamService (broadcast/health), YouTubeChatService (chat polling), LayoutStore (SQLite) |
| `Models/` | Scene, Source, QualityOption, StreamConfig, StreamHealth, YouTube channel/chat, Socials (social bar) |
| `ViewModels/` | MainViewModel — scenes, stream controls, chat; GoLiveViewModel, ReuseImageViewModel, SocialsDialogViewModel |
| `Services/` | YouTubeAuthService (OAuth2), YouTubeStreamService (broadcast/health), YouTubeChatService (chat polling), LayoutStore (SQLite), SocialValidator (social link/fediverse validation + nodeinfo icon resolution) |
| `Helpers/` | ViewModelBase, RelayCommand, ImageCache, AppLog, FocusPreservingListBox, OAuthCredentials |
| `Themes/` | `Controls.xaml` — the dark-theme control styles, merged once in `App.xaml` |
| `MainWindow.xaml` | Dark-theme main UI: scene/source panel, preview, chat, status bar |
| `SocialsDialog.xaml` | Social Media Site Promotion dialog — 6-slot social bar editor with sign-in gate and validation |
## Docs (memory map)
+14 -11
View File
@@ -401,12 +401,12 @@ cache refresh, empty payload, missing zip entry, downloader failure) — **78 pa
### Status: ✅ Done
1. ✅ SQLite database (`Microsoft.Data.Sqlite`) at `%APPDATA%\ytLlive\ytLlive.db`; schema versioned via `PRAGMA user_version` (currently **v6**)
1. ✅ SQLite database (`Microsoft.Data.Sqlite`) at `%APPDATA%\ytLlive\ytLlive.db`; schema versioned via `PRAGMA user_version` (currently **v8**)
2. ✅ Assets live in the DB (BLOB keyed by SHA-256 content hash), never file paths — deleting the original file never breaks a scene
3. ✅ File-model save/open — the active layout file is tracked (default is the AppData DB); **Save Layout As… / Open Layout…** switch the active file; auto-save writes to whatever is active
4. ✅ Auto-save (invisible) — ~1.5s debounce on scene/source add/remove/reorder/rename/hide + any source transform change; flush on window close
5. ✅ Startup — load the active file; seed the five canonical scenes only when the DB is empty; (+) re-adds a missing canonical scene and is hidden once all five are present; adding beyond the five is rejected
6. ✅ Schema v1 → v6 — webcam columns (v2), singleton `Webcam` + per-scene `WebcamSceneConfig` (v3), `RectWidth`/`RectHeight` round-to-rect restore (v4), `Source.IsBackdrop` + `Source.CaptureKey` (v5), `Scene.HasBackdrop` — backdrop **Live-only by policy** (v6, one-time backfill + `EnforceBackdropPolicy` on every load); `WindowHandle` stays in-memory (per-session); save = transactional rewrite; orphaned assets pruned
6. ✅ Schema v1 → v8 — webcam columns (v2), singleton `Webcam` + per-scene `WebcamSceneConfig` (v3), `RectWidth`/`RectHeight` round-to-rect restore (v4), `Source.IsBackdrop` + `Source.CaptureKey` (v5), `Scene.HasBackdrop` — backdrop **Live-only by policy** (v6, one-time backfill + `EnforceBackdropPolicy` on every load), `Scene.HasSocialBar` (v7, dropped per-scene toggle — column back-compat, unread), `Socials.BarEnabled` (v8); the `SocialEntry.Software` fediverse-software column is a **column-presence migration** (commented v8→v9, no version bump — `user_version` stays 8); `WindowHandle` stays in-memory (per-session); save = transactional rewrite; orphaned assets pruned
### Design decisions
@@ -420,16 +420,19 @@ cache refresh, empty payload, missing zip entry, downloader failure) — **78 pa
**Save Layout As… / Open Layout…** switch the active file; auto-save writes to whatever is active.
4. **Auto-save (invisible)** — ~1.5s debounce on scene add/remove/reorder/rename/hide, source
add/remove/reorder, and any source transform change; flush on window close.
5. **Schema** — `Scene` (Id, Name, IsHidden, IsChatScene, HasBackdrop, SortOrder), `Asset` (Id, Hash, Data,
5. **Schema** — `Scene` (Id, Name, IsHidden, IsChatScene, HasBackdrop, HasSocialBar, SortOrder), `Asset` (Id, Hash, Data,
PixelWidth, PixelHeight), `Source` (Id, SceneId FK cascade, AssetId FK, Type, Name, IsEnabled,
X/Y/Width/Height/Opacity, MonitorIndex, DeviceId, ClipShape, IsMirrored, SortOrder)
`user_version` **6** (v1 → v2 = `ALTER TABLE` adds the two webcam columns; v3 = singleton
`Webcam` + per-scene `WebcamSceneConfig`; v4 = `WebcamSceneConfig.RectWidth`/`RectHeight` for the
round-to-rect restore; v5 = `Source.IsBackdrop` + `Source.CaptureKey` for the live-capture
backdrop; v6 = `Scene.HasBackdrop` — the backdrop is **Live-only by policy**
(one-time backfill turns Starting/BRB/Chat/Ending off and drops their backdrop
sources; `EnforceBackdropPolicy` re-normalizes every load). `WindowHandle` stays in-memory
(per-session). Save = transactional rewrite; orphaned assets pruned.
X/Y/Width/Height/Opacity, MonitorIndex, DeviceId, ClipShape, IsMirrored, SortOrder), `Socials` (Id,
BarPosition, BarJustify — back-compat, unread, BarEnabled added via `ALTER`), `SocialEntry` (Id,
SocialsId FK cascade, Service, Handle, ProfileUrl, SortOrder) — `user_version` **8** (v1 → v2 =
`ALTER TABLE` adds the two webcam columns; v3 = singleton `Webcam` + per-scene `WebcamSceneConfig`;
v4 = `WebcamSceneConfig.RectWidth`/`RectHeight` for the round-to-rect restore; v5 = `Source.IsBackdrop`
+ `Source.CaptureKey` for the live-capture backdrop; v6 = `Scene.HasBackdrop` — the backdrop is
**Live-only by policy** (one-time backfill turns Starting/BRB/Chat/Ending off and drops their backdrop
sources; `EnforceBackdropPolicy` re-normalizes every load); v7 = `Scene.HasSocialBar` (per-scene toggle
dropped — column back-compat, unread); v8 = `Socials.BarEnabled`). The `SocialEntry.Software`
fediverse-software column is a column-presence migration (commented v8→v9, no version bump).
`WindowHandle` stays in-memory (per-session). Save = transactional rewrite; orphaned assets pruned.
6. **Startup** — load the active file; seed the five canonical scenes
(Starting/Live/BRB/Chat/Ending, `SceneCatalog`) only when the DB is empty. The (+)
button re-adds a missing canonical scene and is hidden once all five are present;
+11
View File
@@ -68,6 +68,17 @@ the guardrails — the "never do" list is there on purpose.
License: MIT
Home: https://github.com/dotnet/
8. Simple Icons (bundled SVG logo path data for the social bar, e.g. the
mastodon/peertube/pixelfed fediverse logos)
Copyright (c) Simple Icons contributors (public domain dedication)
License: CC0 1.0 Universal (no rights reserved, no attribution required)
Home: https://simpleicons.org/
Source: https://github.com/simple-icons/simple-icons
Used as: path data compiled into the app's `SocialServiceIcons` so each
social entry renders the service's real logo. CC0 imposes no
obligations; it is listed here per this file's "list everything"
policy.
--------------------------------------------------------------------------------
MISCELLANEOUS
+34 -4
View File
@@ -89,9 +89,9 @@ C# / WPF (.NET 8) following MVVM:
| Path | Role |
|------|------|
| `Models/` | Plain data types — Scene, Source (incl. `ClipShape`, `IsMirrored`, `VideoImageSource`), QualityOption, StreamConfig, StreamHealth, YouTubeChannel, ChatMessage |
| `ViewModels/` | MainViewModel — exposes collections + commands for the UI; GoLiveViewModel, ReuseImageViewModel, CameraPickerViewModel |
| `Services/` | YouTube OAuth2, stream/broadcast management, live chat polling, LayoutStore (SQLite), **webcam: `VideoFrame` seam + `CameraDeviceInfo`/`ICameraEnumerator`/`ICameraFrameSource` interfaces + `MediaCaptureCameraEnumerator`/`MediaCaptureFrameSource` (WinRT) + `CameraManager`**, **screen capture: `IFullScreenDetector`/`Win32FullScreenDetector` + `IScreenCaptureSource`/`ScreenCaptureFrameSource` (WinRT GraphicsCapture) + `ScreenCaptureManager` + `ScreenCaptureSourceFactory` + `Direct3D11Helper`/`CaptureInterop` (COM bridges)** |
| `Models/` | Plain data types — Scene, Source (incl. `ClipShape`, `IsMirrored`, `VideoImageSource`), QualityOption, StreamConfig, StreamHealth, YouTubeChannel, ChatMessage, **Socials (`SocialService` enum + `SocialEntry`/`SocialsConfig` + `SocialServiceIcons`) — the social bar** |
| `ViewModels/` | MainViewModel — exposes collections + commands for the UI; GoLiveViewModel, ReuseImageViewModel, CameraPickerViewModel, **SocialsDialogViewModel** |
| `Services/` | YouTube OAuth2, stream/broadcast management, live chat polling, LayoutStore (SQLite), **SocialValidator (`ISocialValidator` seam + `HttpSocialValidator` default)**, **webcam: `VideoFrame` seam + `CameraDeviceInfo`/`ICameraEnumerator`/`ICameraFrameSource` interfaces + `MediaCaptureCameraEnumerator`/`MediaCaptureFrameSource` (WinRT) + `CameraManager`**, **screen capture: `IFullScreenDetector`/`Win32FullScreenDetector` + `IScreenCaptureSource`/`ScreenCaptureFrameSource` (WinRT GraphicsCapture) + `ScreenCaptureManager` + `ScreenCaptureSourceFactory` + `Direct3D11Helper`/`CaptureInterop` (COM bridges)** |
| `Helpers/` | ViewModelBase (INotifyPropertyChanged), RelayCommand, ImageCache, AppLog (file logger), FocusPreservingListBox, OAuthCredentials, **TokenStore (DPAPI session persistence)**, visibility converters |
| `Themes/` | `Controls.xaml` — the single dark-theme source, merged once in `App.xaml` (see `Themes/index.md`) |
| `MainWindow.xaml` | Dark theme; layout: top bar (controls), center (preview), left (scenes/sources), right (chat), bottom (health) |
@@ -112,7 +112,7 @@ C# / WPF (.NET 8) following MVVM:
### Current limitations / TODOs
- `Helpers/OAuthCredentials.cs` contains the real ClientId/ClientSecret. Auth is complete and the session **persists via Windows DPAPI** (`Helpers/TokenStore.cs``%APPDATA%\ytLlive\ytLlive.auth`, CurrentUser scope), reloaded best-effort at startup with a proactive refresh of a near-expiry access token. Sign-in/Change Account lives **inside the Start Stream dialog** (two-state flow — no separate Connect button). A **graceful End Livestream signs out**: `StopStream()` clears the session + token, so the next go-live needs a fresh sign-in; a crash never runs End, so the token survives and the creator stays signed in. `YouTubeAuthService` takes an optional `HttpClient` + `sessionChanged` callback (test seam + save hook; services are still constructed in `MainViewModel`)
- Scene/source/asset layout persists (SQLite, schema v6); the OAuth session persists (DPAPI); the paid-unlock state does not (yet — itch.io key verification pending)
- Scene/source/asset layout + the social bar persist (SQLite, schema v8); the OAuth session persists (DPAPI); the paid-unlock state does not (yet — itch.io key verification pending)
- `YouTubeStreamService` uses hardcoded `1080p`/`60fps` and per-broadcast streams — must switch to the v3 `variable` reusable stream
- Webcam capture is shipped (milestone 1); the live desktop/game backdrop is shipped (ship task #1); **the output compositor (TASK 4 ship step 1) is SHIPPED**, **the FFmpeg locator (TASK 4 ship step 2) is SHIPPED** — full plan in `TASKS.md`; the encoder subprocess + RTMP push, audio capture, and the frame-pipeline wiring follow (each its own PR)
- `StreamConfig` defaults (`TargetBitrate=6000`, `Resolution="1920x1080"`) are stale — the live dropdown drives `StreamHealth.CurrentBitrate`/`FPS` instead
@@ -330,6 +330,32 @@ const). Constructor-injected search dirs / tools dir / downloader (`Func<string,
Task<byte[]>>`) keep it hermetic: tests fake the network with a real in-memory zip. Constructed in the
encoder step (not yet — this PR ships the seam + impl + tests only).
### Social bar (TASK 14 — shipped 2026-08-12, plan in TASKS.md)
A **global bar layer** (never a Source, no sources-list row) that sits over the bottom or top of the
output and carries the creator's social links — content-sized, centered, GREEN glow when ON, top/bottom
snap-drag (default BOTTOM, persisted `SocialBarPosition`). `Models/Socials.cs`: `SocialService` enum
(YouTube/Twitch/X/Instagram/TikTok/Facebook/Discord/Kick/Threads/Bluesky/GitHub/LinkedIn/Pinterest/
Snapchat/Reddit/WhatsApp/Telegram/Link/Website/**Fediverse**), `SocialEntry` (Service/Handle/ProfileUrl/
`FediverseSoftware`), `SocialsConfig` (Entries + `BarPosition` + `BarEnabled`; `BarJustify` dropped,
column back-compat). Configured in the "Social Media Site Promotion" dialog (`SocialsDialog.xaml` +
`ViewModels/SocialsDialogViewModel`, WPF-free, injected `ISocialValidator` + sign-in/sign-out fakes):
ON/OFF bar switch (schema v8), 6 fixed slots — row 1 always YouTube (signed-in → channel handle;
signed-out → sign-in gate → OAuth; delete → confirm sign-out), row 2 free, rows 36 lock icons on
freemium. Service detection (`SocialServiceIcons.DetectService`): URL domain / fediverse `@user@domain` →
**Fediverse** / bare→Website. Validation (`Services/SocialValidator.cs`, `ISocialValidator` seam +
`HttpSocialValidator` default): async GET of the canonical profile URL; 200/redirect = valid, 404/failure =
rejected. Fediverse additionally does a **best-effort nodeinfo lookup** (`/.well-known/nodeinfo` →
`software.name`, stored in `SocialEntry.FediverseSoftware` / the `SocialEntry.Software` column,
column-presence migration, no version bump) so the entry shows the **instance's real logo**
(`LogoDataForFediverse`: mastodon/peertube/pixelfed/misskey/lemmy/pleroma/firefish, generic fediverse
honeycomb fallback — Simple Icons CC0 path data, initials badges gone); nodeinfo failure still validates
(glyph falls back). If the identity domain's nodeinfo is blocked (YunoHost SSO gates
`/.well-known/nodeinfo` behind the login page) but the bare root 302s to the real instance, the lookup
**follows the root redirect** and asks the resolved host. Cancel is a hard stop: `LookupAsync` takes a
`CancellationToken` (dialog VM owns a CTS; Cancel/X/Save abort in-flight lookups, canceled continuations
never touch slot state).
### Licensing — do not violate (GA = paid product; see `THIRD-PARTY-NOTICES.txt`)
This product is closed-source and paid. Every third-party component must stay inside the LGPL/BSD/MIT
@@ -352,6 +378,10 @@ guardrails below — written down so a future "quick fix" never reintroduces a G
— the obligations are not optional.
- **Pin to a moving target** — the `latest` BtbN release tag floats. Only immutable autobuild tags give
a reproducible source offer. Record the tag + variant beside the URL (TASKS.md) every time the pin moves.
- **Use non-CC0 icon art** — the social bar's bundled SVG logo path data comes from **Simple Icons**
(CC0 1.0, public domain — see `THIRD-PARTY-NOTICES.txt`). Replacing or adding logos must stay CC0 or
another public-domain source; a logo asset under a copyleft or attribution license would contaminate
the paid product.
- **Forget the v1 license-texts gate** — `THIRD-PARTY-NOTICES.txt` links the canonical license texts; at
**v1 (GA)** the full texts of every license it names MUST ship alongside it (TASK 4 requirement 9 is the
release blocker). Queued early is wrong; the release pass owns it.