合并 088c9b3(移除 nginx 容器、常驻 5→4)到 main。三路合并零文本冲突,但 088c9b3 漏改了引用被重命名文件的地方,故在本合并提交里一并适配:
1) tests/phase3b-static-guard.js:265 的断言原本读 deploy/nginx.conf,该文件已 rename 成 deploy/nginx.host.conf.example;不改会让 read() 返回空串、断言必然 FAIL,而该护栏被 run-guards.sh / Gitea Actions 收录。新模板 108-109 行确有 location ^~ /admin/ 与 proxy_pass,换名后断言成立。
2) deploy/nginx.ssl.conf.example 的使用说明仍写 cp nginx.conf 与 docker compose up -d nginx(两者都已不存在),改为时代注记,点明本文件只是 HTTPS 片段、缺 /admin/ /setup /relay 段、不可整份照抄(纯注释,不改配置行为)。
验证:bash tests/run-guards.sh → 6 套件 PASS 5 / FAIL 0 / SKIPPED 1(axe 可选未启用);phase3b 94/0、dockerfile-coverage 60/0、schema 116/0、Dart 674 全通过,整体退出码 0。
墨观 InkWatch · 设计系统护栏(tests/)
统一入口(从仓库根运行):
bash tests/run-guards.sh
脚本串行跑 6 个套件(第 6 个默认不跑,见下),任一非零退出则整体失败;无论中途是否有失败都跑完全部套件,末尾打印汇总表(状态 / 退出码 / 耗时 / 指标)。默认路径(前 5 个套件)约 13–14s。
套件
| 套件 | 作用 | 默认 |
|---|---|---|
web-token-guard.js |
源仓库令牌层轻量守卫:tokens.css 首个 :root 关键键与点修值、深色 58 键与层叠顺序、浅色锁定、admin.css 令牌化、admin.js 主题单一出口 |
跑 |
phase3b-static-guard.js |
管理台深色落地静态护栏:首屏脚本逐字一致、6 页 assets/tokens.css 引入顺序、深色 58 键、Dockerfile / nginx 覆盖等 |
跑 |
dockerfile-coverage-test.js |
镜像 COPY 覆盖性(含新增 assets/tokens.css) |
跑 |
schema-consistency-test.js |
指标字段三层一致性 | 跑 |
client/scripts/test.sh |
全量 Dart 测试(含 token_parity_test.dart:CSS :root ↔ Dart theme.dart 双向护栏) |
跑 |
admin-axe-audit.mjs |
管理台 6 页 axe-core 42 格矩阵(3 视口 × 2 皮肤 × 6 页 + E-Ink 6 格) | 不跑(需 RUN_AXE=1) |
web-token-guard.js 是交付目录 03-frontend/verify.mjs 的「源仓库等价轻量版」——后者不入库、CI 不可依赖;前者零外部依赖,只读源仓库 server/business/admin/assets/ 下三个文件,不联网、不读交付目录。单独运行:
node tests/web-token-guard.js
管理台 axe 42 格矩阵(tests/admin-axe-audit.mjs)
# 单独跑(结果 JSON 默认写 /tmp/iw-admin-axe-result.json)
node tests/admin-axe-audit.mjs
# 自定义结果路径(不要写进仓库;仓库会变大且属临时产物)
node tests/admin-axe-audit.mjs --out /tmp/my-axe.json
# 与整套护栏一起跑
RUN_AXE=1 bash tests/run-guards.sh
矩阵与口径(与交付轮次一致,不因「跑绿」而放宽):
- 3 视口
1280×800(Web)/375×812(移动 PWA)/1920×1080(大屏,DSF=1)× 2 皮肤 light / dark × 6 页 = 36 格; - 外加 E-Ink 单列(
1920×1080+ light +data-iw-surface="eink")6 格 = 42 格; - 规则集
runOnly.tag = wcag2a / wcag2aa / wcag21a / wcag21aa / wcag22aa / best-practice,禁用规则 0 条; - 判定:任一格
violations > 0(含 minor)即 FAIL;incomplete不计 PASS,逐条打印规则与节点数并转人工复核;存在未执行格时整轮不得判 PASS。 - 退出码:
0跑满 42 格且 violations=0;1有 violations / 未跑满;2环境受限(缺浏览器或 axe-core)。
实现方式:Node 内置 http 起只读静态服务指向 server/business/admin/(listen(0) 由 OS 分配空闲端口,跑完 close,/api/* 一律 401);child_process 启无头浏览器 + CDP(Node 22 内置 fetch/WebSocket,零运行时依赖);Runtime.evaluate 注入 axe.min.js。深色先 setAttribute('data-iw-theme','dark') 并等样式重算再跑 color-contrast。
前置条件:
- 浏览器:macOS 的
Microsoft Edge/Google Chrome/Chromium,Linux 的google-chrome/microsoft-edge/chromium任一即可;也可IW_BROWSER_BIN=<绝对路径>指定。缺失时脚本以退出码 2 明确报错并给安装提示,不会静默跳过。 - axe-core:默认从
/tmp/iw-admin-axe读取;不存在时自动npm install --prefix /tmp/iw-admin-axe --no-save --no-package-lock axe-core@^4.14.0(需联网,不写进任何package.json)。离线时退出码 2 并给出手动安装命令;也可IW_AXE_JS=<axe.min.js 路径>预置。 - 耗时:单跑约 45s,故默认不进快跑路径(默认约 13–14s)。
产物(均在 /tmp,不入库):/tmp/iw-admin-axe-result.json(每格 probe / violations / incomplete / screenshot 字段)、/tmp/inkwatch-admin-axe-shots/*.png(42 张截图)。
当前实测(HEAD 38c3d8f):42/42 格 violations=0,incomplete=12(全部为 color-contrast 几何遮挡,20 节点,转人工复核)。
环境变量
PHASE3B_PREVIEW=<path>:指向交付目录preview.html。存在时phase3b启用「7 处首屏脚本 hash 比对」(87 PASS)。未设时脚本自动探测本地交付目录候选;找不到则走不带 preview 的分支(85 PASS / 0 FAIL + 1 SKIP,属预期),并明确打印 SKIP 原因。SKIP_FLUTTER_TESTS=1:默认必须跑全量 Dart;仅在 Flutter 不可用时的显式降级,汇总表标SKIPPED (explicit),不会静默跳过。FLUTTER_BIN=<path>:透传给client/scripts/test.sh,指定 flutter 可执行文件(CI runner 上 flutter 通常不在$HOME/development/flutter)。RUN_AXE=1:启用第 6 套件「管理台 axe 42 格矩阵」。默认不跑(保护约 14s 的本地快跑体验);未设置时汇总表显式标SKIPPED (RUN_AXE not set)并打印跳过原因,绝不静默。启用后整体耗时约 59s。IW_BROWSER_BIN/IW_AXE_JS/IW_AXE_PKG/IW_AXE_OUT:分别覆盖浏览器路径、axe.min.js 路径、自动安装的包规格、结果 JSON 路径(--out优先)。
CI
.gitea/workflows/design-system-guard.yml 在 push / pull_request / workflow_dispatch 触发,执行同一入口。前提是 Gitea 实例已启用 Actions;未启用时该文件只作声明、不会运行。本地复现无需 Actions:
bash tests/run-guards.sh # 快跑:PASS 5 / SKIPPED 1(RUN_AXE 未设)
RUN_AXE=1 bash tests/run-guards.sh # 全套:PASS 6 / FAIL 0 / SKIPPED 0
axe 段在 CI 里默认关闭:runner 未必有 Chromium/Edge,联网装 axe-core 也可能被策略挡住,强行默认开启只会制造噪声失败。启用方式二选一(见 workflow 文件头注释):
- 手动触发:Actions → Run workflow →
run_axe填true; - 仓库变量:Settings → Actions → Variables 设
IW_RUN_AXE=1,之后 push / PR 也带上该套件。
runner 需自带 Chromium 系浏览器;若启用而浏览器缺失,该套件以退出码 2 失败(汇总表 FAIL),不会伪造成 PASS。