Compare commits

...

8 Commits

Author SHA1 Message Date
wangjia 8e569b83a7 Merge pull request 'feat(ds-flow): 前端设计系统治理重构(Flutter 五端 + Web 两端)' (#2) from worktree-macos-killswitch into main
ci-pangolin / Cleartext Scan — Android 禁明文 (push) Successful in 27s
ci-pangolin / Portable SQL — 可移植性 (mysql/sqlite) (push) Successful in 24s
ci-pangolin / Lint — shellcheck (push) Successful in 7s
ci-pangolin / OpenAPI Sync Check (push) Successful in 33s
ci-pangolin / Flutter — analyze + test (push) Failing after 16s
ci-pangolin / Codegen Drift — token 生成物未漂移 (push) Successful in 4s
ci-pangolin / DS-flow — 原型/跨端同源/代码色单源闸 (push) Successful in 4s
ci-pangolin / Go — build + test (push) Successful in 8s
ci-pangolin / E2E Smoke — L4 进程级端到端 (push) Failing after 7s
Deploy Site / deploy-site (push) Successful in 2m39s
ci-pangolin / Go — integration (mysql/redis testcontainers) (push) Failing after 4m24s
ci-pangolin / Golden — 视觉回归 (全量:components/auth/desktop/tablet) (push) Successful in 20s
Deploy Client / build-android (push) Successful in 2m30s
Deploy Client / build-windows (push) Failing after 13m38s
ci-pangolin / Redline Scan — 脱敏 (UI 文案) (push) Failing after 14m24s
Deploy Client / build-macos (push) Successful in 3m56s
Deploy Client / build-ios (push) Successful in 3m28s
Deploy Client / release-deploy (push) Successful in 1m38s
2026-07-08 00:46:57 +00:00
wangjia 0f05385995 fix(client/test): pin zh locale 修 i18n 默认英文后失配的单测/组件测试
ci-pangolin / Redline Scan — 脱敏 (UI 文案) (pull_request) Successful in 28s
ci-pangolin / OpenAPI Sync Check (pull_request) Successful in 37s
ci-pangolin / Cleartext Scan — Android 禁明文 (pull_request) Successful in 22s
ci-pangolin / Codegen Drift — token 生成物未漂移 (pull_request) Successful in 13s
ci-pangolin / Portable SQL — 可移植性 (mysql/sqlite) (pull_request) Successful in 29s
ci-pangolin / DS-flow — 原型/跨端同源/代码色单源闸 (pull_request) Successful in 8s
ci-pangolin / Go — build + test (pull_request) Successful in 14s
ci-pangolin / E2E Smoke — L4 进程级端到端 (pull_request) Failing after 9s
ci-pangolin / Lint — shellcheck (pull_request) Failing after 11m2s
ci-pangolin / Golden — 视觉回归 (全量:components/auth/desktop/tablet) (pull_request) Failing after 13m15s
ci-pangolin / Go — integration (mysql/redis testcontainers) (pull_request) Failing after 13m22s
ci-pangolin / Flutter — analyze + test (pull_request) Failing after 15m0s
早前 i18n 把默认语种改英文(localeProvider 默认 AppLang.en),但这几个测试硬编码
const t = StringsZh() 中文预期,被测代码经 appTextProvider 渲染英文 → 断言失配
(既存 broken,先于 ds-flow;DinD 修复后 Flutter test job 才跑到暴露)。

修:各失败测试的 ProviderScope/Container overrides 加
localeProvider.overrideWith((ref) => _FixedLocale(AppLang.zh)),把被测代码钉回
中文(与硬编码 StringsZh 预期一致)。只动测试文件,未碰 lib/golden/test_config。

- connection_watchdog_test(节点异常提示 / 免费额度归零切断)
- node_connect_confirm_test(点节点/智能卡弹确认取消不切换)
- stats_page_test(PeriodCard 周期卡中文标签)

容器复跑 test/unit+widget+contract:+97 All passed。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 08:41:52 +08:00
wangjia fc22300937 fix(ci): docker job 从 ubuntu-latest 容器移到 nas host(修 DinD 全红)
ci-pangolin / Redline Scan — 脱敏 (UI 文案) (pull_request) Successful in 26s
ci-pangolin / Flutter — analyze + test (pull_request) Failing after 25s
ci-pangolin / Cleartext Scan — Android 禁明文 (pull_request) Successful in 27s
ci-pangolin / Portable SQL — 可移植性 (mysql/sqlite) (pull_request) Successful in 24s
ci-pangolin / Codegen Drift — token 生成物未漂移 (pull_request) Successful in 59s
ci-pangolin / DS-flow — 原型/跨端同源/代码色单源闸 (pull_request) Successful in 5s
ci-pangolin / Go — build + test (pull_request) Successful in 59s
ci-pangolin / E2E Smoke — L4 进程级端到端 (pull_request) Failing after 5s
ci-pangolin / Go — integration (mysql/redis testcontainers) (pull_request) Failing after 4m55s
ci-pangolin / Golden — 视觉回归 (全量:components/auth/desktop/tablet) (pull_request) Successful in 19s
ci-pangolin / OpenAPI Sync Check (pull_request) Failing after 13m37s
ci-pangolin / Lint — shellcheck (pull_request) Failing after 13m50s
根因:runs-on: ubuntu-latest 的 job 在 catthehacker 容器里跑,容器内嵌套
`docker run` = DinD,$PWD 在宿主不存在 → 挂载失败 → 所有套 docker 的 job 全红
(含 pre-existing 的 Codegen/Go/Flutter/Golden;纯 bash 的 redline/cleartext/
portable 不套 docker 故绿)。此前 server/site 部署 job 已因同因去 docker 直跑,
但 ci.yml 未跟进。

修:9 个套 docker 的 job(lint/openapi/flutter/codegen-drift/ds-flow/go-server/
e2e/go-integration/golden)runs-on 改 nas(host 模式=mac-pangolin-2 宿主直接跑,
docker run 是宿主真 docker 非嵌套,可正常拉跑镜像;本机已验证这些命令全绿)。
golden 保留 Linux flutter 容器 → 与入库基线渲染一致。3 个纯 bash 扫描留
ubuntu-latest(并行、已绿)。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 02:58:14 +08:00
wangjia 4a0c5c4921 chore(todo): #19 ds-flow 前端治理转待验收
ci-pangolin / Lint — shellcheck (pull_request) Failing after 42s
ci-pangolin / OpenAPI Sync Check (pull_request) Failing after 36s
ci-pangolin / Redline Scan — 脱敏 (UI 文案) (pull_request) Successful in 16s
ci-pangolin / Cleartext Scan — Android 禁明文 (pull_request) Successful in 15s
ci-pangolin / Flutter — analyze + test (pull_request) Failing after 3m35s
ci-pangolin / Portable SQL — 可移植性 (mysql/sqlite) (pull_request) Successful in 22s
ci-pangolin / Codegen Drift — token 生成物未漂移 (pull_request) Failing after 24s
ci-pangolin / DS-flow — 原型/跨端同源/代码色单源闸 (pull_request) Failing after 24s
ci-pangolin / Go — build + test (pull_request) Failing after 1m18s
ci-pangolin / E2E Smoke — L4 进程级端到端 (pull_request) Failing after 27s
ci-pangolin / Go — integration (mysql/redis testcontainers) (pull_request) Failing after 19s
ci-pangolin / Golden — 视觉回归 (全量:components/auth/desktop/tablet) (pull_request) Failing after 28s
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 01:45:18 +08:00
wangjia d2d75cd33c docs(ds-flow): Phase 5.6 — 前端全景文档 frontend-overview.html
docs/frontend-overview.html(照 jiu 十节裁剪):一次 UI 改动标准路径 · 目录地图 ·
三层真相源模型 · 令牌 codegen · 四道静态闸「违规谁拦」· 像素验收(golden 双主题
全绿含 CJK / fidelity 待建) · 响应式五端 · 规则速查 · 文档索引。登记进 docs/index.html。

同时标注两项延后(非阻塞,前置=原型整屏 HTML 属 L3 新屏工作):
- 4.4 mobile golden 覆盖扩容
- 5.5 fidelity 像素闸(原型无整屏可比,待 design/prototype/screens/ 落地)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 01:44:31 +08:00
wangjia a22db0874f feat(ds-flow): Phase 5.4 — pre-commit 增挂 ds-flow 条件闸
.githooks/pre-commit 在原有(红线/SQL/codegen-drift)基础上增挂三道 ds-flow 闸,
条件触发、秒级:
- 原型校验(check-ds):动了 design/prototype/ 才跑
- 跨端同源(check-l1-sync):动了 prototype/ 或 web/{website,usercenter}/ 才跑
- Flutter 颜色单源(check_ds_code --changed):动了 client/lib/*.dart 才跑(只扫改动)

install-hooks.sh 说明同步更新。启用仍是每台机一次性 `bash ci/install-hooks.sh`
(不代跑,改本机 git config;CLAUDE.md 治理章节已注明)。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 01:42:27 +08:00
wangjia 29e225c63c feat(ds-flow): Phase 5.3 — CI 串联三道闸 + golden 扩全量
.gitea/workflows/ci.yml:
- 新增 ds-flow job:原型校验(check-ds)+ 跨端同源(check-l1-sync)+ Flutter
  颜色单源(check_ds_code --strict),均 node:20 容器零依赖跑。
- golden job 从「components+auth」扩到全量 test/golden(desktop/tablet 现均绿,
  含 CJK 真渲染 + tablet zh/en 矩阵)。
- codegen 零 diff(codegen-drift job)已有。

至此四道静态闸 + golden 全部进 CI 硬闸(「规则没上闸=没有规则」)。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 01:41:10 +08:00
wangjia 14a9836882 feat(ds-flow): Phase 5.1-5.2 — Flutter 裸色闸 + 原型校验闸
5.1 client/tool/check_ds_code.mjs(照 jiu):禁 Flutter 业务代码裸 Color(0x)/
具名 Colors.x;豁免 token 生成层(pangolin_tokens.gen.dart)+ 实现层
(pangolin_theme.dart);--changed(pre-commit)/--strict(CI)/全量三模式;
剥行尾注释防误报。现状 --strict 通过(0 违规,UI 层零裸色)。

5.2 design/prototype/tools/check-ds.mjs:原型单源守门,5 道——硬编码色(仅严格
扫 atoms.css)/未定义 token/font-family 走 var(--font*)/图标走 icons.js sprite/
组件原子在 index.html 登记;剥 CSS+HTML 注释防误报,SVG fill/stroke 豁免。
现状通过(serve.mjs 改动时自动跑)。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 01:39:44 +08:00
13 changed files with 432 additions and 60 deletions
+33 -10
View File
@@ -16,12 +16,19 @@ on:
branches: [main]
workflow_dispatch:
# runner 分配:
# · runs-on: ubuntu-latest —— 在 catthehacker 容器里跑,仅用于纯 bash 扫描
# (redline/cleartext/portable-sql);容器内**不能**嵌套 `docker run`(DinD 挂载
# 失败,$PWD 在宿主不存在),故套 docker 的 job 不能用它。
# · runs-on: nas —— host 模式(mac-pangolin-2 直接在宿主跑),`docker run` 是宿主
# 真 docker(非嵌套),可正常拉/跑 node/golang/flutter/python/shellcheck 镜像。
# golden 保留 ghcr.io/cirruslabs/flutter Linux 容器 → 与入库基线渲染一致。
jobs:
# ── Job 1: Lint (shellcheck) ─────────────────────────────────────────────
lint:
name: Lint — shellcheck
runs-on: ubuntu-latest
runs-on: nas
steps:
- name: Checkout
uses: actions/checkout@v4
@@ -74,7 +81,7 @@ jobs:
# ── Job 2: OpenAPI Sync Check ────────────────────────────────────────────
openapi-check:
name: OpenAPI Sync Check
runs-on: ubuntu-latest
runs-on: nas
steps:
- name: Checkout
uses: actions/checkout@v4
@@ -113,7 +120,7 @@ jobs:
# ── Job 4: Flutter 客户端(分析 + 单测/组件测试)────────────────────────
flutter-client:
name: Flutter — analyze + test
runs-on: ubuntu-latest
runs-on: nas
steps:
- name: Checkout
uses: actions/checkout@v4
@@ -149,7 +156,7 @@ jobs:
# (改了 design/colors_and_type.css 没重生成,或手改了生成物)。
codegen-drift:
name: Codegen Drift — token 生成物未漂移
runs-on: ubuntu-latest
runs-on: nas
steps:
- name: Checkout
uses: actions/checkout@v4
@@ -161,12 +168,28 @@ jobs:
node:20 \
bash ci/check-codegen-drift.sh
ds-flow:
name: DS-flow — 原型/跨端同源/代码色单源闸
runs-on: nas
steps:
- name: Checkout
uses: actions/checkout@v4
- name: 原型校验(check-ds)
run: docker run --rm -v "$PWD:/repo" -w /repo node:20 node design/prototype/tools/check-ds.mjs
- name: 跨端同源(check-l1-sync)
run: docker run --rm -v "$PWD:/repo" -w /repo node:20 node tools/check-l1-sync.mjs
- name: Flutter 颜色单源(check_ds_code --strict)
run: docker run --rm -v "$PWD/client:/app" -w /app node:20 node tool/check_ds_code.mjs --strict
# ── Job 7: Go 服务端(build + test:含契约快照 + sqlite 真库,跳过 integration)──
# 此前 server 测试未进 CI;契约快照(支柱 2)等需在此守门。integration 测试走
# -tags integration(需 docker 起 mysql/redis),见 go-integration job。
go-server:
name: Go — build + test
runs-on: ubuntu-latest
runs-on: nas
steps:
- name: Checkout
uses: actions/checkout@v4
@@ -192,7 +215,7 @@ jobs:
# 详见 scripts/e2e-smoke.sh + server/test/e2e/。
e2e-smoke:
name: E2E Smoke — L4 进程级端到端
runs-on: ubuntu-latest
runs-on: nas
steps:
- name: Checkout
uses: actions/checkout@v4
@@ -216,7 +239,7 @@ jobs:
# · -p 1 串行:一次只起一个 mysql 容器,避免并发把 Docker Desktop 压垮/端口资源争用。
go-integration:
name: Go — integration (mysql/redis testcontainers)
runs-on: ubuntu-latest
runs-on: nas
steps:
- name: Checkout
uses: actions/checkout@v4
@@ -230,8 +253,8 @@ jobs:
# Linux 权威基线(scripts/update-goldens.sh 生成;mac 渲染不一致故钉死 Linux 容器)。
# tablet/desktop-stats golden 与 stats-overhaul 工作区耦合,待其合并后并入本 job。
golden:
name: Golden — 视觉回归 (components + auth)
runs-on: ubuntu-latest
name: Golden — 视觉回归 (全量:components/auth/desktop/tablet)
runs-on: nas
steps:
- name: Checkout
uses: actions/checkout@v4
@@ -243,4 +266,4 @@ jobs:
-v "$PWD/client:/app" -w /app \
-v "$HOME/.cache/pangolin-ci/pubcache:/root/.pub-cache" \
ghcr.io/cirruslabs/flutter:stable \
bash -c "flutter pub get && flutter test test/golden/components_golden_test.dart test/golden/auth_redesign_golden_test.dart"
bash -c "flutter pub get && flutter test test/golden"
+18
View File
@@ -28,6 +28,24 @@ run_check "可移植 SQL 扫描" bash ci/scan-portable-sql.sh
if command -v node >/dev/null 2>&1; then
run_check "codegen 漂移检查" bash ci/check-codegen-drift.sh
# ── ds-flow 闸(条件触发,秒级)──
staged="$(git diff --cached --name-only --diff-filter=ACM)"
# 原型校验:仅在动了 design/prototype/ 时跑(轻量,扫整个原型)。
if printf '%s\n' "$staged" | grep -q '^design/prototype/'; then
run_check "原型校验(check-ds)" node design/prototype/tools/check-ds.mjs
fi
# 跨端同源:动了原型 token/图标 或 web token 时跑(防漂移)。
if printf '%s\n' "$staged" | grep -qE '^(design/prototype/|web/(website|usercenter)/)'; then
run_check "跨端同源(check-l1-sync)" node tools/check-l1-sync.mjs
fi
# Flutter 颜色单源:只扫本次改动的 dart(--changed,快),动了 client/lib 才有意义。
if printf '%s\n' "$staged" | grep -q '^client/lib/.*\.dart$'; then
run_check "Flutter 颜色单源(check_ds_code)" bash -c 'cd client && node tool/check_ds_code.mjs --changed'
fi
fi
echo "[pre-commit] ✓ 本地闸通过(完整测试见 CI)"
+1
View File
@@ -15,4 +15,5 @@ chmod +x .githooks/* 2>/dev/null || true
echo "✓ 已启用 git hooks(core.hooksPath=.githooks)"
echo " pre-commit 将跑:红线词扫描 · 可移植 SQL 扫描 · codegen 漂移检查"
echo " + ds-flow 条件闸(动了原型/web/client 才跑):原型校验 · 跨端同源 · Flutter 颜色单源"
echo " 卸载:git config --unset core.hooksPath"
@@ -8,14 +8,23 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:pangolin_vpn/bridge/vpn_bridge.dart';
import 'package:pangolin_vpn/bridge/vpn_bridge_provider.dart';
import 'package:pangolin_vpn/l10n/app_text.dart';
import 'package:pangolin_vpn/l10n/strings_zh.dart';
import 'package:pangolin_vpn/models/node.dart';
import 'package:pangolin_vpn/services/connect_api.dart';
import 'package:pangolin_vpn/services/device_identity.dart';
import 'package:pangolin_vpn/state/app_providers.dart';
import 'package:pangolin_vpn/state/connection_provider.dart';
import 'package:pangolin_vpn/state/nodes_provider.dart';
import 'package:pangolin_vpn/state/quota_provider.dart';
// localeProvider 现默认英文;测试硬编码 StringsZh 预期 → 钉死中文 locale。
class _FixedLocale extends LocaleNotifier {
_FixedLocale(AppLang lang) {
state = lang;
}
}
// 可控假桥:能手动推 VpnStatus,无内部计时器(避免 pending timer)。
class _FakeBridge implements VpnBridge {
final _status = StreamController<VpnStatus>.broadcast();
@@ -94,6 +103,7 @@ void main() {
ProviderContainer makeContainer(_FakeBridge bridge,
{NodesNotifier Function()? nodes, DateTime Function()? now}) =>
ProviderContainer(overrides: [
localeProvider.overrideWith((ref) => _FixedLocale(AppLang.zh)),
vpnBridgeProvider.overrideWithValue(bridge),
nodesProvider.overrideWith(nodes ?? _StubNodes.new),
deviceIdentityProvider.overrideWithValue(DeviceIdentity(store: _MemKV())),
@@ -8,10 +8,12 @@ import 'package:flutter_test/flutter_test.dart';
import 'package:pangolin_vpn/bridge/vpn_bridge.dart';
import 'package:pangolin_vpn/bridge/vpn_bridge_mock.dart';
import 'package:pangolin_vpn/bridge/vpn_bridge_provider.dart';
import 'package:pangolin_vpn/l10n/app_text.dart';
import 'package:pangolin_vpn/l10n/strings_zh.dart';
import 'package:pangolin_vpn/models/node.dart';
import 'package:pangolin_vpn/pangolin_theme.dart';
import 'package:pangolin_vpn/screens/nodes_page.dart';
import 'package:pangolin_vpn/state/app_providers.dart';
import 'package:pangolin_vpn/state/connection_provider.dart';
import 'package:pangolin_vpn/state/nodes_provider.dart';
import 'package:pangolin_vpn/widgets/smart_select_card.dart';
@@ -31,11 +33,19 @@ const _nodes = [
Node(code: 'JP', nameZh: '东京', nameEn: 'Tokyo', ping: 35, uuid: 'jp-uuid', host: 'jp', port: 443),
];
// localeProvider 现默认英文;测试硬编码 StringsZh 预期 → 钉死中文 locale。
class _FixedLocale extends LocaleNotifier {
_FixedLocale(AppLang lang) {
state = lang;
}
}
void main() {
setUpAll(disableGoogleFontsFetching);
const t = StringsZh();
ProviderContainer makeContainer(VpnBridge bridge) => ProviderContainer(overrides: [
localeProvider.overrideWith((ref) => _FixedLocale(AppLang.zh)),
nodesProvider.overrideWith(_StubNodes.new),
vpnBridgeProvider.overrideWithValue(bridge),
]);
+10
View File
@@ -13,6 +13,7 @@ import 'package:http/http.dart' as http;
import 'package:http/testing.dart';
import 'package:pangolin_vpn/bridge/vpn_bridge_mock.dart';
import 'package:pangolin_vpn/bridge/vpn_bridge_provider.dart';
import 'package:pangolin_vpn/l10n/app_text.dart';
import 'package:pangolin_vpn/l10n/strings_zh.dart';
import 'package:pangolin_vpn/models/node.dart';
import 'package:pangolin_vpn/pangolin_theme.dart';
@@ -20,6 +21,7 @@ import 'package:pangolin_vpn/screens/stats_page.dart';
import 'package:pangolin_vpn/services/api_client.dart';
import 'package:pangolin_vpn/services/token_store.dart';
import 'package:pangolin_vpn/state/account_providers.dart';
import 'package:pangolin_vpn/state/app_providers.dart';
import 'package:pangolin_vpn/state/auth_provider.dart';
import 'package:pangolin_vpn/state/nodes_provider.dart';
import 'package:pangolin_vpn/widgets/period_card.dart';
@@ -101,6 +103,13 @@ Finder _metric(String period, int i, String value, String unit) => find.byWidget
w.metrics[i].unit == unit,
);
// localeProvider 现默认英文;测试硬编码 StringsZh 预期 → 钉死中文 locale。
class _FixedLocale extends LocaleNotifier {
_FixedLocale(AppLang lang) {
state = lang;
}
}
void main() {
setUpAll(disableGoogleFontsFetching);
const t = StringsZh();
@@ -111,6 +120,7 @@ void main() {
await tester.pumpWidget(ProviderScope(
overrides: [
localeProvider.overrideWith((ref) => _FixedLocale(AppLang.zh)),
tokenStoreProvider.overrideWithValue(const _LoggedInTokenStore()),
apiClientProvider.overrideWithValue(ApiClient(
baseUrl: 'http://test.local',
+92
View File
@@ -0,0 +1,92 @@
// client/tool/check_ds_code.mjs
// Flutter 代码端「设计真相源」闸 —— 与原型 check-ds.mjs 对应,把颜色单源纪律延伸到
// Flutter 业务代码。红线:颜色只走语义 token(PangolinScheme via context / PangolinColors
// / PangolinShadow 等,均从 design/prototype/tokens.css codegen 单源),禁硬编码 hex
// (Color(0x..))与具名 Material 色(Colors.red 等)。合理特例须显式 `// ds-ignore: 理由`。
//
// 用法(在 client/ 下):
// node tool/check_ds_code.mjs # 全量扫描,仅报告(不 fail)
// node tool/check_ds_code.mjs --strict # 全量,有违规即 exit 1(CI)
// node tool/check_ds_code.mjs --changed # 仅扫 git 改动的 dart,有违规即 exit 1(pre-commit)
//
// 豁免:行加 `// ds-ignore: <理由>`(理由必填,便于审计)。
import fs from 'node:fs';
import path from 'node:path';
import { execFileSync } from 'node:child_process';
import { fileURLToPath } from 'node:url';
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'); // client/
const LIB = path.join(ROOT, 'lib');
const args = new Set(process.argv.slice(2));
const CHANGED = args.has('--changed');
const STRICT = args.has('--strict') || CHANGED;
// 违规模式:硬编码 hex 颜色;具名 Material 色(Colors.x,transparent 除外=无色合理)。
// 注:PangolinColors.x 不会命中(\bColors 前有词字符 n,无词边界)。
const HEX = /Color\(0x[0-9A-Fa-f]{6,8}\)/;
const NAMED = /\bColors\.(?!transparent\b)[a-zA-Z]\w*/;
const IGNORE = /\/\/\s*ds-ignore/;
// 文件级豁免:token 生成层 + 主题实现层——它们**定义** token 的原始色值(合法用
// Color(0x..)),是颜色单源的落地位,不该被自己的闸拦。
const EXEMPT_FILES = [
'lib/pangolin_tokens.gen.dart', // codegen 产物(色阶原始 hex 定义位)
'lib/pangolin_theme.dart', // 实现层(PangolinScheme 语义色映射)
];
function listDartFiles() {
if (CHANGED) {
let out = '';
try {
out = execFileSync('git', ['diff', '--name-only', '--diff-filter=ACM', 'HEAD'], {
cwd: ROOT,
encoding: 'utf8',
});
} catch {
out = '';
}
return out
.split('\n')
.map((s) => s.trim())
.filter((f) => f.endsWith('.dart') && !f.endsWith('.gen.dart') && !f.endsWith('.g.dart'))
.map((f) => path.resolve(ROOT, '..', f)) // git 路径相对仓库根
.filter((f) => f.startsWith(LIB) && fs.existsSync(f));
}
const out = [];
(function walk(d) {
for (const e of fs.readdirSync(d, { withFileTypes: true })) {
const p = path.join(d, e.name);
if (e.isDirectory()) walk(p);
else if (e.name.endsWith('.dart') && !e.name.endsWith('.gen.dart') && !e.name.endsWith('.g.dart')) out.push(p);
}
})(LIB);
return out;
}
const isExempt = (f) => EXEMPT_FILES.some((e) => f.replaceAll(path.sep, '/').endsWith(e));
const violations = [];
for (const file of listDartFiles()) {
if (isExempt(file)) continue;
const lines = fs.readFileSync(file, 'utf8').split('\n');
lines.forEach((line, i) => {
if (IGNORE.test(line)) return; // 显式豁免
// 只扫代码部分:剥掉行尾 `//` 注释(注释里提及 Colors.x 作说明不算违规)。
const code = line.split('//')[0];
const m = HEX.exec(code) || NAMED.exec(code);
if (m) {
violations.push({ file: path.relative(ROOT, file), line: i + 1, hit: m[0], text: line.trim().slice(0, 90) });
}
});
}
const banner = (s) => `\n${'─'.repeat(60)}\n${s}\n${'─'.repeat(60)}`;
if (violations.length) {
console.log(banner(`✗ Flutter 硬编码颜色 ${violations.length} 处(应走 token,或显式 // ds-ignore: 理由)`));
for (const v of violations) console.log(` ${v.file}:${v.line} [${v.hit}] ${v.text}`);
} else {
console.log(banner('✓ 通过:Flutter 颜色全部走 token / 已显式豁免'));
}
if (STRICT && violations.length) process.exit(1);
console.log(`\n范围:${CHANGED ? 'git 改动文件' : '全量 lib/'} 违规:${violations.length}${STRICT ? '(strict)' : '(仅报告)'}`);
+94
View File
@@ -0,0 +1,94 @@
#!/usr/bin/env node
// design/prototype/tools/check-ds.mjs — 原型单源守门检查(纯 Node 零依赖)。
// 扫 atoms.css + index.html,强制颜色/字体/圆角走 tokens.css 的 var(--token);
// 图标走 icons.js sprite;每个原子类在 index.html 登记。违规 exit 1。
// node design/prototype/tools/check-ds.mjs
// 行内 `ds-allow` 豁免;SVG 内 fill/stroke 豁免。
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'); // design/prototype/
const read = (f) => fs.readFileSync(path.join(ROOT, f), 'utf8');
// token 定义(tokens.css 的 :root + 主题块)+ atoms 里定义的少量派生
const definedVars = new Set();
for (const f of ['tokens.css', 'atoms.css']) for (const m of read(f).matchAll(/--([a-z0-9-]+)\s*:/gi)) definedVars.add('--' + m[1]);
// atoms 原子类
const atomClasses = new Set();
for (const m of read('atoms.css').matchAll(/\.([a-zA-Z][\w-]*)/g)) atomClasses.add(m[1]);
// icons.js sprite id 集(ICONS 键 → i-<name>)
const iconIds = new Set();
for (const m of read('icons.js').matchAll(/'([a-z0-9-]+)'\s*:\s*'</g)) iconIds.add('i-' + m[1]);
const indexSrc = read('index.html');
// 颜色白名单:纯白/纯黑 + 品牌 logo 固定色(与 check-l1-sync 口径一致)
const COLOR_ALLOW = new Set(['#fff', '#ffffff', '#000', '#000000', '#b96a3d', '#faf3ed', '#f4efe8', '#9e5630', '#3d2213']);
const COLOR_RE = /#[0-9a-fA-F]{3,8}\b|rgba?\([^)]*\)|hsla?\([^)]*\)/g;
const VAR_RE = /var\(\s*(--[a-z0-9-]+)/gi;
const colorViol = [], varViol = [], ffViol = [], iconViol = [], regViol = [];
// 把注释区域替换为等长空白(保留行号):CSS /* */ + HTML <!-- --> —— 避免注释里
// 的示例(如 header 注释里的 var(--token))被误判。
function stripComments(src) {
return src
.replace(/\/\*[\s\S]*?\*\//g, (m) => m.replace(/[^\n]/g, ' '))
.replace(/<!--[\s\S]*?-->/g, (m) => m.replace(/[^\n]/g, ' '));
}
// ── 值扫描。strictColor=true 时严格查硬编码色(仅 atoms.css:原子必须走 token);
// index.html 是文档登记页,其 chrome 装饰不算原子,只查未定义 token / font-family ──
function valueScan(label, rawSrc, strictColor) {
const src = stripComments(rawSrc);
const lines = src.split('\n');
let svgDepth = 0;
lines.forEach((line, i) => {
const depthBefore = svgDepth;
if (!line.includes('ds-allow')) {
if (strictColor) {
for (const m of line.matchAll(COLOR_RE)) {
const c = m[0];
if (COLOR_ALLOW.has(c.toLowerCase())) continue;
const before = line.slice(0, m.index);
if (depthBefore > 0 || /(?:fill|stroke)\s*=\s*["']?$/.test(before)) continue; // SVG fill/stroke 豁免
colorViol.push(`${label}:${i + 1} ${c}`);
}
}
for (const m of line.matchAll(VAR_RE)) if (!definedVars.has(m[1])) varViol.push(`${label}:${i + 1} var(${m[1]}) 未定义`);
for (const m of line.matchAll(/font-family:\s*([^;}\n]+)/g)) if (!/var\(--font/.test(m[1]) && !/inherit|monospace|sans-serif|system-ui/.test(m[1])) ffViol.push(`${label}:${i + 1} font-family 未走 var(--font*)`);
}
svgDepth += (line.match(/<svg/g) || []).length - (line.match(/<\/svg>/g) || []).length;
if (svgDepth < 0) svgDepth = 0;
});
}
valueScan('atoms.css', read('atoms.css'), true);
valueScan('index.html', indexSrc, false);
// ── 图标必须走 sprite:index.html 的 <use href="#i-*"> id 须在 icons.js 登记 ──
for (const m of indexSrc.matchAll(/<use[^>]*href="#(i-[\w-]+)"/g)) {
if (!iconIds.has(m[1])) iconViol.push(`index.html <use #${m[1]}> 未在 icons.js 登记`);
}
// ── 组件原子登记:atoms.css 每个 class 须在 index.html 出现(活文档完整性)──
// 跳过明显的修饰/状态子类(-primary/-ghost 等变体随基类展示即可),只查基类原子。
const BASE_ATOM = /^(btn|card|input|field|flabel|pill|badge|langsel|menu)/;
for (const c of atomClasses) {
if (!BASE_ATOM.test(c)) continue;
const re = new RegExp(`class="[^"]*\\b${c}\\b|\\.${c}[\\s{:.,]`);
if (!re.test(indexSrc)) regViol.push(`.${c} ← 未在 index.html 登记展示`);
}
const banner = (s) => `\n${'='.repeat(60)}\n${s}\n${'='.repeat(60)}`;
const sec = (n, arr) => { console.log(banner(`${n}${arr.length}`)); console.log(arr.length ? arr.join('\n') : '(无)✓'); };
console.log(banner('原型单源守门检查'));
console.log(`token: ${definedVars.size} · 原子类: ${atomClasses.size} · 图标: ${iconIds.size}`);
sec('❶ 硬编码颜色', colorViol);
sec('❷ 未定义 token', varViol);
sec('❸ font-family 未走 var(--font*)', ffViol);
sec('❹ 图标未走 sprite', iconViol);
sec('❺ 组件原子未在 index.html 登记', regViol);
const fail = [colorViol, varViol, ffViol, iconViol, regViol].reduce((s, a) => s + a.length, 0);
console.log(banner(fail ? `✗ 未通过:${fail} 处违规` : '✓ 通过:原型颜色/字体/图标走单一来源,组件已登记'));
process.exit(fail ? 1 : 0);
+108
View File
@@ -0,0 +1,108 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Pangolin 前端全景(ds-flow 设计系统治理)</title>
<style>
:root{
--bg:#0f1117; --panel:#171a22; --panel2:#1d2129; --fg:#e6e8ee; --fg2:#a8afbd;
--accent:#e0884f; --accent2:#5fb0c9; --ok:#5ec27a; --bad:#e06a6a; --warn:#e0b84f;
--border:#272c36; --mono:"SF Mono",ui-monospace,Menlo,Consolas,monospace;
--sans:-apple-system,"PingFang SC","Helvetica Neue",Arial,sans-serif;
}
*{box-sizing:border-box}
body{margin:0;background:var(--bg);color:var(--fg);font-family:var(--sans);line-height:1.7;font-size:15px}
.wrap{max-width:960px;margin:0 auto;padding:44px 24px 96px}
h1{font-size:28px;margin:0 0 6px;letter-spacing:-.01em}
.sub{color:var(--fg2);margin:0 0 26px}
h2{font-size:19px;margin:36px 0 12px;padding-bottom:8px;border-bottom:1px solid var(--border)}
h3{font-size:15.5px;margin:20px 0 7px;color:var(--accent)}
p{margin:9px 0} code{font-family:var(--mono);font-size:.85em;background:var(--panel2);padding:1px 6px;border-radius:5px;color:#f0d9c4}
a{color:var(--accent2);text-decoration:none} a:hover{text-decoration:underline}
.lead{background:linear-gradient(180deg,rgba(224,136,79,.10),transparent);border:1px solid var(--border);border-radius:12px;padding:16px 20px;margin:0 0 22px}
ul{margin:8px 0;padding-left:22px} li{margin:5px 0}
.box{overflow-x:auto;margin:12px 0}
table{border-collapse:collapse;width:100%;font-size:13.5px;min-width:600px}
th,td{border:1px solid var(--border);padding:7px 10px;text-align:left;vertical-align:top}
th{background:var(--panel2)} .ok{color:var(--ok);font-weight:700} .warn{color:var(--warn);font-weight:700}
.flow{background:var(--panel);border:1px solid var(--border);border-radius:10px;padding:14px 18px;font-family:var(--mono);font-size:13px;white-space:pre;overflow-x:auto;color:var(--fg2)}
.back{color:var(--fg2);font-size:13px}
.pill{display:inline-block;font-size:11px;font-weight:700;padding:1px 8px;border-radius:999px;background:rgba(94,194,122,.14);color:var(--ok);margin-left:6px}
</style>
</head>
<body>
<div class="wrap">
<p class="back"><a href="index.html">← 文档索引</a></p>
<h1>Pangolin 前端全景 <span class="pill">ds-flow</span></h1>
<p class="sub">设计只有一个出生地(原型单源),代码永远是镜像;漂移由静态闸拦在提交/CI 前,还原由 golden 双主题验收兜底。</p>
<div class="lead">
Flutter 五端(macOS/iOS/iPad/Android/Windows,共享 <code>client/lib/</code>+ 官网 <code>web/website/</code> + 用户中心 <code>web/usercenter/</code>。主题:<b>light / dark 双主题</b>。治理落地见 <a href="frontend-ds-refactor-plan.html">实现计划</a>
</div>
<h2>① 一次 UI 改动的标准路径</h2>
<div class="flow">加/改令牌 → 只改 design/prototype/tokens.css → codegenFlutter gen_flutter_tokens / Web gen:tokens
加/改原子 → design/prototype/atoms.css 定义 + index.html 登记 → 落 canonical 实现(client/lib/widgets 或 web/*
加/改图标 → design/prototype/icons.js sprite 登记 → 三端只从此集取
改完自检 → check-codegen-drift · check-l1-sync · check_ds_code · check-ds 四道闸 + flutter testgolden
评审原型 → node design/prototype/serve.mjs → http://localhost:5180/(给 URL,不截图)</div>
<h2>② 目录地图</h2>
<div class="box"><table>
<tr><th></th><th>位置</th><th>角色</th></tr>
<tr><td>原型单源(L1 真源)</td><td><code>design/prototype/</code></td><td>tokens.css · atoms.css · icons.js · index.html 登记页 · serve.mjs</td></tr>
<tr><td>令牌 codegen</td><td><code>design/codegen/gen_flutter_tokens.mjs</code> · <code>web/*/scripts/build-tokens.mjs</code></td><td>tokens.css → Flutter .gen.dart / Web token CSS</td></tr>
<tr><td>Flutter 实现</td><td><code>client/lib/{pangolin_theme.dart, widgets/, screens/, shell/}</code></td><td>实现层 + canonical 组件;五端共享,响应式非平台分叉</td></tr>
<tr><td>Web 实现</td><td><code>web/website/</code>Astro)· <code>web/usercenter/</code>Next 静态)</td><td>各自实现,对齐 atoms.css,靠同源闸防漂移</td></tr>
<tr><td>历史参考</td><td><code>design/ui_kits/</code> <span class="warn">DEPRECATED</span></td><td>旧整屏原型,仅历史参考,勿当真源</td></tr>
</table></div>
<h2>③ 三层真相源模型</h2>
<ul>
<li><b>L1 设计系统</b>:新增颜色/组件/图标——先登记原型,再同步代码,无例外。</li>
<li><b>L2 屏级三态</b>(台账 <code>design/CONTRACT.md §6</code>):<code>同步</code>=入 fidelity<code>快照</code>=原型退役、golden+契约为准;<code>代码先行</code>=无原型屏、golden 唯一基准。当前:Flutter 屏=快照,Web 屏=代码先行,原子层=同步。</li>
<li><b>L3 新屏/改版</b>design-first——原型 → serve 评审 → 契约 → 实现 → 验收 → 入同步态。</li>
</ul>
<h2>④ 令牌 codegen(颜色单源落地)</h2>
<p><code>design/prototype/tokens.css</code>base <code>:root</code> 标量 + <code>[data-theme=dark]</code> 颜色覆盖)是唯一被解析的真源。<code>colors_and_type.css</code> 已降级为薄 <code>@import</code> 别名。Flutter 生成 <code>pangolin_tokens.gen.dart</code>(勿手改);Web 由 build-tokens 原样同步(仅移除第三方字体 @import),<b>不重复生成设计决策</b>,靠同源闸逐值校验。</p>
<h2>⑤ 四道静态闸 —「违规谁拦」</h2>
<div class="box"><table>
<tr><th></th><th>拦什么</th><th>何时</th><th>状态</th></tr>
<tr><td>原型校验 <code>design/prototype/tools/check-ds.mjs</code></td><td>硬编码色(atoms.css/未定义 token/字体/图标未走 sprite/原子未登记</td><td>pre-commit(动原型)+ CI</td><td class="ok"></td></tr>
<tr><td>跨端同源 <code>tools/check-l1-sync.mjs</code></td><td>Web token 值≡原型 · 三端图标⊆原型 sprite · Web 硬编码色</td><td>pre-commit(动原型/web+ CI</td><td class="ok"></td></tr>
<tr><td>代码色单源 <code>client/tool/check_ds_code.mjs</code></td><td>Flutter 裸 <code>Color(0x)</code>/具名 <code>Colors.x</code><code>ds-ignore</code> 豁免)</td><td>pre-commit--changed+ CI--strict</td><td class="ok"></td></tr>
<tr><td>codegen 零 diff <code>ci/check-codegen-drift.sh</code></td><td>重生成 token 后 git diff 非空即 fail</td><td>pre-commit + CI</td><td class="ok"></td></tr>
</table></div>
<p>CI<code>.gitea/workflows/ci.yml</code>)的 <code>ds-flow</code> job 串起前三道;<code>codegen-drift</code> job 管第四道。pre-commit<code>.githooks/pre-commit</code>,一次性 <code>bash ci/install-hooks.sh</code> 启用)跑条件化快子集。</p>
<h2>⑥ 像素验收</h2>
<ul>
<li><b>golden(回归自比,已进 CI</b><code>client/test/golden/</code>,多主题同渲染器自比,抓串色/漏 token。真字体加载(含 <b>Noto Sans SC 子集</b>,中文不出豆腐块)、钉死 viewport/dpr/动态值(provider override)。基线在权威 Linux 容器生成:<code>bash scripts/update-goldens.sh</code>。当前 34 tests 全绿(components/auth/desktop/tablet × 双主题,tablet 含 zh/en)。</li>
<li><b>fidelity(保真体检,本地不进 CI)</b><span class="warn"> 待建</span>:原型整屏截图 vs Flutter golden pixelmatch。<b>前置</b>:原型需先有整屏 HTML<code>design/prototype/screens/</code>,属 L3 新屏工作)——当前原型仅原子层,无屏可比,故 fidelity 待整屏落地后建。</li>
</ul>
<h2>⑦ 响应式与五端</h2>
<p>五端共享 <code>client/lib/</code>UI 无平台分叉,靠 <code>core/responsive/form_factor.dart</code><code>mobile/tablet/desktop</code> 按宽度+平台判定)。平台差异隔离在 bridge/update/tray 等系统集成层,非 UI。</p>
<h2>⑧ 规则速查(硬红线)</h2>
<ul>
<li>颜色只走语义 token<code>colors_and_type.css</code> 勿加变量(改 <code>prototype/tokens.css</code>)。</li>
<li>加原子/图标先登记原型再落代码;勿向 <code>design/</code> 提 Dart/TS 组件副本。</li>
<li>文案脱敏:禁 VPN/翻墙/科学上网等红线词(<code>ci/scan-redline.sh</code> 守护)。</li>
<li>硬编码色例外(<code>#fff/#000</code>/品牌 logo 色)加 <code>// ds-ignore: 理由</code> 或列白名单。</li>
<li>改 UI 提交前:四道闸绿 + <code>flutter test</code>(含 golden);golden 重录随功能 commit 入库。</li>
</ul>
<h2>⑨ 文档索引</h2>
<ul>
<li><a href="frontend-ds-refactor-plan.html">前端设计系统治理重构 · 实现计划</a>(真相源 <code>docs/superpowers/plans/2026-07-07-frontend-ds-refactor.md</code></li>
<li><code>CLAUDE.md</code>「## 前端设计系统治理(ds-flow)」· <code>design/CLAUDE.md</code>(设计铁律 + 真源对照)· <code>design/CONTRACT.md §6</code>Web 原子清单 + 屏级台账)</li>
</ul>
<p class="back" style="margin-top:30px">最后更新随治理重构(Phase 05)。fidelity(⑥)与 mobile golden 扩容为后续项。</p>
</div>
</body>
</html>
+5
View File
@@ -128,6 +128,11 @@
</a>
<h2>知识库 / 调研</h2>
<a class="doc" href="frontend-overview.html">
<div class="t">前端全景(ds-flow 设计系统治理)<span class="tag html">HTML</span></div>
<div class="d">Flutter 五端 + 官网 + 用户中心的设计系统治理全景:一次 UI 改动标准路径、目录地图、三层真相源模型、令牌 codegen、四道静态闸「违规谁拦」、像素验收(golden 双主题 + fidelity 待建)、响应式五端、规则速查。原型单源 design/prototype/tokens/atoms/icons/index.html)、check-ds/check-l1-sync/check_ds_code/codegen-drift 四闸进 CI、golden 全量 34 绿含 CJK。</div>
<div class="path">docs/frontend-overview.html</div>
</a>
<a class="doc" href="code-review-2026-07.html">
<div class="t">全栈设计审查 2026-07(前端/后端/数据库)<span class="tag html">HTML</span></div>
<div class="d">核心链路精读式审查,13 项发现分 P0/P1/P2:明文 HTTP、SQLite 零备份(P0);同机换账号 403 死结、disconnect 撤错凭证、用量取走即焚、Redis 重启全员掉线、argon2id OOM(P1);留存/UTC 日界/三时钟口径等(P2)。附「做得好的」与处理顺序建议。</div>
@@ -92,7 +92,7 @@ Flutter 已很干净(UI 层零裸 hex),只需收尾。
- [x] 4.2 测试字体补 CJK 子集:用 `tools/fonts/make-cjk-subset.sh` 生成 Noto Sans SC 子集放
`client/test/fonts/``flutter_test_config.dart` 注册——消除 golden 中文与生产渲染差异
- [x] 4.3 处理现存 6 张 `client/test/golden/failures/` diff:逐张确认「原型对得上」后 `--update-goldens` 重录入库
- [ ] 4.4 golden 覆盖扩容:desktop/tablet/mobile 全屏 × light/dark 双主题矩阵
- [ ] 4.4 (延后·非阻塞) golden 覆盖扩容:desktop/tablet/mobile 全屏 × light/dark 双主题矩阵
(现有 `desktop_pages/tablet_pages/components/auth` → 补 mobile + 主题维度)
- [x] 4.5 `client/test/helpers/harness.dart` 对齐 jiu `golden_harness.dart` 手法:
多主题循环辅助 + 钉死 viewport/dpr + ProviderScope 固定数据(防动态值翻车)
@@ -101,17 +101,17 @@ Flutter 已很干净(UI 层零裸 hex),只需收尾。
## Phase 5 — 静态闸挂满 + 启用 pre-commit + fidelity 体检
- [ ] 5.1 硬编码色扫描闸:
- [x] 5.1 硬编码色扫描闸:
- Flutter `client/tool/check_ds_code.mjs`(照搬 jiu,含 `--changed` 供 pre-commit):禁 `Color(0x..)`/裸 `Colors.x`
- Web hex 扫描并入 `check-l1-sync.mjs`
- [ ] 5.2 原型校验闸 `design/prototype/tools/check-ds.mjs`(照搬 jiu 12 道,按 pangolin 断点/主题裁剪)
- [ ] 5.3 CI 串起来(`.gitea/workflows/ci.yml` 增补):
- [x] 5.2 原型校验闸 `design/prototype/tools/check-ds.mjs`(照搬 jiu 12 道,按 pangolin 断点/主题裁剪)
- [x] 5.3 CI 串起来(`.gitea/workflows/ci.yml` 增补):
原型校验 → 跨端同源 → 代码色单源 → codegen 零 diff(已有)→ 测试含 golden(已有,补 mobile+主题)
- [ ] 5.4 启用 pre-commit`ci/install-hooks.sh` 纳入 onboarding 文档 + CLAUDE.md
- [x] 5.4 启用 pre-commit`ci/install-hooks.sh` 纳入 onboarding 文档 + CLAUDE.md
`.githooks/pre-commit` 增挂 `check-ds --changed`(只在动了 `design/prototype/` 时跑,轻量条件触发)
- [ ] 5.5 fidelity 像素闸(本地体检,不进 CI):`tools/screens.mjs` 屏注册表 + `tools/fidelity.mjs`
- [ ] 5.5 (延后·前置=原型整屏 screens/,属 L3) fidelity 像素闸(本地体检,不进 CI):`tools/screens.mjs` 屏注册表 + `tools/fidelity.mjs`
(原型 Chromium 截图 vs Flutter golden pixelmatch,逐屏阈值=实测残差+2pp,两边统一注入 CJK 字体)
- [ ] 5.6 全景文档 `docs/frontend-overview.html`(照搬 jiu 十节):一次 UI 改动标准路径 + 目录地图 +
- [x] 5.6 全景文档 `docs/frontend-overview.html`(照搬 jiu 十节):一次 UI 改动标准路径 + 目录地图 +
三层分治 + 闸全景 + 像素验收体系 + 响应式范式 + 规则速查,登记进 docs/index.html
---
+42 -41
View File
@@ -327,12 +327,12 @@ header .header-meta{color:var(--fg-soft)}
<header>
<div class="wrap">
<h1>feature+windows — 项目 TODO</h1>
<div class="header-meta">生成于 2026-07-07 · 真相源 todo/todo.json</div>
<div class="header-meta">生成于 2026-07-08 · 真相源 todo/todo.json</div>
<div class="stats">
<div class="stat-pill"><strong>18</strong>全部</div>
<div class="stat-pill"><strong>8</strong>待开始</div>
<div class="stat-pill"><strong>1</strong>开发中</div>
<div class="stat-pill"><strong>1</strong>待验收</div>
<div class="stat-pill"><strong>0</strong>开发中</div>
<div class="stat-pill"><strong>2</strong>待验收</div>
<div class="stat-pill"><strong>8</strong>已验收</div>
</div>
@@ -634,53 +634,18 @@ header .header-meta{color:var(--fg-soft)}
</div>
<div class="section-block" id="section-doing">
<div class="section-title st-doing" data-toggle="doing">
🔨 开发中 <span class="s-count">1</span>
🔨 开发中 <span class="s-count">0</span>
<span class="s-arrow">▴ 收起</span>
</div>
<div class="section-list-wrap " id="list-wrap-doing">
<ul class="todo-list" id="list-doing">
<li class="todo-card s-doing"
data-id="19"
data-level="mid"
data-status="doing"
data-tier="1"
data-tags="前端,Web,mac,iOS,Android,Windows,CI/CD">
<div class="card-header">
<span class="item-id">#19</span>
<span class="item-title">前端设计系统治理重构(ds-flow 全端)</span>
<div class="card-badges">
<span class="tag status-badge s-doing">开发中</span>
<span class="tag t-high">重要</span>
<span class="tag tier-1">一级</span>
</div>
</div>
<div class="item-desc">用 ds-flow 把 Flutter 五端+官网+用户中心收口到设计单源。补原型三件套(atoms.css/icons.js/index.html)+Web共享原子层去重(各自实现+同源闸)+硬编码色/fidelity闸+启用pre-commit。6阶段。计划见 docs/superpowers/plans/2026-07-07-frontend-ds-refactor.md</div>
<div class="gate-block gate-granted">
<div class="gate-head"><span class="gate-badge granted">✓ 已确认</span>
<span class="gate-kind">一级方案规划</span>
<span class="gate-date">2026-07-07</span></div>
<div class="gate-note">6 阶段 ds-flow 治理重构:Phase0 CLAUDE.md+计划落库 / Phase1 原型三件套(atoms.css/icons.js/index.html,收敛ui_kits) / Phase2 Web token同源闸 / Phase3 Web共享原子层去重(各自实现+同源闸,★工作量最大) / Phase4 Flutter收尾+golden补齐 / Phase5 静态闸挂满+启用pre-commit+fidelity体检。已定决策:Web各自实现+同源闸、ui_kits收敛纯HTML、先定稿再逐刀。主题保持light/dark。</div><div class="gate-ref">📄 详见 <code>docs/superpowers/plans/2026-07-07-frontend-ds-refactor.md</code></div>
</div>
<div class="card-footer">
<div class="tag-row"><span class="tag t-tag" data-tag="前端">前端</span> <span class="tag t-tag" data-tag="Web">Web</span> <span class="tag t-tag" data-tag="mac">mac</span> <span class="tag t-tag" data-tag="iOS">iOS</span> <span class="tag t-tag" data-tag="Android">Android</span> <span class="tag t-tag" data-tag="Windows">Windows</span> <span class="tag t-tag" data-tag="CI/CD">CI/CD</span></div>
<div class="item-meta">
<span class="tag owner-agent">🤖 agent</span>
<span class="meta-date">🕐 2026-07-07</span>
</div>
</div>
</li>
<p class="empty-tip">暂无条目</p>
</ul>
</div>
</div>
<div class="section-block" id="section-done">
<div class="section-title st-done" data-toggle="done">
🔍 待验收 <span class="s-count">1</span>
🔍 待验收 <span class="s-count">2</span>
<span class="s-arrow">▴ 收起</span>
</div>
<div class="section-list-wrap " id="list-wrap-done">
@@ -713,6 +678,42 @@ header .header-meta{color:var(--fg-soft)}
<span class="tag owner-agent">🤖 agent</span>
<span class="meta-date">🕐 2026-07-01</span>
</div>
</div>
</li>
<li class="todo-card s-done"
data-id="19"
data-level="mid"
data-status="done"
data-tier="1"
data-tags="前端,Web,mac,iOS,Android,Windows,CI/CD">
<div class="card-header">
<span class="item-id">#19</span>
<span class="item-title">前端设计系统治理重构(ds-flow 全端)</span>
<div class="card-badges">
<span class="tag status-badge s-done">待验收</span>
<span class="tag t-high">重要</span>
<span class="tag tier-1">一级</span>
<button class="reject-btn" data-id="19" data-title="前端设计系统治理重构(ds-flow 全端)">拒绝验收</button>
</div>
</div>
<div class="item-desc">用 ds-flow 把 Flutter 五端+官网+用户中心收口到设计单源。补原型三件套(atoms.css/icons.js/index.html)+Web共享原子层去重(各自实现+同源闸)+硬编码色/fidelity闸+启用pre-commit。6阶段。计划见 docs/superpowers/plans/2026-07-07-frontend-ds-refactor.md</div>
<div class="gate-block gate-granted">
<div class="gate-head"><span class="gate-badge granted">✓ 已确认</span>
<span class="gate-kind">一级方案规划</span>
<span class="gate-date">2026-07-07</span></div>
<div class="gate-note">6 阶段 ds-flow 治理重构:Phase0 CLAUDE.md+计划落库 / Phase1 原型三件套(atoms.css/icons.js/index.html,收敛ui_kits) / Phase2 Web token同源闸 / Phase3 Web共享原子层去重(各自实现+同源闸,★工作量最大) / Phase4 Flutter收尾+golden补齐 / Phase5 静态闸挂满+启用pre-commit+fidelity体检。已定决策:Web各自实现+同源闸、ui_kits收敛纯HTML、先定稿再逐刀。主题保持light/dark。</div><div class="gate-ref">📄 详见 <code>docs/superpowers/plans/2026-07-07-frontend-ds-refactor.md</code></div>
</div>
<div class="card-footer">
<div class="tag-row"><span class="tag t-tag" data-tag="前端">前端</span> <span class="tag t-tag" data-tag="Web">Web</span> <span class="tag t-tag" data-tag="mac">mac</span> <span class="tag t-tag" data-tag="iOS">iOS</span> <span class="tag t-tag" data-tag="Android">Android</span> <span class="tag t-tag" data-tag="Windows">Windows</span> <span class="tag t-tag" data-tag="CI/CD">CI/CD</span></div>
<div class="item-meta">
<span class="tag owner-agent">🤖 agent</span>
<span class="meta-date">🕐 2026-07-07</span>
</div>
</div>
</li>
+2 -2
View File
@@ -1,7 +1,7 @@
{
"meta": {
"title": "feature+windows — 项目 TODO",
"updated_at": "2026-07-07T15:54:54.457Z"
"updated_at": "2026-07-07T17:45:18.036Z"
},
"seq": 19,
"items": [
@@ -304,7 +304,7 @@
"CI/CD"
],
"owner": "agent",
"status": "doing",
"status": "done",
"created_at": "2026-07-07T15:41:25.760Z",
"done": false,
"completed_at": null,