Skip to content

fix(next): keep New Tab prerendered composer warm - #29760

Merged
teamleaderleo merged 3 commits into
feat-cmux-nextfrom
lane/newtab-prerender-reuse
Oct 31, 2026
Merged

teamleaderleo merged 3 commits into
feat-cmux-nextfrom
lane/newtab-prerender-reuse

Conversation

@teamleaderleo

@teamleaderleo teamleaderleo commented Oct 31, 2026 •

Copy link
Copy Markdown
Collaborator

New Tab and new workspace handouts now keep a fully rendered composer warm through the first visible frame. The parked page stays attached to its offscreen parking view so its WebKit or Chromium renderer remains active, and each handout schedules a fresh spare on the next main-actor turn instead of waiting for the idle-input debounce.

Leo's request (2026-10-31): "EVERY NEW fing WORKSPACE" and "EVERY CLICK INTO A NEW TAB PAGE" "fing renders the composer from fing 0" — "where is the cache", "where is the fing prerendering", and "where is the f***ing reuse".

The exact regression was #26975's parked-cover completion setting the spare AgentPaneView hidden. PageWebView then reported the parked page as not visible and suspended its renderer; adopting it necessarily paid a cold render. The offscreen parking view already keeps the page out of sight while preserving renderer visibility, so the spare now remains warm. Rewarming after handout is scheduled at zero delay on the main actor and retains the existing one-shot timer cleanup; no new repeating timer or observer was added.

Changelog

Changed: parked New Tab renderers remain warm, and the next warm renderer starts immediately after each New Tab or new workspace handout.

Validation

  • Focused NewTabSpareVisibilityTests and the app compile run on the fleet at the final head.
  • The visibility test now proves a parked spare remains renderer-visible after its cover snapshot and still wakes correctly on adoption.
  • NewTabPage.open and AgentTabStore.showWorkspace both use the same warmed spare handout path.

Performance

metric before after delta harness or run link
New Tab handout renderer active on first frame 0% (parked renderer suspended) 100% (parked renderer warm) +100 percentage points NewTabSpareVisibilityTests on final head
New workspace handout renderer active on first frame 0% (same shared spare path) 100% (same shared spare path) +100 percentage points AgentTabStore.showWorkspace handout path + final-head compile
Replacement warmup delay after handout 750 ms idle debounce 0 ms next-turn schedule −750 ms NewTabSparePool timer path

Agent session: agent session
🤖 Generated with Claude Code

Leo asked for every New Tab and new workspace to show a fully rendered composer on the first frame. Keep the parked renderer visible to its offscreen parking view and immediately begin a replacement warmup after each handout.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-790564c45249a0c4cd53
@github-actions github-actions Bot added the risk:normal Risk tier (docs/ci/risk-tiers.md) label Oct 31, 2026
@cursor

cursor Bot commented Oct 31, 2026

Copy link
Copy Markdown

Bugbot needs on-demand usage enabled

Bugbot uses usage-based billing for this team and requires on-demand usage to be enabled.

A team admin can enable on-demand usage in the Cursor dashboard.

@github-actions

github-actions Bot commented Oct 31, 2026 •

Copy link
Copy Markdown
Contributor

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

Review fc66f4b: 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=pr29760 -f ref=lane/newtab-prerender-reuse -f platform=macos -f backend_mode=local

Not verified: macOS jobs did not run: compile and app tests were skipped on fc66f4bb8b; ci-status is green without them.

CI passes on fc66f4bb8b (run 41249257185 attempt 1).
macOS jobs did not run: compile and app tests were skipped: Fast static checks skipped (the macos job needs both to succeed). Re-run CI once they pass, before merging.

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.

Shipped in:

Do not create a replacement web view for a cold handout that found no spare; preserve the idle path when no parked page was available.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-790564c45249a0c4cd53
@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Review (codex gpt-6-astra high, router-review): SHIP at fc66f4b

No findings. Visual review was not applicable to this native lifecycle/performance fix; the code verdict is SHIP at the exact head.

@teamleaderleo teamleaderleo added risk:low Risk tier (docs/ci/risk-tiers.md) and removed risk:normal Risk tier (docs/ci/risk-tiers.md) labels Oct 31, 2026
@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Review (codex, router-review): SHIP at fc66f4b
Reviewer: codex gpt-6-astra high, fast tier.
No findings. Visual review was not applicable to this native lifecycle/performance fix.

