From 21a4a8cab76246a96c1bd681c294982d2a387e9c Mon Sep 17 00:00:00 2001 From: wangjia <809946525@qq.com> Date: Tue, 7 Jul 2026 18:24:34 +0800 Subject: [PATCH] =?UTF-8?q?ci:=20=E8=A1=A5=E4=B8=89=E9=81=93=E7=BC=BA?= =?UTF-8?q?=E5=8F=A3=E9=97=B8=E2=80=94=E2=80=94CI=20=E5=BC=BA=E5=88=B6=20f?= =?UTF-8?q?lutter=20test=20+=20SSR=20=E6=A8=A1=E6=9D=BF=20token=20?= =?UTF-8?q?=E5=90=8C=E6=BA=90=20+=20=E5=BE=BD=E7=AB=A0=E5=9B=BE=E6=A0=87?= =?UTF-8?q?=E6=98=A0=E5=B0=84=E5=90=8C=E9=9B=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - checks.yml 加 flutter test(DoD 写了但此前无闸,golden 回归全靠自觉; 本地 /release client 门禁同步加了 flutter test,该文件 gitignore 不入库) - check-l1-sync ⑤:backend 公开页模板内联 token 值须与原型 tokens.css 对应主题块一致(模板自包含手拷值,此前仅注释约定零校验;已注入漂移自证) - check-l1-sync ⑥:BADGE_ICON ↔ kStatusIcons 状态词同集(此前两端手工转录) - 顺修 gen_tokens 旧路径注释(client/lib/core/theme/tool/ → client/tool/) Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01JJ1g8XV1YhhmHRzhwWEW7o --- .claude/commands/release.md | 2 +- .gitea/workflows/checks.yml | 10 ++++-- tools/check-l1-sync.mjs | 66 +++++++++++++++++++++++++++++++++++-- 3 files changed, 72 insertions(+), 6 deletions(-) diff --git a/.claude/commands/release.md b/.claude/commands/release.md index 1b0db21..010b3d1 100644 --- a/.claude/commands/release.md +++ b/.claude/commands/release.md @@ -40,7 +40,7 @@ export PATH="/opt/homebrew/bin:$PATH" ``` - **client**: ```bash - cd client && flutter analyze --no-fatal-infos --no-fatal-warnings + cd client && flutter analyze --no-fatal-infos --no-fatal-warnings && flutter test ``` - **site**: ```bash diff --git a/.gitea/workflows/checks.yml b/.gitea/workflows/checks.yml index 663fda2..d792935 100644 --- a/.gitea/workflows/checks.yml +++ b/.gitea/workflows/checks.yml @@ -1,8 +1,9 @@ name: Design Source Checks # 设计真相源检查(治理规则见 CLAUDE.md「设计真相源分层」): -# PR 与合并 main 时自动跑四道闸,全部纯静态检查(node + dart format), -# 不跑 golden/fidelity(重型像素闸维持「定期体检」定位,本地手动跑)。 +# PR 与合并 main 时自动跑:四道静态闸(node + dart format)+ flutter test +# (单测 + golden ×3 主题回归自比,2026-07-07 纳入——此前 DoD 写了但无闸)。 +# fidelity 重型像素闸维持「定期体检」定位,本地手动跑。 on: pull_request: push: @@ -37,3 +38,8 @@ jobs: node tool/gen_tokens.mjs dart format lib/core/theme/app_tokens.g.dart lib/core/theme/app_dims.g.dart lib/core/theme/app_chrome.g.dart git diff --exit-code -- lib/core/theme/app_tokens.g.dart lib/core/theme/app_dims.g.dart lib/core/theme/app_chrome.g.dart + + - name: Flutter 测试(单测 + golden ×3 主题回归自比) + run: | + cd client + flutter test diff --git a/tools/check-l1-sync.mjs b/tools/check-l1-sync.mjs index 3121682..d02ab36 100644 --- a/tools/check-l1-sync.mjs +++ b/tools/check-l1-sync.mjs @@ -6,7 +6,7 @@ // CI(.gitea/workflows/checks.yml,PR + main push)与本地均可直接跑: // node tools/check-l1-sync.mjs // -// 四道检查(任一违规 exit 1): +// 六道检查(任一违规 exit 1): // ① tokens 快照同源 原型 tokens.css ≡ client token_source/tokens.css(逐字节) // ② icons 同源 原型 icons.js 与 web icons.njk 的 symbol 同集且同内容 // ③ 官网 token 值同源 web/assets/tokens.css 与原型(:root+主题A) 同名 token 值一致 @@ -14,6 +14,11 @@ // ④ web 硬编码色扫描 web/**/*.{njk,css,js} 禁裸 hex;白名单 #fff/#ffffff/#1677ff // (对齐原型 check-ds 豁免口径);行内 `ds-allow` 注释可豁免; // 排除 web/assets/tokens.css(token 定义位)与 web/dist/。 +// ⑤ SSR 模板 token 同源 backend 公开页模板(product/shop)内联的 --token 值 +// 须与原型 tokens.css 对应主题块一致(模板自包含无法引 +// tokens.css,靠此闸拦静默漂移;派生变量白名单豁免)。 +// ⑥ 徽章图标映射同集 原型 icons.js BADGE_ICON 与 client status_icon_map.dart +// kStatusIcons 状态词同集(此前两端手工转录无闸)。 import { readFileSync, readdirSync, statSync } from 'node:fs'; import { join, relative } from 'node:path'; @@ -31,7 +36,7 @@ const problems = []; problems.push( '[tokens快照] .superpowers/prototype/tokens.css 与 ' + 'client/lib/core/theme/token_source/tokens.css 不一致 —— ' + - '改原型后需拷贝快照并重跑 client/lib/core/theme/tool/gen_tokens.mjs', + '改原型后需拷贝快照并重跑 client/tool/gen_tokens.mjs', ); } } @@ -103,6 +108,61 @@ const problems = []; } } +// ── ⑤ SSR 模板 token 同源 ───────────────────────────────── +{ + const vars = (css) => + Object.fromEntries( + [...css.matchAll(/(--[\w-]+)\s*:\s*([^;}]+)[;}]/g)].map(([, k, v]) => [k, v.replace(/\s+/g, '')]), + ); + const proto = read('.superpowers/prototype/tokens.css'); + const root = vars(proto.match(/:root\s*{([^}]*)}/s)?.[1] ?? ''); + const theme = (t) => vars(proto.match(new RegExp(`\\[data-theme="${t}"\\]\\s*{([^}]*)}`, 's'))?.[1] ?? ''); + // 模板私有派生变量(主色低透明度/表面高透明度,模板头注释声明来源) + const DERIVED = new Set(['--glow1', '--glow2', '--seal-bg']); + const TEMPLATES = [ + 'backend/internal/handler/templates/public_product.html', + 'backend/internal/handler/templates/public_shop.html', + ]; + for (const f of TEMPLATES) { + const html = read(f); + const blocks = [ + [':root(主题A)', /:root\s*{([^}]*)}/s, { ...root, ...theme('a') }], + ['主题B', /\[data-theme="b"\]\s*{([^}]*)}/s, { ...root, ...theme('b') }], + ['主题C', /\[data-theme="c"\]\s*{([^}]*)}/s, { ...root, ...theme('c') }], + ]; + for (const [label, re, pv] of blocks) { + const body = html.match(re)?.[1]; + if (!body) continue; + for (const [k, v] of Object.entries(vars(body))) { + if (DERIVED.has(k)) continue; + if (!(k in pv)) { + problems.push(`[ssr-token] ${f} ${label} 私造 token ${k} —— 先登记原型 tokens.css(或列入 DERIVED 白名单)`); + } else if (pv[k] !== v) { + problems.push(`[ssr-token] ${f} ${label} ${k} 值漂移:原型=${pv[k]} 模板=${v} —— 改配色先改原型再同步模板`); + } + } + } + } +} + +// ── ⑥ 徽章图标映射同集(BADGE_ICON ↔ kStatusIcons)───────── +{ + const keysOf = (body) => new Set([...body.matchAll(/'([^']+)'\s*:/g)].map((m) => m[1])); + const js = read('.superpowers/prototype/screens/icons.js'); + const protoKeys = keysOf(js.match(/BADGE_ICON\s*=\s*{([\s\S]*?)}/)?.[1] ?? ''); + const dart = read('client/lib/widgets/ds/status_icon_map.dart'); + const dartKeys = keysOf(dart.match(/kStatusIcons\s*=[\s\S]*?{([\s\S]*?)};/)?.[1] ?? ''); + if (!protoKeys.size || !dartKeys.size) { + problems.push('[badge-icon] BADGE_ICON 或 kStatusIcons 解析为空 —— 检查文件结构是否变更'); + } + for (const k of protoKeys) { + if (!dartKeys.has(k)) problems.push(`[badge-icon] 状态词「${k}」仅原型 BADGE_ICON 有 —— 同步 client/lib/widgets/ds/status_icon_map.dart`); + } + for (const k of dartKeys) { + if (!protoKeys.has(k)) problems.push(`[badge-icon] 状态词「${k}」仅 Flutter kStatusIcons 有 —— 新状态先登记原型 icons.js`); + } +} + // ── 汇总 ─────────────────────────────────────────────────── const line = '='.repeat(60); if (problems.length) { @@ -113,5 +173,5 @@ if (problems.length) { process.exit(1); } console.log(line); -console.log('✓ 通过:L1 真相源同源(tokens 快照 / icons / 官网 token 值 / web 无硬编码色)'); +console.log('✓ 通过:L1 真相源同源(tokens 快照 / icons / 官网 token / web hex / SSR 模板 token / 徽章图标映射)'); console.log(line);