Skip to content

Fix Home dock Channels: the Chromium page gets the keyboard at its first open - #29386

Merged
cmux-bot[bot] merged 4 commits into
feat-cmux-nextfrom
fix/home-dock-channels-keyboard
Oct 31, 2026
Merged

cmux-bot[bot] merged 4 commits into
feat-cmux-nextfrom
fix/home-dock-channels-keyboard

Conversation

@lawrencecchen

@lawrencecchen lawrencecchen commented Oct 31, 2026 •

Copy link
Copy Markdown
Contributor

Summary

The docked Home's Channels page in Chromium never got the keyboard at its first open. The key window stayed the cmux window and its first responder stayed the hidden WebKit stand-in (PageWKWebView), so typing into Channels went nowhere until a click. Found by the journey found-desync-channels-type while covering a review case of #29272 (STATUS 1582).

Cause: TabContent.childWindowTab found a cmux page in Chromium only when a page tab's content IS the page. The docked Home is an internal page whose content is HomeDockView, and Channels is a page inside it. So for the dock the focus applier found no Chromium page. It took the page window's key change as unchosen, applied the model again, blurred Chromium pages and took the keys back for the cmux window.

Fix: a container view names the page it shows now (PageWebViewContainer; HomeDockView names Channels while Channels shows). childWindowTab finds it through PageWebView.shown(in:). The applier then gives the Channels page the keyboard as it does for any page tab in Chromium, and the page window's key change is the pane's own. Messages mode is unchanged (no page shows there).

