fix(studio): a line-width preset wins over a width the column was dragged to

The width a hand set is kept in --flow-w and outranks the stylesheet, so once the
reading column had been dragged, choosing the full-width preset changed --doc but
not the column and did nothing. Choosing a preset now drops the dragged width.
This commit is contained in:
esengine committed 2026-10-24 22:54:22 -07:00
1 parent 8c669ebe61
commit d28a6089fc
7 files changed
+110 -2

No files matched your search

+1
View File
@@ -161,6 +161,7 @@ jobs:
node perf/rowhit.mjs
node perf/flowgrip-quiet.mjs
node perf/flow-narrow.mjs
node perf/flow-preset.mjs
node perf/tab-pip.mjs
node perf/context-card-hover.mjs
node perf/sessquick.mjs
@@ -0,0 +1,42 @@
// A line-width preset still works on a column a hand has already dragged: the
// dragged width outranks the stylesheet, so choosing a preset has to drop it.
import assert from "node:assert/strict";
import { chromium } from "playwright";
const PAGE = process.env.PERF_URL ?? "http://localhost:4399/perf.html?pref=zh&turns=3";
const browser = await chromium.launch();
try {
const page = await browser.newPage({ viewport: { width: 1900, height: 900 } });
const errors = [];
page.on("pageerror", (error) => errors.push(error.message));
await page.goto(PAGE, { waitUntil: "networkidle" });
await page.waitForSelector(".flowgrip-l");
const flow = () => page.locator(".flow").first().evaluate((el) => Math.round(el.getBoundingClientRect().width));
const readable = await flow();
const box = await page.locator(".flowgrip-l").boundingBox();
await page.mouse.move(box.x + 3, box.y + box.height / 2);
await page.mouse.down();
await page.mouse.move(box.x + 3 - 60, box.y + box.height / 2, { steps: 6 });
await page.mouse.up();
await page.waitForTimeout(200);
const dragged = await flow();
assert.ok(dragged > readable, `the hand widened the column (${readable} -> ${dragged})`);
const choose = async (name) => {
await page.evaluate(() => document.querySelector('[data-action="chrome.settings"]')?.click());
await page.waitForSelector(".prefs-nav button");
await page.locator(".prefs-nav button", { hasText: "外观" }).first().click();
await page.locator('[data-action="appearance.measure"]', { hasText: new RegExp(`^${name}$`) }).click();
await page.keyboard.press("Escape");
await page.waitForTimeout(400);
return flow();
};
const full = await choose("铺满窗口");
assert.ok(full > dragged, `the full-width preset widens a dragged column (${dragged} -> ${full})`);
const standard = await choose("适宜阅读");
assert.equal(standard, readable, `the reading preset returns to the readable measure (${full} -> ${standard})`);
assert.deepEqual(errors, []);
console.log(`PASS a preset overrides a dragged width: ${readable} -> ${dragged} (dragged) -> ${full} (full) -> ${standard} (reading)`);
} finally {
await browser.close();
}
+2 -1
View File
@@ -26,6 +26,7 @@ import { SendShortcut } from "./SendShortcut";
import { KeysSheet } from "./KeysSheet";
import { SessionTabsSetting } from "./SessionTabsSetting";
import { EscStopSetting } from "./EscStopSetting";
import { forgetDraggedWidth } from "./FlowResizer";
import { PageSleepSetting } from "./PageSleepSetting";
import { ThemeImport } from "./ThemeImport";
import { ThemeRemove } from "./ThemeRemove";
@@ -365,7 +366,7 @@ export function Appearance({ port, theme, onTheme, contrast, onContrast, weight,
<div className="seg" data-text role="group" aria-label={t("正文行宽")}>
{MEASURES.map(([id, name, why]) => (
<button key={id} data-action="appearance.measure" data-value={id}
aria-pressed={(look.width || "standard") === id} title={t(why)} onClick={() => set({ width: id })}>
aria-pressed={(look.width || "standard") === id} title={t(why)} onClick={() => { forgetDraggedWidth(); set({ width: id }); }}>
{t(name)}
</button>
))}
@@ -2,7 +2,7 @@
import { afterEach, describe, expect, it } from "vitest";
import { act, cleanup, fireEvent, render, renderHook } from "@testing-library/react";
import "./testkit";
import { barsFit, flowCeiling, flowFloor, flowMin, FlowGrips, useColumn, type Column } from "./FlowResizer";
import { barsFit, flowCeiling, flowFloor, flowMin, forgetDraggedWidth, FlowGrips, useColumn, type Column } from "./FlowResizer";
const RAIL_MIN = 232;
const DOCK_MIN = 320;
@@ -219,3 +219,16 @@ describe("the keys walk the same path the hand does", () => {
expect(press("l", "ArrowRight", false, 760)).toEqual([748]);
});
});
describe("a line-width preset answers for the dragged width", () => {
it("forgets what a hand set, in storage and in every mounted column", () => {
localStorage.clear();
localStorage.setItem("rx-flow-w", "900");
const { result } = renderHook(() => useColumn(316, 316, RAIL_MIN, DOCK_MIN));
expect(result.current.dragged).toBe(900);
act(() => forgetDraggedWidth());
expect(result.current.dragged).toBe(0);
expect(localStorage.getItem("rx-flow-w")).toBeNull();
expect(result.current.width).toBe(result.current.floor);
});
});
@@ -21,6 +21,16 @@ const NARROWEST = 640;
* the kernel's config. */
export const FLOW_KEY = "rx-flow-w";
const forgetters = new Set<() => void>();
/** A line-width preset and a hand on the grips answer the same question, and
* the width a hand set outranks the stylesheet, so choosing a preset has to
* drop it or the choice changes nothing on a column that was ever dragged. */
export function forgetDraggedWidth() {
try { localStorage.removeItem(FLOW_KEY); } catch { /* storage is a convenience */ }
forgetters.forEach((forget) => forget());
}
const clamp = (v: number, lo: number, hi: number) => Math.min(hi, Math.max(lo, v));
/** What the column may grow to: the window, less the room the two sidebars need
@@ -97,6 +107,11 @@ export function useColumn(railW: number, dockW: number, railMin: number, dockMin
});
const [room, setRoom] = useState(() => document.body.clientWidth);
useEffect(() => onRoomWidth(setRoom), []);
useEffect(() => {
const forget = () => setDragged(0);
forgetters.add(forget);
return () => { forgetters.delete(forget); };
}, []);
const floor = flowFloor(room, railMin, dockMin);
const min = flowMin(room, railMin, dockMin);
@@ -0,0 +1,34 @@
// @vitest-environment jsdom
import { afterEach, beforeEach, expect, it, vi } from "vitest";
import { cleanup, render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import "./testkit";
import { Appearance } from "./Appearance";
import { FLOW_KEY } from "./FlowResizer";
import { MockPort } from "../port/mock";
import { boot, STORAGE } from "../i18n";
import type { AgentPort } from "../port/port";
beforeEach(() => {
localStorage.setItem(STORAGE, "zh");
boot();
vi.spyOn(HTMLCanvasElement.prototype, "getContext").mockReturnValue(null);
});
afterEach(() => {
cleanup();
localStorage.clear();
vi.restoreAllMocks();
});
function panel(width: string) {
return <Appearance port={new MockPort() as unknown as AgentPort} theme="light" onTheme={() => {}} contrast="" onContrast={() => {}}
weight="" onWeight={() => {}} look={{ width } as never} onLook={() => {}} reloadThemes={() => {}} />;
}
it.each(["铺满窗口", "适宜阅读"])("choosing %s drops the width a hand dragged the reading column to", async (name) => {
localStorage.setItem(FLOW_KEY, "900");
render(panel(name === "铺满窗口" ? "standard" : "full"));
await userEvent.click(await screen.findByRole("button", { name }));
expect(localStorage.getItem(FLOW_KEY)).toBeNull();
});
@@ -0,0 +1,2 @@
- 修复:拖动过正文栏之后,在设置里选「铺满窗口」或「适宜阅读」不再没有反应;选了预设就以预设为准,丢掉之前拖出来的宽度。关联 #14411。
- Fixed: choosing the full-width or reading preset in Settings did nothing once the column had been dragged; a preset now wins and drops the width you dragged. Refs #14411.