Merge pull request #14335 from lancy69/fix/14324-remove-workbench-wide

fix(studio): remove redundant workbench widening mode
This commit is contained in:
YHH authored and GitHub committed 2026-10-24 06:58:22 -07:00
commit ef79a2215e
17 files changed
+72 -160

No files matched your search

+3 -8
View File
@@ -112,13 +112,8 @@ async function main() {
const js = (src) => win.webContents.executeJavaScript(src);
const reserved = () => js(`(() => { const e = document.querySelector('.bview'); if (!e) return null; const b = e.getBoundingClientRect(); return { x: b.left, y: b.top, width: b.width, height: b.height }; })()`);
// The browser tab beside the conversation is a switch for that view itself, so
// the page is widened with the browser's own wide control, which is how the
// person makes it the whole pane.
await until("the wide control on the browser panel", () => js(`!!document.querySelector('[data-action="browser.wide"]')`));
await js(`document.querySelector('[data-action="browser.wide"]').click()`);
// A page that is put away is as wide as an agent's viewport, so wide means drawn
// and wider than it was beside the conversation.
await until("the full-view control on the browser panel", () => js(`!!document.querySelector('[data-action="browser.full"]')`));
await js(`document.querySelector('[data-action="browser.full"]').click()`);
await until("the page drawn full width", async () => (!beneath() && width() > aside.width ? guest.getBounds() : null));
// The panel widens over a moment; the rectangle is read once it has stopped.
const same = still;
@@ -149,7 +144,7 @@ async function main() {
process.stdout.write(` (no screenshot: ${err.message})\n`);
}
await js(`document.querySelector('[data-action="browser.wide"]').click()`);
await js(`document.querySelector('[data-action="browser.full"]').click()`);
const back = await until("the page beside the conversation again", async () => (!beneath() && width() < placed.width ? guest.getBounds() : null));
// The column settles a pixel or two after the first measurement, so beside means
// the same place and size to within a few pixels.
+11 -12
View File
@@ -36,33 +36,32 @@ async function main() {
await client.request("POST", `${base}/submit`, { input: "open the page" });
const guest = await until("a page view", async () => guests()[0], 60000);
await until("the page beside the conversation", async () => (guest.getBounds().x >= 0 ? true : null), 30000);
await until("the wide control", () => js(`!!document.querySelector('[data-action="browser.wide"]')`));
await until("the full-view control", () => js(`!!document.querySelector('[data-action="browser.full"]')`));
const panelRect = () => js(`(() => { const e = document.querySelector('.sidepanel'); if (!e) return null; const b = e.getBoundingClientRect(); return { x: b.left, y: b.top, width: b.width, height: b.height }; })()`);
const covers = (view, rect) => view.x < rect.x + rect.width && view.x + view.width > rect.x && view.y < rect.y + rect.height && view.y + view.height > rect.y;
for (const [width, height, wide] of [[1440, 900, false], [1440, 900, true], [1100, 760, false], [1100, 760, true], [900, 700, true], [700, 700, true], [1440, 900, "panel-first"], [1100, 760, "panel-first"]]) {
for (const [width, height, full] of [[1440, 900, false], [1440, 900, true], [1100, 760, false], [1100, 760, true], [900, 700, true], [700, 700, true], [1440, 900, "panel-first"], [1100, 760, "panel-first"]]) {
win.setContentSize(width, height);
await wait(900);
const label = `${width}px ${wide === "panel-first" ? "side question open, then the browser widened" : wide ? "widened" : "beside the conversation"}`;
if (wide === true && (await js(`document.querySelector('.pane')?.getAttribute('data-browser-size')`)) !== "wide") {
await js(`document.querySelector('[data-action="browser.wide"]')?.click()`);
const label = `${width}px ${full === "panel-first" ? "side question open, then full view" : full ? "full view" : "beside the conversation"}`;
if (full === true && (await js(`document.querySelector('.pane')?.getAttribute('data-browser-size')`)) !== "full") {
await js(`document.querySelector('[data-action="browser.full"]')?.click()`);
await wait(1200);
}
if (wide === false && (await js(`document.querySelector('.pane')?.getAttribute('data-browser-size')`)) === "wide") {
await js(`document.querySelector('[data-action="browser.wide"]')?.click()`);
if (full === false && (await js(`document.querySelector('.pane')?.getAttribute('data-browser-size')`)) === "full") {
await js(`document.querySelector('[data-action="browser.full"]')?.click()`);
await wait(1200);
}
if (wide === "panel-first") {
// Back beside the conversation first, then the question, then the widening.
if ((await js(`document.querySelector('.pane')?.getAttribute('data-browser-size')`)) === "wide") {
await js(`document.querySelector('[data-action="browser.wide"]')?.click()`);
if (full === "panel-first") {
if ((await js(`document.querySelector('.pane')?.getAttribute('data-browser-size')`)) === "full") {
await js(`document.querySelector('[data-action="browser.full"]')?.click()`);
await wait(1000);
}
await js(`document.querySelector('[data-action="side.toggle"]').click()`);
await until("the side panel", async () => (await panelRect()) || null, 10000);
await wait(800);
await js(`document.querySelector('[data-action="browser.wide"]')?.click()`);
await js(`document.querySelector('[data-action="browser.full"]')?.click()`);
await wait(1500);
} else {
await js(`document.querySelector('[data-action="side.toggle"]').click()`);
@@ -41,7 +41,6 @@ try {
['.chrome [data-action="settings.section"][data-value="tools"]', "沙盒与运行边界"],
['[data-action="pane.view"][data-value="analysis"]', "打开运行分析"],
['[data-action="pane.view"][data-value="browser"]', "关闭工作台"],
['[data-action="browser.wide"]', "加宽工作台"],
['[data-action="browser.full"]', "全屏工作台"],
['[data-action="workbench.files"]', "展开文件树"],
['[data-action="workbench.new"]', "新建标签"],
@@ -56,13 +55,13 @@ try {
assert.equal(await button.getAttribute("title"), null);
const box = await tip.boundingBox();
assert.ok(box.x >= 0 && box.y >= 0 && box.x + box.width <= 1440 && box.y + box.height <= 960);
if (selector.includes("browser.wide")) await page.screenshot({ path: "/tmp/reasonix-tooltip-workbench.png" });
if (selector.includes("browser.full")) await page.screenshot({ path: "/tmp/reasonix-tooltip-workbench.png" });
if (selector.includes("reply.quote")) await page.screenshot({ path: "/tmp/reasonix-tooltip-reply.png" });
await page.mouse.move(1, 1);
await tip.waitFor({ state: "detached" });
}
await page.locator('[data-action="browser.wide"]').focus();
await page.getByRole("tooltip").filter({ hasText: "加宽工作台" }).waitFor();
await page.locator('[data-action="browser.full"]').focus();
await page.getByRole("tooltip").filter({ hasText: "全屏工作台" }).waitFor();
await page.keyboard.press("Escape");
assert.equal(await page.locator(".icon-tooltip").count(), 0);
await page.getByRole("button", { name: "下一页", exact: true }).click();
-1
View File
@@ -128,7 +128,6 @@ export const ACTIONS: UIAction[] = [
{ id: "session.batch-delete", kind: "destructive", target: "none", proof: "authority-effect" },
{ id: "chrome.settings", kind: "navigation", target: "none", proof: "interaction" },
{ id: "browser.open", kind: "view", target: "none", proof: "interaction" },
{ id: "browser.wide", kind: "view", target: "none", proof: "interaction" },
{ id: "browser.full", kind: "view", target: "none", proof: "interaction" },
{ id: "browser.back", kind: "navigation", target: "none", proof: "interaction" },
{ id: "browser.reload", kind: "repeatable", target: "none", proof: "interaction" },
-1
View File
@@ -949,7 +949,6 @@ export const EN: Record<string, string> = {
"搜索会话": "Search conversations",
"在提问之间移动": "Move between your questions",
"工作台全屏": "Workbench full screen",
"工作台加宽": "Widen the workbench",
"缩小 / 放大 / 复原界面": "Make the interface smaller / larger / its default size",
"发送(发送键设为修饰键 + Enter 时)": "Send when the send key is set to the modifier plus Enter",
"发送,或用 Shift 换行": "Send, or a new line with Shift",
@@ -8,8 +8,6 @@ export const EN_DOCUMENT: Record<string, string> = {
"已请求系统应用打开": "Requested opening in the system application",
"已请求显示文件位置": "Requested showing the file location",
"已复制路径": "Path copied",
"加宽工作台": "Widen workbench",
"恢复分栏": "Restore split view",
"全屏工作台": "Expand workbench",
"退出工作台全屏": "Exit expanded workbench",
"展开文件树": "Show file tree",
+7 -12
View File
@@ -292,18 +292,13 @@ and selected states, while long titles truncate within the remaining width.
the global icon navigation is not visible. It follows the window's existing
navigation visibility state, including collapsed-only mode and narrow-window
folds, so both columns never offer the same footer actions together.
- Browser wide mode changes the existing pane's grid tracks, keeping the
workbench, pages, transcript and composer in place. The conversation takes
34% of the pane up to 380px; the browser takes the remaining width.
- The saved split width is untouched, and its divider is absent while wide so dragging
cannot silently overwrite a width that is not being drawn.
- The compact composer's metrics row wraps inside the composer. Absolute
positioning below the box lets balance and task controls cross the browser seam.
- Under the side fold, wide mode stacks the browser above the conversation and
composer. Two columns at phone width cannot keep either surface usable.
- The workbench toggle and Ctrl/Cmd+Shift+B share `browser.wide`. Escape returns
to split before the window can close the browser or stop the run. Closing the
dock or leaving the conversation resets wide mode; mounting never expands it.
- The docked workbench uses the divider's remembered width. Pointer dragging and
keyboard resizing share that width; there is no intermediate width preset.
- Full-workbench view preserves the divider width and keeps the conversation and
composer mounted. Its button and Ctrl/Cmd+Shift+F share `browser.full`.
- Escape restores split view before the window can close the workbench or stop
the run. Closing the dock resets full view; a pending decision returns to split
so the person can answer.
- Column widths change on `.app`, so the tween belongs there and `.cols` only
reads the result into a track. The tween must be cut during a drag: a .34s
@@ -0,0 +1,4 @@
.pane[data-browser-size="full"]:has(> .pbody[data-dock]) > .findhost { grid-area: 1 / 1 / 2 / 3 }
.pane[data-browser-size="full"]:has(> .pbody[data-dock]) > .scroll[data-pane="browser"] { grid-area: 2 / 1 / 4 / 3; border-left: 0 }
.pane[data-browser-size="full"]:has(> .pbody[data-dock]) > :is(.pbody, .compose, .gutter-r) { display: none }
.workbench-tabs > [data-action="browser.full"] { margin-left: auto; }
@@ -1,27 +0,0 @@
.pane[data-browser-size="wide"]:has(> .pbody[data-dock]) {
grid-template-columns: minmax(0, min(34%, 380px)) minmax(0, 1fr);
}
.pane[data-browser-size="wide"] .studio-meterrail {
position: static;
flex-wrap: wrap;
margin-top: 8px;
padding-inline: 0;
}
[data-fold~="side"] .app[data-browser="on"] .pane[data-browser-size="wide"]:has(> .pbody[data-dock]) {
grid-template: "nav" auto "find" auto "side" minmax(0, 1.2fr) "body" minmax(0, 1fr) "compose" auto / minmax(0, 1fr);
}
[data-fold~="side"] .app[data-browser="on"] .pane[data-browser-size="wide"]:has(> .pbody[data-dock]) > .pbody {
display: flex;
}
[data-fold~="side"] .app[data-browser="on"] .pane[data-browser-size="wide"]:has(> .pbody[data-dock]) > .compose {
display: block;
}
.pane[data-browser-size="full"]:has(> .pbody[data-dock]) > .findhost { grid-area: 1 / 1 / 2 / 3 }
.pane[data-browser-size="full"]:has(> .pbody[data-dock]) > .scroll[data-pane="browser"] { grid-area: 2 / 1 / 4 / 3; border-left: 0 }
.pane[data-browser-size="full"]:has(> .pbody[data-dock]) > :is(.pbody, .compose, .gutter-r) { display: none }
.workbench-tabs > [data-action="browser.full"] { margin-left: auto; }
@@ -1,15 +0,0 @@
import { t } from "../i18n";
import { StudioIcon } from "./StudioIcon";
import { ariaChord, chord } from "./keys";
import { TooltipButton } from "./TooltipButton";
export function BrowserWideToggle({ wide, onToggle }: { wide: boolean; onToggle: () => void }) {
const label = wide ? t("恢复分栏") : t("加宽工作台");
return (
<TooltipButton className="workbench-files" data-action="browser.wide" aria-pressed={wide}
aria-label={label} aria-keyshortcuts={`${ariaChord("B")}+Shift`}
title={label} shortcut={chord("Shift+B")} onClick={onToggle}>
<StudioIcon name="panel" />
</TooltipButton>
);
}
@@ -12,7 +12,6 @@ const KEYS: Array<[string, string]> = [
[`${chord("↑")} / ${chord("↓")}`, "在提问之间移动"],
[chord("Shift+E"), "优化提示词"],
[chord("Shift+F"), "工作台全屏"],
[chord("Shift+B"), "工作台加宽"],
[`${chord("-")} / ${chord("+")} / ${chord("0")}`, "缩小 / 放大 / 复原界面"],
[chord("Enter"), "发送(发送键设为修饰键 + Enter 时)"],
["Enter / Shift+Enter", "发送,或用 Shift 换行"],
+2 -3
View File
@@ -55,7 +55,7 @@ import { RunAnalysis } from "./RunAnalysis";
import { useBrowserTabs } from "./BrowserPanel";
import { useRevealAgentPages, useRevealBrowserOpen } from "./browserreveal";
import { WorkbenchPanel } from "./WorkbenchPanel";
import { useBrowserWide } from "./browserwide";
import { useBrowserFull } from "./browserfull";
import { refreshTodos } from "../state/restore";
import { speedOf } from "./speed";
import { RuntimeBar } from "./RuntimeBar";
@@ -459,7 +459,7 @@ function PaneView({ port, hub, rt, title, active, visible, sideHost, side, onFoc
// Chinese literals that the reducer had written — a translation key deciding
// whether the run reads as moving.
const blocked = hasPendingDecision(status);
const browserSize = useBrowserWide(docked, active && visible, blocked);
const browserSize = useBrowserFull(docked, active && visible, blocked);
const run = runState({ blocked, running, hasItems: s.items.length > 0, terminal: s.terminal });
const cost = s.metrics.coverage === "none" ? NO_COST : s.metrics.cost;
const contextPercent = ctx && ctx.window > 0 ? Math.min(100, Math.round((ctx.used / ctx.window) * 100)) : null;
@@ -581,7 +581,6 @@ function PaneView({ port, hub, rt, title, active, visible, sideHost, side, onFoc
fileRequest={conversationFiles.request}
agentRequest={agentRuns.request}
tabs={pages}
wideMode={docked ? { wide: browserSize.size === "wide", onToggle: browserSize.toggle } : undefined}
fullMode={docked ? { full: browserSize.size === "full", onToggle: browserSize.toggleFull } : undefined}
manual={manualBrowser}
shown={visible && workbench}
@@ -30,13 +30,13 @@ it("waits 400ms for hover and cancels both pending and visible hints on leave",
it("supports keyboard focus, Escape, current-state labels and the existing button ref", () => {
const ref = createRef<HTMLButtonElement>();
const clicked = vi.fn();
const view = render(<TooltipButton ref={ref} title="加宽工作台" shortcut="⌘Shift+B" onClick={clicked}>Wide</TooltipButton>);
const view = render(<TooltipButton ref={ref} title="Expand" shortcut="Shortcut" onClick={clicked}>Toggle</TooltipButton>);
const button = screen.getByRole("button");
expect(ref.current).toBe(button);
fireEvent.focus(button);
expect(screen.getByRole("tooltip").textContent).toContain("⌘Shift+B");
view.rerender(<TooltipButton ref={ref} title="恢复分栏" onClick={clicked}>Wide</TooltipButton>);
expect(screen.getByRole("tooltip").textContent).toBe("恢复分栏");
expect(screen.getByRole("tooltip").textContent).toContain("Shortcut");
view.rerender(<TooltipButton ref={ref} title="Restore" onClick={clicked}>Toggle</TooltipButton>);
expect(screen.getByRole("tooltip").textContent).toBe("Restore");
fireEvent.keyDown(button, { key: "Escape" });
expect(screen.queryByRole("tooltip")).toBeNull();
fireEvent.blur(button);
@@ -35,7 +35,6 @@ import { OpenWithMenu } from "./OpenWithMenu";
import type { HubPort } from "../port/hub";
import { useHtmlPreview } from "./useHtmlPreview";
import { setShowsHiddenFiles, showsHiddenFiles } from "../state/prefs";
import { BrowserWideToggle } from "./BrowserWideToggle";
import { BrowserFullToggle } from "./BrowserFullToggle";
import { documentView, sourceView } from "./document/registry";
import { TooltipButton } from "./TooltipButton";
@@ -70,7 +69,6 @@ export function WorkbenchPanel({
onExternal,
onAddToChat,
onQuoteSelection,
wideMode,
fullMode,
fileRequest,
agentRequest,
@@ -104,7 +102,6 @@ export function WorkbenchPanel({
onAddToChat?: (reference: string) => void;
// Text selected in an open file, with the file it came from, on its way to the composer.
onQuoteSelection?: (text: string, path: string) => void;
wideMode?: { wide: boolean; onToggle: () => void };
fullMode?: { full: boolean; onToggle: () => void };
fileRequest?: { path: string; consumed?: () => void };
agentRequest?: AgentRequest;
@@ -450,7 +447,6 @@ export function WorkbenchPanel({
if (launcher && e.key === "Escape") { e.stopPropagation(); toggleLauncher(); newTab.current?.focus(); }
}}>
<header className="workbench-tabs">
{wideMode && <BrowserWideToggle {...wideMode} />}
<div
className="workbench-tablist"
role="tablist"
@@ -35,54 +35,23 @@ function open(active = true, setup?: (port: MockPort) => void) {
return { ...view, width, close };
}
describe("browser wide mode", () => {
it("keeps the page and conversation mounted and leaves the saved split width alone", async () => {
describe("workbench sizing", () => {
it("keeps divider resizing available when the retired widening shortcut is pressed", async () => {
const { container, width } = open();
const frame = await screen.findByLabelText("网页地址");
const composer = container.querySelector(".compose");
const button = screen.getByRole("button", { name: "加宽工作台" });
expect(button.getAttribute("aria-pressed")).toBe("false");
fireEvent.click(button);
expect(container.querySelector(".pane")?.getAttribute("data-browser-size")).toBe("wide");
expect(screen.getByRole("button", { name: "恢复分栏" }).getAttribute("aria-pressed")).toBe("true");
expect(screen.getByLabelText("网页地址")).toBe(frame);
expect(container.querySelector(".compose")).toBe(composer);
expect(composer?.hasAttribute("hidden")).toBe(false);
expect(container.querySelector(".pbody")?.hasAttribute("data-full")).toBe(false);
fireEvent.click(screen.getByRole("button", { name: "恢复分栏" }));
expect(container.querySelector(".pane")?.getAttribute("data-browser-size")).toBe("split");
expect(screen.getByLabelText("网页地址")).toBe(frame);
expect(width).not.toHaveBeenCalled();
});
it("puts the wide toggle at the left edge of the workbench bar and the full-size toggle at the right edge", async () => {
const { container } = open();
await screen.findByLabelText("网页地址");
const bar = container.querySelector<HTMLElement>(".workbench-tabs")!;
expect(bar.firstElementChild).toBe(screen.getByRole("button", { name: "加宽工作台" }));
expect(bar.lastElementChild).toBe(screen.getByRole("button", { name: "全屏工作台" }));
const event = new KeyboardEvent("keydown", { key: "B", ctrlKey: true, shiftKey: true, bubbles: true, cancelable: true });
act(() => { document.body.dispatchEvent(event); });
expect(event.defaultPrevented).toBe(false);
expect(container.querySelector(".pane")?.getAttribute("data-browser-size")).toBe("split");
const divider = screen.getByRole("separator", { name: "调整浏览器宽度" });
fireEvent.keyDown(divider, { key: "ArrowLeft", shiftKey: true });
expect(width).toHaveBeenCalledWith(608);
expect(screen.queryByRole("button", { name: "加宽工作台" })).toBeNull();
});
it("toggles with Ctrl+Shift+B and spends Escape leaving wide mode before the browser closes", async () => {
const { container, close } = open();
await screen.findByLabelText("网页地址");
fireEvent.keyDown(document.body, { key: "B", ctrlKey: true, shiftKey: true });
expect(container.querySelector(".pane")?.getAttribute("data-browser-size")).toBe("wide");
fireEvent.keyDown(document.body, { key: "Escape" });
expect(container.querySelector(".pane")?.getAttribute("data-browser-size")).toBe("split");
expect(close).not.toHaveBeenCalled();
fireEvent.keyDown(document.body, { key: "B", ctrlKey: true, shiftKey: true });
fireEvent.keyDown(document.body, { key: "B", ctrlKey: true, shiftKey: true, repeat: true });
expect(container.querySelector(".pane")?.getAttribute("data-browser-size")).toBe("wide");
fireEvent.keyDown(document.body, { key: "B", ctrlKey: true, shiftKey: true });
expect(container.querySelector(".pane")?.getAttribute("data-browser-size")).toBe("split");
fireEvent.keyDown(document.body, { key: "Escape" });
expect(close).toHaveBeenCalledOnce();
});
it("ignores shortcuts on an inactive pane and keys already handled by a field", async () => {
it("ignores full-view shortcuts on an inactive pane and keys already handled by a field", async () => {
const hidden = open(false);
fireEvent.keyDown(document.body, { key: "B", ctrlKey: true, shiftKey: true });
fireEvent.keyDown(document.body, { key: "F", ctrlKey: true, shiftKey: true });
expect(hidden.container.querySelector(".pane")?.getAttribute("data-browser-size")).toBe("split");
cleanup();
const { container } = open();
@@ -90,7 +59,7 @@ describe("browser wide mode", () => {
const input = document.createElement("input");
input.addEventListener("keydown", e => e.preventDefault());
document.body.append(input);
fireEvent.keyDown(input, { key: "B", ctrlKey: true, shiftKey: true });
fireEvent.keyDown(input, { key: "F", ctrlKey: true, shiftKey: true });
expect(container.querySelector(".pane")?.getAttribute("data-browser-size")).toBe("split");
input.remove();
});
@@ -98,38 +67,38 @@ describe("browser wide mode", () => {
it("resets to split after closing the dock without replacing the page", async () => {
open();
const frame = await screen.findByLabelText("网页地址");
fireEvent.click(screen.getByRole("button", { name: "加宽工作台" }));
fireEvent.click(screen.getByRole("button", { name: "全屏工作台" }));
fireEvent.click(screen.getByRole("button", { name: "工作台" }));
fireEvent.click(screen.getByRole("button", { name: "工作台" }));
expect(screen.getByRole("button", { name: "加宽工作台" }).getAttribute("aria-pressed")).toBe("false");
expect(screen.getByRole("button", { name: "全屏工作台" }).getAttribute("aria-pressed")).toBe("false");
expect(screen.getByLabelText("网页地址")).toBe(frame);
});
it("gives a transient layer first use of Escape", async () => {
const { container, close } = open();
await screen.findByLabelText("网页地址");
fireEvent.click(screen.getByRole("button", { name: "加宽工作台" }));
fireEvent.click(screen.getByRole("button", { name: "全屏工作台" }));
const dismiss = (e: KeyboardEvent) => e.stopPropagation();
window.addEventListener("keydown", dismiss, true);
try {
fireEvent.keyDown(document.body, { key: "Escape" });
expect(container.querySelector(".pane")?.getAttribute("data-browser-size")).toBe("wide");
expect(container.querySelector(".pane")?.getAttribute("data-browser-size")).toBe("full");
expect(close).not.toHaveBeenCalled();
} finally {
window.removeEventListener("keydown", dismiss, true);
}
});
it("does not treat IME composition or a plain Ctrl+B as the wide shortcut", async () => {
it("does not treat IME composition or a plain Ctrl+F as the full-view shortcut", async () => {
const { container } = open();
await screen.findByLabelText("网页地址");
act(() => document.body.dispatchEvent(new KeyboardEvent("keydown", { key: "B", ctrlKey: true, shiftKey: true, isComposing: true, bubbles: true })));
fireEvent.keyDown(document.body, { key: "b", ctrlKey: true });
act(() => document.body.dispatchEvent(new KeyboardEvent("keydown", { key: "F", ctrlKey: true, shiftKey: true, isComposing: true, bubbles: true })));
fireEvent.keyDown(document.body, { key: "f", ctrlKey: true });
expect(container.querySelector(".pane")?.getAttribute("data-browser-size")).toBe("split");
});
it("shows the page alone in full mode, keeps the conversation mounted, and Escape returns to the split", async () => {
const { container, close } = open();
const { container, close, width } = open();
const frame = await screen.findByLabelText("网页地址");
const composer = container.querySelector(".compose");
fireEvent.click(screen.getByRole("button", { name: "全屏工作台" }));
@@ -140,16 +109,21 @@ describe("browser wide mode", () => {
fireEvent.keyDown(document.body, { key: "Escape" });
expect(container.querySelector(".pane")?.getAttribute("data-browser-size")).toBe("split");
expect(close).not.toHaveBeenCalled();
expect(width).not.toHaveBeenCalled();
expect(screen.getByRole("separator", { name: "调整浏览器宽度" })).toBeTruthy();
fireEvent.keyDown(document.body, { key: "Escape" });
expect(close).toHaveBeenCalledOnce();
});
it("toggles full mode with Ctrl+Shift+F and does not mix it up with wide mode", async () => {
it("toggles full mode with Ctrl+Shift+F without reacting to the retired widening shortcut or repeats", async () => {
const { container } = open();
await screen.findByLabelText("网页地址");
fireEvent.keyDown(document.body, { key: "F", ctrlKey: true, shiftKey: true });
expect(container.querySelector(".pane")?.getAttribute("data-browser-size")).toBe("full");
fireEvent.keyDown(document.body, { key: "B", ctrlKey: true, shiftKey: true });
expect(container.querySelector(".pane")?.getAttribute("data-browser-size")).toBe("wide");
fireEvent.keyDown(document.body, { key: "F", ctrlKey: true, shiftKey: true });
expect(container.querySelector(".pane")?.getAttribute("data-browser-size")).toBe("full");
fireEvent.keyDown(document.body, { key: "F", ctrlKey: true, shiftKey: true, repeat: true });
expect(container.querySelector(".pane")?.getAttribute("data-browser-size")).toBe("full");
fireEvent.keyDown(document.body, { key: "F", ctrlKey: true, shiftKey: true });
expect(container.querySelector(".pane")?.getAttribute("data-browser-size")).toBe("split");
});
@@ -1,13 +1,12 @@
import { useCallback, useEffect, useState } from "react";
import { pressedChord } from "./keys";
import { listenAction } from "./listen";
import "../styles/browserwide.css";
import "../styles/browserfull.css";
export type BrowserSize = "split" | "wide" | "full";
type BrowserSize = "split" | "full";
export function useBrowserWide(docked: boolean, active: boolean, blocked = false) {
export function useBrowserFull(docked: boolean, active: boolean, blocked = false) {
const [size, setSize] = useState<BrowserSize>("split");
const toggle = useCallback(() => setSize((size) => size === "wide" ? "split" : "wide"), []);
const toggleFull = useCallback(() => setSize((size) => size === "full" ? "split" : "full"), []);
useEffect(() => {
if (!docked) setSize("split");
@@ -19,10 +18,7 @@ export function useBrowserWide(docked: boolean, active: boolean, blocked = false
if (!docked || !active) return;
const key = (e: KeyboardEvent) => {
if (e.defaultPrevented || e.isComposing || e.repeat) return;
if (pressedChord(e, "b", true)) {
e.preventDefault();
toggle();
} else if (pressedChord(e, "f", true)) {
if (pressedChord(e, "f", true)) {
e.preventDefault();
toggleFull();
} else if (e.key === "Escape" && size !== "split") {
@@ -33,7 +29,7 @@ export function useBrowserWide(docked: boolean, active: boolean, blocked = false
};
// Document bubbling follows controls and dismiss layers, but precedes the
// window's Escape handler that closes the browser or stops the turn.
return listenAction(document, "keydown", { action: "browser.wide", listener: key as EventListener });
}, [docked, active, size, toggle, toggleFull]);
return { size: docked ? size : "split", toggle, toggleFull };
return listenAction(document, "keydown", { action: "browser.full", listener: key as EventListener });
}, [docked, active, size, toggleFull]);
return { size: docked ? size : "split", toggleFull };
}
@@ -0,0 +1,2 @@
- 调整:移除工作台的“加宽”按钮、独立加宽模式及 Ctrl/Cmd+Shift+B 快捷键。可拖动分隔条或聚焦后用方向键调宽;工作台全屏及退出后恢复原分栏宽度保持不变。关联 #14324。
- Changed: removed Widen mode, its button, and Ctrl/Cmd+Shift+B. Use the divider to resize; full-workbench view still restores the saved split width. Refs #14324.