Testing

  • Red first: PageWebViewShownInContainerTests (CmuxNextPagesTests), commit bf49e3f. Fleet step 03d1e6b8ebf66877cde96fa7 failed on its own assertion (PageWebView.shown(in: dock) → nil).
  • Green at acfd611: step 038ec695373a1987c1766761 passed PageWebViewShownInContainerTests, ChromiumPageFocusInvariantTests and PageWebViewChromiumTests.
  • Compile of the head: job 34a7e534fe7f6341d9424037 (CmuxNext with tests, app, cmux-tui) passed.
  • Journey scripts/journeys/journeys/fuzz/found-desync-channels-type.json (this PR adds the file; the harness lands with journeys: the action fuzzer (correctness and speed) as a mode of the smoke-gate driver #29071). Run from a checkout of the harness branch, whole host, app active, through the mini scheduler (spec channels-keys-journeys), cmux20.

Observed / Not run

Observed:

Not run:

  • Typed keys into Channels: debug.key's sequence mode has no event timestamp on the capture minis, so the journey reads the focus state, not a typed character.
  • A Channels page opened as its own tab (home.channels): there the content is the page itself, which the old rule already covered.

Changelog

Fixed: Typing in Home's Channels view works the first time Channels opens, without a click first

Proof

No capture: there is no visual change; the change is which window holds the keyboard. The journey's frames are in its run folder.


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


Summary by cubic

Fixes the docked Home's Channels view in Chromium never getting the keyboard at its first open, so typing into Channels works without clicking first.

The focus applier only found a Chromium page when the page tab's content is the page itself. For the docked Home, an internal page whose content is HomeDockView, Channels sits inside that view, so no Chromium page was found; the cmux window kept the keys and the hidden WebKit stand-in stayed first responder. A container view now names the page it shows (PageWebViewContainer; HomeDockView names Channels while Channels shows), and TabContent.childWindowTab finds it through PageWebView.shown(in:). The applier then gives the Channels page the keyboard like any other Chromium page tab. Messages mode shows no page, so it is unaffected.

Written for commit daa0827. Summary will update on new commits.

View guided diff Turn on auto-fix

… page its tab shows

PageWebViewShownInContainerTests: PageWebView.shown(in:) must name the page a container shows now (the
docked Home's Channels page inside HomeDockView). It fails here: shown(in:) keeps the old rule (only a
content that IS a page), the rule TabContent.childWindowTab has on feat. Because of it, Channels in
Chromium never gets the keyboard at its first open (STATUS 1582): journey
scripts/journeys/journeys/fuzz/found-desync-channels-type.json, red on feat f2f3083b (cmux19), on
#29272 at 976b215 and at ad21c67 (whole host, mini scheduler, 2026-10-31 12:09-12:20Z): key window
ShellWindow, first responder the hidden PageWKWebView.
…ard at its first open

Channels in Chromium never got the keyboard at its first open (STATUS 1582; red on feat f2f3083b,
#29272 at 976b215 and ad21c67, whole host, 2026-10-31). The dock focused the page before its
Chromium page existed, so the hidden WebKit stand-in became first responder. At attach the page
granted Chromium focus itself and its page window became key, but TabContent.childWindowTab saw
only a page tab whose content IS a page. For the docked Home (an internal page whose content is
HomeDockView) the focus applier found no Chromium page: it took the key change as unchosen,
applied the model again, blurred Chromium pages and reclaimed the keys for the cmux window.

Now a container view names the page it shows (PageWebViewContainer; HomeDockView: Channels while it
shows), and childWindowTab finds it through PageWebView.shown(in:). The applier then gives the
Channels page the keyboard like any page tab in Chromium (one key change, the page recorded), the
page window's key change is the pane's own, and G1 counts that page window as its pane's.
@lawrencecchen lawrencecchen added the risk:normal Risk tier (docs/ci/risk-tiers.md) label Oct 31, 2026
@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 acfd611: 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=pr29386 -f ref=fix/home-dock-channels-keyboard -f platform=macos -f backend_mode=local

Shipped in:

@blacksmith-sh

blacksmith-sh Bot commented Oct 31, 2026 •

Copy link
Copy Markdown

Found 1 test failure on Blacksmith runners:

Failure

Test View Logs
test_every_listed_hook_is_one_glaeda_released_with_the_check (main.SigningHooksInSt
epWithGlaedaTests.test_every_listed_hook_is_one_glaeda_released_with_the_check)/
test_every_listed_hook_is_one_glaeda_released_with_the_check (main.SigningHooksInSt
epWithGlaedaTests.test_every_listed_hook_is_one_glaeda_released_with_the_check)
View Logs

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

@lawrencecchen

Copy link
Copy Markdown
Contributor Author

Review (claude-opus-5.5 independent reviewer): SHIP at acfd611
reviewer-session: 12ddbbda-d8fa-47a7-8876-3bd5329d49e0
author-session: abd34adf46f857456
perf claim: none

Scope: diff 967b0e9..acfd611 (HomeDockView.swift, CEFPageChromiumPage.swift, new PageWebViewContainer.swift, PageWebViewShownInContainerTests.swift, the journey JSON). I read the code with git show <sha>:<path>. I did not build or run anything. I found no correctness defect. The notes below do not block the merge.

Verified (file:line at the head)

  • Callers of childWindowTab (git grep at the head): FocusEffectApplier.swift:131/137 (.agentPage/.page in moveResponder), :267-268 (reclaimKeyAfterActivation), :394 (paneShowingChildWindowPage), KeyRouter+TypeAhead.swift:114 (keyInOtherPage), InputObservationBuilder.swift:34 (W1/W4/W5 childWindowTabs). At this commit, ChildPageGeometry.sample and DebugKey.pageWindow do not call it: they still match .browser only (ChildPageGeometry.swift:52, DebugKey.swift:259).
  • Channels mode: shownPageView returns channels (HomeDockView.swift:19), so .page returns focusPage(page) (FocusEffectApplier.swift:137). This is correct for Channels: the dock has no native primary input in Channels (primaryInputView is nil unless the mode is messages, HomeDockView+PrimaryInputTarget.swift:12). reclaimKeyAfterActivation no longer takes the keys from the Channels page window. A page-window key change over the dock now finds the dock pane (paneShowingChildWindowPage), so a click is .chosenPage and not "unchosen". W5 no longer flags a key Channels page window while the model targets .page(dock).
  • Messages mode: shownPageView is nil, so childWindowTab is nil and .page keeps landKeyboardInPrimaryInput and the focusTarget path, as on the base. A hidden Channels page that was made earlier is never named.
  • Messages -> Channels: show() sets mode before it unhides (HomeDockSwitchView.swift show). focusContent(.channels) calls channels.focusPage(), which calls setFocused(true) (PageWebView.swift:509). The page window becomes key without a click, which gives .unchosenPage and .responder(.windowOrNone), and the reducer reapplies (Accepted::Reapply, reducer.rs:473). Then .page records the page through pageFocus.focus. This agrees with the head journey: key window CefNSWindow, applier child page recorded, consistent: true, no mismatches (step 11 of result.json).
  • Channels -> Messages: the mode change sends no focus event. The makeFirstResponder call in focusPrimaryInput reports .content(dock), which is Accepted::Same and a no-op (reducer.rs:487-488). The keys still come back, for these reasons: show() sets mode = .messages before it hides Channels. Chromium orders the page window out during AppKit's hide walk, and the cmux window becomes key (the CEFTab.swift isShown comment records this). ChildPageFocus.keysReturned then blurs the page and sends .windowOrNone (FocusEffectApplier.swift init observer). The reapply then sees childWindowTab == nil and lands the primary input. Because the mode changes first, a grant during the hide walk cannot show the window again (the loop that comment describes). The base had no reverse gap because Channels never held the keys there. This PR adds a dependency on this hide-walk path (note 2).
  • KeyRouter: keyInOtherPage now counts the dock's Channels window as "another page" when a different pane has focus. That is correct, and better than the base. typesIntoPrimaryInput does not change (no primary input in Channels). routePageKey is .browserPage only and does not change.
  • G1 on the head journey (page window (618, 268, 892, 692) covers no pane): I checked that this does not come from this PR. After new_workspace the window shows a New Tab page (clip frame-00022). Its page window fills the content area under the 28 pt tab row (window 410,268 1100x720). ChildPageGeometry.sample at this head counts only .browser hosts. Fix desync after Cmd-N: a late Chromium page gets the keys through the focus model; G1 counts cmux pages #29272 changes sample to use childWindowTab. These two PRs work together: without this PR, Fix desync after Cmd-N: a late Chromium page gets the keys through the focus model; G1 counts cmux pages #29272 would report the dock's Channels page window as covering no pane.

Notes (do not block)

  1. The regression guard tests the seam, not the bug site. PageWebViewShownInContainerTests uses a test Dock. If you revert HomeDockView: PageWebViewContainer or the childWindowTab change, it still passes. Only the journey (whose harness comes with journeys: the action fuzzer (correctness and speed) as a mode of the smoke-gate driver #29071) protects the real path. An app test of childWindowTab is hard because of the as? CEFPageChromiumPage cast. At least test HomeDockView.shownPageView in both modes if a HomeDockView can be built in CmuxNextAppTests.
  2. The switch back to Messages is not tested. The journey's finally switches back but checks nothing. Add a step after home.switchView messages: key window ShellWindow, first responder in the message box, and debug.focus consistent (W4: applier child page nil). This step would show at once if a CEF change stops ordering the page window out in the hide walk.
  3. Other .page readers still do not see the dock's Channels page. TabContent.inputReadiness (TabContent.swift:42, so Channels has no type-ahead at first open), KeyRouter.focusedPage (KeyRouter+Context.swift:205, page commands and context) and InternalPageView.focusTarget (InternalPageView.swift:83, the WebKit fallback in Channels) all still use view.content as? PageWebView. These existed on the base and are not regressions. Change them to PageWebView.shown(in:) in a follow-up so that all page lookups use the same rule.

@lawrencecchen

Copy link
Copy Markdown
Contributor Author

Checks at acfd611 before ready-to-land: 2 red, both base findings. nightly-guards / workflow-guard-tests / release-notary (JSONDecodeError in its test run, https://github.com/manaflow-ai/cmux/actions/runs/41203808710/job/124544758726) is red on unrelated open PRs too: #29388, #29384, #29383, #29381, #29377. nightly-guards / Guard status is its wrapper. This PR changes no workflow or release file. Receipt: #29386 (comment). Review notes 1-3 (an app test of HomeDockView.shownPageView, a journey check of the switch back to Messages, and the other page readers on PageWebView.shown(in:)) go to a follow-up.

@lawrencecchen lawrencecchen added the ready-to-land Reviewed at this head; next-autoland merges it into feat-cmux-next when its only reds are base reds label Oct 31, 2026
@cmux-bot
cmux-bot Bot temporarily deployed to ci-routing-agent October 31, 2026 19:43 Inactive
@cmux-bot
cmux-bot Bot temporarily deployed to ci-routing-agent October 31, 2026 19:43 Inactive
@cmux-bot
cmux-bot Bot merged commit cf41428 into feat-cmux-next Oct 31, 2026
170 of 175 checks passed
@cmux-bot
cmux-bot Bot deleted the fix/home-dock-channels-keyboard branch October 31, 2026 20:08
lawrencecchen added a commit that referenced this pull request Nov 3, 2026
…he Channels page (#33869)

* test(home): red: the page readers must find the Home dock's Channels page through its container

HomeDockChannelsPageReadersTests: TabContent.inputReadiness and InternalPageView.focusTarget read only
'content as? PageWebView', so for the docked Home in Channels mode (the page inside HomeDockView, a
PageWebViewContainer) the type-ahead has no input readiness and the focus target is the dock view.
#29386 review note 3. Fails here.

* fix(home): the type-ahead, the key router's focused page and the tab's focus target find the Home dock's Channels page

#29386 review note 3. TabContent.inputReadiness, KeyRouter.focusedPage and InternalPageView.focusTarget
now find a tab's page through PageWebView.shown(in:), the rule TabContent.childWindowTab already uses:
the content itself when it is a page, else the page a PageWebViewContainer shows (the docked Home's
Channels page while Channels shows). Before, for Channels: no type-ahead (keys typed at its first open
were not held for the page), no page commands or page context from the key router, and on a WebKit
fallback the dock view (which takes no keys) became first responder. Messages mode is unchanged: the
dock shows no page there, and its primary input still comes first in focusTarget.

* fix(home): the type-ahead stays off for the Home dock's Channels page

Review of #33869 (FIX at 181170d): TabContent.inputReadiness through PageWebView.shown(in:) gave the
docked Channels page a type-ahead hold. That page focuses no input when it opens, so its readiness stays
not ready and the hold ate its own bare keys (j, k, r, t, c, e) until the 2 s limit. inputReadiness reads
a page tab's own page again; the test now guards that a page a container shows gets no readiness.
KeyRouter.focusedPage and InternalPageView.focusTarget keep the shown(in:) rule. The standalone Channels
tab (its content is the page) has the same hold on bare keys today; that is not changed here.

This branch was previously deployed

1 inactive deployment
ci-routing-agent — daa08273 Deployed Oct 31, 2026 by cmux-bot[bot] via cmux-tui tree preflight #91181
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-to-land Reviewed at this head; next-autoland merges it into feat-cmux-next when its only reds are base reds risk:normal Risk tier (docs/ci/risk-tiers.md)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant