docs: v1 = feature-complete ruling — queue TASKs 32-36, close the out-of-product list, fix the map's lies
Audit of institutional knowledge lost across the refactor (creator PM session 2026-09-01): - New queue: TASK 32 resilience (blip retry/measured-grace sign/one-click back-on-air/ crash-safe fragmented-MP4 recording/pre-flight), TASK 33 auto step-down (v1 - the map claimed it existed; it didn't), TASK 34 drawer scheduling, TASK 35 scene-linked audio, TASK 36 gold pass (visibility unlock, flash-live enable, screens/layers audit, native verification suite, expiry reminders, signing/installer/EULA/Velopack, compile-flag ceiling HARDENED+MOCK_REWARDS). - 'v1 = the finished product' + closed 'Out of product' list (Stream Deck, profiles, chroma, virtual cam, replay buffer, restream, clipping, advanced-tab, bug-reporter, D3DImage preview) - the 10% margin, bounded and written. - Corrections: ffmpeg 'does reconnect' claims (input-side flags only; retry is app-side); TASK 2's orphaned 'resume deferred to TASK 3' now owned by TASK 32; TASK 2 End-signs-out superseded by TASK 18 explicit sign-out; Alerts freed from stale 'the one paid feature' language (paid = flash removal only); TASK 3 item 16 superseded; TASK 9 items 4/6/7 reconciled; TASK 10 monetization chain scoped v1; README roadmap/Structure rewritten.
This commit is contained in:
@@ -186,7 +186,7 @@ The AI hallucinated through multiple commits that night on background/scene prop
|
||||
|
||||
### Current limitations / TODOs
|
||||
|
||||
- `Helpers/OAuthCredentials.cs` contains the real ClientId/ClientSecret. Auth is complete and the session **persists via Windows DPAPI** (`Helpers/TokenStore.cs` → `%APPDATA%\ytLlive\ytLlive.auth`, CurrentUser scope), reloaded best-effort at startup with a proactive refresh of a near-expiry access token. Sign-in/Change Account lives **inside the Start Stream dialog** (two-state flow — no separate Connect button). A **graceful End Livestream signs out**: `StopStream()` clears the session + token, so the next go-live needs a fresh sign-in; a crash never runs End, so the token survives and the creator stays signed in. `YouTubeAuthService` takes an optional `HttpClient` + `sessionChanged` callback (test seam + save hook; services are still constructed in `MainViewModel`)
|
||||
- `Helpers/OAuthCredentials.cs` contains the real ClientId/ClientSecret. Auth is complete and the session **persists via Windows DPAPI** (`Helpers/TokenStore.cs` → `%APPDATA%\ytLlive\ytLlive.auth`, CurrentUser scope), reloaded best-effort at startup with a proactive refresh of a near-expiry access token. Sign-in/Change Account lives **inside the Start Stream dialog** (two-state flow — no separate Connect button). Sign-out is **explicit only** (Logout / Change Account → `SignOutYouTubeAsync`): `StopStream()` does NOT clear the session — TASK 18's 2026-08-29 reversal ("stopping a recording leaves the creator signed in") — the older "graceful End signs out" line here was stale and is corrected against the code (2026-09-01). `YouTubeAuthService` takes an optional `HttpClient` + `sessionChanged` callback (test seam + save hook; services are still constructed in `MainViewModel`)
|
||||
- Scene/source/asset layout + the social bar persist (SQLite, schema v8); the OAuth session persists (DPAPI); the paid-unlock state persists (LayoutStore Settings table — `LicenseKey`/`LicenseValidatedAt`/`IsPremium`, 14-day offline grace)
|
||||
- `YouTubeStreamService` manages the **variable reusable stream** (shipped 2026-08-16): `GetOrCreateReusableStreamAsync` lists `liveStreams?mine=true` and reuses the existing `cdn.isReusable` stream, creating it only on first use (`resolution=variable`, `frameRate=variable`); the stream is cached via `LayoutStore` (`SaveReusableStream`/`LoadReusableStream`, Settings table) and bound at broadcast insert (`boundStreamId`). Health (shipped 2026-08-16): `GetStreamHealthAsync(streamId)` polls `liveStreams?part=status` for `healthStatus` + `configurationIssues[]` → `StreamHealth`; banner decision in pure `StreamHealthReporter`
|
||||
- Webcam capture is shipped (milestone 1); the live desktop/game backdrop is shipped (ship task #1); **the output compositor (TASK 4 ship step 1) is SHIPPED**, **the FFmpeg locator (TASK 4 ship step 2) is SHIPPED**, **the encoder + RTMP push (TASK 4 ship step 3) is SHIPPED**, **WASAPI audio capture (TASK 4 ship step 4) is SHIPPED**, **frame-pipeline wiring (TASK 4 ship step 5) is SHIPPED**, **health stats (TASK 4 ship step 6) is SHIPPED**, **one-click go-live + private-only enforcement (TASK 4 ship step 7) is SHIPPED** — TASK 4 (RTMP Ingest) is fully done; full plan in `TASKS.md`
|
||||
@@ -870,11 +870,14 @@ crooked.
|
||||
Product: `d105dfa1-497e-423b-8cd4-e0ee2e3abbc0`. Checkout: `llamacasty.com` → Polar hosted page.
|
||||
Org ID: `c05fb364-b967-4f6c-adf2-8a144e46d085` (org-scoped OAT — `organization_id` omitted from API calls).
|
||||
Key prefix: `LCYT-`. Discounts: `LLAMAFOUNDER` (100% off, 50 uses), `LLAMA50` (50% off, 12 months). Details in `MONETIZATION.md`.
|
||||
- **Support (creator's model):** in-app bug-reporting mechanism → issues into git; most queries are
|
||||
- **Support (creator's model, corrected 2026-09-01):** support = email + GitHub issues — the
|
||||
once-planned in-app bug-reporter is **out of product** (TASKS.md → closed list). Most queries are
|
||||
how-tos / feature requests / manual-skimmers. Maintenance cadence = "when I get around to it" with
|
||||
emergency patches; not a 24/7 service promise.
|
||||
emergency patches; not a 24/7 service promise. The only license chatter is the paid-user expiry
|
||||
reminder (TASK 36 item 5); active subscribers see zero license UI.
|
||||
|
||||
**Monetization awareness (built-in, ungated, free — 2026-09-01):** the app is monetization-aware by
|
||||
**Monetization awareness (built-in, ungated, free — 2026-09-01; v1 SCOPE per the complete-v1
|
||||
ruling — build order: capture → report → journey → Alerts):** the app is monetization-aware by
|
||||
design, for **every** creator, free and ungated (the only subscriber swap remains the branding-flash
|
||||
removal above). It has three layers, all free — this is the product's differentiator, not a paid tier:
|
||||
|
||||
@@ -904,8 +907,9 @@ removal above). It has three layers, all free — this is the product's differen
|
||||
**What was rejected:** always-on watermark (obscurable — replaced by the flash), hard stream-time
|
||||
cutoffs (the worst dead end — a stream dying mid-broadcast reads as broken, and YouTube streams
|
||||
routinely run 2-4 hours), soft-limit nagging, freemium feature tiers, and donation-only (relies on
|
||||
the kindness of strangers). Resolution/quality ceilings are **deferred** — that decision belongs to
|
||||
the resolution & streaming-constraints conversation, not monetization.
|
||||
the kindness of strangers). Resolution/quality ceilings stay rejected (fixed 2026-09-01, no longer
|
||||
"deferred"): the free tier never loses quality — auto step-down (**TASK 33**) is a *protect the
|
||||
stream* feature, never a monetization penalty.
|
||||
|
||||
## Auth gates Go Live, but not exploration
|
||||
|
||||
@@ -940,16 +944,7 @@ These are the hard facts behind every decision. Full list in `TASKS.md`.
|
||||
to subscribers and serve as post-mortem review tapes; bulk-delete pre-GA. The unlock is a
|
||||
deliberate final-pass item in **TASK 36 (gold pass)**, wired with the dialog selection — never
|
||||
opportunistic. The PRIVATE badge keeps showing when the stream is actually private.
|
||||
- **Full broadcast form (TASK 9 item 7)** — the go-live dialog exposes all YouTube API-supported fields.
|
||||
**Core tab** (always visible): title, description, visibility (Private/Unlisted/Public), made-for-kids,
|
||||
schedule (start + optional end datetime). **Advanced tab** (expandable, sane defaults): latency
|
||||
(Normal/Low/Ultra-Low, default Low), DVR (default on), embed (default on), record-from-start
|
||||
(default on), projection (rectangular/360°, default rectangular), closed captions
|
||||
(disabled/embedded/HTTP, default disabled), auto-start (default on), auto-stop (default on),
|
||||
monitor stream (default off, for testing), region restrictions (country codes, optional).
|
||||
`categoryId` is removed from `CreateBroadcast` (not a `liveBroadcast` field, silently ignored).
|
||||
Monetization via `liveBroadcasts.update` (not settable on insert) — separate step after broadcast
|
||||
creation. Go-live order (TASK 9, shipped 2026-08-16):
|
||||
- **Full broadcast form (TASK 9 item 7) — RESCOPED 2026-09-01** — the core editable fields ship (since 2026-08-24) as the always-visible **Text drawer**: title, description, tags, visibility, made-for-kids, live-editable; scheduling ships as **TASK 34** (drawer ☑ Scheduled + adoption at Start). The old **Advanced tab is permanently out of product** (the 10% margin — see TASKS.md → "Out of product"): latency locked `low`, DVR/record-from-start locked on, embed/projection/CC/region fixed at sane defaults, invisible — every exposed field is a support ticket. `categoryId` is removed from `CreateBroadcast` (not a `liveBroadcast` field, silently ignored). Monetization enablement (if ever needed) rides the reward-events chain via `liveBroadcasts.update`, not a form field. Go-live order (TASK 9, shipped 2026-08-16):
|
||||
`BeginGoLive` → `PrepareAndStartLiveAsync` — ensure the reusable stream (`GetOrCreateReusableStreamAsync`,
|
||||
cache it), create the broadcast bound to it (`CreateBroadcast(..., stream.Id)` → `boundStreamId`),
|
||||
THEN start the pump (the URL must exist before `FramePump.StartAsync`, which reads it once). Failure →
|
||||
@@ -959,7 +954,9 @@ These are the hard facts behind every decision. Full list in `TASKS.md`.
|
||||
(`cdn.resolution=variable`, `cdn.frameRate=variable`, `isReusable=true`) only on first use; the
|
||||
ingestion URL is cached via `LayoutStore` Settings (`SaveReusableStream`/`LoadReusableStream`) and
|
||||
bound to each broadcast at insert (`boundStreamId`). Any quality tier works without recreating the
|
||||
stream, and auto step-down is done by us dropping bitrate on the fly (zero API calls).
|
||||
stream, and auto step-down rides the same property — we drop bitrate/resolution on the fly, zero
|
||||
API calls — **but the deciding governor is not built**: the old present tense there was a map-lie,
|
||||
corrected 2026-09-01; step-down is **TASK 33** (v1 scope).
|
||||
- **Quality is greyed out while live** — resolution/frameRate/ingestionType are immutable after
|
||||
stream creation; editing title/description/privacy is fine at any time.
|
||||
- **Report-by-exception health (SHIPPED 2026-08-16, TASK 9 item 3)** — `YouTubeStreamService.GetStreamHealthAsync(streamId)`
|
||||
|
||||
Reference in New Issue
Block a user