@qijenchen/design-system 0.1.0-beta.47 → 0.1.0-beta.48

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -72,6 +72,15 @@ if echo "$CONTENT" | grep -qE '<DS\.Empty[^>]+title=' && \
72
72
  VIOLATIONS="${VIOLATIONS} - <Empty title=...> 無 icon 無 description = 違反 Empty.tsx:11「預設只需 description」minimal mock looks weird\n"
73
73
  fi
74
74
 
75
+ # Pattern 8: 硬寫色值 / 字級 / shadow 繞過 DS token(2026-06-02 CF conformance-model 補主防線 —
76
+ # composition-fidelity 從 pixel-identity 收窄成 identity-opt-in 後,「consumer 用對 DS token」改由靜態
77
+ # conformance 防線保證,對齊 Polaris stylelint-polaris / Atlassian eslint-plugin / Carbon stylelint。
78
+ # 既有 check_layout_space_magic_numbers 守「間距」;此 pattern 補「色值/字級/shadow」缺口。
79
+ # 零誤判優先:只抓 hardcoded(`-[var(--...)]` token 用法不匹配)。
80
+ if echo "$CONTENT" | grep -qE '\b[a-z][a-z-]*-\[(#[0-9a-fA-F]{3,8}|rgb|rgba|hsl|hsla)[(]?|\btext-\[[0-9]|\bshadow-(sm|md|lg|xl|2xl)\b'; then
81
+ VIOLATIONS="${VIOLATIONS} - 硬寫色值/字級/shadow 繞過 DS token(bg-[#hex] / text-[14px] / shadow-md)→ 改 semantic color token / text-body 等 typography token / shadow-[var(--elevation-N)](per ui-development.md「Tailwind 5 條核心」rule 3)\n"
82
+ fi
83
+
75
84
  # Pattern 6: Overlay trigger without defaultOpen state for visual demo
76
85
  # (Skip in production .tsx; only enforce in .stories.tsx where visual snapshot matters)
77
86
  if echo "$FILE" | grep -qE '\.stories\.tsx$'; then
