Commit Graph
21 Commits
Author SHA1 Message Date
gramps 6d1c982e26 feat(entitlement): IEntitlement seam — Steam DLC answers, everything fails closed
The first half of surge day 1. One question, "may this session emit Pro output?", with
one method on the interface so nothing downstream knows which rail proved it.

SteamEntitlement reads the lifetime SKU the way Valve intends: SteamAPI.Init() then
SteamApps.BIsDlcInstalled(dlcAppId). Four different failure paths all resolve to the
free tier rather than to Pro or to a crash — no DLC id configured, no Steam client,
steam_api64.dll missing (a throw on first touch), and simply not owned. A copied folder
run without Steam is the common case, not an exceptional one.

NotConfigured() is the shipping default until Steamworks creates the DLC app; it answers
false without ever touching the native library, so today's builds behave exactly as they
did before this commit.

HasProOutput is now IsPremium || DeveloperMode || rail, with an internal EntitlementOverride
test seam (internal, because a public setter here is a licence bypass). Four tests: no id
reads false, no client reads false without throwing, the fake reports what a test sets, and
the view model takes its answer from the rail.

Steamworks.NET 2024.8.0 added; the native DLL still comes from scripts/steam-sdk.sh, so the
csproj change is three lines and no formatting churn.
2026-11-01 08:13:25 -08:00
gramps 279eb08dea feat(preview): right-click is the menu, zoom is the single-handed gesture
Creator ruling 2026-11-01, and the reason is live-stream ergonomics: no two-handed
chords while a broadcast or recording is running.

- Edit mode: left selects / drags / resizes, right opens the context menu, Ctrl+right
  toggles the zoom box. Chords are acceptable here because editing is a stopped activity.
- Preview mode: left toggles the zoom box, right opens the context menu, and Ctrl+right
  is swallowed outright — handled rather than left to fall through, so Ctrl+right cannot
  open a menu by accident while one hand is on the mouse.

This also removes the old live behaviour where right-click activated the magnifier at the
cursor point (and dismissed it). Zoom in preview is now a plain toggle; if point-zoom is
wanted back it needs a one-button gesture, not a chord.

Build: Release 0 warnings. Debug is currently blocked by the running app holding the exe
(PID 47252) — close it and the Debug build copies normally.
2026-11-01 08:10:04 -08:00
gramps fdb27f0758 feat(ui): gear to the rail, EDIT on the panel edge, preview undo/redo
Three creator directives (2026-11-01), done ahead of the surge.

**Gear moved to the preview rail** — first tab, orange (Brush.Attention), gear icon
only. Left-click opens the same app menu the top bar gear owned (Settings / Bug /
Feature / About), right-click goes straight to App Settings; both behaviours moved
with it, so nothing lost an entry point. TopBar keeps the wordmark and EDIT.

**EDIT right edge aligned to the layers panel.** The work area is margin 8 + a 360px
column, the top bar is padding 16, so the shared edge is 352 inside the bar. TopBar
column 0 is now a fixed 352 grid with EDIT right-aligned in it — alignment by
arithmetic instead of by eye.

**Preview undo/redo: Ctrl+Z / Ctrl+Shift+Z.** Models/PreviewHistory snapshots element
REFERENCES plus geometry, visibility, order and membership — a deep clone would mirror
every property of a dozen element subclasses and quietly miss the next one added.
One snapshot per gesture (pushed at drag/resize start, never in the move loop), bounded
at 50, redo survives multiple undos, cleared on layout load. Gated on EditingActive:
undo while the preview shows program output would rewrite a scene nobody is looking at.

Honest scope, in the type's doc comment: layout edits only. Colour/opacity/border/font
edits are not captured yet, so Ctrl+Z after one of those restores the last layout change.

