Skip to content

Commit 5825fae

Browse files
committed
test(ui): strip comments in status red ratchet
1 parent f2a91eb commit 5825fae

2 files changed

Lines changed: 51 additions & 18 deletions

File tree

‎app/src/ui/primitives/statusLineBaseline.ts‎

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,17 @@
66
* 走「切片 + 棘轮」:**本表冻结迁移后的实测值,只许减**。
77
*
88
* 口径:域 = `app/src/**` 的 `.ts`/`.tsx` 减 `*.test.ts(x)` **减 `ui/primitives/**`**
9-
* (原语层是语义色的真源,不是待收敛的调用点;同 T1 的 `prod` 口径)。字面量 = 七个三红十六进制
9+
* (原语层是语义色的真源,不是待收敛的调用点;同 T1 的 `prod` 口径。`ui/icons/**` **不**减 ——
10+
* 图标也是调用点)。字面量 = 七个三红十六进制
1011
* (`#e11d48`/`#dc2626`/`#ef4444`/`#b91c1c`/`#d32f2f`/`#c62828`/`#f43f5e`),
11-
* **按出现次数**计(同一行两次算 2)。
12+
* **按出现次数**计(同一行两次算 2;**不是**按行)。
13+
* ⚠️ **先剥注释**(`sliceScan.stripComments` 状态机,与三个兄弟棘轮同口径 —— T13–T15 评审 M-2):
14+
* 注释里列举色值(本仓有先例:`ClassroomBanners.tsx` / `utils/refineDiff.ts` 的 `@ai-context`)
15+
* **不算命中**;不剥注释会把「解释为什么用这个色」判成违规。冻结值即此口径下的实测值。
1216
*
13-
* ⚠️ 本表是**迁移后的实测快照**(2026-09-12,T15 收口时重测),不是迁移前的基线:迁移后仍剩下的
14-
* 是**切片外的存量**(含 T15 因 B1/B2 守卫回退的 6 处)。数字来自 `tmp/t15rec` 的探针,可复算。
17+
* ⚠️ 本表是**迁移后的实测快照**(2026-09-12,T15 收口时重测;同日 fix 单元按剥注释口径重新冻结),
18+
* 不是迁移前的基线:迁移后仍剩下的是**切片外的存量**(含 T15 因 B1/B2 守卫回退的 6 处)。
19+
* 数字来自 `tmp/t15rec` 的探针,可复算(fix 单元用 `tmp/fix-t13t15/scan-red.mjs` 独立复算过)。
1520
* **真迁走一处 ⇒ 手工收紧这里**(棘轮只许降)。
1621
*/
1722
export const FROZEN_RED_BY_FILE: Readonly<Record<string, number>> = {
@@ -26,10 +31,10 @@ export const FROZEN_RED_BY_FILE: Readonly<Record<string, number>> = {
2631
"components/NotePreviewView.tsx": 3,
2732
"components/NoteRowContextMenu.tsx": 3,
2833
"components/RefineWorkbench.tsx": 3,
29-
"utils/refineDiff.ts": 3,
34+
"utils/refineDiff.ts": 2,
3035
"components/AiTaskPanel.tsx": 2,
3136
"components/CaptureFloatPanel.tsx": 2,
32-
"components/ClassroomBanners.tsx": 2,
37+
"components/ClassroomBanners.tsx": 1,
3338
"components/InterviewSteps.tsx": 2,
3439
"components/LiveActivityPanel.tsx": 2,
3540
"components/NoteLinkToSystem.tsx": 2,
@@ -85,4 +90,4 @@ export const FROZEN_RED_BY_FILE: Readonly<Record<string, number>> = {
8590
};
8691

8792
/** 全仓三红十六进制字面量的**冻结总数**(= 上面逐文件之和的独立校验和,只许降) */
88-
export const FROZEN_RED_TOTAL = 116;
93+
export const FROZEN_RED_TOTAL = 114;

‎app/src/ui/primitives/statusLineRatchet.test.ts‎

Lines changed: 39 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,12 @@
77
* **不该**染上语义色)。没有棘轮,「还没迁完」就退化成「永远在迁」。
88
*
99
* 四条判据:
10-
* ① 迁移面**冻结**(49 文件,只许增不许减)—— 迁走了文件要**主动**把它登记进来;
11-
* ② 三红字面量**逐文件 ≤ 冻结值** + **总数 ≤ 116** + 新增文件里**不得出现**三红字面量;
10+
* ① 迁移面**冻结**(49 文件,只许增不许减)+ **逐文件 `<StatusLine` 处数下界**(② 的牙齿,
11+
* T13–T15 评审 M-7 的结清 —— 只判「出现过」时,把 5 处收敛成 1 处也照样绿);
12+
* ② 三红字面量**逐文件 ≤ 冻结值** + **总数 ≤ 114** + 新增文件里**不得出现**三红字面量;
13+
* ⚠️ 计数**先剥注释**(`sliceScan.stripComments`,与三个兄弟棘轮同口径 —— 评审 M-2 的结清:
14+
* 原实现读原文,于是注释里列举色值的两处(`ClassroomBanners.tsx:13` / `utils/refineDiff.ts:13`)
15+
* 被算成命中,纯注释改动会假红)。
1216
* ③ `error` 档必须走 `--ed-stamp` **文字色**(不是别的档、不是底色)—— `StatusLine.tsx` 的
1317
* 用色契约与控制方 2026-09-11 裁决③ 同源;`StatusLine.css` 里**连底色属性名都不出现**
1418
* (「错误色绝不进按钮底色」因此是结构保证,不是纪律);
@@ -30,6 +34,8 @@ import { dirname, join } from "node:path";
3034
import { fileURLToPath } from "node:url";
3135
import { describe, expect, it } from "vitest";
3236
import { FROZEN_RED_BY_FILE, FROZEN_RED_TOTAL } from "./statusLineBaseline";
37+
// 剥注释仪器:三个兄弟棘轮(loading / emptyState / confirm)都用它 ⇒ 口径必须同源,不许本地重写
38+
import { stripComments } from "./sliceScan";
3339

3440
const HERE = dirname(fileURLToPath(import.meta.url));
3541
const SRC = join(HERE, "..", "..");
@@ -83,13 +89,26 @@ function walk(prefix = "", out: string[] = []): string[] {
8389
const PROD = walk().filter((r) => !r.startsWith("ui/primitives/"));
8490
const read = (rel: string): string => readFileSync(join(SRC, ...rel.split("/")), "utf8");
8591

86-
/** 逐文件三红计数(按**出现次数**,同一行两次算 2 —— 与基线生成器同口径) */
92+
/**
93+
* 逐文件 `<StatusLine` 处数**下界**(① 的牙齿,评审 M-7 的结清)。只写 > 1 的 8 个,其余一律 1;
94+
* 下界是「迁移面真的把错误行交给了原语」的最小证据 —— 加处数不红(那不是回潮),掉到下界以下必红。
95+
*/
96+
const MIN_CALLS: Readonly<Record<string, number>> = {
97+
"components/GroupDeleteConfirm.tsx": 5,
98+
"components/EnrichPanel.tsx": 4,
99+
"components/AiRefineCard.tsx": 2, "components/AiServicePanel.tsx": 2, "components/GoalDetail.tsx": 2,
100+
"components/ModelManagementPanel.tsx": 2, "components/RefineLaunchDialog.tsx": 2,
101+
"components/RefineWorkbench.tsx": 2,
102+
};
103+
104+
/** 一段文本里三红十六进制字面量的**出现次数**(同一行两次算 2 —— 与基线生成器同口径) */
105+
const countIn = (text: string): number => RED_HEX.reduce((n, h) => n + (text.split(h).length - 1), 0);
106+
107+
/** 逐文件三红计数:**先剥注释**(M-2 —— 注释里列举色值不算命中,与三个兄弟棘轮同口径) */
87108
function redCounts(): Map<string, number> {
88109
const map = new Map<string, number>();
89110
for (const rel of PROD) {
90-
const text = read(rel);
91-
let n = 0;
92-
for (const h of RED_HEX) n += text.split(h).length - 1;
111+
const n = countIn(stripComments(read(rel)));
93112
if (n > 0) map.set(rel, n);
94113
}
95114
return map;
@@ -99,19 +118,25 @@ const CSS = read("ui/primitives/StatusLine.css");
99118
const TSX = read("ui/primitives/StatusLine.tsx");
100119

101120
describe("状态行棘轮(B11 · T15)", () => {
102-
it("① 迁移面 49 文件:逐条在盘上、都真的用了 StatusLine、且与回退账不重叠", () => {
121+
it("① 迁移面 49 文件:逐条在盘上、`<StatusLine` 处数 ≥ 下界、且与回退账不重叠", () => {
103122
expect(MIGRATED).toHaveLength(49);
104123
expect(new Set(MIGRATED).size, "迁移面有重复项").toBe(49);
105124
expect([...MIGRATED].sort(), "迁移面未按字典序(对拍会假红)").toEqual([...MIGRATED]);
106125
const missing = MIGRATED.filter((rel) => !PROD.includes(rel));
107126
expect(missing, `迁移面里这些文件在盘上不存在(改名 / 迁移后本行先红):\n${missing.join("\n")}`).toEqual([]);
108-
const noUse = MIGRATED.filter((rel) => !/<StatusLine[\s/>]/.test(read(rel)));
109-
expect(noUse, `登记为「已迁移」但没有 <StatusLine>(迁移面是虚的):\n${noUse.join("\n")}`).toEqual([]);
127+
const thin = MIGRATED.map((rel) => [rel, (stripComments(read(rel)).match(/<StatusLine[\s/>]/g) ?? []).length] as const)
128+
.filter(([rel, n]) => n < (MIN_CALLS[rel] ?? 1))
129+
.map(([rel, n]) => `${rel}: 实测 ${n} < 下界 ${MIN_CALLS[rel] ?? 1}`);
130+
expect(thin, `登记为「已迁移」但 \`<StatusLine\` 处数掉到下界以下(迁移面是虚的):\n${thin.join("\n")}`).toEqual([]);
131+
// 下界表不许有僵尸键 / 漏键(49 个迁移面文件里 > 1 的恰 8 个)
132+
const keys = Object.keys(MIN_CALLS);
133+
expect(keys.filter((k) => !MIGRATED.includes(k)), "下界表里有不在迁移面的键").toEqual([]);
134+
expect(keys.filter((k) => (MIN_CALLS[k] ?? 0) < 2), "下界表里的值必须 > 1(=1 是默认值)").toEqual([]);
110135
const overlap = MIGRATED.filter((rel) => REVERTED_BY_B1_B2.includes(rel));
111136
expect(overlap, `回退账里的文件不许同时算进迁移面(两个账本必须互斥):\n${overlap.join("\n")}`).toEqual([]);
112137
});
113138

114-
it("② 三红只许减:逐文件 ≤ 冻结值 · 总数 ≤ 116 · 新增文件里不得出现三红", () => {
139+
it("② 三红只许减:逐文件 ≤ 冻结值 · 总数 ≤ 114 · 新增文件里不得出现三红", () => {
115140
const grown = [...COUNTS.entries()].filter(([f, n]) => n > (FROZEN_RED_BY_FILE[f] ?? 0))
116141
.map(([f, n]) => `${f}: ${FROZEN_RED_BY_FILE[f] ?? 0} → ${n}`);
117142
expect(grown, `这些文件的三红字面量**涨了**(棘轮只许降;真迁移了请手工收紧 statusLineBaseline):\n${grown.join("\n")}`).toEqual([]);
@@ -147,9 +172,12 @@ describe("状态行棘轮(B11 · T15)", () => {
147172
expect(/\baria-live\s*=/.test(TSX), "StatusLine 重复声明了 aria-live(role 已隐含,会制造第二个真源)").toBe(false);
148173
});
149174

150-
it("④ 仪器自证:七值可读、正/负样本、域边界(原语层在域外)", () => {
175+
it("④ 仪器自证:七值可读、剥注释口径、正/负样本、域边界(原语层在域外)", () => {
151176
expect(RED_HEX).toHaveLength(7);
152177
expect(RED_HEX.every((h) => /^#[0-9a-f]{6}$/.test(h)), "词表里有非法十六进制").toBe(true);
178+
// 剥注释口径自证(M-2):字符串里的三红算命中、行/块注释里的**不算** —— 两条各一,防口径单侧失效
179+
expect(countIn(stripComments('const a = "#b91c1c"; // #dc2626\n/* #ef4444 */')), "注释里的三红被计入了").toBe(1);
180+
expect(countIn(stripComments("// #b91c1c\n/* #dc2626 #ef4444 */")), "全是注释却仍有命中").toBe(0);
153181
// 正样本:域内确实还有命中(基线非空;否则下面的棘轮是空真)
154182
expect(COUNTS.size, "域内一处三红都没有 ⇒ 基线表是空真").toBeGreaterThan(0);
155183
expect([...COUNTS.values()].reduce((a, b) => a + b, 0), "域内三红总数与基线不符").toBe(FROZEN_RED_TOTAL);

0 commit comments

Comments
 (0)