diff --git a/HANDOFF.md b/HANDOFF.md index 7b93d33..59e8a40 100644 --- a/HANDOFF.md +++ b/HANDOFF.md @@ -1,12 +1,11 @@ -# HANDOFF — 2026-11-09 (TASK 33 policy; vertical checks passed) +# HANDOFF — 2026-11-09 (TASK 33 done; queue re-ordered for 1.0) ## State - `main`. TASK 21 (video layer + soundtrack), TASK 64 (green/blue screen) and TASK 32·4 (fragmented MP4) are complete; TASK 49 is retired (YouTube owns chat moderation — we just don't paint tombstones). -- **Starting art**: `Assets/starting-background.jpg` is the corrected onboarding screen (earlier - images in `Assets/archive/`). Bundled art is hash-keyed into the layout DB, so - `StampDefaultBackgroundAsset` compares bytes and re-stamps on a mismatch — no version bump. +- **Starting art**: `Assets/starting-background.jpg` is the corrected onboarding screen; bundled art is + hash-keyed into the layout DB, so `StampDefaultBackgroundAsset` compares bytes and re-stamps. - Left panel alert stack shipped: Steam licence indicator pinned at row 1, event rows LIFO, click → About or Logs with the event's lines painted. Detail: `docs/ai/ui-surface.md`. - Site: demo video (silent loop) is the hero; Starting screen is in the scenes section. @@ -17,8 +16,9 @@ ## Next action -- **App queue starts at 34** (scheduled streams; cite its research flags first), then 35/37/40/50/55, - Move transitions, 9·5. +- **Queue re-ordered (creator ruling):** 35 → 37 → 40 → 55 → Move → 9·5 → 14 slices → 65 (clips) → + 66 → 67 → 68 → 36. TASK 34 (scheduling) and 50 (publishing) are **retired** — platform's surface. +- **New:** 65 replay clips, 66 timed ticker, 67 per-event alert videos, 68 edit-while-live. - Site repo commits are local (`llgit.llamachile.tube`); deploy is `./deploy.sh`, not a push. - Do not push the app absent a checkpoint request. - **Steam is a wait, not a task:** identity verification pending, tax review in Valve's queue (up to 30 diff --git a/PRIVACY.md b/PRIVACY.md index 746d7ac..50ec82a 100644 --- a/PRIVACY.md +++ b/PRIVACY.md @@ -13,7 +13,8 @@ kept locally, and what is never collected. | Your layout: scenes, sources, assets, settings | `%APPDATA%\ytLlive\ytLlive.db` | So the app looks the same next time | | YouTube sign-in token | `%APPDATA%\ytLlive\ytLlive.auth`, encrypted with Windows DPAPI (CurrentUser) | So you stay signed in; only your Windows account can decrypt it | | Diagnostic log | `%APPDATA%\ytLlive\startup.log`, capped at 5 MB | To diagnose crashes you report | -| Recordings | the folder you choose | Your files; the app never uploads them unless you press Publish | +| Recordings | the folder you choose | Your files; the app never uploads them | +| Replay clips (if you use them) | `clips\` beside your recordings | Only the three most recent are kept; older ones are deleted automatically. Local only, never uploaded | Nothing else is written to disk. There is no analytics service, no crash reporter, no advertising SDK and no telemetry of any kind. diff --git a/TASKS.md b/TASKS.md index 764923a..18779ac 100644 --- a/TASKS.md +++ b/TASKS.md @@ -1,106 +1,55 @@ # TASKS — the active queue -> One line per live item; the detail lives in `TASKS/task-*.md`. Done work is in -> [`TASKS/archive.md`](TASKS/archive.md) (2026-10-29 snapshot of the old catalogue). Retired -> decisions are in [`docs/ai/decisions.md`](docs/ai/decisions.md). +> One line per live item; the detail lives in `TASKS/task-*.md`. Done work and the retired surge plan are +> in [`TASKS/archive.md`](TASKS/archive.md). Retired decisions are in +> [`docs/ai/decisions.md`](docs/ai/decisions.md). > Markers: ☐ queued · 🔶 in progress · ⏳ blocked · ⏸ deferred · ❌ retired. -## The surge — six days, all remaining coding (2026-11-02 → 11-07) +## Next up -**Every open coding row is assigned below.** No later phase, no "after launch" list. Target is -**6–8 units a day** (above the measured 3–5 baseline — that is the point of a surge). If a day -overruns, the smallest item moves to the next day; a launch blocker never moves. +**Creator ruling 2026-11-09 — finish the queue before the new features, gold pass always last.** -**Rules, every unit, no exceptions:** one Good Dog test · clean build 0 warnings · per-class tests -green · `./scripts/verify.sh` on the declared scope · commit with the doc fact it falsifies -corrected. No new scope mid-week; anything discovered goes on a list for day 6. +`35 → 37 → 40·A/C/D → 55·2–3 → Move → 9·5 → 14 (slices) → 65 → 66 → 67 → 68 → 36` -| Day | Units | Done when | -|---|---|---| -| **1 · Mon** — licensing | Paid-app ownership check (`BIsSubscribedApp`) + Debug fake + app-wide `IsAppOwned` · greyed tier entries · About conversion copy + llamacasty.com link · current-launch Logs tab (replaces the separate system-message strip) · Debug-only `InternalsVisibleTo` | A locked tier opens About without changing the selected free tier or leaving the app; the site opens only from its link | -| **2 · Tue** — release blockers | Visibility flow (creator-selected at release; development/test stays Private) · `OutputProfile` entitlement: 30 fps free / 60 fps paid regardless of resolution · ~~`StreamConfig` defaults~~ retired (no production call sites) · TASK 53 (vertical resolution check ✅; remaining output contract open) · 9·5 webcam identity key · Move transitions (per-layer carry-across-scenes) | Visibility is selectable at creation and live-editable; the 30/60-fps gate is tested; remaining Day 2 items stay queued | -| **3 · Wed** — resilience | TASK 32: blip retry → measured grace → amber RECONNECTING → one-click Back on air → fragmented MP4 → ingest pre-flight · 33 bandwidth step-down | Killing the network mid-broadcast recovers without ending it | -| **4 · Thu** — content & media | ~~3·17 Text layer~~ ✅ · ~~21 media acquisition wiring~~ ✅ (picker, loop, lifecycle) · 49 chat profanity filter · 34 scheduled streams | Text layer renders/persists; media file plays, loops; scheduled stream created | -| **5 · Fri** — data & settings | 3·20 RewardEvent persistence + `superChatEvents.list` backfill · 39·2 YPP slice 2 · 37 defaults-vs-current split · 35 scene-linked audio · 40·A/C/D settings round · 55·2–3 disk hygiene | Session report reads persisted events; defaults can be reverted; temp clips cleaned | -| **6 · Sat** — finish & prove | 50 publish recording · preview undo (Ctrl+Z) · 14 creator feedback batch · 36 gold pass · full sweep: clean build, whole per-class suite, `publish.sh` artifact, EULA/privacy/notices in the artifact · commit + push | Gold pass list empty; artifact stages; suite green; pushed | - -**Day 6 is the only polish day.** No polish passes before it — a surge dies on cosmetic passes taken -in the middle. - -## Next up (after the surge slice) - -**Start here:** TASK 33 (bandwidth step-down) — the last unbuilt piece of the resilience row, and the -only launch blocker left in code. Then TASK 34 (scheduled streams; its two research flags must be -cited first), then 35 / 37 / 40·A/C/D / 55·2–3. TASK 32 is complete, Back on air included. +The rule behind the two retirements below: **do not rebuild the platform's own surface.** Scheduling, +publishing, moderation and channel management are YouTube's; the app owns capture → scene → sound → +output. ## Queue -> Every open coding row here is assigned to a surge day above. Rows still marked ☐ on -> 2026-11-07 are a failure of the plan, not a scheduling note. | Task | Status | Scope | Detail | |---|---|---|---| -| 53 | ✅ | Output-format contract — six size/FPS modes, vertical YouTube playback, and recordings visually accepted | [`task-53`](TASKS/task-53-output-formats.md) | -| — | ✅ | Output entitlement code: free at 30 fps / paid at 60 fps regardless of resolution; Steam App ID is still pending | [`commercial`](docs/ai/commercial.md) | -| 3·17 | ✅ | **Text layer** — content, font family/size/weight/style, colour, alignment, drag/resize, persisted (done 2026-11-04) | [`task-03`](TASKS/task-03-capture-pipeline.md) | -| 21 | ✅ | Media source: video file added from the (+) menu decodes, composites, plays its soundtrack, loops, and stops with its last reference (done 2026-11-05) | [`task-21`](TASKS/task-21-media-source.md) | -| 64 | ✅ | **Green/Blue Screen** — detect + key from the decode pipe, offer on add, Amount/Softness/Fringe, opt-in auto-apply, alert clips included, persisted (done 2026-11-05) | [`task-64`](TASKS/task-64-green-blue-screen.md) | -| — | ☐ | Transitions **Move**: per-layer carry-across-scenes flag, per-scene placement, fixed z band | [`ui-surface`](docs/ai/ui-surface.md) | -| 55·2–3 | ☐ | Disk hygiene: delete `%TEMP%` alert clips on close + sweep at startup; low-disk guard before REC (slice 1 shipped) | [`task-55`](TASKS/task-55-disk-hygiene.md) | -| 40·A/C/D | ☐ | App Settings: active-camera picker; persist output resolution + surface transition defaults + confirm-before-End; accent consolidation | [`task-40`](TASKS/task-40-app-settings-round.md) | -| 3·20 | ✅ | Session report — **derived from `superChatEvents.list` on demand, not persisted** (YouTube is the authority; no ledger). Per-currency totals, money off by default (done 2026-11-05) | [`task-03`](TASKS/task-03-capture-pipeline.md) | -| 32 | ✅ | Stream resilience: fragmented MP4, blip retry, poll-driven RECONNECTING (no countdown — YouTube's cutoff is not a constant), ingest pre-flight, one-click Back on air (`BackOnAirTests`) | [`task-32`](TASKS/task-32-stream-resilience.md) | -| 33 | 🔶 | Auto step-down — **`StepDownPolicy` + 12 unit tests done 2026-11-09** (two ladders, budgets, floors, cooldown, ambiguity, recovery behind a flag); policy, planner, VM watch, encoder relaunch and the toast are in (17 tests); remaining: shed our own capture cost before the fps step, and the session-report line | [`task-33`](TASKS/task-33-bandwidth-step-down.md) | -| 34 | ☐ | Scheduled streams (Text-drawer version) | [`task-34`](TASKS/task-34-scheduled-streams.md) | -| 35 | ☐ | Scene-linked audio | [`task-35`](TASKS/task-35-scene-linked-audio.md) | -| 37 | ☐ | Defaults vs current layout split (build-id); enables one-click revert to defaults | [`task-37`](TASKS/task-37-defaults-current-split.md) | -| — | ✅ | **Preview undo (Ctrl+Z / Ctrl+Shift+Z)** — done 2026-11-01: `Models/PreviewHistory` + `MainViewModel.Preview`, one step per gesture, gated on `EditingActive`, cleared on layout load. Property edits not yet in the stack | [`ui-surface`](docs/ai/ui-surface.md) | -| 39·2 | ✅ | YPP slice 2 — three eligibility bars (subs / watch hours / Shorts views) from Analytics, dated thresholds with a staleness test, "x of 3 criteria met" (done 2026-11-05) | [`task-39`](TASKS/task-39-ypp-journey-tracker.md) | -| 49 | ❌ | Chat profanity filter — **retired 2026-11-05**: YouTube owns moderation (blocked words + hold for review run before the API). Replaced by not rendering YouTube's moderation markers (tombstones / no-display events) | [`task-49`](TASKS/task-49-chat-profanity-filter.md) | -| 50 | ☐ | One-button publish of the local recording (resumable upload) | [`task-50`](TASKS/task-50-publish-recording.md) | -| 54 | 🔶 | Freemium: EULA + privacy shipped; one codebase for free Demo + paid app. **Blocked on Valve**: identity verification pending, tax review up to 30 business days; app IDs, store page and build review all follow | [`task-54`](TASKS/task-54-marcom-free-merch.md) | +| 35 | ☐ | **Scene-linked audio** — the scene remembers the room: BRB mutes the mic, Chat restores it, applied once at the live transition | [`task-35`](TASKS/task-35-scene-linked-audio.md) | +| 37 | ☐ | **Defaults vs current layout split** — separate branded defaults from the live workspace; enables one-click revert | [`task-37`](TASKS/task-37-defaults-current-split.md) | +| 40·A/C/D | ☐ | **Settings round** — active-camera picker; persist output resolution + surface transition defaults + confirm-before-End; accent consolidation | [`task-40`](TASKS/task-40-app-settings-round.md) | +| 55·2–3 | ☐ | **Disk hygiene** — delete `%TEMP%` alert clips on close + sweep at startup; low-disk guard before REC (slice 1 shipped) | [`task-55`](TASKS/task-55-disk-hygiene.md) | +| — | ☐ | **Move transition** — per-layer carry-across-scenes flag, per-scene placement, fixed z band | [`ui-surface`](docs/ai/ui-surface.md) | +| 9·5 | ☐ | **Webcam identity key reconciliation** — one stable identity per camera across re-plugs | [`task-09`](TASKS/task-09-live-stream-management.md) | +| 14 | 🔶 | **Creator feedback batch** — Branch 2 shipped; Branch 3 = mic input gain, webcam blank/standby, opacity+geometry consistency, Show-in-folder on stop | [`task-14`](TASKS/task-14-creator-feedback-batch.md) | +| 65 | ☐ | **Replay clips** — 10/15/20 s (default 15), RAM ring of fragmented MP4, F10 to save; replay layer plays once then hides in Live and loops the last 1–3 elsewhere; silent by default; three kept | [`task-65`](TASKS/task-65-replay-clips.md) | +| 66 | ☐ | **Timed ticker messages** — "every N minutes show this", from a creator-written list | [`task-66`](TASKS/task-66-timed-ticker.md) | +| 67 | ☐ | **Per-event alert videos** — a clip per monetization event, falling back to the default | [`task-67`](TASKS/task-67-per-event-alert-videos.md) | +| 68 | ☐ | **Editing while live** + first-run On-Air state matching the 1-2-3 screen | [`task-68`](TASKS/task-68-edit-while-live.md) | +| 36 | ☐ | **Gold pass** — the v1 gate: screens/layers audit (every control does something), native-Windows verification, branding-flash decision. Always the last unit | [`task-36`](TASKS/task-36-gold-pass.md) | +| 34 | ❌ | **Scheduled streams — retired 2026-11-09.** YouTube owns scheduling *and* the reminder that makes it useful; the app neither creates nor displays scheduled broadcasts, and there is no value in a copy that can fall out of sync | [`decisions`](docs/ai/decisions.md) | +| 50 | ❌ | **Publish recording — retired 2026-11-09.** YouTube Studio owns upload (resumable, quota-aware, and it explains rejections). In-app publishing would also widen the OAuth consent to whole-channel video management. The recording lands in the creator's folder; the app adds "Show in folder" instead | [`decisions`](docs/ai/decisions.md) | +| 54 | 🔶 | Freemium: EULA + privacy shipped; one codebase for free Demo + paid app. **Blocked on Valve** — identity verification pending, tax review up to 30 business days | [`task-54`](TASKS/task-54-marcom-free-merch.md) | | — | 🔶 | **Steam ownership** — `BIsSubscribedApp(paidAppId)` + Debug fake + app-wide `IsAppOwned`; paid app ID awaits Steamworks | [`commercial`](docs/ai/commercial.md) | -| — | ❌ | **Polar subscription path** — retired 2026-11-01 with the one-price model. Design kept in `decisions.md`, not built | [`decisions`](docs/ai/decisions.md) | -| — | ✅ | **OutputProfile gate** — resolver + `EncoderOptions` wiring (`Models/OutputProfile.cs`, one ffmpeg/two outputs, dev override); higher tiers follow `IsAppOwned` | [`commercial`](docs/ai/commercial.md) | -| — | ✅ | **Demo conversion UI** — owned users get higher tiers; non-owners see greyed tiers, and selecting one opens About with conversion copy and an explicit llamacasty.com link. No browser opens until the link is clicked | [`commercial`](docs/ai/commercial.md) | -| — | ✅ | **System-message strip replaced by Logs tab** — current launch's newest 500 raw AppLog entries, newest-first; no prior-launch import or logging-policy changes | [`ui-surface`](docs/ai/ui-surface.md) | -| — | ☐ | **Steam packaging** — `publish` output + `steam_api64.dll`, paid app and associated Demo setup, depot, `steamcmd` upload, store pages + assets | [`Distribution.md`](Distribution.md) | -| — | ☐ | **Training-video series** — capture the first 8–10 recurring questions as videos on `@llamacasty`; each is documentation, channel content and monetization at once | MARCOM → support→content loop | -| 9·5 | ☐ | Webcam identity key reconciliation | [`task-09`](TASKS/task-09-live-stream-management.md) | -| 14 | 🔶 | Creator feedback batch — branch 3+ | [`task-14`](TASKS/task-14-creator-feedback-batch.md) | -| — | ❌ | Retired: `StreamConfig` defaults are unused; encoder output comes from the selected quality tier | [`Models/index.md`](Models/index.md) | -| 36 | ☐ | **Gold pass** — the v1 gate, always the last unit (see 1.0 gates) | [`task-36`](TASKS/task-36-gold-pass.md) | -| 48 | ⏸ | Distribution & packaging — route settled 2026-10-31 (winget/portable first; Steam + Store parked). Build steps: [`Distribution.md`](Distribution.md); starts after the entitlement seam | [`task-48`](TASKS/task-48-distribution-msix.md) | -| 10 | ❌ | Retired 2026-10-08; superseded by the TASK 54 rows above — the Polar design it described is rebuilt as a lease (`commercial.md`), not revived as written | [`task-10`](TASKS/task-10-monetization.md) | - -## Pending the creator's hands (not programming) - -- ✅ App Settings grouping + right-click gear — reported working 2026-10-29. -- ✅ Transition picker/durations, Wipe, preview + program transitions — reported working 2026-10-29. -- ✅ Brand logo layer — reported working 2026-10-29. -- ⏳ Vertical-tier camera check (Live tile inside the band, Chat rect-only) — outstanding. -- ⏳ **Webcam cadence**: a Release take in the failing geometry (vertical tier first) blocks Units 1–2. - No fix until a reading names the stage. - -## Creator-requested follow-ups (park for Day 6) - -- Group controls under **This PC** and **YouTube**. -- ON-AIR opens a preflight using the saved visibility default and an explicit **Start Broadcast — [scope]** action. -- Persistent Help icon immediately left of Edit opens the creator-triggered, app-only 1-2-3 first-broadcast guide. Keep copy top-aligned and high-contrast; never composite the guide into broadcast output. -- Add YouTube category selection; send tags through the video API; support thumbnail upload. -- Add tooltips to settings tabs and make Logs wrapping effective. +| — | ☐ | **Steam packaging** — `publish` output + `steam_api64.dll`, paid app and Demo, depots, `steamcmd` upload, store pages + capsule assets, measured system requirements | [`Distribution.md`](Distribution.md) | +| 48 | ⏸ | Distribution & packaging (winget/portable) — parked behind the entitlement seam | [`task-48`](TASKS/task-48-distribution-msix.md) | +| — | ☐ | **Training-video series** — 32 subjects listed in `youTubeTrainingVideos.md`; each is documentation, channel content and support deflection at once | `youTubeTrainingVideos.md` | ## 1.0 gates (before the first shipped build) -- **Release visibility flow** — default to Private; let the creator choose Private/Unlisted/Public - in the ON-AIR preflight and change it live through `UpdateBroadcast`; keep development/test runs - forced Private. Hide the separate TEST button as already decided for v1. -- **Kill the dev multi-instance affordance** — `Helpers/InstanceProfile.cs` is already `#if DEBUG`; - re-prove it on a Release build. -- **Release engineering** — `scripts/publish.sh` **done** (self-contained win-x64, `ffmpeg\` - guarded, no test artifacts), `LlamaCasty.exe` assembly rename **done**, full license texts beside - `THIRD-PARTY-NOTICES.txt` (TASK 4 req 9). Outstanding: Debug-only `InternalsVisibleTo`. -- **Gold pass** (TASK 36): the screens/layers audit, native-Windows verification, and the - branding-flash decision. +- **Release visibility flow** — default to Private; let the creator choose Private/Unlisted/Public at + creation and change it live through `UpdateBroadcast`; keep development/test runs forced Private. +- **Kill the dev multi-instance affordance** — `Helpers/InstanceProfile.cs` is `#if DEBUG`; re-prove on a + Release build. +- **Single-instance guard** — Release has none: two launches share one layout DB (read-whole/write-whole) + and one audio pipe. A named mutex that activates the first window. +- **Gold pass** (TASK 36). +- Release engineering: `publish.sh` **done**, exe rename **done**, full licence texts beside + `THIRD-PARTY-NOTICES.txt` needed; Debug-only `InternalsVisibleTo` outstanding. ## Research diff --git a/TASKS/archive.md b/TASKS/archive.md index c86c570..46374a8 100644 --- a/TASKS/archive.md +++ b/TASKS/archive.md @@ -474,3 +474,55 @@ These were explicitly ruled out: default. Tests: `BrandLogoTests`. On-screen check pending. - ☐ **Cube** — GPU path gated on capability; CPU path only if it holds 60fps. - Gear: right-click opens App Settings (built, on-screen check pending). Left-click menu stays for now. + +--- + +# 2026-11-09 trim — completed rows and the retired surge plan + +Moved out of `TASKS.md` when the queue was refactored (creator ruling: finish the queue, then the new +features). Nothing here is active; the reasons for the two `❌` rows live in `docs/ai/decisions.md`. + +## Completed + +| Task | Scope | +|---|---| +| 53 | Output-format contract — six size/FPS modes, vertical YouTube playback, recordings visually accepted | +| 3·17 | Text layer — content, font, colour, alignment, drag/resize, persisted (2026-11-04) | +| 21 | Media source — video file decodes, composites, plays audio, loops, stops with last reference (2026-11-05) | +| 64 | Green/Blue Screen — detect + key from the decode pipe, opt-in auto-apply, alert clips included (2026-11-05) | +| 3·20 | Session report — derived from `superChatEvents.list` on demand, never persisted (2026-11-05) | +| 39·2 | YPP slice 2 — three eligibility bars, dated thresholds, "x of 3 criteria met" (2026-11-05) | +| 32 | Stream resilience — fragmented MP4, blip retry, poll-driven RECONNECTING, ingest pre-flight, one-click Back on air | +| 33 | Auto step-down — cause-classified (machine → shed, fps, resolution; upload → bitrate), one-way, floors | +| — | Preview undo (Ctrl+Z / Ctrl+Shift+Z) | +| — | OutputProfile gate + Demo conversion UI | +| — | System-message strip replaced by the Logs tab | + +## Retired rows + +| Task | Why | +|---|---| +| 49 | Chat profanity filter — YouTube owns moderation (2026-11-05) | +| 10 | Polar subscription path — superseded by the one-price model (2026-11-01) | +| — | `StreamConfig` defaults — no production call sites | +| 34 | Scheduled streams — retired 2026-11-09, platform's surface | +| 50 | Publish recording — retired 2026-11-09, platform's surface | + +## The surge plan (2026-11-02 → 11-07) — superseded + +Six days of assigned work, written before the queue was re-ordered. Kept for the record only; the live +order is in `TASKS.md`: + +| Day | Units | +|---|---| +| 1 | Licensing: ownership check, greyed tiers, About conversion, Logs tab | +| 2 | Release blockers: visibility flow, 30/60-fps gate, vertical resolution, webcam identity, Move | +| 3 | Resilience: TASK 32 + 33 | +| 4 | Content & media: text layer, media wiring, profanity filter (retired), scheduled streams (retired) | +| 5 | Data & settings: reward persistence, YPP slice 2, defaults split, scene audio, settings round, disk hygiene | +| 6 | Finish: publish recording (retired), undo, feedback batch, gold pass, sweep | + +## Creator hands — cleared 2026-11-09 + +- Vertical-tier camera check: 1–6 pass (Live tile inside the band, Chat rect-only, 16:9 restored exactly). +- Webcam cadence, vertical geometry: 29.8 distinct images/s — the 25.6/s failure does not reproduce. diff --git a/TASKS/task-14-creator-feedback-batch.md b/TASKS/task-14-creator-feedback-batch.md index 8173bc6..203e3b4 100644 --- a/TASKS/task-14-creator-feedback-batch.md +++ b/TASKS/task-14-creator-feedback-batch.md @@ -22,3 +22,15 @@ 13. ✅ Web source URI hidden for webcam — URI section was showing for webcam due to WPF binding path `SelectedElement.Type` not resolving on `WebcamSceneConfig`; replaced with `IsWebSource` virtual property on `SceneElement` (mirrors existing `IsWebcam` pattern), overridden in `Source` to return `Type == SourceType.WebSource`, bound via `BoolToVis` 14. ✅ Check/revert consistent order + accept — all three element sections (webcam border, countdown timer, web URI) now use consistent [✕][✓] button order; green check buttons enabled with click handlers that save current values as snapshot baseline (so revert undoes to last accepted state, not initial selection) + +### Branch 3 — queued 2026-11-09 (creator testing notes) + +| # | Slice | Why | +|---|---|---| +| 15 | ☐ **Mic input gain** — a dB stage *ahead of* the gate, meter reads post-gain | A headset mic sits close enough to clear the gate; a table-mounted boom is quieter and the gate eats it. Not AGC — automatic gain pumps on breath and silence. Clipping is already the limiter's job | +| 16 | ☐ **Webcam blank / standby** — a visibility hotkey for the webcam layer (tenth registration), optional standby media while hidden | "My webcam has to go away *now*." The privacy blur covers the screen case; this covers the camera in one keystroke | +| 17 | ☐ **Opacity + geometry consistency** — every layer type exposes the same opacity control and the same position/size affordances | Parity across layer types. Geometry fields stay, but collapse behind a "Geometry" disclosure: keyboard-only and screen-reader users cannot drag a layer, and precise alignment between two monitors is otherwise guesswork | +| 18 | ☐ **Show in folder** — on the stop notification, reveal the finished recording | Publishing is retired to YouTube (TASK 50); the app's remaining job is "where did my file go?" | + +One Good Dog test per slice. If any of them turns out to be a redesign rather than a fix, it gets its own +task number instead of hiding in this batch. diff --git a/TASKS/task-65-replay-clips.md b/TASKS/task-65-replay-clips.md new file mode 100644 index 0000000..a0aa23c --- /dev/null +++ b/TASKS/task-65-replay-clips.md @@ -0,0 +1,65 @@ +# TASK 65 — Replay clips: buffer the last 15 seconds, show it, save it + +> Catalog: [`TASKS.md`](../TASKS.md) — status and requirements live here. +> Spec settled with the creator 2026-11-09. **Not a Twitch-clip clone:** the platform owns viewer +> clips; this is the creator-side replay buffer YouTube does not give you. + +## The rule + +**15 seconds is a clip.** Options 10 / 15 / 20, default **15**, no slider, no 30. A clip is a moment, +not a segment — 3-minute Shorts are the platform's problem, not ours. + +## Why the ring holds encoded bytes, not frames + +A 1080p BGRA composite frame is 8.3 MB; fifteen seconds at 60 fps is **~7.4 GB** of raw frames. The +15 MB figure is *after* compression. So the ring keeps **fragmented MP4** (`frag_keyframe+empty_moov`) +that ffmpeg writes to a pipe, in a **byte ring in RAM** — never on disk, never raw. + +- Fragments are **1 s** (not 2): the ring can only hand back whole fragments, so a longer fragment + means the save can miss the punchline. +- The buffer is capped **by bytes**, not seconds — bitrate moves. When 15 s would exceed the cap, the + clip is simply shorter. +- The ring is a member of the process: it dies with the app, so a crash leaves no debris and needs no + sweep. **Nothing touches the disk until F10.** + +## Trigger + +**F10** saves the clip (F1–F9 are taken; F11 is the fallback if a game steals F10). No focus switch, no +mouse. Deferred deliberately: **voice triggering** (always-listening in a streaming app is a privacy +liability, and the game's own audio produces false positives) and **`!clip`** (only for viewers/mods, +never the creator, who cannot reach a keyboard mid-moment). + +## The replay layer + +One layer type, added by the creator to the scenes they want; behaviour comes from the scene: + +| Scene | Behaviour | +|---|---| +| **Live** | plays the new clip **once**, then **hides** — you're there to see it or you're not | +| Every other scene | loops the last **1–3** saved clips **in order** (highlight reel), configurable | + +- **Audio off by default**, toggleable per layer. The show's sound design is the creator's; a replay + should not re-mix it uninvited. +- **The replay layer is excluded from the clip's own composition.** Otherwise saving while a clip plays + records a video of a video (hall of mirrors). Viewers still see it live; the clip simply doesn't. + +## Storage and retention + +- Saved to `clips\` beside recordings, as a normal `.mp4`. +- **The three most recent are kept**; the fourth deletes the oldest. +- **Auto-clip on monetization events** (off by default): a Super Chat / membership / raid saves the + preceding 15 s hands-free. Same retention, so it cannot accumulate. +- **Disk discipline:** no steady writes while live (the ring is RAM); the free-space guard that already + protects REC also disables the ring when space is low — **the recording always wins**. + +## Privacy + +Named in `PRIVACY.md`: clips are local only, the three most recent are kept, older ones are deleted +automatically, and nothing is uploaded. The first save says the same in one line, so the policy is not +only a document. + +## Tests + +- Ring: fragment boundaries respected, byte cap enforced, save emits a playable MP4, oldest pruned. +- Layer: once-then-hide in Live, in-order loop elsewhere, silent by default, layer excluded from the clip. +- Failure: save with an empty ring (just started) says so instead of writing a 0-byte file. diff --git a/TASKS/task-66-timed-ticker.md b/TASKS/task-66-timed-ticker.md new file mode 100644 index 0000000..354a88e --- /dev/null +++ b/TASKS/task-66-timed-ticker.md @@ -0,0 +1,17 @@ +# TASK 66 — Timed ticker messages (the ticker's real job) + +> Catalog: [`TASKS.md`](../TASKS.md). Creator idea 2026-11-09. + +**Problem.** The ticker today is wired to YouTube events that a new streamer will rarely see — no +memberships, no Super Chats, nothing to tick. A feature that fires twice a year is furniture. + +**Idea.** Give the ticker its own clock: a list of creator-written messages plus an interval. + +- Config: interval (minutes) and a list of lines; rotate in order, newest config wins on restart. +- Example the creator gave: *every 20 minutes, promote the merch channel*. +- Yields to an alert: an event's own ticker beats a scheduled one, and the clock resumes after. +- Off by default, one message to start, no randomisation and no scheduling-per-day until asked. + +**Where:** the ticker section of the alert box properties. + +**Tests:** interval firing, rotation order, alert pre-emption, persistence across restart. diff --git a/TASKS/task-67-per-event-alert-videos.md b/TASKS/task-67-per-event-alert-videos.md new file mode 100644 index 0000000..91a851f --- /dev/null +++ b/TASKS/task-67-per-event-alert-videos.md @@ -0,0 +1,16 @@ +# TASK 67 — Per-event alert videos + +> Catalog: [`TASKS.md`](../TASKS.md). Creator idea 2026-11-09. + +**Problem.** The app plays one alert clip for every event. That single clip was always a placeholder. + +**Idea.** A clip per event kind — Super Chat, Super Sticker, membership, subscription, gift, raid — +falling back to the default when a kind has none assigned. + +- Each slot accepts a local file; the existing picker and the decode/keying pipe are reused unchanged + (TASK 21 / 64), including green/blue screen — a clip is a clip. +- Missing slot → default clip → nothing, in that order, and never an error for the creator who only + filled one. +- The alert box's display method, sound byte and ticker stay per-event as they already are. + +**Tests:** slot selection per event kind, fallback chain, a keyed per-event clip composites correctly. diff --git a/TASKS/task-68-edit-while-live.md b/TASKS/task-68-edit-while-live.md new file mode 100644 index 0000000..123e79f --- /dev/null +++ b/TASKS/task-68-edit-while-live.md @@ -0,0 +1,28 @@ +# TASK 68 — Editing while live, and the first-run On-Air state + +> Catalog: [`TASKS.md`](../TASKS.md). Creator rulings 2026-11-09. + +## Part 1 — edit while live + +Creators need to change a layout mid-show: hide a layer, nudge a camera, swap a video. Today editing is +gated to "not live", so the only way is to stop the show. + +- Allow the layer panel and geometry edits while streaming/recording. +- An edit becomes program immediately — one canvas, no preview/program split — so an accidental drag is + visible to viewers. Undo (Ctrl+Z) must therefore be live too, and the UI must say when editing is live. +- Preview-mode gestures stay as they are: left = zoom while live, right = menu; **no chord while live**. + Editing live is an explicit mode the creator turns on, not something a stray click does. +- Never allow: removing the Background, deleting a scene that is on air, changing the output tier's + composition mid-frame. + +## Part 2 — first-run state + +The app should open in **On-Air mode** to match the 1-2-3 screen, so the first thing the creator sees +is the state they will stream from. + +Risk to settle before building: an armed ON-AIR pill changes what Start does. The 1-2-3 screen must +*explain* the state rather than let the app default into the riskier action silently — a creator who +expects a recording must not go live by pressing Start. + +**Tests:** edit during a session changes the composite; undo restores; a blocked edit action reports and +does nothing; first-run state matches the guide's copy. diff --git a/docs/ai/decisions.md b/docs/ai/decisions.md index 128b6cc..ed4b568 100644 --- a/docs/ai/decisions.md +++ b/docs/ai/decisions.md @@ -219,6 +219,20 @@ Do not build these; each was explicitly ruled out. (), so no client-side moderation machinery is built on them. - Virtual camera output — a different product and a device-driver swamp; our output is one YouTube stream. -- Replay buffer, stream clipping, multi-destination restreaming, an in-app bug reporter, and an - advanced broadcast-form tab (latency/DVR/embed/projection/CC/region are fixed at sane defaults; - every exposed field is a support ticket). +- Multi-destination restreaming, an in-app bug reporter, and an advanced broadcast-form tab + (latency/DVR/embed/projection/CC/region are fixed at sane defaults; every exposed field is a support + ticket). +- **Replay buffer — REVERSED 2026-11-09 (TASK 65).** It was out on "different product" grounds; the + creator's case won: YouTube gives viewers clips but gives the creator no last-15-seconds buffer, so + this is the app's surface, not the platform's. Scoped tightly: 10/15/20 s, RAM ring of encoded + fragments (raw frames would be ~7.4 GB), three kept, silent by default. +- **Do not rebuild the platform's own surface (2026-11-09).** Scheduling and publishing are retired for + this reason: YouTube owns scheduled broadcasts *and* the reminder that gives them value, and Studio + owns upload (resumable, quota-aware, explains rejections) — reimplementing either would be a worse + copy that can fall out of sync. The app's edges are capture → scene → sound → output; everything past + the file belongs to YouTube. Publishing would also widen the OAuth consent to whole-channel video + management for one convenience. +- **Voice triggering — deferred 2026-11-09.** Not a recognition-quality problem: an always-listening + trigger in a streaming app hears the *game*, so the content is the false-positive source, and the + consent cost is worse than the upload scope we refused. Windows Voice Access or a Stream Deck can + send the keystroke; the app keeps F10. diff --git a/youTubeTrainingVideos.md b/youTubeTrainingVideos.md index 6753b69..1f311e8 100644 --- a/youTubeTrainingVideos.md +++ b/youTubeTrainingVideos.md @@ -66,6 +66,9 @@ launch set already in `MARCOM.md`. | Layout autosave | "close the app, come back, it is where you left it" | | Record with no account, no sign-in | "you do not need YouTube to press record" | | Test stream (private, simulated chat) | "practice without an audience" | +| **Replay clips** — F10 saves the last 15 s; a layer replays it (once in Live, a looping reel elsewhere) | "clip that" — the moment nobody had to leave the keyboard for | +| **Timed ticker messages** — every N minutes, your own line | "promote something without remembering to" | +| **Per-event alert videos** — a different clip per event kind | "your Super Chat gets its own moment" | | **Resilience**: ingest pre-flight (dead network never creates a ghost broadcast), reconnect without ending the stream, one-click **Back on air**, crash-safe fragmented MP4 | "the network dropped and the stream came back" — proof beats a feature list | | Chat overlay (fonts, colours, fade, never stored) | privacy + looks | | Alerts: built-in clip, custom video, sound byte, ticker | "your Super Chat moment" |