6 tests in PreviewHistoryTests; 19 pass across it and the neighbouring UI classes; Debug
and Release both build at 0 warnings.
2026-11-01 07:46:10 -08:00
gramps 27fc6fb507 feat(profile): the free budget is pixels and fps, enforced once — with a dev-only release hatch
The paid delta is output quality, so the rule needs to be measurable rather than a word:
921,600 pixels and 30 fps, fail-closed. Models/OutputProfile.cs is that rule as pure
policy — a permitted tier passes through untouched (Pro, or the free tier's own 720p30),
a disallowed one resolves to the best tier the budget allows and carries ITS bitrate, so
a capped user is never offered an 8 Mbps budget for a 720p frame.

Applied once, in BuildEncoderOptions. That is genuinely one place: the stream and the
local recording are two output blocks of ONE ffmpeg process fed by one EncoderOptions,
so there is no second encoder to forget. (commercial.md claimed "both encoders — never
two checks"; the code is stronger than the doc, and the doc is corrected.)

Developer override, as asked: a "Developer mode — all resolutions" checkbox in App
Settings, bound to MainViewModel.DeveloperMode, which feeds HasProOutput. It is not
hidden in a release build — Controls/DeveloperPanel's Page and Compile items are removed
when Configuration != Debug, and OverlayHost adds it at runtime inside #if DEBUG, so a
shipped exe has no BAML for it at all. The setter also refuses the value when
DevBuild.IsDevelopment is false, which is now the single source for that question (the
scoreboard partial had its own duplicate const).

Two traps worth their lines: a `const bool` dev flag makes `if (!Flag) return;` an
unreachable-code warning in one configuration or the other (CS0162), so DevBuild uses
static readonly; and a Condition on an ItemGroup is invisible to a test reading
item.Attribute("Condition") — read the parent.

Tests: 14 pass across OutputProfileTests, AssemblyIdentityTests and StorePackagingTests.
The invariant test is the one that matters: iterate every shipped tier under a free
entitlement and assert none emits more than the budget, so a future 4K tier fails here
instead of shipping to everyone. App builds Debug and Release at 0 warnings.
2026-11-01 07:11:49 -08:00
gramps abfab25685 build: the exe is LlamaCasty.exe, and the publish script proves the artifact
Renames AssemblyName to LlamaCasty before the first Steam depot — Steamworks is explicit
that renaming a live exe is painful, so this had to land first, not later.

Four assembly-qualified pack URIs carried the old name, not three: MainWindow (window
icon), OverlayHost (logo), RenameRecordingDialog and KeepBroadcastDialog. Pack URIs
resolve by assembly name at runtime, so a stale one compiles, passes every test and then
silently renders nothing the first time that view opens. AssemblyIdentityTests guards
both halves — the assembly name, and no source XAML still naming the legacy assembly.
Packaging/Package.appxmanifest and package-msix.sh carry the exe name too.

scripts/publish.sh replaces the by-hand publish: single-file win-x64, ReadyToRun,
ffmpeg beside the exe, and it refuses to finish if ffmpeg, LICENSE, PRIVACY.md or
THIRD-PARTY-NOTICES.txt are missing, or if a test assembly leaked in.

That last guard exists because the first publish was wrong in a way no test could see:
PublishSingleFile + IncludeNativeLibrariesForSelfExtract bundled the libav*.dll family
into the exe — a 9-file output with no ffmpeg folder and a 182 MB binary, so the locator
would have found nothing at runtime. ExcludeFromSingleFile="true" on the content keeps
the folder a folder. Diffing against a control publish without the single-file flags
found it; guessing at item globs wasted two attempts first (recorded in MyMistakes.md).

Measured: 127 MB exe + 129 MB ffmpeg = 257 MB depot. Tests: AssemblyIdentity,
StorePackaging, BuildStamp, BundledFonts all pass; app builds with 0 warnings.
2026-11-01 07:01:50 -08:00
gramps 8f79b20a18 docs(distribution): Steam is the only channel — the zip, winget and the Store are dead
The site said "Coming soon to Steam" while the app docs still said direct download plus
winget primary with Steam parked. One of them had to move, and the creator's ruling is
Steam exclusive: a free app on Steam with the paid edition as a Steam DLC, and the
$10/month plan sold off-Steam by Polar.

Records the constraint that shapes the code, not just the prose:
- Steamworks requires in-app purchases to use the Steam Wallet (Microtransactions doc,
  quoted in commercial.md), so the monthly is *stated*, never *sold*, inside a Steam
  build; the lifetime is plain DLC bought on the store page and read with BIsDlcInstalled.
- Entitlement is `steamDlcOwned || polarKeyValid`, both resolving to one OutputProfile.
  steam_api64.dll ships beside the exe and is a functional dependency, not the rejected
  "IP protection" native DLL — without it DLC ownership cannot be read. Launched without
  Steam, the app falls back to the free profile rather than refusing to start.
- Signing is moot: Steam installs never meet SmartScreen. Artifact Signing stays on the
  shelf for the day a non-Steam download exists. Year-one fixed cost is ~$115.
- The exe rename must land before the first depot upload — Steamworks warns about
  renaming a live exe.

Distribution.md is rewritten to SteamPipe mechanics (publish output, depot, steamcmd,
DLC wiring, clean-account purchase/refund test). product.md, decisions.md (a new
"Steam exclusive" entry with every dead vector), TASKS.md (Steamworks entitlement row,
Steam packaging row), ai.md, index.md and the TASK 48 banner follow. MyMistakes.md gains
the Steam rules that cost real time today: installs skip SmartScreen, updates are free,
the Wallet rule, and the recurring-billing caveat.
2026-10-31 11:05:53 -07:00
gramps 57ef9e7a5e docs(commercial): the claim page was never needed, signing waits for revenue, and support is a filter
Four things derived this session superseded what the plan said twenty-four hours earlier,
and the docs are corrected where each one died:

**Licensing needs no server.** Polar's customer-portal endpoints authenticate on the key
itself plus the public organization_id; the org access token is only needed to create
products. Channel binding rides in `conditions: {channel_id}`, re-checked on every
validate — so the "claim page" (a hosted OAuth flow that wrote the UC id onto the key)
and the key database it implied are deleted. Device management, key rotation and
subscription auto-revocation are Polar's; four states, one lease, no callback endpoint.

**Signing is deferred until two subscribers.** It never removes the day-one SmartScreen
warning (EV's instant-bypass died in 2024), so buying it before revenue buys only the
publisher line. Renamed to Azure Artifact Signing, $9.99/mo Basic, and it requires a
paid Azure subscription. Year-one fixed cost drops from ~$135 to ~$15.

**The Store account is free.** Microsoft waived the $19 individual fee and removed the
$99 company fee in May 2026, which makes the Store the only zero-cost route to a
Microsoft-signed install — the free answer to SmartScreen, noted beside the paid one.

**Support is a filter, not a promise.** Email, best effort, no SLA, refunds on request
without argument; no training service, no helpdesk. A repeated question becomes a
training video on @llamacasty, which is content and monetization instead of a ticket.
GitHub issues on the releases repo are the deliberate friction.

Also settled: binaries live in a second Gitea repo (`llamacasty-releases`) push-mirrored
to a public GitHub repo — issues and assets public, code private, one publish direction
(a git mirror carries refs, not GitHub Release assets); `@llamacasty` is
standard-verified with intermediate features, so live streaming, >15 min uploads and
custom thumbnails work today and no SIM or ID is needed; and OAuth comes out of Testing
mode by publishing the app (100-user lifetime cap, no 7-day token death).

TASKS.md now opens with the four-day surge slice — artifact shape, output gate,
entitlement seam, real-machine day — because those are what the first public release
waits on. HANDOFF.md rewritten. MyMistakes.md gains the Polar recipe and the
"verify means three different things" entry. MARCOM.md gains the support→content loop
and the surface roles.
2026-10-31 10:08:05 -07:00
gramps 7827d239e2 docs(commercial): one source of truth for the money — prices, entitlement, channels
The commercial plan was spread over five documents written in five different weeks,
and they contradicted each other: product.md sold the Store, decisions.md called the
Store dead then re-opened, Distribution.md (736 lines) still described Polar as a
download host, MARCOM.md quoted "no subscription" next to a paid tier, and README.md
promised "free forever". An outsider could not tell what ships.

docs/ai/commercial.md is now the only place money is decided: free = whole app at a
921,600 px / 30 fps output profile covering the recording as well as the stream; Pro =
$10/mo or $400 lifetime through Polar, key bound to the buyer's YouTube channel ID via
a claim page, 2 activations, 7–14 day lease with offline grace, fail closed, never
downgrade a running stream; one portable zip + winget primary, Steam and the Store
parked; Azure Trusted Signing (~$9.99/mo), no EV; Founding 200 promo; ~$135/yr costs;
anti-piracy posture and what is permanently rejected.

Everything else now points at it instead of restating it:
- product.md — money sections replaced with pointers; packaging rewritten to
  winget-first, Store parked; signing updated to Trusted Signing.
- decisions.md — graveyard with cause of death for free-forever, the Store ordering,
  the updater, and the 2026-10-30 reversal of the quality-ceiling rejection.
- Distribution.md — rewritten to release mechanics only (artifact, GitHub Releases,
  winget manifest, signing, checklist, takedown): 736 → 120 lines.
- TASKS.md — TASK 54 rewritten, TASK 48's route settled, TASK 10 superseded, five new
  rows (entitlement seam, output-profile gate, licence UI, system messages, packaging).
- README.md — "free forever" replaced with the real split.
- capture.md — obs-backgroundremoval is GPL-3.0 and so licence-INCOMPATIBLE since the
  proprietary EULA; the old "compatible" line was falsified by that change.
- TASKS/task-10, task-48, task-51, task-54 — superseded-banner at the top of each, so
  none of them reads as current policy.
- HANDOFF.md — rewritten: plan settled, next code unit is the entitlement seam.

MARCOM.md and MONETIZATION.md (gitignored) were corrected in place: policy pointer at
the top of each, the flash removed from every live claim, the Polar fee break-even
corrected to ~$900/mo, and the projections flagged as written against a dead model.
2026-10-31 08:32:25 -07:00
gramps c72b61b61e docs(decisions): the site publishes the model, not the terms
Records the split so neither side drifts: the app repo holds the real numbers (720p30 free, $10/mo
or $400 lifetime, Store individual account) and the site holds only the promise (free forever, every
feature unlocked, a paid edition at launch).
2026-10-30 16:16:31 -07:00
gramps 54de9c3dfb docs(decisions): Store route re-opened via an Individual developer account
Creator ruling 2026-10-30: the Microsoft Store is the distribution target again, through an
Individual (not Company) developer account — which is what made the 2026-10-22 withdrawal necessary
in the first place, and why no legal entity is now required. The publisher line shows the verified
individual name; distribution work stays parked until the account exists, and the site's 'coming
soon to the Microsoft Store' copy is accurate again.
2026-10-30 16:15:28 -07:00
gramps 0498f7e130 legal: proprietary EULA + privacy policy; GPL terms retired
Licensing posture changes to closed-source commercial software (creator ruling 2026-10-30): free
tier caps output at 720p30, a paid licence unlocks 1080p ($10/month or $400 lifetime).

- LICENSE is now an End User License Agreement: grant, what you may do with your own content,
  no redistribution/reverse engineering, third-party components prevail, updates at publisher
  discretion, warranty and liability limits, termination, governing law.
- ADDITIONAL-TERMS.md deleted — those were GPL §7 terms and have no meaning under the EULA.
- THIRD-PARTY-NOTICES.txt keeps every third-party obligation and drops the GPL header and §6
  source offer; it states plainly that LlamaCasty ships no source.
- PRIVACY.md is new: exactly what is written locally (layout DB, DPAPI token, capped log,
  recordings you choose), what leaves the machine (Google's APIs only, with your account), and the
  standing promises (chat rendered never stored, no telemetry, no ad SDKs).
- README, docs/ai/product.md, docs/ai/decisions.md and TASKS.md record the decision and the
  obligations that still bind (ffmpeg LGPL as a separate process, OFL fonts, CC0 icons, model
  provenance, notices in the About screen).

Deliberately NOT decided here: key issuance/verification and the resolution gate itself. Earlier
GPL builds remain GPL for the versions that shipped under it.
2026-10-30 16:10:38 -07:00
gramps 84c335a929 design: bundle Inter + JetBrains Mono (OFL-1.1) as the standard typefaces
The DESIGN.md type standard needs the fonts actually present — the machine's installed faces
are not a design system. Four Inter weights (400/500/600/700) and two JetBrains Mono weights
(500/600) ship as WPF resources from Assets/Fonts; the token families resolve them with the
`/Assets/Fonts/#Family` pack form, so nothing depends on an install or a network fetch.

Licensing (recorded in docs/ai/product.md): OFL-1.1 is a free licence but is NOT GPL-compatible
in the linking sense. These are separate .ttf files loaded through WPF's font stack — an
aggregate, not linked code — so they coexist with the GPL-3.0 app. Obligations carried:
copyright line in THIRD-PARTY-NOTICES.txt (new items 13-14), the OFL text beside the files
(Assets/Fonts/OFL-*.txt), no modification, no reserved-name reuse.

Guard: BundledFontsTests — files exist and are real binaries, the csproj ships them, the theme
uses pack URIs (a bare family name would silently fall back), the notice and both OFL texts are
present, and no third font family sneaks in.

Pending visual confirmation: the first launch should render panel text in Inter and telemetry
in JetBrains Mono.
2026-10-30 07:45:19 -07:00
gramps 565b8dbd6f docs(design): DESIGN.md becomes the visual standard (tokens merged, controls rejected)
Merges the 2026-10-29 Stitch export into the existing 2026-10-26 token sheet and promotes the
file from "redesign exploration" to the normative standard:

- type scale (32→10px), spacing, radii and elevation levels — the three scales the old
  DESIGN.md listed as missing
- meter thresholds: cyan ≤ -12 dB, amber -12..-3, red > -3
- interaction rules: focus ring, hover step, 150ms/90ms easing, no springs

Four export tokens rejected as Material-3 warm-seed artifacts (recorded with reasons):
tertiary #4cd7f6 (kept #38bdf8), outline #ab8986, outline-variant #5b403e,
on-surface-variant #e4beba (kept slate #94a3b8).

Product rulings recorded: #e94560 retired as the app's general accent (blue takes every
interactive role, red is live/record only, wordmark artwork untouched); error gets its own
surface (#93000a) so it cannot be mistaken for on-air red.

The export's dock, four bays, page-nav sidebar, CPU/GPU/NVENC readouts and acrylic blur are
explicitly marked exploratory, not spec — the design principle is a simpler surface, not
hardware cosplay. On-stream output styling stays outside this theme.

Falsified facts fixed in the same commit: product.md brand section now points at DESIGN.md for UI
tokens (MARCOM.md stays the marketing palette), and ui-surface.md no longer calls DESIGN.md an
unreconciled exploration.

Docs-only unit: scope-check run, no build.
2026-10-30 07:41:43 -07:00
gramps 5a102a4a37 docs: decisions log, queue trim, HANDOFF rewrite — the map stops being a treadmill
TASKS.md 467 → 66 lines: one line per live item, detail stays in TASKS/
task-*.md. The old catalogue (63 rows of paragraph status prose plus the
historical narration) is preserved verbatim as TASKS/archive.md — a
2026-10-29 snapshot, not the live queue. The "out of product" list and every
retired decision moved to docs/ai/decisions.md with cause of death.

HANDOFF.md 440 → 40 lines, and it is now OVERWRITTEN, not appended: branch,
what's in flight, next action, live landmines, pending creator checks. History
belongs to git log; the previous handoff is reachable at ee3c592:HANDOFF.md.

docs/ai/decisions.md (new) collects what no longer governs: the paid tier and
its pricing, Polar, Store IAP, the Store route and why it died, the deleted
Velopack updater, signing economics, the retired monetization ideas, and the
permanent out-of-product list.

Facts that lived in the old ai.md "Current limitations" section were re-homed
rather than dropped with the router rewrite: OAuth/DPAPI/session behaviour →
docs/ai/youtube-api.md; paths, persistence and the Google project number →
docs/ai/environment.md.

verify.sh caps now cover all four always-read files (AGENTS 210, ai 100,
HANDOFF 40, TASKS 120).

Docs-only unit: scope-check run, no build.
2026-10-29 11:10:51 -07:00
gramps c9ac348976 docs(ai): ai.md becomes a router; invariants move to AGENTS.md; budgets gated
ai.md 228 → 64 lines. It now carries only: what the product is, the build
commands, the architecture table, a "where the truth lives" table, and a
pointer to HANDOFF. No narrative, no status, no duplicated invariants.

AGENTS.md (195 lines) is now the single always-read file:
- the 20 invariants that break output, wipe user data, or ship a legal/trust
  breach — one line each, pointing at the doc that explains it (they replace
  110 lines of invariant stubs that duplicated docs/ai/*.md verbatim)
- Response style + No-Fluff Mode moved in from ai.md (rules belong in the rules file)
- onboarding is now: HANDOFF → ai.md router → the ONE docs/ai file → dir index;
  TASKS.md is read when the question is "what's next", not to write code
- memory rides with the change but is no longer a unit of its own; HANDOFF is
  ≤40 lines and overwritten, never appended

Anti-rebloat gate: scripts/verify.sh gains step 1/4, a doc-budget check
(AGENTS ≤210, ai ≤100; HANDOFF/TASKS caps land with the queue trim). A commit
that pads a capped file instead of moving detail fails the gate.

schema.md and docs/index.md corrected for the new roles and reading order.
Docs-only unit: scope-check run, no build.
2026-10-29 11:08:07 -07:00
gramps a311c8feb1 docs(ai): partition ai.md into five subsystem files (verbatim moves)
ai.md 1,308 → 228 lines. No prose rewritten: every block was moved as-is so
nothing could be lost in translation, then replaced by a pointer.

New files:
- docs/ai/environment.md — LAN/SSH + Gitea push recipe, Windows dotnet
  build/run, test-suite map, hermetic real-MainWindow test rule
- docs/ai/product.md — brand/naming, AI transparency, GPL licensing
  prohibitions, free-forever monetization, auth/account assumptions,
  packaging & distribution
- docs/ai/youtube-api.md — go-live ordering, the broadcast-binding fix, the
  private-only lock, health parsing, end-stream pre-flight, Test mode,
  chat insert shape
- docs/ai/overlays.md — social bar, native alert box (animations, clip,
  ticker), chat box conventions
- docs/ai/ui-surface.md — control surface, scene strip, preview rail,
  hotkeys, broadcast drawer, toasts, transitions, brand logo, launch
  geometry, theme/properties conventions

ai.md keeps the response/derivation rules, the architecture table, the four
existing invariant stubs, and one pointer per moved subsystem.
docs/ai/index.md routes every file with a "read when" column.

References falsified by the move were fixed in the same commit: AGENTS.md
build note → environment.md → Run, and the "Related" footers in
frame-pipeline.md / output.md / capture.md now point at the owning files.

Docs-only unit: scope-check run, no build (no source touched).
2026-10-29 11:05:53 -07:00
gramps a4dfca6b6c docs(memory): split the output path (ffmpeg, encoder, recording, audio) out of ai.md
Unit 3. Verbatim move of 'FFmpeg locator', 'Live encoder + RTMP push',
'Local recording' and 'Live audio capture' into docs/ai/output.md, grouped as
encoder / recording / audio with a 'Related' footer.

ai.md's stub keeps the decisions that must not be re-litigated or rediscovered:
ffmpeg is bundled (never download/fetch; no-network test), args pure and no -re,
probe order NVENC->QSV->AMF->OpenH264 and never libx264, REC/ON-AIR radio-
exclusive by creator ruling, a modal's falsy result must never fall through into
its side effect, full StopStream teardown on every failure, audio pipe closes
BEFORE the video pump, and honest-by-measurement audio (true dBFS meters,
SpeechLeveler + -1 dBFS limiter, live Func gains).

- ai.md Services row and one 'FFmpeg locator section above' pointer repointed;
  MyMistakes.md dead-Pin entry repointed.
- docs/ai/index.md gains the output.md row.

ai.md 1,558 -> 1,289 lines. No source touched; no push.
2026-10-26 10:28:01 -07:00
gramps 096bf6debe docs(memory): split capture (backdrop, webcam, effects, vertical tier) out of ai.md
Unit 2 of slimming the mandatory read. Verbatim move again - the four capture
blocks leave ai.md unchanged in content, and one stub carries the law.

- docs/ai/capture.md: 'Screen backdrop capture', 'Webcam capture', 'Webcam
  background effects' and 'Vertical (9:16) tier camera placement', byte-
  identical, grouped under backdrop / webcam / vertical headings, with a
  'Related in ai.md' footer.
- ai.md stub keeps: the locked-Background model and NormalizeBackgrounds;
  ActiveBackgroundImage is fed by static art only (2026-08-23 regression);
  window:/picker: keys are session-scoped and a live pin beats auto-detect;
  the settled WinRT interop paths (TryGetDataUnsafe, from-DXGIDevice); the
  VideoFrame seam, own-catch subtype ladder and no fallback camera; one app-
  level webcam identity with live-lock attainability; CameraManager as the one
  effects processing point with per-session mask smoothing; geometry-only
  vertical tier and the model-level rect lock.
- docs/ai/index.md gains the capture.md row; Services/index.md (2) and
  TASKS/task-38-window-backdrop.md repointed.

ai.md 1,975 -> 1,558 lines. No source touched; no push.
2026-10-26 10:26:53 -07:00
gramps 804b1d2f5f docs(memory): split the frame pipeline out of ai.md into docs/ai
ai.md is the mandatory read every session; at 2,702 lines / ~64k tokens it
costs the whole subsystem history even when the task never touches it. This
is a verbatim move, not a compression - nothing is deleted and each stub is
written from the moved text.

- docs/ai/frame-pipeline.md: 'Frame cadence', 'Per-element composite timing +
  scene dump', 'What the first instrumented take said', 'Render budget',
  'Scene compositor' and 'Live frame pipeline', byte-identical, plus a short
  'Related in ai.md' footer and two stale 'below' pointers corrected.
- ai.md keeps two stubs carrying the invariants that must not be missed
  (overlay on every render path, VideoFrame.Epoch, StripPool threads,
  Release-only render numbers; arrival-stamped rawvideo, non-blocking submit,
  stop order, burned _outputIndex judge) and links the new file.
- docs/ai/index.md + docs/index.md route the split; schema.md inventory lists
  the new tier.
- 4 index-row references and 2 TASKS.md anchors repointed; ai.md's Services
  row too.

Measured: ai.md 63.6k -> 47.1k tokens, 2,702 -> 1,975 lines. Progressive
disclosure per schema.md convention 5. No source touched; no push.
2026-10-26 10:25:21 -07:00
gramps a9eb360288 TASK 18: local (record-only) recording pipeline + top-bar pills/light/Start-End button UX
Encoder dual-output: StreamEnabled/RecordEnabled/RecordPath options; per-output
ffmpeg block (stream ->flv, record ->mp4); throw unless at least one output.

RecordingFile: auto-name ty-<yyyymmdd>-<start hhmm>-0000.mp4, rename-on-stop to
ty-...-<len hhmmss>.mp4 with numeric-suffix on collision. LayoutStore persists
record folder. MainViewModel: REC/ON-AIR pills, status light, single Start<->End
button (replaces Start/Stop variants), StopStream no longer signs out, explicit
session timer. About overlay widened. Sign In label + spacing before Start.

Tests: 244 passed / 246 (2 pre-existing: AudioPipeline gains-mute, RoundClip).
Build 0 warnings. Scope check clean.

Memory-map correction (derived-solution rule, 2026-08-29): image-shrink recipe was
never recorded when first done, so it was re-derived. Added Derived-solution/recipes
rule to AGENTS.md; MyMistakes.md now a permanent recipes registry (records the
verified WPF-imaging shrink recipe); schema.md rows MyMistakes as registry; ai.md
points to the rule. README hero image replaced: 3.2MB screenshot -> 1400x794 ->
188KB docs/ytLlive-preview.jpg (JPEG q82).
2026-08-29 09:02:55 -07:00
gramps 3c32c8c105 README: add Screenshot section with app preview (docs/ytLlive-preview.png) 2026-08-07 14:30:09 -07:00