Repository navigation
Banners: your own images anywhere on the picture (plan 186) - #728
Merged
Merged
Conversation
Co-Authored-By: Claude <noreply@anthropic.com>
…ing) Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
…try twin Plan 186 S2. The Banner type and every banners.* method and the banners.changed event are registered with strict schemas (allowUnknown false): files are basenames only, the Phase 2 timer is optional and never null, lists are capped at 24. The pinned banner fixtures validate in TS. bannerDrawRect mirrors Rust OverlayRect::pixels and the overlay gravity rules and reproduces every case of protocol-fixtures/banner-geometry.json. bannerAssetUrl builds videorc-asset://banner/<basename>. Banner import gets the takeover import's 45 s request budget. Co-Authored-By: Claude <noreply@anthropic.com>
…f-surface banners Plan 186 S2 and S4 step 4. - banners:pick-image: main's PNG/JPEG/WebP dialog issues an input-file capability (the pickScreenImage shape), through the full IPC chain with a semantic contract (null or exactly one input-file selection), main only. - videorc-asset://banner/<file> serves the backend's managed Banners dir beside Screens: basename only, realpath, no symlinks, regular files. - Main subscribes its own backend event socket to banners.changed and reads banners.list on connect. A bad banner payload is dropped with a warning and never retires the capture and present-pump socket. - Both proof-surface scene builders add one image layer per showing banner (a switch on, Assets order, after the takeover), placed by bannerDrawRect against the scene or compositor canvas, with opacity, stacked above every scene layer. A banner change re-places them on the current scene. Banner layers never count as source liveness, and the scene fingerprint ignores them, so a banner change at the same revision is never a conflicting scene. Co-Authored-By: Claude <noreply@anthropic.com>
Plan 186 S5. The Studio provider holds the one banner set for every
surface that comes later (the Scene page, the Stream Manager relay, the
remotes) behind its own context, useStudioBanners(), so a show, a hide or
a move never republishes the core context.
- The list loads with banners.list on connect, outside the bootstrap
batch so a failed read never fails it, and follows banners.changed. A
reply commits only when no banners.changed overtook it.
- importBanner runs the takeover single-flight (lib/banner-import.ts): one
picker and one import at a time, fenced across reconnects, and it never
shows the new banner.
- updateBanner, reorderBanners, deleteBanner, showBanner, hideBanner (no
id hides all) and toggleBanner resolve false (or null) after a failure.
selectedBannerId / selectBanner carry Assets → Place on Scene to the
Scene page; a deleted banner is never selected.
- A backend refusal is a warning toast through toastUserError with the
verbatim sentence ("Couldn't show the banner" + "Four banners are
showing. Hide one first."); anything else is the usual failure.
UserFacingError gains a warning tone for it. The copy (lib/banners.ts)
loads only on a failure, so the shell stays lean: +1.4 KB gzip eager.
- No success toast for show, hide or a place; a confirmed delete names
what went.
Co-Authored-By: Claude <noreply@anthropic.com>
Plan 186 S5. A Banners section between Backgrounds and Takeovers, in the
Assets lazy chunk, is the one home that imports, renames, orders and
deletes banners.
- "Import image…" stays on while recording or live; it says "Choosing
image…" then "Importing…" with the shared spinner, and stops at 24
banners with the reason. The description carries the sharp sizes.
- Each MediaCard tile shows its thumbnail contained in a 16:9 box over a
monochrome checkerboard from the foreground token (BannerThumbnail), so
transparency reads in both themes and a 6:1 strip is never cropped. A
showing banner wears the one in-use look: the rim and a "Showing" chip
(InUseChip gains a label).
- The hover ⋯ holds Show / Hide (off at four showing, with the reason),
Place on Scene (selects it and opens the Scene page), Rename… in place,
Move earlier / Move later with their keys, and Delete… last. Alt+←/→
and drag reorder too.
- Delete asks ("Delete banner?") and holds the question while it runs; a
refusal during a session comes back as the hook's warning toast.
- Loading and offline states come before "No banners yet", which has its
own Import image… action.
Co-Authored-By: Claude <noreply@anthropic.com>
A `banners` table beside `stream_screens`, a managed `Banners` dir beside `Screens`, and the `Banner` wire type pinned by protocol-fixtures/high-risk-contracts.json (camelCase, the two timer fields omitted when off, never null). Import runs on the AssetImport lane: magic-byte sniff (PNG/JPEG/WebP), animated PNG (acTL) and WebP (ANIM/VP8X flag) refused, 25 MB / 40 MP from the header / 16 px floor, the 24-banner cap, decode under a 192 MB limit with the EXIF orientation applied, Lanczos3 on premultiplied pixels down to 3840 px, hasAlpha, atomic `<id>.png` + `<id>.thumb.png`, and the row with `banner_default_places`, hidden, both switches on, opacity 1.0. Nothing is published half done. RPCs: banners.list (observation), banners.importImage (AssetImport lane, file-mutation policy), banners.update / reorder / delete (mutations), banners.show / hide (LiveControl lane); every mutation ends in `banners::on_banners_changed`, which emits `banners.changed` with the full list and is the seam S3 extends to install the compositor set (`shown_banners_for_compositor`). The backend enforces the four-showing cap, the overlay rect rules, opacity 0.2..1.0, names of 1..40 characters, 0 = timer off, the managed-dir guard on delete, and refuses deleting a showing banner while a session runs. Shown state survives a relaunch. `banner_draw_rect` reproduces every case of protocol-fixtures/banner-geometry.json. Co-Authored-By: Claude <noreply@anthropic.com>
…86 S7)
Three camelCase intents beside takeoverShow: `bannerShow {bannerId}`,
`bannerHide {bannerId?}` (no id hides all; the field is omitted, never
null) and `bannerToggle {bannerId}`. They share one "banner" debounce
bucket, separate from "takeover", and a blank id is refused before relay.
No LAN route or LAN_EVENTS change: they ride `remote.intent`.
Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
…ector Co-Authored-By: Claude <noreply@anthropic.com>
…ender tests Co-Authored-By: Claude <noreply@anthropic.com>
…r intents
The Studio renderer pushes a read-only {id, name, shown} list to the Stream
Manager through main (comments-window:banners-push/-get/banners) and answers
an explicit show, hide or hide-all relayed from it (comments-window:banner,
banner-request, banner-result-push), quietly: a refusal's sentence goes
back to the window that asked. remote.describe gains banners, whitelisted
to the same three fields, and bannerShow, bannerHide and bannerToggle run
through runBannerAction with the takeover-style refusal copy.
Co-Authored-By: Claude <noreply@anthropic.com>
… in ⌘K Stream Manager's status bar gains a Banners menu beside Mark clip, on air or not: a check per banner, then Hide all banners, mirrored in the narrow ⋯ menu, with "Import banners in Assets" when there are none. The chat composer's /banner <name> shows or hides the banner the name means (exact wins, else a unique prefix), /banner off hides them all, and /banner lists the names; it never posts to chat. ⌘K gains a Banners group: Show banner or Hide banner per banner, then Hide all banners while any shows. Co-Authored-By: Claude <noreply@anthropic.com>
The banner IPC schemas move into electron-ipc-contract, so the renderer chunk of banner-commands carries only the name matching and completions; the Stream Manager loads the banner refusal rules only when a show or hide fails; and its Banners action is the word alone, like Clear, so no icon glyph rides into the window. Stream Manager eager JS: +2.3 KB gzip for the whole feature; the main window: +250 B gzip. Co-Authored-By: Claude <noreply@anthropic.com>
…rotocol doc The Stream Deck plugin gains a Banner action: pick a banner in the property inspector, the key shows its name (with ON while it shows) and a press sends bannerToggle by id. The phone's Controls gain a Banners group of named show and hide keys. Both re-read remote.describe after each remote.state, because a banner's shown lives in describe. No LAN route or LAN_EVENTS entry: docs/remote-control.md adds the three intents, the describe field and the plan's phone leak argument. Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
…draws (plan 186 S3) Banners now draw on the picture: after the scene and the takeover, before captions, the Buddy and the highlight card (D5), on the Metal path (all three branches of try_gpu_compose_with_chrome) and the CPU path (render_compositor_yuv420p_frame), on the primary and the auxiliary leg. - CompositorRuntime holds one Arc<BannerSet> at snapshot level beside the takeover; install_banner_set swaps it from on_banners_changed (before the banners.changed event) and once at backend start (shown survives a relaunch, D3). No scene commit, no capture change, no session is touched. - banner_prepare.rs: an off-lock worker (the process runtime's blocking pool) decodes the stored PNG and scales it once per (banner, file revision, canvas, rect, opacity) with premultiplied Lanczos3, opacity baked in, into a 160 MB LRU BannerBitmapCache (entries used within 1 s are never evicted). A tick never waits: until a prepare lands the last prepared bitmap draws (Metal scales it into the rect, the CPU draws it at its old size at the new position). banners.show refuses with "Four banners are showing. Hide one first." when the estimate for the current canvases would exceed the budget. - Metal: blended quads from key-addressed textures in namespace 9; the keyed slots are now (namespace, slot) with four slots for namespace 9 (LRU evicted) and the Buddy's namespace 7 unchanged. One upload per prepared revision, never per frame. - CPU: composite_overlay_at gains an opacity argument (1.0 is the identity blit) over a shared straight-alpha helper that reads BGRA directly. - Leg plan per banner (D6): the idle preview draws every shown banner with a switch on; in a session overlay_leg_plan with the shared-leg fallback, planned against an OverlaySessionShape the session sets on the runtime after the compositor is armed (cleared on run stop, lease release and a new run), so a set swap mid-session re-plans in place. - D13 notices: overlay_start_notices_with_banners adds the shown banners as one item labelled `banners`; the three existing sentences are unchanged. - Fade (D10): 200 ms ease-out per layer, computed once per tick at the snapshot; a hidden banner draws until its fade-out ends, then leaves the set; Show mid-fade-out reverses from the current value. - Live drag: a `banner:<id>` editor draft with a transform moves that banner's picture on the idle preview for the tick (keyed by the committed rect, so a drag sample never queues a prepare); chrome draws as before. Tests: fade curve and set transitions, the exhaustive leg table, the notice sentences byte for byte, prepare-off-lock, the budget refusal, CPU/Metal banner parity (half-alpha edges, opacity 0.5, the card wins), banner above the takeover, the vertical leg's rect, no upload across 120 ticks, four keyed slots, the draft override and the install swap. Co-Authored-By: Claude <noreply@anthropic.com>
…prepare helpers gated clippy -D warnings on the non-test build: the two-argument overlay_start_notices wrapper had no production caller once recording.rs passed the shown banners, so the one function now takes the banner switches (`&[]` for none) and keeps its documented name; the prepare service's inspection helpers (cache_len, cache_bytes, is_exact, prepares_completed) are test-only. Co-Authored-By: Claude <noreply@anthropic.com>
… after-start refusal (plan 186 S4) The legacy graph's full-canvas RGBA FIFO now carries the banners over the takeover: prepared once for the output canvas off every lock, recomposed when the takeover or the banner set changes, and created when a banner shows at start. The Windows direct D3D11 recording is never chosen with a banner showing. A session that cannot draw a banner added after its start refuses a new Show with the pinned sentence. Co-Authored-By: Claude <noreply@anthropic.com>
Each leg draws the same banner_draws_for_leg the Metal and CPU paths draw (leg plan from the session shape, fade, prepared bitmap), as immutable image layers at z 6 to 9 under the captions, placed by the shared banner_draw_rect oracle and uploaded once per prepare. The pump reads the banner set with its committed snapshot each tick, so Show, Hide and moves land live. Cross-checked with cargo xwin check and clippy (CI flags) for x86_64-pc-windows-msvc; the pure layer geometry is tested on every host. Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
…S11) "Buddy, show the sponsor banner", "take the banner down" and "hide the banners" run through the banners.show and banners.hide functions and end in on_banners_changed. A banner noun in the sentence said to Buddy hands the utterance to the banner grammar before the highlight and clear grammar; utterances without it are unchanged. An unreadable banner sentence is "didn't catch that" and never reaches the cloud parser. Co-Authored-By: Claude <noreply@anthropic.com>
…contract Co-Authored-By: Claude <noreply@anthropic.com>
…fact smoke (plan 186 S8) smoke:banners imports an opaque strip and a half-transparent logo, then in a recording, a stream to local horizontal and vertical receivers, and both, shows, moves and hides them over a takeover, and samples every artifact at the banners' own rects: placement, the per-leg 'Appears in' routing, a real alpha blend, banners above the takeover, one session throughout, fresh compositor frames during prepares, and the A/V analyzer. It joins the smoke:recording-studio bundle beside the takeover smoke. Co-Authored-By: Claude <noreply@anthropic.com>
One process task owns a pure book of each banner's timing: it watches the stored list (whatever changed it), hides a banner its Hide after says, and during a session shows a repeating banner every period, show to show. A manual Show or Hide wins and restarts the cycle; a refused Show waits a period and a Hide the store did not apply waits a few seconds, so nothing spins. Woken by every banner change and at the next deadline. Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
The voice track branched before S4 added the session check to show_banner; its show_banner_and_publish now passes it, like the RPC and the timers. Co-Authored-By: Claude <noreply@anthropic.com>
|
Note Currently processing new changes in this PR. This may take a few minutes, please wait... ⚙️ Run configuration
⛔ Files ignored due to path filters: 1
📒 Files selected for processing: 113
✨ Finishing Touches
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
Windows refuses to delete a directory while the test's SQLite database inside it is still open, so the last-line remove_dir_all(...).unwrap() failed 14 banner tests in the Windows source gates even though every banner assertion before it passed. Use the same best-effort cleanup the noise_cleanup tests use, and record the Windows CI result in the plan. Co-Authored-By: Claude <noreply@anthropic.com>
Owner
Author
Verification (head
|
TheOrcDev
marked this pull request as ready for review
October 21, 2026 08:50
TheOrcDev
added a commit
that referenced
this pull request
Oct 21, 2026
macOS 0.9.144-beta.1 and Windows/Linux 0.9.144-alpha.1: banners (#728), Clean cut recording rows (#726), and the live-stream fixes from the owner's 2026-10-21 stream (#730): Buddy shows up on command and says when it is off stream, nothing opens under the docked preview, and the chat stays on the newest message. Co-authored-by: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Banners: your own images, placed anywhere on the picture, shown and hidden while recording or live (plan 186,
plans/186-banners-on-the-picture.md). They're free for everyone.What you can do
/banner <name>and/banner offin the chat composer;How it works
Arc<BannerSet>on the compositor runtime, shared by the idle preview and both legs. Showing, hiding or moving a banner never touches capture, the scene graph or a session.bannerstable and theAssetImportlane, but they keep alpha. Images are downscaled with premultiplied Lanczos3 and EXIF rotation is applied.banner_draw_rect/bannerDrawRect), pinned byprotocol-fixtures/banner-geometry.json.Phone remote (LAN) leak argument
LAN_EVENTS. The newbannerShow,bannerHideandbannerToggleintents ride the existingremote.intent/remote.describe.Verification
Gate results are recorded in the plan's "As built" section and summarized in the comment below.
Owner by-eye, in light and dark, is still owed: import a sponsor PNG, place it, go live with a vertical destination, Show and Hide from Stream Manager and with
/banner, check the fade and the soft-size warning, and compare the recording with the preview.🤖 Generated with Claude Code