三个页面此前各是脱离外壳的独立全屏路由,各写了一套一样的 *-shell / *-topbar(只有一个「返回对话」链接)/ *-hero 骨架, 侧栏在打开它们的瞬间整个消失。 新增 AppNavRail(只读:两个 GET,零写操作)与 SecondaryShell。 会话行走 sessionHref → /?c=<uuid>,跳转复用现有侧栏的 persistLoginSessionReturn + location.assign,行为完全一致。 刻意不带重命名/收藏/归档/删除与账户菜单:那套连着 Home() 的乐观更新与 回滚层,为四个路由把它整体上提远超需要,且会撞 useState 增长门禁。 四个路由渲染标记完全不变:/ ○、/chart ○、/ephemeris ○、/reports ƒ。 CSS gzip 39,825→39,727(−0.25%);per-route JS 体积构建不输出, 已作为口径缺口记录。 两处自身问题被测试抓到并修复:usePathname() 在 app-router 上下文外 返回 null(类型说是 string)、fetch 在 jsdom 里可能不存在。 另差点随 hero 一起丢掉 BUG-717 的 eyebrow 文案(成本与速度承诺, 会印在失败页上),已放回 tab 行下方。 /reports/[reportId] 留给 R6 与目录一起做;根边界页保留「返回对话」, 它们不得 import globals.css,挂不了外壳。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0193vBv6w5MV2cifdTUu9H5P
52 lines
2.4 KiB
TypeScript
52 lines
2.4 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import { readFileSync } from "node:fs";
|
|
import test from "node:test";
|
|
|
|
const css = readFileSync(new URL("../src/app/globals.css", import.meta.url), "utf8");
|
|
const sidebar = readFileSync(new URL("../src/lib/sidebar-state.ts", import.meta.url), "utf8");
|
|
|
|
/** Widths that may appear in @media (min-width / max-width). Keep in sync with the comment at the top of globals.css. */
|
|
const ALLOWED = new Set([480, 640, 641, 767, 768, 860, 1023, 1024]);
|
|
|
|
function mediaWidthBreakpoints(source: string): number[] {
|
|
const widths = new Set<number>();
|
|
for (const block of source.matchAll(/@media[^{]+/g)) {
|
|
for (const hit of block[0].matchAll(/\((?:min|max)-width:\s*(\d+)px\)/g)) {
|
|
widths.add(Number(hit[1]));
|
|
}
|
|
}
|
|
return [...widths].sort((a, b) => a - b);
|
|
}
|
|
|
|
test("CSS width breakpoints stay on the published allowlist", () => {
|
|
assert.deepEqual(mediaWidthBreakpoints(css), [...ALLOWED].sort((a, b) => a - b));
|
|
});
|
|
|
|
test("tablet CSS cuts at 1023 so it matches sidebarViewportForWidth", () => {
|
|
assert.doesNotMatch(css, /@media[^{]*max-width:\s*900px/);
|
|
assert.match(css, /@media \(max-width: 1023px\)/);
|
|
assert.match(sidebar, /if \(width < 768\) return "mobile"/);
|
|
assert.match(sidebar, /if \(width < 1024\) return "tablet"/);
|
|
});
|
|
|
|
test("report centre and reader share the global mobile cut", () => {
|
|
// 原值 `.report-center-shell` / 新值 `.report-center-body`
|
|
// 原因:报告中心并入 app 外壳(T4.2),页面根不再是自己的全屏滚动容器;
|
|
// 「用全局 767 断点、不自立断点」这条要求没变,下面两条 doesNotMatch 仍在守。
|
|
assert.match(css, /@media \(max-width: 767px\) \{[\s\S]*\.report-center-body/);
|
|
assert.match(css, /@media \(max-width: 767px\) \{[\s\S]*\.personal-report-reader-body/);
|
|
assert.doesNotMatch(css, /@media \(max-width: 720px\)/);
|
|
assert.doesNotMatch(css, /@media \(max-width: 760px\)/);
|
|
});
|
|
|
|
test("report TOC keeps a content-width exception at 860px", () => {
|
|
assert.match(css, /Report TOC is the third column/);
|
|
assert.match(css, /@media \(max-width: 860px\) \{[\s\S]*\.personal-report-md-layout/);
|
|
});
|
|
|
|
test("ephemeris page stacks at the global mobile cut", () => {
|
|
// 原值 `.ephemeris-shell` / 新值 `.ephemeris-body` / 原因同上。
|
|
assert.match(css, /@media \(max-width: 767px\) \{[\s\S]*\.ephemeris-body/);
|
|
assert.match(css, /@media \(max-width: 767px\) \{[\s\S]*\.ephemeris-panchanga \{[\s\S]*grid-template-columns:\s*1fr/);
|
|
});
|