Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
a0ff10a
test: the New Tab style switch draws four looks, the dots that name a…
teamleaderleo Oct 14, 2026
c403b89
cmux NEXT: New Tab style switch: the dots again, with three looks to …
teamleaderleo Oct 14, 2026
695dfab
cmux NEXT: the New Tab style switch hides its radio circles
teamleaderleo Oct 14, 2026
726b376
Merge origin/feat-cmux-next (47f12a74704e) into feat/newtab-style-var…
teamleaderleo Oct 14, 2026
ffde5a5
cmux NEXT: New Tab chat rows lead with the harness mark
teamleaderleo Oct 14, 2026
1ad79b4
test: the New Tab style radios draw no native dot (red)
teamleaderleo Oct 14, 2026
fdf7edc
fix: the New Tab style switch draws no native radio dots
teamleaderleo Oct 14, 2026
e8c8785
Merge commit '29227a186e64293db86b10d566b9e4267e97e248' into fix/newt…
teamleaderleo Oct 14, 2026
bcf5539
Merge origin/fix/newtab-style-radios (e8c8785b6b71) into feat/newtab-…
teamleaderleo Oct 14, 2026
34dbfaf
chore(next): PR autofix at bcf553986de3
Oct 14, 2026
8274bf9
Merge origin/feat-cmux-next into feat/newtab-style-variants: Leo's ro…
teamleaderleo Oct 14, 2026
c6e0f82
test: the Labs New Tab style switch look reaches every New Tab (red)
teamleaderleo Oct 14, 2026
bccd53e
cmux NEXT: the New Tab style switch is a Labs option in every build
teamleaderleo Oct 14, 2026
2e00ed7
cmux NEXT: Labs New Tab style switch strings in every locale
teamleaderleo Oct 14, 2026
c57e324
test: format the New Tab style switch test
teamleaderleo Oct 14, 2026
b9a030a
chore(next): PR autofix at 2e00ed74f3fe
Oct 14, 2026
d5f7e2a
chore: regenerate settings exports and strings for labs.newTabStyle
teamleaderleo Oct 14, 2026
b1fb7f4
Merge commit 'abc641f41d9c1c234440a70ff73f3d010cab0432' into feat/new…
teamleaderleo Oct 14, 2026
0d27b6e
Merge remote-tracking branch 'origin/feat/newtab-style-variants' into…
teamleaderleo Oct 14, 2026
3b533a1
test: the Labs style switch test starts NSApplication like the other …
teamleaderleo Oct 14, 2026
8dfdd1e
chore(next): PR autofix at 3b533a1c2e09
Oct 15, 2026
1c282a0
Revert "cmux NEXT: New Tab chat rows lead with the harness mark"
teamleaderleo Oct 15, 2026
f578fc9
cmux NEXT: the round style dots sit and fade exactly as #20473
teamleaderleo Oct 15, 2026
d2ce1a7
Merge commit '1cb7e587bffecb7f3e0358825af43cfc4fbab36c' into feat/new…
teamleaderleo Oct 15, 2026
f3c3396
chore: regenerate settings exports and strings on the merged head
teamleaderleo Oct 15, 2026
085ec95
Merge commit '4c9081bf692913dffacc65ff585660b0b75eec44' into feat/new…
teamleaderleo Oct 15, 2026
8bfbc0a
chore(next): PR autofix at 085ec950bd14
Oct 15, 2026
b5fc9ab
test: every New Tab style switch look is a Debug Settings choice
teamleaderleo Oct 15, 2026
8093a3f
cmux NEXT: New Tab style switch looks in Debug Settings, not Labs
teamleaderleo Oct 15, 2026
15ead42
Merge commit '8bfbc0a2340aef8d30229f4718d1c74413205718' into feat/new…
teamleaderleo Oct 15, 2026
33aa6c9
Merge commit '0664bd9847b5f7c59e2b89bf117660bb49138bf1' into feat/new…
teamleaderleo Oct 15, 2026
49e1cc7
fix: tooltip-allow for the style switch's dot and sketch titles
teamleaderleo Oct 15, 2026
17a5dc3
refactor: New Tab style switch tunable lives with the agent pane
teamleaderleo Oct 15, 2026
257ebd0
Merge feat-cmux-next into feat/newtab-style-variants
teamleaderleo Oct 20, 2026
46577c6
Merge feat-cmux-next into feat/newtab-style-variants
teamleaderleo Oct 21, 2026
b54b44a
New Tab style switch: its tunable in its own file; the hint name show…
teamleaderleo Oct 21, 2026
6221a05
Merge branch 'feat-cmux-next' into feat/newtab-style-variants
teamleaderleo Oct 21, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ public nonisolated struct AgentPaneNewTab: Codable, Sendable, Equatable {
/// The template (`tabs.newTabTemplate`: "cards", "default", "composer", "threads", "console",
/// "classic" or "terminal"); nil follows `layout`. The page validates it.
public var template: String?
/// The style switch's look (Debug Settings `newTab.styleSwitch`); nil is the page's default.
public var styleSwitch: String?
/// The agent last picked.
public var lastAgent: String?
/// The harness and model picked last, anywhere and per folder; nil when none was.
Expand Down Expand Up @@ -82,6 +84,7 @@ public nonisolated struct AgentPaneNewTab: Codable, Sendable, Equatable {
self.projects = projects
self.layout = layout
self.template = template
self.styleSwitch = AgentPaneTunables.newTabStyleSwitch.override?.rawValue
self.lastAgent = lastAgent
self.lastPicks = lastPicks
self.home = home
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
public import CmuxNextDesign

nonisolated extension AgentPaneTunables {
/// The New Tab page's style switch look. The page draws round for no override, so only an
/// override is sent (`AgentPaneNewTab.styleSwitch`).
public static let newTabStyleSwitch = Tunable<AgentPaneNewTabStyleSwitch>.choice(
"newTab.styleSwitch", .tabs, "New tab style switch",
help: "How the New Tab page shows its styles. Applies to the next new tab.",
default: .round, code: "AgentPaneTunables.newTabStyleSwitch")
}

/// The New Tab page's style switch looks (webviews newtab/templates.ts STYLE_SWITCHES). Round is
/// the page's own look (#20473); the others are opt-in in Debug Settings (Lawrence 2026-10-14: no
/// Labs; a look moves to Settings once it earns it).
public nonisolated enum AgentPaneNewTabStyleSwitch: String, Sendable, CaseIterable, TunableChoice {
case round, hint, dots, strip, names

public var tunableTitle: String {
switch self {
case .round: "Round dots (#20473)"
case .hint: "Dots that name a pick"
case .dots: "Chip dots (before #20127)"
case .strip: "Layout sketches"
case .names: "Style names (#20336)"
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,7 @@ public nonisolated struct AgentPaneTunables {
modelMenu.descriptor,
pagesFollowLinkPlacement.descriptor,
newTabPrivateChatLists.descriptor,
newTabStyleSwitch.descriptor,
newTabWidgets.descriptor,
pageHost.descriptor,
reasoningSlider.descriptor,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
@testable import CmuxNextAgentPane
@testable import CmuxNextApp
import AppKit
import CmuxNextActions
import CmuxNextDesign
import CmuxNextSettings
Expand Down Expand Up @@ -55,4 +56,23 @@ struct NewTabStyleSwitchTests {
for save in saves { await save.value }
#expect(settings.snapshot.newTabTemplate == .threads)
}

/// Every look the New Tab style switch has is a Debug Settings choice (`newTab.styleSwitch`; no
/// Labs, Lawrence 2026-10-14) that reaches the next New Tab; with none set the page draws its
/// own look, round dots.
@Test func eachDebugStyleSwitchLookReachesTheNextNewTab() throws {
_ = NSApplication.shared
let services = AppServices(environment: AppEnvironment.current([:]))
let store = TunableStore.shared
store.activate(file: nil)
store.register(AgentPaneTunables.all)
defer { store.reset([AgentPaneTunables.newTabStyleSwitch.key]) }

#expect(NewTabPage.sparePage(services).styleSwitch == nil)
for look in AgentPaneNewTabStyleSwitch.allCases {
#expect(store.set(AgentPaneTunables.newTabStyleSwitch.key, .choice(look.rawValue)) != nil)
#expect(NewTabPage.sparePage(services).styleSwitch == look.rawValue)
}
#expect(Set(AgentPaneNewTabStyleSwitch.allCases.map(\.rawValue)) == ["round", "hint", "dots", "strip", "names"])
}
}
1 change: 1 addition & 0 deletions webviews/src/agent-session/acpmux/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6577,6 +6577,7 @@ function AcpmuxPane() {
{!harnessesOpen && showNewTab && !newTab.history && (
// The page's style, under it; a pick shows in place at once.
<StyleSwitch
look={newTab.styleSwitch}
current={pageStyle(shownTemplate(newTab))}
onCancel={() => window.dispatchEvent(new Event(FOCUS_LOCATION_EVENT))}
onPick={(style) =>
Expand Down
6 changes: 5 additions & 1 deletion webviews/src/agent-session/acpmux/NewTabPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import {
type AcpmuxSessionEntry,
} from "./sessionList";
import { type StringKey, type Translate, translate, useT } from "./i18n";
import { parseNewTabTemplate, type NewTabTemplate } from "./newtab/templates";
import { parseNewTabTemplate, parseStyleSwitch, type NewTabTemplate, type StyleSwitchLook } from "./newtab/templates";
import { seedWarmNewTab } from "./newtab/warmSnapshot";

/// The three things a new tab can become (#16620). Order is the switch's order and Tab's cycle.
Expand Down Expand Up @@ -89,6 +89,8 @@ export type NewTabHost = {
layout: "a" | "b";
/// The saved template (`tabs.newTabTemplate`); unset follows `layout` (newtab/templates.ts).
template?: NewTabTemplate;
/// The style switch's look (Debug Settings `newTab.styleSwitch`); unset is the default look.
styleSwitch?: StyleSwitchLook;
/// The agent last picked (decision Q3).
lastAgent?: string;
/// The home folder, so `~/path` reads as a folder.
Expand Down Expand Up @@ -125,6 +127,7 @@ export function newTabHost(handshake: { newTab?: unknown; cwd?: unknown }): NewT
const omnibar = omnibarContext(object.omnibar);
const openTabs = Array.isArray(object.openTabs) ? omnibarContext({ tabs: object.openTabs })?.tabs : undefined;
const template = parseNewTabTemplate(object.template);
const styleSwitch = parseStyleSwitch(object.styleSwitch);
return {
hotkeys,
initialKind,
Expand All @@ -142,6 +145,7 @@ export function newTabHost(handshake: { newTab?: unknown; cwd?: unknown }): NewT
: {}),
layout: object.layout === "a" ? "a" : "b",
...(template ? { template } : {}),
...(styleSwitch ? { styleSwitch } : {}),
...(typeof object.lastAgent === "string" && object.lastAgent ? { lastAgent: object.lastAgent } : {}),
...(typeof object.home === "string" && object.home.startsWith("/") ? { home: object.home } : {}),
...(typeof object.inputToken === "string" && object.inputToken ? { inputToken: object.inputToken } : {}),
Expand Down
49 changes: 38 additions & 11 deletions webviews/src/agent-session/acpmux/newtab/NewTabScreen.gallery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,18 @@ async function waitForTabJump(
);
}

/// Picks Threads on the style switch and waits for the save and the check (every look is radios).
const pickThreads = async (ctx: PlayContext) => {
await ctx.waitFor(() => ctx.document.querySelector(".nt-style input[value=default]:checked"));
await ctx.click({ role: "radio", name: "Threads" });
await ctx.waitFor(() =>
galleryNativeCalls(ctx.document).some(
(call) => call.method === "newTab.setTemplate" && call.params?.template === "threads",
),
);
await ctx.waitFor(() => ctx.document.querySelector(".nt-style input[value=threads]:checked"));
};

/// Types a prompt, pastes an image and starts Now Playing, then checks that the composer's card
/// and its field are where the empty page drew them (Leo 2026-10-19: the composer kept moving).
async function holdsItsPlace(ctx: PlayContext): Promise<void> {
Expand Down Expand Up @@ -1060,19 +1072,34 @@ export default agentPaneEntry({
},
},
"style-switch": {
note: "The New Tab style dots (Leo 2026-10-14): one small round dot per style under the page, the current one in the accent and the accent dot sliding to a pick. A pointer over a dot shows its name; a pick shows the picked name for a moment, then fades it, and is saved as tabs.newTabTemplate.",
note: "The New Tab style dots with nothing set in Debug Settings (Leo 2026-10-14, #20473): one small round dot per style under the page, the current one in the accent and the accent dot sliding to a pick. A pointer over a dot shows its name; a pick shows the picked name for a moment, then fades it, and is saved as tabs.newTabTemplate.",
ready: newTab({ template: "default" }),
snapshot: noChat(manySessions(4)),
play: async (ctx) => {
await ctx.waitFor(() => ctx.document.querySelector(".nt-style input[value=default]:checked"));
await ctx.click({ role: "radio", name: "Threads" });
await ctx.waitFor(() =>
galleryNativeCalls(ctx.document).some(
(call) => call.method === "newTab.setTemplate" && call.params?.template === "threads",
),
);
await ctx.waitFor(() => ctx.document.querySelector(".nt-style input[value=threads]:checked"));
},
play: pickThreads,
},
"style-switch-hint": {
note: "Debug Settings newTab.styleSwitch = hint: a dot per style on a chip, its name as the tooltip, and a pick's name over the dots for about a second.",
ready: newTab({ template: "default", styleSwitch: "hint" }),
snapshot: noChat(manySessions(4)),
play: pickThreads,
},
"style-switch-dots": {
note: "Debug Settings newTab.styleSwitch = dots: the dots as they were before #20127, named by tooltip.",
ready: newTab({ template: "default", styleSwitch: "dots" }),
snapshot: noChat(manySessions(4)),
play: pickThreads,
},
"style-switch-strip": {
note: "Debug Settings newTab.styleSwitch = strip: a 14px sketch of each style's layout, named by tooltip.",
ready: newTab({ template: "default", styleSwitch: "strip" }),
snapshot: noChat(manySessions(4)),
play: pickThreads,
},
"style-switch-names": {
note: "Debug Settings newTab.styleSwitch = names: the style names of #20336, the current one on a small fill that slides to a pick.",
ready: newTab({ template: "default", styleSwitch: "names" }),
snapshot: noChat(manySessions(4)),
play: pickThreads,
},
"no-template-dots": {
note: "New Tab shows no template dots and no default-kind toggle: the template is set in Settings, and a terminal opens only when picked (Leo 2026-10-12). The host payload is what it sent before, dots on.",
Expand Down
Loading