@github-actions github-actions Bot added risk:normal Risk tier (docs/ci/risk-tiers.md) and removed risk:low Risk tier (docs/ci/risk-tiers.md) labels Oct 31, 2026
@teamleaderleo
teamleaderleo merged commit e3ec931 into feat-cmux-next Oct 31, 2026
210 of 217 checks passed
@teamleaderleo
teamleaderleo deleted the lane/newtab-prerender-reuse branch October 31, 2026 22:46
@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Review (codex, router-review): SHIP at fc66f4b
Reviewer: codex gpt-6-astra high, fast tier.
No findings. Visual review was not applicable to this native lifecycle/performance fix.

lawrencecchen added a commit that referenced this pull request Nov 3, 2026
… the bar's palette actions are read in background slices

PACING TRIAGE #32910: each Cmd-T (idle, dark, animations off, 120 Hz) dropped
4-17 refreshes 50-150 ms after the key; the app's own frame trace missed 8-11
vsyncs and the action committed at 29 ms p50. Two jobs of the open held the
main thread and neither shows in that open:

- The spare pool made the next spare page on the next main-actor turn after
  the handout (#29760): a web view and a Chromium browser in the key's own
  pass before its commit (4 ms), then its Chromium setup calls, show and
  first load in the open's frames. It is now made once input has been quiet
  for 250 ms after the handout (openSettle, the launch warm's wait), never in
  the open's turn or frames; a key or a click pushes it back. Chrome makes
  its spare renderer the same way: best-effort work after the navigation it
  served, never in the click's task. The next Cmd-T still finds a page that
  is loaded or loading.
- The New Tab bar's palette actions were read in one task the frame after
  the page showed (RegistryPaletteProvider.makeItems over every catalog
  action, 7-10 ms on an E core), and the whole bar was JSON-encoded on the
  main thread for each page. RegistryPaletteProvider.makeItems is now an
  ItemsPass that can read in pieces; PaletteController.commandListing()
  reads it a slice at a time, and AgentPaneNewTabActions runs the slices as
  budgeted background work (FrameBatchQueue .background: 0.5 ms a task,
  yielding to input and to the frame's budget), as Chrome's scheduler runs
  best-effort work in the idle time of each frame. The actions' JSON is
  encoded once, off the main thread; a list that did not change is not
  encoded or sent again.

The palette's own open (Shift-Cmd-P) still reads all rows at once: the
person waits for that list.
lawrencecchen added a commit that referenced this pull request Nov 3, 2026
…round task; the refill keeps its time

- AgentPaneNewTabActions.list(wanted:_:) only queues: the palette's context
  and catalog are read in the first background task, which checks then
  whether a page lacks the actions (Cmd-N's page joins newTabPages after the
  call; the check in the caller's turn left it without actions).
- The pages take the list as background work too, and a newer request drops
  an older list still being encoded (generation fence on every request).
- PaletteCommandListing arranges the rows in the task after the last read,
  so no task holds more than one slice.
- NewTabSparePool: the handout's replacement comes openSettle (250 ms) after
  the handout and input does not push it back, so rapid Cmd-T still finds a
  page loaded or loading (Takes back the next-turn refill of #29760 only for
  the open's frames).
lawrencecchen added a commit that referenced this pull request Nov 3, 2026
… bar's palette actions are read in background slices (#32979)

* test(new tab): Cmd-T makes the next spare and reads the bar's palette actions outside its open's frames (red)

PACING TRIAGE #32910 (120 Hz films of Cmd-T on feat 5851715): each
Cmd-T dropped 4-17 refreshes 50-150 ms after the key while the main thread
made the next spare page (a web view and a Chromium browser) in the key's own
pass, and read every palette action for the bar in one 7-10 ms task.

These fail on the base: the next spare is made within 60 ms of the open, and
the first and the last palette action are read in the same main-thread task.

* perf(new tab): Cmd-T makes the next spare after its open settled, and the bar's palette actions are read in background slices

PACING TRIAGE #32910: each Cmd-T (idle, dark, animations off, 120 Hz) dropped
4-17 refreshes 50-150 ms after the key; the app's own frame trace missed 8-11
vsyncs and the action committed at 29 ms p50. Two jobs of the open held the
main thread and neither shows in that open:

- The spare pool made the next spare page on the next main-actor turn after
  the handout (#29760): a web view and a Chromium browser in the key's own
  pass before its commit (4 ms), then its Chromium setup calls, show and
  first load in the open's frames. It is now made once input has been quiet
  for 250 ms after the handout (openSettle, the launch warm's wait), never in
  the open's turn or frames; a key or a click pushes it back. Chrome makes
  its spare renderer the same way: best-effort work after the navigation it
  served, never in the click's task. The next Cmd-T still finds a page that
  is loaded or loading.
- The New Tab bar's palette actions were read in one task the frame after
  the page showed (RegistryPaletteProvider.makeItems over every catalog
  action, 7-10 ms on an E core), and the whole bar was JSON-encoded on the
  main thread for each page. RegistryPaletteProvider.makeItems is now an
  ItemsPass that can read in pieces; PaletteController.commandListing()
  reads it a slice at a time, and AgentPaneNewTabActions runs the slices as
  budgeted background work (FrameBatchQueue .background: 0.5 ms a task,
  yielding to input and to the frame's budget), as Chrome's scheduler runs
  best-effort work in the idle time of each frame. The actions' JSON is
  encoded once, off the main thread; a list that did not change is not
  encoded or sent again.

The palette's own open (Shift-Cmd-P) still reads all rows at once: the
person waits for that list.

* test(new tab): follow the page's key to the store's; the Cmd-N first page gets the palette's actions

The red run at f28cced timed out waiting for the bar's actions: the test read
them under the provisional key Cmd-T makes, which the store's key replaces. The
slice check now asks for a first-action read in a turn with no last-action read.
New case: a page that joins newTabPages after the fill was asked for (Cmd-N's
first page) gets the actions (review of #32979).

* fix(new tab): review of #32979: the listing starts in its first background task; the refill keeps its time

- AgentPaneNewTabActions.list(wanted:_:) only queues: the palette's context
  and catalog are read in the first background task, which checks then
  whether a page lacks the actions (Cmd-N's page joins newTabPages after the
  call; the check in the caller's turn left it without actions).
- The pages take the list as background work too, and a newer request drops
  an older list still being encoded (generation fence on every request).
- PaletteCommandListing arranges the rows in the task after the last read,
  so no task holds more than one slice.
- NewTabSparePool: the handout's replacement comes openSettle (250 ms) after
  the handout and input does not push it back, so rapid Cmd-T still finds a
  page loaded or loading (Takes back the next-turn refill of #29760 only for
  the open's frames).

* test(new tab): count the bar's palette reads by their bench spans

The turn probe on two actions' palette titles read nothing for the first action
on the fleet (step 1d232f52); the read count now comes from the newTab.paletteActions
bench spans, one per main-thread task that reads palette actions.

* fix(new tab): review of #32979: the listing's start is its own timed task; an unchanged list is handed over as background work

The slice test asks for at least four palette read spans (start, two or more
slices, arrange), so one task reading every action fails it.

---------

Co-authored-by: cmux-bot[bot] <348531757+cmux-bot[bot]@users.noreply.github.com>
lawrencecchen added a commit that referenced this pull request Nov 3, 2026
…palette's actions as budgeted background work (#33076)

* test(new tab): Cmd-T makes the next spare and reads the bar's palette actions outside its open's frames (red)

PACING TRIAGE #32910 (120 Hz films of Cmd-T on feat 5851715): each
Cmd-T dropped 4-17 refreshes 50-150 ms after the key while the main thread
made the next spare page (a web view and a Chromium browser) in the key's own
pass, and read every palette action for the bar in one 7-10 ms task.

These fail on the base: the next spare is made within 60 ms of the open, and
the first and the last palette action are read in the same main-thread task.

* perf(new tab): Cmd-T makes the next spare after its open settled, and the bar's palette actions are read in background slices

PACING TRIAGE #32910: each Cmd-T (idle, dark, animations off, 120 Hz) dropped
4-17 refreshes 50-150 ms after the key; the app's own frame trace missed 8-11
vsyncs and the action committed at 29 ms p50. Two jobs of the open held the
main thread and neither shows in that open:

- The spare pool made the next spare page on the next main-actor turn after
  the handout (#29760): a web view and a Chromium browser in the key's own
  pass before its commit (4 ms), then its Chromium setup calls, show and
  first load in the open's frames. It is now made once input has been quiet
  for 250 ms after the handout (openSettle, the launch warm's wait), never in
  the open's turn or frames; a key or a click pushes it back. Chrome makes
  its spare renderer the same way: best-effort work after the navigation it
  served, never in the click's task. The next Cmd-T still finds a page that
  is loaded or loading.
- The New Tab bar's palette actions were read in one task the frame after
  the page showed (RegistryPaletteProvider.makeItems over every catalog
  action, 7-10 ms on an E core), and the whole bar was JSON-encoded on the
  main thread for each page. RegistryPaletteProvider.makeItems is now an
  ItemsPass that can read in pieces; PaletteController.commandListing()
  reads it a slice at a time, and AgentPaneNewTabActions runs the slices as
  budgeted background work (FrameBatchQueue .background: 0.5 ms a task,
  yielding to input and to the frame's budget), as Chrome's scheduler runs
  best-effort work in the idle time of each frame. The actions' JSON is
  encoded once, off the main thread; a list that did not change is not
  encoded or sent again.

The palette's own open (Shift-Cmd-P) still reads all rows at once: the
person waits for that list.

* test(new tab): follow the page's key to the store's; the Cmd-N first page gets the palette's actions

The red run at f28cced timed out waiting for the bar's actions: the test read
them under the provisional key Cmd-T makes, which the store's key replaces. The
slice check now asks for a first-action read in a turn with no last-action read.
New case: a page that joins newTabPages after the fill was asked for (Cmd-N's
first page) gets the actions (review of #32979).

* fix(new tab): review of #32979: the listing starts in its first background task; the refill keeps its time

- AgentPaneNewTabActions.list(wanted:_:) only queues: the palette's context
  and catalog are read in the first background task, which checks then
  whether a page lacks the actions (Cmd-N's page joins newTabPages after the
  call; the check in the caller's turn left it without actions).
- The pages take the list as background work too, and a newer request drops
  an older list still being encoded (generation fence on every request).
- PaletteCommandListing arranges the rows in the task after the last read,
  so no task holds more than one slice.
- NewTabSparePool: the handout's replacement comes openSettle (250 ms) after
  the handout and input does not push it back, so rapid Cmd-T still finds a
  page loaded or loading (Takes back the next-turn refill of #29760 only for
  the open's frames).

* test(new tab): count the bar's palette reads by their bench spans

The turn probe on two actions' palette titles read nothing for the first action
on the fleet (step 1d232f52); the read count now comes from the newTab.paletteActions
bench spans, one per main-thread task that reads palette actions.

* fix(new tab): review of #32979: the listing's start is its own timed task; an unchanged list is handed over as background work

The slice test asks for at least four palette read spans (start, two or more
slices, arrange), so one task reading every action fails it.

* test(new tab): the native New Tab page reads the palette's actions in budgeted slices (red)

Follow-up of #32979 (lead: no partial fixes): the native page (tabs.nativeNewTabPage)
still read every palette action in one main-thread task, on the frame after it
showed or at the first keystroke. Fails on 4ad8532: one newTab.paletteActions
span per page.

* perf(new tab): the native New Tab page and the History page read the palette's actions as budgeted background work

Follow-up of #32979 (lead: no partial fixes). The native New Tab page
(tabs.nativeNewTabPage) read every palette action in one main-thread task on
the frame after it showed, or in its first keystroke's turn; the History page
read them in its open's turn. Both now go through AgentPaneNewTabActions:
- NativeNewTabModel asks for the actions (PaletteActionsRequest) and takes
  them when they come; a keystroke's turn reads nothing (the rows show the
  actions once listed, as Chrome's omnibox shows a provider's matches when
  they arrive). An adoption drops the earlier page's late answer.
- AgentPaneNewTabActions keeps every waiter of a listing (the web pages' fill
  and each native page), not only the last one.
- The History page opens without the actions and fills them as the New Tab
  pages do.

* fix(new tab): review of #33076: the test reads the new tab's own page; a closed native page starts no listing

- NativeNewTabSpareTests: the cold pass found the first, parked page's model;
  it now takes the page of the tab it opened (pages.existingView), checks it is
  a new model, and counts the reads before and after the keystroke.
- PaletteActionsRequest carries the page's wanted check: a native page that is
  gone (or adopted again) by the listing's first task starts no read.
lawrencecchen added a commit that referenced this pull request Nov 3, 2026
…ond Cmd-T does not load cold (#33136)

* test(new tab): Cmd-T makes the next spare and reads the bar's palette actions outside its open's frames (red)

PACING TRIAGE #32910 (120 Hz films of Cmd-T on feat 5851715): each
Cmd-T dropped 4-17 refreshes 50-150 ms after the key while the main thread
made the next spare page (a web view and a Chromium browser) in the key's own
pass, and read every palette action for the bar in one 7-10 ms task.

These fail on the base: the next spare is made within 60 ms of the open, and
the first and the last palette action are read in the same main-thread task.

* perf(new tab): Cmd-T makes the next spare after its open settled, and the bar's palette actions are read in background slices

PACING TRIAGE #32910: each Cmd-T (idle, dark, animations off, 120 Hz) dropped
4-17 refreshes 50-150 ms after the key; the app's own frame trace missed 8-11
vsyncs and the action committed at 29 ms p50. Two jobs of the open held the
main thread and neither shows in that open:

- The spare pool made the next spare page on the next main-actor turn after
  the handout (#29760): a web view and a Chromium browser in the key's own
  pass before its commit (4 ms), then its Chromium setup calls, show and
  first load in the open's frames. It is now made once input has been quiet
  for 250 ms after the handout (openSettle, the launch warm's wait), never in
  the open's turn or frames; a key or a click pushes it back. Chrome makes
  its spare renderer the same way: best-effort work after the navigation it
  served, never in the click's task. The next Cmd-T still finds a page that
  is loaded or loading.
- The New Tab bar's palette actions were read in one task the frame after
  the page showed (RegistryPaletteProvider.makeItems over every catalog
  action, 7-10 ms on an E core), and the whole bar was JSON-encoded on the
  main thread for each page. RegistryPaletteProvider.makeItems is now an
  ItemsPass that can read in pieces; PaletteController.commandListing()
  reads it a slice at a time, and AgentPaneNewTabActions runs the slices as
  budgeted background work (FrameBatchQueue .background: 0.5 ms a task,
  yielding to input and to the frame's budget), as Chrome's scheduler runs
  best-effort work in the idle time of each frame. The actions' JSON is
  encoded once, off the main thread; a list that did not change is not
  encoded or sent again.

The palette's own open (Shift-Cmd-P) still reads all rows at once: the
person waits for that list.

* test(new tab): follow the page's key to the store's; the Cmd-N first page gets the palette's actions

The red run at f28cced timed out waiting for the bar's actions: the test read
them under the provisional key Cmd-T makes, which the store's key replaces. The
slice check now asks for a first-action read in a turn with no last-action read.
New case: a page that joins newTabPages after the fill was asked for (Cmd-N's
first page) gets the actions (review of #32979).

* fix(new tab): review of #32979: the listing starts in its first background task; the refill keeps its time

- AgentPaneNewTabActions.list(wanted:_:) only queues: the palette's context
  and catalog are read in the first background task, which checks then
  whether a page lacks the actions (Cmd-N's page joins newTabPages after the
  call; the check in the caller's turn left it without actions).
- The pages take the list as background work too, and a newer request drops
  an older list still being encoded (generation fence on every request).
- PaletteCommandListing arranges the rows in the task after the last read,
  so no task holds more than one slice.
- NewTabSparePool: the handout's replacement comes openSettle (250 ms) after
  the handout and input does not push it back, so rapid Cmd-T still finds a
  page loaded or loading (Takes back the next-turn refill of #29760 only for
  the open's frames).

* test(new tab): count the bar's palette reads by their bench spans

The turn probe on two actions' palette titles read nothing for the first action
on the fleet (step 1d232f52); the read count now comes from the newTab.paletteActions
bench spans, one per main-thread task that reads palette actions.

* fix(new tab): review of #32979: the listing's start is its own timed task; an unchanged list is handed over as background work

The slice test asks for at least four palette read spans (start, two or more
slices, arrange), so one task reading every action fails it.

* test(new tab): a rapid second Cmd-T adopts a spare too (red)

Lead decision on #32979: a Cmd-T 150 ms after the one before must not load
cold. On #32979's head the pool keeps one spare and makes its replacement
250 ms after the handout, so the second of two Cmd-T 150 ms apart loads its
page cold. The test also asks that no spare is made in the pair's frames and
that dropAll leaves no spare parked.

* perf(new tab): two spares, made after an open settled as background work

A rapid second Cmd-T (150 ms after the first) loaded its New Tab page cold:
the pool kept one spare and made the next 250 ms after the handout. As Chrome
keeps a spare renderer ready and makes the next one as best-effort work after
the navigation it served:

- AgentPaneSpareSlot holds up to a capacity; the New Tab pool keeps two. A
  rapid pair adopts two loaded pages. Every parked spare draws the prepared
  context; the next one handed out gives the cover; dropAll (memory pressure,
  the last window closing) drops both.
- AgentPaneSpareRefiller (CmuxNextAgentPane) decides when a handout's
  replacement is made: after the key's frame committed, the open's tab-strip
  motion ended (TabStripView.whenStill) and the adopted page drew its first
  frame in its pane, then 250 ms of idle time (input does not push it back),
  as one background-class task on the app's scheduler (it yields to the
  person's input and the frame's budget). The second spare is made only after
  the first one drew, so two makes never share frames. The quiet-input warm
  stays armed as the backstop.

CmuxNextApp app_lines -2.

* test(new tab): the rapid-pair test asks that every opening of the pair is a spare, and waits for the fill without an issue

The red run (step 600126c2) failed at a count of 3 openings: a cold open also records the store tab's opening.

* fix(new tab): the second spare is made once input is quiet, not when the first one painted

Films of the head (6c09d472): the first Cmd-T of each film dropped 12-13
refreshes 130-200 ms after the key. A parked Chromium page reports its first
paint only once it shows, so the second spare's make (waiting on that paint)
ran in the first open's frames. It now waits for the pool's quiet-input warm
(750 ms, pushed back by each key or click).

* test(new tab): review of #33136: a 250 ms pair and an unpainted second spare; waits fail when they time out

- aSecondCmdT250msLaterRestartsTheRefill: the first open's refill wait must not
  land in a second open 250 ms later.
- anUnpaintedSecondSpareDoesNotHoldTheFirstsContext: the next spare handed
  out draws the pane's context while the other spare has not painted.
- The fill waits with until (fails on timeout); the dropAll check covers every
  spare made after the pair.

* fix(new tab): review of #33136: per-page prepare and covers, no capture in the key's turn, a later open restarts the refill, spares only at normal memory pressure

- prepareParked prepares each parked page that finished its handshake and
  first paint, and waits (once per page) only on the others; preparedFor
  follows the next spare handed out. It does nothing while an open settles.
- AgentPaneSpareCovers (CmuxNextAgentPane): one capture generation per page,
  taken when the page is parked, fitted or prepared; take() captures nothing
  (it took a WebKit snapshot of the next spare during Cmd-T's motion).
- AgentPaneSpareRefiller: each handout cancels the pending wait and starts its
  own (a generation fence), and no parked page changes until the latest open
  settled; the pool's quiet-input warm ends a wait whose page never framed.
- Memory: spares are made only at normal pressure; after a pressure drop the
  pool waits for normal; one spare on a Mac with less than 16 GB.
- debug.new_tab lists every parked spare (pid, footprint) and takes
  capacity N (an A/B of one against two spares on one build).
- warmNow's recycled-page path arms the second spare; whenStill's doc comment
  sits on whenStill.

* test(new tab): the refill and context tests fail without the review fixes

The red run at cfaa252 passed all three on the old code. Now:
- aSecondCmdTRestartsTheFirstOnesRefill: the second open's page never reports
  its frame, so the first open's pending wait must not make a spare in the
  500 ms after the second key.
- anUnpaintedSecondSpareDoesNotHoldTheFirstsContext: the context changes after
  both spares are parked (the second never paints).

* test(new tab): a launch makes one spare until one served an open; the rig sets capacity 2 and hands out the launch spare

Re-review of #33136 (B2): the second spare's load met the session's first
Cmd-T (12-13 dropped refreshes on that press).

* fix(new tab): the second spare only after a spare served an open; review lows

- NewTabSparePool makes its second spare only after this session used a spare
  (the first handout's refill makes it otherwise): a launch loads one page as
  before, and the second page's load never meets the session's first open,
  as Chrome makes its spare renderer after the navigation it served.
- dropAll clears the paint waits; the quiet-input backstop that ends a pending
  open prepares the parked pages; no native spare under memory pressure;
  debug.new_tab reports each spare's own window.

* test(new tab): a retarget to another window before any open still makes one spare (red)

Re-review of #33136: the one-page-at-launch gate was only on warmNow's trailing
calls, so a retarget (another window clicked first) made page 2 after the
quiet-input warm.

* fix(new tab): one page until a spare served an open, on every make path

One condition (wantsPage: fewer than capacity spares, or than one before a spare
served an open) in warmNow's make guard and scheduleWarm, instead of gates on
two trailing calls: a retarget, the return to normal memory pressure, the
capacity setter and the WebKit probe no longer make page 2 at launch.

---------

Co-authored-by: cmux-bot[bot] <348531757+cmux-bot[bot]@users.noreply.github.com>

This branch was previously deployed

1 inactive deployment
ci-routing-agent — fc66f4bb Deployed Oct 31, 2026 by teamleaderleo via namespace autofix #10291
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