Skip to content

Banners: your own images anywhere on the picture (plan 186) - #728

Merged
TheOrcDev merged 33 commits into
mainfrom
plan-186-banners
Oct 21, 2026
Merged

TheOrcDev merged 33 commits into
mainfrom
plan-186-banners

Conversation

@TheOrcDev

Copy link
Copy Markdown
Owner

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

  • Import in Assets → Banners: PNG, JPEG or WebP, up to 25 MB and 40 megapixels. Transparency is kept. You can keep up to 24 banners.
  • Recommended sizes appear in the Assets description: wide banner 2400 × 400, logo 512 × 512, full frame 3840 × 2160.
  • Place a banner on the Scene page by dragging and resizing it. Resizing keeps the image's shape, and there are snaps. The inspector shows the exact size the banner needs where you put it ("Your image is 800 × 133 px: it will look soft at this size. Use one at least 2304 × 384 px.").
  • The vertical stream gets its own position for each banner.
  • Show and Hide from:
    • the Scene page;
    • Stream Manager's Banners menu;
    • /banner <name> and /banner off in the chat composer;
    • ⌘K;
    • a Stream Deck Banner key;
    • the phone remote;
    • Buddy ("show the sponsor banner", "hide the banners").
  • Up to 4 banners show at once, with a 200 ms fade.
  • Appears in: Stream / Recording on each banner. These follow the overlay leg plan, and the Go Live sheet sentences cover banners too.
  • Timing:
    • "Hide after" (10 s to 5 min);
    • "Show every" (5 to 30 min, only while recording or live) for sponsor rotation;
    • a manual Show or Hide always wins.
  • Stacking: banners draw over the scene and any takeover, and under captions, Buddy and the highlight card. A banner that is showing stays on when you switch scenes or relaunch the app.

How it works

  • Not a scene source. A banner is an overlay-style layer: one 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.
  • Backend-owned import. Banners follow the takeover pattern with a banners table and the AssetImport lane, but they keep alpha. Images are downscaled with premultiplied Lanczos3 and EXIF rotation is applied.
  • Drawing. Each bitmap is prepared off the render thread at its exact pixel size per canvas, with opacity baked in (160 MB cache). It is then drawn 1:1 on all five render paths:
    • Metal: namespace 9 keyed textures, one upload per prepare;
    • CPU: a true straight-alpha blend;
    • Windows D3D11 media session: z 6 to 9;
    • the legacy FFmpeg graph: composed into the takeover's RGBA FIFO frame;
    • the Electron proof surface.
  • One geometry definition (banner_draw_rect / bannerDrawRect), pinned by protocol-fixtures/banner-geometry.json.
  • Sessions that can't add a banner after they start (4K60 legacy without the FIFO, Windows direct D3D11 recording) refuse a later Show with: "This recording can't add banners after it starts. Show the banner before you press Record."

Phone remote (LAN) leak argument

  • Banner ids are opaque UUIDs.
  • Names are labels the streamer typed for graphics they show viewers.
  • No file paths and no image bytes cross the LAN; the phone shows names only.
  • No route is added to the LAN router and nothing is added to LAN_EVENTS. The new bannerShow, bannerHide and bannerToggle intents ride the existing remote.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

TheOrcDev and others added 29 commits October 20, 2026 19:17
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>
…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>
…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>
@coderabbitai

coderabbitai Bot commented Oct 20, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Note

Currently processing new changes in this PR. This may take a few minutes, please wait...

⚙️ Run configuration
  • Configuration used: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: f2583541-bbd3-4a92-b548-88f9d76faa27
📥 Commits

Reviewing files that changed from the base of the PR and between dacbec2 and 30b5c04.

