From 7c456042a6693dda47b989a766724455ef9bdcba Mon Sep 17 00:00:00 2001 From: gramps Date: Wed, 12 Aug 2026 18:39:08 -0700 Subject: [PATCH] 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) --- HANDOFF.md | 14 ++++++++------ README.md | 7 ++++--- TASKS.md | 25 ++++++++++++++----------- THIRD-PARTY-NOTICES.txt | 11 +++++++++++ ai.md | 38 ++++++++++++++++++++++++++++++++++---- 5 files changed, 71 insertions(+), 24 deletions(-) diff --git a/HANDOFF.md b/HANDOFF.md index 9c4fe4b..5a78ad6 100644 --- a/HANDOFF.md +++ b/HANDOFF.md @@ -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`); diff --git a/README.md b/README.md index 5804968..3f7652c 100644 --- a/README.md +++ b/README.md @@ -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) diff --git a/TASKS.md b/TASKS.md index 17495d3..399a7cf 100644 --- a/TASKS.md +++ b/TASKS.md @@ -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; diff --git a/THIRD-PARTY-NOTICES.txt b/THIRD-PARTY-NOTICES.txt index 84af37a..dcd6233 100644 --- a/THIRD-PARTY-NOTICES.txt +++ b/THIRD-PARTY-NOTICES.txt @@ -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 diff --git a/ai.md b/ai.md index 0663b87..14ba1c4 100644 --- a/ai.md +++ b/ai.md @@ -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>`) 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 3–6 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.