The handoff asked for the RTMP diagnostic as item 1 and claimed the next code unit was packaging. Both are now wrong, and per the map's own rule a stale handoff is worse than none because the reader trusts it. 1. DIAGNOSTIC ANSWERED, and it narrowed the question rather than closing it. ffmpeg reaches YouTube — rtmp://a.rtmp.youtube.com/live2/..., 7.4 MB video plus 46 KB audio muxed, no socket error. The broadcast never leaves `ready`: enableAutoStart=true was sent, yet close-out refused `complete` with 403 invalidTransition, which is only legal from `live`. Two things stay honestly unknown — the push was only 8s, and the app never polls broadcast status during a stream (only stream health), so the log cannot show whether or when it flipped. Next step is one log line plus one 60-second test. 2. Packaging is DEFERRED, not next. Creator ruling 2026-10-04: still in active development, so ship-time work waits. Item 1 of "Next" said the opposite. 3. Push state: origin/main =cc2d1efas of 2026-10-04, tree clean. Stale push state has now bitten twice here, so the line now tells the next session to verify with rev-list instead of believing the file. 4. Test count 381, not 380. 5. New landmine: the frame pump logged 86 stalls per 5s (142 lines in an 8-second window) while stats still read 301/300 frames and 0 dropped. Those two facts contradict each other; recorded as a measurement to take, not a bug to fix from the log. Startup stall (TRAX / unreachable NAS) shipped incc2d1efand is documented in ai.md; not duplicated here.
345 lines
24 KiB
Markdown
345 lines
24 KiB
Markdown
# HANDOFF — current state
|
||
|
||
**Branch:** `main` (pre-1.0, no feature branches — creator ruling 2026-08-24).
|
||
**Pushed 2026-10-04:** `origin/main` = `cc2d1ef`, working tree clean. That push carried
|
||
13 commits (the 2026-09-27 → 10-02 backlog plus two units from today). The stale-push-state
|
||
failure `MyMistakes.md` warns about has now bitten twice, so the rule stands: **check
|
||
`git rev-list --count origin/main..main` before trusting any line here about push state.**
|
||
|
||
## 🚨 FIXED 2026-09-27 — "Change Account" no longer signs you out before the browser opens
|
||
|
||
**Reported as:** *"I clicked on my signed-in account pic, then clicked sign-in to another account. It
|
||
opened the browser page to login under a different account and I canceled the request. But when
|
||
returned to the app, I see logged out — and should not have happened! Cannot sign-in because the
|
||
callback url was not released."* Also reported as hitting **both** running instances.
|
||
|
||
**Two independent defects, and the second one was masking a race:**
|
||
|
||
1. **The sign-out ran first.** `ChangeAccountAsync` was `await SignOutYouTubeAsync(); await
|
||
SignInAsync();`. `SignOutYouTubeAsync` → `TokenStore.Clear()` → **deletes** `ytLlive.auth`. So the
|
||
working account's token was destroyed *before the browser even opened*; cancelling the chooser left
|
||
nothing. Now `ChangeAccountAsync` is just `await SignInAsync()` — success overwrites the token via
|
||
the `sessionChanged` hook, cancel/failure keeps the current session. No sign-out is needed at all.
|
||
2. **The retry couldn't rebind the callback port.** Abandoning the browser gives the app no signal, so
|
||
the attempt held `localhost:8765` for its full 5-minute timeout. A new `AuthenticateAsync` now
|
||
cancels and awaits the abandoned attempt before binding, **serialised by a `SemaphoreSlim`**
|
||
(`_authGate`) — without the gate, the takeover window let a third caller see a free port and race
|
||
to bind it, i.e. the takeover reproduced the original failure. My own test caught this.
|
||
|
||
**The cross-instance claim was NOT the cause** and is still open as a *separate* question: no code path
|
||
can sign out a second instance (`TokenStore` is per-`InstanceProfile.DataRoot`, `Clear()` is a local
|
||
file delete with no server-side revoke, and no second instance had a token at inspection time). Verify
|
||
both processes carried distinct `YTLIVE_INSTANCE` values and ran Debug builds before chasing it.
|
||
|
||
Test: `YouTubeAuthServiceTests.RetryingAfterAnAbandonedAttempt_ReleasesTheCallbackPort`. Full suite
|
||
**370/370**, 0 warnings (build `ytLive.Tests.csproj`, not just the app project, or VSTest runs a stale
|
||
test DLL). Added an `openBrowser` ctor seam to `YouTubeAuthService` so tests never launch a real
|
||
browser. **Coverage gap:** the *ordering* fix (defect 1) has no automated test — `_youtubeAuth` is
|
||
hardcoded in `MainViewModel` and `ChangeAccountAsync` is private, so a real test would shell out to
|
||
the browser. Wiring an auth seam into `MainViewModel` is a deliberate follow-up, not a bug-fix extra.
|
||
|
||
## 🗄 PARKED — Google OAuth verification: attempted, hit a Google-side bug, tabled by ruling
|
||
|
||
**Creator ruling: unimportant for where we currently sit.** Verification is *not* a live blocker and
|
||
**must not be re-attempted, re-derived, or re-researched** without a new ruling. The 2026-09-27 entry
|
||
that preceded this one wrongly sat at `⏭ NEXT STEP` and framed it as the top priority — that was the
|
||
plan going in, not the outcome, and the outcome was never written down. Corrected 2026-09-28.
|
||
|
||
**What happened:** the verification attempt was made and failed on a bug **on Google's side** — not
|
||
something we can fix, work around, or retry our way past. ⛔ **The exact mechanism was never recorded
|
||
and is deliberately left blank** rather than reconstructed from memory: a confidently wrong note here
|
||
is worse than none, because the next session would trust it. Do not go looking for the cause.
|
||
|
||
**Owning account (creator-confirmed): `llamachile.shop@gmail.com` ("gramps").** Cloud project number
|
||
`198374416003` (from the ClientId) — banked in `ai.md:233`, **do not re-hunt it.** The one-hop console
|
||
link is `https://console.cloud.google.com/apis/credentials?project=198374416003` (picker defaults to
|
||
**"Recent"** — switch to **"All"**). ⛔ Not the same account as the YouTube channel signed into the app.
|
||
|
||
**What tabling costs, stated honestly:** if the consent screen is still in **Testing**, the 100-user
|
||
cap and the **7-day refresh-token expiry** both stand — that expiry is why the creator kept having to
|
||
re-consent. Parking verification does not fix that; it accepts it for the solo-use phase. It becomes
|
||
real work the moment a *second real user* needs to sign in, or at Store submission (TASK 48). The
|
||
sensitive-vs-restricted classification was never read and is moot until then.
|
||
|
||
**One of these two survives the tabling, and the other was a bad idea (corrected 2026-09-28):**
|
||
- ✅ **DONE — `Services/YouTubeAuthService.cs` no longer requests `auth/youtube.force-ssl`.**
|
||
It is **redundant**: its grant is *"see, edit, and permanently delete your YouTube videos,
|
||
ratings, comments and captions"*, a strict subset of `auth/youtube`'s *"Manage your YouTube
|
||
account"*, which is the scope we keep. Google's own `liveBroadcasts.insert` reference lists
|
||
`auth/youtube` as an accepted **alternative** to `force-ssl`, so every endpoint this app calls
|
||
(`channels`, `liveBroadcasts`, `liveStreams`, `liveChat`, `playlistItems`) is covered.
|
||
Two consent lines became one. **No forced re-consent** — a refresh token keeps the scopes it
|
||
was originally granted, so existing sessions are untouched; the reduced set applies at the
|
||
next fresh sign-in. Removing a scope cannot narrow capability when the one kept is broader.
|
||
- ❌ **NOT doing this, and the earlier note recommending it was wrong: moving
|
||
`playlistItems.list` off the user's session token.** It was written during the verification
|
||
hunt on the theory that "public data should not ride on user credentials". Walking the code
|
||
kills it: `ChannelStatsService` is built as `new ChannelStatsService(_youtubeAuth)` with **one**
|
||
`HttpClient`, and `channels?mine=true` (same service) **genuinely needs the session**. To split
|
||
it you would need a second unauthenticated client **plus a Google API key**, because every
|
||
YouTube Data API request needs a credential — without a key the call 403s. So the "fix" means
|
||
**shipping a new extractable credential in the binary, subject to quota abuse, to replace a
|
||
token the app already holds.** It buys **zero** verification-surface reduction (the data is
|
||
public; no scope is involved) and zero correctness. Strictly worse. ⛔ Do not "fix" this later
|
||
without a new ruling.
|
||
|
||
## ✅ DECIDED 2026-09-27 — pricing: `$10/mo` subscription, or `$400` lifetime, **no annual**
|
||
|
||
**Creator's ruling, verbatim: *"Let's just go with $10 monthly recurring subscription for option
|
||
A and, for option B, a lifetime sub for $400. No annual."* ⛔ **No `.99` prices** (earlier
|
||
ruling: *"Let's stop with the x.99 stuff - I find that irritating. Just say: ten bucks a
|
||
month"*). This **supersedes** the 2026-09-21 one-time `$29 → $49` model and the old `$99/yr`
|
||
sub. No feature gating: free gets everything, the branding flash is the only paid delta.
|
||
|
||
**✅ Store revenue share VERIFIED — 15%, and subscriptions are NOT penalised.** Read from the
|
||
ADA v8.10 PDF itself (downloaded, text-extracted — not a search excerpt). §6(b)(i) charges
|
||
**15%** on "any Apps (and any In-App Products in such Apps) that are **not listed in Section
|
||
6(b)(iii)**"; 12% is Games-only; 30% covers Xbox console apps/games, Xbox non-subscription
|
||
IAP, and Windows 8/Phone 8. LlamaCasty is a Windows PC App — not a Game, not Xbox, not Win8 —
|
||
so **6(b)(i) governs both tiers.** The agreement's own changelog (**v8.0, Oct 26 2017**) says
|
||
it outright: *"implement the **85/15 revenue share for non-Game subscriptions**."*
|
||
|
||
⇒ **`$10/mo` nets `$8.50/mo` (~$102/yr). `$400` lifetime nets `$340`.** Also confirmed: the
|
||
15% applies *after* VAT/GST (Net Receipts), payouts are monthly above a **$50** threshold, and
|
||
no better small-indie rate was found in the standard terms.
|
||
|
||
**⛔ Two obligations §6(h) attaches to the recurring tier** (why lifetime is the hedge):
|
||
(1) we must **fulfil the subscription for the entire period as marketed**, and on breach
|
||
Microsoft may refund the customer *"the full amount, plus taxes... in Microsoft's sole
|
||
discretion"*; (2) **raising the price disables auto-renew** — so `$10` is effectively locked
|
||
for the product's life absent Microsoft's price-change process.
|
||
|
||
**⏳ Still open:** the `$50` subscriber→lifetime upgrade-discount SKU was discussed and is
|
||
**not approved** — do not declare it. Individual vs Company Partner Center account still
|
||
unanswered (get it right before enrolling).
|
||
|
||
## ✅ DECIDED 2026-09-27 — distribution is the Microsoft Store: MSIX + Store IAP
|
||
|
||
**Creator's criteria, verbatim: "zero headaches, minimal maintenance (for me) while still
|
||
providing accountability and a reasonably easy upgrade flow."** Route A is the only option
|
||
where all four are solved by handing the work to Microsoft rather than to a certificate
|
||
vendor: **$0/yr, no certificate, no HSM, no annual renewal, no SmartScreen ramp**, plus Store
|
||
auto-update, Store-side payments/entitlements/refunds/support, and Microsoft review as the
|
||
accountability layer. The rejected options and their reasons are in
|
||
`TASKS/research-store-certification.md` §3 — **read that before reopening the question, not
|
||
before re-deriving it.**
|
||
|
||
**The big consequence: the whole licensing backend is deleted.** `PolarLicenseService`,
|
||
`PolarLicense`, `MainViewModel.License.cs` (`PremiumUrl`, customer portal, the
|
||
`OfflineGracePeriod = 14 days` subscription-era artifact, renewal/lapse copy) and the wrong
|
||
"Polar unlocks alerts" string all become dead code. `IsPremium` comes from the **Store
|
||
entitlement** instead of an HTTP call — which also deletes the entire "network flaky → app
|
||
thinks I'm expired" bug class. **Velopack, the update URL and the DO droplet go too.**
|
||
|
||
**What does not change: the entitlement.** Free gets everything; the branding flash stays the
|
||
ONLY paid delta. Store IAP changes how `IsPremium` is *obtained*, never what it *gates*.
|
||
|
||
**⛔ The old "still open: the price" note is retired — the price is settled, see the top of
|
||
this file.** The old "~$69 floor" and "don't break the launch-price promise" notes refer to
|
||
the old Polar storefront and are **not** current.
|
||
|
||
### ✅ DONE 2026-09-28 — TASK 48 item 1: bundle ffmpeg (was the "first code unit")
|
||
|
||
The app no longer downloads an executable at runtime. `scripts/fetch-ffmpeg.sh` populates a
|
||
gitignored `ffmpeg/` from a **SHA-256-verified** pinned month-end BtbN artifact; `ytLive.csproj`
|
||
copies it to output *and publish* and **fails the build** if it's missing; `FfmpegLocator` lost its
|
||
downloader, its zip extraction and the `%APPDATA%\ytLlive\tools` cache, and now resolves
|
||
**bundle → PATH** with a loud, actionable failure. The pin that 404'd is gone, so the
|
||
2026-09-01 failure class cannot recur. Suite **379/379 at that point** (380 now — see Test
|
||
state); 16 locator facts including an
|
||
integration test that **executes the bundled binary** (which is what proves the sibling DLLs
|
||
resolve). `THIRD-PARTY-NOTICES.txt` corrected — it still named the *stale* tag
|
||
`autobuild-2026-08-09-13-03`, i.e. the one that 404'd.
|
||
|
||
**Fresh-clone cost, deliberately accepted:** `./scripts/fetch-ffmpeg.sh` once (~65 MB). 135 MB of
|
||
DLLs in git would tax every commit on this 9p tree; the URL + SHA-256 are the audit trail.
|
||
|
||
**Answering "other 3p binaries needing bundling?" — no, verified.** ffmpeg was the only
|
||
download-and-execute. `e_sqlite3.dll` + `WebView2Loader.dll` already ship via NuGet
|
||
(`runtimes\win-x64\native\`), OpenH264 is compiled *into* `avcodec-63.dll`, the rest are managed.
|
||
Two gaps remain that bundling cannot fix, and they are the next real risks: the **WebView2 Evergreen
|
||
runtime** is a machine prerequisite (item 2's manifest dependency + a graceful-failure path), and
|
||
**Velopack's updater downloads and executes**, so its teardown in step 3 is a compliance action.
|
||
|
||
### Deferred, not next: the packaging project + `Package.appxmanifest` (TASK 48 items 2–3)
|
||
|
||
**Creator ruling 2026-10-04: still in active development, so ship-time work waits and
|
||
queued features come first.** The unit is unchanged and ready when it is wanted:
|
||
Full trust, `webcam`/`microphone`, English only; Velopack deleted. Add the `DefaultRecordFolder()`
|
||
comment in the same unit (the mediumIL invariant below becomes load-bearing the moment the manifest
|
||
lands). Full-trust writes under a real package identity are still **unverified** — confirm on a
|
||
real packaged build before trusting them with recordings.
|
||
|
||
Carried over from the research unit, still open:
|
||
- **⛔ `Distribution.md:318` recommended a $400+/yr EV certificate for a SmartScreen bypass
|
||
Microsoft removed in March 2024.** Fixed then — had it shipped, it would have cost
|
||
$400+/yr to buy what $150 buys. Now moot anyway (MSIX is signed by Microsoft), but the
|
||
lesson in `MyMistakes.md` stands: a policy citation is a claim about **scope**, not just
|
||
text.
|
||
- **WebView2 Evergreen runtime** is a machine prerequisite the app will hard-fail without. The
|
||
manifest needs the framework dependency, and WebView2 init needs a graceful error instead of a
|
||
crash. New, found while bundling ffmpeg.
|
||
|
||
### ⛔ Two invariants that break silently if touched
|
||
|
||
- **Full trust, or recording breaks.** `DefaultRecordFolder()` writes to Downloads/MyVideos;
|
||
that passes through unvirtualized **only** at `mediumIL`. Flip the manifest to
|
||
`appContainer` and output vanishes with no error. A comment is owed there **when the
|
||
manifest lands** (TASK 48 item 6) — not before.
|
||
- **Chat is rendered, never stored.** That non-persistence half is the load-bearing part of
|
||
the 11.12 certification answer. No chat history, moderation log, analytics, or crash payload
|
||
may be added without re-arguing 11.12 first.
|
||
|
||
## 🚨 OPEN 2026-09-28 — the RTMP push never lands: recordings reach YouTube as EMPTY placeholders
|
||
|
||
**This is a live bug, not a queued feature**, and it reframes a complaint the map previously
|
||
treated as closed. It was found on 2026-09-28 while scoping TASK 50, and the handoff had no
|
||
entry for it at all — corrected here.
|
||
|
||
**What the creator observed:** the channel carries **20+ private videos, all content-less
|
||
placeholders** (creator-inspected, 2026-09-28). This is the real explanation of the
|
||
2026-09-26 report "I still cannot get private test recordings saved" — it was never true that
|
||
the app could not *find* an archived VOD. **No video content is ever produced on the channel.**
|
||
|
||
**Mechanism (verified against the code AND the channel):** `liveBroadcasts.insert` succeeds and
|
||
creates the placeholder; everything after it is what fails. Content only exists if all three of
|
||
these land, and at least one does not:
|
||
1. broadcast reaches **LIVE** via `liveBroadcasts/transition`,
|
||
2. ffmpeg actually pushes RTMP to the bound stream key,
|
||
3. the broadcast ends and YouTube transcodes it into a VOD.
|
||
|
||
`enableAutomaticArchiving` is **never set**, so YouTube's `true` default applies — the archiving
|
||
route exists but has nothing to archive. Broadcasts are inserted `private`
|
||
(`Services/YouTubeStreamService.cs:49`), so the orphans are not public, and the app has **no
|
||
delete path** — the creator clears each one by hand in Studio.
|
||
|
||
**✅ DIAGNOSTIC ANSWERED 2026-10-04 — ffmpeg reaches YouTube; the broadcast never goes LIVE.**
|
||
One test-mode push, read from `startup.log`. The three steps, resolved:
|
||
1. `new MediaFoundationReader`/RTMP path — **WORKS.** ffmpeg opened
|
||
`rtmp://a.rtmp.youtube.com/live2/4pew-…`, muxed **7.4 MB video + 46 KB audio**, and
|
||
finalised the FLV with no socket error. Step 2 is fine.
|
||
2. **The broadcast stays `ready` — this is the break.** `enableAutoStart = true` *is* sent
|
||
on insert (`Services/YouTubeStreamService.cs:73`), yet at close-out YouTube refused
|
||
`transition=complete` with **403 `Invalid transition`** — which is only legal from `live`.
|
||
So YouTube never flipped `ready → live`.
|
||
3. Never reached (nothing to archive because nothing went live).
|
||
|
||
**Two unknowns, and the honest state of each:**
|
||
- **The push ran only 8 seconds** (08:48:23.9 → 08:48:32.8). YouTube may simply need longer
|
||
to accept and validate the ingest before it auto-starts. Untested — do not assume.
|
||
- **The app cannot see this either way.** During a stream it polls **stream** health
|
||
(`liveStreams`) and never the **broadcast** status; `GetLifeCycleStatusAsync` is called
|
||
*only* from the end path (`YouTubeStreamService.cs:146`). So the log contains no evidence
|
||
either way, and `EndBroadcastAsync` skips only on `complete`/`revoked`, meaning it tries
|
||
`complete` against a `ready` broadcast and earns the 403.
|
||
|
||
**⇒ Next step, small and cheap:** log the broadcast's `lifeCycleStatus` while streaming
|
||
(reuse the existing health poll timer — no new loop), then run **one 60-second** test. That
|
||
single run answers it. Do not go hunting beyond it until it has.
|
||
|
||
**Sequencing constraint — do not flip the flag early.** `enableAutomaticArchiving: false` is the
|
||
correct end state (TASK 50 will upload the local file deliberately), but archiving is
|
||
**currently the only route a recording takes to YouTube** — flipping it before TASK 50 ships
|
||
would leave test streams with nothing at all and re-create the 2026-09-26 complaint in a worse
|
||
form. Ship the upload, flip the flag in the same task, never before.
|
||
|
||
Detail and the open 11.12 re-check: [`TASKS/task-50-publish-recording.md`](TASKS/task-50-publish-recording.md).
|
||
|
||
## Landmines
|
||
|
||
- **Frame pump jitter, unquantified (2026-10-04).** The same test stream logged **86 stalls
|
||
per 5s** (`FramePump stall: iteration 40–50ms` against a 17ms budget; 142 such lines in an
|
||
8-second window) while `FramePump stats` still read 301/300 frames, 0 dropped. Nominal 60fps
|
||
with zero drops *and* repeated 3× overruns is a contradiction worth one measurement, not a
|
||
guess. Do not "fix" it from the log alone.
|
||
- **Stale fact inside `TASKS.md` (left alone, flagged here per the Scope Lock).** The "1.0 gates"
|
||
section asserts "**TASK 36 is now shipped**", but the catalog row 36 reads **☐ Queued** and
|
||
`task-36-gold-pass.md` still shows items 1–6 unchecked. The line most likely means *item 2*
|
||
(branding flash goes live) shipped at `9761b1d`. **Needs a one-line fix, not a code change** —
|
||
flagging rather than fixing because it is not this unit's job.
|
||
- **A stale `ytLive.exe` (PID 2544) locked `bin/.../ytLive.exe`** and broke `dotnet run` with
|
||
MSB3027. Killed. **If the build fails to copy `ytLive.exe`, check for a running instance first.**
|
||
- **`LayerReorderPersistenceTests.RealMouseDrag_OnTheLayerList_PersistsTheReorder` is flaky by
|
||
environment** (needs an interactive desktop session; 2 fail / 1 pass in isolation). **Run the
|
||
suite with ytLive CLOSED.** Not a regression.
|
||
- **`RealAppHost.RunAsync` exists for a reason** — a frame-pump test MUST `await` inside it. A
|
||
blocking wait occupies the one shared STA thread and **hangs the whole suite with no output.**
|
||
Always background a `vstest` run and poll the log; a foreground piped `vstest` returns nothing
|
||
in this shell even on success.
|
||
- **`GlobalHotkeys`: two instances registering the SAME global hotkey** — Windows refuses the
|
||
second. Left alone deliberately.
|
||
- **MSIX writes under a real package identity are unverified.** The docs say full trust passes
|
||
user-profile writes through, but confirm on a real packaged build before trusting it with
|
||
recordings (TASK 48 item 6).
|
||
|
||
## Test state
|
||
|
||
**381/381, 0 warnings, re-verified 2026-10-04 on a clean test-project build** (380 + the
|
||
TRAX probe test added today). Build the **test** project, not just the app project, or VSTest
|
||
runs a stale DLL.
|
||
|
||
**⚠️ New flake, do not chase it as a regression:** on the *first* full-suite run,
|
||
`AudioPipelineTests.MasterLimiter_CapsTheLiveMix_OnThePipe` failed with `Expected 0.891,
|
||
Actual 0` — a **zero**, not a slightly-off value, which is the signature of the pipe not being
|
||
fed yet rather than the limiter misbehaving. It passed 27/27 as a class run and 380/380 on an
|
||
immediate full-suite re-run. It reads the live mix **off the named pipe**, so it is a
|
||
whole-suite scheduling artifact of the same family as the `RealAppHost` STA landmine below —
|
||
**if it ever goes red, re-run before believing it.**
|
||
|
||
## Uncommitted / untracked
|
||
|
||
- `ffmpeg/` — the 9 bundled binaries (~135 MB), **gitignored by design**. Populated by
|
||
`scripts/fetch-ffmpeg.sh`. Do not commit it.
|
||
- `MARCOM.md`, `MONETIZATION.md` — both edited (privacy-copy guard; the Polar-obsolete banner
|
||
and the re-opened-price note) and both **gitignored by design** (confidential business
|
||
files, `.gitignore:10-11`). They stay local, as intended. Same for `CREDENTIALS.md` —
|
||
**never commit those.**
|
||
|
||
## Next
|
||
|
||
1. **Log the broadcast status during a stream, then one 60-second test** — the RTMP
|
||
diagnostic came back "ffmpeg fine, broadcast never goes live", which is a *narrower*
|
||
question than the one item 1 used to ask. One code change (reuse the health poll timer)
|
||
plus one long test closes it. It is the live bug, so it still sorts first; nothing below
|
||
reorders. Packaging is NOT next — the creator's ruling 2026-10-04: we are still in active
|
||
development, so ship-time work waits and queued features come first.
|
||
2. **The packaging project + `Package.appxmanifest`** (TASK 48 items 2–3) — the next code unit.
|
||
Full trust, `webcam`/`microphone`, English only; Velopack deleted. Add the
|
||
`DefaultRecordFolder()` comment in the same unit, and the WebView2 framework dependency +
|
||
graceful-failure path.
|
||
3. **TASK 50 — one-button "Publish this video"** (`TASKS/task-50-publish-recording.md`). Queued
|
||
behind the Store submission by its own precedence note ("MSIX first, publish second — unless
|
||
the creator reverses that"). Scoping is done; the build is not. ⛔ Do **not** flip
|
||
`enableAutomaticArchiving` until it ships. It also carries an **open 11.12 re-check**: a
|
||
publish feature makes the app *push* creator content to a third party, so the IARC answers
|
||
must not go to certification describing an app that cannot publish.
|
||
4. **Declare the IAP products in Partner Center** — the price is **settled** ($10/mo, $400
|
||
lifetime, no annual, 15% verified), so this is unblocked. **Blocks:** the Store listing and the
|
||
`OverlayHost.xaml` copy. ⛔ The Individual-vs-Company account question is still unanswered —
|
||
get it right before enrolling.
|
||
5. **Polar teardown** (TASK 48 item 0) — `PolarLicenseService`, `PolarLicense`,
|
||
`MainViewModel.License.cs`, the `OverlayHost.xaml` string, the `csproj`/`App.xaml.cs`
|
||
Velopack sites. `IsPremium` ← Store entitlement. **Do this as one unit** — a half-torn-down
|
||
licensing path is worse than either end state.
|
||
6. **Vertical recording verification** — the compositor tier is proven by
|
||
`Render_VerticalTier_Outputs_1080x1920_From_The_Center_Crop`; the *record* path has never
|
||
been run. `FramePump.cs:645` flags off-size tiers as a known follow-up.
|
||
7. **WACK + certification notes + the privacy policy** (TASK 48 items 4–5) — the policy must
|
||
state camera/mic is OS-gated, nothing is retained, and **nothing is fetched at runtime**
|
||
(now literally true — the ffmpeg bundle is what makes that sentence defensible).
|
||
8. **Post-session efficacy report** — roll up `CurrentHealth` (dropped frames, duration, health
|
||
message) when a stream or recording ends. `SessionTeardownTests` is the natural home.
|
||
9. **Measure whether the Windows camera toggle gates DShow** — gates all privacy-forward copy
|
||
(`MARCOM.md` guard). Not a certification risk; a trust risk.
|
||
10. **TASK 36:212 stale "shipped" line** — one-line fix, needs a hand.
|
||
|
||
**Dropped from the list** because the ruling made them moot: multi-instance's ffmpeg tools-dir
|
||
race (item 1 deleted the tools dir entirely), `scripts/publish.sh` + the `LlamaCasty.exe` rename
|
||
(the Store packages and delivers — *revisit only if we ever ship outside the Store*), and the
|
||
alert-gating copy (the wrong string dies with the Polar teardown in step 3).
|
||
|
||
**Everything else in the v1 queue:** stream resilience (32), bandwidth step-down (33), scheduled
|
||
streams (34), scene-linked audio (35), the master limiter (12), text source (3.16), reward
|
||
events (3.20), the media picker (21), webcam identity (9.5), settings units A/C/D (40), and the
|
||
defaults split (37). Ship-checklist items live in `TASKS.md` -> "1.0 gates".
|