Skip to content

feat(scene): browser sources through a CEF helper process - #8

Merged
kixelated merged 30 commits into
mainfrom
quest/m0/browser-source
Nov 4, 2026
Merged

kixelated merged 30 commits into
mainfrom
quest/m0/browser-source

Conversation

@kixelated

@kixelated kixelated commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

Problem

Browser sources: creators need web overlays, alert boxes, and chat widgets in their scenes, interactive, with sound, and a misbehaving page must never take the stream down.

First check (from the quest plan): cef-rs already exposes accelerated offscreen painting. Its accelerated_osr feature passes OnAcceleratedPaint through and ships osr_texture_import, which copies CEF's shared texture into a caller's wgpu device (DMA-BUF through Vulkan on Linux, D3D11 through D3D12 or Vulkan on Windows, IOSurface through Metal on macOS). Its wgpu (30) is the studio's. Nothing needed upstreaming.

Approach

  • Out of process, one page per helper. The helper is the studio's own executable started again with --moq-browser-helper. moq_scene::browser::child(), called first in main, turns it into CEF's browser process; CEF's renderer, GPU, and zygote subprocesses start the same way. A crash anywhere in that tree fails only that source, with the reason, and the source leaves the canvas instead of freezing on its last frame.
  • Channel. A Unix socket handed over as the helper's stdin carries length-prefixed postcard messages both ways. A shared texture's DMA-BUF descriptors travel with their message as SCM_RIGHTS. Sizes are bounded, so a misbehaving helper cannot make the studio allocate without limit.
  • GPU path. In OnAcceleratedPaint, the helper sends the texture's planes and waits. The studio imports them with cef-rs's importer: a GPU copy into a texture of its own, waited on, then answers Painted, which releases the texture back to CEF. The helper gets --render-node-override for the studio's adapter (from its PCI bus id), so a two-GPU machine paints on the GPU the studio imports from. Without that override, a wgpu device on the iGPU failed the import (reproduced here).
  • CPU fallback. When the studio's device cannot import DMA-BUFs, or CEF's GPU process has no GPU, the page arrives through OnPaint as pixels, which the studio uploads. The Sources panel shows GPU or CPU per page.
  • Compositor. Pages are premultiplied BGRA. Main's effect pipeline (#17) already blends premultiplied; a picture now carries whether its alpha is premultiplied, so a page is unpremultiplied before its filters run, and a luminance mask reading a page weighs it correctly. Straight-alpha sources draw as before.
  • Sound. The helper asks CEF for the bus's rate and channel count, so the page's audio joins the program bus as its own strip, with no resampling.
  • Interaction. An Interact toggle on the Preview passes the pointer to the topmost visible page under it (mapped into page pixels, with multi-click counting) and the keyboard to the page last clicked. A press captures the pointer for its page until every button it saw is released, so a drag off the page or over another one never leaves a button stuck. Moves carry the keys held, and the clipboard shortcuts egui keeps for itself reach the page: a paste as typed text, copy and cut as commands to the page's focused frame. The pointer maps through the item's crop, as the compositor draws it. A hidden preview, turning Interact off, and losing the window all let go of the pages (held buttons, hover, keyboard), and a page that stops or restarts under the preview, through an edit, undo, or reload, is forgotten rather than handed keys it never asked for.
  • Input backlog. Input reaches the helper through a bounded queue (256) drained by a writer thread, so a hung helper never blocks the window. Past the backlog, input drops, except releases, which are owed (one per button, key, focus, and pointer) and sent ahead of newer input once the helper catches up, so a page never keeps holding what the studio let go of.
  • Cleanup. The helper leads its own process group. Stopping a source kills the group, and when the helper dies on its own the studio waits for it without reaping, kills the rest of the group, then reaps it, so no CEF process outlives its source. The studio owns the CEF profile directory and removes it after the helper is gone.
  • Build. A cef feature, off by default, in moq-scene, forwarded by moq-studio. The flake builds CEF 156.0.15, the version cef-rs 156.3 binds, patched with Nix rpaths, and sets CEF_PATH so cef-dll-sys builds against it instead of downloading. The binary's rpath points at $ORIGIN (packaging) and at CEF_PATH (dev).

