Commit Graph
612 Commits
Author SHA1 Message Date
gramps 5f7bcc2425 fix(text): show selected formatting and style controls
Use category-filtered font and selected-item dropdowns, separate horizontal and vertical alignment, and reuse the border-color palette templates for text color. Persist vertical alignment additively. Follow the established static text-source pattern: https://obsproject.com/kb/text-sources
2026-11-04 17:05:06 -08:00
gramps 5c4acf141b feat(text): composite shared text rasters in output
Raster static text on UI-thread changes and reuse the same transparent BGRA frame in preview and all compositor paths. Cache text frame identity in the frame-pump signature and invalidate static bakes on edits. Follow OBS text-source conventions: https://obsproject.com/kb/text-sources
2026-11-04 15:01:47 -08:00
gramps 58eb640c4e feat(text): add static text editor and preview
TextOverlay layers now have a multiline editor, Windows font and size controls, color/emphasis/alignment settings, and a visible draggable preview. Text sources follow OBS's static text-source conventions: https://obsproject.com/kb/text-sources
2026-11-04 14:28:47 -08:00
gramps 60c4a0032f feat(text): persist independent text layer settings
Add additive defaults and round-trip tests for per-layer text content and formatting. Model the static text source after OBS text-source conventions: https://obsproject.com/kb/text-sources
2026-11-04 14:22:32 -08:00
gramps 106896c408 docs: close Task 53 and advance queue 2026-11-04 13:49:39 -08:00
gramps 7216c95449 feat(entitlement): gate 60 fps output through Steam 2026-11-04 12:14:46 -08:00
gramps 1993e7cc8b docs(task-53): accept vertical live-player check 2026-11-04 11:37:10 -08:00
gramps ede6ecac8e docs(task-53): retire platform safe-zone requirement 2026-11-04 11:35:29 -08:00
gramps bde2a4cf4b docs(task-53): accept six output samples and advance queue 2026-11-04 11:32:44 -08:00
gramps 4b7e798757 feat(ui): show YouTube-confirmed live state in top bar
Normal ON-AIR stays red until YouTube reports lifeCycleStatus=live, then turns green; TEST stays gold and stop restores the idle color.

Status-color reference: https://www.carbondesignsystem.com/building-blocks/core/patterns/status-indicators
2026-11-04 11:19:40 -08:00
gramps f921c0174b docs: reconcile Day 2 release policy and output tasks 2026-11-04 11:18:16 -08:00
gramps 6310f50c27 perf(video): move tier scaling to ffmpeg
The frame pump now submits crop-sized BGRA frames; FFmpeg scales them to final tier dimensions. Bundled scaler benchmark: 300 608x1080 to 1080x1920 frames in 0.435s (scaler-only).

Reference: https://ffmpeg.org/ffmpeg-filters.html#scale-1
2026-11-04 10:52:35 -08:00
gramps 76e8c22815 fix(ui): own private broadcast close-out dialog 2026-11-04 10:11:39 -08:00
gramps a6aea5bf26 feat(ui): color and acknowledge pinned log errors
Add independent AppLog message types and explicit Class 1/2 severity. Wrap and color log rows, pin Class 1 above Class 2, and let acknowledgement return entries to the current-launch timeline without changing file format or rotation.

