docs(queue): six veggies, then the new work; scheduling and publishing retired
Creator ruling 2026-11-09: finish the queue before the new features, gold pass always last. **Retired (platform's surface, not ours):** - 34 scheduled streams — YouTube owns scheduling *and* the reminder that makes it worth anything. - 50 publish recording — Studio owns upload (resumable, quota-aware, explains rejections), and an in-app uploader would widen the OAuth consent to whole-channel video management for one convenience. The app adds "Show in folder" instead. **New tasks:** 65 replay clips (the creator-side buffer YouTube does not give you), 66 timed ticker messages, 67 per-event alert videos, 68 edit-while-live + first-run On-Air. **TASK 65 spec** is the interesting one: 15 s is a clip (10/15/20, default 15); the ring holds encrypted *encoded* fragments in RAM — raw 1080p frames would be ~7.4 GB for 15 s — so there is no steady disk write and no crash debris; F10 saves; the replay layer plays once then hides in Live and loops the last 1–3 elsewhere; silent by default; three kept; the layer is excluded from its own clip (hall of mirrors). **TASK 14 gains Branch 3:** mic input gain ahead of the gate (table boom mics fall under it), webcam blank hotkey, opacity/geometry parity with a collapsed Geometry disclosure, Show-in-folder. **Decisions recorded:** the replay-buffer reversal (it was retired in 2022-era reasoning as "different product"; the case for it won), the platform-surface rule, and voice triggering deferred — always- listening in a streaming app hears the game, so the content is its own false-positive source. Queue refactored to 56 lines by archiving completed rows and the superseded surge plan; HANDOFF at 40.
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
Reference in New Issue
Block a user