Files
LlamaCasty/TASKS.md
T
gramps f5a9d46881 TASK 36: composite the branding credit into the output, not just the preview
The credit existed only as a WPF BrandFlashLayer TextBlock in the preview at
25% opacity on a 300s timer. A viewer of the stream or the recording never saw
it, so "free tier shows branding" was not actually being delivered. The frame
pump has carried an unused per-frame flashFrame slot for exactly this.

Reverses the documented "Pre-GA posture" (ai.md Monetization), which kept the
flash preview-only so test VODs stayed clean. Creator ruling 2026-09-26: the
free tier has to be honest advertising, so it now reaches the broadcast.

One rendered VideoFrame feeds BOTH the frame pump and the preview, so the
creator's view cannot drift from what viewers get. Spec: 500ms in / 1000ms hold
/ 500ms out, first credit ~5s after go-live then every rand(30s)+30s, single
unwrapped line at a random spot inside the frame every time, neon white core
with a feathered red/blue halo.

Neon recipe is derivative work, per AGENTS.md: bright core + feathered
multi-radius halo with R and B split in opposite directions, from
https://nudaui.dev/components/neon-glow (layered text-shadow falloff),
https://help.maxon.net/rg/en-us/Content/html/Blurs-and-Glows-chromatic-glow.html
("with no displacement, the green channel is all but invisible behind your
white text" once R and B are split) and the OBS obs-stroke-glow-shadow
"feathered stroke with user-defined size and intensity". Neon blue is #4d8bff
rather than the theme's #0f3460, which renders near-black as a halo.

Also fixes a PRE-EXISTING bug found on the way: FramePump's fully-static
shortcut stretched the cached bake and returned it, silently discarding every
per-frame overlay - the social bar and the alert ticker were already lost
there. SceneCompositor.Overlay is now internal (it copies before blitting, so
the bake is never mutated) and the shortcut composites overlays first.

The credit is deliberately NOT folded into SceneGraph.GetBakedBase:
SceneRegion.Matches compares element ids only, so a credit in the cached bake
would freeze there and never expire. Per-frame only, and Epoch is stamped
every read because the 8MB master buffer is recycled - without that the
paste cache would freeze a stale credit.

BrandFlashEnabled is now derived !IsPremium and non-assignable, and the
presenter re-checks the licence on every read, so a key entered mid-credit
cuts the advertisement on the next tick instead of letting it finish.

Tests: 10 new facts. 357 total, 356 pass; the one failure is the known
environment-flaky RealMouseDrag layer test (needs an uncovered desktop
session). Adds RealAppHost.RunAsync - a frame-pump test must await inside the
collection's shared STA thread or the whole suite deadlocks silently.

NOTE: scope-check.sh flags Helpers/InstanceProfile.cs, AppLog.cs,
TokenStore.cs, WebView2Manager.cs and InstanceIsolationTests.cs as outside
this commit. That is a false positive: it uses `git diff HEAD`, which cannot
distinguish a planned second commit in the same session from an unrelated
edit. Those files are the dev multi-instance unit, committed immediately
after this one - as is the InstanceProfile paragraph in ai.md, which shares a
file with the Monetization section corrected here.
2026-09-27 08:55:55 -07:00

18 KiB
Raw Blame History

ytLlive — Task List

Catalog: task files live in TASKS/ (one file per task). This file is the index. Memory-map conventions: schema.md; architecture/decisions: ai.md.

Checklist markers — every task's Status line uses the same states:

  1. ✅ — completed / shipped
  2. 🔶 — in progress
  3. ⏳ — blocked / waiting
  4. ☐ — not started / queued
  5. ❌ — exception (known-issue, deliberately excluded, or merged)

2026-09-01 — v1 = feature-complete ruling: there is no v1.x. Everything queues to v1 or to "Out of product — permanently" (file end). Read those two sections before adding or reviving anything here.


Task catalog

# Task Status File
01 Initial Scaffold ✅ Done TASKS/task-01-initial-scaffold.md
02 YouTube OAuth2 Authentication ✅ Done TASKS/task-02-oauth2-auth.md
03 Capture Pipeline (Scenes/Sources) 🔶 In progress — 27/30 items done; items 16, 17, 20 open TASKS/task-03-capture-pipeline.md
04 RTMP Ingest to YouTube ✅ Done — all 7 items shipped TASKS/task-04-rtmp-ingest.md
05 Layout Persistence (SQLite) ✅ Done TASKS/task-05-layout-persistence.md
06 UI polish batch ✅ Done TASKS/task-06-ui-polish-batch.md
07 Meter scaling amplification ✅ Done TASKS/task-07-meter-scaling.md
08 Audio milestone ✅ SHIPPED 2026-08-14 TASKS/task-08-audio-milestone.md
09 YouTube Live Stream Management ⏳ In progress — items 1–3, 4 shipped; item 5 open; item 6 locked (TASK 36) TASKS/task-09-live-stream-management.md
10 Monetization: watermark-only one-time license + Polar billing 🔶 In progress — steps 1–7 shipped TASKS/task-10-monetization.md
11 Post-pause polish batch (creator's 8 review issues) ✅ SHIPPED 2026-08-15 TASKS/task-11-polish-batch.md
12 Master limiter on the live mix ☐ Queued TASKS/task-12-master-limiter.md
13 Social media launch kit 🔶 Scoped — queued after v1 TASKS/task-13-social-launch-kit.md
14 Creator feedback batch 🔶 In progress — Branch 2 shipped TASKS/task-14-creator-feedback-batch.md
15 Stock scene background images ✅ Shipped TASKS/task-15-scene-backgrounds.md
16 Kill infinity display ✅ Shipped TASKS/task-16-kill-infinity-display.md
17 Web source rendering (WebView2) ✅ Done TASKS/task-17-web-source-rendering.md
18 Local recording ✅ Shipped a9eb360 (2026-08-29) TASKS/task-18-local-recording.md
19 Scene transitions MERGED → TASK 23 (Control Surface UX) TASKS/task-19-scene-transitions.md
20 Hotkeys (keyboard shortcuts) ✅ Done — shipped 2026-08-26 TASKS/task-20-hotkeys.md
21 Media source (video file playback) 🔶 In progress — UI picker remaining TASKS/task-21-media-source.md
22 Audio sync offset ✅ Done (2026-08-31) TASKS/task-22-audio-sync-offset.md
23 Studio mode MERGED → TASK 23 (Control Surface UX) TASKS/task-23-studio-mode.md
24 Toast notifications ✅ Done (2026-08-23) TASKS/task-24-toast-notifications.md
25 Background consolidation ✅ Done (2026-08-23) TASKS/task-25-background-consolidation.md
30 Gear menu cleanup + Default Location for Recordings + preview layout ✅ Done (2026-08-29) TASKS/task-30-gear-cleanup-preview-layout.md
31 SceneGraph component + static/dynamic compositor optimization ✅ Done (2026-08-31) TASKS/task-31-scenegraph-optimization.md
32 Stream resilience ☐ Queued (2026-09-01) TASKS/task-32-stream-resilience.md
33 Bandwidth auto step-down ☐ Queued (2026-09-01) TASKS/task-33-bandwidth-step-down.md
34 Scheduled streams (Text-drawer version) ☐ Queued (2026-09-01) TASKS/task-34-scheduled-streams.md
35 Scene-linked audio ☐ Queued (2026-09-01) TASKS/task-35-scene-linked-audio.md
36 Gold pass ☐ Queued (2026-09-01) TASKS/task-36-gold-pass.md
37 Defaults vs current layout split ☐ Queued (2026-09-20) TASKS/task-37-defaults-current-split.md
38 Capture Window… backdrop (in-app window picker) ✅ Done — shipped 2026-09-21 TASKS/task-38-window-backdrop.md
39 YPP journey tracker (slice 1: current-scope data) 🔶 Slice 1 ✅ — 2026-09-22; refresh 403 fixed 2026-09-23 (auditDetails part dropped); slice 2 (Analytics + ETA) queued TASKS/task-39-ypp-journey-tracker.md
40 App Settings round: active-camera picker + config dialog, gear move, defaults, accent ☐ Queued (2026-09-22) — plan saved TASKS/task-40-app-settings-round.md
41 Test Stream mode: private test broadcast + TEST drawer (mock chat input + simulated events) ✅ Done (2026-09-22) TASKS/task-41-test-stream-mode.md
42 Top bar redesign: one REC-or-Stream surface, gear up top, sign-in/account zone world-gated ✅ Done (2026-09-22) TASKS/task-42-top-bar-redesign.md
43 Native events & alerts: chat parity (all six event types + server poll cadence) + six-animation native alert box ✅ Done (2026-09-24) TASKS/task-43-native-alerts.md
44 TEST-tab chat fix: resolve liveChatId from snippet (not contentDetails) + poll until the broadcast is live ✅ Done (2026-09-25); full suite 318/318 TASKS/task-44-test-chat-fix.md
45 TEST-tab chat fix #2: insert body must declare snippet.type (400 MISSING_REQUIRED_FIELD) ✅ Done (2026-09-25) TASKS/task-45-chat-insert-type.md
46 Drawers: click outside the rail closes whichever is open — TEST added to the existing Stream Settings + YPP dismiss behavior ✅ Done (2026-09-25) TASKS/task-46-drawer-click-outside-close.md
47 Alert box video: built-in/custom alert clip (+ six-animation fallback) with read-time fade in/out + ticker (now in the preview too, 3 display methods) + alert volume in the live mix ✅ Done (2026-09-26) TASKS/task-47-alert-videos.md

Open items (at a glance)

  • TASK 3 — items 16 (Text source) + 20 (RewardEvent capture → SQLite, Alerts' persistence half) open; 17 (Alerts) is DONE via TASK 43 (2026-09-24) — native six-event alert box
  • TASK 9 — item 5 open (webcam identity key reconciliation)
  • TASK 10 — Velopack update URL pending
  • Shipping / release build (creator-queued 2026-09-26) — no publish config exists yet: the csproj has only OutputType=WinExe + TargetFramework, so a plain dotnet publish is framework-dependent (customer needs the .NET 8 Desktop Runtime preinstalled). Distribution.md targets a self-contained ~80–120MB LlamaCasty.exe uploaded to Polar as a File Download benefit. Two deliverables:
    1. scripts/publish.sh wrapping dotnet publish ytLive.csproj -c Release -r win-x64 --self-contained true -o ./publish/win-x64, and asserting the output contains no test/xunit artifacts. (The compile side needs nothing — the tests are a separate project with a one-way reference, plus explicit <Compile Remove="ytLive.Tests\**" /> in ytLive.csproj; the risk is packaging: ytLive.Tests/bin/.../ytLive.exe exists and would ship a Debug build + test DLLs if anyone ever zips a bin folder by hand. Never copy folders; always publish.)
    2. Condition InternalsVisibleTo("ytLive.Tests") to Debug only (ytLive.csproj:78-82 is unconditional, so it ships in Release) — it hands the test assembly name to anyone decompiling and advertises that internal members are externally reachable, which conflicts with the obfuscation posture in Distribution.md §1.2.
    3. Decide the shipped exe name. Distribution.md promises the customer LlamaCasty.exe; <AssemblyName>ytLive</AssemblyName> actually publishes ytLive.exe. Renaming the AssemblyName is 4 lines (1 csproj + 3 pack URIs that spell the assembly name — MainWindow.xaml:14 is the window icon, so missing it ships a broken taskbar/Alt-Tab icon). Namespaces stay ytLive.*; the 230-file re-brand is NOT wanted. Do NOT touch the %APPDATA%\ytLlive folder — breaking (orphans users' layout DB). See the naming table in ai.md. Do NOT add -p:PublishTrimmed=true: WPF is not trim-compatible and it fails at runtime (BAML/XAML resource resolution), not at build time. PublishReadyToRun is safe.
  • TASK 12 — queued future
  • TASK 13 — queued post-v1
  • TASK 14 — Branch 2 shipped; branch 3+ in progress
  • TASK 21 — UI picker slice remaining (Windows-only verification pending)
  • TASK 32–36 — all queued future work (2026-09-01)
  • Webcam resource lifecycle (2026-09-17) — startup slice shipped (OS poll at start, single-camera auto-lock, persistent red alert in the Layers panel on lock failure, re-polls until resolved); gate slice shipped same day — webcam = app-level default, one per stream per scene, Add places the default directly, offered only when a camera is ATTAINABLE (identity + live session, not just a saved identity), app base lock keeps the session after the last placement is removed, startup adopts a solo camera as default. TASK 26's app-wide gate superseded by creator directive. The App Settings webcam selector is now TASK 40 Unit A (plan saved 2026-09-22). Remaining next slice (user-queued): static (+) catalog rows (Background, YouTubeEvent, quoted labels) with reason-greying.
  • TASK 40 — App Settings round — plan saved 2026-09-22 (TASKS/task-40-app-settings-round.md), four units, one Good Dog test each: A active web camera section under Recordings (Change… sub-menu = live enumeration incl. vcams; Tune… config dialog via ICameraControlProbe, live-only tweaks), B gear moved top-left right of the wordmark (left-click App Settings, right-click Bug/Feature/About), C persist output resolution default + surface default scene transition/duration + confirm-before-End, D accent/theme color (consolidate 69 hardcoded accent sites into a DynamicResource brush + picker). Unit B (gear) is DONE early — shipped 2026-09-22 as part of TASK 42 (top bar redesign): gear top-right of the brand, single click = the Settings/Bug/ Feature/About menu, removed from BottomBar. A + C + D remain queued.
  • TASK 37 — queued (2026-09-20): split the layout data into a default set (the established DB) and a current set carrying the build-id; a saved current is honored only by the same build-id, otherwise defaults load — enables one-click revert to defaults too. Capture needed work OUT of current scope here; do not bolt it onto an in-flight change.
  • TASK 41 — Test Stream mode — DONE 2026-09-22 (TASKS/task-41-test-stream-mode.md): a Test button next to Start runs the real private-only go-live pipeline with IsTestStream set, and the TEST drawer (right rail, third tab) hosts Mock Chat Input (real liveChat/messages.insert → ~2s poll round-trip renders on the overlay) + simulated Subscriber/New Member/Super Chat events that inject through the poller's MessageReceived seam (IsSimulated — YouTube's insert API only creates text, so these are local-only by design; styled member/SuperChat rows validate the chat overlay). Real Stinger/TTS alert widgets resolved by TASK 43's native alert box.
  • TASK 43 — Native events & alerts — DONE 2026-09-24 (TASKS/task-43-native-alerts.md): StreamElements replaced with NATIVE YouTube events. Chat parity half: YouTubeChatService now decodes all SIX liveChat/messages event types (ChatEventKind on ChatMessage) — the four formerly-empty event rows fixed — and re-arms its poll on the server's pollingIntervalMillis (streamList semantics, clamp 1000–6000ms, maxResults=2000). Alerts half: a new SourceType.AlertBox ("Stream Alerts", one per layout) is an OBS-style celebration zone where the six events play six unique branded animations (AlertRenderer + the AlertOverlayLayer component; 33ms ticker, deterministic Advance test clock; idle = transparent). Creator rulings baked in: free-sub mention = chat row only (no sub alert — YouTube emits none), NO viewer count, every alert card carries the "made with LlamaCasty!" brand line.
  • TASK 44 — TEST-tab chat fix — DONE 2026-09-25 (TASKS/task-44-test-chat-fix.md): the open creator report ("I still cannot post a chat message in the TEST tab") was a single cause: GetBroadcastLiveChatIdAsync read contentDetails.liveChatId (lifeChatId lives in snippet) AND ran before the broadcast was live (RTMP push flips ready→live via enableAutoStart; YouTube only populates the id on live broadcasts — official GetLiveChatId.java sample lists broadcastStatus=active). Fixed by reading snippet.liveChatId and polling with a bounded retry after the frame pump starts; chat stays non-fatal.
  • TASK 47 take four (2026-09-26) — the announcement strip was output-only (a frame-pump callback with no preview consumer, since a master-width global overlay cannot ride a per-element Image) and the marquee was paced at a fixed 140px/s (~17s per pass, so a 10s alert showed the text once). Now: a tickerPreviewSink publishes it to a global AlertTickerElement in PreviewPane.xaml (mirroring SocialBarElement), and the creator picks Scroll / Flash / Solid in the panel, with Scroll paced in reads per alert (3 inside a 10s alert) rather than px/s. 15 new facts; suite 339/339.
  • TASK 47 — Alert box video — DONE 2026-09-26 (TASKS/task-47-alert-videos.md): the TASK 43 alert box now plays a video on every alert — a shipped built-in mp4 (Assets/alert-default.mp4, stamped into the Asset table at startup) that the creator can swap for their own file via the new Stream Alerts section (path only, never stored in the DB; six AlertRenderer animations remain the fallback). Per-alert decode via a new IAlertClipDecoder fx (ffmpeg rawvideo bgra + f32le pipes, real-time paced, disposed at drain); ~0.3s fade-in/out rides the alpha envelope + scales the audio (mixed into the live stream at unity — no duck, a per-alert Volume slider instead); an auto-composed message ticker ("Funder — Super Chat · $10.00") scrolls along the very top of the frame (never-baked dynamic overlay threaded through SceneCompositor + FramePump). Creator rulings: custom + fallback (not either/or), ticker on top, no ducking.

1.0 gates (do these before the first shipped build)

  • Kill the dev multi-instance affordance. Helpers/InstanceProfile.cs is already entirely #if DEBUG, so a Release build ships as DataRoot => DefaultRoot / WebViewDataFolder => null. Nothing to do but keep it that way — and re-prove it: InstanceIsolationTests.TheAlternateProfile_IsConfinedToTheDebugBuild (source-level, always runs) plus a Release build in which the InstanceVariable field is absent from metadata. ⚠️ Do NOT grep the binary for YTLIVE_INSTANCE — consts are inlined and appear in neither build.
  • TASK 36 is now shipped — the branding flash composites into recordings and the stream. Test VODs from an unlicensed instance carry the credit; use a license key for clean captures.
  • Still queued from earlier: scripts/publish.sh (self-contained win-x64, no test artifacts, Debug-only InternalsVisibleTo, no WPF trimming), and the LlamaCasty.exe assembly rename (three pack URIs incl. MainWindow.xaml:14 — do not rename %APPDATA%\ytLlive).
  • Decide the open business question: OverlayHost.xaml copy says Polar unlocks alerts too, but alerts are not gated by IsPremium. Either fix the copy or gate the alerts — do not leave it.

YouTube Live API research facts

See TASKS/research-youtube-api.md — authoritative facts for v3 build.


Out of product — permanently

These were explicitly ruled out:

  • D3D11 swap compositor (superseded by software compositor; TASK 3 item 16)
  • Background removal (ONNX/DirectML; TASK 3 item 19)
  • OBS-style source expansion (game capture, browser source, media playlist, VLC, color-key, MIDI — the minimal source set is deliberate; creators who need more have graduated to OBS)
  • Multi-layout (auto-save, single layout; multi-layout = OBS territory)
  • Cross-process stream resume (API makes it impossible; creator ruling 2026-09-01)