Ref: https://logtape.org/manual/levels
2026-11-02 18:17:18 -08:00
gramps 01a3fcaaf7 docs: replace system-message strip with Logs tab 2026-11-02 15:55:04 -08:00
gramps 264a90dae8 test(ui): align top bar mode guard with live decision 2026-11-02 11:43:57 -08:00
gramps c856d11fb5 test(ui): configure EDIT-off live start in pill guard 2026-11-02 11:38:17 -08:00
gramps c9bd7e6093 test(audio): match mic fader unity ceiling 2026-11-02 11:34:48 -08:00
gramps e6a7147dc3 test(ui): follow the relocated settings gear 2026-11-02 11:31:49 -08:00
gramps 0e1f948189 test(scoreboard): check shared dev-build gate 2026-11-02 11:28:11 -08:00
gramps caf91e6564 test(ui): read notification colors from app theme 2026-11-02 11:24:50 -08:00
gramps 82609a5046 test(ui): follow shared geometry panel gate 2026-11-02 11:20:54 -08:00
gramps 91e308e8e2 test(ui): inspect themed property hierarchy 2026-11-02 11:18:10 -08:00
gramps 221b81628a test(scoreboard): assert the themed pill switch 2026-11-02 11:14:15 -08:00
gramps 3985a413a7 test(ypp): keep upload window fixture current 2026-11-02 11:10:21 -08:00
gramps 9fa06c67c2 feat(ui): add current-launch logs tab
Add Live Chat and Logs tabs to the non-edit left panel. Expose a bounded process-local AppLog feed so the newest messages appear first without importing earlier startup.log contents or changing file logging.

Ref: https://obsproject.com/forum/resources/understanding-obs-studio-log.473
2026-11-02 11:06:43 -08:00
gramps 20f059422b feat(alerts): add scene-local recent ticker
Model the AlertBox and standalone ticker with shared settings; scene tickers render the in-memory active and queued events without saving history. Keep AlertBox content active-event-only.

Ref: https://obsproject.com/kb/obs-studio-overview
2026-11-02 10:47:48 -08:00
gramps 6208cb9409 feat(ui): route locked tiers through About
Keep non-owner resolution choices visible but muted. Selecting one preserves the free tier and opens About; external navigation is only the explicit llamacasty.com link. Ref: https://streamlabs.com/desktop
2026-11-02 09:41:38 -08:00
gramps ff5e8d94ae feat(entitlement): pivot to paid app ownership
Check Steam ownership of the paid app with BIsSubscribedApp; keep Debug fake ownership out of Release. Ref: https://partner.steamgames.com/doc/api/ISteamApps#BIsSubscribedApp
2026-11-02 09:18:03 -08:00
gramps 54902cb838 ui(properties): no raw typography left in the panel, and one heading rank
The earlier pass checked wiring only; this is the typography sweep. Raw Foreground/FontSize
labels are gone from the elements panel:

- Opacity, Timer (minutes) and URI are headings above their control (PropertyHeader), with the
  control row on the panel's 10px indent — the same shape as Border Thickness/Opacity.
- Scroll speed, scoreboard Opacity/Text size and their value readouts are PropertyFieldLabel.
- The drag hint and the scoreboard sign-in note are PropertyHint.
- Simulate (dev) keeps its orange, gains the field-label typography.
- Edge, Text colour and Background were PropertySubHeader/SubHeader: ONE heading rank now, so
  PropertySubHeader and the legacy SubHeader are deleted from Themes/Controls.xaml.
- DeveloperPanel — the last panel with raw typography and an unstyled CheckBox — now uses the
  same styles, and PropertiesStyleTests accepts PropertyPillCheck (the guard went stale when
  every check box became a pill switch). 18 tests pass; Release 0 warnings.
2026-11-01 16:26:20 -08:00
gramps 790cff45c2 ui(alerts): Video, Sound byte and Ticker are headings, not sub-headings
Same PropertyHeader style as Geometry and Stream Alerts — one heading rank in the properties
panel, not two.
2026-11-01 16:19:07 -08:00
gramps b3d7cf30b2 ui(properties): sub-headings sit flush left, level with the category header
Their rank is carried by size and colour, not by an indent — so Video, Sound byte and Ticker all
line up on the left edge exactly like each other, and only the controls under them tab in.
2026-11-01 16:17:38 -08:00
gramps 31b655cecb ui(alerts): a Ticker sub-heading, with Message ticker / Display / Volume tabbed under it 2026-11-01 16:15:41 -08:00
gramps 0fdd9d5faf fix(brand): one logo, not two — and hit-testing follows what you see
The brand exemption for the universal blur put a BrandLogoOverlay above the blur host while the
canvas ItemsControl kept drawing the same element, so two logos sat on top of each other.

