Skip to content

Commit 92ea5d6

Browse files
committed
feat(shell): add column spec registry
13 ColumnSpec rows copied verbatim from spec 6.2 (key names = existing useColumnLayout persistence keys, unchanged). Six call sites now take columnSpec(...) so pages no longer self-declare widths or thresholds; guard test scans pages/ and components/ code lines for that. Outline column wired: the hardcoded 180 is absorbed into the registry, NoteReadingView takes outlineWidth + ColumnResizer, and onToggleOutline now dispatches (folded -> expand, which clears the auto-fold state and fixes the J1-3 deadlock; unfolded -> manual fold, so the collapse button keeps its semantics). Executor stays useColumnLayout (ruling A5). Also carries the A7 cross-task constant navActionsFull=1400 in shell/breakpoints.ts for T7's top bar (not a column-contract need); its comment separates measured readings from inference.
1 parent 742b892 commit 92ea5d6

12 files changed

Lines changed: 484 additions & 33 deletions

‎app/src/components/NoteReadingView.tsx‎

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ import {
2525
type SelectionNoteAction,
2626
} from "../utils/noteSelectionMenu";
2727
import SelectionActionMenu from "./note-selection/SelectionActionMenu";
28+
// 批 3 T8:大纲列的规格(默认宽 180)来自单一注册表,组件不再写死;
29+
// 列宽执行器仍是 useColumnLayout(A5 裁决:注册表持规格、hook 执行)
30+
import { columnSpec } from "../shell/columnRegistry";
31+
import ColumnResizer from "./ColumnResizer";
2832

