fix(border-color): keep ComboBox popup clicks inside LeftPanel (TASK 63)
This commit is contained in:
@@ -311,7 +311,13 @@ C# / WPF (.NET 8) following MVVM:
|
||||
the `YtComboBox` popup carries no width binding, so the pop-up is as wide as its widest row
|
||||
(measured 142.7px for the border-colour rows against a 100px ComboBox) — a row is never clipped,
|
||||
and the popup may be wider than the closed box. Measure (`RenderTargetBitmap`/`ActualWidth` in a
|
||||
throwaway test), don't guess.
|
||||
throwaway test), don't guess. **And a `Popup`'s visual root (`PopupRoot`) has a `null` visual
|
||||
parent (`VisualTreeHelper.GetParent(popupRoot) == null`)** even though `PopupRoot` IS a `Visual`:
|
||||
its only link back to the `Popup` and the owning `ComboBox` is `LogicalTreeHelper.GetParent`. Any
|
||||
ancestor walk used by a window-level click-outside guard (`LeftPanel.IsDescendantOf`) MUST fall
|
||||
back to `LogicalTreeHelper.GetParent(child)` when `VisualTreeHelper.GetParent(child)` returns
|
||||
`null`, or clicking a `ComboBoxItem` inside `PART_Popup` looks like an outside click and
|
||||
deselects `SelectedElement` before the item click registers (fixed 2026-10-22).
|
||||
- **A field in a dense panel carries a FIXED width (TASK 61):** a WPF element sizes itself to its
|
||||
content, and in a `DockPanel` the **last child fills the row** — so a row of same-kind fields looks
|
||||
broken unless every field has an explicit `Width` AND the row sets `LastChildFill="False"`. The
|
||||
@@ -2068,13 +2074,13 @@ Apply this to every UI decision:
|
||||
> Everything below is the historical paid-tier design.
|
||||
|
||||
Full pricing and the Year 1 revenue forecast in `MONETIZATION.md` (gitignored). ⚠️ **No Polar
|
||||
product, discount code, or license key exists any more** — billing is Microsoft Store IAP.
|
||||
product, discount code, or license key exists any more** — nothing bills today; Polar is dormant.
|
||||
Brand palette and assets in `MARCOM.md` (gitignored). This section covers the in-app model.
|
||||
|
||||
Free forever: **all features unlocked for everyone** — no feature lock between free and paid.
|
||||
The **only difference is watermarking**. This is deliberate: no creator will tolerate a watermark,
|
||||
and as a good-guy developer, we give them complete access to every feature so no one can call us
|
||||
crooked.
|
||||
Free forever: **all features unlocked for everyone** — no feature lock, no paid tier, and since
|
||||
2026-10-08 **no watermark either** (the credit is OFF by default; see the banner above). No
|
||||
creator will tolerate a watermark, and as a good-guy developer we give everyone complete access
|
||||
so no one can call us crooked. Money comes from gratuity/merch, not gating (TASK 54).
|
||||
|
||||
- **Free:** a periodic branding credit — "made with LlamaCasty!" in **full-frame neon** (white core,
|
||||
feathered red/blue halo with the channels split in opposite directions), at a **random position
|
||||
@@ -2122,34 +2128,24 @@ crooked.
|
||||
finish. That's it. No feature gating. Alerts, social bar slots, voice filters, TRAX, recording —
|
||||
everything is free. **How `IsPremium` is obtained is a billing concern and has changed twice;
|
||||
what it gates has not.**
|
||||
- **Pricing (✅ DECIDED 2026-09-27 — supersedes the 2026-09-21 one-time "own it" model):**
|
||||
**`$10/mo` subscription, or `$400` lifetime. No annual. No `.99` prices.** ⛔ The old `$29`
|
||||
founder → `$49` list lifetime ruling is **dead — do not cite it.** Two tiers, one delta: they
|
||||
break even at **month 40**, so they barely compete, which is why lifetime is safe to offer at
|
||||
launch instead of saving it for later. The free tier is the *full* app (only the watermark
|
||||
differs), so accessibility is preserved without pricing down. ⛔ No annual tier: Microsoft does
|
||||
not pro-rate and **the developer cannot refund** the forfeited remainder — verbatim the
|
||||
grievance that drove the ruling. **ADA §6(h): raising the price disables auto-renew, so `$10` is
|
||||
effectively locked for the product's life.** Full reasoning in `MONETIZATION.md` → Pricing.
|
||||
- **Billing: ⛔ Polar is DELETED — Microsoft Store IAP, Microsoft as merchant of record** (ruling
|
||||
2026-09-27; teardown in `TASKS/task-48-distribution-msix.md` item 0). **Revenue share 15%,
|
||||
subscriptions included** — verified from the ADA v8.10 PDF, §6(b)(i); the agreement's own v8.0
|
||||
changelog says *"85/15 revenue share for non-Game subscriptions"*. ⇒ net **~$8.50/mo**,
|
||||
**~$340** lifetime. Microsoft handles VAT/GST/sales tax, refunds, chargebacks, the customer
|
||||
portal, and device deactivation — the strongest argument for the switch. Net receipts are after
|
||||
tax, so commission is not paid on pass-through tax. Payouts monthly, $50 threshold.
|
||||
⚠️ **No Polar product, org ID, key prefix, or discount code exists any more** — `d105dfa1-…`,
|
||||
`c05fb364-…`, `LCYT-`, `LLAMAFOUNDER`/`LLAMA50`, the customer-portal validate endpoint, and the
|
||||
14-day offline-grace machine are all being deleted. Products are declared in **Partner Center**
|
||||
instead: one auto-renewing monthly subscription + one non-expiring lifetime IAP.
|
||||
⛔ A discounted subscriber→lifetime upgrade SKU is **not** approved.
|
||||
- **Pricing: ⛔ SUPERSEDED 2026-10-08 by the free-forever ruling (TASK 54).** Retained as
|
||||
reasoning only: `$10/mo` subscription or `$400` lifetime, no annual, ruled 2026-09-27 and
|
||||
overtaken ten days later. If a paid tier ever returns, these are the numbers to argue from.
|
||||
**How `IsPremium` would be obtained is a billing concern; what it gates is not.**
|
||||
- **Billing: ⏸ dormant — no paid tier exists** (free-forever ruling 2026-10-08, TASK 54). The
|
||||
Store IAP plan of 2026-09-27 was never implemented (`Windows.Services.Store` appears nowhere
|
||||
in the repo) and was withdrawn with the Store route on 2026-10-22. If a paid tier ever
|
||||
returns, the provider is **Polar** — merchant of record for VAT/sales tax, 5% + $0.50 Starter,
|
||||
**$50 already paid**; `Distribution.md` §4.2/§4.5 holds the design. ⚠️ No Polar product, org
|
||||
ID, key prefix, or discount code exists (`d105dfa1-…`, `c05fb364-…`, `LCYT-`, `LLAMAFOUNDER`/
|
||||
`LLAMA50`, the customer-portal validate endpoint, and the 14-day offline-grace machine are all
|
||||
gone).
|
||||
- **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. The only license chatter is the paid-user **expiry
|
||||
reminder — now moot** (superseded twice: keys are moot because the lifetime entitlement never
|
||||
expires, and the entire license-key system is moot because Store IAP has no keys); paid users see
|
||||
zero license UI. TASK 36 item 5 (expiry reminder) is struck.
|
||||
emergency patches; not a 24/7 service promise. There is **no licence chatter at all** — the app
|
||||
is free, there are no keys, and no user sees licence UI. TASK 36 item 5 (expiry reminder) is
|
||||
struck.
|
||||
|
||||
**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
|
||||
@@ -2569,52 +2565,57 @@ published decisions (recorded in `TASKS/task-43-native-alerts.md`):
|
||||
|
||||
---
|
||||
|
||||
## Windows packaging & distribution (✅ DECIDED 2026-09-27 — Store MSIX + Store IAP)
|
||||
## Windows packaging & distribution (⛔ REVERSED 2026-10-22 — direct + Steam; Store dropped)
|
||||
|
||||
**The distribution route is settled: Microsoft Store, MSIX package, Store IAP.** Creator's
|
||||
criteria, verbatim: *"zero headaches, minimal maintenance (for me) while still providing
|
||||
accountability and a reasonably easy upgrade flow."* Route A was chosen because it 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 live in
|
||||
`TASKS/research-store-certification.md` §3 — **read that before reopening the question**, not
|
||||
before re-deriving it. Executable checklist: `TASKS/task-48-distribution-msix.md`.
|
||||
**The Microsoft Store route is dead.** A brand name on the Store's *publisher* line requires a
|
||||
**Company** account, which requires a registered legal entity (an LLC — ~$85 New Mexico with a
|
||||
~2-week SOS backlog, ~$143 Wyoming). Partner Center does **not** convert an Individual account to
|
||||
a Company account later, and an Individual account publishes under the verified legal name. The
|
||||
creator will not fund an entity for a nameplate, so the 2026-09-27 Store ruling was withdrawn on
|
||||
2026-10-22. Store IAP was never implemented either — `Windows.Services.Store` appears nowhere in
|
||||
the repo.
|
||||
|
||||
**The route now:**
|
||||
|
||||
1. **Direct download from `llamacasty.com`** — the app is **free forever** (TASK 54), so there is
|
||||
nothing to bill in v1.
|
||||
2. **Steam when $100 exists** — Steam Direct is $100 per product (recoupable after $1,000 AGR),
|
||||
accepted categories include *Audio/Video Production* / *Player Tools* (OBS Studio is the
|
||||
precedent), the **developer/publisher name is self-chosen** so the brand needs no entity,
|
||||
5+ screenshots at 1920×1080 + capsule art, a build Valve runs, and a **2-week live page**
|
||||
before release. Valve takes 30%.
|
||||
3. **Polar is dormant, not deleted-in-principle** — chosen checkout/licence provider if a paid
|
||||
tier ever returns; $50 already paid. `Distribution.md` §4.2/§4.5 holds the design.
|
||||
4. **The Microsoft Store: not pursued.** The MSIX package stays as a working artifact and is
|
||||
submitted nowhere. History: `TASKS/research-store-certification.md`,
|
||||
`TASKS/task-48-distribution-msix.md`.
|
||||
|
||||
**Consequences that are architecture, not packaging detail:**
|
||||
|
||||
- **⛔ 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 package is built by `scripts/package-msix.sh` (2026-10-22, TASK 48 slice 2)** — publish
|
||||
- **⛔ The updater is now MISSING, not deleted-by-design.** Velopack and its bootstrap were
|
||||
removed on 2026-10-22 (TASK 48 slice 3) because the Store was going to deliver updates — that
|
||||
premise is gone. An update path must come back, and
|
||||
`ytLive.Tests/StorePackagingTests.cs` (which asserts no updater returns) must be rewritten
|
||||
with the chosen updater rather than left to veto one.
|
||||
- **The MSIX package still builds** via `scripts/package-msix.sh` (TASK 48 slice 2) — publish
|
||||
`Release -r win-x64 --self-contained` into `artifacts/msix-stage`, copy
|
||||
`Packaging/Package.appxmanifest` in as `AppxManifest.xml` + `Packaging/Assets/`, then
|
||||
`makeappx pack` → `artifacts/LlamaCasty.msix` (159 MB, gitignored; `MSIX_PFX` signs it for a
|
||||
local install). The manifest is a **full-trust `packagedClassicApp` at `mediumIL`** with
|
||||
`runFullTrust` + webcam + microphone — that is what lets the app launch the bundled ffmpeg as
|
||||
a child process; an `appContainer` package would virtualise the recordings folder instead.
|
||||
`Square310x310Logo`/`Wide310x150Logo` live on `uap:DefaultTile`, **not** on `VisualElements`.
|
||||
Logos come from the app's own artwork via `scripts/make-store-logos.py`.
|
||||
- **⛔ 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
|
||||
wrong "Polar unlocks alerts" string in `OverlayHost.xaml`. **`IsPremium` is derived from the
|
||||
Store entitlement**, not a remote HTTP call. That deletes the entire "network flaky → app
|
||||
thinks I'm expired" bug class along with the offline-grace machine.
|
||||
- **⛔ Bundle ffmpeg — no runtime downloads** (Store policy 10.2.2; also the 2026-09-01 404).
|
||||
See the FFmpeg locator section above. **This is the first code unit.**
|
||||
`runFullTrust` + webcam + microphone. `Square310x310Logo`/`Wide150x150Logo` traps are in
|
||||
`MyMistakes.md`. Logos come from the app's own artwork via `scripts/make-store-logos.py`.
|
||||
- **⛔ Bundle ffmpeg — no runtime downloads** (was Store policy 10.2.2; still right for a direct
|
||||
download, and it fixes the 2026-09-01 404). See the FFmpeg locator section above.
|
||||
- **⛔ 64-bit Windows only (creator ruling 2026-10-09).** Both csproj files set
|
||||
`<PlatformTarget>x64</PlatformTarget>` (the test project too — an AnyCPU test project
|
||||
referencing an x64 app raises MSB3270 and breaks the 0-warning gate). Why: the bundled ffmpeg
|
||||
is the win64 build, ONNX Runtime 1.31 ships no win-x86 native (only win-x64/win-arm64), and a
|
||||
32-bit address space can't hold the 1080p frame buffers. Windows 11 is 64-bit-only anyway. The
|
||||
MSIX publish targets x64 only (ARM64 is a possible later add: both ffmpeg and ONNX Runtime have
|
||||
arm64 builds). Public copy says "64-bit Windows 10 or 11".
|
||||
32-bit address space can't hold the 1080p frame buffers. Windows 11 is 64-bit-only anyway.
|
||||
Public copy says "64-bit Windows 10 or 11".
|
||||
|
||||
**What does NOT change: the monetization posture.** Free gets everything; the branding flash
|
||||
stays the **only** paid delta (`TASK 36` item 2). Store IAP changes how `IsPremium` is
|
||||
*obtained*, never what it *gates*. `Monetization` above still governs.
|
||||
**What does NOT change: the monetization posture.** The app is **free forever with no watermark**
|
||||
(TASK 54, 2026-10-08), so `IsPremium` gates nothing in v1 and the branding flash is OFF by
|
||||
default. `Monetization` above governs.
|
||||
|
||||
### ⛔ Durable invariant: full trust, or recording silently breaks
|
||||
|
||||
@@ -2642,19 +2643,21 @@ EV-signed file now accrues reputation exactly like an OV-signed one, so EV ($400
|
||||
what OV (~$150–300/yr) buys. Signing is **not** instant regardless — a valid cert stops the
|
||||
*malware* warning immediately, but "unrecognized publisher" clears only as SmartScreen accrues
|
||||
reputation from real download volume. Cert validity is capped at **460 days** (recurring
|
||||
line item) and private keys must live on an **HSM or hardware token**. The **Store MSIX route
|
||||
needs no certificate at all** — Microsoft re-signs. `Distribution.md` §4.3 was corrected
|
||||
2026-09-27; it previously recommended EV.
|
||||
line item) and private keys must live on an **HSM or hardware token**. **The 2026-10-22 ruling
|
||||
is to ship unsigned and accept the "unrecognized publisher" warning** until download reputation
|
||||
accrues — buying a certificate is deferred, not rejected. `Distribution.md` §4.3 was corrected
|
||||
2026-09-27 (it previously recommended EV).
|
||||
|
||||
### Distribution and licensing are independent — but the ruling collapsed them
|
||||
### Distribution and licensing are independent — and the channel decision does not decide licensing
|
||||
|
||||
A **Store-distributed** app *may* still verify licenses with **Polar** (policy 10.8.1
|
||||
explicitly permits a secure third-party purchase API for non-game PC products), so "should we
|
||||
use the Store?" did not logically imply "drop Polar?" — route B was a real option. **The
|
||||
creator chose route A instead**, so Polar is gone. Recording the distinction because it was
|
||||
the reason the decision was thought-through rather than assumed: the licensing choice rode on
|
||||
a distribution decision, and the *only* route that removed the licensing backend was the one
|
||||
that also removed the certificate. Both burdens had the same single solution.
|
||||
A **Store-distributed** app *may* still verify licences with **Polar** (Store policy 10.8.1
|
||||
explicitly permits a secure third-party purchase API for non-game PC products), so "which
|
||||
channel?" never implied "which billing?" — route B was a real option. The 2026-10-22 reversal
|
||||
proves the point in the other direction: dropping the Store did not by itself restart Polar,
|
||||
because the **2026-10-08 free-forever ruling** (TASK 54) had already removed the paid tier.
|
||||
Polar is therefore *dormant*, ready if a paid tier ever returns, and nothing in v1 depends on it.
|
||||
The lesson worth keeping: the licensing choice rides on a **monetization** decision, not on a
|
||||
distribution decision.
|
||||
|
||||
### ⛔ "Only when the user says so" is enforced by Windows, not by us
|
||||
|
||||
|
||||
Reference in New Issue
Block a user