- The canvas presenter for a brand logo is collapsed: BrandLogoOverlay is the only render, and it
  now carries the element's Opacity so the canvas copy is not missed.
- Hit-testing follows the eye: a click inside the logo's bounds selects the logo, because it is the
  topmost layer in the compositor too. Without that you saw the logo on top and grabbed the webcam
  underneath it.
- SelectedLayerPreviewHitTestingTests updated to the new contract (collapsed presenter, logo wins
  its bounds, list selection still works). 3 tests pass; Release 0 warnings.
2026-11-01 16:12:38 -08:00
gramps 41b3933b08 fix(background): refresh BOTH preview layers, not just the thumbnails
The preview draws two background images — BackgroundImage and, above it, ActiveBackgroundImage
(the static art) — while the mini-pane reads the scene snapshot. The default-restore path only
called UpdateBackgroundImage, so the thumbnails updated and the preview did not.

It now calls UpdateActiveBackground as well.

Also adds the generic cure behind the Background bug: OnSelectedElementPropertyChanged invalidates
the scene bake for ANY property change. A static layer's pixels are cached by element identity, so
a property edit without invalidation repaints nothing and the control looks dead — the failure was
never specific to UseDefaultBackground, only first noticed there.

Background + thumbnail tests pass (21); Release 0 warnings.
2026-11-01 16:01:41 -08:00
gramps d6088f8e35 fix(background): Use default background actually restores the shipped art
Browse… writes the chosen picture into BOTH CustomBackgroundPath and AssetId, and the default
stamp bails when AssetId is already set. So flipping "Use default background" back on changed a
bool while the preview kept showing the custom image — it looked like the switch did nothing.

Turning the default on now clears the custom pointer and the asset, re-runs
StampDefaultBackgroundAsset for the scene's shipped art, and only then invalidates the bake,
updates the preview and refreshes thumbnails.

BackgroundTests pass (17); Release 0 warnings.
2026-11-01 15:59:10 -08:00
gramps c297b3fb0f ui(properties): answer the visual review — quiet actions, real hint tone, dividers, legible disabled
Review findings from a screenshot of the Stream Alerts panel, each addressed:

- "Reset to built-in" and "Browse…" were YtButton-derived (white, semi-bold, 13px), so a reset
  action outranked the section header. Property-panel actions now use PropertyButton: bordered,
  11px, normal weight, OnSurface text.
- Explanation text and field labels were the same muted blue-gray. PropertyHint is now
  OnSurfaceDisabled, one clear step below a label.
- Groups inside a category ran together. PropertyDivider (1px OutlineVariant) separates Video,
  Sound byte and Message ticker.
- The disabled loop switch was near-invisible. Disabled now keeps the label at OnSurfaceVariant
  and dims the whole control to 0.55 — readable, obviously inactive.
- Switch "on" was full-strength accent, shouting about static state. The track now sits at 0.6
  opacity when on, which also stops it competing with the slider's accent fill.

No new palette entries: every change uses an existing token, so DESIGN.md stays the one colour
source. Release 0 warnings; token and typography tests pass.
2026-11-01 15:53:40 -08:00
gramps 8221bd6478 ui(properties): one typography hierarchy, pill switches, and tokens instead of hex
Creator directive 2026-11-01: the properties panels had 14 hand-rolled heading styles, 37
hand-rolled labels, four literal grey hex values, and 14 square checkboxes.

- PropertyHeader / PropertySubHeader / PropertyFieldLabel / PropertyHint in Themes/Controls.xaml
  now carry the whole hierarchy: category bright and 11px, labels and hints dim, size and weight
  doing the level. Colour groups the panel — bright for what you edit, dim for its labels and
  help — and no new palette entry was needed; DESIGN.md stays the only colour source.
- Every checkbox in the panel is a pill switch (PropertyPillCheck, 32x16 animated thumb).
  IsChecked semantics unchanged, so no binding moved.