2933
interface Props {
3034
note: Note;
@@ -37,6 +41,14 @@ interface Props {
3741
headerExtra?: ReactNode;
3842
/** v0.15:大纲列折叠(统一列折叠体系——父层 useColumnLayout 驱动,替代悬浮按钮) */
3943
outlineFolded?: boolean;
44+
/** v0.15 大纲列宽(批 3 T8:由 `columnSpec("notes-outline")` 的执行器经父层传入——
45+
* 此前组件写死 180 ⇒ hook 的宽度记忆永不生效,即审计 J1-6「假可调」。
46+
* 缺省 = 注册表默认值,故 `180` 全仓只剩注册表一处) */
47+
outlineWidth?: number;
48+
/** 大纲列拖拽手柄(缺省不渲染——未接执行器的调用点不会凭空多出 6px 手柄) */
49+
onOutlineResize?: (delta: number) => void;
50+
/** 手柄双击恢复默认宽(缺省=双击无动作) */
51+
onOutlineReset?: () => void;
4052
/** v0.19.1(REQ-260):外部注入的命中词搜索(引用跳转——key 递增可重触发;
4153
* 复用 A2 搜索高亮与定位,仅阅读态) */
4254
externalSearch?: { key: number; query: string } | null;
@@ -55,7 +67,8 @@ interface Props {
5567

5668
export default function NoteReadingView({
5769
note, editing, editor, auxPanels, headerExtra,
58-
outlineFolded = false, externalSearch = null, onToggleOutline,
70+
outlineFolded = false, outlineWidth = columnSpec("notes-outline").default,
71+
onOutlineResize, onOutlineReset, externalSearch = null, onToggleOutline,
5972
onEdit, onPinToggle, onDelete, onTagClick, onOpenSession, onTaskToggle, onImageOpen,
6073
onSelectionAction,
6174
}: Props) {
@@ -167,11 +180,13 @@ export default function NoteReadingView({
167180

168181
return (
169182
<>
170-
{/* v0.11.5/v0.15 大纲面板(统一列折叠体系:折叠 → 26px 窄条) */}
183+
{/* v0.11.5/v0.15 大纲面板(统一列折叠体系:折叠 → 26px 窄条)
184+
批 3 T8:宽度来自执行器(此前写死 180),并补上列拖拽手柄——与
185+
NotesGroupsColumn/NotesListColumn 同款:手柄在**条件之外**(折叠态也渲染) */}
171186
{outlineFolded ? (
172187
<ColumnBar icon="📑" title="大纲" onClick={() => onToggleOutline?.()} />
173188
) : (
174-
<div style={{ width: 180, flexShrink: 0, borderRight: "1px solid #f3f4f6", overflowY: "auto", padding: "12px 8px", background: "#fafafa" }}>
189+
<div data-testid="outline-panel" style={{ width: outlineWidth, flexShrink: 0, borderRight: "1px solid #f3f4f6", overflowY: "auto", padding: "12px 8px", background: "#fafafa" }}>
175190
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", fontSize: 11, fontWeight: 600, color: "#6b7280", marginBottom: 8 }}>
176191
<span>大纲</span>
177192
<button
@@ -204,6 +219,8 @@ export default function NoteReadingView({
204219
))}
205220
</div>
206221
)}
222+
{/* 大纲列拖拽手柄(批 3 T8:折叠/展开两态都渲染——与其它列同款) */}
223+
{onOutlineResize && <ColumnResizer onResize={onOutlineResize} onReset={onOutlineReset} />}
207224

208225
{/* 正文区 */}
209226
<div style={{ flex: 1, display: "flex", flexDirection: "column", overflow: "hidden", position: "relative" }}>
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
// @vitest-environment jsdom
2+
/**
3+
* @ai-context 大纲列接线守卫(批 3 T8 / 规格 §6.2「大纲列 180/140/260 · 1280 · **接线**
4+
* 拖拽+记忆或删钩子(现为「假可调」)」+ 审计 J1-6 假可调 / J1-3 折叠死局)。
5+
*
6+
* Why 用**真执行器 + 真注册表规格**(不是给组件塞假 props):本文件要证的正是「页面那句
7+
* `useColumnLayout("notes-outline", columnSpec("notes-outline"))` 的结果真的走到了 DOM」——
8+
* 用假 props 只能证明组件认这个 prop,证不了接线。故 Harness 逐字复刻页面形态。
9+
*
10+
* 判据(每条都配了能红的变异体,见 task-8-report §变异体实测):
11+
* ① localStorage 里记的列宽 → 渲染宽度(旧实现写死 180 ⇒ 记忆无效 ⇒ 红);
12+
* ② 未记忆时 = 注册表默认 180(零视觉变化);
13+
* ③ 窄窗自动折叠下点窄条能展开(J1-3 死局:旧实现只翻 manualFolded ⇒ 红);
14+
* ④ 展开态的 ✕「收起大纲」仍能折叠(若把 onToggleOutline 一刀切成 expand() ⇒ 红);
15+
* ⑤ 拖拽手柄在大纲列上(条件之外,折叠态也在)。
16+
*
17+
* 边界:jsdom 不排版——本文件只判 `style.width` 与元素有无,**不判像素**(像素归 T14 探针)。
18+
*/
19+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
20+
import { cleanup, fireEvent, render, screen } from "@testing-library/react";
21+
import { useRef } from "react";
22+
import type { Note } from "../../types";
23+
import type { NoteEditHandle } from "../NoteEditView";
24+
import { useColumnLayout } from "../../hooks/useColumnLayout";
25+
import { columnSpec } from "../../shell/columnRegistry";
26+
import NoteReadingView from "../NoteReadingView";
27+
import NotesReadingColumn from "./NotesReadingColumn";
28+
29+
const { invokeMock } = vi.hoisted(() => ({ invokeMock: vi.fn() }));
30+
vi.mock("@tauri-apps/api/core", () => ({ invoke: invokeMock }));
31+
32+
const note: Note = {
33+
id: 1,
34+
title: "标题",
35+
content: "# 正文\n内容段落",
36+
source: "session",
37+
session_id: 42,
38+
rule_version: null,
39+
purify_stats: null,
40+
tags: "[]",
41+
properties: null,
42+
pin: 0,
43+
group_id: null,
44+
created_at: 1,
45+
updated_at: 2,
46+
};
47+
48+
const noop = vi.fn();
49+
50+
/** 页面形态的宿主:真 hook + 真注册表规格(= NotesPage.tsx 的那一行) */
51+
function Harness() {
52+
const outlineCol = useColumnLayout("notes-outline", columnSpec("notes-outline"));
53+
const editorRef = useRef<NoteEditHandle | null>(null);
54+
return (
55+
<NotesReadingColumn
56+
selected={note}
57+
editing={false}
58+
setEditing={noop}
59+
readerSearch={null}
60+
noteColors={{}}
61+
groups={[]}
62+
editorRef={editorRef}
63+
outlineCol={outlineCol}
64+
onChanged={noop}
65+
onError={noop}
66+
onOpenAi={noop}
67+
onOpenModelCard={noop}
68+
onSelectionAction={noop}
69+
onPinToggle={noop}
70+
onDelete={noop}
71+
onTaskToggle={noop}
72+
onTagClick={noop}
73+
onImageOpen={noop}
74+
onCleanNotice={noop}
75+
/>
76+
);
77+
}
78+
79+
/** 大纲列渲染宽度(jsdom 不排版,读 inline style) */
80+
const outlineWidth = () => (screen.getByTestId("outline-panel") as HTMLElement).style.width;
81+
82+
beforeEach(() => {
83+
invokeMock.mockReset();
84+
invokeMock.mockResolvedValue([]);
85+
window.localStorage.clear();
86+
// 规格 §1 决策 17 默认窗宽 1280;大纲列阈值 = 1280 ⇒ `1280 < 1280` 为假 ⇒ 不自动折叠。
87+
// 显式声明视口(jsdom 默认 1024 会让大纲列静默折叠——本仓既有先例 SelectionActionMenu.test.tsx)
88+
window.innerWidth = 1280;
89+
});
90+
91+
afterEach(() => {
92+
cleanup();
93+
window.localStorage.clear();
94+
window.innerWidth = 1024;
95+
});
96+
97+
describe("大纲列接线(批 3 T8)", () => {
98+
it("① 宽度来自执行器的记忆值(此前组件写死 180 ⇒ 记忆永不生效)", async () => {
99+
window.localStorage.setItem("layout:col-width:notes-outline", "240");
100+
render(<Harness />);
101+
expect(await screen.findByTestId("outline-panel")).toBeTruthy();
102+
expect(outlineWidth()).toBe("240px");
103+
});
104+
105+
it("② 未记忆时 = 注册表默认 180(规格默认值经 hook 走到 DOM)", async () => {
106+
render(<Harness />);
107+
expect(await screen.findByTestId("outline-panel")).toBeTruthy();
108+
expect(outlineWidth()).toBe("180px");
109+
});
110+
111+
it("②b 组件自身缺省也来自注册表(不传 outlineWidth 时 = 180px)", async () => {
112+
// 期望值刻意写**字面** "180px"(不写成 `${columnSpec(...).default}px` ——
113+
// 那样等于拿注册表和它自己比,判据会退化成同义反复、永远绿)
114+
render(
115+
<NoteReadingView
116+
note={note}
117+
editing={false}
118+
onEdit={noop} onPinToggle={noop} onDelete={noop} onTagClick={noop}
119+
onOpenSession={noop} onTaskToggle={noop} onImageOpen={noop}
120+
/>,
121+
);
122+
expect(await screen.findByTestId("outline-panel")).toBeTruthy();
123+
expect(outlineWidth()).toBe("180px");
124+
});
125+
126+
it("③ J1-3:窄窗自动折叠下点窄条可展开(旧实现只翻手动折叠 ⇒ 死局)", async () => {
127+
window.innerWidth = 1024; // < 1280 ⇒ 自动折叠
128+
render(<Harness />);
129+
const bar = await screen.findByTestId("column-bar");
130+
expect(screen.queryByTestId("outline-panel")).toBeNull();
131+
fireEvent.click(bar);
132+
// expand() 同时清自动/手动折叠态 ⇒ 窄窗下也真的展开
133+
expect(await screen.findByTestId("outline-panel")).toBeTruthy();
134+
expect(screen.queryByTestId("column-bar")).toBeNull();
135+
});
136+
137+
it("④ 展开态的 ✕「收起大纲」仍折叠(收起语义不许被 expand() 取代)", async () => {
138+
render(<Harness />);
139+
expect(await screen.findByTestId("outline-panel")).toBeTruthy();
140+
fireEvent.click(screen.getByTitle("收起大纲"));
141+
expect(await screen.findByTestId("column-bar")).toBeTruthy();
142+
expect(screen.queryByTestId("outline-panel")).toBeNull();
143+
});
144+
145+
it("⑤ 拖拽手柄接在大纲列上(与其它列同款:在折叠三元之外,折叠态也在)", async () => {
146+
render(<Harness />);
147+
expect(await screen.findByTestId("outline-panel")).toBeTruthy();
148+
expect(screen.getByTestId("column-resizer")).toBeTruthy();
149+
fireEvent.click(screen.getByTitle("收起大纲"));
150+
expect(await screen.findByTestId("column-bar")).toBeTruthy();
151+
expect(screen.getByTestId("column-resizer")).toBeTruthy();
152+
});
153+
});

‎app/src/components/notes/NotesReadingColumn.tsx‎

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,13 @@
1919
* 三栏宽度分配);空态占位的中文文案与硬编码色 `#9ca3af` 逐字保留
2020
* (本批不 token 化)。
2121
* @ai-context: 等价红线——`outlineCol` 的 localStorage 键 `notes-outline` 与
22-
* `{default:180,min:140,max:260,autoFoldBelow:breakpointFor("outlineCol")}`
23-
* (批 3 T5 前该实参逐字为 1100,现由 `shell/breakpoints.ts` 单一真源
24-
* 给 1280)由页面逐字传入,本组件不得另建列状态;J1-3(只翻
25-
* manualFolded 的折叠死局)为既有缺陷,原样保留。
22+
* `columnSpec("notes-outline")`(批 3 T8 起:默认 180 / 140·260 /
23+
* autoFoldBelow `breakpointFor("outlineCol")`=1280)由页面逐字传入,
24+
* 本组件不得另建列状态。批 3 T8 两处接线:① 宽度经 `outlineWidth`
25+
* 注入 NoteReadingView(此前组件写死 180 ⇒ J1-6「假可调」);
26+
* ② `onToggleOutline` 在**已折叠**时走 `expand()`——旧实现只翻
27+
* manualFolded,窄窗自动折叠下 `folded` 恒真 ⇒ J1-3「折叠后点窄条
28+
* 永不展开」的死局;未折叠时(✕ 收起)仍是手动折叠,语义不变。
2629
*/
2730
import type { RefObject } from "react";
2831
import type { Note, NoteGroup } from "../../types";
@@ -99,7 +102,18 @@ export default function NotesReadingColumn({
99102
? { key: readerSearch.key, query: readerSearch.search }
100103
: null}
101104
outlineFolded={outlineCol.folded}
102-
onToggleOutline={() => outlineCol.setManualFolded(!outlineCol.manuallyFolded)}
105+
// 批 3 T8:大纲列宽/拖拽经 props 注入(此前 NoteReadingView 写死 180,
106+
// hook 的宽度记忆与 min/max 夹取全部失效——审计 J1-6「假可调」)
107+
outlineWidth={outlineCol.width}
108+
onOutlineResize={outlineCol.resizeBy}
109+
onOutlineReset={outlineCol.resetWidth}
110+
// 批 3 T8(J1-3):窄条(ColumnBar)点击必须走 expand()——它同时清自动/手动
111+
// 折叠态;旧实现只翻 manualFolded,窄窗自动折叠下 folded 恒为真 ⇒ 点窄条
112+
// 永不展开。未折叠时的 ✕「收起大纲」仍是手动折叠(与 NotesGroupsColumn 的
113+
// bar=expand / onCollapse=setManualFolded 同款形态)
114+
onToggleOutline={() =>
115+
outlineCol.folded ? outlineCol.expand() : outlineCol.setManualFolded(true)
116+
}
103117
editor={
104118
<RichEditorView
105119
key={selected.id}

‎app/src/pages/ClassroomPage.tsx‎

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,12 +47,13 @@ import { useClassroomShortcuts } from "../hooks/useClassroomShortcuts";
4747
import type { Note, ProfileKind } from "../types";
4848
// Low 清扫:标题截断长度单一定义源(与 MaterialInputPanel 共享)
4949
import { NOTE_TITLE_MAX_LEN } from "../utils/constants";
50-
// 批 3 T5:断点改走单一真源(规格 §6.2 阈值 860→1100,两列页)
51-
import { breakpointFor } from "../shell/breakpoints";
50+
// 批 3 T8:列规格(宽/夹取/阈值)改从 `shell/columnRegistry` 取——页面不再自建规格
51+
import { columnSpec } from "../shell/columnRegistry";
5252

5353
export default function ClassroomPage({ onOpenSessions }: { onOpenSessions?: (sessionId: number) => void }) {
54-
// v0.15:左栏列状态(可拖拽 + 记忆 + 窄窗折叠;默认 320=历史值)
55-
const leftCol = useColumnLayout("classroom-left", { default: 320, min: 240, max: 420, autoFoldBelow: breakpointFor("twoCol") });
54+
// v0.15:左栏列状态(可拖拽 + 记忆 + 窄窗折叠;规格 §6.2 两列页阈值 1100)
55+
// 批 3 T8:规格来自 `columnRegistry`(默认 320 / 240·420 / autoFoldBelow 1100),执行仍由 hook 完成
56+
const leftCol = useColumnLayout("classroom-left", columnSpec("classroom-left"));
5657
// 批 2b:采集生命周期单一状态源(挂载拉取+事件+看门狗在 provider;本页消费)
5758
const { active, sessionId, pausedReason, starting, stopping, pending, notice, start, pause, resume, stop } =
5859
useCaptureControl();

‎app/src/pages/KnowledgePage.tsx‎

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,8 @@ import KnowledgeModelDialog from "../components/KnowledgeModelDialog";
3333
import ConceptCardRow from "../components/ConceptCardRow";
3434
import KnowledgeSampleView from "../components/KnowledgeSampleView";
3535
import KnowledgeGraphView from "../components/KnowledgeGraphView";
36-
// 批 3 T5:断点改走单一真源(规格 §6.2 阈值 860→1100,两列页)
37-
import { breakpointFor } from "../shell/breakpoints";
36+
// 批 3 T8:列规格(体系列/详情列两行的宽·夹取·阈值)改从 `shell/columnRegistry` 取
37+
import { columnSpec } from "../shell/columnRegistry";
3838

3939
type MiddleView = "tree" | "canvas" | "concept" | "model" | "graph";
4040

@@ -59,8 +59,9 @@ interface Props {
5959

6060
export default function KnowledgePage({ focusSystemId, onOpenNote, onOpenGroup, createSystemSignal }: Props) {
6161
// v0.15:左列可拖拽/记忆/窄窗折叠(默认 260=历史值);详情面板宽度由父层持有
62-
const leftCol = useColumnLayout("knowledge-left", { default: 260, min: 200, max: 360, autoFoldBelow: breakpointFor("twoCol") });
63-
const detailCol = useColumnLayout("knowledge-detail", { default: 320, min: 260, max: 420 });
62+
// 批 3 T8:两行规格来自 `columnRegistry`(体系列 autoFoldBelow 1100;详情列不折叠)
63+
const leftCol = useColumnLayout("knowledge-left", columnSpec("knowledge-left"));
64+
const detailCol = useColumnLayout("knowledge-detail", columnSpec("knowledge-detail"));
6465
const [systems, setSystems] = useState<KnowledgeSystem[]>([]);
6566
const [selectedSystemId, setSelectedSystemId] = useState<number | null>(null);
6667
const [nodes, setNodes] = useState<KnowledgeNode[]>([]);

‎app/src/pages/NotesPage.tsx‎

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,9 @@ import { useNotesPageEditing } from "../hooks/useNotesPageEditing";
3030
import { useNotesBatchActions } from "../hooks/useNotesBatchActions";
3131
import { useNotesListData } from "../hooks/useNotesListData";
3232
import { useNotesDeepLink } from "../hooks/useNotesDeepLink";
33-
// 批 3 T5:断点改走单一真源——组列/列表列 860 与 700 统一为三列页 1024;
34-
// 大纲列 1100 抬到规格的 1280(最晚折叠,规格 §6.2)
35-
import { breakpointFor } from "../shell/breakpoints";
33+
// 批 3 T8:列规格(组列/列表列/大纲列三行的宽·夹取·阈值)改从 `shell/columnRegistry` 取
34+
// ——页面不再自建规格;断点值(t5 曾在此写 breakpointFor)现在住在注册表里
35+
import { columnSpec } from "../shell/columnRegistry";
3636

3737
interface Props {
3838
focusNoteId?: number | null;
@@ -88,10 +88,12 @@ export default function NotesPage({ focusNoteId, focusNoteSearch, focusGroupId,
8888
onReload: () => void list.load(list.keyword, list.tagFilter, list.sortMode),
8989
onStatus: list.setStatus,
9090
});
91-
// v0.15:三栏列状态(可拖拽 + 宽度记忆 + 窄窗自动折叠;默认值=历史固定宽度)
92-
const groupsCol = useColumnLayout("notes-groups", { default: 240, min: 180, max: 320, autoFoldBelow: breakpointFor("threeCol") });
93-
const listCol = useColumnLayout("notes-list", { default: 320, min: 240, max: 420, autoFoldBelow: breakpointFor("threeCol") });
94-
const outlineCol = useColumnLayout("notes-outline", { default: 180, min: 140, max: 260, autoFoldBelow: breakpointFor("outlineCol") });
91+
// v0.15:三栏列状态(可拖拽 + 宽度记忆 + 窄窗自动折叠)
92+
// 批 3 T8:三行规格来自 `columnRegistry`(240/180·320·1024、320/240·420·1024、
93+
// 180/140·260·1280),执行仍由 hook 完成——页面不再自建规格
94+
const groupsCol = useColumnLayout("notes-groups", columnSpec("notes-groups"));
95+
const listCol = useColumnLayout("notes-list", columnSpec("notes-list"));
96+
const outlineCol = useColumnLayout("notes-outline", columnSpec("notes-outline"));
9597

9698
// A6:注意力跟踪
9799
useNoteAttention(selected?.id ?? null, selected?.title ?? "");

‎app/src/pages/SessionsPage.tsx‎

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ import { useDbRefresh } from "../hooks/useDbRefresh";
2323
import type {
2424
BatchNoteResult, BatchSessionDeleteResult, CourseGroup, SessionDetail, SessionListItem,
2525
} from "../types";
26-
// 批 3 T5:断点改走单一真源(规格 §6.2 阈值 860→1100,两列页)
27-
import { breakpointFor } from "../shell/breakpoints";
26+
// 批 3 T8:列规格(宽/夹取/阈值 1100)改从 `shell/columnRegistry` 取——页面不再自建规格
27+
import { columnSpec } from "../shell/columnRegistry";
2828

2929
interface Props {
3030
focusSessionId?: number | null;
@@ -46,8 +46,9 @@ interface Toast {
4646
}
4747

4848
export default function SessionsPage({ focusSessionId, focusRefineTaskId, onFocusRefineTaskConsumed, onRefineTaskStarted, active, onOpenNote }: Props) {
49-
// v0.15:左栏列状态(可拖拽 + 记忆 + 窄窗折叠;默认值=历史固定宽度 320)
50-
const listCol = useColumnLayout("sessions-list", { default: 320, min: 240, max: 420, autoFoldBelow: breakpointFor("twoCol") });
49+
// v0.15:左栏列状态(可拖拽 + 记忆 + 窄窗折叠;规格 §6.2 两列页阈值 1100)
50+
// 批 3 T8:规格来自 `columnRegistry`,执行仍由 hook 完成
51+
const listCol = useColumnLayout("sessions-list", columnSpec("sessions-list"));
5152
const [items, setItems] = useState<SessionListItem[]>([]);
5253
const [groups, setGroups] = useState<CourseGroup[] | null>(null); // REQ-078:课程分组模式
5354
const [grouped, setGrouped] = useState(false);

‎app/src/shell/breakpoints.test.ts‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,13 @@ describe("断点(规格 §1 决策 16 / §6.2 阈值口径)", () => {
2626
expect(BREAKPOINTS.nav).toBe(NAV_MIN_WIDTH);
2727
});
2828

29+
it("右侧动作簇档位(A7)≥ 导航满档,且逐字 = 1400", () => {
30+
// 批 3 T8 新增用例(不改既有断言):该档位是跨任务依赖(T7 顶栏右簇文字),
31+
// 由 T7 的 CDP 探针实测推出(1353.8 的满档需求 > 默认窗 1280 ⇒ 右簇文字只能后置)。
32+
expect(BREAKPOINTS.navActionsFull).toBe(1400);
33+
expect(BREAKPOINTS.navActionsFull).toBeGreaterThan(BREAKPOINTS.navFull);
34+
});
35+
2936
it("breakpointFor 对所有键都返回正数(防漏键导致 undefined 静默成 NaN 比较)", () => {
3037
for (const k of Object.keys(BREAKPOINTS) as (keyof typeof BREAKPOINTS)[]) {
3138
expect(breakpointFor(k), k).toBeGreaterThan(0);

0 commit comments

Comments
 (0)