Skip to content

Picture-in-picture and media tray - #9

Merged
siyabendoezdemir merged 35 commits into
mainfrom
lane/media
Oct 10, 2026
Merged

siyabendoezdemir merged 35 commits into
mainfrom
lane/media

Conversation

@siyabendoezdemir

@siyabendoezdemir siyabendoezdemir commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

Picture-in-picture and the media tray, built from spec/media.md (MD) and spec/feel.md FL-32. main (with split view, PR #5) is merged in.

Built

  • Done: MD-01, MD-03, MD-04, MD-07, MD-09 (no artwork yet), MD-10, MD-12, FL-32.
  • Partial:
    • MD-02: while the tab stays selected, the engine's own picture-in-picture window shows the video, because Orbit's player needs the tab's view. Orbit's player takes over when you leave the tab.
    • MD-05: hardware media keys can't be tested under Xvfb.
    • MD-06: the player doesn't yet move aside for Orbit's alerts and dialogs.
    • MD-08: the site-controls checkbox (SB-04) and the Settings switch (ST-10) belong to other lanes; the commands they need are in place.
    • MD-11: muting one pane from the split row's menu works and is tested here. There's no screen-share indicator, and no switcher indicators, because TB-05 has no overlay yet.
    • MD-13: no document picture-in-picture, leave-call or chat toggle.
    • MD-14: a call is only detected when the page shows its own camera.
    • MD-16: no haptic tick on macOS, and playback smoothness isn't measured.
    • KS-11: the media key is untested; the calendar shortcut belongs to TB-21/FV-09.
    • SB-10: the download and update trays belong to other lanes.
  • Not started: MD-15 (P2).

Decisions where the spec was open

  • Back to the tab: the tab is selected at once, then a still of the last frame flies from the player back to the video's place in the page. With reduced motion, or a hidden source window, the player just closes.
  • Back to the player: it grows out of the sidebar controller, and the controller makes way.
  • Engine handover: leaving a tab right after asking for picture-in-picture hands the video over at once, from the tab's state rather than waiting for the page's report.
  • Controls: if the pointer isn't over the player when it lands, its controls hide.
  • Input: empty wheel events are ignored, and losing pointer capture ends a drag. Pinch zooms (MD-06) rather than resizes.
  • Late resize: leaving a split while its panes were still settling could leave the player showing the page's top-left corner. Fixed in agent.ts: before the video has moved into the player, a resize at another size no longer cancels the move.

Review first

  • close() and back() in src/main/media/player.ts (the still frame and the flight back).
  • The resize handling in src/main/media/agent.ts. It relies on the player always putting the video back when it closes, which it does today.
  • capturePage can return a blank image with hardware video overlays. That's detected, and the player then closes without the flight.
  • Window stacking, shape and shadow aren't checked with a real window manager or on macOS.

Tests

  • Unit: 249 passing (36 for this lane: media.test.ts 23, pip.test.ts 13).
  • e2e: 189 in total, 19 in media.spec.ts. In the last full local runs at load average 12–50, the failures were timing budgets that also fail on main's own build under that load (CB-22, SP-08). Media plus split together passed 54/54 on one worker.

Review, verification and lead fixes

  • Review (21:45 UTC): approve with three fixes (M1 reduced motion on the macOS runner, M2 frames claim, M3 page styles shrinking the floating video). The media agent fixed all three plus F2, F4, F7, F9 and F11, each with a test that fails without it (3d7bca9, 88c081a).
  • Verification (23:47 UTC): approve with one fix. V1: MD-04's elapsed-time check assumed the long-video fixture is 60 s; on the macOS runner it is 67 s.
  • Lead fixes (D18):
    • 369a98f (tests only):
      • V1: MD-04 compares the elapsed text with the video's position. It passes with the fixture at 60 and at 70 chunks.
      • MD-07 waits for Orbit's media report before leaving each tab (the test side of V-F2, which made it flaky in suite runs).
    • 2fbe461: merged main (PR Peek (link preview) and Mini Window #8, Peek and Mini Windows).
      • rpc.ts: both event sets.
      • state.ts: TabState keeps media and floating.
      • Feature status: the union of both sides, with no duplicate IDs; FEATURES.md regenerated.
  • Checks at 2fbe461: build, typecheck and lint exit 0; unit tests 289/289. The final check is below, on this PR's merged head.
  • Open follow-ups: V-F1 to V-F6 and F1, F3, F5, F6, F8, F10, listed in reviews/lane-media.md.

Merge with PRs #10 and #11, and the local check (merge gate, D42)

GitHub Actions is off for cost, so a local check is the merge gate. PRs #10 and #11 merged first (04:56 and 04:57 UTC), and this PR's head now includes them:

The check, on da6a207 (Linux under Xvfb, one worker):

npm run build; npm run typecheck; npm run lint; npx vitest run
xvfb-run -a -s "-screen 0 1440x900x24" ./node_modules/.bin/playwright test --workers 1 --retries 0
  • Build, typecheck and lint exit 0; unit 293/293.
  • E2E: 245 passed, 2 skipped, 0 failed (11.0 min). The 2 skipped are MD-01's and MD-09's test.fixme tests for media inside frames, which isn't built yet (their notes say so).
  • No test logged an uncaught exception, and no quit needed the kill.
  • macOS: not run, so unverified. That includes M1 (reduced motion on GitHub's macOS image) and V1 (the long-video fixture's length there).

🤖 Generated with Claude Code


Note

High Risk
Large cross-cutting feature (main window management, injected page scripts, tab view borrowing, and global media state) with complex PiP lifecycle; bugs could affect playback, focus, or quit behavior.

Overview
Adds Orbit’s media lane: automatic and manual picture-in-picture, a floating player that borrows the tab’s view (with enter/exit motion and sidebar tray), background media/call controllers, and camera/mic indicators on tab rows.

Main process installs an isolated page script (agent.ts) that long-polls media state, lifts video for PiP, and drives controls; MediaManager ties tab visibility to auto-PiP and tray membership; PipPlayer owns the always-on-top window, gestures, and “back to tab” flight. Renderer adds pip view (PipApp), MediaTray above the bottom bar, and hooks/commands/actions for PiP, site snooze settings, and hardware media keys.

Tab lifecycle now treats playing/PiP/camera/mic tabs as busy for sleep, auto-archive, and Clear (via isMediaBusy). Split/favorites enforces the 12-tile cap when separating, duplicating, or creating splits from Favorites (FV-02). Reliability: logs uncaught main exceptions instead of blocking quit; e2e defers __orbit evaluates; pane-strip timers clear on window close; CI pins Linux to ubuntu-24.04 for Xvfb deps. Feature checklist and persistence pick up PiP settings and runtime tab.media.

Reviewed by Cursor Bugbot for commit da6a207. Bugbot is set up for automated code reviews on this repo. Configure here.

siyabendoezdemir and others added 11 commits October 9, 2026 18:01
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sidebar: Spaces strip and undo toast from main, media tray kept just above
the bottom bar. feature-status.json: union of both sides; FEATURES.md
regenerated.

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

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nd, rounded window shape

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

- "Back to tab" and double-click select the tab at once (state first), then a still of the
  last frame flies from the player to the video's place in the page (MD-16); reduced motion
  or an unseen page: the player just closes.
- The tray's Picture in Picture button sends the controller's rect, so the player grows out
  of the controller, and the controller gives way at once (MD-10, MD-16).
- Leaving a tab right after asking for picture-in-picture hands the video to the player even
  before the page's report arrives (MD-02).
- The player's controls hide after the enter motion when the pointer isn't over it; empty
  wheel events and lost pointer capture no longer stop or strand a motion.
- Media e2e: frame budgets scale with FRAME_SLACK, sizes derive from the work area (macOS CI
  runner, 1024x768 checked), the fixture's own duration is read; unit tests for the player
  geometry, motion curves, window shape, clock and settings clean-up.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… move into the player

Leaving a split while its panes were still settling could let a resize at the pane's old size
reach the page after the player armed the lift: the video stayed in the page and the player
showed the page's corner. Until the lift shows, other sizes now leave it armed; the player still
drops it when it gives up or closes.

Tests: per-pane mute from the split row's menu (MD-11), a video in a split pane floats only when
the split is left and goes back into its pane (MD-01), and the late resize itself.

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

- docs/feature-status.json: MD-01 to MD-16, FL-32, KS-11 and SB-10 rewritten for the floating
  player Orbit draws itself (they still described the engine's native window).
- FEATURES.md regenerated.
- The tray test checks it sits just above the bottom bar and pushes the list up (SB-10); the
  keyboard test carries KS-11.

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

cursor Bot commented Oct 9, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: 01b43ccb-90cc-4d8c-8386-aa39fa51f983)

The bar opens from 97 % scale and 4 px up. That keeps it centred, so the
test's "centred" wait also passed mid-animation, and its top moved by
about 1.1 px before the second reading. It failed both attempts on PR #9's
Linux job. The test now waits for the bar's transform to come to rest
before each reading.

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

cursor Bot commented Oct 9, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: 1c230b72-33c2-4893-a48e-15c98251e3a4)

siyabendoezdemir added a commit that referenced this pull request Oct 9, 2026
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
siyabendoezdemir and others added 3 commits October 9, 2026 22:12
- A tab resting on a full, 4-pane split row no longer gets a half
  highlighted: the row reorders like any other, as the page drop already
  shows no indicator for a full split (SV-25, split review follow-up).
  New e2e test fails without the fix (the half is highlighted).
- SV-26 grow-in test: the frame sampler is installed before the pane is
  added, times frames from when the card enters the page, checks the
  spring's shape only on frames that came in time, and stops by itself.
  CI hung on it for 120 s and, on retry, saw a first sample at 94 %.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… !important rules)

M1. The main process now reads reduced motion the same way pages do.
- richMotion() returns !systemPreferences.getAnimationSettings().prefersReducedMotion.
  That value follows the OS setting and Chromium's --force-prefers-(no-)reduced-motion
  switches; shouldRenderRichAnimation ignores both switches.
- launchOrbit passes --force-prefers-no-reduced-motion. With { reducedMotion: true } it passes
  --force-prefers-reduced-motion instead. The main process then matches Playwright's page
  default on every runner, including GitHub's macOS image, which turns Reduce Motion on.
  The three MD-16 tests failed with a GTK profile that has animations off; they pass now.
- New FL-28 e2e, launched with reduced motion:
  - the player is in its corner from its first frame (lastEnter null);
  - a released player eases into its corner over 120 ms with no overshoot, instead of the
    spring (F2);
  - Back to tab ends the player at once: no flight, no still, no blank frame.

M2. MD-01 and MD-09 are now partial: media inside frames isn't seen yet. R1 (a video in a
frame floats) and R2 (x pauses audio in a frame) are test.fixme until frame support (F1).

M3. The lift rules now use video[data-orbit-pip]:not(#_):not(#_):not(#_). A page's own
!important size rule (#wrap video{width:100%!important;...}) no longer stretches the floating
video over the whole page area. The new e2e fails without this (982x800 instead of 320x180).

Follow-ups from the review:
- F11: closing the player while Back to tab takes its still now clears the still, so the next
  video never shows the old frame. The e2e fails without this.
- F7: tall videos are also at least 200 px wide, and the player is never taller than the
  work area.
- F4: the player's controls belong to the session's window, not to the first window, which
  may have closed. The e2e fails without this.
- F9: e2e for the right-click Picture in Picture item, on and off.
- Media fixtures moved to ports 3330-3349.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
FEATURES.md regenerated from the merged tests and feature status.

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

cursor Bot commented Oct 9, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: 64d294d0-64d6-4dfe-a452-b0b5ecf1957a)

siyabendoezdemir and others added 9 commits October 9, 2026 23:00
…02, lead review F1)

A Favorite split counts as one tile (D19). Separating it makes one tile per
pane, and moving a pane out makes two, so both could take a profile past
the 12-Favorite limit. Both are now refused with the "Favorites are full"
toast when they would; a split anywhere else is never limited. The toast
text is one constant, shared with the tabs code.

Unit test for the count, and an e2e test that fills Favorites, checks both
refusals leave everything as it was, then frees a tile and moves a pane
out.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Playwright runs main-process evaluates through the inspector, which can
run them in the middle of other main-process code, even inside a store
update. A tab a test created there was overwritten when that update
finished, so it vanished: about 1 run in 12 of the new Favorites test.
A stack trace placed the test's tabs.create inside immer's patch
generation for a favicon update.

launchOrbit now wraps app.evaluate so the test's function runs after a
setImmediate. The test hooks' run and action defer too, which covers the
perf and capture scripts. The Favorites test then passed 60 of 60, and
the full suite passes. The SP-11 flake (a profile change that reached
listeners, then reverted in the store) fits the same cause.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
They bound 127.0.0.1:3390 and 3392, so two full suites running at once
on one machine (several agents do) could collide. Now the OS picks a free
port.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Three command bar tests opened real DuckDuckGo and YouTube searches and a Peek test opened
meet.google.com. They only check URLs, but the pages loaded from CI and dev machines. launchOrbit
now starts Orbit with --proxy-server pointing at a closed loopback port (loopback, where the
fixtures are served, bypasses it), and waits until every profile's session resolves through it:
a session picks the command line's proxy up about 50 ms after the sidebar shows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The favicon tests' servers moved to port 0 in 6d73442, which the OS picks from outside the
project's port range. listenInRange takes the first free port from a base up to 3399, as the
fixture servers already did; startFixtures, the Peek fixtures and both favicon servers use it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…aves only after Orbit has the page's report

Verification V1: the long-video fixture is 60 one-second chunks, but its length varies by
machine (67 s on the macOS runner), so a 75 % seek showed 0:50 where the test wanted 0:4x.
The elapsed text is now compared with the video's position. Passes at 60 and 70 chunks.

Verification V-F2 (test side): MD-07 left a tab right after its page reported playing, before
Orbit had the page's media report, and a tab left then doesn't float. The test now waits for
the report first, as FL-28 does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Conflicts: rpc.ts (both event sets), state.ts (TabState keeps media and floating; settings keep
the PiP fields with Peek's MW-11 note), feature-status.json (union; no duplicate IDs) and
FEATURES.md (regenerated).

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

cursor Bot commented Oct 9, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: d60794b4-b7b8-4c06-9ec8-4e948900b2bc)

…blocked quitting (D28)

Electron's default uncaughtException handler shows a modal error box that holds the
main thread until someone dismisses it, so Orbit couldn't quit. Under Xvfb nobody
dismisses it: e2e app.close() hung, then Playwright's worker teardown, which failed
whole jobs (macOS CI on PR #7 and main, three local full runs). A user would have
seen the box and an app that wouldn't quit.

- src/main/index.ts: log "[orbit] uncaught exception:" and carry on.
- tests/e2e/helpers.ts: app.close() fails a test whose main process logged one
  (takeUncaught() claims one thrown on purpose) and kills an Orbit that still
  hasn't quit 15 s after being asked, so one stuck quit can't hang the run.
- core.spec.ts: [TB-22] a throw in a main-process timer is logged, Orbit quits and
  the session restores. Without the fix nothing is logged and the quit hangs until
  the harness kills it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
siyabendoezdemir and others added 10 commits October 10, 2026 00:20
… before the evaluate replies

fromMini sends a command from the Mini Window's own page. mini.control's close can
close that page before Playwright gets the evaluate's reply, which then fails with
"Target page, context or browser has been closed" (MW-10, once in a full run). The
helper now ignores that error only when the page really closed.

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

A pane added a moment ago springs open from zero width (SV-26), so its first
bounding box can be empty. The poll now retries instead of failing at once.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…de-out threw and blocked quitting (D28)

The strip fades out for 120 ms before its view hides. When its window closed
during the fade (as happens when Orbit quits right after a pane strip hid), the
fade's timer still ran on a view whose page was gone: view.webContents was
undefined, and reading isDestroyed() threw. Nothing caught that, and Electron's
modal error box then held the main thread, so Orbit never quit. This is the
exception behind the SV-03 hangs: 15 repeats of SV-03 with the D28 check logged
it once.

The window's closed handler now clears the strip's show, hide and fade timers,
and every check of the strip's view also handles a view whose webContents is gone.

Test: "[SV-12] a window that closes while its pane strip fades out closes cleanly
(D28)". It fails without the fix (3/3: the TypeError above) and passes with it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…make a 13th Favorite (PR #10 review M1)

A Favorite split could still take Favorites past 12 four ways (FV-02):
- Duplicate put a copy beside it as a new tile;
- Open in Split View on one of its panes and a Today tab left that pane's
  split in place and added the new split as a tile;
- ⌘⇧T or ⌘Z on a pane closed from it put the pane back in its Favorites
  spot when the split was full again;
- ⌘⇧T on a whole Favorite split removed from the sidebar put it back after
  Favorites had filled up.

Duplicate and Open in Split View are now refused with the "Favorites are
full" toast. Open in Split View tries the change on a throwaway copy of the
state and refuses it when a Favorites grid would grow past 12. A tab or
split coming back to a full Favorites grid lands in Today instead.

Tests (each fails without the fix: 13 tiles, or a split id instead of null):
- split-tabs.test.ts: a closed pane reopened, and one undone, while its
  Favorite split is full; a removed Favorite split reopened once Favorites
  filled up.
- split.spec.ts: Duplicate and Open in Split View at 12 tiles are refused,
  and both work once there is room.

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

A row that has just finished growing in drops `.is-growing`, and its slot
goes back to `content-visibility: auto`. For a frame or two after that,
hit testing skips the row, so the test's press landed on the slot and no
drag started. The reviewer saw 4 failures in 15 runs at f8a1988. The test
now waits until `elementFromPoint` at the row's centre is the row. With
the wait, SP-21 passed 15 of 15 runs. Users can lose a click the same way:
that follow-up (F1) goes to the feel lane.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…#11 review M1)

checkQuit kept only the last 4,000 characters of stderr before it looked
for the "[orbit] uncaught exception:" marker. An exception logged in one
longer write (a big message, a cause chain, an AggregateError) lost its
marker to the trim: the test passed and takeUncaught() returned ''.

The scan now looks for the marker in the whole chunk first, keeps up to
4,000 characters from it, and otherwise carries only a marker-length tail
so a marker split across two writes is still found.

New TB-22 test: a main-process timer throws a 5,000-character message.
It fails without the fix (takeUncaught() returns '') and passes with it,
3/3. core + split + smoke e2e: 62/62.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…Ubuntu 26 (Oct 19)

GitHub's notice says `ubuntu-latest` becomes Ubuntu 26 from 2026-10-19.
The e2e job installs xvfb, libgbm1 and libasound2t64 with apt, which is
only known to work on 24.04, the image `ubuntu-latest` runs today. So
nothing changes until that date; after it, the jobs stay on 24.04 until
someone checks 26 on purpose. CI-only change (D18).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… gets the offline proxy, deferred evaluates and the quit check

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
siyabendoezdemir added a commit that referenced this pull request Oct 10, 2026
Conflicts: src/shared/rpc.ts keeps both renderer views ('library' and 'pip');
FEATURES.md regenerated. Typecheck, lint and build exit 0; unit 352/352.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
siyabendoezdemir added a commit that referenced this pull request Oct 10, 2026
Every conflict is two additions side by side, kept together:
- state.ts: chrome's runtime tab fields (blockedPopups, permissionPrompt,
  pendingUrl) and media's tab media state; persist.ts lists all of them as
  runtime-only.
- rpc.ts: NavCommands and MediaCommands/MediaEvents; renderer views keep
  chrome's (sidebar, tools, prompt, loading, link) and add media's 'pip'.
- model.ts, ops.ts, persist.ts: both lanes' imports.
- Sidebar.tsx: chrome's structure (private sidebar, floating card, ResizeHandle
  taking the window) with media's tray above the bottom bar in normal windows.
- helpers.ts: media's launchOrbit (reducedMotion and args), with chrome's
  example of args in its comment.
- feature-status.json: union (no ID on both sides); FEATURES.md regenerated.

Typecheck and lint exit 0.

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

cursor Bot commented Oct 10, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: 56f9b7fd-b1fa-48da-ad71-53f1f343f639)

@siyabendoezdemir
siyabendoezdemir merged commit effd33f into main Oct 10, 2026
1 check passed
siyabendoezdemir added a commit that referenced this pull request Oct 10, 2026
Conflicts:
- Sidebar.tsx: the update banner and media's tray both sit above the Space switcher, the banner first.
- rpc.ts: RendererView takes both 'pip' and 'onboarding'.
- FEATURES.md: regenerated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
siyabendoezdemir added a commit that referenced this pull request Oct 10, 2026
docs/ARCHITECTURE.md §9: main's E2E bullet (dead proxy, deferred evaluates,
the quit check) with this branch's merge-gate bullet in place of the CI one.
FEATURES.md regenerated.

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