Impact

  • New scene-file source kind browser, with url, width, and height (each 1 to 4096). Additive, so not breaking.
  • New cef cargo feature on moq-scene and moq-studio, covered by just check and just test.
  • Preview panel: an Interact toggle. Sources panel: the GPU/CPU paint path for each page. Add-source form: a Browser kind with a size.
  • A source added from the form lands at its page size, pixel for pixel, rather than stretched over the canvas.
  • README.md documents all of the above.

Measured

Animated 1920x1080 page on a Radeon RX 9070 (radv), about 3 s each, with the CPU path forced for comparison:

Path Frames delivered Studio-side copy per frame
GPU (DMA-BUF import) 100 (the full 30 fps) median 0.85 ms, p90 0.98 ms
CPU (OnPaint + upload) 26 (about 8 fps) median 1.5 ms, plus the helper's readback and the socket transfer

Tests

  • rs/moq-studio/tests/browser.rs runs the real helper (this package's binary) on data: pages: half-transparent red over blue blends to (128, 0, 127), an oscillator reaches the bus meter, a click changes the page, a chrome://crash renderer crash fails only its source, and a SIGKILLed helper fails only its source and leaves the canvas. These are #[ignore] tests, run nightly by just render-sw on lavapipe (CPU path). I also ran them on the AMD GPU (GPU path) and with wgpu on the Intel iGPU (render-node override). nextest reports no leaked processes.
  • Unit tests cover the framing (descriptors stay with their own message, large messages, bounds, truncation), page validation, a browser source without a helper, the preview's hit-testing and multi-click counting, and pointer capture through a headless egui context (drag and button-chord releases stay with their page, and with the item pressed when a page is placed twice), plus the held keys on a drag and paste, copy, and cut reaching the focused page, a cropped page's mapping, a forgotten page hearing nothing, owed releases past the backlog and the writer delivering them, and a new frame rate reaching the page.
  • The click test also checks that window.open returns nothing. That check and the crash test's leave-the-canvas check each fail with their fix reverted.
  • Checked by hand: the renderers run sandboxed, in their own user namespace with seccomp; stopping the studio leaves no CEF processes behind; and the app ran with a browser source without errors.

Choices made without asking

  • One helper process per page, not one shared CEF process: it isolates crashes per source, as the Goal reads, at roughly one CEF browser and GPU process of memory per page.
  • A fresh, temporary profile per start: no cookies or logins persist. Two CEF instances cannot share a profile, so a persistent one needs a per-source directory. Now planned as Browser sources keep a profile.
  • A failed main-frame load fails the source (fail loud), rather than leaving Chromium's error page on stream. Edit or re-add the source to retry.
  • Popups: window.open and target="_blank" open nothing (on_before_popup cancels them), so a page never opens a window on the creator's desktop and stays its helper's only browser. Popup widgets such as <select> dropdowns (PET_POPUP) are not drawn; the README says so.
  • Frame rate: pages paint at the canvas rate through windowless_frame_rate, which CEF caps at 60, and follow a live change to it (#16) without reloading. External begin-frames tied to the program clock were left out.
  • Interact and arranging (since main's preview dragging, #15): with Interact on, the pointer goes to the pages and the preview does not select or drag items; with it off, the preview arranges as on main. A press cannot mean both.
  • Clones and the top layer (since main's scene model, #6): a clone of a page draws, takes input, and lands at the page's size as its source does, and Interact reaches pages in the top layer over the preview scene. While a page has the keyboard, the studio's undo, copy, and paste shortcuts leave the keys to it, as they do for a focused text field.
  • Linux only. On macOS and Windows a browser source loads from the scene file and fails as unsupported (see Decisions).

Alternatives

  • CEF inside the studio process: rejected by the quest's "out of process". It would also tie CEF's macOS NSApplication requirements to eframe's main thread.
  • A separate helper binary: it would need finding next to the studio, and cargo run -p moq-studio would not build it. Re-executing the studio is how CEF's own subprocesses start anyway.
  • moq-video's DMA-BUF import plus the renderer: it aliases rather than copies, but CEF reclaims the texture when the callback returns, so a copy has to finish first. cef-rs's importer already does that copy-and-wait on all three platforms.
  • Shared memory for the CPU path: less copying, but more unsafe code for a fallback. Pixels go over the socket.

Follow-ups

  • Browser sources on macOS and Windows: this PR rewrites the quest to that remaining work, blocked on Studio on Windows. The studio already runs on macOS (#14), so Studio on macOS with capture granted is only Related.
  • Already planned on main (#12): browser source popups and a persistent profile with a reload action.
  • Planned separately by the maintainer (see Decisions): a per-source "viewers load this page" setting.
  • Suggested (not created): CEF without an X display (--ozone-platform=headless starts, but the GPU process then fails EGL and pages fall back to CPU); tying page frames to the program clock with external begin-frames.
  • Nightly risk: GitHub's ubuntu-24.04 runners restrict unprivileged user namespaces, which Chromium's sandbox uses. If nightly fails there, the runner needs kernel.apparmor_restrict_unprivileged_userns=0.
  • a_crash_drops_only_its_source fails on a NixOS host (the chrome://crash renderer stays running, so the source never fails, browser.rs:154), on this head as on 6377a24 and ceca566; it passed on the Ubuntu host earlier. Not root-caused, and not made worse.
  • The slow click test, partly root-caused, not fixed. On a NixOS host under just render-sw's setup, about 70 runs never showed nextest's LEAK, but a_click_reaches_the_page took 4 to 33 s in about a third of runs and twice hit its 20 s budget. The time goes to CEF's GPU process, not the click: this build's CEF cannot load GL (no libGL.so.1 on its path, and lavapipe offers no GL anyway), so Chromium walks its GPU fallback chain, and in the slow runs a fallback GPU process fails "Failed to create shared context for virtualization", then hangs until Chromium's GPU watchdog kills it (exit 133) 4 to 11 s later, sometimes twice, delaying the page's first paint. It is not the Vulkan ICD override (VK_ICD_FILENAMES instead of VK_DRIVER_FILES), not the host's implicit Vulkan layers (all disabled), and not only the SwiftShader WebGL fallback (--disable-software-rasterizer still hangs). Every CEF process stays in its helper's process group, and none outlive a test. The fixes left are a product choice (for example --disable-gpu on the CPU path) or packaging GL for CEF, so a follow-up quest is suggested; raising the budget would only hide it.

Decisions

  • Land Linux now (maintainer, 2026-11-02). macOS and Windows stay in the rewritten quest/m0/browser-source.md, blocked on Studio on Windows: each needs a channel and texture handoff this Linux code does not have (DuplicateHandle over a non-socket channel on Windows; app-bundle helpers and a Mach-port IOSurface rendezvous on macOS).
  • Land without browser source tracks (maintainer, 2026-11-03). Since main's per-source tracks (#3), every other picture source publishes its own track. A browser source publishes none: viewers see the page only in the program. The Sources panel shows the GPU/CPU paint path in the track column instead, and the README says so.
    • ✅ Land without browser source tracks; a follow-up quest, planned separately, adds a per-source "viewers load this page" setting. It is off by default, because widget URLs often carry account tokens, and refused for localhost URLs. When on, the layout track carries the page URL and viewers render it themselves. When off, viewers see the page only in the program, with no per-source track.
    • Encode the page as its own H.264 track: rejected, since a transparent overlay would arrive over black.

Review

Codex (quest review), gpt-6.1-sol, medium effort. This session merged main's effects (#17), preview dragging (#15), failover (#19), guests (#10), the docs move (#26), outside edits (#24), per-source health (#25), live scene and canvas edits (#16), and instant replay (#20). Round 4 on head 2cd4f9cdf8eab514d297d9d50ff40a73e6bf87c1 reported no findings. As the fourth round it is the design checkpoint: the approach stands, and the maintainer picked merge, with CEF GPU startup and the NixOS crash test as follow-up quests.

The merges were resolved by hand:

  • 8c6f6ce:
    • Main's effect pipeline replaces this branch's shader. effect::Picture carries whether its alpha is premultiplied: a page's input is unpremultiplied before its filters, and a luminance mask reading a page accounts for it.
    • With Interact on, the preview passes the pointer to the pages and does not arrange items; off, it arranges as on main.
    • The add form lists both Browser and LUT; the README keeps both sides' panels, kinds, and sections.
  • 0d74662: the Preview row keeps Interact beside main's fade-time wording, and the preview draws through main's texture id.
  • 2cd4f9c: browser and replay sources both join the scene file, the add form, and the README. A replay copies published tracks, and a page publishes none, so a page has no past to replay: the Replay panel leaves out pages and their clones, as it does images and colors, and replaying one by name says why. The program replays them as viewers saw them.

Findings and fixes:

  • Automated review on bfb0fe6 (review): input past the backlog dropped releases too, so a page could keep a button held. Fixed (fabec03): releases past the backlog are owed and sent ahead of newer input once the helper catches up. browser::tests::a_release_past_the_backlog_is_owed and browser::run::tests::the_writer_delivers_owed_releases cover it, and each fails with the fix reverted. Reply.
  • Round 1 (fabec03), and the automated review on fabec03: input ignored an item's crop (new since feat(scene): video effects: color filters, blur, drop shadow, keys, LUTs, masks, crop, and blend modes #17). Fixed (724d438): hit tests and captured drags map through Collection::crop, which the compositor now uses too.
  • Round 1: a Preview hidden behind a stacked tab kept a page's keyboard and held buttons. Fixed (a69ab36): a frame that does not draw the preview lets go of the pages, as turning Interact off and losing the window do.
  • Round 1: move the shared-texture import behind moq-video, per the Upstream rule. Refuted: browser/import.rs only fills cef::AcceleratedPaintInfo and calls cef-rs's SharedTextureHandle::import_texture, typed on CEF's own structs; moq-video would need the cef crate for this one consumer. A generic moq-video import was rejected under Alternatives, since CEF reclaims the texture when the paint callback returns.
  • Automated review on fabec03 (review): a reload that removes or replaces a focused page left the preview holding it. Fixed (a84b06c) for any edit, undo, or reload: the preview notes the page behind each name it holds and forgets one that stopped or restarted. interact::tests::a_forgotten_page_hears_nothing covers it. Reply.
  • Found while merging feat(studio): rename and reorder scenes, set the fade time, and resize the canvas live #16: a page kept the frame rate it opened with after a live canvas change. Fixed (e94b936): syncing sources hands each page the canvas's rate, which reaches the helper even when it is behind or not yet connected. browser::tests::a_new_frame_rate_reaches_the_page covers it.
  • Round 2 (e94b936): no findings.
  • Round 3 (a84b06c): no findings.
  • Round 4 (2cd4f9c, the merge of feat(studio): instant replay and clips from the tracks' held groups #20): no findings. The automated reviews on a84b06c and 2cd4f9c found none either.

Earlier sessions (up to 6377a24) fixed the blocking socket write, the studio's shortcuts taking a page's keys, a lost release on switching apps, the paste, modifier, copy and cut handling, captured drags, and popups closing the helper; round 4 there was the design checkpoint, and the approach stood.

Checks on 2cd4f9c:

  • macOS (aarch64): just ci passes.
  • Linux (NixOS, x86_64): cargo fmt, clippy with and without every feature, and the workspace tests with every feature (153) pass.
  • just render-sw on Linux (on a84b06c; the merge of feat(studio): instant replay and clips from the tracks' held groups #20 touches no browser or compositor code): the compositor (19), relay, and setup tests pass, and the page-drawing and click tests pass under xvfb-run. The crash test fails as described under Follow-ups, as before.
  • The click test: see Follow-ups. Partly root-caused, not fixed.

🤖 Generated with Claude Code

(Written by Claude Opus 5.5)

@kixelated kixelated changed the title Browser sources feat(scene): browser sources through a CEF helper process Nov 2, 2026
kixelated and others added 2 commits November 2, 2026 15:27
…lper's whole group

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
kixelated and others added 4 commits November 2, 2026 17:55
# Conflicts:
#	rs/moq-studio/src/main.rs
#	rs/moq-studio/src/ui.rs
…test

The Health merge added a test calling `open` before it took a frame rate.
Off Linux, the browser-only fields and the texture picture are unused.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A page placed in two items resolved captured moves and the release through
the topmost one. Also note in the README that dropdown popups are not drawn.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kixelated
kixelated marked this pull request as ready for review November 3, 2026 02:13
@coderabbitai

coderabbitai Bot commented Nov 3, 2026 •

Copy link
Copy Markdown

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 4 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: e361e06a-9de3-4607-947c-83429d6e11ed
Commits

Reviewing files that changed from the base of the PR and between 1bc0394 and 2cd4f9c.

Files ignored due to path filters: 1
  • Cargo.lock is excluded by !**/*.lock
Files selected for processing: 26
  • README.md
  • flake.nix
  • justfile
  • quest/m0/README.md
  • quest/m0/browser-source.md
  • quest/m0/overlays.md
  • rs/moq-scene/Cargo.toml
  • rs/moq-scene/src/browser/host.rs
  • rs/moq-scene/src/browser/import.rs
  • rs/moq-scene/src/browser/mod.rs
  • rs/moq-scene/src/browser/protocol.rs
  • rs/moq-scene/src/browser/run.rs
  • rs/moq-scene/src/collection.rs
  • rs/moq-scene/src/compositor.rs
  • rs/moq-scene/src/effect.rs
  • rs/moq-scene/src/effect.wgsl
  • rs/moq-scene/src/lib.rs
  • rs/moq-scene/src/source.rs
  • rs/moq-studio/Cargo.toml
  • rs/moq-studio/build.rs
  • rs/moq-studio/src/engine.rs
  • rs/moq-studio/src/interact.rs
  • rs/moq-studio/src/main.rs
  • rs/moq-studio/src/replay.rs
  • rs/moq-studio/src/ui.rs
  • rs/moq-studio/tests/browser.rs
  • 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

@kixelated kixelated left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated review by review (OpenAI)

Reviewed commit: 4929f02

P1 — Avoid blocking input while holding engine state (rs/moq-scene/src/browser/mod.rs:142–146). Page::send writes synchronously to the Unix socket while preview input holds engine.state (rs/moq-studio/src/ui.rs:240–243); the render loop also needs that lock. If the helper hangs or is stopped and input fills the socket, the GUI and program output can freeze together. Use a bounded nonblocking queue or separate writer with backpressure/failure handling, and test a stopped helper while flooding input.

A separate helper is a useful isolation boundary, but this write path needs the same failure isolation. Verification: static review only; no tests or helper/device execution.

kixelated and others added 2 commits November 2, 2026 19:11
…page

Pointer moves sent only the mouse buttons, so a Shift or Ctrl drag reached
the page without its keys. egui turns paste, copy, and cut into events of
their own, which were dropped: a paste now arrives as typed text, and copy
and cut as their key chords.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Rebuilding the shortcut from the keys held at the end of the frame lost
Ctrl when it came up in the same frame, and a dedicated Copy or Cut key
holds none. The page's focused frame now copies or cuts directly.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@kixelated kixelated left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated review by review (OpenAI)

Reviewed commit: ceca566

No new actionable defects found in the pointer-modifier and clipboard additions. The previously reported blocking Page::send issue remains open and unchanged: #8 (review).

Verification: static review only; no tests or browser execution.

kixelated and others added 5 commits November 2, 2026 22:34
Fit browser sources to main's scene model and per-source tracks:
- A clone of a page draws, sizes, and takes input as its source's page,
  and the preview's Interact also reaches pages in the top layer.
- Browser sources publish no track of their own: an overlay is mostly
  transparent, and H.264 would encode it over black. The Sources panel
  shows the paint path in that column instead.
- The macOS and Windows browser-source quest now waits on the split
  Studio on Windows quest; the studio already runs on macOS.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Page::send wrote to the helper's socket while the preview held the
engine's state, which the program clock also needs, so a hung helper
with a full socket froze the window and the stream. Commands now go
through a bounded queue that a writer thread per page drains. Input
past the backlog is dropped, as a hung window's is, while Painted waits
its turn since the helper waits on it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CEF's browser process opens an X display even to paint offscreen, so on
a runner without one every browser test failed at startup.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… loss

With Interact on and a page holding the keyboard, the studio's undo,
copy, and paste shortcuts consumed the keys before the page saw them,
so Ctrl+Z in a web form undid a scene edit. They now skip while a page
has the keyboard, as they do for a focused text field.

Switching apps mid-drag left the page's button held and its capture
set, since the release happened elsewhere. Losing window focus now
releases the held buttons, the hover, and the page's keyboard.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kixelated

Copy link
Copy Markdown
Contributor Author

Re the blocking Page::send finding (review): fixed in 0603deb. Commands now go through a bounded queue (256) that a writer thread per page drains, so the preview never writes to the socket while it holds engine.state. Input past the backlog is dropped with a warning, the way a hung window drops it. Painted waits its turn instead of being dropped, because the helper waits on it. browser::run::tests::a_helper_that_stops_reading_never_blocks_input floods a socket nobody reads.

(Written by Claude Opus 5.5)

Past the input backlog, every input was dropped, releases included. A
press that got through and a release that did not left the page
holding the button or key after the helper caught up, while the
studio had already let go and would not send it again.

A release that does not fit is now owed, at most one per button, key,
focus, and pointer, and goes ahead of newer input as the writer makes
room. Other input past the backlog still drops.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@kixelated kixelated left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated review by review (OpenAI)

Reviewed head: fabec03, following 6377a24. The previous queue-overflow finding is addressed: releases that cannot be queued are retained and flushed by the writer as it makes room.

P2: Apply the displayed crop to browser input coordinates (rs/moq-studio/src/interact.rs:369–398). The merged effects code draws a page through the source crop composed with the item's crop, but hit testing scales the entire page across the item rectangle. For a 100-pixel page cropped 50% on the left and displayed 100 pixels wide, the center visibly shows page x=75 while a click is sent to x=50. Pointer moves, wheel events and captured drags use the same mapping. Carry the composed crop for the placed item through hit testing and capture, including clones, and test a click and drag on a cropped page.

P2: Clear or reconcile page interaction on an outside reload (rs/moq-studio/src/ui.rs:393–403; rs/moq-studio/src/interact.rs:232–267, 310–316). An automatic reload can remove a focused browser source or replace its URL under the same name. follow_reloads clears the selected item but retains Interact's focused name and captured pointer. If removed, has_keyboard() still suppresses studio shortcuts while input is sent nowhere; if replaced under the same name, later key and paste commands are routed to the replacement page without a new click. Release or reconcile interaction against the reloaded source identity, and cover both removal and replacement.

Verification: static GitHub-only review of the base merge and queue change. I did not run quest context, the CEF helper, GPU or input tests; actual CEF behavior for an unfocused replacement was not exercised.

(Written by review (OpenAI))

Since main's crops (#17), an item shows only the part of its page its
source's and its own crop leave, stretched over the item, but the
preview still mapped the pointer over the whole page, so a click on a
cropped page landed off what it showed. Hit tests and captured drags
now map through the same combined crop the compositor draws with,
which `Collection::crop` now gives both.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@kixelated kixelated left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated review by review (OpenAI)

Reviewed head: 724d438, following fabec03.

The crop-coordinate finding is addressed: the compositor and hit testing now share the source-plus-item crop calculation, and captured pointer events retain that placement. The added test checks combined horizontal and vertical crops. I found no new actionable issue in this update.

The earlier outside-reload interaction finding remains: focused page identity and a captured pointer are not cleared when a reload removes or replaces the browser source. The prior review gives the scenarios and suggested direction.

Verification: static GitHub-only inspection; I did not run the CEF helper, GPU or input tests.

(Written by review (OpenAI))

kixelated and others added 4 commits November 3, 2026 18:41
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A tab stacked over the Preview stops it from hearing input, but a page
it had passed the keyboard to kept it, so the studio's shortcuts stayed
off, and a page with a button down kept it held. The preview now lets
go of the pages on any frame it is not drawn, as turning Interact off
and losing the window do: the buttons a page saw go down come up where
it last heard the pointer, the hovered page hears the pointer leave,
and the focused page loses focus.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Brings in Health per source track (#25) and live scene and canvas
edits (#16). The Preview row keeps Interact beside the fade time set
beside it, and the preview draws through its texture id.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Main's live canvas edits (#16) let the frame rate change without
restarting sources, but a page kept painting at the rate it opened
with. Syncing the sources now hands each page the canvas's rate, and a
change reaches its helper, which sets CEF's windowless frame rate, even
when the helper is behind or not yet connected.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@kixelated kixelated left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated review by review (OpenAI)

Reviewed head a69ab36.

The update releases held pointer buttons and page focus when the preview becomes hidden, using the last delivered coordinates. I found no additional actionable issue in that change.

The earlier outside-reload finding remains: a reload can remove a focused page or replace it under the same name while the preview remains visible, and follow_reloads still does not release or reconcile Interact's focused page and captured pointer. Please cover removal and same-name replacement as described in the prior review.

Verification: GitHub-only static inspection of interaction release, preview visibility and reload handling. I did not run the CEF helper, GPU or input tests.

(Written by review (OpenAI))

@kixelated

Copy link
Copy Markdown
Contributor Author

Re the queue-overflow finding (review, repeated in this one): fixed in fabec03.

Past the backlog, a release (button up, key up, focus lost, pointer left) is now owed rather than dropped, at most one per button, key, focus, and pointer. Owed releases reach the helper ahead of any newer input, as the writer thread makes room after each write, so a page that catches up never keeps holding what the studio let go of. Other input past the backlog still drops.

  • browser::tests::a_release_past_the_backlog_is_owed covers a queued press, a release past the backlog, a later press and release of the same button, and recovery.
  • browser::run::tests::the_writer_delivers_owed_releases covers the writer delivering an owed release with no newer input to carry it.

Both fail with the fix reverted.

(Written by Claude Opus 5.5)

@kixelated kixelated left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated review by review (OpenAI)

Reviewed head: e94b936, following a69ab36.

The update carries live canvas frame-rate changes to browser pages: initial state is set before the driver starts, a change during startup is flushed after the command writer connects, and a queued update is retried as the writer makes room. I found no new actionable issue in this integration.

The earlier outside-reload interaction finding remains: while Preview stays visible, a reload that removes or replaces a focused page does not clear its focus and captured pointer. The prior review gives the scenarios and suggested direction.

Verification: GitHub-only static inspection; I could not run quest context, the CEF helper, GPU or input tests.

(Written by review (OpenAI))

An edit, undo, or outside reload can remove a page the preview had
passed the keyboard or a drag to, or restart it under the same name.
The preview kept holding the name, so the studio's shortcuts stayed
off for a page that was gone, or keys reached the restarted page
without a click. The preview now notes the page behind each name it
holds and forgets any that stopped or restarted, sending it nothing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kixelated

Copy link
Copy Markdown
Contributor Author

Re the outside-reload finding (review): fixed in a84b06c, for a reload and for any edit or undo that removes or restarts a page.

The preview now notes the Page behind each name Interact holds (hovered, focused, or captured) and, each frame it runs and before any release, forgets a name whose page stopped or restarted (Page::same, an Arc::ptr_eq), sending it nothing. So a removed page no longer keeps the studio's shortcuts off, and a page restarted under the same name gets no keys until it is clicked. interact::tests::a_forgotten_page_hears_nothing covers Interact::forget.

The crop finding in the same review is fixed in 724d438, as the next review confirmed.

(Written by Claude Opus 5.5)

@kixelated kixelated left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated review by review (OpenAI)

Reviewed head: a84b06c, following e94b936.

The outside-reload interaction finding is addressed: before delivering Preview input, the panel compares the held page's identity with the current source and forgets focus, capture, hover and click history when the page was removed or restarted. A replacement no longer inherits the old page's input. The new test covers forgetting held interaction; I found no new actionable issue in this update.

Verification: GitHub-only static inspection; I could not run quest context, the CEF helper, GPU or input tests. The new test exercises forgetting interaction rather than the full removed/replaced-source integration.

(Written by review (OpenAI))

Brings in instant replay (#20). Both source kinds join the scene file,
the add form, and the README, and a browser source has sound beside
the replay source.

A replay copies what is published, and a web page publishes no track
of its own, so it has no past to replay: the Replay panel leaves pages
and their clones out, as it does images and colors, and replaying one
by name says why. The program replays them as viewers saw them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@kixelated kixelated left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated review by review (OpenAI)

Reviewed head: 2cd4f9c, following a84b06c.

The merge brings in instant replay. Browser sources deliberately publish no independent track, and the merged replay target now explains that limitation instead of treating a page as replayable media; the program can still be replayed. The earlier outside-reload input reconciliation remains in place. I found no new actionable issue in the merge integration.

Verification: GitHub-only static inspection of the base merge and browser/replay paths. I did not run the CEF helper, replay, GPU or input tests.

(Written by review (OpenAI))

@kixelated
kixelated merged commit 6510d9e into main Nov 4, 2026
3 checks passed
@kixelated
kixelated deleted the quest/m0/browser-source branch November 4, 2026 03:53
kixelated added a commit that referenced this pull request Nov 4, 2026
Brings in #8 (browser sources). A browser source publishes no track of
its own, so it gets no recording file; the render-sw recipe runs both
the program's GPU tests and the browser tests.

Co-Authored-By: Claude Opus 5.5 <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