- Literal #888/#666/#555 became OnSurfaceVariant / OnSurfaceDisabled.
- 13 headings, 2 sub-headers and 31 in-row labels now reference a style instead of repeating
  colour + size + alignment inline.

Controls audited for backing: every Button/Slider/ComboBox/CheckBox/ToggleButton/TextBox in the
panel has a Command or Click, a Value binding, an ItemsSource or Selected*, IsChecked, or Text —
no dead controls. Release 0 warnings; typography and token tests pass.
2026-11-01 15:51:33 -08:00
gramps 82960d0ca3 docs(video): the training-video plan — formats, why Shorts are a funnel, and what to film
New `youTubeTrainingVideos.md`: the feature list worth filming, split into "ready now" and
"after the surge lands" so nothing gets filmed before it exists, plus the format decisions
settled on 2026-11-01.

Format: 16:9 long-form is the library (search intent, walkthroughs, conversion); vertical Shorts
are trailers cut from the same pass. A 16:9 video cannot be a Short, so it is a per-video choice.
TikTok is a mirror of the verticals, never a strategy.

The numbers behind it: Shorts need 1,000 subs + 10M views in 90 days for YPP versus 4,000 watch
hours in a year; Shorts RPM is $0.02-0.08 per 1,000 against $3-8 for tutorials; and one $79 sale
nets ~$55.30, which is what 700k-2M Shorts views pay. Shorts are an install engine, not revenue.

Film first: privacy blur, auto-ducking, webcam blur.
2026-11-01 15:43:48 -08:00
gramps 5d836aecbb docs(commercial): one price — $79 Steam DLC rising to $99, subscription retired
Creator ruling 2026-11-01. One SKU, one price, Steam does the selling.

- Pro is a one-time Steam DLC on the free app: $79 introductory, $99 on a stated date, announced
  where the price is shown and actually raised.
- The subscription is retired. The reasoning is in decisions.md: no service behind a monthly fee
  (no servers, no cloud, no content), ~1-3 subscribers per 1,000 installs against 5-8% churn, and
  it carried the most expensive code in the product.
- Polar goes dormant. Its customer-portal design is recorded, not built. The "freemium on Steam,
  convert on Polar, Valve gets nothing" proposal is rejected: in-app purchases must use the Steam
  Wallet, so it risks the listing to save ~$24 a sale.
- Entitlement collapses to `steamDlcOwned`. No lease, no key, no portal, no device limit; the
  greyed tier opens the Steam store overlay, so every purchase stays inside Valve's own surface.
- The site publishes the model and links the store page. It takes no payment and hosts no binaries.
- The $400 lifetime died with the subscription it anchored. Founding 200 died with the coupons and
  passes it needed.

Costs: $115 fixed year one; $79 nets $55.30 after Steam, so three sales cover the year.
2026-11-01 15:40:44 -08:00
gramps 1c2eedcaba ui(audio): the volume faders stop at 0 dB — no unreachable track
Mic and desktop gains run -60 to 0 dB and that is the ceiling: 0 dB is full input level, and
nothing past it is offered or drawn. The unity tick is gone with the zone it explained.
2026-11-01 15:16:03 -08:00
gramps 1074a46e9c ui(audio): a 0 dB unity tick on both volume faders
The faders run -60 to +6 dB but the ladder tops out at 0 dBFS, so the last 6 dB looked like
dead track. The tick marks unity at 60/66 of the track: what is left of it is boost, and now it
reads that way. Both mic and desktop faders; the lip-sync slider is milliseconds and gets none.
2026-11-01 15:13:21 -08:00
gramps b00bf48b9d feat(alerts): sound byte under Built-in video, and alert audio that reaches the device
Creator request: a sound-byte browse + label under Built-in video, with an option to loop the
sound while the clip plays. The video itself is always one pass to completion.

- Source gains AlertAudioPath and AlertAudioLoop, persisted (column + migration + INSERT +
  SELECT + reader, the whole chain — that chain is where the blur crashed once).
- The sound byte plays through MediaPlayer on the default output device; MediaEnded re-seeks to
  zero while looping, and StopClip kills it so a tail cannot bleed into the next alert.
