Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
546a6b4
test(cmux-next): New Tab omnibar suggests reddit.com in a popover
teamleaderleo Oct 12, 2026
39cd805
fix(cmux-next): New Tab suggestions are an omnibar popover
teamleaderleo Oct 12, 2026
db2e3ed
fix(cmux-next): keep blank tabs movable; complete addresses from 3 le…
teamleaderleo Oct 12, 2026
b441431
Merge commit 'fa06cf7deb4410a8b804f39361cce205706771dd' into leo/next…
teamleaderleo Oct 12, 2026
4c5269b
cmux-next: Opacity in Ghostty mode repaints live and leaves Material …
teamleaderleo Oct 12, 2026
1c31b87
Agent pane: Show more lines in the changes view (cx-ncc.85, 3/3) (#19…
teamleaderleo Oct 12, 2026
13d1832
cmux-next: a terminal whose shell the daemon cannot reach says so, no…
teamleaderleo Oct 12, 2026
5eb8b52
fix(cmux-next): keep shortcut search keyboard parity (#19778)
teamleaderleo Oct 12, 2026
6da1efa
cmux-tui: a registry: tab's landed check builds on Windows (no bead) …
teamleaderleo Oct 12, 2026
75691a2
Agent pane: hideable chrome; Hide from the control, Show from its row…
teamleaderleo Oct 12, 2026
2f3e1f9
Chrome tab keys move between sidebar items from every focus (Leo 2026…
teamleaderleo Oct 12, 2026
e458387
New Tab: a calm default with a real Send button; no caret restart; no…
teamleaderleo Oct 12, 2026
bf3cac7
cx-ncc.85: pass git diff paths and context_lines through the App (#19…
teamleaderleo Oct 12, 2026
0cd1cc7
Recently Closed: leave out untouched New Tab pages and about:blank ro…
teamleaderleo Oct 12, 2026
f3bf24d
cmux-next menus: highlight rows in the theme accent (#19784)
teamleaderleo Oct 12, 2026
b2eb605
cmux-next: iconic public-domain paintings as backdrop art (cx-t2x.7) …
teamleaderleo Oct 12, 2026
87c537a
CmuxNext: the chat header's Terminal honors split-inherit-working-dir…
teamleaderleo Oct 12, 2026
21b316f
webviews: New Worktree shows the host's folder question instead of en…
teamleaderleo Oct 12, 2026
951211a
cmux-next: rapid workspace create/close runs in press order (cx-7cit9…
teamleaderleo Oct 12, 2026
5672519
ci: publish art thumbnails and a public manifest to files.cmux.com (c…
teamleaderleo Oct 12, 2026
9976fcb
test: keys typed right after Cmd-N all land in the New Tab field (cx-…
teamleaderleo Oct 12, 2026
47e8b36
cmux link sync pull: bring a Cloud folder back by a three-way merge (…
teamleaderleo Oct 12, 2026
201ae8d
webviews: chat-header-changes test expects the shortcut as the toolti…
teamleaderleo Oct 12, 2026
496f28a
New Tab: one scroller, soft edges (#19791)
teamleaderleo Oct 12, 2026
db50884
Merge feat-cmux-next; New Tab suggestions use the shared Popover
teamleaderleo Oct 12, 2026
f20bd14
test(gallery): typed-reddit allows the popover's own rows to reorder
teamleaderleo Oct 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
106 changes: 106 additions & 0 deletions .github/workflows/cmux-art-publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
name: cmux art publish

# Publishes cmux-next's backdrop art (cx-t2x.7) to files.cmux.com so the app can download the
# works that do not ship in it, and cmux.com/art can show the collection (cx-t2x.8).
# art/manifest.json records each work's SHA-256. Uploads (bucket cmux-binaries, public under
# https://files.cmux.com/):
# cmux-art/<sha256>.avif each art/full image and each 480x300 thumbnail, by its own hash
# (immutable, write-once)
# cmux-art/manifest.json art/manifest.json plus each work's image and thumbnail URL
# (replaced on every publish, cached for five minutes)
# A changed image gets a new hash and so a new object; no image is ever overwritten.
on:
push:
branches: [feat-cmux-next]
paths:
- "art/**"
- "Packages/macOS/CmuxNext/Sources/CmuxNextDesign/Resources/BackdropArt/**"
- ".github/workflows/cmux-art-publish.yml"
workflow_dispatch:

permissions:
contents: read

concurrency:
group: cmux-art-publish-${{ github.ref }}
cancel-in-progress: false

jobs:
publish:
if: github.repository == 'manaflow-ai/cmux' && github.ref == 'refs/heads/feat-cmux-next'
name: publish the backdrop art to files.cmux.com
environment: artifacts
runs-on: ${{ vars.LINUX_RUNNER || 'blacksmith-4vcpu-ubuntu-2404' }}
timeout-minutes: 15
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false

- name: Check every image against the manifest
run: |
set -euo pipefail
python3 -I - <<'EOF'
import hashlib, json, pathlib, re, sys
works = json.loads(pathlib.Path("art/manifest.json").read_text())["works"]
bad = []
for work in works:
if not re.fullmatch(r"[a-z0-9-]+", work["id"]) or not re.fullmatch(r"[0-9a-f]{64}", work["sha256"]):
bad.append(f"{work['id']}: malformed id or sha256")
continue
data = pathlib.Path(f"art/full/{work['id']}.avif").read_bytes()
if hashlib.sha256(data).hexdigest() != work["sha256"]:
bad.append(f"{work['id']}: art/full does not match the manifest's sha256")
thumbs = pathlib.Path("Packages/macOS/CmuxNext/Sources/CmuxNextDesign/Resources/BackdropArt")
public = json.loads(pathlib.Path("art/manifest.json").read_text())
base = public["remote"]
for work in public["works"]:
work["image"] = f"{base}{work['sha256']}.avif"
thumb = thumbs / f"{work['id']}.thumb.avif"
if thumb.exists():
work["thumbnail"] = f"{base}{hashlib.sha256(thumb.read_bytes()).hexdigest()}.avif"
else:
bad.append(f"{work['id']}: no thumbnail")
if not bad:
pathlib.Path("public-manifest.json").write_text(json.dumps(public, ensure_ascii=False, indent=1) + "\n")
print("\n".join(bad) or f"{len(works)} images match the manifest")
sys.exit(1 if bad else 0)
EOF

- name: Upload to R2 (write-once, by hash)
env:
AWS_ACCESS_KEY_ID: ${{ secrets.CF_R2_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.CF_R2_SECRET_ACCESS_KEY }}
AWS_DEFAULT_REGION: auto
R2_ENDPOINT: "https://${{ secrets.CF_R2_ACCOUNT_ID }}.r2.cloudflarestorage.com"
run: |
set -euo pipefail
python3 -I -c 'import json; [print(w["id"], w["sha256"]) for w in json.load(open("art/manifest.json"))["works"]]' |
while read -r id sha; do
python3 scripts/ci/upload-r2-object.py --write-once --endpoint-url "$R2_ENDPOINT" --bucket cmux-binaries \
--cache-control "public, max-age=31536000, immutable" --file "art/full/$id.avif" \
--key "cmux-art/$sha.avif" --content-type image/avif
done
for thumb in Packages/macOS/CmuxNext/Sources/CmuxNextDesign/Resources/BackdropArt/*.thumb.avif; do
sha="$(sha256sum "$thumb" | cut -d' ' -f1)"
python3 scripts/ci/upload-r2-object.py --write-once --endpoint-url "$R2_ENDPOINT" --bucket cmux-binaries \
--cache-control "public, max-age=31536000, immutable" --file "$thumb" \
--key "cmux-art/$sha.avif" --content-type image/avif
done
python3 scripts/ci/upload-r2-object.py --endpoint-url "$R2_ENDPOINT" --bucket cmux-binaries \
--cache-control "public, max-age=300" --file public-manifest.json \
--key cmux-art/manifest.json --content-type "application/json; charset=utf-8"

- name: Verify a sample through the public URL
run: |
set -euo pipefail
python3 -I -c 'import json; [print(w["sha256"]) for w in json.load(open("art/manifest.json"))["works"][::40]]' |
while read -r sha; do
got="$(curl -fsS --retry 3 --max-time 60 "https://files.cmux.com/cmux-art/$sha.avif?verify=$GITHUB_RUN_ID" | sha256sum | cut -d' ' -f1)"
[[ "$got" == "$sha" ]] || { echo "::error::cmux-art/$sha.avif served $got"; exit 1; }
done
thumb="$(python3 -I -c 'import json; print(json.load(open("public-manifest.json"))["works"][0]["thumbnail"])')"
curl -fsS --retry 3 --max-time 60 -o /dev/null "$thumb?verify=$GITHUB_RUN_ID"
served="$(curl -fsS --retry 3 --max-time 60 "https://files.cmux.com/cmux-art/manifest.json?verify=$GITHUB_RUN_ID" | sha256sum | cut -d' ' -f1)"
[[ "$served" == "$(sha256sum public-manifest.json | cut -d' ' -f1)" ]] || { echo "::error::cmux-art/manifest.json is not this run's"; exit 1; }
echo "published https://files.cmux.com/cmux-art/" | tee -a "$GITHUB_STEP_SUMMARY"
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,13 @@ extension ActionRegistry {
}

/// Like ``refuse(_:)`` for an explicit target that names nothing: the
/// control socket answers `not_found` instead of `unavailable`.
/// control socket answers `not_found` instead of `unavailable`. Quiet: a
/// keyboard or menu run whose target is gone (closed while the menu was
/// open, or by a close pressed just before) is a no-op, not a notice
/// naming a raw id (cx-7cit9).
public func refuseNotFound(_ reason: String) {
if isCapturingRefusal, capturedRefusal == nil { capturedRefusalIsNotFound = true }
refuse(reason)
refuse(reason, quiet: true)
}

/// ``capturingRefusal(_:)`` that also says whether the refusal was
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,11 @@ public nonisolated enum ActionSurfaceExport {
}

/// The binding table's default entries for `id` beyond its catalog key
/// (`KeyBindingDefaults`: browser tab keys, Ctrl-Cmd arrows for pane
/// resize, Ctrl-Cmd H/J/K/L for pane focus, list navigation, the palette keys): each entry's keys and its `when` clause
/// (`KeyBindingDefaults`: the Chrome tab keys, Ctrl-Cmd arrows for pane
/// resize, Ctrl-Cmd J/K for pane focus, list navigation, the palette keys): each entry's keys and its `when` clause
/// (text, or null), in table order, each key sequence once.
public static func defaultAliases(for id: ActionID) -> [[String: Any]] {
let entries = KeyBindingDefaults.tabSwitching + KeyBindingDefaults.actionAliases + KeyBindingDefaults.listNavigation
+ KeyBindingDefaults.homeNavigation
+ KeyBindingDefaults.paletteKeys
var seen: [[Shortcut]] = []
var aliases: [[String: Any]] = []
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ nonisolated enum HomeActionCatalog: ActionCatalogGroup {
cliName: "home archive-chief",
surfacePlan: ActionSurfacePlan(palette: .exempt(.noTargetSurface), cli: .offered, contextMenuExemption: .noTargetSurface)
),
// Cmd-Shift-[ / ] while Home is shown (KeyBindingDefaults.homeNavigation, a scoped default).
// No keys: Cmd-Shift-[ / ] are Chrome tab keys everywhere, Home too (KeyBindingDefaults.tabSwitching).
ActionDescriptor(
id: "home.previousConversation", title: t("action.home.previousConversation", "Previous Conversation"),
keywords: ["home", "conversation", "previous", "person", "people", "chief", "up"],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,15 +97,17 @@ nonisolated enum PaneActionCatalog: ActionCatalogGroup {
id: "focusLeft",
title: String(localized: "action.focusLeft", defaultValue: "Focus Pane Left", bundle: .module),
keywords: ["navigate"],
defaultShortcut: Shortcut(Shortcut.leftArrowKey, modifiers: [.option, .command]), category: .pane,
// Ctrl-Cmd-H: Cmd-Opt-Left is a Chrome tab key (KeyBindingDefaults.tabSwitching).
defaultShortcut: Shortcut("h", modifiers: [.control, .command]), category: .pane,
symbol: "arrow.left.square", surfaces: [.palette, .keyboard], targets: [.pane],
cliName: "pane focus-left"
),
ActionDescriptor(
id: "focusRight",
title: String(localized: "action.focusRight", defaultValue: "Focus Pane Right", bundle: .module),
keywords: ["navigate"],
defaultShortcut: Shortcut(Shortcut.rightArrowKey, modifiers: [.option, .command]), category: .pane,
// Ctrl-Cmd-L: Cmd-Opt-Right is a Chrome tab key (KeyBindingDefaults.tabSwitching).
defaultShortcut: Shortcut("l", modifiers: [.control, .command]), category: .pane,
symbol: "arrow.right.square", surfaces: [.palette, .keyboard], targets: [.pane],
cliName: "pane focus-right"
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -159,19 +159,19 @@ nonisolated enum TabActionCatalog: ActionCatalogGroup {
surfaces: [.palette, .contextMenu], targets: [.tab], cliName: "tab clear-name"
),
// Previous / Next (Leo 2026-10-09): the focused pane's tabs when it has 2+, else the
// sidebar rows (`PreviousNext`). The browser-style keys run these; Ctrl-Tab and
// Ctrl-PageDown are `KeyBindingDefaults.tabSwitching`.
// sidebar rows (`PreviousNext`). No default key: the Chrome tab keys select sidebar
// items (`KeyBindingDefaults.tabSwitching`, Leo 2026-10-12).
ActionDescriptor(
id: "navigate.next",
title: String(localized: "action.navigate.next", defaultValue: "Next Tab or Workspace", bundle: .module),
keywords: ["tab", "workspace", "switch", "next", "sidebar"], defaultShortcut: Shortcut("]", modifiers: [.command, .shift]),
keywords: ["tab", "workspace", "switch", "next", "sidebar"],
category: .tab, symbol: "chevron.right.square", surfaces: [.palette, .keyboard, .menu], targets: [.tab],
mainMenu: .file
),
ActionDescriptor(
id: "navigate.previous",
title: String(localized: "action.navigate.previous", defaultValue: "Previous Tab or Workspace", bundle: .module),
keywords: ["tab", "workspace", "switch", "previous", "sidebar"], defaultShortcut: Shortcut("[", modifiers: [.command, .shift]),
keywords: ["tab", "workspace", "switch", "previous", "sidebar"],
category: .tab, symbol: "chevron.left.square", surfaces: [.palette, .keyboard, .menu], targets: [.tab],
mainMenu: .file
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,14 +51,14 @@ nonisolated enum WorkspaceActionCatalog: ActionCatalogGroup {
ActionDescriptor(
id: "nextSidebarTab",
title: String(localized: "action.nextSidebarTab", defaultValue: "Next Sidebar Item", bundle: .module),
keywords: ["switch", "workspace", "sidebar", "item", "section"], defaultShortcut: Shortcut("]", modifiers: [.control, .command]),
keywords: ["switch", "workspace", "sidebar", "item", "section", "next", "tab"], defaultShortcut: Shortcut("]", modifiers: [.command, .shift]),
category: .workspace, symbol: "chevron.down.square", surfaces: [.palette, .keyboard, .menu],
targets: [.workspace], cliName: "workspace next", mainMenu: .file
),
ActionDescriptor(
id: "prevSidebarTab",
title: String(localized: "action.prevSidebarTab", defaultValue: "Previous Sidebar Item", bundle: .module),
keywords: ["switch", "workspace", "sidebar", "item", "section"], defaultShortcut: Shortcut("[", modifiers: [.control, .command]),
keywords: ["switch", "workspace", "sidebar", "item", "section", "previous", "tab"], defaultShortcut: Shortcut("[", modifiers: [.command, .shift]),
category: .workspace, symbol: "chevron.up.square", surfaces: [.palette, .keyboard, .menu],
targets: [.workspace], cliName: "workspace previous", mainMenu: .file
),
Expand Down
Loading
Loading