# ytLlive β Task List
> **Catalog:** task files live in [`TASKS/`](TASKS/) (one file per task). This file is the
> index. Memory-map conventions: [`schema.md`](schema.md); architecture/decisions: [`ai.md`](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`](TASKS/task-01-initial-scaffold.md) |
| 02 | YouTube OAuth2 Authentication | β
Done | [`TASKS/task-02-oauth2-auth.md`](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`](TASKS/task-03-capture-pipeline.md) |
| 04 | RTMP Ingest to YouTube | β
Done β all 7 items shipped | [`TASKS/task-04-rtmp-ingest.md`](TASKS/task-04-rtmp-ingest.md) |
| 05 | Layout Persistence (SQLite) | β
Done | [`TASKS/task-05-layout-persistence.md`](TASKS/task-05-layout-persistence.md) |
| 06 | UI polish batch | β
Done | [`TASKS/task-06-ui-polish-batch.md`](TASKS/task-06-ui-polish-batch.md) |
| 07 | Meter scaling amplification | β
Done | [`TASKS/task-07-meter-scaling.md`](TASKS/task-07-meter-scaling.md) |
| 08 | Audio milestone | β
SHIPPED 2026-08-14 | [`TASKS/task-08-audio-milestone.md`](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`](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`](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`](TASKS/task-11-polish-batch.md) |
| 12 | Master limiter on the live mix | β Queued | [`TASKS/task-12-master-limiter.md`](TASKS/task-12-master-limiter.md) |
| 13 | Social media launch kit | πΆ Scoped β queued after v1 | [`TASKS/task-13-social-launch-kit.md`](TASKS/task-13-social-launch-kit.md) |
| 14 | Creator feedback batch | πΆ In progress β Branch 2 shipped | [`TASKS/task-14-creator-feedback-batch.md`](TASKS/task-14-creator-feedback-batch.md) |
| 15 | Stock scene background images | β
Shipped | [`TASKS/task-15-scene-backgrounds.md`](TASKS/task-15-scene-backgrounds.md) |
| 16 | Kill infinity display | β
Shipped | [`TASKS/task-16-kill-infinity-display.md`](TASKS/task-16-kill-infinity-display.md) |
| 17 | Web source rendering (WebView2) | β
Done | [`TASKS/task-17-web-source-rendering.md`](TASKS/task-17-web-source-rendering.md) |
| 18 | Local recording | β
Shipped `a9eb360` (2026-08-29) | [`TASKS/task-18-local-recording.md`](TASKS/task-18-local-recording.md) |
| 19 | Scene transitions | MERGED β TASK 23 (Control Surface UX) | [`TASKS/task-19-scene-transitions.md`](TASKS/task-19-scene-transitions.md) |
| 20 | Hotkeys (keyboard shortcuts) | β
Done β shipped 2026-08-26 | [`TASKS/task-20-hotkeys.md`](TASKS/task-20-hotkeys.md) |
| 21 | Media source (video file playback) | πΆ In progress β UI picker remaining | [`TASKS/task-21-media-source.md`](TASKS/task-21-media-source.md) |
| 22 | Audio sync offset | β
Done (2026-08-31) | [`TASKS/task-22-audio-sync-offset.md`](TASKS/task-22-audio-sync-offset.md) |
| 23 | Studio mode | MERGED β TASK 23 (Control Surface UX) | [`TASKS/task-23-studio-mode.md`](TASKS/task-23-studio-mode.md) |
| 24 | Toast notifications | β
Done (2026-08-23) | [`TASKS/task-24-toast-notifications.md`](TASKS/task-24-toast-notifications.md) |
| 25 | Background consolidation | β
Done (2026-08-23) | [`TASKS/task-25-background-consolidation.md`](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`](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`](TASKS/task-31-scenegraph-optimization.md) |
| 32 | Stream resilience | β Queued (2026-09-01) | [`TASKS/task-32-stream-resilience.md`](TASKS/task-32-stream-resilience.md) |
| 33 | Bandwidth auto step-down | β Queued (2026-09-01) | [`TASKS/task-33-bandwidth-step-down.md`](TASKS/task-33-bandwidth-step-down.md) |
| 34 | Scheduled streams (Text-drawer version) | β Queued (2026-09-01) | [`TASKS/task-34-scheduled-streams.md`](TASKS/task-34-scheduled-streams.md) |
| 35 | Scene-linked audio | β Queued (2026-09-01) | [`TASKS/task-35-scene-linked-audio.md`](TASKS/task-35-scene-linked-audio.md) |
| 36 | Gold pass | β Queued (2026-09-01) | [`TASKS/task-36-gold-pass.md`](TASKS/task-36-gold-pass.md) |
| 37 | Defaults vs current layout split | β Queued (2026-09-20) | [`TASKS/task-37-defaults-current-split.md`](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`](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`](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`](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`](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`](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`](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`](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`](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`](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`](TASKS/task-47-alert-videos.md) |
| 48 | Distribution & packaging: route decision, bundled ffmpeg, MSIX + code signing | β³ Queued β **blocked on the creator's route decision**; bundled-ffmpeg half is unblocked and recommended | [`TASKS/task-48-distribution-msix.md`](TASKS/task-48-distribution-msix.md) |
| 49 | Chat profanity filter (local, opt-in, non-persistent, user word list) | β Queued (2026-09-27) | [`TASKS/task-49-chat-profanity-filter.md`](TASKS/task-49-chat-profanity-filter.md) |
---
## Research index
| Research | Covers |
|---|---|
| [`TASKS/research-youtube-api.md`](TASKS/research-youtube-api.md) | YouTube Live Streaming API v3 β authoritative facts for the v3 build |
| [`TASKS/research-store-certification.md`](TASKS/research-store-certification.md) | **Windows Store policies 7.20 + MSIX packaging + code-signing economics** (2026-09-27). Feeds TASK 48. Records which policies bind, which don't, and why β read it before re-litigating distribution |
---
## Open items (at a glance)
### Creator-reported batch (2026-09-26) β proof-of-concept round, recordings used as the reference
The creator is **not** getting YouTube private test recordings saved, so local recordings are the
proof of concept for compositing; the live path is assumed to share it. **Verified true, not assumed:**
one `_framePump` is built in the `MainViewModel` ctor with a single `brandFlash:` callback, and BOTH
`Streaming.Operations.cs:68` (go-live) and `:199` (record) call that same `StartAsync` β there is no
second encoder path needing a "redirect". Record+simulcast is one ffmpeg with two outputs.
1. β
**Recording save dialog: Cancel discards** β was silently saving under the default name. Enter
accepts the default; Cancel/Escape/X **deletes** the footage and names the file if the delete fails.
`MainViewModel.CompleteRecordingSave` (internal) + `ytLive.Tests/RecordingSaveDialogTests.cs`.
2. β
**Brand flash in ALL scenes + in recordings** β was gated on `IsLive` via
`UpdateLiveVisuals()`, so a recording made without ever going live carried no credit. The
presenter is now `Start()`ed once in the `MainViewModel` ctor and never stopped on live-state
churn; the licence gate rides on `IsPremium` β `Enabled` as before. Also fixed the
premium-edge/restart trap that app-lifetime exposed. Tests: `BrandFlashOutputTests`
(12 facts) incl. `Credit_IsComposited_WithNoLiveSession_AndSoARecordingCarriesIt` and
`ADowngradeMidSession_RestartsTheCadenceTimer`.
3. β
**Ticker confined to the alert box** β was a global 1920Γ48 bar at the top edge; the
creator wants it over the *Stream Alerts video*. The strip now renders at the alert box's own
size and carries the box's origin on a new `VideoFrame.Placement`; every blit site reads
`OriginX/OriginY` instead of a literal `0, 0`, and the preview element is bound to the same
rect so preview and output cannot drift. No alert box β no ticker. Height clamped to the 48px
strip; `CopyStrip` clips rows so a short box can't overrun the buffer. Tests: 3 new facts
incl. an output-side `SceneCompositor.Render` check that the pixels land in the box and NOT at
the top-left corner.
4. β **Two instances still refuse to run.** `InstanceProfile` isolates layout/auth/log/WebView, but
`FfmpegLocator._toolsDir` is still the hardcoded shared `%APPDATA%\ytLlive\tools` and both
instances can `Directory.CreateDirectory` + `ExtractBinaries` into it. Also the launch method is
unconfirmed: `dotnet run` while the first app holds `bin/β¦/ytLive.exe` dies with `MSB3027` before
any instance starts β that is a build-output lock, not a log conflict.
5. β **Post-session efficacy report** β on end of stream *or* recording, report what worked and what
failed, including the **dropped frames** the creator saw. `CurrentHealth` already tracks
`DroppedFrames`/`StreamDuration`; `SessionTeardownTests` is the natural home for the roll-up.
- **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 β **now owned by TASK 48** (retires if the Store handles updates)
- **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
`` 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`; `ytLive` 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.
- **TASK 36 item 6 (release engineering) is now TASK 48** (2026-09-27) β code signing, the
installer, and the Velopack update URL have one owner instead of three scattered mentions.
**EULA draft/review and the THIRD-PARTY-NOTICES license-texts gate stay in TASK 36 item 6**,
as does the agreed build-posture ceiling (HARDENED + MOCK_REWARDS, additive-only).
- **TASK 48 β distribution & packaging** β **β³ the route decision belongs to the creator.**
Research is done and MSIX is the front-runner; the four real combinations (Store+Store IAP /
Store+Polar / Polar-hosted+own cert / dominated Store-EXE) and their cert costs are tabulated
in `TASKS/research-store-certification.md` Β§3. Two things are settled and unblocked:
**(a) bundle ffmpeg instead of downloading it** β `FfmpegLocator` currently downloads an
unsigned exe from GitHub and runs it, which is Store policy 10.2.2 (dynamic code inclusion)
*and* is the root cause of the 2026-09-01 expired-pin 404; **(b) the `Distribution.md` EV
claim is dead** β Microsoft removed the SmartScreen EV bypass in March 2024, so paying
$400+ buys what $150 buys. Full-trust MSIX is viable (mediumIL, not AppContainer, and the
three technical disqualifiers β drivers, per-user services, elevation β are all clear).
Packaging, Velopack removal, WACK, the demo account, the privacy policy and the IARC
questionnaire are all specified in the task file and waiting.
- **TASK 49 β chat profanity filter** β queued, not blocked, size S. Local, on-device,
**non-persistent**, opt-in, **user-supplied word list (never a hardcoded slur list)**, and it
must **never match the SuperChat amount or reward fields** (financial data, Store 10.5.5).
The non-persistence half is the same property that makes the 11.12 UGC certification answer
strong, so **no future chat-history/moderation-log/analytics feature may quietly break it.**
---
## 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`](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)