@qijenchen/design-system 0.1.0-beta.125 → 0.1.0-beta.126

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.
@@ -3,8 +3,8 @@
3
3
  "schemaVersion": 1,
4
4
  "role": "template-consumer",
5
5
  "release": {
6
- "designSystem": "0.1.0-beta.125",
7
- "storybookConfig": "0.1.0-beta.125"
6
+ "designSystem": "0.1.0-beta.126",
7
+ "storybookConfig": "0.1.0-beta.126"
8
8
  },
9
9
  "upgradeTrust": {
10
10
  "schemaVersion": 1,
@@ -42,10 +42,10 @@
42
42
  "implementationSha256": "98b74118fb5b410b77ad2994aa9105fa7609c7353822f8c5213cce54d94f0af4"
43
43
  },
44
44
  "payload": {
45
- "forkCorpusLockSha256": "30c877a2da6a1ddff199fbb5f38f17081dd0e24b5febd0b65ca1d0b623b786c4",
45
+ "forkCorpusLockSha256": "32ab428089a73ab3708bd40c6590efc70837837024d71bbfc00d322f803aae6e",
46
46
  "discoveryPolicySha256": "fba289aeeb5a3f11139b9589f7ab08b5e8c8799c7bdf8f0ea57ebfeb8e7c9da6",
47
47
  "providerMaterializersSha256": "732e85b01a12e7dd19469e26e4427f0e0cfe5117a78fc9f69c2f0f89702f463a",
48
- "providerLifecycleSha256": "73ac6182c2e146a46015d18183370b8ed7ed20c04055b4682e4ca0e8d6594d33",
48
+ "providerLifecycleSha256": "27a43d1aa75a417aefcb23c1f4f9db3a717d40d76928113264cc2f3069298d8a",
49
49
  "providerInventorySha256": "00adf1c4f6b3367055a9cc3fdfe658660771b4c9931f696e12761a95076ad92c",
50
50
  "executionRuntimeSha256": "d9377cc45308731f287d7b1711b7c38fc4e02ff8f84729bcbbf01bb52d136904",
51
51
  "commonInstructionSha256": "a72c51a412d6cdb3961d691744013c8362e826626a330cecf4a296a6f4d93a5c",
@@ -109,7 +109,7 @@
109
109
  "scripts/lib/closed-tool-execution.mjs": "eacd0cd92c5bff03e40ff4e82c02fae9bf2e6f543f157eef3d41c3ed8b7d3cb6",
110
110
  "scripts/lib/consumer-control-plane-policy.mjs": "51aba211bb2f755eff7fa48bb060f9835356a62d2643ab5049147a9896c7bb72",
111
111
  "scripts/lib/exact-workspace-dependencies.mjs": "b4921ad903a3ed3d901187d406a87270190945c5ca1874b089e80df898d47a97",
112
- "scripts/lib/governance-dependency-bootstrap.mjs": "8b37cf68731810ad665e02b7ac4a2a5a4ad55b01c6d9e0cbb37f33f3b00118f3",
112
+ "scripts/lib/governance-dependency-bootstrap.mjs": "0598ec2f7b2975f5948072bc9fdb7f24682f3e0abed3cca36696dd869dfc0ffc",
113
113
  "scripts/lib/provider-lifecycle.mjs": "9aabb2c98efc454b8555308a34830e6590b034b10a63f76f788fbd4cf37ce7e7",
114
114
  "scripts/lib/verified-exact-npm-runtime.mjs": "811ed3390a4ff6b4e818a5ce8b5c0ce34e10f48fb443b6177d6e4c8e14fa9670",
115
115
  "scripts/lib/workspace-post-create.mjs": "f3ddc6db740d5c4d48b1c6e16c55222325e4d1c16fe6644a455e7b7e5bea0a0d",
@@ -332,32 +332,38 @@ function assertRemediatedFinding(name, finding) {
332
332
  return
333
333
  }
334
334
  if (name === 'tar') {
335
+ // 2026-08-28 upstream re-score: GHSA-r292-9mhp-454m moderate→high (CVSS 7.5), advisory source
336
+ // renumbered 1124287→1145647, patched tar = 7.5.21. Our security overlay already ships 7.5.22,
337
+ // so exposure is unchanged — this pins the re-scored registry shape, drift still fails closed.
335
338
  invariant(
336
- finding.severity === 'moderate'
339
+ finding.severity === 'high'
337
340
  && finding.isDirect === false
338
341
  && exactArray(finding.nodes, ['node_modules/npm/node_modules/tar'])
339
342
  && exactArray(finding.effects, ['npm'])
340
343
  && finding.range === '<=7.5.20'
341
344
  && Array.isArray(finding.via)
342
345
  && finding.via.length === 1
343
- && finding.via[0]?.source === 1124287
346
+ && finding.via[0]?.source === 1145647
344
347
  && finding.via[0]?.name === 'tar'
345
348
  && finding.via[0]?.dependency === 'tar'
346
349
  && finding.via[0]?.url === 'https://github.com/advisories/GHSA-r292-9mhp-454m'
347
- && finding.via[0]?.severity === 'moderate'
350
+ && finding.via[0]?.severity === 'high'
348
351
  && finding.via[0]?.range === '<=7.5.20',
349
352
  'npm audit tar finding differs from the exact remediated bundled preimage',
350
353
  )
351
354
  return
352
355
  }
353
356
  if (name === 'npm') {
357
+ // Follows the tar re-score; npm 11.19.1 ships fixed tar so the metavulnerability range now
358
+ // closes at 11.19.0 (our governed runtime). Moving to a fixed npm stays tracked in the
359
+ // cloud-compat baton alongside ip-address/undici.
354
360
  invariant(
355
- finding.severity === 'moderate'
361
+ finding.severity === 'high'
356
362
  && finding.isDirect === true
357
363
  && exactArray(finding.via, ['tar'])
358
364
  && exactArray(finding.nodes, ['node_modules/npm'])
359
365
  && exactArray(finding.effects, [])
360
- && finding.range === '<=10.9.8 || >=11.0.0-pre.0',
366
+ && finding.range === '<=10.9.8 || 11.0.0-pre.0 - 11.19.0 || >=12.0.0-pre.0.0',
361
367
  'npm audit npm metavulnerability differs from the verified tar overlay closure',
362
368
  )
363
369
  return
@@ -359,7 +359,7 @@
359
359
  {
360
360
  "schemaVersion": 1,
361
361
  "file": "consumer/managed-files/46f10bc55cdfdf834d0749eebff42ea0bb8576f361b36d2b6ba1851354c50150.blob",
362
- "sha256": "8b37cf68731810ad665e02b7ac4a2a5a4ad55b01c6d9e0cbb37f33f3b00118f3",
362
+ "sha256": "0598ec2f7b2975f5948072bc9fdb7f24682f3e0abed3cca36696dd869dfc0ffc",
363
363
  "source": "scripts/lib/governance-dependency-bootstrap.mjs",
364
364
  "classification": null,
365
365
  "destination": "scripts/lib/governance-dependency-bootstrap.mjs"
@@ -919,7 +919,7 @@
919
919
  {
920
920
  "schemaVersion": 1,
921
921
  "file": "manifest.json",
922
- "sha256": "95ca1369f94e33ff984628d89071547c57b7aa5b605dfc563e662259e2dffbf4",
922
+ "sha256": "ae35b443d9957c6e2c80dbddafa6b53590a0b6fea61715ab8468ee94a8f5994a",
923
923
  "source": null,
924
924
  "classification": null,
925
925
  "destination": null
@@ -104,20 +104,20 @@
104
104
  },
105
105
  "providerLifecycle": {
106
106
  "schemaVersion": 4,
107
- "releaseVersion": "0.1.0-beta.125",
107
+ "releaseVersion": "0.1.0-beta.126",
108
108
  "immutableHead": {
109
109
  "providerInventorySha256": "86e9d0668cb54ad1bf05c36e78b4aa80417d5f468c929dd3ddcf69c1afbf32e4",
110
- "releaseVersion": "0.1.0-beta.124",
111
- "snapshotSha256": "5c727741c6fcbf52a024c7c1c7559c75dc62901897f38b70f802fd83ce3b0bdd"
110
+ "releaseVersion": "0.1.0-beta.125",
111
+ "snapshotSha256": "756cd3ddcbf1510cf9a01bce069d34a3a8745cb5da3316c59a50d547bb35f3f2"
112
112
  },
113
- "ledgerSha256": "e4ce8a826c0c4766a406dc0f10e582bd53ddb6722432530921fad7aae9cb58f5",
114
- "previousSnapshotSha256": "5c727741c6fcbf52a024c7c1c7559c75dc62901897f38b70f802fd83ce3b0bdd",
115
- "currentSnapshotSha256": "756cd3ddcbf1510cf9a01bce069d34a3a8745cb5da3316c59a50d547bb35f3f2",
113
+ "ledgerSha256": "d6fcc771331404daba601bef0ae55f076a9b97c4989ac6082cf725c7a23c4104",
114
+ "previousSnapshotSha256": "756cd3ddcbf1510cf9a01bce069d34a3a8745cb5da3316c59a50d547bb35f3f2",
115
+ "currentSnapshotSha256": "d6733118fa2660394b5c74e6457893a3b13bb662d07b5e6058d8e1ce2e36cf8a",
116
116
  "currentProviderInventorySha256": "86e9d0668cb54ad1bf05c36e78b4aa80417d5f468c929dd3ddcf69c1afbf32e4",
117
117
  "retiredProviders": [],
118
118
  "currentSnapshot": {
119
- "releaseVersion": "0.1.0-beta.125",
120
- "previousSnapshotSha256": "5c727741c6fcbf52a024c7c1c7559c75dc62901897f38b70f802fd83ce3b0bdd",
119
+ "releaseVersion": "0.1.0-beta.126",
120
+ "previousSnapshotSha256": "756cd3ddcbf1510cf9a01bce069d34a3a8745cb5da3316c59a50d547bb35f3f2",
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.124",
197
- "previousSnapshotSha256": "bd9db3e80ef712d803eac0ff3e102bea3479c4adf1bf48fda2379e7d116d9450",
196
+ "releaseVersion": "0.1.0-beta.125",
197
+ "previousSnapshotSha256": "5c727741c6fcbf52a024c7c1c7559c75dc62901897f38b70f802fd83ce3b0bdd",
198
198
  "providers": [
199
199
  {
200
200
  "id": "claude",
@@ -69,7 +69,7 @@
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 保證真跑。**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 檔位)當成保證,再據以生出新規則,是同一類病。發現升格 → 立即撤回並留檔於該文件的「來源總帳」。**(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),不是邊界。**復原/交易類機制必有顯式逃生口 + skip-identical**(未變目標不准碰;全有全無原子性造成的死鎖 = 過度設計,2026-08-11 拆除)。 | 2026-08-08 4 次 provenance 升格 + policy 自鎖;2026-08-11 一日連環五鎖全是自家機制,逐層拆除後 release 全自動走通(詳 historical-bugs.md,含 user 原話) |
72
+ | **M36** | **禁把自己的推論寫成 user 的決定;禁用自家治理設定把自己鎖死**(2026-08-08 user directive)。**(a) Provenance 不得升格**:任何文件/回覆標「user 拍板」「你最早的決定」「已拍板」,**必須當場引得出 user 的原話**;引不出來一律標「AI 推導」或「AI 建議、user 採納」。**引文一律逐字,禁刪語氣詞**——把「…才對吧?」寫成「…才對」= 竄改證據,比誤判嚴重(2026-08-09 錨例);user 說「還在思考」之後仍當已決,同罪。**問句 ≠ 同意**——user 問「這是 X 的情況?」是要澄清,不是核准。**推論 ≠ 既有**——把某個既有值(如 Dialog 的 maxWidth 檔位)當成保證,再據以生出新規則,是同一類病。發現升格 → 立即撤回並留檔於該文件的「來源總帳」。**(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 拆除)。 | 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
 
@@ -260,4 +260,5 @@ DatePicker `today + selected`:藍 bar 疊在藍底隱形;`hover + disabled`:ring
260
260
  ### M36 — 禁推論升格 user 決定;禁自鎖
261
261
  - 2026-08-08 同一 session 連犯 4 次 provenance 升格(「<768 modal 無入口是你拍板」/「X = 終結 session」/「392px user 接受」/「816 讓位線」),user 原話「我他媽到底哪有拍板過這件事」;同日又因 `claude-permission-policy` 把 sandbox unix socket 鎖死,先叫 user 去買 API key 而非解自家鎖,user 原話「你他媽不要又再作繭自縛為自己加鎖了」。
262
262
  - 2026-08-11 一日連環五鎖:build graph journal 死鎖(復原全刪全蓋碰禁寫目標)/ authority fingerprint 把自家 index-publish 誤判竄改 / blanket 授權辨識漏「開頭裸可以」/ `gh auth status` 帳號級體檢擋 repo-scoped token / gh Go TLS 拒 sandbox 代理——五層全是自家機制,user 原話「不要再說有人擋你了,完全沒有」;逐層拆除後 release 全自動走通,零 user 指令。
263
+ - 2026-08-28 hooks/scripts symlink 本地殘影(8/1 舊版 generator 合法生成、8/2 佈局改版後無人能寫回):我先誤稱「我們自己沙箱設的保護」(實查自家 settings `denyWrite=[]`,鎖是 Claude Code 對 hook 設定目錄的**平台內建**防注入保護),再指 `! git restore` 給 user(對話內 `!` 與 Bash **同一個沙箱**,必再被擋 = 指死路),user 原話「你不要再作繭自縛了,我說要做就做」;正解一行 `git update-index --skip-worktree`(只寫 `.git` 可寫區,不碰被鎖路徑)→ codify M36(b') 第 4 問(鎖的主人是誰 + 交 user 的指令必先驗證不在同牆內)。
263
264
  - 四路同 provider 對抗審查查不到 (a) 類(reviewer 無對話紀錄),故必須以 M36 rule + 來源總帳格式機械化。
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.125",
7
+ "version": "0.1.0-beta.126",
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",
@@ -46,6 +46,13 @@
46
46
  outline-offset: 2px;
47
47
  }
48
48
 
49
+ /* 文字反白 — 底色 token 化(--text-selection;SSOT = color.spec.md「Highlight」段)。
50
+ 只動底色、不動字色:半透明品牌藍疊在原字色下,反白後文字對比 ≥4.5:1 由驗收量測保證
51
+ (MDN ::selection 對比要求)。 */
52
+ ::selection {
53
+ background-color: var(--text-selection);
54
+ }
55
+
49
56
  /* Button pointer cursor(Tailwind v4 移除 v3 的 button cursor,補回符合 UX 直覺) */
50
57
  button:not(:disabled),
51
58
  [role="button"]:not(:disabled) {
@@ -243,6 +243,33 @@ Icon 色彩 canonical 的 SSOT 住 `patterns/element-anatomy/item-anatomy.spec.m
243
243
  <span className="bg-notification text-white">3</span> // badge 計數
244
244
  ```
245
245
 
246
+ ### Highlight — 文字反白與搜尋配對
247
+
248
+ | Token | Utility | 值 | 用途 |
249
+ |-------|---------|------|------|
250
+ | `--text-selection` | `bg-text-selection` | blue-5 @ 30% 半透明 | 文字反白(`::selection`)底色;全域樣式住 `styles/base.css`,只動底色不動字色 |
251
+ | `--search-match` | `bg-search-match` | amber-3 | 頁內搜尋(Ctrl+F 類)**所有配對**的行內底色 |
252
+ | `--search-match-current` | `bg-search-match-current` | amber-5(dark:amber-4) | 配對中**目前這一筆**(find 游標所在) |
253
+
254
+ 設計依據(2026-08-28 拍板):
255
+
256
+ - **反白必半透明**:對齊 VS Code theme-color 對 selection 類 token 的鐵律「The color must not be opaque so as not to hide underlying decorations」(https://code.visualstudio.com/api/references/theme-color);反白後文字須維持對比 ≥ 4.5:1(https://developer.mozilla.org/en-US/docs/Web/CSS/::selection)
257
+ - **搜尋雙檔制**是編輯器/瀏覽器普世慣例:VS Code `editor.findMatchBackground`(「Color of the current search match」)與 `editor.findMatchHighlightBackground`(「Color of the other search matches」)兩 token 分立;琥珀/黃族底色 = 瀏覽器原生 find 慣例
258
+ - **對階**:amber-3 / -5 對齊 Radix scale 語意 step 3 =「UI element background」/ step 5 =「Active / Selected UI element background」(https://www.radix-ui.com/colors/docs/palette-composition/understanding-the-scale);行內 highlight 鐵約束 = 文字保持原色可讀 → 底色停留亮階(≤ step 5)
259
+ - **為何字尾是 `-current` 不是 `-active`**:`-active` 在本系統 = 按壓語意且映射第 7 階(`--amber-active` = amber-7,見「互動狀態推導」),同字不同映射會破壞一致性;`current` 取自 VS Code 官方描述原詞
260
+ - **Dark**:`--text-selection` / `--search-match` 由 primitive 階梯公式自動反轉(1–5 變深)免 override;`--search-match-current` **需降一階覆寫 amber-4**——數學驗收(oklch→sRGB→WCAG)dark amber-5 配白字對比 3.03 過不了 4.5,amber-4 = 5.89 ✓ 且與 amber-3 區辨 1.86 優於 light 的 1.31。全組量測:light 反白 11.41 / amber-3 12.21 / amber-5 9.82;dark 反白 13.04 / amber-3 10.97 / amber-4 5.89(皆 ≥ 4.5)
261
+
262
+ ```tsx
263
+ <mark className="bg-search-match text-inherit">報表</mark> // 一般配對
264
+ <mark className="bg-search-match-current text-inherit">報表</mark> // 目前這一筆
265
+ ```
266
+
267
+ 禁止:
268
+
269
+ - ❌ `<mark>` 用 UA 預設黃 — 必套 `bg-search-match`(或 `-current`)+ `text-inherit`
270
+ - ❌ 搜尋配對語意裸用 `--color-amber-3/5` — 必經 semantic token(未來調色只動一處)
271
+ - ❌ `--search-match-current` 借給按壓 / 持續選中場景 — 那是 `--neutral-active` / `--neutral-selected` 家族的域;`--text-selection` 亦禁借給資料列選取
272
+
246
273
  ### Identity — 品牌
247
274
 
248
275
  | Token | 用途 |
@@ -362,4 +362,42 @@ export const Interactive: Story = {
362
362
  </div>
363
363
  </div>
364
364
  ),
365
- }
365
+ }
366
+ export const FunctionalHighlight: Story = {
367
+ name: '反白與搜尋配對',
368
+ render: () => (
369
+ <div className="max-w-xl space-y-6">
370
+ <section className="space-y-2">
371
+ <h3 className="text-body font-medium">文字反白(::selection)</h3>
372
+ <p data-selection-demo className="rounded-md border border-border bg-surface p-4 text-body">
373
+ 第三季營收報告已於 10 月 14 日送交財務委員會審閱,附件包含分區銷售明細與年度預測修正。
374
+ </p>
375
+ <span className="block text-caption text-fg-muted">
376
+ 此段由 play 自動反白 — 底色 <code>--text-selection</code>(blue-5 @ 30% 半透明,字色不變)
377
+ </span>
378
+ </section>
379
+ <section className="space-y-2">
380
+ <h3 className="text-body font-medium">頁內搜尋配對(Ctrl+F)</h3>
381
+ <p className="rounded-md border border-border bg-surface p-4 text-body">
382
+ 搜尋「報表」:月度<mark className="rounded-xs bg-search-match text-inherit">報表</mark>
383
+ 已歸檔;季度<mark className="rounded-xs bg-search-match-current text-inherit">報表</mark>
384
+ 為目前所在配對,尚待審核;年度<mark className="rounded-xs bg-search-match text-inherit">報表</mark>
385
+ 排定下週產出。
386
+ </p>
387
+ <span className="block text-caption text-fg-muted">
388
+ 全部配對 <code>bg-search-match</code>(amber-3)/ 目前這一筆 <code>bg-search-match-current</code>(amber-5)— 雙檔制對齊 VS Code find 與瀏覽器原生慣例
389
+ </span>
390
+ </section>
391
+ </div>
392
+ ),
393
+ play: async ({ canvasElement }) => {
394
+ const target = canvasElement.querySelector('[data-selection-demo]')
395
+ if (target) {
396
+ const range = document.createRange()
397
+ range.selectNodeContents(target)
398
+ const selection = window.getSelection()
399
+ selection?.removeAllRanges()
400
+ selection?.addRange(range)
401
+ }
402
+ },
403
+ }
@@ -392,6 +392,17 @@
392
392
  --chart-3: var(--color-green-6);
393
393
  --chart-4: var(--color-yellow-7);
394
394
  --chart-5: var(--color-deep-orange-6);
395
+
396
+ /* Highlight — 文字反白與頁內搜尋配對(2026-08-28 拍板;SSOT 詳 color.spec.md「Highlight」段)
397
+ 反白必半透明(VS Code theme-color「must not be opaque」鐵律)、只動底色不動字色(對比 ≥4.5:1 驗收)。
398
+ 搜尋雙檔制 = VS Code findMatchBackground(current)/ findMatchHighlightBackground(others)普世慣例;
399
+ amber-3 / -5 對階 Radix step 3(UI element bg)/ step 5(Active/Selected bg);行內文字保持原色
400
+ → 底色停亮階,故 -current 不取 -active 映射的第 7 階(--amber-active 已佔且 -active = 按壓語意)。
401
+ Dark:-selection / -match 由 primitive 公式自動反轉;-current 需降一階覆寫 amber-4
402
+ (dark amber-5 白字對比 3.03 < 4.5,詳 dark 區塊註解)。 */
403
+ --text-selection: color-mix(in oklch, var(--color-blue-5) 30%, transparent);
404
+ --search-match: var(--color-amber-3);
405
+ --search-match-current: var(--color-amber-5);
395
406
  }
396
407
 
397
408
 
@@ -451,6 +462,12 @@
451
462
  --indigo-hover: var(--color-indigo-7);
452
463
  --indigo-active: var(--color-indigo-5);
453
464
 
465
+ /* Highlight — dark 唯一覆寫:-current 降一階 amber-4。
466
+ 數學驗收(oklch→sRGB→WCAG):dark amber-5 配白字對比 3.03 過不了 4.5 鐵門檻;
467
+ amber-4 = 5.89 ✓ 且與 amber-3(全部配對)區辨 1.86 優於 light 的 1.31。
468
+ --text-selection / --search-match 由 primitive 公式自動反轉,不需覆寫。 */
469
+ --search-match-current: var(--color-amber-4);
470
+
454
471
  /* Inverse — dark mode 反轉(深字 + 黑色透明 overlay)
455
472
  overlay 值為 light mode neutral-* 的鏡射(black @ 2% / 4%)
456
473
  2026-05-20 fix:raw oklch → primitive --black-a85(既有 alpha token,L46)*/
@@ -496,6 +513,11 @@
496
513
  --color-warning: var(--warning);
497
514
  --color-notification: var(--notification);
498
515
 
516
+ /* Highlight(反白 / 搜尋配對) */
517
+ --color-text-selection: var(--text-selection);
518
+ --color-search-match: var(--search-match);
519
+ --color-search-match-current: var(--search-match-current);
520
+
499
521
  /* Presence Status(Avatar badge) */
500
522
  --color-status-online: var(--status-online);
501
523
  --color-status-busy: var(--status-busy);