@@ -534,7 +534,7 @@ rule_story_baseline_reference() {
534
534
  # `.claude/references/story-baseline-registry.json` antiPatterns regex,逐條 scan 寫入內容。
535
535
  # Severity=block → record_worst 2;severity=warn → stderr only。Allowlist
536
536
  # `// @story-baseline-allow: <reason>` 整檔豁免。
537
- # Ship as warn mode first(2026-05-20),確認 zero existing violation 才升 P0 BLOCKER。
537
+ # 2026-06-02 P0 BLOCKER(block-severity antiPattern):DS + consumer 全掃確認零現有違規後升級。
538
538
 
539
539
  rule_story_archetype_registry() {
540
540
  case "$TOOL" in
@@ -592,8 +592,7 @@ rule_story_archetype_registry() {
592
592
  echo " 修法:Read baseline story + helpers,抄 production archetype。" >&2
593
593
  echo " 或加 \`// @story-baseline-allow: <reason>\` 檔頭豁免(audit-logged)。" >&2
594
594
  echo " 詳 .claude/rules/meta-patterns.md M35 + memory/feedback_nearest_same_purpose_canonical.md" >&2
595
- # Ship as warn first(2026-05-20):not record_worst 2,exit 0
596
- # Future:zero existing violation → 改 record_worst 2 升 P0 BLOCKER
595
+ record_worst 2 # 2026-06-02 P0 BLOCKER:DS + consumer(ds-product-template)全掃確認零現有違規後升級(原 2026-05-20 ship-as-warn 的 TODO 條件達成);只 block-severity antiPattern(warn-severity 仍 stderr-only)
597
596
  fi
598
597
  done <<< "$PATTERNS"
599
598
  done
@@ -10,10 +10,11 @@
10
10
  # R5 name_jargon(PostToolUse;reads disk;L<n> layer / canonical / 中英夾雜 jargon)
11
11
  # R6 description_jargon(PostToolUse;TS generic in description: → stderr warn)
12
12
  # R7 story_baseline_reference(PreToolUse;wrap Sidebar/ChromeHeader/DataTable 無 baseline marker → stderr warn)
13
- # R8 story_archetype_registry(PreToolUse;讀 .claude/references/story-baseline-registry.json,warn-only)
13
+ # R8 story_archetype_registry(PreToolUse;讀 .claude/references/story-baseline-registry.json;
14
+ # block-severity antiPattern → P0 record_worst 2,2026-06-02 升 P0)
14
15
  #
15
16
  # Test 重點:silent skip / 各 rule fire / allowlist marker escape。
16
- # 不測 R3/R8(需 spec.md registry 完整 fixture,scope 超 batch A)。
17
+ # 不測 R3(需 spec.md frontmatter);R8 P0 block-severity 已測(#11,2026-06-02)。
17
18
 
18
19
  set -u
19
20
 
@@ -194,12 +195,26 @@ STORIES_APP="/foo/my-project/packages/design-system/src/components/AppShell/app-
194
195
  run_hook "PreToolUse" "Write" "$STORIES_APP" '
195
196
  export const Default = () => (
196
197
  <Sidebar>
197
- <SidebarHeader><span>Acme</span></SidebarHeader>
198
+ <SidebarHeader><WorkspaceBrand /></SidebarHeader>
198
199
  </Sidebar>
199
200
  );
200
201
  '
201
202
  expect_warn "10. R7 wrap <Sidebar> no @story-baseline → stderr warn" "R7 story_baseline_reference"
202
203
 
204
+ # 11. R8 archetype registry P0(2026-06-02 升 P0;DS+consumer 零違規確認後升級):
205
+ # wrap <Sidebar> + simplified <SidebarHeader><span> mock(registry block-severity antiPattern)→ BLOCK
206
+ # 有 @story-baseline marker(R7 missing-marker 不 fire)→ exit 2 純由 R8 record_worst 2 造成
207
+ STORIES_R8="/foo/my-project/packages/design-system/src/components/AppShell/app-shell-r8.stories.tsx"
208
+ run_hook "PreToolUse" "Write" "$STORIES_R8" '
209
+ // @story-baseline: @qijenchen/design-system/components/Sidebar/sidebar.stories.tsx#IconCollapse
210
+ export const Default = () => (
211
+ <Sidebar>
212
+ <SidebarHeader><span>Acme</span></SidebarHeader>
213
+ </Sidebar>
214
+ );
215
+ '
216
+ expect_block "11. R8 simplified-mock <SidebarHeader><span> → P0 BLOCK" "R8 story_archetype_registry"
217
+
203
218
  echo ""
204
219
  echo "=== Summary ==="
205
220
  echo "Passed: $PASS / $((PASS + FAIL))"
@@ -1,101 +1,47 @@
1
- # Composition Fidelity Visual Diff(SSOT,2026-05-27 codify)
1
+ # Composition Fidelity(SSOT,2026-05-27 初版 / 2026-06-02 conformance-model 修正)
2
2
 
3
- **Mechanical 機制讓「DS components 在 consumer (ds-product-template) 渲染必跟 DS canonical 一致」可被驗證**(byte-identity 不夠,需 visual diff)。
3
+ **目標**:驗證「DS components 在 consumer(ds-product-template / fork)被正確使用、沒違反設計原則 / SSOT token」。
4
4
 
5
- Per user 2026-05-27 verbatim directive 對應 root cause:**DS source 提供 primitive,consumer 自由 compose;npm byte-identity 不約束 compose 方式**。Consumer 可寫 `<Avatar size={32}>` 即使 DS canonical demo 用 `<Avatar size={24}>` — bytes 同但 visual drift。
5
+ **2026-06-02 模型修正(per CF research + world-class benchmark + 專案 2026-05-27 自身結論)**:
6
+ 驗的是「**consumer 有沒有用對 DS(conformance)**」,**不是**「consumer 畫面跟 DS showcase 長得一模一樣(pixel-identity)」。後者對「內容刻意不同的產品範本」是 **false-positive 來源、世界級公認反 pattern**。
6
7
 
7
- ## 對齊世界級
8
+ ## 對齊世界級(2026-06-02 WebFetch verified,修正初版未驗 benchmark)
8
9
 
9
- - **Polaris**(Shopify):Chromatic visual regression on every PR(component-level + page-level)— Polaris 自家 templates partner consumer apps 都被 diff
10
- - **Material UI X** PR snapshot gate:`mui-x` package consumer apps 用 `@mui/x-data-grid` 必過 visual snapshot
11
- - **Carbon Design System**(IBM):Percy visual diff cross DS storybook + consumer-app sample
12
- - **Atlassian Design System**:VR(visual regression)CI on consumer repos pulling DS
10
+ 初版宣稱「Polaris/MUI/Carbon/Atlassian 都做 cross DS+consumer pixel diff」= **未驗證 + 不實**(M22/M26 違反)。實證(URL):
13
11
 
14
- ## 機制三層
12
+ - **Consumer 用對 DS = 一律靜態 lint,非截圖**:Polaris [`stylelint-polaris`](https://polaris-react.shopify.com/tools/stylelint-polaris)(40+ rule promote DS adoption in consuming apps;「Please use a Polaris color token」)/ Atlassian [`@atlaskit/eslint-plugin-design-system`](https://atlassian.design/components/eslint-plugin-design-system/)(`ensure-design-token-usage` / `prefer-primitives` / `use-tokens-space`,帶 auto-fix)/ Carbon [`stylelint-plugin-carbon-tokens`](https://github.com/carbon-design-system/stylelint-plugin-carbon-tokens)(「enforce Carbon tokens ... rather than hard-coded values」)/ MUI(TS theme 型別 + eslint)。
13
+ - **Visual regression = 同一個 story 跨 commit 比自己的 baseline**(抓 DS 改壞自己),非跨 repo 比不同畫面:[Chromatic](https://www.chromatic.com/docs/branching-and-baselines/)(「baseline = last known good state of the story ... from a previous commit on that same branch」)/ [Storybook](https://storybook.js.org/docs/writing-tests/visual-testing)(「compare rendered pixels of every story against known baselines」+ 測 component-in-isolation)。
14
+ - **VR deterministic 鐵律**:seed 靜態 fixture + mask 動態區。產品 app 內容本質跟 DS showcase 不同 → 拿來 pixel 比必全紅 = 100% false positive。
15
15
 
16
- | | 落地 | SSOT |
16
+ ## 機制三層(conformance 主、pixel identity 窄)
17
+
18
+ | 層 | 驗什麼 | 落地 |
17
19
  |---|---|---|
18
- | 1 source-level marker | Consumer story tsx 開頭 `// @story-baseline: <DS-path>#<exportName>` | story-rules.md「Production-grade composition fidelity」段 + check_story_invariants.sh R7 |
19
- | 2 anti-pattern hook | DS-internal grep regex simplified mock pattern | `.claude/references/story-baseline-registry.json` + check_story_invariants.sh R8 |
20
- | 3 **visual diff CI** | DS canonical screenshot vs consumer story screenshot,pixel diff threshold | `scripts/composition-fidelity-visual-diff.mjs` + `npm run composition-fidelity` |
20
+ | **1 marker(conformance 意圖)** | consumer wrap 高風險 primitive 必標 `// @story-baseline: <DS-path>#<export>` cite canonical | `check_consumer_story_baseline.sh`(P0)+ `check_story_invariants.sh` R7 |
21
+ | **2 靜態 conformance(主防線)** | consumer 用對 token/primitive、沒 simplified mock、沒硬寫色值/字級/間距/shadow、沒 API-misuse | `check_consumer_ds_primitive_misuse.sh`(P0,含 2026-06-02 Pattern 8 硬寫 token)+ `check_layout_space_magic_numbers.sh`(P0 間距)+ `check_story_invariants.sh` R8(P0,registry antiPattern)+ `check_chrome_header_avatar_canonical.sh` / `check_sidebar_menu_button_implicit_wrap.sh` |
22
+ | **3 pixel/DOM identity(opt-in,窄用)** | 僅「忠實複製 replica」或「同 story 跨版本回歸」才比 render identity | `scripts/composition-fidelity-visual-diff.mjs`,**只比標了 `@composition-fidelity-mode` mapping**;單獨 `@story-baseline` = conformance 不做 identity diff |
21
23
 
22
- 1+2 ship(2026-05-20)。**層 3 = 本檔 SSOT(2026-05-27 ship)**。
24
+ **層 3 為何 opt-in(2026-06-02)**:`@story-baseline` 單獨 = conformance 意圖(交層 2 驗)。pixel/DOM identity 只在該 consumer story **額外標** `@composition-fidelity-mode: pixel|shell-only|structural` 才跑(用於忠實複製 / same-story 回歸)。0 個 opt-in → script exit 0(conformance 由層 2 保證)。**禁** 拿產品範本(內容刻意不同)去 pixel 比 DS showcase。
23
25
 
24
- ## 層 3 用法
26
+ ## 層 3 用法(opt-in 時)
25
27
 
26
28
  ```bash
27
- # Local — against running storybooks:
28
- npm run composition-fidelity -- \
29
- --ds-url=http://localhost:9001 \
30
- --consumer-url=http://localhost:9002 \
31
- --consumer-root=/path/to/ds-product-template \
32
- --out=.claude/snapshots/composition-fidelity \
33
- --threshold-pct=2
34
-
35
- # CI — against built storybook-static dirs:
36
- npm run composition-fidelity -- \
29
+ node scripts/composition-fidelity-visual-diff.mjs \
37
30
  --ds-static=storybook-static \
38
31
  --consumer-static=/path/to/ds-product-template/storybook-static \
39
- --consumer-root=/path/to/ds-product-template \
40
- --threshold-pct=0.5
32
+ --consumer-root=/path/to/ds-product-template --threshold-pct=0.5
41
33
  ```
42
34
 
43
- **Mapping SSOT**:`@story-baseline:` marker in consumer source file(s)。Script parses marker → derives DS canonical story id cross-side screenshot + pixelmatch diff。Fail PR 若任一 mapping diff > threshold-pct
44
-
45
- **Exit codes**:0 = all within threshold / 1 = at least one exceeds / 2 = setup error。
46
-
47
- ## Threshold guidance
48
-
49
- - `0.5%` — strict baseline change(intentional fix必 update baseline)
50
- - `2%` — typical(allows brand text difference like "Acme Inc" vs "Acme Product")
51
- - `5%` — initial bootstrap(consumer 多元化內容差異)
52
-
53
- **Initial ds-product-template baseline 1.41%**(measured 2026-05-27):brand text + NAV labels content-level diff。Structural composition byte-equal。
54
-
55
- ## CI workflow(shipped)
56
-
57
- Actual gate is centralized in DS repo at `.github/workflows/composition-fidelity.yml`:checkout DS + `ajenchen/ds-product-template`,build both Storybooks,and run the local DS script against the two `storybook-static` directories.
58
-
59
- ```yaml
60
- # .github/workflows/composition-fidelity.yml(design-system)
61
- on: [push, pull_request]
62
- jobs:
63
- composition-fidelity:
64
- runs-on: ubuntu-latest
65
- steps:
66
- - uses: actions/checkout@v4
67
- with: { path: design-system }
68
- - uses: actions/checkout@v4
69
- with: { repository: ajenchen/ds-product-template, path: ds-product-template }
70
- - run: npm ci --legacy-peer-deps
71
- working-directory: design-system
72
- - run: npm ci --legacy-peer-deps
73
- working-directory: ds-product-template
74
- - run: npm run build-storybook
75
- working-directory: design-system
76
- - run: npm run build-storybook
77
- working-directory: ds-product-template
78
- - run: |
79
- node scripts/composition-fidelity-visual-diff.mjs \
80
- --ds-static=storybook-static \
81
- --consumer-static=../ds-product-template/storybook-static \
82
- --consumer-root=../ds-product-template \
83
- --threshold-pct=0.5
84
- working-directory: design-system
85
- ```
35
+ 掛在 `.github/workflows/composition-fidelity.yml`(checkout DS + ds-product-template,build 兩邊 storybook 後比對;**build-storybook 前必先 `build:lib`** 否則 storybook-config tsc 找不到 `@qijenchen/design-system` 型別 = TS2307)
86
36
 
87
37
  ## 不該做的事
88
38
 
89
- - ❌ baseline screenshots commit product-workspace repo(stale 風險)— 改 fetch from DS Pages live
90
- - ❌ Threshold 0%(content-level diff 必然有,brand text / NAV labels)
91
- - ❌ raw DOM diff(M32 educated:pixel-quantified verify attribute existence)
92
- - ❌ 抽樣 5 stories(M-rule 不抽樣 / 不少於 user 明示「所有元件」scope)
93
-
94
- ## 反 pattern 錨例
39
+ - ❌ **拿產品範本 demo 當 pixel-identity baseline DS showcase 比**(內容刻意不同 = false positive,world-class 公認反 pattern)— 2026-06-02 identity opt-in 修正
40
+ - ❌ baseline story 用 `Math.random()` 等非確定性產值(render 不可重現 → 任何 diff 都是 noise)— 2026-06-02 `sidebar.stories.tsx` PageContent
41
+ - ❌ baseline screenshots commit 進 consumer repo(stale)— fetch from DS Pages live
42
+ - ❌ 抽樣 N stories(M-rule 不抽樣)
95
43
 
96
- **2026-05-27**:user 抓 AppShell Avatar+Label drift。Triple-verify 發現:
97
- 1. Source byte-equivalent(DS sidebar.stories.tsx WorkspaceBrand 跟 product-workspace App.tsx 同 pattern)
98
- 2. Stale build artifact:DS storybook-static built BEFORE commit 4e3256c1 fix → DS render 用 ItemAvatar wrapper / consumer 用 raw Avatar
99
- 3. User screenshot 從 stale deploy 看到「DS-rendered」vs「consumer-rendered」structural diff
44
+ ## 歷史錨例
100
45
 
101
- **Lesson**:byte-identity ≠ visual identity ≠ deployed identity。三層皆需 mechanical verifyComposition-fidelity 機制覆蓋第 3 層。
46
+ - **2026-05-27**:user 抓 AppShell Avatar+Label drift。Root cause = stale build artifact(DS storybook-static built BEFORE fix commit)。Lesson:byte-identity ≠ visual ≠ deployed identity。
47
+ - **2026-06-02**:CF check 從建立起 30 次全紅 = 把產品範本(App.tsx,Acme Product / 自訂 nav / dashboard 內容)硬比 DS `sidebar#IconCollapse`(Acme Inc / 不同 nav / 隨機亂數內容),pixel/DOM identity 物理上不可能。修法:層 3 改 opt-in、補層 2 靜態 conformance(Pattern 8 + R8 升 P0)、修 baseline 隨機亂數。對齊專案 2026-05-27 自身結論(memory `feedback_ai_ground_truth_unreliable_mechanical_primary`:render fidelity 由架構保障、template-vs-canonical pixel diff = noise 非 drift)。
@@ -7,7 +7,7 @@
7
7
  "scripts/composition-fidelity-visual-diff.mjs",
8
8
  "product-workspace apps/template/src/AllDsComponents.stories.tsx (DsCanonicalPortal)"
9
9
  ],
10
- "generatedAt": "2026-06-02T14:01:59.871Z"
10
+ "generatedAt": "2026-06-02T14:55:19.698Z"
11
11
  },
12
12
  "components": {
13
13
  "accordion": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qijenchen/design-system",
3
- "version": "0.1.0-beta.47",
3
+ "version": "0.1.0-beta.48",
4
4
  "private": false,
5
5
  "description": "World-class design system — components, patterns, tokens, hooks (single source of truth for team distribution).",
6
6
  "type": "module",
@@ -132,10 +132,11 @@ const PageContent = ({ title, description }: { title: string; description: React
132
132
  <div className="max-w-2xl">
133
133
  <p className="text-body text-fg-secondary mb-6">{description}</p>
134
134
  <div className="grid grid-cols-2 gap-4">
135
- {['專案數量', '團隊成員', '本週提交', '待處理'].map((t) => (
135
+ {/* 固定值(非 Math.random) story 是 visual baseline,必確定性,否則每次 build 像素不同 = composition-fidelity / visual regression 無法比對(2026-06-02 fix)*/}
136
+ {([['專案數量', 24], ['團隊成員', 8], ['本週提交', 47], ['待處理', 3]] as const).map(([t, v]) => (
136
137
  <div key={t} className="rounded-lg border border-divider bg-surface p-4">
137
138
  <p className="text-caption text-fg-muted">{t}</p>
138
- <p className="text-h5 font-semibold mt-1">{Math.floor(Math.random() * 100)}</p>
139
+ <p className="text-h5 font-semibold mt-1">{v}</p>
139
140
  </div>
140
141
  ))}
141
142
  </div>