- The clip's OWN soundtrack moved off the mixer and onto the device as well
  (Services/Audio/AlertAudioOutput: one WaveOutEvent + buffered float provider).

Why the move: the creator asked that alert audio — the clip's track or the added sound byte —
appear in the desktop sound bar. Only audio that reaches the default device is picked up by the
WASAPI loopback that feeds both the meter and the stream. The old EnqueueAlertAudio route put the
sound in the stream but left the meter flat and the room silent.

Tests: the sound byte and its loop flag round-trip through SQLite, and a fresh alert layer starts
with no sound byte and no loop. Release 0 warnings.
2026-11-01 14:13:57 -08:00
gramps 46d12e5873 fix(alerts): Test alert actually plays — it was queuing into nothing
TestAlert called Enqueue, which is the pure test seam: it fills the queue and returns. The
alert layer needs three more things to show anything — its scene list, the playback ticker
running, and the preview refreshed — and all three live in OnMessageReceived, the same entry
the chat feed uses (MainViewModel.Chat.cs).

Now it calls OnMessageReceived(message, Scenes), so the clip advances on the ticker and writes
frames to the box's VideoImageSource, which is what the preview pane displays.
2026-11-01 14:07:14 -08:00
gramps 005b459fe0 feat(alerts): a Test alert button in the alert properties
Sits to the right of the Built-in video pill, labelled "Test alert". It enqueues one synthetic
Super Chat through the real AlertOverlayLayer, so what plays is exactly what a viewer triggers —
the custom clip when one is set, the built-in clip otherwise — and it goes through the same
output path (preview and encoder) as a live alert. Local only: nothing reaches YouTube and no
chat row is invented.
2026-11-01 14:04:20 -08:00
gramps 754377c5db ui(audio): every header label in the sound area uses one colour
MIC's device name was the only header label at full-strength OnSurface (it was brightened
because it shows a choice), and the lip-sync reading was Tertiary — three different values
across four bays in the same row. All four header labels are now Brush.OnSurfaceVariant, the
standard secondary-text token. No other colours touched.
2026-11-01 13:59:57 -08:00
gramps b75c2b3001 ui(audio): one row grid across the dock — ladders on a line, volume bars on a line
Every bay is now header / ladder / controls. Lip-sync has no ladder, so it holds an empty 20px
row (10px meter + the two margins) that keeps its slider exactly level with the other three,
and MIC's ladder gained the missing 4px bottom margin that had its slider sitting 4px high.

Presets stay under the slider in the narrow lip-sync bay.
2026-11-01 13:57:36 -08:00
gramps 2fedc7cb64 ui(audio): the lip-sync slider moves up one line — onto the header
Not under it: the offset slider now rides the header line beside the LIP-SYNC badge and the
reading, and the preset row is what stays below. The box is narrow, so a slider on its own line
was the only thing making it two-and-a-bit rows tall.
2026-11-01 13:56:53 -08:00
gramps 789272142d fix(build): the wordmark number counts commits again, not compiles
GenerateBuildStamp read BuildCounter.txt, added 1 and wrote it back on every CoreCompile, so
the number tracked BUILDS: a day of iteration reached #1122 against 563 commits. It now uses
git rev-list count of HEAD, which is what the comment always claimed (and why the file existed
in .gitignore). No counter file, no drift; a missing git still falls back to the start.

Also moves the MIC and lip-sync volume bars up one line: both were pinned to the floor of their
bay by a star row plus VerticalAlignment=Bottom. They now sit directly under their headers,
with the auto rows sized to content.
2026-11-01 13:56:17 -08:00
gramps 35c7809c28 fix(audio): bay ladders fill their bay again — the 320px cap was a mistake
Capping the three bay meters at 320px and left-aligning them made each ladder look cut off
mid-bay, which is what the creator saw as deleted bars. The caps are gone; the master ladder
keeps its own 420-900px range so it stays the longest without shortening anything else.
2026-11-01 13:52:31 -08:00