⛔ Files ignored due to path filters: 1
  • apps/streamdeck-plugin/imgs/banner.svg is excluded by !**/*.svg
📒 Files selected for processing: 113
  • CONTEXT.md
  • apps/desktop/src/main/backend-event-message.test.ts
  • apps/desktop/src/main/backend-event-message.ts
  • apps/desktop/src/main/index.ts
  • apps/desktop/src/main/native-preview-scene-authority.test.ts
  • apps/desktop/src/main/preload-api-policy.test.ts
  • apps/desktop/src/main/preview-surface-banners.test.ts
  • apps/desktop/src/main/preview-surface-banners.ts
  • apps/desktop/src/preload/api-policy.ts
  • apps/desktop/src/preload/index.ts
  • apps/desktop/src/renderer/comments/main.tsx
  • apps/desktop/src/renderer/src/backendClient.test.ts
  • apps/desktop/src/renderer/src/backendClient.ts
  • apps/desktop/src/renderer/src/components/background-replace-dialog.test.ts
  • apps/desktop/src/renderer/src/components/banner-thumbnail.tsx
  • apps/desktop/src/renderer/src/components/banner-timing-chip.tsx
  • apps/desktop/src/renderer/src/components/banners-section.test.ts
  • apps/desktop/src/renderer/src/components/banners-section.tsx
  • apps/desktop/src/renderer/src/components/command-palette.test.ts
  • apps/desktop/src/renderer/src/components/command-palette.tsx
  • apps/desktop/src/renderer/src/components/go-live-dialog-flow.test.ts
  • apps/desktop/src/renderer/src/components/go-live-dialog-labels.test.ts
  • apps/desktop/src/renderer/src/components/go-live-dialog-testids.test.ts
  • apps/desktop/src/renderer/src/components/go-live-dialog.tsx
  • apps/desktop/src/renderer/src/components/scene/banner-inspector.test.ts
  • apps/desktop/src/renderer/src/components/scene/banner-inspector.tsx
  • apps/desktop/src/renderer/src/components/scene/overlay-stage.test.ts
  • apps/desktop/src/renderer/src/components/scene/overlay-stage.ts
  • apps/desktop/src/renderer/src/components/scene/scene-stage.tsx
  • apps/desktop/src/renderer/src/components/stream-manager/chat-pane.tsx
  • apps/desktop/src/renderer/src/components/stream-manager/marker-composer.test.ts
  • apps/desktop/src/renderer/src/components/stream-manager/stream-manager-status-bar-banners.test.ts
  • apps/desktop/src/renderer/src/components/stream-manager/stream-manager-status-bar.tsx
  • apps/desktop/src/renderer/src/components/stream-manager/stream-manager.tsx
  • apps/desktop/src/renderer/src/components/tabs/assets-tab.tsx
  • apps/desktop/src/renderer/src/components/tabs/layout-tab.tsx
  • apps/desktop/src/renderer/src/components/takeover-screens-section.tsx
  • apps/desktop/src/renderer/src/hooks/studio-provider.integration.test.ts
  • apps/desktop/src/renderer/src/hooks/use-studio.tsx
  • apps/desktop/src/renderer/src/lib/banner-import.test.ts
  • apps/desktop/src/renderer/src/lib/banner-import.ts
  • apps/desktop/src/renderer/src/lib/banner-place.test.ts
  • apps/desktop/src/renderer/src/lib/banner-place.ts
  • apps/desktop/src/renderer/src/lib/banner-size.test.ts
  • apps/desktop/src/renderer/src/lib/banner-size.ts
  • apps/desktop/src/renderer/src/lib/banner-start-notices.ts
  • apps/desktop/src/renderer/src/lib/banner-timing.test.ts
  • apps/desktop/src/renderer/src/lib/banner-timing.ts
  • apps/desktop/src/renderer/src/lib/banners.test.ts
  • apps/desktop/src/renderer/src/lib/banners.ts
  • apps/desktop/src/renderer/src/lib/buddy-command-view.test.ts
  • apps/desktop/src/renderer/src/lib/buddy-tab-view.test.ts
  • apps/desktop/src/renderer/src/lib/buddy-tab-view.ts
  • apps/desktop/src/renderer/src/lib/overlay-layout.test.ts
  • apps/desktop/src/renderer/src/lib/phone-remote-page.test.ts
  • apps/desktop/src/renderer/src/lib/remote-surface.test.ts
  • apps/desktop/src/renderer/src/lib/remote-surface.ts
  • apps/desktop/src/renderer/src/lib/user-error.test.ts
  • apps/desktop/src/renderer/src/lib/user-error.ts
  • apps/desktop/src/shared/backend-rpc-contract.test.ts
  • apps/desktop/src/shared/backend-rpc-contract.ts
  • apps/desktop/src/shared/backend.ts
  • apps/desktop/src/shared/banner-assets.test.ts
  • apps/desktop/src/shared/banner-assets.ts
  • apps/desktop/src/shared/banner-commands.test.ts
  • apps/desktop/src/shared/banner-commands.ts
  • apps/desktop/src/shared/banner-geometry.test.ts
  • apps/desktop/src/shared/banner-geometry.ts
  • apps/desktop/src/shared/electron-ipc-contract.test.ts
  • apps/desktop/src/shared/electron-ipc-contract.ts
  • apps/desktop/src/shared/native-preview-scene-authority.ts
  • apps/desktop/src/shared/protocol-contract-fixtures.test.ts
  • apps/desktop/src/shared/renderer-security-policy.ts
  • apps/desktop/src/shared/session-markers.test.ts
  • apps/desktop/src/shared/session-markers.ts
  • apps/streamdeck-plugin/bin/plugin.js
  • apps/streamdeck-plugin/bin/videorc-client.js
  • apps/streamdeck-plugin/manifest.json
  • apps/streamdeck-plugin/package.json
  • apps/streamdeck-plugin/src/plugin.ts
  • apps/streamdeck-plugin/src/videorc-client.test.ts
  • apps/streamdeck-plugin/src/videorc-client.ts
  • apps/streamdeck-plugin/ui/property-inspector.html
  • crates/videorc-backend/remote_web/app.js
  • crates/videorc-backend/remote_web/index.html
  • crates/videorc-backend/src/banner_prepare.rs
  • crates/videorc-backend/src/banner_timers.rs
  • crates/videorc-backend/src/banners.rs
  • crates/videorc-backend/src/cohost.rs
  • crates/videorc-backend/src/cohost_command.rs
  • crates/videorc-backend/src/compositor.rs
  • crates/videorc-backend/src/compositor_scene_switch_tests.rs
  • crates/videorc-backend/src/main.rs
  • crates/videorc-backend/src/metal_compositor.rs
  • crates/videorc-backend/src/overlay_layout.rs
  • crates/videorc-backend/src/protocol.rs
  • crates/videorc-backend/src/recording.rs
  • crates/videorc-backend/src/remote_control.rs
  • crates/videorc-backend/src/state.rs
  • crates/videorc-backend/src/storage.rs
  • crates/videorc-backend/src/windows_d3d11_session.rs
  • docs/banners.md
  • docs/buddy-commands-contract.md
  • docs/overlays.md
  • docs/remote-control.md
  • package.json
  • plans/186-banners-on-the-picture.md
  • plans/README.md
  • protocol-fixtures/banner-geometry.json
  • protocol-fixtures/high-risk-contracts.json
  • scripts/lib/recording-studio-gates.mjs
  • scripts/lib/recording-studio-gates.test.mjs
  • scripts/smoke-banners-app.mjs
+---------------------------------------------------------------+
| You're one `console.log` away from enlightenment. Keep going. |
+---------------------------------------------------------------+
  \
   \   (\__/)
       (•ㅅ•)
       /   づ
✨ Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

TheOrcDev and others added 4 commits October 21, 2026 01:24
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>
@TheOrcDev

Copy link
Copy Markdown
Owner Author

Verification (head 30b5c041)

Local, macOS

  • cargo test -p videorc-backend: 3,553 passed, 15 ignored. cargo fmt --check --all and cargo clippy -p videorc-backend -- -D warnings are clean. The release build passes (CARGO_PROFILE_RELEASE_STRIP=false).
  • pnpm typecheck, pnpm lint, pnpm format:check: pass.
  • Desktop tests: 486 files, 5,036 passed, 1 skipped. pnpm test:scripts: 2,419 passed.
  • Renderer budget: eager JS 389,082 gzip against the 390,000 ceiling, which was not raised.
  • pnpm smoke:banners: PASS in recording, streaming and both modes, 5 artifacts. I checked the horizontal and vertical contact sheets by eye.
  • pnpm smoke:recording-studio: steps 1 to 29 passed. The bundle stopped at probe:preview-lifecycle ("Main window is not ready", cycle 28). Every remaining step then passed on its own.
  • probe:preview-lifecycle A/B against a base worktree at dacbec26: the branch failed twice right after heavy smoke runs, then passed 100/100 three times in a row. The base passed every run. This matches the known host-load flake. It is recorded in the plan, not waived.
  • smoke:remote-control, smoke:remote-lan, smoke:captions-dual-orientation, smoke:captions-contract: PASS.
  • The owner tried the dev build on 2026-10-21: "it was pretty good".

CI

  • Linux gates, JS, and the Windows installer: pass.
  • Windows source gates: Clippy, the stability passes, Test (3 stability passes), desktop and Node tests all pass. The first run failed 14 banner tests, each only at its final temp-dir cleanup: Windows won't delete a folder while the test's SQLite file is open. Fixed in 30b5c041 with the best-effort cleanup the noise_cleanup tests already use.
  • Inherited red, same on main: "Rust advisory audit" on macOS and Windows. RUSTSEC-2026-0381 (webpki-roots) and RUSTSEC-2026-0382 (rustls-webpki) were published after main last passed, alongside unmaintained warnings for zbus_names/zvariant. This PR doesn't touch Cargo.lock. On macOS the audit runs first, so that job skipped its fmt, clippy and test steps; they ran locally and on Linux and Windows CI. A dependency PR follows.
  • Inherited red, same on main (dacbec26): "Windows startup incident diagnostics" fails the controlled-audio and FFmpeg-tone matrices (A/V tail over 100 ms). The FFmpeg-tone control never runs Videorc code.

Still owed: a by-eye check in both light and dark, a packaged-app take, and a real Windows session.

@TheOrcDev
TheOrcDev marked this pull request as ready for review October 21, 2026 08:50
@TheOrcDev
TheOrcDev merged commit 3894ab8 into main Oct 21, 2026
3 of 7 checks passed
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant