Skip to content

cmux-next: Run board, run.attach bridge and retention reaper (rebuilt from #21305) - #22408

Merged
teamleaderleo merged 16 commits into
feat-cmux-nextfrom
cmux-next-run-board-attach
Oct 21, 2026
Merged

teamleaderleo merged 16 commits into
feat-cmux-nextfrom
cmux-next-run-board-attach

Conversation

@teamleaderleo

@teamleaderleo teamleaderleo commented Oct 20, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

#21305 can't merge into feat-cmux-next anymore (37 conflicting files). Most of its durable Run API landed in reworked form in #21337 and #21634, and its priority sidebar landed in #21234 and #21281. This PR rebuilds the parts that hadn't landed on the current architecture, and fixes one bug in the landed code that the rebuild would otherwise have switched on.

Run board (webview, gallery only). webviews/src/agent-session/acpmux/run-board/ holds the board plus its client for /api/runs. The client reads a run's snapshot, replays events from a cursor, and mints and redeems a one-use attach ticket. Gallery entries cover the base board, the Compare and Timeline variants, and the top-two tournament judge.

Compared with #21305, the board:

  • follows today's type-scale and layer tokens;
  • restarts its replay session when the run changes;
  • takes the session id from the Run view's placement;
  • disables Attach when the view says the attempt isn't attachable.

The board isn't mounted in the app. The agent page's CSP is connect-src 'none' and these routes need cmux.com auth, so mounting it means routing the fetches through native first.

run.attach (agent pane → App). The page sends run.attach with a redeemed ticket's ids, the worker session and a live ws/wss trusted-carrier endpoint. The App opens that terminal as a remote-terminal tab in the pane, and only under these conditions:

  • the person made a fresh gesture in the pane, used once, as tab.open requires to run a line;
  • the target is a Cloud machine whose daemon is already connected and whose session matches the ticket;
  • the target is never this Mac, an SSH host or a paired server.

The route is never dialed, and no token crosses the bridge. Nothing in the app sends run.attach yet (see above).

Retention reaper and artifact route (web).

  • Retention cron. The landed dogfood path keeps a finished attempt's VM attachable for a retention window, and its comment says a cron destroys the VM afterwards. No route called reapExpiredCloudVmAttempts, though. /api/cron/run-attempt-retention now runs it every minute, monitored like the sibling crons.
  • Promoted VMs are skipped. While building this, review found that claimExpiredAttemptCleanups would also claim an attempt that had been promoted to a persistent runtime. Promotion keeps the same VM and doesn't clear the retention window, so the cron would have destroyed that runtime's live VM. The claim now skips attempts that have a runtime_id.
  • Already-gone VMs count as cleaned. A VM the reaper finds already gone is marked cleaned. Before, it failed every minute and blocked the rows behind it.
  • Artifact route. POST /api/runs/[id]/attempts/[attemptId]/artifacts exposes the landed recordRunArtifact to workers.
  • Ported tests. feat(cmux-next): durable runs, attach bridge, and top-two UI tournament #21305's tests for the run model, the provider view, worker auth and the live-database route suite come across. The route suite now expects a changed retry of the same event id to get a 409, which is fix(runs): replay worker retries only for the writing lease; fence retention handoffs #21634's rule.

Not rebuilt from #21305:

Testing

  • Webview tests: cd webviews && bun test src/agent-session/acpmux/run-board passes, 25/25. bunx --bun vp check src/agent-session/acpmux/run-board is clean.
  • Webview style checks: check:layers, check:type-scale, check:system-blue and check:browser-lanes pass. bun run check as a whole fails on this checkout, but only on base files this PR doesn't touch:
    • check:text-focus and check:dialog-keys fail on FileGrid, FilesUI, Screenshots and Browser Swift files;
    • oxfmt flags src/pages/settings/*;
    • the typecheck can't find the ungenerated pages/settings/generated/strings.json.
  • Web unit tests: cd web && bun test tests/run-view.test.ts tests/run-model.test.ts tests/run-worker-auth.test.ts tests/cron-auth.test.ts tests/backend-error-coverage.test.ts passes. bun run typecheck, bun run lint:complexity and bunx eslint on the changed web files are all clean.
  • Live Postgres (podman on huge-blue, migrations applied with scripts/db-migrate.mjs), with CMUX_DB_TEST=1 at ff6c6a5:
    • run-route-db-behavior 9/9;
    • run-repository-db-behavior 9/9;
    • run-retention-db-behavior 6/6;
    • run-attempt-promotion 7/7;
    • runtime-promotion-db-behavior 1/1.
  • Promoted-VM regression: the new test never reaps the VM of an attempt promoted to a persistent runtime failed with the old claim (Expected: false, Received: true, 5/6 at bfb7f1e) and passes with the fix.
  • Swift: xcrun swiftc -parse on every changed file passes, and scripts/cmux-next/prepush-gate.py reports no new violations. AgentPaneRunAttachTests was added; it covers decoding, refusal of a stale or untrusted endpoint, the single-use gesture gate and the missing-host reply.

Observed / Not run

Observed: everything listed under Testing.

Adopted from a desktop session with no live owner. Fixes since adoption: the expired-attempt cleanup claim only reaps ephemeral or runtime-less attempts, promoteAttemptToRuntime throws RuntimeAttemptConflictError while a cleanup lease is active, public import Foundation in AgentPaneRunAttach.swift, and size trims. Base merged at 8ae8fd5 (clean).

At head 63031b4:

  • Fleet cmux-ci compile-check --app passed (job 2222dd98a78664c0dbdc20f8).
  • Fleet run-swift-testing-suites.sh Packages/macOS/CmuxNext: AgentPaneRunAttachTests passed. The run also failed 36 suites this PR doesn't touch (Accounts, ClassicUpgradeSimulation, FolderNameIndexSpeed, AgentPaneSendPickedFolderPrompt gesture checks, among others), so that run as a whole is not green.
  • CI reds are base only: Web tests (1/4) fails "keyboard shortcuts docs > table aliases" and react-apps-check fails as on base. The CI Swift jobs were still queued at merge time.

Follow-up: runError in web/services/runs/http.ts doesn't map RuntimeAttemptConflictError to 409 yet.

Not run:

  • CI's cmux-next swift test hadn't run at merge time (queued).
  • The retention cron and the artifact route weren't exercised against a deployed backend.
  • run.attach hasn't been driven in a tagged build, because nothing in the app sends it yet.

Review: correctness review by a separate review subagent at eaca1ab (verdict: pass with fixes). Its findings are fixed in bfb7f1e..ff6c6a5; the same reviewer re-checked those at ff6c6a5 (pass).
Runtime test: run-retention-db-behavior "never reaps the VM of an attempt promoted to a persistent runtime" plus the Run DB suites above, CMUX_DB_TEST=1 bun test --max-concurrency=1 <file> against Postgres 16 on huge-blue at ff6c6a5.

Changelog

none

Proof

Not UI: the Run board appears only in the webview gallery, whose advisory run captures it (agent-pane.run-board* entries). Nothing user-visible changes in the app.

🤖 Generated with Claude Code


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.


Summary by cubic

Rebuilds the Run board webview and run.attach bridge from #21305 on the landed durable Run API, and adds the run-attempt retention reaper and worker artifact route. Fixes three reaper bugs that would have destroyed promoted runtimes' VMs and blocked cleanup rows.

Run board and attach bridge

  • The board reads a run's snapshot, replays events by cursor, and mints and redeems a one-use attach ticket; it's gallery-only, not mounted in the app.
  • run.attach opens the attempt's terminal as a remote-terminal tab only on a connected Cloud machine whose daemon session matches the ticket, with a fresh pane gesture; the route is never dialed and no token crosses the bridge.
  • CmuxNextApp stays under its size cap by joining the attach wiring onto fewer lines, with no behavior change.
  • run.attach's init imports Foundation publicly so the package builds with InternalImportsByDefault.

Retention reaper and artifact route

  • /api/cron/run-attempt-retention runs the landed reaper every minute, monitored like the sibling crons.
  • The reaper skips attempts promoted to a persistent runtime, not every attempt with a runtime: each launched attempt already owns an ephemeral runtime, so claiming all of them would reap every finished VM.
  • Promotion is refused while the reaper holds an attempt's cleanup lease, closing the race where promotion commits before the destroy and loses its VM.
  • A VM the reaper finds already gone counts as cleaned instead of failing every minute and starving older rows.
  • POST /api/runs/[id]/attempts/[attemptId]/artifacts exposes recordRunArtifact to workers.

Written for commit 63031b4. Summary will update on new commits.

View guided diff Turn on auto-fix

teamleaderleo and others added 8 commits October 19, 2026 23:32
Rebuilt from #21305 onto the landed Run API (#21337, #21634). The board reads
a durable run's snapshot and replays its events by cursor, mints and redeems a
one-use attach ticket, and asks the App to open the worker terminal
(run.attach). Gallery entries: the base board, the Compare and Timeline
variants, and the top-two tournament judge.

Adapted to current lints: type-scale and layer tokens instead of raw sizes and
z-index, an output element for attach status, and a narrow fetch type so tests
pass plain functions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… (run.attach)

Rebuilt from #21305. The pane decodes run.attach (ids, a live trusted-carrier
ws/wss endpoint, the worker session) and, with the person's fresh gesture in
the pane, asks the App to open the attempt's terminal as a remote-terminal tab.
The App opens it only on a connected remote worker daemon whose session is the
one the ticket names; the route is never dialed and no token is accepted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The landed Run API schedules a short terminal retention window after an
attempt finishes and its comment says a cron reaper destroys the VM after it,
but no route called reapExpiredCloudVmAttempts, so retained VMs were never
destroyed by it. Add /api/cron/run-attempt-retention (monitored, every
minute) and the worker artifacts route over the landed recordRunArtifact.
Port #21305's tests for the run model, provider view, worker auth and the
live-database route suite.

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

#21634 made a retry with the same event id and different content a 409; the
suite ported from #21305 still expected the first event back.

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

Fails on the base: claimExpiredAttemptCleanups claims any finished attempt
whose retention expired, including one promoted to a persistent runtime, so
the retention cron would destroy the runtime's live VM.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
claimExpiredAttemptCleanups now skips attempts linked to a runtime: promotion
hands the attempt's VM to a persistent runtime without clearing the
retention window, so the new cron would destroy it. A VM the reaper finds
already gone now counts as cleaned instead of failing every minute and
starving older rows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The board rebuilds its replay session when the run id changes, reads the
latest snapshot without a stale closure (vp check's exhaustive-deps), takes
the session id from the view's placement and disables Attach when the view
says the attempt is not attachable.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
daemon(machine:) also resolves SSH hosts and paired servers; the ticket names a
Cloud VM, and the page's fields are not verified natively, so the lookup is
now Cloud-only.

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

github-actions Bot commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

cmux status (one comment, edited in place; each line is updated by the job that owns it)

Review 63031b4: HQ feed card · gallery before/after runs · checks

App of this head (app.zip, kept 1 day)

gh workflow run reload-build.yml -R manaflow-ai/cmux --ref feat-cmux-next -f tag=pr22408 -f ref=cmux-next-run-board-attach -f platform=macos -f backend_mode=local

Unclear: web / react-apps-check failed: no known signature; failed step: Check webview test shards.

CI failed on 63031b46ce (run 39564002329 attempt 1): 5 code, 1 unknown.
macOS jobs did not run: compile and app tests were skipped: Fast static checks skipped.

Job Verdict Why
web / react-apps-check (1) code a web test failed
web / react-apps-check (4) code a web test failed
web / Web tests (1/4) code a web test failed
web / react-apps-check (2) code a web test failed
web / react-apps-check (3) code a web test failed
web / react-apps-check unknown no known signature; failed step: Check webview test shards

Failures:

  • seen on other PRs composer-history.test.ts ↑ and ↓ in an empty prompt step through the chat's earlier prompts: failed (job)
  • seen on other PRs pane-english.test.ts agent pane strings > no user-visible English is written in the pane's sources: failed (job)
  • seen on other PRs composer-suggested-prompt.test.tsx nothing is suggested from another folder, another worktree, a merged PR, an old chat or a busy one: failed (job)
  • seen on other PRs composer-suggested-prompt.test.tsx a folder with a trailing slash is the same checkout: failed (job)
  • seen on other PRs composer-suggested-prompt.test.tsx a chat that knows its branch takes no PR from another branch: failed (job)
  • seen on other PRs composer-suggested-prompt.test.tsx a chat with turns gets no suggestion: failed (job)
  • seen on other PRs composer-suggested-prompt.test.tsx Tab writes the suggestion without sending; Shift-Tab does not take it; Esc puts it away: failed (job)
  • seen on other PRs composer-suggested-prompt.test.tsx the chat folder's PR from the header's gh read is suggested when no session reports one: failed (job)
  • seen on other PRs quiet-access-chip.test.ts chromium: Full access draws in the model chip's color: failed (job)
  • seen on other PRs quiet-access-chip.test.ts webkit: Full access draws in the model chip's color: failed (job)
  • ... 11 more in the job logs

A web test can import code this PR changes without being one of its files: python3 scripts/ci/web_test_verdict.py --pr 22408 --file web/tests/docs-shortcuts.test.ts checks its import graph (on Linux, in a sandbox) and its failures on other branches.

Matched log lines
web / react-apps-check (1): (fail) ↑ and ↓ in an empty prompt step through the chat's earlier prompts [1669.72ms]
web / react-apps-check (4): (fail) nothing is suggested from another folder, another worktree, a merged PR, an old chat or a busy one [54.30ms]
web / Web tests (1/4): (fail) keyboard shortcuts docs > table aliases are extra keys on their action's row [0.50ms]
web / react-apps-check (2): (fail) the chat composer at 420 px: the strip's marks line up with the prompt's edges [1622.84ms]
web / react-apps-check (3): (fail) a chat on an SSH machine > counts its running turn from the prompt and offers no machine switch mid-chat [31540.19ms]

Not re-run automatically: web / react-apps-check (1), web / react-apps-check (4), web / Web tests (1/4), web / react-apps-check (2), web / react-apps-check (3) are not machine failures.

Written by scripts/ci/classify_failures.py (ci-failure-attribution.yml); signatures are its SIGNATURES table. A machine verdict is the runner's fault, not this PR's; yours means the failing file is one this PR changes, also red on main that main's latest full suite fails the same way, seen on other PRs that it failed on another pull request's run lately.

red: web / Web status; base red: the same failure on the base 8ae8fd5; does not block
red: web / Web tests (1/4); no finished base run on 8ae8fd5 to compare; do not merge until green; see lane-rules
red: web / react-apps-check; base red: the same failure on the base 8ae8fd5; does not block
red: web / react-apps-check (1); NEW: - [] + [ + "run-board/runAttach.ts:30 "This worker terminal endpoint has expired. Request a fresh attach ticket."", + "run-board/runAttach.ts:37 "This worker do is not red on the base 8ae8fd5; do not merge until green; see lane-rules
red: web / react-apps-check (2); base red: the same failure on the base 8ae8fd5; does not block
red: web / react-apps-check (3); base red: the same failure on the base 8ae8fd5; does not block
red: web / react-apps-check (4); base red: the same failure on the base 8ae8fd5; does not block

CI fast guards failed on 63031b46ce (https://github.com/manaflow-ai/cmux/actions/runs/39564002329). It does not block the merge; a red guard merged into main breaks it for every open PR.
The log named no failed step; see the run.

Agents: python3 scripts/ci/guard_attribution.py fix applies the mechanical fixes locally.

Tour of 63031b46ce (run): no media. The tour job failed before the tour ran (build or setup); the run log has the output.

@blacksmith-sh

blacksmith-sh Bot commented Oct 20, 2026 •

Copy link
Copy Markdown

Found 20 test failures on Blacksmith runners:

Failures

Test View Logs
test/agent-pane-math.test.tsx/agent-pane-math.test.tsx View Logs
test/agent-pane-math.test.tsx/agent-pane-math.test.tsx View Logs
test/agent-pane-peer-chat.test.ts/agent-pane-peer-chat.test View Logs
test/agent-pane-peer-chat.test.ts/agent-pane-peer-chat.test View Logs
test/agent-pane-working-counts.test.ts/agent-pane-working-counts.test View Logs
test/agent-pane-working-counts.test.ts/agent-pane-working-counts.test View Logs
test/composer-history.test.ts/composer-history.test View Logs
test/composer-history.test.ts/composer-history.test View Logs
test/composer-location-strip.test.ts/composer-location-strip.test View Logs
test/composer-location-strip.test.ts/composer-location-strip.test View Logs

...and 10 more test failures. View all on Blacksmith

Fix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Bead cx-f59ie created for this PR by the cmux coordinator.

@greptile-apps

greptile-apps Bot commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerView in GreptileConfidence Score: 4/5

[Medium impact] The changes affect Run workflows in the web service and the macOS agent pane.

Fix the retention filter before merging; it leaves ordinary finished Run VMs running.

Findings

  1. P1 Finished VMs keep running ▶
  2. P2 Replay skips fetched events ▶

Summary

Adds gallery Run boards, a native run.attach bridge, a retention cron, and a worker artifact route.

  • Run attempts appear in a gallery board with comparison and replay views.
  • A fresh pane gesture can open a retained Cloud worker terminal in a new tab.
  • A minute-by-minute reaper clears expired attempt VMs without claiming promoted runtimes.
  • Workers can publish artifact manifests for a specific Run attempt.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart TD
  Launch[Launch ordinary attempt] --> Ephemeral[Create ephemeral runtime]
  Ephemeral --> Link[Set attempt runtimeId]
  Link --> Finish[Attempt finishes]
  Finish --> Retain[Retention window expires]
  Retain --> Filter{runtimeId is null?}
  Filter -->|No| Skip[Cleanup skips VM]
  Filter -->|Yes| Destroy[Destroy VM]
Loading

Reviews (1) · Last reviewed commit: "fix(cmux-next): run.attach opens only on..." · Reviewed by Greptile

Comment thread web/services/runs/repository.ts Outdated
Comment on lines +781 to +782
// A promoted attempt's VM belongs to its persistent runtime now; never reap it.
isNull(runAttempts.runtimeId),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Finished VMs keep running

runtimeId does not mean an attempt was promoted. launchCloudVmAttempt creates an ephemeral runtime and links every attempt to it before creating the VM. This filter therefore skips ordinary finished attempts too, so the new cron never destroys their retained VMs. They keep using paid resources and can fill the user's machine allowance.

Check whether the linked runtime is persistent instead of excluding every non-null runtimeId. Add a regression test using an attempt linked to an ephemeral runtime.

Comment on lines +523 to +524
cursor = result.nextCursor;
this.transportCursor = cursor;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Replay skips fetched events

readPages saves its cursor before returning the fetched events. If page one succeeds and page two fails with a network error or HTTP 503, replay throws without applying page one's events. The next retry starts after that saved cursor, permanently skipping those activity and status updates.

Keep cursor changes local until the whole read succeeds, and add a test where the second page fails once.

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

cmux-next guards / defaults-snapshot is red on 20 of 20 fresh feat-cmux-next PR heads in the last 2:00:00, so it is most likely a base red, not this PR's. The base-reds lane has it; a base merge picks up the fix once it lands.

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

CI / web / react-apps-check (4) is red on 10 of 14 fresh feat-cmux-next PR heads in the last 2:00:00, so it is most likely a base red, not this PR's. The base-reds lane has it; a base merge picks up the fix once it lands.

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Orphaned: no activity for 14 h, and no lane holds a lease on it. The orphan sweep could not merge feat-cmux-next in: conflicts in Packages/macOS/CmuxNext/Sources/CmuxNextAgentPane/AgentPageProvider.swift. Listed for a lane to resolve.

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Owned by gallery-loop-b4 on cosmic-gray · session

AgentPageProvider's method list keeps base's pane.setDisplay and this PR's run.attach.

Agent-Session: 01a15ba2-c7ad-7850-9e71-5b51f1c9ed9f
The size cap refuses any source growth in CmuxNextApp. The run.attach closure and its
onRunAttach hook are joined into fewer lines, and four one-statement functions and three
guard blocks in the same two files are written on one line each. No behavior change.

Agent-Session: 01a15ba2-c7ad-7850-9e71-5b51f1c9ed9f
Base now builds CmuxNextAgentPane with InternalImportsByDefault, so AgentPaneRunAttach's public
init(params:now: Date = Date()) failed to compile (fleet compile-check 3476a896 at 208bdff).

Agent-Session: 01a15ba2-c7ad-7850-9e71-5b51f1c9ed9f
…l runtime (failing)

Review of #22408: launchCloudVmAttempt gives every attempt an ephemeral runtime, so skipping any
attempt with a runtime_id skips every real Run VM and none is ever destroyed. The promoted-VM
test now inserts its runtime as persistent explicitly.

Agent-Session: 01a15ba2-c7ad-7850-9e71-5b51f1c9ed9f
…istent runtime

Every launched attempt is attached to its own ephemeral runtime before its VM exists, so the
claim now left-joins cloud_runtimes and keeps an attempt whose runtime is ephemeral; the row lock
stays on run_attempts (FOR UPDATE cannot lock the nullable side of an outer join).

Agent-Session: 01a15ba2-c7ad-7850-9e71-5b51f1c9ed9f
…cleanup lease (failing)

Review of #22408: a promotion that commits after the reaper's claim but before destroyVm still
loses its VM.

Agent-Session: 01a15ba2-c7ad-7850-9e71-5b51f1c9ed9f
…p its VM

Agent-Session: 01a15ba2-c7ad-7850-9e71-5b51f1c9ed9f
@teamleaderleo teamleaderleo added the risk:normal Risk tier (docs/ci/risk-tiers.md) label Oct 21, 2026
@teamleaderleo
teamleaderleo merged commit 802f9b7 into feat-cmux-next Oct 21, 2026
@teamleaderleo
teamleaderleo deleted the cmux-next-run-board-attach branch October 21, 2026 09:24
teamleaderleo added a commit that referenced this pull request Oct 21, 2026
…d focus (base text-focus red) (#23300)

#22408's timeline.css put an outline on '.timeline-board input:focus-visible',
which check-text-focus-rings reads as a ring on a text field and fails the
base 'web lint (fast)' job. The board's only input is the review checkbox;
the selector now says so.


Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15c78-27ca-7140-a6c7-95f0db83b18c&host=huge-blue

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
teamleaderleo added a commit that referenced this pull request Oct 21, 2026
…ble (#23301)

#22408 wrote the run board's attach and replay errors and the "Agent output"
row title as English literals, which test/pane-english.test.ts rejects. These
errors reach the board's alert through error.message, so they are translated:
eight runBoard.* keys in acpmux/Localizable.xcstrings with all 21 languages,
read through translate(). Error codes are unchanged.


Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15c78-27ca-7140-a6c7-95f0db83b18c&host=huge-blue

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

Shipped in:

teamleaderleo added a commit that referenced this pull request Oct 21, 2026
…icks (#23563)

* test(gallery): agent-pane plays reach their state through the matrix's trusted input

The agent-pane entries test now installs the gallery matrix's trusted input driver
(trustedGalleryInput: cmuxGalleryInput through Playwright's mouse and keyboard) on
every page, and a case can set the page size of a smaller matrix frame. It adds
eight states that fail today because the driver clicks a target that is not on
screen, so nothing receives the click and the play's waitFor never holds:

- run-board-timeline#approved, run-board-tournament#approve-winner,
  run-board-tournament#select-review, run-board.compare#approve-winner: the review
  checkbox and approve button sit below the fold.
- composer-row#fast-off and #fast-on (720x300), composer-pickers#fast-mode
  (640x260): the model menu scrolls in the short frame and its Fast mode row is
  half clipped.
- artifact-panel#versions (760x640): the first draft's Open preview is scrolled
  out above the latest reply.

The run-board states and artifact-panel#versions check the play's error only. Their
steps shift layout by design or by a width follow-up: the compare receipt and the
approve button change width (the receipt anchor moves 92 px), and the side panel
narrows the chat.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15c2b-c6e5-7540-ad96-0eb60604230e&host=huge-blue

* gallery: trusted play input scrolls its target into view before it clicks

The matrix runner's trusted driver (cmuxGalleryInput) clicks at the target's
on-screen point, and playRunner never scrolled the target into view first. A
target below the fold or scrolled out of a short frame got no click, so the play's
waitFor never held. Before a trusted input step, the target now scrolls into view
the way a person would bring it on screen, outside the measured step so the scroll
is not counted as an anchor move or a layout shift. Synthetic input is unchanged.

This reaches the run-board review and approve states (timeline#approved,
tournament#approve-winner, tournament#select-review, compare#approve-winner),
broken since the run board landed in #22408; their four lines leave
broken-allowlist.json. It also fixes composer-row#fast-off, composer-row#fast-on,
composer-pickers#fast-mode and artifact-panel#versions in the matrix's short
frames: #23473 dropped those keys from the allowlist, and they failed in gallery
health on #23498.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15c2b-c6e5-7540-ad96-0eb60604230e&host=huge-blue

* test(gallery): image-viewer#open and #dismiss-and-restore reach the viewer under trusted input

Their plays click the first reply image, which is above the fold in the transcript. With
playRunner's scroll into view removed, both cases fail (0 pass, 2 fail). With it, both pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15c2b-c6e5-7540-ad96-0eb60604230e&host=huge-blue

* gallery: image-viewer#open and #dismiss-and-restore render, so they leave the allowlist

When trusted input scrolls the target into view, the first reply image is clicked. This brings
back both states, which gallery health now reports as rendered.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15c2b-c6e5-7540-ad96-0eb60604230e&host=huge-blue

---------

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

risk:normal Risk tier (docs/ci/risk-tiers.md)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant