diff --git a/App.xaml.cs b/App.xaml.cs index ab1345a..95aa7bd 100644 --- a/App.xaml.cs +++ b/App.xaml.cs @@ -1,6 +1,5 @@ using System.Windows; using System.Windows.Threading; -using Velopack; using ytLive.Helpers; namespace ytLive; @@ -31,19 +30,8 @@ public partial class App : Application // the per-build GUID is forensic only and lands here. AppLog.Write($"Build {ytLive.Helpers.BuildStamp.Id} (compiled {ytLive.Helpers.BuildStamp.BuiltLocal})"); - // Velopack auto-update bootstrap — checks for updates on startup and - // applies them (the app restarts automatically if an update is found). - // Update URL is configured per-deployment (self-hosted on DO droplet); - // when no URL is set, the check is a no-op. - try - { - VelopackApp.Build() - .Run(); - } - catch (Exception ex) - { - AppLog.Write($"Velopack bootstrap failed (non-fatal): {ex.Message}"); - } + // There is deliberately NO self-update bootstrap here: the Microsoft Store updates the + // app (TASK 48 — Store MSIX route), and a second updater would fight it. base.OnStartup(e); AppLog.Write("App OnStartup end"); diff --git a/Distribution.md b/Distribution.md index 62d0738..c5cf691 100644 --- a/Distribution.md +++ b/Distribution.md @@ -409,10 +409,12 @@ Set-AuthenticodeSignature -FilePath "LlamaCasty.exe" ` ### 4.4 Update Mechanism — ⛔ OBSOLETE (2026-09-27) > **Superseded by Store updates.** Microsoft delivers and updates the app from the listing, so -> there is nothing to check and no manifest to host. The Velopack updater currently in -> `App.xaml.cs` **downloads *and executes***, which Store policy **10.2.2** does not permit — its -> removal is a **compliance action**, not optional cleanup (TASK 48 item 3). The Polar-native design -> below is retained as a record of the approach that was rejected; it is not an option. +> there is nothing to check and no manifest to host. The Velopack updater that used to sit in +> `App.xaml.cs` **downloaded *and executed***, which Store policy **10.2.2** does not permit — its +> removal was a **compliance action**, not optional cleanup, and it is **done** (TASK 48 slice 3, +> 2026-10-22: the package reference and the bootstrap are gone; `ytLive.Tests/StorePackagingTests.cs` +> guards it). The Polar-native design below is retained as a record of the approach that was +> rejected; it is not an option. **Polar-native approach (no custom infrastructure):** diff --git a/HANDOFF.md b/HANDOFF.md index b99f33a..900dafa 100644 --- a/HANDOFF.md +++ b/HANDOFF.md @@ -77,6 +77,16 @@ unchanged; the seams are two `internal` properties). Guard: `ytLive.Tests/AppLogRotationTests.cs`. Slices 2 (temp alert-clip cleanup) and 3 (low-disk guard before recording) are still open. +- **TASK 48 slice 3 — the self-updater is gone (shipped 2026-10-22).** The Store route (creator + ruling 2026-09-27) means the Store updates the app, so the Velopack package + bootstrap were + **deleted** (`ytLive.csproj`, `App.xaml.cs`) — a compliance action, not cleanup: the updater + downloaded *and executed*, which Store policy 10.2.2 does not permit. The DO update-URL droplet + dies with it. Guard: `ytLive.Tests/StorePackagingTests.cs` (no updater package, no updater API + in any app source file). The app was smoke-tested after the removal: `MainWindow loaded`, no + exceptions. **Still open on TASK 48:** slice 2 (the `Package.appxmanifest` + packaging project), + slice 6 (the full-trust recording invariant comment), slice 7 (disqualifier re-check), slice 8 + (the pre-submission gates) — and slices 4/5 are the creator's (Certification Kit, Partner + Center listing, the demo account). - **The previous session (HA lights) produced NO ytLive code** — it was spent on the creator's Home Assistant light alert system (off-repo, `192.168.50.145`). See `MyMistakes.md` → 2026-10-21: the plan was one tweak to a verified-working script; the script was rewritten twice, the alert path broke, and @@ -270,10 +280,10 @@ Zombie entities deleted from the registry: `script.leak_strobe`, `script.doorbel Galaxy `cef.log` at 2.7 GB is NOT ours.) ## Tests -- Last full run: **436/436 passed, 0 warnings** (2026-10-22 — after TASK 55 slice 1's - `AppLogRotationTests`; the run before was 435/435 after TASK 63, and the runs before that were - 435/435 after TASK 61+62, 433/433 after TASK 60, 432/432 after TASK 59 and 431/431 after - TASK 58). The one failure seen earlier in the +- Last full run: **437/437 passed, 0 warnings** (2026-10-22 — after TASK 48 slice 3's + `StorePackagingTests`; the run before was 436/436 after TASK 55 slice 1's + `AppLogRotationTests`, and the one before that 435/435 after TASK 63). The one failure seen + earlier in the session was `LayerReorderPersistenceTests.RealMouseDrag_OnTheLayerList_PersistsTheReorder`, which **passes in isolation** (re-run: 3/3) — the known real-mouse flake, not a regression. - Known flaky-by-environment (all pass in isolation): `AudioPipelineTests.Mix_HonorsProviderGains…`, diff --git a/TASKS.md b/TASKS.md index 8b08aec..d234957 100644 --- a/TASKS.md +++ b/TASKS.md @@ -63,7 +63,7 @@ | 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: **MSIX + Store IAP** | 🔶 In progress — **✅ route decided 2026-09-27 (Store MSIX + Store IAP)**; **item 1 (bundle ffmpeg) SHIPPED 2026-09-28** | [`TASKS/task-48-distribution-msix.md`](TASKS/task-48-distribution-msix.md) | +| 48 | Distribution & packaging: **MSIX + Store IAP** | 🔶 In progress — **✅ route decided 2026-09-27 (Store MSIX + Store IAP)**; **item 1 (bundle ffmpeg) SHIPPED 2026-09-28**; **item 3 (remove the Velopack updater) SHIPPED 2026-10-22** — the Store updates the app | [`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) | | 50 | One-button "Publish this video" (resumable upload of the local recording) | ☐ Queued (2026-09-28) | [`TASKS/task-50-publish-recording.md`](TASKS/task-50-publish-recording.md) | | 51 | **Webcam background effects: Remove + Blur** | ✅ **Built 2026-10-09** (v1 requirement) — bundled MediaPipe Selfie Segmenter → ONNX (verified vs Google's runtime), Remove + Blur with strength, per-scene config persisted, one processing point in CameraManager, radio-pill UI in the webcam's Layers properties. Creator check 2026-10-09: **Remove jitter FIXED** (temporal mask smoothing 0.75/0.25, OBS/Jitsi-derived) and **Blur "heavy" FIXED** (quadratic radius 1→6 small px, default 0.5 = 2, StreamFX-consensus); recheck on the real camera pending | [`TASKS/task-51-webcam-bg-effects.md`](TASKS/task-51-webcam-bg-effects.md) | @@ -129,7 +129,7 @@ second encoder path needing a "redirect". Record+simulcast is one ffmpeg with tw `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) +- **TASK 10** — the Velopack update URL is **RETIRED (2026-10-22)**: the updater was deleted (TASK 48 slice 3) and the Store updates the app - **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). diff --git a/TASKS/task-10-monetization.md b/TASKS/task-10-monetization.md index 04700a5..c908e12 100644 --- a/TASKS/task-10-monetization.md +++ b/TASKS/task-10-monetization.md @@ -68,7 +68,7 @@ licensing path is being deleted because Store IAP supplies the same bit from the handles purchase. Paid users see zero license UI. 7. ⛔ **Licensing model / renewal-lapse path** — already dead before the Store ruling (a perpetual key never expires). Now superseded again: **Store IAP has no keys at all.** -8. 🔶 **Velopack auto-updates** — NuGet packages + bootstrap in `App.xaml.cs`. **TEARDOWN is a - compliance action, not cleanup:** the updater downloads *and executes*, and Store policy 10.2.2 - does not permit it. Tracked as [`TASKS/task-48-distribution-msix.md`](task-48-distribution-msix.md) - item 3; the Store updates the app instead. \ No newline at end of file +8. ✅ **Velopack auto-updates** — NuGet package + bootstrap in `App.xaml.cs`. **DELETED + 2026-10-22 (TASK 48 slice 3)** — a compliance action, not cleanup: the updater downloaded + *and executed*, and Store policy 10.2.2 does not permit it. The Store updates the app + instead. Guard: `ytLive.Tests/StorePackagingTests.cs`. \ No newline at end of file diff --git a/TASKS/task-48-distribution-msix.md b/TASKS/task-48-distribution-msix.md index c2d7977..ec8f349 100644 --- a/TASKS/task-48-distribution-msix.md +++ b/TASKS/task-48-distribution-msix.md @@ -158,15 +158,21 @@ There is currently **no `.manifest` file in the repo at all**, so this is purely **Do NOT use `appContainer`** — see the invariant in item 6. -## 3. ☐ Remove Velopack — 3 edit sites +## 3. ✅ Remove Velopack — 3 edit sites (DONE 2026-10-22) The Store handles updates, so this is simply **deleted**. Trivially removable; the code was written defensively for exactly this. -- `ytLive.csproj:92` — `` -- `App.xaml.cs:3` — `using Velopack;` -- `App.xaml.cs:38-46` — the sole call site, already try/caught and commented - "(non-fatal) … when no URL is set, the check is a no-op" +- `ytLive.csproj:92` — `` — **REMOVED** +- `App.xaml.cs:3` — `using Velopack;` — **REMOVED** +- `App.xaml.cs:38-46` — the sole call site (already try/caught and commented + "(non-fatal) … when no URL is set, the check is a no-op") — **REMOVED** + +**Guard:** `ytLive.Tests/StorePackagingTests.cs` — the ONE test asserts the app project +references no updater package (Velopack/Squirrel/NetSparkle/AutoUpdater/WinSparkle) and that no +app source file uses the updater API (matched as CODE — a `using` directive, a type usage — not +as a bare word, so a file's prose may still name what it deliberately does not do). The app was +smoke-tested after the removal: `MainWindow loaded`, no exceptions. **Retires:** the pending "Velopack update URL" item (`TASKS.md` open items, `task-10-monetization.md:43`) and the self-hosted DigitalOcean droplet. diff --git a/ai.md b/ai.md index e7e50e9..acb61e0 100644 --- a/ai.md +++ b/ai.md @@ -2583,8 +2583,10 @@ before re-deriving it. Executable checklist: `TASKS/task-48-distribution-msix.md **Consequences that are architecture, not packaging detail:** -- **⛔ Velopack and the update URL are deleted** — the Store auto-updates. 3 edit sites, - `ytLive.csproj:92` + `App.xaml.cs:3,38-46`. The self-hosted DO droplet dies with it. +- **⛔ Velopack and the update URL are deleted** — the Store auto-updates. **DONE 2026-10-22** + (TASK 48 slice 3): the `PackageReference` in `ytLive.csproj` and the bootstrap in + `App.xaml.cs` are gone, and `ytLive.Tests/StorePackagingTests.cs` guards that no updater + package or updater API comes back. The self-hosted DO droplet dies with it. - **⛔ The Polar licensing subsystem is deleted** — `PolarLicenseService`, `PolarLicense`, `MainViewModel.License.cs` (`PremiumUrl`, customer portal, the `OfflineGracePeriod = 14 days` subscription-era artifact, renewal/lapse copy), and the diff --git a/ytLive.Tests/StorePackagingTests.cs b/ytLive.Tests/StorePackagingTests.cs new file mode 100644 index 0000000..6d1a4b9 --- /dev/null +++ b/ytLive.Tests/StorePackagingTests.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; +using System.Xml.Linq; +using Xunit; + +namespace ytLive.Tests; + +/// TASK 48, slice 3 — the creator's ruling (2026-09-27): the app ships as a +/// **Microsoft Store MSIX** and the **Store updates it**. The self-update subsystem that +/// existed for the abandoned self-hosted route (a Velopack bootstrap plus the DigitalOcean +/// update-URL droplet) is therefore deleted, not parked: a second updater inside a +/// Store-installed package fights the Store's own updater. +/// +/// This test is the guard: the app project references no updater package and no app +/// source file uses the updater API — so the deleted subsystem cannot creep back in, and +/// the "the Store updates it" decision stays the only update path. +public class StorePackagingTests +{ + /// Known self-update packages. If one of these comes back, the Store route has + /// been quietly reversed. + private static readonly string[] UpdaterPackages = + { + "Velopack", "Squirrel", "NetSparkle", "AutoUpdater", "WinSparkle" + }; + + [Fact] + public void The_app_ships_no_self_update_subsystem() + { + // 1. The app project references no updater package. + var project = XDocument.Load(SourceAssert.RepoPath("ytLive.csproj")); + var packages = project.Descendants() + .Where(e => e.Name.LocalName == "PackageReference") + .Select(e => (string?)e.Attribute("Include")) + .Where(name => name != null) + .ToHashSet(StringComparer.OrdinalIgnoreCase); + foreach (var updater in UpdaterPackages) + Assert.DoesNotContain(updater, packages); + + // 2. No app source file uses the updater API. Matched as CODE (a using directive, + // a type usage), not as a bare word — a file's prose is allowed to name what it + // deliberately does not do. + var root = Path.GetDirectoryName(SourceAssert.RepoPath("ytLive.csproj"))!; + foreach (var file in AppSourceFiles(root)) + { + var text = File.ReadAllText(file); + Assert.False( + Regex.IsMatch(text, @"^\s*using\s+Velopack\s*;", RegexOptions.Multiline), + $"{Path.GetFileName(file)} still imports the Velopack updater — the Store updates this app."); + Assert.False( + Regex.IsMatch(text, @"\bVelopackApp\b"), + $"{Path.GetFileName(file)} still calls the Velopack updater — the Store updates this app."); + } + } + + /// Every C# file of the app project (not the test project, not build output). + private static IEnumerable AppSourceFiles(string root) + { + return Directory.EnumerateFiles(root, "*.cs", SearchOption.AllDirectories) + .Where(path => !path.Contains($"{Path.DirectorySeparatorChar}ytLive.Tests{Path.DirectorySeparatorChar}")) + .Where(path => !path.Contains($"{Path.DirectorySeparatorChar}obj{Path.DirectorySeparatorChar}")) + .Where(path => !path.Contains($"{Path.DirectorySeparatorChar}bin{Path.DirectorySeparatorChar}")); + } +} diff --git a/ytLive.csproj b/ytLive.csproj index 585eb94..015f5e3 100644 --- a/ytLive.csproj +++ b/ytLive.csproj @@ -125,7 +125,6 @@ -