@qijenchen/design-system 0.1.0-beta.134 → 0.1.0-beta.136
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.
- package/ds-canonical/fork/consumer/lock.json +4 -4
- package/ds-canonical/fork/governance.lock +2 -2
- package/ds-canonical/fork/hooks/check_ds_anchor_preflight.sh +2 -2
- package/ds-canonical/fork/manifest.json +10 -10
- package/ds-canonical/hooks/README.md +6 -4
- package/ds-canonical/hooks/check_ds_anchor_preflight.sh +2 -2
- package/ds-canonical/hooks/session_start_governance_check.sh +5 -5
- package/ds-canonical/hooks/stop_self_audit.sh +40 -0
- package/ds-canonical/hooks/tests/test_stop_self_audit.sh +31 -0
- package/ds-canonical/references/ssot-index.md +2 -2
- package/ds-canonical/rules/README.md +1 -1
- package/ds-canonical/rules/meta-patterns.md +4 -4
- package/ds-canonical/skills/design-system-audit/references/rule-placement.md +10 -6
- package/ds-canonical/skills/governance-health/SKILL.md +3 -1
- package/package.json +1 -1
- package/src/components/AgentPanel/agent-panel.spec.md +3 -3
- package/src/components/Checkbox/checkbox.spec.md +1 -1
- package/src/components/Select/select.spec.md +1 -1
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
"schemaVersion": 1,
|
|
4
4
|
"role": "template-consumer",
|
|
5
5
|
"release": {
|
|
6
|
-
"designSystem": "0.1.0-beta.
|
|
7
|
-
"storybookConfig": "0.1.0-beta.
|
|
6
|
+
"designSystem": "0.1.0-beta.136",
|
|
7
|
+
"storybookConfig": "0.1.0-beta.136"
|
|
8
8
|
},
|
|
9
9
|
"upgradeTrust": {
|
|
10
10
|
"schemaVersion": 1,
|
|
@@ -42,10 +42,10 @@
|
|
|
42
42
|
"implementationSha256": "98b74118fb5b410b77ad2994aa9105fa7609c7353822f8c5213cce54d94f0af4"
|
|
43
43
|
},
|
|
44
44
|
"payload": {
|
|
45
|
-
"forkCorpusLockSha256": "
|
|
45
|
+
"forkCorpusLockSha256": "98b621dc50c9f3baeafa1c8a422931066f35a887ad8944d3c79389e11e31c67f",
|
|
46
46
|
"discoveryPolicySha256": "fba289aeeb5a3f11139b9589f7ab08b5e8c8799c7bdf8f0ea57ebfeb8e7c9da6",
|
|
47
47
|
"providerMaterializersSha256": "732e85b01a12e7dd19469e26e4427f0e0cfe5117a78fc9f69c2f0f89702f463a",
|
|
48
|
-
"providerLifecycleSha256": "
|
|
48
|
+
"providerLifecycleSha256": "27e611b19cb01f2ad64fb4548de352b212bde528757f7d816d19a4f52b3f9159",
|
|
49
49
|
"providerInventorySha256": "00adf1c4f6b3367055a9cc3fdfe658660771b4c9931f696e12761a95076ad92c",
|
|
50
50
|
"executionRuntimeSha256": "d9377cc45308731f287d7b1711b7c38fc4e02ff8f84729bcbbf01bb52d136904",
|
|
51
51
|
"commonInstructionSha256": "a72c51a412d6cdb3961d691744013c8362e826626a330cecf4a296a6f4d93a5c",
|
|
@@ -679,7 +679,7 @@
|
|
|
679
679
|
{
|
|
680
680
|
"schemaVersion": 1,
|
|
681
681
|
"file": "hooks/check_ds_anchor_preflight.sh",
|
|
682
|
-
"sha256": "
|
|
682
|
+
"sha256": "b2dee125fc51d840a6b18e8dcdfd61b0b3e52e30b2d3e8bf8f9d190d96cdc12b",
|
|
683
683
|
"source": "check_ds_anchor_preflight.sh",
|
|
684
684
|
"classification": "SHIP_REWRITTEN",
|
|
685
685
|
"destination": null
|
|
@@ -919,7 +919,7 @@
|
|
|
919
919
|
{
|
|
920
920
|
"schemaVersion": 1,
|
|
921
921
|
"file": "manifest.json",
|
|
922
|
-
"sha256": "
|
|
922
|
+
"sha256": "ec51e4107e3345673c76cda0d4a9fc9aafa4bf47a93be3c65efd0cb7e8c9538c",
|
|
923
923
|
"source": null,
|
|
924
924
|
"classification": null,
|
|
925
925
|
"destination": null
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
# Detection:scan transcript 過去 ~30 turns:
|
|
22
22
|
# (a) 有 Grep / Read tool call hit `node_modules/@qijenchen/design-system/src/**/*.spec.md` OR
|
|
23
23
|
# `**/*.stories.tsx`(canonical baseline) → PASS
|
|
24
|
-
# (b) 無 →
|
|
24
|
+
# (b) 無 → P0 BLOCKER(exit 2),並在 stderr 印出「grep canonical → 3-column owner table」的修法。
|
|
25
25
|
#
|
|
26
26
|
# 對齊 meta-patterns.md M29 「視覺/結構 propose 前必 grep DS spec.md 找 owner SSOT(M29)— 出 3-column 表」+
|
|
27
27
|
# `packages/design-system/ds-canonical/rules/self-verify.md` Pre-edit phase
|
|
@@ -185,7 +185,7 @@ fi
|
|
|
185
185
|
|
|
186
186
|
REL_PATH=${FILE_PATH#"$GOVERNANCE_PROJECT_DIR"/}
|
|
187
187
|
|
|
188
|
-
#
|
|
188
|
+
# BLOCKER 訊息本體(hybrid:先印修法、再 exit 2;對齊 check_substantive_edit_approval_preflight.sh)
|
|
189
189
|
cat >&2 <<EOF
|
|
190
190
|
🚨 M29 DS Anchor Preflight — visual/structural edit 偵測
|
|
191
191
|
|
|
@@ -104,20 +104,20 @@
|
|
|
104
104
|
},
|
|
105
105
|
"providerLifecycle": {
|
|
106
106
|
"schemaVersion": 4,
|
|
107
|
-
"releaseVersion": "0.1.0-beta.
|
|
107
|
+
"releaseVersion": "0.1.0-beta.136",
|
|
108
108
|
"immutableHead": {
|
|
109
109
|
"providerInventorySha256": "86e9d0668cb54ad1bf05c36e78b4aa80417d5f468c929dd3ddcf69c1afbf32e4",
|
|
110
|
-
"releaseVersion": "0.1.0-beta.
|
|
111
|
-
"snapshotSha256": "
|
|
110
|
+
"releaseVersion": "0.1.0-beta.135",
|
|
111
|
+
"snapshotSha256": "8814d3c2a71f1eae73408864def3b566bbcb88a45377dc53a28fec00b90e7a3a"
|
|
112
112
|
},
|
|
113
|
-
"ledgerSha256": "
|
|
114
|
-
"previousSnapshotSha256": "
|
|
115
|
-
"currentSnapshotSha256": "
|
|
113
|
+
"ledgerSha256": "523ce8f6ceec7fbaca1fba28e38946b97b3b7281de38acf422efabc34afa070d",
|
|
114
|
+
"previousSnapshotSha256": "8814d3c2a71f1eae73408864def3b566bbcb88a45377dc53a28fec00b90e7a3a",
|
|
115
|
+
"currentSnapshotSha256": "1601a1ea9c1a957f0ba228ddcca2acc17f1d0f2843f91101cf0780825075c0e3",
|
|
116
116
|
"currentProviderInventorySha256": "86e9d0668cb54ad1bf05c36e78b4aa80417d5f468c929dd3ddcf69c1afbf32e4",
|
|
117
117
|
"retiredProviders": [],
|
|
118
118
|
"currentSnapshot": {
|
|
119
|
-
"releaseVersion": "0.1.0-beta.
|
|
120
|
-
"previousSnapshotSha256": "
|
|
119
|
+
"releaseVersion": "0.1.0-beta.136",
|
|
120
|
+
"previousSnapshotSha256": "8814d3c2a71f1eae73408864def3b566bbcb88a45377dc53a28fec00b90e7a3a",
|
|
121
121
|
"providers": [
|
|
122
122
|
{
|
|
123
123
|
"id": "claude",
|
|
@@ -193,8 +193,8 @@
|
|
|
193
193
|
"retiredProviders": []
|
|
194
194
|
},
|
|
195
195
|
"immutableHeadSnapshot": {
|
|
196
|
-
"releaseVersion": "0.1.0-beta.
|
|
197
|
-
"previousSnapshotSha256": "
|
|
196
|
+
"releaseVersion": "0.1.0-beta.135",
|
|
197
|
+
"previousSnapshotSha256": "684bf42d3c7a9e075f60cebce0c68d442239cda935624c4dbe8d07acbcae8fcd",
|
|
198
198
|
"providers": [
|
|
199
199
|
{
|
|
200
200
|
"id": "claude",
|
|
@@ -56,15 +56,17 @@
|
|
|
56
56
|
|------|--------|
|
|
57
57
|
| `stop_passive_logging.sh` | **Dispatcher**(2026-05-13 prune):一次跑 5 rule(tsc sanity / harvest corrections / capture metrics / governance drift / infra best-practice score)— stop hook count 3 → 2 |
|
|
58
58
|
| `stop_self_audit.sh` | turn 行為 audit(claim 沒 verify / prune trigger / topic 重複 ≥ 3 次 → BLOCKER inject,M20 100+ failure mode 升級 2026-05-13) |
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
59
|
+
|
|
60
|
+
Stop 只有上面兩支**檔案**。2026-05-13 prune 把 `stop_harvest_corrections.sh`(user 糾正信號)、
|
|
61
|
+
`stop_capture_metrics.sh`(session metric snapshot)、`stop_meta_self_audit.sh`(infra-score,
|
|
62
|
+
8 維 score 跌 ≥ 5 / 任何 dim < 80 → silent log)折進 `stop_passive_logging.sh` 的 R2 / R3 / R5,
|
|
63
|
+
**舊檔名已不存在**;要看那三條邏輯去 dispatcher 內對應的 rule function。
|
|
62
64
|
|
|
63
65
|
### SessionStart
|
|
64
66
|
|
|
65
67
|
| Hook | 做什麼 |
|
|
66
68
|
|------|--------|
|
|
67
|
-
| `session_start_governance_check.sh` |
|
|
69
|
+
| `session_start_governance_check.sh` | 多個 check(行數 / prune 逾期 / corrections / benchmark 新鮮度 / fire-weighted test gap / scan-similar-bugs / hook 數 / memory 數 / branch sprawl / counter drift / cross-repo smoke)。**數量與門檻不在本檔硬寫** — 以該腳本的 `^# Check N` 段落與 AGENTS.md `# 治理 canonical` 為準 |
|
|
68
70
|
|
|
69
71
|
### UserPromptSubmit
|
|
70
72
|
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
# Detection:scan transcript 過去 ~30 turns:
|
|
22
22
|
# (a) 有 Grep / Read tool call hit `packages/design-system/src/**/*.spec.md` OR
|
|
23
23
|
# `**/*.stories.tsx`(canonical baseline) → PASS
|
|
24
|
-
# (b) 無 →
|
|
24
|
+
# (b) 無 → P0 BLOCKER(exit 2),並在 stderr 印出「grep canonical → 3-column owner table」的修法。
|
|
25
25
|
#
|
|
26
26
|
# 對齊 meta-patterns.md M29 「視覺/結構 propose 前必 grep DS spec.md 找 owner SSOT(M29)— 出 3-column 表」+
|
|
27
27
|
# `packages/design-system/ds-canonical/rules/self-verify.md` Pre-edit phase
|
|
@@ -185,7 +185,7 @@ fi
|
|
|
185
185
|
|
|
186
186
|
REL_PATH=${FILE_PATH#*/my-project/}
|
|
187
187
|
|
|
188
|
-
#
|
|
188
|
+
# BLOCKER 訊息本體(hybrid:先印修法、再 exit 2;對齊 check_substantive_edit_approval_preflight.sh)
|
|
189
189
|
cat >&2 <<EOF
|
|
190
190
|
🚨 M29 DS Anchor Preflight — visual/structural edit 偵測
|
|
191
191
|
|
|
@@ -217,12 +217,12 @@ elif [ -d "$HOOKS_DIR" ]; then
|
|
|
217
217
|
2>/dev/null | wc -l | tr -d ' ')
|
|
218
218
|
HOOK_COUNT=${HOOK_COUNT:-0}
|
|
219
219
|
fi
|
|
220
|
-
# 2026-08-05
|
|
221
|
-
#
|
|
222
|
-
#
|
|
223
|
-
#
|
|
220
|
+
# 現值**不在此硬寫**:本檔曾同時留下「59、headroom=1」(2026-08-05 註解)與「已為 60、零 headroom」
|
|
221
|
+
# (2026-07-31 訊息字串)兩個互相矛盾的庫存數,兩個都會過期。真值一律由下方 find predicate 當場算,
|
|
222
|
+
# 或 `npm run sync:counters` 跑出;60 是邊界不是基準,下一支 first-class hook 必先由
|
|
223
|
+
# /knowledge-prune retire / consolidate 既有 hook 騰出空間。
|
|
224
224
|
if [ "$HOOK_COUNT" -gt 60 ]; then
|
|
225
|
-
BLOCKERS="${BLOCKERS}\n- Hook count ${HOOK_COUNT}(hard 60 — Anthropic guideline ~15;含 root + lib/,排 retired/tests
|
|
225
|
+
BLOCKERS="${BLOCKERS}\n- Hook count ${HOOK_COUNT}(hard 60 — Anthropic guideline ~15;含 root + lib/,排 retired/tests/ 與 _* helper)。超 60 = 先跑 /knowledge-prune 評估 retire / consolidate,不直接 re-raise。"
|
|
226
226
|
elif [ "$HOOK_COUNT" -gt 26 ]; then
|
|
227
227
|
# 2026-05-15 raised soft cap 25→26 per /knowledge-prune D2 audit:
|
|
228
228
|
# 26 wired hooks reflects M30 wrapper-schema-drift 新增 dedicated hook(justified evolution
|
|
@@ -489,6 +489,30 @@ if [ -n "$LAST_ASSISTANT" ] && [ -n "$TRANSCRIPT_PATH" ] && [ -f "$TRANSCRIPT_PA
|
|
|
489
489
|
fi
|
|
490
490
|
fi
|
|
491
491
|
|
|
492
|
+
# ── Mechanism 10: 自鎖後把動作推給 user(M36(b),2026-09-19)────────────────
|
|
493
|
+
# SSOT = meta-patterns M36(b)/(b') + governance/memory/feedback_anti_self_lock_release_transport.md。
|
|
494
|
+
#
|
|
495
|
+
# 為什麼要機械化:這條規則 2026-08-08 就立了、2026-08-11 連環五鎖逐層拆完、2026-08-28 還特別
|
|
496
|
+
# 寫下「**禁**指路 `!`(與 Bash 同沙箱必再擋)或推 user 終端機」—— 然後 2026-09-19 又犯:
|
|
497
|
+
# `git branch -D` 被 classifier 擋下,沒走等價的 `git update-ref -d`(plumbing,只寫 .git 可寫區),
|
|
498
|
+
# 直接寫「你要清的話在終端機打 `! git branch -D ...` 就行」,而且是 user 反問才發現。
|
|
499
|
+
# M36(a) 有 Mechanism 8 把關,M36(b) 一直是**零機械強制** —— 所以它才會重犯。
|
|
500
|
+
#
|
|
501
|
+
# 判準:本 turn 有工具被擋(denied / EPERM / not permitted),且 reply 把一個**具體指令**交給
|
|
502
|
+
# user 去跑 → CRITICAL。真正的 human-only 邊界(login / MFA / OAuth / 帳單 / 缺 credential
|
|
503
|
+
# reference)明文豁免,那些本來就該交給 user。
|
|
504
|
+
if [ -n "$LAST_ASSISTANT" ]; then
|
|
505
|
+
BLOCKED_RE='(has been denied|Permission to use|Operation not permitted|EPERM|not accessible by personal access token|Blocked by classifier)'
|
|
506
|
+
HANDOFF_RE='(在終端機(打|輸入|跑|執行)|你要?(自己)?(跑|執行|打|輸入)|請你(跑|執行|手動)|麻煩你(跑|執行)|`![[:space:]])'
|
|
507
|
+
HUMAN_ONLY_RE='(auth login|gh auth|MFA|OAuth|二階段|登入|帳單|billing|付費|credential reference|vault|Secret Manager)'
|
|
508
|
+
if printf '%s' "${THIS_TURN_TOOLS:-}" | grep -qE "$BLOCKED_RE" \
|
|
509
|
+
&& printf '%s' "$LAST_ASSISTANT" | grep -qE "$HANDOFF_RE" \
|
|
510
|
+
&& ! printf '%s' "$LAST_ASSISTANT" | grep -qE "$HUMAN_ONLY_RE"; then
|
|
511
|
+
CRITICAL_SELF_LOCK_HANDOFF=1
|
|
512
|
+
WARNINGS="${WARNINGS}\n • M36(b) 自鎖後把動作推給 user:本 turn 有工具被擋,而你請 user 自己跑指令。"
|
|
513
|
+
fi
|
|
514
|
+
fi
|
|
515
|
+
|
|
492
516
|
# ── Mechanism 6: capability-bound PushNotification gap ────────────────────
|
|
493
517
|
# Provider-neutral runtime 不可假設 exact tool 存在。只有 adapter/registry 明確宣告
|
|
494
518
|
# `push-notification` capability 時才檢查;缺宣告 = UNOBSERVED/nonblocking。
|
|
@@ -733,6 +757,22 @@ if [ "${CRITICAL_CLAIM_VERIFY:-0}" = "1" ] && [ -n "$LAST_ASSISTANT" ]; then
|
|
|
733
757
|
fi
|
|
734
758
|
fi
|
|
735
759
|
|
|
760
|
+
# ── BLOCKER for Mechanism 10 自鎖後把動作推給 user(M36(b),2026-09-19)──
|
|
761
|
+
# 升級邏輯同 M1/M4:第一次 block 阻 turn,同 hash 降 warn 防 loop。
|
|
762
|
+
if [ "${CRITICAL_SELF_LOCK_HANDOFF:-0}" = "1" ] && [ -n "$LAST_ASSISTANT" ]; then
|
|
763
|
+
LOCK_HASH=$(printf '%s' "${LAST_ASSISTANT: -200}" | governance_hash_prefix)
|
|
764
|
+
LAST_BLOCKED_LOCK_FILE="$STATE_DIR/.last-blocked-self-lock.txt"
|
|
765
|
+
LAST_BLOCKED_LOCK=""
|
|
766
|
+
[ "$STATE_WRITES" = "1" ] && [ -f "$LAST_BLOCKED_LOCK_FILE" ] && LAST_BLOCKED_LOCK=$(cat "$LAST_BLOCKED_LOCK_FILE" 2>/dev/null || echo "")
|
|
767
|
+
if [ "$LOCK_HASH" != "$LAST_BLOCKED_LOCK" ]; then
|
|
768
|
+
[ "$STATE_WRITES" = "1" ] && { mkdir -p "$STATE_DIR" 2>/dev/null; echo "$LOCK_HASH" > "$LAST_BLOCKED_LOCK_FILE" 2>/dev/null || true; }
|
|
769
|
+
REASON=$(printf '%s' \
|
|
770
|
+
"🚨 SELF-LOCK HANDOFF BLOCKER(M36(b)):本 turn 有工具被擋,而你在 reply 裡請 user 自己跑指令。先答 M36(b') 三問 — (1) 憑證真的缺嗎?(2) 體檢測的是真實需要的能力嗎?(3) 有沒有已驗證可通的等價傳輸?尤其 git plumbing(update-ref -d / update-index --cacheinfo,只寫 .git 可寫區)、harness 檔案工具、index-authoritative generator。禁指路 '!':它與 Bash 同沙箱,同一道牆必再擋,等於把死路交給 user。真 human-only 只有 login/MFA/OAuth/帳單/缺 credential reference。做到了,或確認是真 human-only 邊界,才可結束 turn。" \
|
|
771
|
+
"本機制 = M36(b) 從純散文升為 mechanical BLOCKER(2026-09-19;規則 2026-08-08 就立、2026-08-28 已明文禁指路 '!',仍於 2026-09-19 再犯且是 user 反問才發現)。")
|
|
772
|
+
emit_governance_block "$REASON"
|
|
773
|
+
fi
|
|
774
|
+
fi
|
|
775
|
+
|
|
736
776
|
# ── BLOCKER for Mechanism 4 peer-design-no-approval(2026-05-09 user-authorized + escalated)──
|
|
737
777
|
if [ "${CRITICAL_PEER_DESIGN_NO_APPROVAL:-0}" = "1" ] && [ -n "$LAST_ASSISTANT" ]; then
|
|
738
778
|
DESIGN_HASH=$(printf '%s' "${LAST_ASSISTANT: -200}" | governance_hash_prefix)
|
|
@@ -486,5 +486,36 @@ T20A=$(peer_edit_transcript \
|
|
|
486
486
|
'const nextValue = normalizeValue(value)')
|
|
487
487
|
run_test "Test 20a: wrong-target engineering intent cannot authorize Button → block" 1 "$T20A"
|
|
488
488
|
|
|
489
|
+
# Tests 21-24: Mechanism 10 —— 自鎖後把動作推給 user(M36(b))。
|
|
490
|
+
# 對照組刻意四組:必紅一組、必綠三組。少了「必紅」那組,這支閘的綠燈就是零證據
|
|
491
|
+
# (2026-09-19 錨例:規則 2026-08-08 已立、2026-08-28 已明文禁指路 '!',仍再犯 —— 因為零機械強制)。
|
|
492
|
+
self_lock_transcript() {
|
|
493
|
+
# $1 = 工具結果文字(要不要含「被擋」);$2 = assistant reply 文字
|
|
494
|
+
printf '%s\n%s\n%s\n' \
|
|
495
|
+
'{"message":{"role":"user","content":[{"type":"text","text":"收尾"}]}}' \
|
|
496
|
+
"$(jq -nc --arg t "$1" '{message:{role:"user",content:[{type:"tool_result",content:$t}]}}')" \
|
|
497
|
+
"$(jq -nc --arg t "$2" '{message:{role:"assistant",content:[{type:"text",text:$t}]}}')"
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
T21=$(self_lock_transcript \
|
|
501
|
+
'Permission to use Bash with command git branch -D foo has been denied.' \
|
|
502
|
+
'純本機殘留,不影響任何東西,你要清的話在終端機打 ! git branch -D foo 就行。')
|
|
503
|
+
run_test "Test 21: 被擋後叫 user 自己跑指令 → block" 1 "$T21"
|
|
504
|
+
|
|
505
|
+
T22=$(self_lock_transcript \
|
|
506
|
+
'Permission to use Bash with command gh pr merge has been denied.' \
|
|
507
|
+
'這一步是真的 human-only:請你跑 gh auth login 完成登入,之後我會自動續跑。')
|
|
508
|
+
run_test "Test 22: 真 human-only(gh auth login)交辦 → no block" 0 "$T22"
|
|
509
|
+
|
|
510
|
+
T23=$(self_lock_transcript \
|
|
511
|
+
'ok' \
|
|
512
|
+
'你要跑的話在終端機打 npm run dev 就能看即時預覽。')
|
|
513
|
+
run_test "Test 23: 本 turn 沒被擋,只是一般教學 → no block" 0 "$T23"
|
|
514
|
+
|
|
515
|
+
T24=$(self_lock_transcript \
|
|
516
|
+
'Permission to use Bash with command git branch -D foo has been denied.' \
|
|
517
|
+
'改用 git plumbing 繞過該限制:已執行 git update-ref -d refs/heads/foo,分支已刪除。')
|
|
518
|
+
run_test "Test 24: 被擋後自己走等價傳輸、沒交辦 → no block" 0 "$T24"
|
|
519
|
+
|
|
489
520
|
echo "Results: $PASS PASS, $FAIL FAIL"
|
|
490
521
|
[ "$FAIL" -eq 0 ] || exit 1
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
| **DataTable scroll**(3-region synced + cross-OS scrollbar)| `data-table.spec.md:245-251` | 「center body 用 native overflow-x-auto(非 ScrollArea),pinned column 三 region 同步」 | ✅ aligned(v15.13 interim CSS parity for Windows Bug H,deferred ScrollArea full migration)| 2026-04-30 / 2026-05-07 |
|
|
23
23
|
| **Cell view vs edit content position**(picker offset)| `field.spec.md`「Layout Family 4」段 + `data-table.spec.md`「Body cell internal」row | 「Field control view + edit 共用同 wrapper geometry(Model A:view×default = edit 幾何減 chrome,保留 px/py)」 | ✅ aligned:DataTable cell 場景走 Field naked(`showDisplayEndIcon` opt-in);standalone `mode="view"` 走 view×default = edit 幾何減 chrome(2026-07-16 Model A,原 bare-span deferral 已兌現;值內容層 4-mode 共享 renderer,見 select.tsx 檔頭 `@renderer-symmetry-allow`)| 2026-05-07~08 round 1-5 → 2026-07-16 Model A resolved |
|
|
24
24
|
| **Field state machine focus dominates**(v13.3)| `field-wrapper.tsx` cva compoundVariants + `field.spec.md`「focus dominates」段 + `check_field_family_invariants.sh` A.3 | 「focus dominates everything;naked 不寫平行 outline ring」 | ✅ aligned;hook A.3 enforce | 2026-05-06 v9-v13.3 5 round refinement |
|
|
25
|
-
| **Protected solo workflow**(1 task = 1 branch + 1 PR / required CI / standing engineering delegation)| `infra/governance/release-workflow.json` + `feedback_solo_dev_workflow.md` + AGENTS.md
|
|
25
|
+
| **Protected solo workflow**(1 task = 1 branch + 1 PR / required CI / standing engineering delegation)| `infra/governance/release-workflow.json` + `feedback_solo_dev_workflow.md` + AGENTS.md「# Git / release canonical」 | protected main 禁 direct push;PR 建立正常;required CI/conversations resolved **且 user 對當前 PR head 說過「發版」**後,由 `release:auto` 走 `pr-checks → merge → publish → readback → consumer`(缺同意收據會停在 `AWAITING_USER_RELEASE_CONSENT`;規則 SSOT = AGENTS.md「# Git / release canonical」1.5);preview/canary/attestation 非 blocking | ✅ aligned(M28 + hooks + GitHub ruleset)| 2026-07-20 migration / 2026-08-02 five-step reconciliation |
|
|
26
26
|
| **Wrapper-vs-primitive schema unify**(SelectOption / MenuItemProps 等)| `SelectMenu/select-menu.tsx` SelectMenuOption + `Combobox/combobox.tsx` extends + `PeoplePicker/people-picker.tsx` extends | 「wrapper API schema 必 `extends primitive`,wrapper 內部 mapping 必 forward 全 primitive surface field(M30)」 | ✅ aligned(2026-05-10 Round 1 commit `561945b` Combobox extends + forward 全 field;PeoplePicker 對齊 + hook `check_wrapper_primitive_schema_drift.sh` BLOCKER)| 2026-05-10 M30 codify |
|
|
27
27
|
| **空值顯示符號 + 分流**(empty value display)| `field-context.ts` `useFieldEmptyDisplay()`(分流 hook)+ `field-wrapper.tsx` `EMPTY_DISPLAY = '-'`(半形常數)+ `field-controls.spec.md`「null / undefined 值」段 | 「符號 = 半形 hyphen `-`(U+002D),**非**全形 em dash;`surface==='table-cell' && isEditable ? '' : '-'`;可編輯 form edit 走 native placeholder。全 Field family 空值渲染必經 hook,consumer 禁直引 `EMPTY_DISPLAY`」 | ✅ aligned(2026-07-08 user 拍板半形;hook `check_field_family_invariants.sh` A.6 BLOCKER — 禁非 owner 直引常數 + 禁 hardcode 全形 em dash;DescriptionList / ProfileCard / FileViewer consumer 同步半形)| 2026-07-08 user directive |
|
|
28
28
|
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
|
|
36
36
|
## 與 hook 關係
|
|
37
37
|
|
|
38
|
-
- `check_ds_anchor_preflight.sh`(live,P0 BLOCKER exit 2,2026-07-07 升級)— 偵測 production tsx / apps consumer 內 DS primitive wrap 而無 canonical read trace → block;bypass `
|
|
38
|
+
- `check_ds_anchor_preflight.sh`(live,P0 BLOCKER exit 2,2026-07-07 升級)— 偵測 production tsx / apps consumer 內 DS primitive wrap 而無 canonical read trace → block;bypass `GOVERNANCE_BYPASS_DS_ANCHOR=1`(audit-logged)
|
|
39
39
|
- `check_field_family_invariants.sh` A.3 — 機械化 enforce v13.3 SSOT
|
|
40
40
|
- `check_pattern_invariants.sh` C.4 — 機械化 enforce row slot SSOT
|
|
41
41
|
- `check_canonical_propagation.sh` E.2 — 機械化 enforce L3 primitive import
|
|
@@ -27,7 +27,7 @@ paths:
|
|
|
27
27
|
|
|
28
28
|
| | Canonical/provider bootstrap | Canonical rules |
|
|
29
29
|
|---|---|---|
|
|
30
|
-
| 載入時機 | 每 session start 載入(
|
|
30
|
+
| 載入時機 | 每 session start 載入(行數預算見 `AGENTS.md`「行數預算」,本檔不硬寫數值)| 支援 path scope 的 adapter 於編對應 file 時載入;其他 provider 按 index 讀取 |
|
|
31
31
|
| 內容 | 6 mindset + 治理 / 稽核 / SSOT canonical / 任務導航 / fundamental rules | 該 file family 細節規則(spec 撰寫 / UI 開發 / story / self-verify)|
|
|
32
32
|
|
|
33
33
|
## 新增 rule 流程
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
| **M7** | **新 protocol / skill / rule 寫完,必反向 cross-check 既有 Meta-Principle 是否該套用**。尤其:consistency-class 的 protocol 必走「一致性類稽核必先全掃再判」(本章節);audit skill 必加「Self-improvement capture」Phase F step;Rule 觸及「canonical」「SSOT」「rationale」keyword → 必明示 substantive vs 表達層分權。**+ Sub-rule M34 (folded 2026-05-22)**:Hook detection regex 必對齊 spec wording 廣度。Triple test before merge new hook — (1) Spec rule wording 是「一般類別」(broad)還是「特定 keyword」(narrow)?(2) Hook regex 匹配「一般類別」還是只「特定 keyword subset」?(3) Spec broad + Hook narrow → **gap**,必擴 hook regex(unicode pattern / negative-list approach / counter-example DS-wide scan)。Propose 新 hook 必先寫 3-column table:`Spec wording` / `Hook regex` / `Gap?(broad-vs-narrow)`。 | principle-audit-protocol v1 漏套既有 Phase 0 全掃;2026-05-17 story name `照妳` `決策N` 等中英人話 detection regex 漏 |
|
|
48
48
|
| **M8** | **訂立 / 修改 cross-component canonical 前必 world-class benchmark**。任何 predicate / decision tree / taxonomy 類 spec,**必先 grep 至少 3 家世界級 DS**(Polaris / Material / Atlassian / Ant Design / Carbon / Apple HIG / VS Code / Figma 等)列對照表,再訂 rule。**絕對禁止**憑直覺開場寫 predicate,user 問才補對照。每個 category / variant / case 必附世界級 reference(實作名或 API 指向);沒有對照的 rule 視同未成熟,工程 rule 先擴大 research,task/deliverable 明確要求 peer 時才因缺 review 標 `REVIEW-BLOCKED`;只有採用 OG-only 產品/UI/UX原則是真人決策。 | item-anatomy Inline Action 疊代 4 次才有世界級對照 |
|
|
49
49
|
| **M9** | **Predicate / decision tree 寫完,present 前必 4 題自測**,防 membership drift。(a) 每 example 回跑決策樹;(b) cap / constraint cross-check;(c) 每 example 對 ≥3 家 world-class DS;(d) 每 category ≥1 example。任一題失敗 → 重收斂,**禁止 present**。 | Cat 1 IA 塞 endAction / FileItem sm 違 ≤24 cap |
|
|
50
|
-
| **M10** | **Proactive exhaustive scan**:canonical migration 完成前禁止只改「直覺相關」元件;final report 前禁止省略「我知道但沒講」的 tech debt。**Mechanical 落地**:batch-end(session ≥2 fix commits)跑一次 `/scan-similar-bugs` grep DS-wide(2026-05-12 per-fix → batch-end 重構);`session_start_governance_check.sh` Check 6 偵測 24h 內 fix commit 漏跑 → soft 提醒。**+ Sub-rule 掃描不得截斷(2026-08-07)**:盤「誰在讀這個值 / 誰呼叫這個函式」時,grep 一律**禁接 `head -N` / `-m`** —— 截斷是假陰性,會把還有人讀的東西誤判成死狀態而刪掉。要收斂輸出改用 `-l`(只列檔)或 `\| wc -l` 先看總數,確認總數後再全看。同理 `cmd \| head` 的 exit code 屬於 head,`&&` 分支會報出相反結論,判斷成敗必看 `PIPESTATUS`。**刪除/歸零任何欄位前,必須先把 reader 清單列完整**,只查到一個入口就下結論 = 違反。**+ Sub-rule 自檢不得只做單句層級(2026-08-09)**:任何「全盤檢查/稽核/確認沒問題」都必須跑**兩個維度**——(1) **逐句驗證據**(cite/數字/引文逐字);(2) **全體結論兩兩相容性**——把文件所有結論列成清單,逐對問「這兩條能同時為真嗎」。只做 (1) = **假檢查**,因為矛盾在單句層級恆為隱形(每句單看都成立)。且**任何一旦成立的結論必須當場登記進該文件的結論總帳**——沒登記的結論會在後續回合被重新當成未決題辯論。 | dismiss migration 漏 FileViewer / 7 題 silent tech debt user 一次炸;2026-08-07 grep 接 head 截斷 = 假陰性鎖死升級路線;2026-08-09 結論未入總帳 → 兩輪無謂辯論(詳 historical-bugs.md) |
|
|
50
|
+
| **M10** | **Proactive exhaustive scan**:canonical migration 完成前禁止只改「直覺相關」元件;final report 前禁止省略「我知道但沒講」的 tech debt。**Mechanical 落地**:batch-end(session ≥2 fix commits)跑一次 `/scan-similar-bugs` grep DS-wide(2026-05-12 per-fix → batch-end 重構);`session_start_governance_check.sh` Check 6 偵測 24h 內 fix commit 漏跑 → soft 提醒。**+ Sub-rule 掃描不得截斷(2026-08-07)**:盤「誰在讀這個值 / 誰呼叫這個函式」時,grep 一律**禁接 `head -N` / `-m`** —— 截斷是假陰性,會把還有人讀的東西誤判成死狀態而刪掉。要收斂輸出改用 `-l`(只列檔)或 `\| wc -l` 先看總數,確認總數後再全看。同理 `cmd \| head` 的 exit code 屬於 head,`&&` 分支會報出相反結論,判斷成敗必看 `PIPESTATUS`。**刪除/歸零任何欄位前,必須先把 reader 清單列完整**,只查到一個入口就下結論 = 違反。**+ Sub-rule 自檢不得只做單句層級(2026-08-09)**:任何「全盤檢查/稽核/確認沒問題」都必須跑**兩個維度**——(1) **逐句驗證據**(cite/數字/引文逐字);(2) **全體結論兩兩相容性**——把文件所有結論列成清單,逐對問「這兩條能同時為真嗎」。只做 (1) = **假檢查**,因為矛盾在單句層級恆為隱形(每句單看都成立)。且**任何一旦成立的結論必須當場登記進該文件的結論總帳**——沒登記的結論會在後續回合被重新當成未決題辯論。 | dismiss migration 漏 FileViewer / 7 題 silent tech debt user 一次炸;2026-08-07 grep 接 head 截斷 = 假陰性鎖死升級路線;2026-08-09 結論未入總帳 → 兩輪無謂辯論(詳 historical-bugs.md);**2026-09-18/19 只修同族一條還明文寫「另一條不變」→ 隔天被同一種方式咬**:CI 比值閘的「長工單一最長」因尾端噪音誤紅,改成比合計時同一段註解寫下「frameGap 那條不變」,隔天 frameGap 就在**零個執行期檔案改動**的 commit 上誤紅(同一份建置量到 215 與 311,尾端散布 ±45% > 門檻 ±25%)。修任一條有同族的缺陷時,**必當場列出同族全部呼叫點逐一判定**,不得只改觸發的那一個;「另一條不變」這種話出現在 commit 裡就是沒掃完的訊號 |
|
|
51
51
|
| **M11** | **User-perspective interactive state walk**:改完 UI 後 present 前必親自走 7 題 self-test(static / hover / focus-visible / active / keyboard / 範例真實性 / CSS 對稱)。每 UI 改動未跑 7 題就 commit = 違反。 | ListBody 修完 user 連抓 5 波 |
|
|
52
52
|
| **M12** | **Binary strict rule + fix-time root-invariant pre-question(「必 X」/「禁 Y」/ bug fix)前必 benchmark + invariant test**。3 題自測:≥3 家世界級一致 / counter-example scan / root invariant vs surface observation。震盪症狀(A→not A→A)= meta invariant 沒抓到,**停下 present**。**Fix-time 延伸**(2026-05-12 absorbs 原 M32(b)):visual / behavior bug 改前必 inline 答 3 題 — Q1「root invariant 是什麼?」Q2「symptom 在哪 layer?」Q3「fix 是 root-layer 還是 surface-layer?」。surface-only 修(改 symptom 不動 invariant)違反。對齊 5-whys / Toyota TPS / Linux kernel「fix the root not symptom」canonical。 | hover bg 震盪 4 次(bg-edge vs content-padding invariant);2026-05-12 cell-align fix surface-modify Field padding 不動 inline-flex `!h-full` root invariant |
|
|
53
53
|
| **M13** | **User 第 2 次提相關問題 → 自動截圖 verify**。當 user 就同一視覺 / 行為主題第 2 次問 / 糾正,AI 必 invoke 截圖 verify(`visual-audit.mjs --scope=component:XXX`)。第 3 次才 verify = 違反。**Corollary**:user 指定「所有 X 都要 Y」,同 session 全部做完 + 建 hook 防線。 | avatar-NameCard migration 分批拖延 user 第 2 次催才全改 |
|
|
@@ -64,12 +64,12 @@
|
|
|
64
64
|
| **M24** | **State 顯著性 precedence:disabled > muted > emphasis**。元件在 disabled state 時,內部所有文字載體(label / value / placeholder / icon)統一切 disabled token(`text-fg-disabled` neutral-6),**不**繼續 muted token(`text-fg-muted` neutral-7)。muted 是裝飾,disabled 是語意 state — state 勝 emphasis。同理:error / warning state 內 placeholder 應對應 state token。Hook `check_field_family_invariants.sh` A.4(原 check_disabled_placeholder_color folded,live P1 stderr)。 | 2026-05-04 5+ violation:`bareInputStyles` 永遠 muted / `select.tsx` 3 處 plain&tag empty 不分 mode / `textarea.tsx` 同;Input.tsx 唯一做對。User 5+ 次糾正才 codify |
|
|
65
65
|
| **M25** | **Layered chain invariant 必整鏈 forward(viewport-aware overlay scroll 範例)**。Overlay surface(Popover / HoverCard / Dialog / Sheet)的 viewport-aware scroll 機制要求 root → SurfaceBody 之間**所有中間 wrapper 都 forward `flex flex-col h-full`**;任何中間 div 沒 forward → SurfaceBody flex-1 失效 → body 不 scroll。同類 chain pattern:density 透傳 / fieldCtx 鏈 / theme subtree(M3 portal 逃逸對偶)。Hook `check_pattern_invariants.sh` C.1(原 check_overlay_panel_scroll_chain folded,live P1 WARN)。 | 2026-05-04:Filter / Sort panel root `<div w-[640px]>` 無 flex-col → user 縮 viewport 時 body 不 scroll。NameCard 因自設 max-h flex-col 才繞過(無中間 wrapper) |
|
|
66
66
|
| **M26** | **Behavior / visual canonical decision 前必跑 WebFetch + WebSearch 取 ≥ 3 source,不可憑印象 propose**。M22 升級版 — M22 管「寫 spec / tsx 含 claim 必附 cite」(實作後),M26 管「propose / 決策前必先 fetch」(實作前)。Pipeline:(1) WebFetch 3 家世界級 source(Atlassian / Material / Polaris / Ant / Carbon / Apple HIG / shadcn / Radix);(2) 全 403 → WebSearch fallback 用 snippet,**明示「search-only confidence」**;(3) WebFetch + WebSearch 都失敗 → STOP propose,告知 user「無法 verify,要看 screenshot/實機」。Hook `check_propose_without_benchmark.sh`(2026-05-26 backfill — 前期 doc claim 但 file missing;UserPromptSubmit 偵測 user prompt 含 propose / visual / behavior decision keyword + 近 20 turn WebFetch/WebSearch < 2 → soft inject 提醒)。 | 2026-05-05:user 反覆糾「為什麼每次都沒 webfetch 只憑印象」— Jira drag handle / cell display 兩題我憑印象 propose 多輪;升級成硬規範 |
|
|
67
|
-
| **M28** | **Git ops 必先 grep canonical**(mindset #2 AI-ops 子規則)。AI 執行 branch/PR/merge/release ops 前必讀 `governance/memory/feedback_solo_dev_workflow.md` + `AGENTS.md # Git / release canonical`。鐵律:1 task = 1 working branch + 1 PR;protected main 禁 direct push;PR 建立是正常路徑;standard release 只走 `infra/governance/release-workflow.json`
|
|
68
|
-
| **M29** | **DS Anchor Preflight invariant — 視覺 / 結構 propose 前必 grep `*.spec.md` 找 owner SSOT**。動 component / pattern / token 視覺結構前,**必先**:(a) `rg <touched component> + <behavior keyword>` in `packages/design-system/src/**/*.spec.md` (b) 出 **3-column owner table**(`candidate owner spec` / `canonical sentence/table row` / `conflicting code/comment if any`)(c) 若 spec ≠ code → STOP 寫 RFC 不直接 propose implementation。沒 3-column = anti-pattern,提案不被接受。SSOT index `packages/design-system/ds-canonical/references/ssot-index.md`(high-risk interface owner mapping)Step 0.1 先查再 grep verify。Hook `check_ds_anchor_preflight.sh` P0 BLOCKER(exit 2,2026-07-07 升級;bypass
|
|
67
|
+
| **M28** | **Git ops 必先 grep canonical**(mindset #2 AI-ops 子規則)。AI 執行 branch/PR/merge/release ops 前必讀 `governance/memory/feedback_solo_dev_workflow.md` + `AGENTS.md # Git / release canonical`。鐵律:1 task = 1 working branch + 1 PR;protected main 禁 direct push;PR 建立是正常路徑;standard release 只走 `infra/governance/release-workflow.json` 的五步,由 `release:auto` 續跑 —— **但合併前有一道發版同意閘**(user 對當前 PR head 說「發版」才落 receipt;缺則停在 `AWAITING_USER_RELEASE_CONSENT`)。五步與同意閘的散文 SSOT = `AGENTS.md`「# Git / release canonical」,本條不重述。Hook `check_solo_workflow.sh` 只機械攔 R1 第二 branch / R2 direct-main push;retired tag-preflight、preview/canary、attestation、soak、fleet ceremony 不得回到 blocking graph。Break-glass override audit-logged,仍不能繞 GitHub ruleset。 | 2026-05-08 的 branch/PR sprawl促成機械化;2026-07-20 保留一任務一分支與 protected PR;2026-07-26 淘汰 chat-keyword gate,2026-08-02 再移除 retired local tag-preflight branch。 |
|
|
68
|
+
| **M29** | **DS Anchor Preflight invariant — 視覺 / 結構 propose 前必 grep `*.spec.md` 找 owner SSOT**。動 component / pattern / token 視覺結構前,**必先**:(a) `rg <touched component> + <behavior keyword>` in `packages/design-system/src/**/*.spec.md` (b) 出 **3-column owner table**(`candidate owner spec` / `canonical sentence/table row` / `conflicting code/comment if any`)(c) 若 spec ≠ code → STOP 寫 RFC 不直接 propose implementation。沒 3-column = anti-pattern,提案不被接受。SSOT index `packages/design-system/ds-canonical/references/ssot-index.md`(high-risk interface owner mapping)Step 0.1 先查再 grep verify。Hook `check_ds_anchor_preflight.sh` P0 BLOCKER(exit 2,2026-07-07 升級;bypass GOVERNANCE_BYPASS_DS_ANCHOR=1 audit-logged)。codex-collab/SKILL.md Step 0.1 mandatory pre-grep。對齊 Carbon「design spec is source of truth」+ Atlassian「system-wide coordination first」。 | 2026-05-08:Claude+Codex 連 5 round 跳過 spec anchor pre-grep,直覺加 primitive(D path / Path A);user 抓「為何沒研讀設計原則」 |
|
|
69
69
|
| **M30** | **Wrapper-vs-primitive schema unify invariant — wrapper API option / config schema 必 `extends` primitive SSOT,且 wrapper 內部轉換 mapping 必 forward 全 primitive surface field**。Primitive(`SelectMenuOption` / `MenuItemProps` / `ItemAnatomyProps` 等)宣告 SSOT 後,所有 wrapper(`Select.SelectOption` / `Combobox.SelectOption` / `PeoplePicker` 等)若有同類 schema,(a) 用 TypeScript `extends primitive` 機械繼承,**不可平行 declare** weak schema(同名 / 部分欄位)(b) wrapper 內部 `xxxToYyy()` mapping 必 forward 全 primitive surface field(不可只 forward `value` + `label`,silently drop avatar / description / disabled 等)(c) wrapper-only field 加在 `extends body`(不污染 primitive)。Hook `check_wrapper_primitive_schema_drift.sh` 機械強制(P0 BLOCKER):grep `export interface .*Option \{` cross components/,同名 ≥ 2 處且未 `extends` primitive → BLOCK。對齊 Polaris ChoiceList / Material Autocomplete / Carbon Dropdown wrapper-vs-primitive schema-extension idiom。 | 2026-05-10 PeoplePicker weak schema 漏 avatar,mapping 只 forward 2 fields(詳 historical-bugs.md) |
|
|
70
70
|
| **M32** | **Audit script 必 pixel-quantified verify ≠ attribute existence**(2026-05-12)。Audit 必驗 `rect.top / .left / .height` numeric pixel,**不**可只驗 `getAttribute('data-state')` / `class.includes(...)` = false-positive trap(DOM-pass ≠ visual-pass)。對齊 Material X-DataGrid / AG Grid playwright pixel snapshot / Polaris visual diff canonical。Hook `check_pixel_quantified_audit.sh` 機械強制(scans `getAttribute(` without paired `getBoundingClientRect(`)。**+ Sub-invariant(2026-05-30,doc-claim layer)**:story/anatomy/a11y/spec 的**文字宣稱 ≠ code 真實**——audit 必 adversarial **讀元件 .tsx +(其 wrap 的 Radix/cmdk/react-day-picker 等 lib)source**,逐句驗 keyboard map/ARIA role/focus 行為/prop 存在性/native-vs-custom;prose-skim「looks fine」= false-pass(prose-pass ≠ source-verified)。**「無 code 改動」≠ 可跳過**。`/deep-audit-cross-codex` A.1b + CP-A1b + report-validator F 機械強制。Anchor:2026-05-30 獨立 adversarial 抓 403 findings/64 單元/202 FALSE_CLAIM(前期 prose-skim story dim 全漏);prop-existence deterministic detector 因 passthrough over-flag 證實此類本質需 LLM 讀 source → 用 mandatory-dim + report-validator 保證真跑。**+ Sub-invariant「儀器要先有對照組」(2026-09-08 folded,absorbs 4 bugs)**:任何量測/閘在拿它的**綠燈**當證據前,必須先證明它**在該紅的時候會紅** —— 把被驗的東西弄壞一次,看它是否指名該處,再還原。缺這一步的綠燈是零證據。四個錨(同一天):(a) 22 支瀏覽器腳本因缺沙箱參數永遠回 `SKIPPED-ENV`,332 條不變式從沒真的跑過;(b) 自己寫的 C5/G2 測試假綠(抓到鎖定欄、用滿捲位置),補對照組才發現;(c) `transition-colors` 的 transition-property 含 `outline-color`,聚焦後立刻量抓到過渡中間值,差點寫成「全 DS 焦點框失效」;(d) 本輪新建的 7 組閘 `grep -rl <name> .github` 回 **0** —— 從沒被 CI 呼叫,跟 ci.yml 註解裡記過兩次的 distribute-column-widths / pagination 同一種病;(e) Playwright headless 預設帶 `--hide-scrollbars`,任何量捲軸厚度的探針永遠得 0 —— Bug H(Windows 捲軸溢出)五個月沒人量到就是這樣,要 `ignoreDefaultArgs` 拿掉再用根規則 `::-webkit-scrollbar{width:17px}` 造 Windows 幾何(2026-09-08);(f) 同頁多個目標逐一 `scrollIntoView` 後,先前算好的視窗座標拿去截圖是**全白** —— 截圖前一刻才對該元素重算(同日)。(g) **headless Chromium 的 rAF 被釘在 16.7ms**,連故意把每列加上 box-shadow+blur 都不動,CPU 降速 6 倍也不動 —— 任何「每幀時間」類量具在 headless 一律無效,綠燈是零證據;同一天建了兩版都被自我檢查判死(2026-09-15)。要量幀時間只能在真瀏覽器(而且分頁必須是可見的,背景分頁 rAF 不跑);headless 改用 CDP `Tracing`(看光柵/主執行緒任務)或 `LayerTree.profileSnapshot`(光柵成本),那兩個會紅。(h) **跨網域/跨主機的效能比較無效;先歸因再消融**(2026-09-15,兩天教訓):user 機器「main 17ms / 分支 33ms」追了兩天、DS 內部消融七項全 33、本機永遠重現不出 —— 最後用 Long Animation Frames API 在他機器上看長幀 `scripts[].sourceURL`,5301ms 全在 `thin-client-min.js`(那台機器的網路/安全產品對 `*.netlify.app` 注入的遠端隔離 thin client),而 main 在 github.io 是白名單本地渲染、main 放到 Netlify 一樣慢。**唯一變數是網域**。機械問法:(1) A/B 兩頁同網域嗎?(2) 長幀裡的 script 來源是我們建置裡的檔嗎?兩題沒答完前,任何消融都是白做(`governance/memory/reference_perf_validation_same_host.md`)。**機械化問法**:寫完任一閘立刻問三題 —— 「弄壞它會紅嗎(跑一次)」「誰呼叫它(grep CI)」「它的量測值會不會受動畫/時序影響(等穩態再量)」。**Split note**:原 mega 8 sub-invariants 拆 4 home — (b) fix-time root-invariant → M12 extension / (c)(d)(h) batch + parallel + claim-verify → `/bug-fix-rhythm` skill / (e) tool preflight(CLI binary 必跑 4-test discovery:which / npx / package.json / auth.json,禁短路「not installed」假警報)— 原 memory `feedback_tool_binary_preflight_sweep.md` 已 retire,invariant 一行收容於此 / (f) ship gate → AGENTS.md `# 稽核 canonical` / (g) Layer marker → M31 + hook。git blame 2026-05-12 commit 留 audit trail。 | 2026-05-12 4 audit-vs-visual gap absorbed:cell-align / divider 2px / breadcrumb tooltip / row-alignment 全 audit「Δ=0 ALL PASS」但 user 抓視覺壞(詳 historical-bugs.md)。|
|
|
71
71
|
| **M31** | **Provider-adaptive adversarial two-track 5-step canonical**。**Activation boundary**:只有 task/deliverable 明確要求 independent review 時才啟動;exact run waiver 必記 receipt,optional review 缺席不得阻擋一般工程、deep audit 或 standard release。執行 authority = `packages/design-system/ds-canonical/skills/independent-review/SKILL.md` / `deep-audit-cross-codex/SKILL.md` + `packages/governance/canonical/providers.json` + `packages/governance/src/provider-review-binding.mjs#resolveProviderReviewBinding`;skill 舊名稱只保留 discovery compatibility,不指定 reviewer。**Step 0 入口 gate**:啟動後 primary author 先完成獨立 Phase A,再由 resolver 選出不同 provider、不同 context、read-only、已認證的 peer;無可用 peer/transport/certification/evidence = required-review claim `REVIEW-BLOCKED`,禁止同模型或同 context 假扮 second opinion。**Step 1-5**(啟動後每題必過):(1)兩方各自在隔離 context 熟讀相同完整 inventory/spec/canonical/source,NO-SAMPLE;(2)兩方各自驗證 deterministic gates + applicable runtime/visual evidence,不可只一方 verify;(3)需要視覺判斷時兩方各自執行 target-bound browser/DOM/pixel 稽核;(4)每個 finding 都附 owner path/精確證據/reasoning,未證實即撤回;(5)closed synthesis 比較兩份 content-addressed artifact,agree 時補齊缺漏,disagree 時以 canonical evidence 較強者勝,保留 unresolved,不得 pass-through。legacy-named compatibility hook `check_codex_collab_5step.sh` 只檢查流程證據,不是 provider 或 transport authority。對齊 RFC 學術同儕審查 / Linux kernel-mailinglist cite source / Google ML eng-design-review。 | 2026-05-10 的 pass-through 事故證明「拿 peer 結論代替 primary 驗證」會造成 drift;後續 cite battle 以 `field-controls.spec.md` + `item-anatomy.spec.md` 找回雙 owner,現已抽象成 provider-neutral second-opinion invariant。 |
|
|
72
|
-
| **M36** | **禁把自己的推論寫成 user 的決定;禁用自家治理設定把自己鎖死**(2026-08-08 user directive)。**(a) Provenance 不得升格**:任何文件/回覆標「user 拍板」「你最早的決定」「已拍板」,**必須當場引得出 user 的原話**;引不出來一律標「AI 推導」或「AI 建議、user 採納」。**引文一律逐字,禁刪語氣詞**——把「…才對吧?」寫成「…才對」= 竄改證據,比誤判嚴重(2026-08-09 錨例);user 說「還在思考」之後仍當已決,同罪。**問句 ≠ 同意**——user 問「這是 X 的情況?」是要澄清,不是核准。**推論 ≠ 既有**——把某個既有值(如 Dialog 的 maxWidth 檔位)當成保證,再據以生出新規則,是同一類病。**規範的「理由」也要引得出原文(2026-09-18 加)**——說「X 存在的唯一理由是 Y」「X 是為了 Y」前,必須先讀到 X 自己寫的理由並逐字引;**現象上成立 ≠ 那就是它的理由**。錯例:Combobox 的 tagPadding,規格白紙黑字寫「確保 tag 四邊等距」,我看到 sm/md 剛好讓 Tag 內文字落在 `--field-px` 那條線上,就寫成「它存在的唯一理由就是把字推回那條線」,並據此把 lg(四邊等距算出 5、字落 15px)判成「兩條規範打架」丟給 user 拍板 —— **那是我自己造出來的假議題**,而且該規則三天前才立、commit message 就寫著理由。危險之處在於:編造的理由會**同時**寫進 code 註解、spec、閘的檔頭與 commit,下一個人只讀得到我的版本。機械問法:(1)這個「理由」我是**讀到的**還是**看出來的**?(2)引得出檔名行號嗎?(3)我要據此宣告「衝突 / 例外 / 該改」之前,原文有沒有說過那句話?第 (3) 題沒過就不准把它升級成待決事項 —— 假議題比沉默更貴。發現升格 → 立即撤回並留檔於該文件的「來源總帳」。**(b) 禁自鎖(anti-self-lock)**:遇到「被我們自己的 policy/schema/sandbox 設定擋住授權內動作」時,**先解自己的鎖**(改 canonical owner → 走正常 PR),或改用不需放寬安全邊界的既有機制;**禁止**因自家設定擋路就把動作丟回 user、或宣稱做不到。真正的 human-only boundary 只有 login/MFA/OAuth、缺 credential reference、plan 外付費、法律/帳號權限——**自家治理設定不在此列**。**(b') 判自鎖的機械三問(2026-08-11 codify,任何模型任何環境)**:宣稱「被擋、需要 user」前必逐題答:(1)**憑證真的缺嗎?**——canonical credential reference(`~/.config/qijenchen-governance/github-token`)存在且可用 ≠ 缺憑證;工具讀錯存放處(gh 存的 OAuth 過期但 token 檔可用)= 工程 bug 自修,禁報 login 邊界。(2)**體檢測的是真實需要的能力嗎?**——帳號級 preflight(`gh auth status`)擋 repo-scoped token = 體檢錯位,改 target-bound 驗證(能讀目標 repo 即可)。(3)**這條傳輸不通,有沒有已驗證可通的等價傳輸?**——gh 的 Go TLS 不信 sandbox 代理憑證但 curl 可通 → 換傳輸(shim),不是邊界。(4)**鎖的主人是誰?(2026-08-28 加)**——自家 policy/schema/settings 有列 → 解自己的鎖走正常 PR;**自家清單沒列卻仍 EPERM = 平台內建安全邊界**(如 Claude Code 對 hook 設定目錄的防注入保護、沙箱不可關),不試圖解鎖,改找**不碰被鎖路徑**的等價機制(git plumbing 只寫 `.git` 可寫區、harness 檔案工具、index-authoritative generator);**任何要交 user 執行的指令,必先驗證它不在同一道牆內**——對話內 `!` 與 Bash 同沙箱,指同牆死路 = 把動作丟回 user 的變形,同罪。**復原/交易類機制必有顯式逃生口 + skip-identical**(未變目標不准碰;全有全無原子性造成的死鎖 = 過度設計,2026-08-11 拆除)
|
|
72
|
+
| **M36** | **禁把自己的推論寫成 user 的決定;禁用自家治理設定把自己鎖死**(2026-08-08 user directive)。**(a) Provenance 不得升格**:任何文件/回覆標「user 拍板」「你最早的決定」「已拍板」,**必須當場引得出 user 的原話**;引不出來一律標「AI 推導」或「AI 建議、user 採納」。**引文一律逐字,禁刪語氣詞**——把「…才對吧?」寫成「…才對」= 竄改證據,比誤判嚴重(2026-08-09 錨例);user 說「還在思考」之後仍當已決,同罪。**問句 ≠ 同意**——user 問「這是 X 的情況?」是要澄清,不是核准。**推論 ≠ 既有**——把某個既有值(如 Dialog 的 maxWidth 檔位)當成保證,再據以生出新規則,是同一類病。**規範的「理由」也要引得出原文(2026-09-18 加)**——說「X 存在的唯一理由是 Y」「X 是為了 Y」前,必須先讀到 X 自己寫的理由並逐字引;**現象上成立 ≠ 那就是它的理由**。錯例:Combobox 的 tagPadding,規格白紙黑字寫「確保 tag 四邊等距」,我看到 sm/md 剛好讓 Tag 內文字落在 `--field-px` 那條線上,就寫成「它存在的唯一理由就是把字推回那條線」,並據此把 lg(四邊等距算出 5、字落 15px)判成「兩條規範打架」丟給 user 拍板 —— **那是我自己造出來的假議題**,而且該規則三天前才立、commit message 就寫著理由。危險之處在於:編造的理由會**同時**寫進 code 註解、spec、閘的檔頭與 commit,下一個人只讀得到我的版本。機械問法:(1)這個「理由」我是**讀到的**還是**看出來的**?(2)引得出檔名行號嗎?(3)我要據此宣告「衝突 / 例外 / 該改」之前,原文有沒有說過那句話?第 (3) 題沒過就不准把它升級成待決事項 —— 假議題比沉默更貴。發現升格 → 立即撤回並留檔於該文件的「來源總帳」。**(b) 禁自鎖(anti-self-lock)**:遇到「被我們自己的 policy/schema/sandbox 設定擋住授權內動作」時,**先解自己的鎖**(改 canonical owner → 走正常 PR),或改用不需放寬安全邊界的既有機制;**禁止**因自家設定擋路就把動作丟回 user、或宣稱做不到。真正的 human-only boundary 只有 login/MFA/OAuth、缺 credential reference、plan 外付費、法律/帳號權限——**自家治理設定不在此列**。**(b') 判自鎖的機械三問(2026-08-11 codify,任何模型任何環境)**:宣稱「被擋、需要 user」前必逐題答:(1)**憑證真的缺嗎?**——canonical credential reference(`~/.config/qijenchen-governance/github-token`)存在且可用 ≠ 缺憑證;工具讀錯存放處(gh 存的 OAuth 過期但 token 檔可用)= 工程 bug 自修,禁報 login 邊界。(2)**體檢測的是真實需要的能力嗎?**——帳號級 preflight(`gh auth status`)擋 repo-scoped token = 體檢錯位,改 target-bound 驗證(能讀目標 repo 即可)。(3)**這條傳輸不通,有沒有已驗證可通的等價傳輸?**——gh 的 Go TLS 不信 sandbox 代理憑證但 curl 可通 → 換傳輸(shim),不是邊界。(4)**鎖的主人是誰?(2026-08-28 加)**——自家 policy/schema/settings 有列 → 解自己的鎖走正常 PR;**自家清單沒列卻仍 EPERM = 平台內建安全邊界**(如 Claude Code 對 hook 設定目錄的防注入保護、沙箱不可關),不試圖解鎖,改找**不碰被鎖路徑**的等價機制(git plumbing 只寫 `.git` 可寫區、harness 檔案工具、index-authoritative generator);**任何要交 user 執行的指令,必先驗證它不在同一道牆內**——對話內 `!` 與 Bash 同沙箱,指同牆死路 = 把動作丟回 user 的變形,同罪。**復原/交易類機制必有顯式逃生口 + skip-identical**(未變目標不准碰;全有全無原子性造成的死鎖 = 過度設計,2026-08-11 拆除)。**(b'') 機械化(2026-09-19)**:(b) 從 2026-08-08 立規到 2026-09-19 都只有散文、零強制 —— 而 (a) 早就有 `stop_self_audit.sh` Mechanism 8 把關。結果就是 2026-09-19 再犯同一件事:`git branch -D` 被 classifier 擋下,沒走等價的 `git update-ref -d`(plumbing 只寫 `.git` 可寫區),直接寫「你要清的話在終端機打 `!` …」——**正是 2026-08-28 那條錨例白紙黑字禁止的動作**,而且是 user 反問才發現。現由同檔 **Mechanism 10** 強制:本 turn 有工具被擋 + reply 把具體指令交給 user → BLOCKER,turn 不結束;真 human-only(login/MFA/OAuth/帳單/缺 credential reference)明文豁免。**教訓本身**:規則重犯代表缺的是強制面而不是再寫一遍散文 —— 同一條 M-rule 的兩個子款,有 hook 的那款沒再犯,沒 hook 的那款犯了兩次。 | 2026-08-08 4 次 provenance 升格 + policy 自鎖;2026-08-11 一日連環五鎖全是自家機制,逐層拆除後 release 全自動走通;2026-08-28 平台鎖誤判自家鎖 + 指 `!` 同沙箱死路給 user,正解 skip-worktree 一行(詳 historical-bugs.md,含 user 原話) |
|
|
73
73
|
<!-- M27 retired 2026-05-15(per /knowledge-prune D3 audit):self-flagged「M23 子規則」 → collapsed into M23 sub-bullet (c) "framework prop name namespace conflict"。Original case (TanStack size vs DS density) + hook `check_datatable_invariants.sh(r2 folded,2026-06-11)` retained 在 M23 children。 -->
|
|
74
74
|
<!-- M33 retired 2026-05-22(per /knowledge-prune deep audit Lens 1+2):folded into M20 as sub-rule「Stop hook overfire defer anti-pattern」。Original session context(2026-05-13 claim-verify-gap)+ hook `stop_self_audit.sh` P1 warn detection(Mechanism 4 + 次 turn auto-inject)retained at M20 entry。Single-bug rule → 與 M20 同類「AI self-correction under feedback signals」合併。 -->
|
|
75
75
|
|
|
@@ -69,12 +69,16 @@
|
|
|
69
69
|
- **判斷法**:這條規則能「機械化在 tool 執行前後自動跑」嗎? 是 → canonical hook + `registrations.json`;否 → canonical rule 或 spec
|
|
70
70
|
- **當前 inventory**:以 `packages/design-system/ds-canonical/hooks/registrations.json`、canonical hook tree 與 `scripts/sync-governance-counters.mjs --check` 動態結果為準;本文件不硬寫數量或複製 hook 清單。
|
|
71
71
|
|
|
72
|
-
### Level 8 — Command workflow (`packages/design-system/ds-canonical/commands/*.md`)
|
|
73
|
-
|
|
74
|
-
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
72
|
+
### Level 8 — Command workflow (`packages/design-system/ds-canonical/commands/*.md`)— **相容層,不是新居民的去處**
|
|
73
|
+
|
|
74
|
+
- **現狀**:居民 0。各家 agent runtime 已把單步 command 併進 skills 面,新的 `/<name>` 一律由同名 skill 提供。
|
|
75
|
+
此判準與該 home 自己的 charter(`ds-canonical/commands/README.md`)一致 —— 兩邊曾分歧:本表原本把
|
|
76
|
+
Level 8 寫成「重複使用 ≥ 3 次就放這」的活躍去處,charter 卻寫「只保留舊 consumer 的相容 pointer」。
|
|
77
|
+
以 charter + 空 inventory 為準。
|
|
78
|
+
- **唯一還可以留在這裡的東西**:外部 consumer 仍固定呼叫舊路徑時的 thin pointer,且必須只是同名 skill 的
|
|
79
|
+
指標,不得持有第二份 substantive workflow。
|
|
80
|
+
- **新增單步 action 的正確去處** → Level 7 Skill(單步就是只有一個 step 的 skill,不需要另一個 home)
|
|
81
|
+
- **inventory**:以 canonical commands 目錄實況為準,本文件不硬寫數量
|
|
78
82
|
|
|
79
83
|
### Level 9 — Planning (`governance/planning/`)
|
|
80
84
|
|
|
@@ -67,7 +67,9 @@ Output: 「`check_sideoffset_canonical.sh`(retired;已移入 hooks/retired/,此
|
|
|
67
67
|
|
|
68
68
|
#### 2c. Pending corrections → codify 候選
|
|
69
69
|
|
|
70
|
-
`user-corrections.jsonl`
|
|
70
|
+
`user-corrections.jsonl` 未 codify 數超過門檻 = user 反覆糾正類似錯誤,該升級到 canonical。
|
|
71
|
+
**門檻不在本檔硬寫**(同檔 Phase 2 表格曾寫 20、此處曾寫 10,兩個都會漂):唯一 SSOT 是
|
|
72
|
+
`session_start_governance_check.sh` Check 3 的 `-gt` 判斷(現行 soft 20 / hard 40)。
|
|
71
73
|
|
|
72
74
|
Output: 「過去 4 週 user 糾正 15 條,sample: ..., 提議:升級 M19「XX pattern」或擴充 `# Meta-Pattern 預警`」
|
|
73
75
|
|
package/package.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"type": "git",
|
|
5
5
|
"url": "git+https://github.com/ajenchen/design-system.git"
|
|
6
6
|
},
|
|
7
|
-
"version": "0.1.0-beta.
|
|
7
|
+
"version": "0.1.0-beta.136",
|
|
8
8
|
"private": false,
|
|
9
9
|
"description": "World-class design system — components, patterns, tokens, hooks (single source of truth for team distribution).",
|
|
10
10
|
"type": "module",
|
|
@@ -114,9 +114,9 @@ SMIL keySplines 無法消費 CSS var,`agent-panel-logo.tsx` 內常數為 swell/s
|
|
|
114
114
|
M23(f)「無內建行為的 callback 一律必填 prop(型別層擋)」必為必填。鍵盤路徑不變(Esc 照舊)。
|
|
115
115
|
- **遮罩** = `lib/overlay-coexistence.ts` 的 `CoexistenceMask`,與並存 Dialog 同一層 `z-30`、同一顆 `--overlay`、同樣替 `persistentElements` 挖洞
|
|
116
116
|
(層級句見 `../Dialog/dialog.spec.md`「並存」段:遮罩 z-30 < 並存 modal z-40 < 代理蓋板 z-[45] < 一般 modal z-50);它是面板根節點的**兄弟**,
|
|
117
|
-
|
|
117
|
+
**接住**留白處的指標:**點它會關閉面板**(2026-09-17 裁示,見上方「與 app 的推擠與斷點」),但不會穿到底下去關掉並存的 modal 或碰到宿主
|
|
118
118
|
(2026-09-16 user 第二次回報:第一版遮罩 `pointer-events:none`,點擊穿過去打到 modal 的外部點擊偵測、把 modal 關掉;修法 = 遮罩在面板的保留集合裡、
|
|
119
|
-
modal 的外部點擊守衛把常駐殼子樹裡的目標視為不關,Dialog 遮罩算洞時「遮罩不是洞」)
|
|
119
|
+
modal 的外部點擊守衛把常駐殼子樹裡的目標視為不關,Dialog 遮罩算洞時「遮罩不是洞」);面板的關閉有三條路(× / 入口鈕 / 蓋板態點遮罩,見「Esc 與關閉語意」);
|
|
120
120
|
- **淡入** = 與面板同相的 `--motion-duration-surface`(見「動畫總表」),偏好減少動態時不淡入。
|
|
121
121
|
「像 Sheet」只到外觀:面板**不是** Sheet、也不用 `SheetOverlay`(Sheet 遮罩是 z-50,會壓過並存 modal;且面板永不進 Radix 的 DismissableLayer 疊,見負向鐵律)。
|
|
122
122
|
世界級對照:各家**預設**都是點遮罩即關 —— Material Components Web dialog 的 `scrimClickAction` 預設 `close`
|
|
@@ -615,7 +615,7 @@ Esc 則是「關最內層的暫時性浮層」,面板不是暫時性浮層,所
|
|
|
615
615
|
|
|
616
616
|
- `dismissable-layer.tsx:59-61` 把 Esc 只送給疊最上層 → 面板一旦入疊,就會在「它剛好是最上層」時被 Esc 關掉,和上表第二列直接相反;
|
|
617
617
|
- 入疊還連帶吃到 `disableOutsidePointerEvents`(外點關閉)與焦點 trap,面板會從常駐 app UI 變成暫時性浮層。
|
|
618
|
-
- 蓋板態的遮罩是面板自家渲染的 `CoexistenceMask`(
|
|
618
|
+
- 蓋板態的遮罩是面板自家渲染的 `CoexistenceMask`(點擊 = 關閉面板,不是 Radix 的 dismiss 語意),**不是** Radix Overlay;「像 Sheet」只到外觀,不得改用 Sheet / SheetContent 承載面板。
|
|
619
619
|
|
|
620
620
|
因為靜默,所以配一支機械閘:`scripts/agent-panel-dismissable-layer-invariant.mjs`。
|
|
621
621
|
|
|
@@ -20,7 +20,7 @@ benchmark:
|
|
|
20
20
|
- Polaris Checkbox: github.com/Shopify/polaris/tree/main/polaris-react/src/components/Checkbox
|
|
21
21
|
---
|
|
22
22
|
|
|
23
|
-
<!-- @spec-budget-exception: SSOT-heavy 例外(`AGENTS.md`「行數預算」的 foundational SSOT 例外 ≤800-1200)— 本檔持有跨元件 SSOT:「與 Switch 的分界」(~45 行)+「Clamp 政策(Label/Description 行數)」(~35 行);2026-07-14 knowledge-prune P2 結論「殘餘超標內容全是 canonical,再砍必動 meaning」(
|
|
23
|
+
<!-- @spec-budget-exception: SSOT-heavy 例外(`AGENTS.md`「行數預算」的 foundational SSOT 例外 ≤800-1200)— 本檔持有跨元件 SSOT:「與 Switch 的分界」(~45 行)+「Clamp 政策(Label/Description 行數)」(~35 行);2026-07-14 knowledge-prune P2 結論「殘餘超標內容全是 canonical,再砍必動 meaning」(該次掃描明細留在 provider home 的 log,屬歷史 provenance,不是本例外的 authority —— 例外成立與否只看本句陳述的理由)。 -->
|
|
24
24
|
|
|
25
25
|
|
|
26
26
|
# Checkbox 設計原則
|
|
@@ -13,7 +13,7 @@ benchmark:
|
|
|
13
13
|
- Polaris Select: github.com/Shopify/polaris/tree/main/polaris-react/src/components/Select
|
|
14
14
|
---
|
|
15
15
|
|
|
16
|
-
<!-- @spec-budget-exception: SSOT-heavy 例外(`AGENTS.md`「行數預算」的 foundational SSOT 例外 ≤800-1200)— 本檔持有跨元件 SSOT:「與 RadioGroup 的分界」(~40 行)+ 3 筆拍板記錄(2026-07-13 D2 hidden-input mirror / 2026-05-21 D3 dual-mode / 2026-06-12 R2 open-pair 例外);2026-07-14 knowledge-prune P2 結論「殘餘超標內容全是 canonical,再砍必動 meaning」(
|
|
16
|
+
<!-- @spec-budget-exception: SSOT-heavy 例外(`AGENTS.md`「行數預算」的 foundational SSOT 例外 ≤800-1200)— 本檔持有跨元件 SSOT:「與 RadioGroup 的分界」(~40 行)+ 3 筆拍板記錄(2026-07-13 D2 hidden-input mirror / 2026-05-21 D3 dual-mode / 2026-06-12 R2 open-pair 例外);2026-07-14 knowledge-prune P2 結論「殘餘超標內容全是 canonical,再砍必動 meaning」(該次掃描明細留在 provider home 的 log,屬歷史 provenance,不是本例外的 authority —— 例外成立與否只看本句陳述的理由)。 -->
|
|
17
17
|
|
|
18
18
|
# Select 設計原則
|
|
19
19
|
|