@qijenchen/design-system 0.1.0-beta.123 → 0.1.0-beta.125
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/dist/components/PeoplePicker/people-picker.d.ts.map +1 -1
- package/dist/components/PeoplePicker/people-picker.js +1 -1
- package/dist/components/PeoplePicker/people-picker.js.map +1 -1
- package/dist/patterns/element-anatomy/item-anatomy.js +1 -1
- package/ds-canonical/adapters/claude/agents/README.md +1 -1
- package/ds-canonical/commands/README.md +1 -1
- package/ds-canonical/fork/codex/agents/skills/bug-fix-rhythm/SKILL.md +3 -3
- package/ds-canonical/fork/codex/agents/skills/product-ui-audit/SKILL.md +1 -1
- package/ds-canonical/fork/codex/agents/skills/propose-options/SKILL.md +3 -3
- package/ds-canonical/fork/codex/agents/skills/prototype/SKILL.md +1 -1
- package/ds-canonical/fork/codex/agents/skills/prototype/references/evaluation-matrix.md +1 -1
- package/ds-canonical/fork/codex/agents/skills/scan-similar-bugs/SKILL.md +1 -10
- package/ds-canonical/fork/codex/agents/skills/visual-audit/SKILL.md +1 -2
- package/ds-canonical/fork/codex/agents/skills/visual-audit/references/visual-checklist.md +2 -2
- package/ds-canonical/fork/consumer/lock.json +7 -7
- package/ds-canonical/fork/governance.lock +33 -33
- package/ds-canonical/fork/hooks/check_consumer_app_invariants.sh +1 -1
- package/ds-canonical/fork/manifest.json +10 -10
- package/ds-canonical/fork/providers/claude/skills/bug-fix-rhythm/SKILL.md +3 -3
- package/ds-canonical/fork/providers/claude/skills/product-ui-audit/SKILL.md +1 -1
- package/ds-canonical/fork/providers/claude/skills/propose-options/SKILL.md +3 -3
- package/ds-canonical/fork/providers/claude/skills/prototype/SKILL.md +1 -1
- package/ds-canonical/fork/providers/claude/skills/prototype/references/evaluation-matrix.md +1 -1
- package/ds-canonical/fork/providers/claude/skills/scan-similar-bugs/SKILL.md +1 -10
- package/ds-canonical/fork/providers/claude/skills/visual-audit/SKILL.md +1 -2
- package/ds-canonical/fork/providers/claude/skills/visual-audit/references/visual-checklist.md +2 -2
- package/ds-canonical/fork/providers/codex/skills/bug-fix-rhythm/SKILL.md +3 -3
- package/ds-canonical/fork/providers/codex/skills/product-ui-audit/SKILL.md +1 -1
- package/ds-canonical/fork/providers/codex/skills/propose-options/SKILL.md +3 -3
- package/ds-canonical/fork/providers/codex/skills/prototype/SKILL.md +1 -1
- package/ds-canonical/fork/providers/codex/skills/prototype/references/evaluation-matrix.md +1 -1
- package/ds-canonical/fork/providers/codex/skills/scan-similar-bugs/SKILL.md +1 -10
- package/ds-canonical/fork/providers/codex/skills/visual-audit/SKILL.md +1 -2
- package/ds-canonical/fork/providers/codex/skills/visual-audit/references/visual-checklist.md +2 -2
- package/ds-canonical/fork/skills/bug-fix-rhythm/SKILL.md +2 -2
- package/ds-canonical/fork/skills/product-ui-audit/SKILL.md +1 -1
- package/ds-canonical/fork/skills/propose-options/SKILL.md +2 -2
- package/ds-canonical/fork/skills/prototype/SKILL.md +1 -1
- package/ds-canonical/fork/skills/prototype/references/evaluation-matrix.md +1 -1
- package/ds-canonical/fork/skills/scan-similar-bugs/SKILL.md +1 -1
- package/ds-canonical/fork/skills/visual-audit/references/visual-checklist.md +2 -2
- package/ds-canonical/hooks/check_consumer_app_invariants.sh +1 -1
- package/ds-canonical/hooks/check_datatable_invariants.sh +1 -1
- package/ds-canonical/hooks/check_plugin_fork_health.sh +1 -1
- package/ds-canonical/hooks/check_propose_discipline.sh +1 -1
- package/ds-canonical/hooks/check_story_invariants.sh +2 -2
- package/ds-canonical/hooks/check_storybook_addon_packaging.sh +1 -1
- package/ds-canonical/hooks/lib/_app_shell_primary_header_consistency.sh +1 -1
- package/ds-canonical/references/README.md +0 -1
- package/ds-canonical/rules/README.md +1 -1
- package/ds-canonical/rules/meta-patterns.md +7 -9
- package/ds-canonical/skills/bug-fix-rhythm/SKILL.md +2 -2
- package/ds-canonical/skills/design-system-audit/SKILL.md +1 -1
- package/ds-canonical/skills/design-system-audit/references/audit-prompts.md +6 -6
- package/ds-canonical/skills/design-system-audit/references/historical-bugs.md +24 -1
- package/ds-canonical/skills/governance-health/SKILL.md +2 -2
- package/ds-canonical/skills/new-component/SKILL.md +9 -9
- package/ds-canonical/skills/new-component/references/new-component-checklist.md +3 -3
- package/ds-canonical/skills/product-ui-audit/SKILL.md +1 -1
- package/ds-canonical/skills/propose-options/SKILL.md +2 -2
- package/ds-canonical/skills/prototype/SKILL.md +1 -1
- package/ds-canonical/skills/prototype/references/evaluation-matrix.md +1 -1
- package/ds-canonical/skills/scan-similar-bugs/SKILL.md +1 -1
- package/ds-canonical/skills/visual-audit/references/visual-checklist.md +2 -2
- package/package.json +1 -1
- package/src/components/DataTable/data-table.css +29 -2
- package/src/components/DataTable/data-table.spec.md +1 -1
- package/src/components/PeoplePicker/people-picker.tsx +5 -1
- package/ds-canonical/references/preflight-gate-baseline.json +0 -216
- /package/ds-canonical/hooks/{lib/_approval_re.sh → retired/_approval_re.sh.retired-2026-08-18} +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"people-picker.d.ts","sourceRoot":"","sources":["../../../src/components/PeoplePicker/people-picker.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAG9B,OAAO,KAAK,EAAE,SAAS,EAAE,YAAY,EAAwB,UAAU,EAAE,MAAM,8CAA8C,CAAA;AAS7H,OAAO,EAA6F,KAAK,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAkB9I,OAAO,EACL,mCAAmC,EACnC,8BAA8B,EAG/B,MAAM,yBAAyB,CAAA;AAGhC,OAAO,EAAE,mCAAmC,EAAE,8BAA8B,EAAE,CAAA;AAyB9E,MAAM,WAAW,iBAAkB,SAAQ,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC,EAAE,UAAU,GAAG,cAAc,CAAC;IAChH,sFAAsF;IACtF,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB,gDAAgD;IAChD,OAAO,CAAC,EAAE,YAAY,CAAA;IACtB;8EAC0E;IAC1E,KAAK,CAAC,EAAE,UAAU,CAAA;IAClB,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,CAAA;IACzB,6CAA6C;IAC7C,KAAK,CAAC,EAAE,WAAW,GAAG,WAAW,EAAE,GAAG,IAAI,CAAA;IAC1C,oEAAoE;IACpE,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,WAAW,EAAE,KAAK,IAAI,CAAA;IACzC,4BAA4B;IAC5B,MAAM,CAAC,EAAE,WAAW,EAAE,CAAA;IACtB;oFACgF;IAChF,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;;;sEAGkE;IAClE,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,wDAAwD;IACxD,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB;uFACmF;IACnF,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,sCAAsC;IACtC,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,6BAA6B;IAC7B,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACtC;;;;OAIG;IACH,YAAY,CAAC,EAAE,OAAO,GAAG,MAAM,CAAA;IAC/B;;;OAGG;IACH,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB,+DAA+D;IAC/D,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC7B;;;;;OAKG;IACH,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB;;;;;OAKG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAA;IAC5B,iBAAiB;IACjB,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB;AAED,QAAA,MAAM,YAAY,
|
|
1
|
+
{"version":3,"file":"people-picker.d.ts","sourceRoot":"","sources":["../../../src/components/PeoplePicker/people-picker.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAG9B,OAAO,KAAK,EAAE,SAAS,EAAE,YAAY,EAAwB,UAAU,EAAE,MAAM,8CAA8C,CAAA;AAS7H,OAAO,EAA6F,KAAK,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAkB9I,OAAO,EACL,mCAAmC,EACnC,8BAA8B,EAG/B,MAAM,yBAAyB,CAAA;AAGhC,OAAO,EAAE,mCAAmC,EAAE,8BAA8B,EAAE,CAAA;AAyB9E,MAAM,WAAW,iBAAkB,SAAQ,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC,EAAE,UAAU,GAAG,cAAc,CAAC;IAChH,sFAAsF;IACtF,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB,gDAAgD;IAChD,OAAO,CAAC,EAAE,YAAY,CAAA;IACtB;8EAC0E;IAC1E,KAAK,CAAC,EAAE,UAAU,CAAA;IAClB,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,CAAA;IACzB,6CAA6C;IAC7C,KAAK,CAAC,EAAE,WAAW,GAAG,WAAW,EAAE,GAAG,IAAI,CAAA;IAC1C,oEAAoE;IACpE,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,WAAW,EAAE,KAAK,IAAI,CAAA;IACzC,4BAA4B;IAC5B,MAAM,CAAC,EAAE,WAAW,EAAE,CAAA;IACtB;oFACgF;IAChF,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;;;sEAGkE;IAClE,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,wDAAwD;IACxD,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB;uFACmF;IACnF,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,sCAAsC;IACtC,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,6BAA6B;IAC7B,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACtC;;;;OAIG;IACH,YAAY,CAAC,EAAE,OAAO,GAAG,MAAM,CAAA;IAC/B;;;OAGG;IACH,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB,+DAA+D;IAC/D,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC7B;;;;;OAKG;IACH,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB;;;;;OAKG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAA;IAC5B,iBAAiB;IACjB,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB;AAED,QAAA,MAAM,YAAY,0FAmZhB,CAAA;AAIF,eAAO,MAAM,gBAAgB;;;;;;;;;;;CAYnB,CAAA;AAEV,OAAO,EAAE,YAAY,EAAE,CAAA;AAGvB,OAAO,EACL,aAAa,EACb,kBAAkB,EAClB,eAAe,EACf,sBAAsB,EACtB,aAAa,EACb,KAAK,WAAW,EAChB,KAAK,UAAU,GAChB,MAAM,kBAAkB,CAAA"}
|
|
@@ -89,7 +89,7 @@ const PeoplePicker = React.forwardRef(function PeoplePicker2({
|
|
|
89
89
|
}, [ref]);
|
|
90
90
|
if (resolvedMode === "view") {
|
|
91
91
|
if (!showDisplayEndIcon) {
|
|
92
|
-
return /* @__PURE__ */ jsx("div", { ref, className: cn("w-full min-w-0", className), "aria-label": ariaLabel, ...rest, children: isEmpty ? /* @__PURE__ */ jsx("span", { className: fieldEmptyColorClass(resolvedMode), children: emptyDisplay }) : isMulti ? /* @__PURE__ */ jsx(MultiPersonDisplay, { value, size, measured: true }) : /* @__PURE__ */ jsx(PersonDisplay, { value, size }) });
|
|
92
|
+
return /* @__PURE__ */ jsx("div", { ref, className: cn("flex w-full min-w-0", className), "aria-label": ariaLabel, ...rest, children: isEmpty ? /* @__PURE__ */ jsx("span", { className: fieldEmptyColorClass(resolvedMode), children: emptyDisplay }) : isMulti ? /* @__PURE__ */ jsx(MultiPersonDisplay, { value, size, measured: true }) : /* @__PURE__ */ jsx(PersonDisplay, { value, size }) });
|
|
93
93
|
}
|
|
94
94
|
const iconSize = ICON_SIZE[size];
|
|
95
95
|
return /* @__PURE__ */ jsxs(
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"people-picker.js","sources":["../../../src/components/PeoplePicker/people-picker.tsx"],"sourcesContent":["// @benchmark-unverified-blanket: file-level retraction per M22 (d) — claims herein not individually URL-cited; treat as unverified visual/usage rumor unless retrofit per-claim. Hook escape preserved.\n// @placeholder-vocabulary-allow: 2026-07-04 Q4 完成 field-controls.spec.md 共享 contract b — emptyPlaceholder forward 已移除,emptyText 直達 Combobox → SelectMenu emptyText(search-empty 真住所);placeholder 為 trigger empty SSOT。\n// @cell-metric-escape-allow: comment describes RETIRED `tagAreaPaddingLeftPx={8}` magic — current code is surface-guarded (`surface === 'form'` only injects `!px-[var(--field-px)]`; table-cell context untouched, lets naked `!px-[var(--table-cell-px)]` SSOT take over). Hook regex grep'd the comment word, not the live code path. Per (a) fix 2026-05-13 user-approved Path a.\nimport * as React from 'react'\nimport { ChevronDown } from 'lucide-react'\nimport { cn } from '@/lib/utils'\nimport type { FieldMode, FieldVariant, FieldVariantInternal, FieldWidth } from '@/design-system/components/Field/field-types'\nimport { fieldWrapperStyles, nakedCellRowModeAlign } from '@/design-system/components/Field/field-wrapper'\nimport { useFieldEmptyDisplay, fieldEmptyColorClass } from '@/design-system/components/Field/field-context'\nimport { ItemSuffix } from '@/design-system/patterns/element-anatomy/item-anatomy'\nimport { useFieldSurface, useResolvedFieldSize, useResolvedFieldDisabled, useResolvedFieldMode, useResolvedFieldVariant } from '@/design-system/components/Field/field-context'\nimport { Avatar } from '@/design-system/components/Avatar/avatar'\nimport { Tag } from '@/design-system/components/Tag/tag'\nimport { Select as SelectPublic } from '@/design-system/components/Select/select'\nimport { Combobox as ComboboxPublic } from '@/design-system/components/Combobox/combobox'\nimport { PersonDisplay, MultiPersonDisplay, PersonAvatarTag, buildPersonProfileCard, resolvePerson, type PersonValue } from './person-display'\nimport {\n getAvatarStackVisibleCount,\n AVATAR_STACK_AVATAR_PX,\n AVATAR_STACK_OVERFLOW_CHIP_PX,\n} from './avatar-stack-overflow'\nimport type { WithFieldVariantInternal } from '@/design-system/components/Field/field-types'\n\n// ── @internal naked variant forward 通道(2026-07-14 API 策展 E)──────────────────────────\n// naked 已從公開 FieldVariant 拆出(field-types.ts FieldVariantInternal)。PeoplePicker 是\n// Select / Combobox 的 wrapper:cell-registry 傳入的 naked 需原樣 forward 給 primitive\n// (M30 wrapper forward 全 primitive surface)。型別層 widen,純型別、零 runtime。\nconst Select = SelectPublic as WithFieldVariantInternal<typeof SelectPublic>\nconst Combobox = ComboboxPublic as WithFieldVariantInternal<typeof ComboboxPublic>\n// Pure helpers extracted to sibling for file-size budget(2026-05-18,P1 ≤ 500 lines)。\n// 不消費 component closure 的純 constant / 純 mapping function 全部搬走,主檔保留 SSOT-bearing\n// render logic(消費 Combobox / Select / state 等 closure 的部分)。\n// SSOT primitive re-export(backward-compat 對外 import 路徑保持 `./people-picker`)。\nimport {\n PEOPLE_PICKER_LENGTH1_WRAPPER_CLASS,\n getPeoplePickerTagWrapperClass,\n personToSelectOption,\n findPerson,\n} from './people-picker-helpers'\nimport { ICON_SIZE } from '@/design-system/tokens/uiSize/icon-size'\nimport { useIsTouchDevice } from '@/design-system/hooks/use-is-touch-device'\nexport { PEOPLE_PICKER_LENGTH1_WRAPPER_CLASS, getPeoplePickerTagWrapperClass }\n\n// ── PeoplePicker ────────────────────────────────────────────────────────────\n// **2026-05-07 v15.6 SSOT 重構 v2**:\n//\n// - **single mode** wraps `<Select searchable selectedItemRenderer>`\n// - **multi mode** 兩種 displayMode(consumer 自選),**皆 wrap `<Combobox>`**(同 SSOT,\n// 差別在 tagRenderer 視覺):\n// - **'stack'**(default,baseline 既有視覺)— Avatar 疊合 + `+N` overflow indicator,\n// 不可 wrap。tagRenderer 渲染 avatar stack(visible count 走 shared `avatar-stack-overflow`\n// primitive deterministic formula,2026-05-15 Bug 3 fix;view 路徑 `<MultiPersonDisplay>` 同 primitive)。\n// 對齊 Notion / Linear / Atlassian / Slack 多人 quick-glance idiom。\n// - **'pill'**(opt-in)— 每人 Tag pill,可 wrap。Wrap `<Combobox tagRenderer>`,\n// tagRenderer 用 Tag 元件 `avatar` prop SSOT(不塞 children)。\n// `pillShowAvatar` 控 pill 內是否顯 avatar prefix(default true,false → 純文字 pill)。\n// 對齊 GitHub Reviewers / Combobox tag-input idiom。\n\n// **codex P2 fix(2026-05-07 v15.10)**:`extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onChange'>`\n// 讓 consumer 可傳 `id` / `data-testid` / `onBlur` / `onFocus` / `aria-*` 等 HTML root props,\n// component 內部 `...rest` forward 到 trigger 容器(對齊 DS 既有 Combobox / Select 慣例)。\n// `onChange` 衝突走 Omit(本 component 用 PersonValue[] custom signature)。\n// 2026-07-17 Dim 26 修:`defaultValue` 一併 Omit — 本元件 controlled-only(spec「Controlled-only\n// rationale」),React.HTMLAttributes 內建的 `defaultValue` 若不 Omit 會讓 consumer 傳入假\n// uncontrolled API,runtime 經 `...rest` 靜默轉送 trigger div 後無效。Omit 本身即 type-level\n// regression(consumer 傳 defaultValue 直接 TS 編譯錯)。DS 先例 steps.tsx:166 同款 Omit。\nexport interface PeoplePickerProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onChange' | 'defaultValue'> {\n /** Field mode(edit / view / readonly / disabled),默認 inherit Field context 或 'edit' */\n mode?: FieldMode\n /** Field chrome variant(對齊 Select / Combobox)*/\n variant?: FieldVariant\n /** 寬度軸 — `fill` 填滿容器(default)/ `hug` 依內容收縮(value 寬 + slot 寬 + gap + 內 padding);\n * chrome 與互動不變。SSOT → field-controls.spec.md「寬度軸(width: fill / hug)」。 */\n width?: FieldWidth\n size?: 'sm' | 'md' | 'lg'\n /** 當前已選的人(單選 PersonValue,多選 PersonValue[])*/\n value?: PersonValue | PersonValue[] | null\n /** 值變更 callback(永遠 emit array — single mode 取 [0] 即 single value)*/\n onChange?: (value: PersonValue[]) => void\n /** 可選人員清單(edit mode 下拉顯示)*/\n people?: PersonValue[]\n /** 2026-05-12 Stream C Issue 4 fix(codex Q3 Cluster C):trigger empty placeholder。\n * Default '請選擇人員'。**禁** 將 `emptyText`(search-empty)當 trigger placeholder 傳。 */\n placeholder?: string\n /** 搜尋框 placeholder — **僅 multi 模式 panel-top search(`searchIn='menu'`,default)生效**。\n * single mode wrap `<Select searchable>` 走 inline-trigger 搜尋,提示取自 `placeholder`\n * (Select 無 searchPlaceholder prop);multi `searchIn='trigger'` inline 搜尋同理走\n * placeholder 規則(spec「Trigger display SSOT canonical table」)。 */\n searchPlaceholder?: string\n /** Multi 模式搜尋框 accessible name。僅轉送 wrapped Combobox。 */\n searchAriaLabel?: string\n /** 搜尋無結果訊息(filtered menu empty)。**僅**用於 SelectMenu `emptyText`(菜單空狀態,\n * 2026-07-04 Q4 接線完成),不轉 trigger placeholder(2026-05-12 Issue 4 semantic fix)。 */\n emptyText?: string\n className?: string\n disabled?: boolean\n /** Initial open state(uncontrolled)*/\n defaultOpen?: boolean\n /** open state 變更 callback */\n onOpenChange?: (open: boolean) => void\n /**\n * Multi mode 顯示樣式(default 'stack')。Single mode 此 prop 忽略。\n * - 'stack' — Avatar 疊合 + `+N`(空間省、不可 wrap;default)\n * - 'pill' — 每人 Tag pill(可 wrap)\n */\n multiDisplay?: 'stack' | 'pill'\n /**\n * `multiDisplay='pill'` 模式下是否顯示 avatar prefix(default true)。\n * 設 false → 純文字 pill,進一步節省空間。對齊 Tag 元件 `avatar` prop SSOT。\n */\n pillShowAvatar?: boolean\n /** Pill 模式下是否允許 wrap(default true)— 對齊 Combobox `wrap` prop */\n pillWrap?: boolean\n /**\n * 搜尋型態(2026-05-12 規則 3 ship,3-mode SSOT 對齊 A1-A5 spec):\n * - `'menu'`(default,backward-compat)— 浮層內搜尋(panel-top search)\n * - `'trigger'`(multi 模式 opt-in)— inline 搜尋(浮層開時 name 拿掉,avatar 後接 input cursor,\n * 類 Combobox inline-trigger idiom)\n * Single mode 永遠 inline-trigger(wrap Select searchable 直接走 inline),此 prop multi 才有意義。\n */\n searchIn?: 'menu' | 'trigger'\n /**\n * 一鍵清空(2026-08-05):X 在 ChevronDown 左,family SSOT → field-controls.spec.md「下拉箭頭」段。\n * 機械轉發 wrapped Select(single)/ Combobox(multi)的 `clearable` — X 渲染與清空行為 SSOT 在基座\n * (SelectClearButton / Combobox clear),本 wrapper 不另 render。何時開的判準繼承 select.spec.md\n * 「Clearable」:「無選擇是有效狀態」(選填欄位 / 可清 filter)才開;必填欄位不開。\n */\n clearable?: boolean\n /**\n * View 態是否渲 ChevronDown + Field naked wrapper(D-path opt-in,2026-05-08)\n * — DataTable cell view↔edit 像素級對齊用。預設 false(裸 PersonDisplay,backward compat)。\n * 設 true 時 view 走 fieldWrapperStyles(naked variant)+ ItemSuffix ChevronDown,\n * 與 edit (Select / Combobox wrapped) 同 DOM 結構,消除 Layer-B padding mismatch。\n */\n showDisplayEndIcon?: boolean\n /** a11y label */\n 'aria-label'?: string\n}\n\nconst PeoplePicker = React.forwardRef<HTMLDivElement, PeoplePickerProps>(function PeoplePicker({\n mode: modeProp,\n variant: variantProp,\n width,\n size: sizeProp,\n value,\n onChange,\n people = [],\n placeholder = '請選擇人員', // i18n-allow: DS default(2026-05-12 Stream C Issue 4)\n searchPlaceholder = '搜尋人員…', // i18n-allow: DS default\n searchAriaLabel = '搜尋人員', // i18n-allow: DS default\n emptyText = '沒有符合的人員', // i18n-allow: DS default — only for SelectMenu noResultsText\n className,\n disabled: disabledProp,\n defaultOpen = false,\n onOpenChange,\n multiDisplay = 'stack',\n pillShowAvatar = true,\n pillWrap = true,\n searchIn = 'menu',\n clearable = false,\n showDisplayEndIcon = false,\n 'aria-label': ariaLabel,\n ...rest\n}, ref) {\n const surface = useFieldSurface()\n const emptyDisplay = useFieldEmptyDisplay()\n const size = useResolvedFieldSize(sizeProp) // B 組 cascade fix\n const disabled = useResolvedFieldDisabled(disabledProp)\n // 2026-06-08 SSOT:mode/disabled/variant 統一經 helper;修 <Field disabled> 漏 cascade(原只讀 fieldCtx.mode)\n const resolvedMode: FieldMode = useResolvedFieldMode({ mode: modeProp, disabled })\n const resolvedVariant: FieldVariantInternal = useResolvedFieldVariant(variantProp)\n const isMulti = Array.isArray(value)\n const isEmpty = !value || (isMulti && value.length === 0)\n // 觸控多選一律走既有 pill 型態(2026-08-05 user 拍板:「手機上自動把多選 people picker 改成\n // combobox 的型態,所有樣式互動都是 combobox 的 SSOT,不要再造輪子」)。理由:stack 的移除鈕\n // 是 hover-reveal,觸控沒有 hover;恆顯 X 又太亂(user 否決)。pill 是 DS 既有 canonical\n // (`multiDisplay='pill'` + Tag `avatar` prop),每顆 pill 自帶 X = Combobox tag 移除 SSOT。\n // 桌機(fine pointer)完全不受影響:仍是 avatar stack + 圓形 +N + hover X。\n // consumer 顯式傳 'pill' 時本行為 no-op(同值)。hook 必在任何 early return 前呼叫(React #310)。\n // **只在 form surface 降階**(2026-08-05):table-cell / inline-edit / toolbar 維持 stack —\n // cell 的 view↔edit 像素對齊與固定列高契約不得被 pill 的 wrap 破壞(user:「尤其是 table\n // 的部分」)。cell 觸控編輯本就走原生 picker,移除成員在 picker 內完成。\n const isTouch = useIsTouchDevice()\n const effectiveMultiDisplay = isTouch && isMulti && surface === 'form' ? 'pill' : multiDisplay\n\n // 2026-07-05 D3 P0 修:以下派生 + 4 hooks 原宣告在 view/readonly/single/pill 四個 early return\n // 之後 — 同一 mounted instance 的 resolvedMode 於 edit↔view/disabled 切換(<Field mode>/<Field\n // disabled> cascade 正是 runtime 翻轉機制)時 hook 數 7→11 變動 → React #310 crash(與 Combobox\n // beta.76 / LinkInput 2026-07-04 同家族)。全部 hoist 到任何 early return 之前(對齊 select.tsx\n // 「所有 hooks 必在 early return 前 call」canonical);effect 加 mode/stack guard,邏輯不變。\n const selectedNames: string[] = !value\n ? []\n : Array.isArray(value)\n ? value.map(v => resolvePerson(v).name)\n : [resolvePerson(value).name]\n\n // SSOT visible count compute via formula primitive + ResizeObserver(multi stack mode 專用)\n const stackContainerRef = React.useRef<HTMLDivElement | null>(null)\n const [stackVisibleCount, setStackVisibleCount] = React.useState<number | undefined>(undefined)\n React.useLayoutEffect(() => {\n if (resolvedMode !== 'edit' || !isMulti || effectiveMultiDisplay !== 'stack' || selectedNames.length <= 1) {\n setStackVisibleCount(undefined); return\n }\n const root = stackContainerRef.current\n if (!root) return\n // 2026-05-15 ROOT CAUSE FIX(user 抓「之前說的問題都還是存在」):\n // stackContainerRef 透過 mergedStackRef 接 Combobox forwarded ref → root **就是** [role=combobox]\n // div 自己。原 `root.querySelector('[role=combobox]')` 不找 self 永遠 null → trigger=null →\n // tagArea=null → available=0 → setStackVisibleCount(0) → 整 stack 全 overflow → fallback 到\n // Combobox DOM-based useOverflowCount(非 deterministic 那個算法)。修:用 root 自己當 trigger,\n // 從 root 內找 tagArea(flex-1 min-w-0 div)。\n const calc = () => {\n // 2026-08-05 native-parity fix(touch 實圖抓「多人只剩 +N」):NativeCombobox root 無\n // role=\"combobox\"(a11y 在隱藏 <select> 上)→ 原查法 trigger=null → available=0 → 全\n // overflow。雙分支通用:查無 combobox role 時 root 自身就是 trigger(native __triggerRef\n // 即 field wrapper root),tagArea(flex-1 min-w-0)兩分支同構。\n const trigger = root.matches('[role=\"combobox\"]') ? root : (root.querySelector<HTMLElement>('[role=\"combobox\"]') ?? root)\n const tagArea = trigger?.querySelector<HTMLElement>('div[class*=\"flex-1\"][class*=\"min-w-0\"]')\n const available = tagArea?.clientWidth ?? trigger?.clientWidth ?? 0\n const visible = getAvatarStackVisibleCount({\n availablePx: available,\n total: selectedNames.length,\n avatarPx: AVATAR_STACK_AVATAR_PX[size],\n overflowChipPx: AVATAR_STACK_OVERFLOW_CHIP_PX[size],\n })\n setStackVisibleCount(visible)\n }\n calc()\n const ro = new ResizeObserver(calc)\n ro.observe(root)\n return () => ro.disconnect()\n }, [resolvedMode, isMulti, effectiveMultiDisplay, selectedNames.length, size])\n // Merge ref:forward to parent + capture for ResizeObserver\n const mergedStackRef = React.useCallback((el: HTMLDivElement | null) => {\n stackContainerRef.current = el\n if (typeof ref === 'function') ref(el)\n else if (ref) (ref as React.MutableRefObject<HTMLDivElement | null>).current = el\n }, [ref])\n\n // ── mode='view' ────────────────────────────────────────────────────────\n // Default(showDisplayEndIcon=false):裸 PersonDisplay / MultiPersonDisplay — backward compat。\n // Opt-in(showDisplayEndIcon=true,2026-05-08 D-path):Field naked wrapper + ItemSuffix ChevronDown,\n // 與 edit (Select / Combobox wrapped) 同 DOM 結構消除 cell view↔edit 像素偏移。\n if (resolvedMode === 'view') {\n if (!showDisplayEndIcon) {\n // 2026-07-17 Dim 9 修:裸顯示分支原直接 return PersonDisplay/MultiPersonDisplay/span,\n // 遺失 ref / className / DOM-safe rest props(違 spec「forwardRef + 單一 root」root props 契約,\n // 與 readonly/disabled 分支不對稱)。補穩定 forwarding root:`w-full min-w-0` 保留\n // PersonDisplay 既有 w-full truncate 寬度約束鏈(單人 fill + 多人 stack 左靠不變),\n // 對齊 switch.tsx:209 view 分支轉發先例。\n return (\n <div ref={ref} className={cn('w-full min-w-0', className)} aria-label={ariaLabel} {...rest}>\n {isEmpty\n ? <span className={fieldEmptyColorClass(resolvedMode)}>{emptyDisplay}</span>\n : isMulti\n ? <MultiPersonDisplay value={value as PersonValue[]} size={size} measured />\n : <PersonDisplay value={value as PersonValue} size={size} />}\n </div>\n )\n }\n // 2026-05-18 改 import ICON_SIZE SSOT(per user『做完』approval,消除 M17 違反 7+ 重複 ternary)\n const iconSize = ICON_SIZE[size as 'sm' | 'md' | 'lg']\n return (\n <div\n // 2026-07-17 Dim 9 修:D-path view root 補 ref / aria-label / {...rest} 轉發(原只套 className,\n // 遺失 root props;對齊 readonly/disabled 分支與 spec root props 契約)。\n ref={ref}\n className={cn(fieldWrapperStyles({ mode: 'view', variant: resolvedVariant, width, size }), className)}\n data-field-mode=\"view\"\n aria-label={ariaLabel}\n {...rest}\n >\n <span className={cn('flex-1 min-w-0 inline-flex items-center', nakedCellRowModeAlign)}>\n {isEmpty\n ? <span className={fieldEmptyColorClass(resolvedMode)}>{emptyDisplay}</span>\n : isMulti\n ? <MultiPersonDisplay value={value as PersonValue[]} size={size} measured />\n : <PersonDisplay value={value as PersonValue} size={size} />}\n </span>\n <ItemSuffix className=\"pointer-events-none\">\n <ChevronDown size={iconSize} className=\"shrink-0 text-fg-muted\" aria-hidden />\n </ItemSuffix>\n </div>\n )\n }\n\n // ── readonly / disabled — Field wrapper chrome,Avatar 視覺保留 ───────────\n if (resolvedMode !== 'edit') {\n return (\n <div\n ref={ref}\n className={cn(fieldWrapperStyles({ mode: resolvedMode, variant: resolvedVariant, width, size }), className)}\n data-field-mode={resolvedMode}\n aria-disabled={resolvedMode === 'disabled' ? true : undefined}\n aria-label={ariaLabel}\n {...rest}\n >\n <span className={cn('flex-1 min-w-0 inline-flex items-center', nakedCellRowModeAlign, resolvedMode === 'disabled' && 'text-fg-disabled')}>\n {/* 2026-07-14 deep-audit 修:disabled 傳入 → 抑制 Avatar ProfileCard hoverCard + avatar dim\n (spec「disabled:灰化整個 field,不可互動」原被 hoverCard falsify);readonly 保留 hoverCard(純展示仍可查閱) */}\n {isEmpty\n ? <span className={fieldEmptyColorClass(resolvedMode)}>{emptyDisplay}</span>\n : isMulti\n ? <MultiPersonDisplay value={value as PersonValue[]} size={size} measured disabled={resolvedMode === 'disabled'} />\n : <PersonDisplay value={value as PersonValue} size={size} disabled={resolvedMode === 'disabled'} />}\n </span>\n {/* 2026-06-26 類型身份 indicator:edit 顯示 / readonly 不顯示 / disabled 保留(fg-disabled,對齊原生 <select disabled>);naked cell 依 showDisplayEndIcon */}\n {(resolvedVariant === 'naked' ? showDisplayEndIcon : resolvedMode === 'disabled') && (\n <ItemSuffix className=\"pointer-events-none\">\n <ChevronDown size={ICON_SIZE[size as 'sm' | 'md' | 'lg']} className={cn('shrink-0', resolvedMode === 'disabled' ? 'text-fg-disabled' : 'text-fg-muted')} aria-hidden />\n </ItemSuffix>\n )}\n </div>\n )\n }\n\n // ── edit mode ─────────────────────────────────────────────────────────────\n // (selectedNames 派生已 hoist 至檔上方 hooks 區,見 D3 P0 修註解)\n\n // ── single mode → wraps Select ────────────────────────────────────────────\n if (!isMulti) {\n // clearable X 清空時 Select emit onChange('') — 空字串必映射為空陣列,\n // 不可進 findPerson(fallback 會回 '' 字串 = 假人員)。清除後回 placeholder 態(select.spec.md「Clearable」)。\n const handleSingleChange = (name: string) => onChange?.(name ? [findPerson(people, name)] : [])\n return (\n <Select\n ref={ref as React.Ref<HTMLDivElement>}\n // 2026-07-14 deep-audit fix:width 補 forward(原漏傳 → single edit path 忽略公開 width prop;\n // multi pill / stack 皆有傳,對齊 M30 wrapper forward 全 primitive surface)\n width={width}\n size={size}\n variant={resolvedVariant}\n options={people.map(personToSelectOption)}\n value={selectedNames[0] ?? null}\n onChange={handleSingleChange}\n searchable\n clearable={clearable}\n placeholder={placeholder}\n // 2026-05-12 Issue 4:placeholder = trigger empty。2026-07-04 Q4:emptyText 走 Select →\n // SelectMenu 接線(search-empty 語意,與 trigger-empty 分離)。\n emptyText={emptyText}\n defaultOpen={defaultOpen}\n onOpenChange={onOpenChange}\n className={className}\n aria-label={ariaLabel}\n selectedItemRenderer={(opt) => <PersonDisplay value={findPerson(people, opt.value)} size={size} />}\n // **codex P2 forward**:Select 原生屬性走 allowlist(`Pick<SelectHTMLAttributes>`,\n // 2026-07-14 API 策展 D,見 select.tsx SelectProps docblock),event handler element\n // 型別跟 PeoplePicker `HTMLAttributes<HTMLDivElement>` 不一致(`onCopy` / `onChange` 等)。\n // Runtime spread 等效 — DOM 收到 attrs 不挑剔(非 allowlist attrs 落 CustomSelect rest,\n // 續 spread 到 trigger div,runtime 行為不變)。\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n // any-allow: rest 含 `onChange: FormEventHandler` 跟 Select onChange signature 衝突 — DOM runtime spread 安全(per codex P2 forward)\n {...(rest as any)}\n />\n )\n }\n\n // ── multi 'pill' → wraps Combobox(對齊 GitHub Reviewers / Combobox idiom)────\n // 觸控多選亦走此分支(effectiveMultiDisplay,見上方 isTouch 註解)。\n if (effectiveMultiDisplay === 'pill') {\n const handleMultiChange = (next: string[]) => {\n onChange?.(next.map(name => findPerson(people, name)))\n }\n return (\n <Combobox\n width={width}\n ref={ref as React.Ref<HTMLDivElement>}\n size={size}\n variant={resolvedVariant}\n options={people.map(personToSelectOption)}\n value={selectedNames}\n onChange={handleMultiChange}\n searchable\n // 2026-07-14 deep-audit fix:searchIn 補 forward(原漏傳 → pill 模式永遠 Combobox default 'menu',\n // documented multi opt-in `searchIn='trigger'` silently 無效;stack branch 已傳,對齊 M30 forward 全 surface)\n searchIn={searchIn}\n clearable={clearable}\n searchPlaceholder={searchPlaceholder}\n searchAriaLabel={searchAriaLabel}\n // 2026-05-12 Stream C Issue 4(codex Q3):placeholder = trigger empty hint('請選擇人員')\n // — semantic clean separation。2026-07-04 Q4 拍板完成 1-cycle 承諾:emptyPlaceholder forward\n // 移除,emptyText 改傳 Combobox → SelectMenu emptyText(search-empty 真住所,菜單空狀態)。\n placeholder={placeholder}\n emptyText={emptyText}\n wrap={pillWrap}\n defaultOpen={defaultOpen}\n onOpenChange={onOpenChange}\n className={className}\n aria-label={ariaLabel}\n // codex P2 forward(see Select branch comment for type-cast rationale)\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n // any-allow: rest 含 `onChange: FormEventHandler` 跟 Combobox onChange signature 衝突 — DOM runtime spread 安全(per codex P2 forward)\n {...(rest as any)}\n // **Tag SSOT canonical**:用 `avatar` prop(不塞 children),Tag 內部統一\n // wrap 進 16×16 圓形 mask container(per Tag tsx line 175)。\n tagRenderer={(item, onRemove) => {\n const p = resolvePerson(findPerson(people, item.value))\n return (\n <Tag\n key={item.value}\n size={size}\n color=\"neutral\"\n // 2026-05-18 7B' fix(per user 拍板「執行」+ Codex Round 3 共識,paired with Combobox L286):\n // 拿掉 `unbounded` 對齊 Tag canonical max-w-40 cap + 內建 ellipsis。PeoplePicker pill 走\n // Combobox tagRenderer slot,SSOT = Tag primitive 視覺(per codex Round 3 verdict)。\n // 人名 99% < 25 chars 不觸發 cap;極端長名(複數姓 + middle name)觸發 ellipsis 是合理 UX。\n avatar={pillShowAvatar\n ? <Avatar src={p.avatarUrl} alt={p.name} size={16} hoverCard={buildPersonProfileCard(p)} />\n : undefined}\n onRemove={onRemove}\n >\n {p.name}\n </Tag>\n )\n }}\n />\n )\n }\n\n // ── multi 'stack' (default) → wraps Combobox 跟 pill mode 同 SSOT,差別在 tagRenderer 視覺。\n //\n // **2026-05-15 Bug 3 fix(Claude+Codex Step 5 比稿 consensus)**:visible count 走 shared\n // `avatar-stack-overflow` primitive deterministic formula(取代 Combobox DOM offsetWidth-based\n // useOverflowCount + 60px fallback 不 deterministic),pass override 給 Combobox bypass internal\n // measurement。`MultiPersonDisplay`(view path)同 primitive,view + edit 結果一致。\n // 對齊 user verbatim SSOT「同 cell width 同 overflow 判斷」+ codex Q3 consensus shared primitive。\n const handleMultiChange = (next: string[]) => {\n onChange?.(next.map(name => findPerson(people, name)))\n }\n // ── edit mode ─────────────────────────────────────────────────────────────\n // (selectedNames 派生已 hoist 至檔上方 hooks 區,見 D3 P0 修註解)\n\n return (\n <Combobox\n width={width}\n ref={mergedStackRef}\n size={size}\n variant={resolvedVariant}\n options={people.map(personToSelectOption)}\n value={selectedNames}\n onChange={handleMultiChange}\n searchable\n searchIn={searchIn}\n clearable={clearable}\n searchPlaceholder={searchPlaceholder}\n searchAriaLabel={searchAriaLabel}\n // 2026-05-12 Stream C Issue 4(codex Q3):placeholder = trigger empty('請選擇人員');emptyText = search-empty(僅 backward-compat forward 1 cycle)\n placeholder={placeholder}\n emptyText={emptyText}\n wrap={false}\n defaultOpen={defaultOpen}\n onOpenChange={onOpenChange}\n // 2026-05-13 (a) fix(user 拍 Path a + Layer A density-drift root-cause):\n // 撤掉 `tagAreaPaddingLeftPx={8}` magic — Combobox `tagPadding[size]` 是 density-dependent\n // calc 公式(`(field-height - icon-size) / 2`),只在 md size + default density 才 = 4px;\n // 其他 size/density 漂 6px / 8px → 4+8=12 spec 公式不成立。\n // (a) fix:form context + 有 tag → 改 inject `!px-[var(--field-px)]`(固定 12px)直接 override `tagPadding[size]`,\n // 達成 GitHub PeoplePicker fixed 12px inset(對齊 cell context 同 13px from cell.left 含 1px border)。\n // - form + 有 tag → `!px-[var(--field-px)]`(12px 固定 inset)+ tagAreaPaddingLeftPx undefined → field.padL=12 ✓\n // - table-cell + 有 tag → naked variant `!px-[var(--table-cell-px)]` 已是 12px,不 inject ✓\n // - isEmpty → 不 inject,走 Combobox 預設文字 inset(`tagPadding[size]` 公式自然 vertical center)\n className={cn(className, !isEmpty && surface === 'form' && '!px-[var(--field-px)]')}\n aria-label={ariaLabel}\n // 2026-05-15 Bug 1 fix(Claude+Codex Step 5 比稿 consensus,user verbatim「就 A」):per-length 動態\n // wrapper class — length=1 降階單人視覺需要 width constraint chain(`flex-1 min-w-0 overflow-hidden`),\n // length>=2 stack 視覺保留 overlap(`-ml-0.5 first:ml-0 relative inline-flex group/avatar`)。對齊\n // spec.md §C row 1(length=1 = avatar+人名+ellipsis)+ §D row 1(length>=2 = stack overlap)。\n // 真根因:Combobox `OverflowTagList` 把所有 tagRenderer 結果包 `shrink-0`,加 `inline-flex` 後\n // wrapper 變 intrinsic content-width → PersonDisplay `w-full` resolves to intrinsic → truncate 無效。\n // 修法:length=1 wrapper 改 `flex-1 min-w-0 overflow-hidden` 提供 width constraint 給 PersonDisplay。\n // SSOT helper `getPeoplePickerTagWrapperClass(count)` 集中,future 改 wrapper 行為改一處。\n tagWrapperClassName={getPeoplePickerTagWrapperClass(selectedNames.length)}\n // 2026-05-16 真 root cause fix:overflow chip wrapper 套同 `-ml-0.5` 讓 chip 物理上\n // 跟 avatar 同 slot(等寬同 step,non-overlapping 多 24px 區塊不再 saw)。對齊 user\n // 「avatars 和 +N 都是同尺寸圓形,空間最多容固定數量圓形」物理模型 directive +\n // MUI AvatarGroup / Primer AvatarStack 共識(`AvatarGroup.js` L54-59 same negative margin)。\n overflowWrapperClassName=\"-ml-0.5 first:ml-0 relative inline-flex\"\n tagAreaGapPx={0}\n tagAreaPaddingLeftPx={undefined}\n // 2026-05-12 Round 7 fix(user 抓 image 2「+N tag 應該圓形不是矩形」+ 對齊 GitHub picker idiom):\n // Combobox default `overflowShape='tag'`(矩形 chip,文字 Combobox 慣例);PeoplePicker stack\n // mode pass `'circle'`(圓形 avatar-shape,跟 avatar 一樣大)。對齊 MultiPersonDisplay readonly path\n // 既有 OverflowIndicator default 'circle' SSOT。\n overflowShape=\"circle\"\n // 2026-05-15 Bug 3 fix:formula-based visible count override(避免 Combobox DOM measurement +\n // 60px fallback 不 deterministic)。SSOT in `./avatar-stack-overflow.ts`,view + edit 共用。\n visibleCountOverride={stackVisibleCount}\n // 2026-05-14 I4 fix(per codex+Layer A 共識):hidden items 在 `+N` overflow popover 顯\n // Tag with avatar(對齊 view path MultiPersonDisplay popover SSOT,user 抓 display vs edit\n // overflow 視覺不一致)。\n renderHiddenTag={(item) => {\n const p = resolvePerson(findPerson(people, item.value))\n return (\n <Tag\n key={item.value}\n color=\"neutral\"\n size=\"sm\"\n avatar={\n <Avatar\n src={p.avatarUrl}\n alt={p.name}\n size={16}\n hoverCard={buildPersonProfileCard(p)}\n />\n }\n onRemove={() => {\n onChange?.(selectedNames.filter(n => n !== item.value).map(n => findPerson(people, n)))\n }}\n >\n {p.name}\n </Tag>\n )\n }}\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n // any-allow: rest 含 `onChange: FormEventHandler` 跟 Combobox onChange signature 衝突 — DOM runtime spread 安全(per codex P2 forward)\n {...(rest as any)}\n tagRenderer={(item, onRemove) => {\n const p = resolvePerson(findPerson(people, item.value))\n // 2026-05-12 Q2 fix(user 拍板「multi 只選 1 人時 trigger = avatar + name,跟 single mode 同」):\n // selectedNames.length === 1 → PersonDisplay(avatar + name)代替 PersonAvatarTag(avatar only)。\n // SSOT 對齊 PeoplePicker single mode line 201 selectedItemRenderer。多選 1 人時視覺等同單選,\n // 只在 length > 1 才走 stack(各 avatar 純 chip)。多選 + inline 搜尋場景拿掉 name 改 cursor\n // 走 `searchIn='trigger'` opt-in(2026-05-12 規則 3 ship,已轉傳 Combobox;default 'menu' 走 panel-top search)。\n if (selectedNames.length === 1) {\n return <PersonDisplay key={item.value} value={p} size={size} onRemove={onRemove} />\n }\n return (\n <PersonAvatarTag\n key={item.value}\n person={p}\n size={size}\n onRemove={onRemove}\n />\n )\n }}\n />\n )\n})\nPeoplePicker.displayName = 'PeoplePicker'\n\n// Story auto-compile metadata\nexport const peoplePickerMeta = {\n component: 'PeoplePicker',\n family: 4,\n variants: {},\n sizes: {},\n // 'active' 移除 — trigger 走 Field chrome、menu 走 MenuItem,皆無自有按壓視覺(2026-07-07 詞彙統一 DS-wide 按壓訊號盤點:檔內 0 active: utility / 0 *-active token)。\n states: ['default', 'hover', 'focus-visible', 'disabled'],\n tokens: {\n bg: [],\n fg: ['text-fg-disabled', 'text-fg-muted'],\n ring: [],\n },\n} as const\n\nexport { PeoplePicker }\n// 2026-07-10 C14 export 缺口修(R2 同類:定義存在但無 export 面,consumer 想守 PersonValue 鏈\n// import 不到 → 被逼手刻)。person-display 表面隨 PeoplePicker subpath + root barrel 一併公開。\nexport {\n PersonDisplay,\n MultiPersonDisplay,\n PersonAvatarTag,\n buildPersonProfileCard,\n resolvePerson,\n type PersonValue,\n type PersonData,\n} from './person-display'\n"],"names":["SelectPublic","ComboboxPublic","PeoplePicker","handleMultiChange"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"people-picker.js","sources":["../../../src/components/PeoplePicker/people-picker.tsx"],"sourcesContent":["// @benchmark-unverified-blanket: file-level retraction per M22 (d) — claims herein not individually URL-cited; treat as unverified visual/usage rumor unless retrofit per-claim. Hook escape preserved.\n// @placeholder-vocabulary-allow: 2026-07-04 Q4 完成 field-controls.spec.md 共享 contract b — emptyPlaceholder forward 已移除,emptyText 直達 Combobox → SelectMenu emptyText(search-empty 真住所);placeholder 為 trigger empty SSOT。\n// @cell-metric-escape-allow: comment describes RETIRED `tagAreaPaddingLeftPx={8}` magic — current code is surface-guarded (`surface === 'form'` only injects `!px-[var(--field-px)]`; table-cell context untouched, lets naked `!px-[var(--table-cell-px)]` SSOT take over). Hook regex grep'd the comment word, not the live code path. Per (a) fix 2026-05-13 user-approved Path a.\nimport * as React from 'react'\nimport { ChevronDown } from 'lucide-react'\nimport { cn } from '@/lib/utils'\nimport type { FieldMode, FieldVariant, FieldVariantInternal, FieldWidth } from '@/design-system/components/Field/field-types'\nimport { fieldWrapperStyles, nakedCellRowModeAlign } from '@/design-system/components/Field/field-wrapper'\nimport { useFieldEmptyDisplay, fieldEmptyColorClass } from '@/design-system/components/Field/field-context'\nimport { ItemSuffix } from '@/design-system/patterns/element-anatomy/item-anatomy'\nimport { useFieldSurface, useResolvedFieldSize, useResolvedFieldDisabled, useResolvedFieldMode, useResolvedFieldVariant } from '@/design-system/components/Field/field-context'\nimport { Avatar } from '@/design-system/components/Avatar/avatar'\nimport { Tag } from '@/design-system/components/Tag/tag'\nimport { Select as SelectPublic } from '@/design-system/components/Select/select'\nimport { Combobox as ComboboxPublic } from '@/design-system/components/Combobox/combobox'\nimport { PersonDisplay, MultiPersonDisplay, PersonAvatarTag, buildPersonProfileCard, resolvePerson, type PersonValue } from './person-display'\nimport {\n getAvatarStackVisibleCount,\n AVATAR_STACK_AVATAR_PX,\n AVATAR_STACK_OVERFLOW_CHIP_PX,\n} from './avatar-stack-overflow'\nimport type { WithFieldVariantInternal } from '@/design-system/components/Field/field-types'\n\n// ── @internal naked variant forward 通道(2026-07-14 API 策展 E)──────────────────────────\n// naked 已從公開 FieldVariant 拆出(field-types.ts FieldVariantInternal)。PeoplePicker 是\n// Select / Combobox 的 wrapper:cell-registry 傳入的 naked 需原樣 forward 給 primitive\n// (M30 wrapper forward 全 primitive surface)。型別層 widen,純型別、零 runtime。\nconst Select = SelectPublic as WithFieldVariantInternal<typeof SelectPublic>\nconst Combobox = ComboboxPublic as WithFieldVariantInternal<typeof ComboboxPublic>\n// Pure helpers extracted to sibling for file-size budget(2026-05-18,P1 ≤ 500 lines)。\n// 不消費 component closure 的純 constant / 純 mapping function 全部搬走,主檔保留 SSOT-bearing\n// render logic(消費 Combobox / Select / state 等 closure 的部分)。\n// SSOT primitive re-export(backward-compat 對外 import 路徑保持 `./people-picker`)。\nimport {\n PEOPLE_PICKER_LENGTH1_WRAPPER_CLASS,\n getPeoplePickerTagWrapperClass,\n personToSelectOption,\n findPerson,\n} from './people-picker-helpers'\nimport { ICON_SIZE } from '@/design-system/tokens/uiSize/icon-size'\nimport { useIsTouchDevice } from '@/design-system/hooks/use-is-touch-device'\nexport { PEOPLE_PICKER_LENGTH1_WRAPPER_CLASS, getPeoplePickerTagWrapperClass }\n\n// ── PeoplePicker ────────────────────────────────────────────────────────────\n// **2026-05-07 v15.6 SSOT 重構 v2**:\n//\n// - **single mode** wraps `<Select searchable selectedItemRenderer>`\n// - **multi mode** 兩種 displayMode(consumer 自選),**皆 wrap `<Combobox>`**(同 SSOT,\n// 差別在 tagRenderer 視覺):\n// - **'stack'**(default,baseline 既有視覺)— Avatar 疊合 + `+N` overflow indicator,\n// 不可 wrap。tagRenderer 渲染 avatar stack(visible count 走 shared `avatar-stack-overflow`\n// primitive deterministic formula,2026-05-15 Bug 3 fix;view 路徑 `<MultiPersonDisplay>` 同 primitive)。\n// 對齊 Notion / Linear / Atlassian / Slack 多人 quick-glance idiom。\n// - **'pill'**(opt-in)— 每人 Tag pill,可 wrap。Wrap `<Combobox tagRenderer>`,\n// tagRenderer 用 Tag 元件 `avatar` prop SSOT(不塞 children)。\n// `pillShowAvatar` 控 pill 內是否顯 avatar prefix(default true,false → 純文字 pill)。\n// 對齊 GitHub Reviewers / Combobox tag-input idiom。\n\n// **codex P2 fix(2026-05-07 v15.10)**:`extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onChange'>`\n// 讓 consumer 可傳 `id` / `data-testid` / `onBlur` / `onFocus` / `aria-*` 等 HTML root props,\n// component 內部 `...rest` forward 到 trigger 容器(對齊 DS 既有 Combobox / Select 慣例)。\n// `onChange` 衝突走 Omit(本 component 用 PersonValue[] custom signature)。\n// 2026-07-17 Dim 26 修:`defaultValue` 一併 Omit — 本元件 controlled-only(spec「Controlled-only\n// rationale」),React.HTMLAttributes 內建的 `defaultValue` 若不 Omit 會讓 consumer 傳入假\n// uncontrolled API,runtime 經 `...rest` 靜默轉送 trigger div 後無效。Omit 本身即 type-level\n// regression(consumer 傳 defaultValue 直接 TS 編譯錯)。DS 先例 steps.tsx:166 同款 Omit。\nexport interface PeoplePickerProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onChange' | 'defaultValue'> {\n /** Field mode(edit / view / readonly / disabled),默認 inherit Field context 或 'edit' */\n mode?: FieldMode\n /** Field chrome variant(對齊 Select / Combobox)*/\n variant?: FieldVariant\n /** 寬度軸 — `fill` 填滿容器(default)/ `hug` 依內容收縮(value 寬 + slot 寬 + gap + 內 padding);\n * chrome 與互動不變。SSOT → field-controls.spec.md「寬度軸(width: fill / hug)」。 */\n width?: FieldWidth\n size?: 'sm' | 'md' | 'lg'\n /** 當前已選的人(單選 PersonValue,多選 PersonValue[])*/\n value?: PersonValue | PersonValue[] | null\n /** 值變更 callback(永遠 emit array — single mode 取 [0] 即 single value)*/\n onChange?: (value: PersonValue[]) => void\n /** 可選人員清單(edit mode 下拉顯示)*/\n people?: PersonValue[]\n /** 2026-05-12 Stream C Issue 4 fix(codex Q3 Cluster C):trigger empty placeholder。\n * Default '請選擇人員'。**禁** 將 `emptyText`(search-empty)當 trigger placeholder 傳。 */\n placeholder?: string\n /** 搜尋框 placeholder — **僅 multi 模式 panel-top search(`searchIn='menu'`,default)生效**。\n * single mode wrap `<Select searchable>` 走 inline-trigger 搜尋,提示取自 `placeholder`\n * (Select 無 searchPlaceholder prop);multi `searchIn='trigger'` inline 搜尋同理走\n * placeholder 規則(spec「Trigger display SSOT canonical table」)。 */\n searchPlaceholder?: string\n /** Multi 模式搜尋框 accessible name。僅轉送 wrapped Combobox。 */\n searchAriaLabel?: string\n /** 搜尋無結果訊息(filtered menu empty)。**僅**用於 SelectMenu `emptyText`(菜單空狀態,\n * 2026-07-04 Q4 接線完成),不轉 trigger placeholder(2026-05-12 Issue 4 semantic fix)。 */\n emptyText?: string\n className?: string\n disabled?: boolean\n /** Initial open state(uncontrolled)*/\n defaultOpen?: boolean\n /** open state 變更 callback */\n onOpenChange?: (open: boolean) => void\n /**\n * Multi mode 顯示樣式(default 'stack')。Single mode 此 prop 忽略。\n * - 'stack' — Avatar 疊合 + `+N`(空間省、不可 wrap;default)\n * - 'pill' — 每人 Tag pill(可 wrap)\n */\n multiDisplay?: 'stack' | 'pill'\n /**\n * `multiDisplay='pill'` 模式下是否顯示 avatar prefix(default true)。\n * 設 false → 純文字 pill,進一步節省空間。對齊 Tag 元件 `avatar` prop SSOT。\n */\n pillShowAvatar?: boolean\n /** Pill 模式下是否允許 wrap(default true)— 對齊 Combobox `wrap` prop */\n pillWrap?: boolean\n /**\n * 搜尋型態(2026-05-12 規則 3 ship,3-mode SSOT 對齊 A1-A5 spec):\n * - `'menu'`(default,backward-compat)— 浮層內搜尋(panel-top search)\n * - `'trigger'`(multi 模式 opt-in)— inline 搜尋(浮層開時 name 拿掉,avatar 後接 input cursor,\n * 類 Combobox inline-trigger idiom)\n * Single mode 永遠 inline-trigger(wrap Select searchable 直接走 inline),此 prop multi 才有意義。\n */\n searchIn?: 'menu' | 'trigger'\n /**\n * 一鍵清空(2026-08-05):X 在 ChevronDown 左,family SSOT → field-controls.spec.md「下拉箭頭」段。\n * 機械轉發 wrapped Select(single)/ Combobox(multi)的 `clearable` — X 渲染與清空行為 SSOT 在基座\n * (SelectClearButton / Combobox clear),本 wrapper 不另 render。何時開的判準繼承 select.spec.md\n * 「Clearable」:「無選擇是有效狀態」(選填欄位 / 可清 filter)才開;必填欄位不開。\n */\n clearable?: boolean\n /**\n * View 態是否渲 ChevronDown + Field naked wrapper(D-path opt-in,2026-05-08)\n * — DataTable cell view↔edit 像素級對齊用。預設 false(裸 PersonDisplay,backward compat)。\n * 設 true 時 view 走 fieldWrapperStyles(naked variant)+ ItemSuffix ChevronDown,\n * 與 edit (Select / Combobox wrapped) 同 DOM 結構,消除 Layer-B padding mismatch。\n */\n showDisplayEndIcon?: boolean\n /** a11y label */\n 'aria-label'?: string\n}\n\nconst PeoplePicker = React.forwardRef<HTMLDivElement, PeoplePickerProps>(function PeoplePicker({\n mode: modeProp,\n variant: variantProp,\n width,\n size: sizeProp,\n value,\n onChange,\n people = [],\n placeholder = '請選擇人員', // i18n-allow: DS default(2026-05-12 Stream C Issue 4)\n searchPlaceholder = '搜尋人員…', // i18n-allow: DS default\n searchAriaLabel = '搜尋人員', // i18n-allow: DS default\n emptyText = '沒有符合的人員', // i18n-allow: DS default — only for SelectMenu noResultsText\n className,\n disabled: disabledProp,\n defaultOpen = false,\n onOpenChange,\n multiDisplay = 'stack',\n pillShowAvatar = true,\n pillWrap = true,\n searchIn = 'menu',\n clearable = false,\n showDisplayEndIcon = false,\n 'aria-label': ariaLabel,\n ...rest\n}, ref) {\n const surface = useFieldSurface()\n const emptyDisplay = useFieldEmptyDisplay()\n const size = useResolvedFieldSize(sizeProp) // B 組 cascade fix\n const disabled = useResolvedFieldDisabled(disabledProp)\n // 2026-06-08 SSOT:mode/disabled/variant 統一經 helper;修 <Field disabled> 漏 cascade(原只讀 fieldCtx.mode)\n const resolvedMode: FieldMode = useResolvedFieldMode({ mode: modeProp, disabled })\n const resolvedVariant: FieldVariantInternal = useResolvedFieldVariant(variantProp)\n const isMulti = Array.isArray(value)\n const isEmpty = !value || (isMulti && value.length === 0)\n // 觸控多選一律走既有 pill 型態(2026-08-05 user 拍板:「手機上自動把多選 people picker 改成\n // combobox 的型態,所有樣式互動都是 combobox 的 SSOT,不要再造輪子」)。理由:stack 的移除鈕\n // 是 hover-reveal,觸控沒有 hover;恆顯 X 又太亂(user 否決)。pill 是 DS 既有 canonical\n // (`multiDisplay='pill'` + Tag `avatar` prop),每顆 pill 自帶 X = Combobox tag 移除 SSOT。\n // 桌機(fine pointer)完全不受影響:仍是 avatar stack + 圓形 +N + hover X。\n // consumer 顯式傳 'pill' 時本行為 no-op(同值)。hook 必在任何 early return 前呼叫(React #310)。\n // **只在 form surface 降階**(2026-08-05):table-cell / inline-edit / toolbar 維持 stack —\n // cell 的 view↔edit 像素對齊與固定列高契約不得被 pill 的 wrap 破壞(user:「尤其是 table\n // 的部分」)。cell 觸控編輯本就走原生 picker,移除成員在 picker 內完成。\n const isTouch = useIsTouchDevice()\n const effectiveMultiDisplay = isTouch && isMulti && surface === 'form' ? 'pill' : multiDisplay\n\n // 2026-07-05 D3 P0 修:以下派生 + 4 hooks 原宣告在 view/readonly/single/pill 四個 early return\n // 之後 — 同一 mounted instance 的 resolvedMode 於 edit↔view/disabled 切換(<Field mode>/<Field\n // disabled> cascade 正是 runtime 翻轉機制)時 hook 數 7→11 變動 → React #310 crash(與 Combobox\n // beta.76 / LinkInput 2026-07-04 同家族)。全部 hoist 到任何 early return 之前(對齊 select.tsx\n // 「所有 hooks 必在 early return 前 call」canonical);effect 加 mode/stack guard,邏輯不變。\n const selectedNames: string[] = !value\n ? []\n : Array.isArray(value)\n ? value.map(v => resolvePerson(v).name)\n : [resolvePerson(value).name]\n\n // SSOT visible count compute via formula primitive + ResizeObserver(multi stack mode 專用)\n const stackContainerRef = React.useRef<HTMLDivElement | null>(null)\n const [stackVisibleCount, setStackVisibleCount] = React.useState<number | undefined>(undefined)\n React.useLayoutEffect(() => {\n if (resolvedMode !== 'edit' || !isMulti || effectiveMultiDisplay !== 'stack' || selectedNames.length <= 1) {\n setStackVisibleCount(undefined); return\n }\n const root = stackContainerRef.current\n if (!root) return\n // 2026-05-15 ROOT CAUSE FIX(user 抓「之前說的問題都還是存在」):\n // stackContainerRef 透過 mergedStackRef 接 Combobox forwarded ref → root **就是** [role=combobox]\n // div 自己。原 `root.querySelector('[role=combobox]')` 不找 self 永遠 null → trigger=null →\n // tagArea=null → available=0 → setStackVisibleCount(0) → 整 stack 全 overflow → fallback 到\n // Combobox DOM-based useOverflowCount(非 deterministic 那個算法)。修:用 root 自己當 trigger,\n // 從 root 內找 tagArea(flex-1 min-w-0 div)。\n const calc = () => {\n // 2026-08-05 native-parity fix(touch 實圖抓「多人只剩 +N」):NativeCombobox root 無\n // role=\"combobox\"(a11y 在隱藏 <select> 上)→ 原查法 trigger=null → available=0 → 全\n // overflow。雙分支通用:查無 combobox role 時 root 自身就是 trigger(native __triggerRef\n // 即 field wrapper root),tagArea(flex-1 min-w-0)兩分支同構。\n const trigger = root.matches('[role=\"combobox\"]') ? root : (root.querySelector<HTMLElement>('[role=\"combobox\"]') ?? root)\n const tagArea = trigger?.querySelector<HTMLElement>('div[class*=\"flex-1\"][class*=\"min-w-0\"]')\n const available = tagArea?.clientWidth ?? trigger?.clientWidth ?? 0\n const visible = getAvatarStackVisibleCount({\n availablePx: available,\n total: selectedNames.length,\n avatarPx: AVATAR_STACK_AVATAR_PX[size],\n overflowChipPx: AVATAR_STACK_OVERFLOW_CHIP_PX[size],\n })\n setStackVisibleCount(visible)\n }\n calc()\n const ro = new ResizeObserver(calc)\n ro.observe(root)\n return () => ro.disconnect()\n }, [resolvedMode, isMulti, effectiveMultiDisplay, selectedNames.length, size])\n // Merge ref:forward to parent + capture for ResizeObserver\n const mergedStackRef = React.useCallback((el: HTMLDivElement | null) => {\n stackContainerRef.current = el\n if (typeof ref === 'function') ref(el)\n else if (ref) (ref as React.MutableRefObject<HTMLDivElement | null>).current = el\n }, [ref])\n\n // ── mode='view' ────────────────────────────────────────────────────────\n // Default(showDisplayEndIcon=false):裸 PersonDisplay / MultiPersonDisplay — backward compat。\n // Opt-in(showDisplayEndIcon=true,2026-05-08 D-path):Field naked wrapper + ItemSuffix ChevronDown,\n // 與 edit (Select / Combobox wrapped) 同 DOM 結構消除 cell view↔edit 像素偏移。\n if (resolvedMode === 'view') {\n if (!showDisplayEndIcon) {\n // 2026-07-17 Dim 9 修:裸顯示分支原直接 return PersonDisplay/MultiPersonDisplay/span,\n // 遺失 ref / className / DOM-safe rest props(違 spec「forwardRef + 單一 root」root props 契約,\n // 與 readonly/disabled 分支不對稱)。補穩定 forwarding root:`w-full min-w-0` 保留\n // PersonDisplay 既有 w-full truncate 寬度約束鏈(單人 fill + 多人 stack 左靠不變),\n // 對齊 switch.tsx:209 view 分支轉發先例。\n // 2026-08-20 修 beta.100 迴歸(user 拍板):root 必須是 `flex` 彈性容器,不可為純 block ——\n // block 會把 MultiPersonDisplay 的 inline-flex root 放進文字基線行盒,字型下伸空間(~6.5px)\n // 把頭像串推向行盒頂 → DataTable cell 內視覺偏上 6.4px。flex 容器無行盒語意;\n // 單人(flex root)/空值(span)子項幾何不變(2026-08-20 線上注入實證 7.5/7.5 置中)。\n return (\n <div ref={ref} className={cn('flex w-full min-w-0', className)} aria-label={ariaLabel} {...rest}>\n {isEmpty\n ? <span className={fieldEmptyColorClass(resolvedMode)}>{emptyDisplay}</span>\n : isMulti\n ? <MultiPersonDisplay value={value as PersonValue[]} size={size} measured />\n : <PersonDisplay value={value as PersonValue} size={size} />}\n </div>\n )\n }\n // 2026-05-18 改 import ICON_SIZE SSOT(per user『做完』approval,消除 M17 違反 7+ 重複 ternary)\n const iconSize = ICON_SIZE[size as 'sm' | 'md' | 'lg']\n return (\n <div\n // 2026-07-17 Dim 9 修:D-path view root 補 ref / aria-label / {...rest} 轉發(原只套 className,\n // 遺失 root props;對齊 readonly/disabled 分支與 spec root props 契約)。\n ref={ref}\n className={cn(fieldWrapperStyles({ mode: 'view', variant: resolvedVariant, width, size }), className)}\n data-field-mode=\"view\"\n aria-label={ariaLabel}\n {...rest}\n >\n <span className={cn('flex-1 min-w-0 inline-flex items-center', nakedCellRowModeAlign)}>\n {isEmpty\n ? <span className={fieldEmptyColorClass(resolvedMode)}>{emptyDisplay}</span>\n : isMulti\n ? <MultiPersonDisplay value={value as PersonValue[]} size={size} measured />\n : <PersonDisplay value={value as PersonValue} size={size} />}\n </span>\n <ItemSuffix className=\"pointer-events-none\">\n <ChevronDown size={iconSize} className=\"shrink-0 text-fg-muted\" aria-hidden />\n </ItemSuffix>\n </div>\n )\n }\n\n // ── readonly / disabled — Field wrapper chrome,Avatar 視覺保留 ───────────\n if (resolvedMode !== 'edit') {\n return (\n <div\n ref={ref}\n className={cn(fieldWrapperStyles({ mode: resolvedMode, variant: resolvedVariant, width, size }), className)}\n data-field-mode={resolvedMode}\n aria-disabled={resolvedMode === 'disabled' ? true : undefined}\n aria-label={ariaLabel}\n {...rest}\n >\n <span className={cn('flex-1 min-w-0 inline-flex items-center', nakedCellRowModeAlign, resolvedMode === 'disabled' && 'text-fg-disabled')}>\n {/* 2026-07-14 deep-audit 修:disabled 傳入 → 抑制 Avatar ProfileCard hoverCard + avatar dim\n (spec「disabled:灰化整個 field,不可互動」原被 hoverCard falsify);readonly 保留 hoverCard(純展示仍可查閱) */}\n {isEmpty\n ? <span className={fieldEmptyColorClass(resolvedMode)}>{emptyDisplay}</span>\n : isMulti\n ? <MultiPersonDisplay value={value as PersonValue[]} size={size} measured disabled={resolvedMode === 'disabled'} />\n : <PersonDisplay value={value as PersonValue} size={size} disabled={resolvedMode === 'disabled'} />}\n </span>\n {/* 2026-06-26 類型身份 indicator:edit 顯示 / readonly 不顯示 / disabled 保留(fg-disabled,對齊原生 <select disabled>);naked cell 依 showDisplayEndIcon */}\n {(resolvedVariant === 'naked' ? showDisplayEndIcon : resolvedMode === 'disabled') && (\n <ItemSuffix className=\"pointer-events-none\">\n <ChevronDown size={ICON_SIZE[size as 'sm' | 'md' | 'lg']} className={cn('shrink-0', resolvedMode === 'disabled' ? 'text-fg-disabled' : 'text-fg-muted')} aria-hidden />\n </ItemSuffix>\n )}\n </div>\n )\n }\n\n // ── edit mode ─────────────────────────────────────────────────────────────\n // (selectedNames 派生已 hoist 至檔上方 hooks 區,見 D3 P0 修註解)\n\n // ── single mode → wraps Select ────────────────────────────────────────────\n if (!isMulti) {\n // clearable X 清空時 Select emit onChange('') — 空字串必映射為空陣列,\n // 不可進 findPerson(fallback 會回 '' 字串 = 假人員)。清除後回 placeholder 態(select.spec.md「Clearable」)。\n const handleSingleChange = (name: string) => onChange?.(name ? [findPerson(people, name)] : [])\n return (\n <Select\n ref={ref as React.Ref<HTMLDivElement>}\n // 2026-07-14 deep-audit fix:width 補 forward(原漏傳 → single edit path 忽略公開 width prop;\n // multi pill / stack 皆有傳,對齊 M30 wrapper forward 全 primitive surface)\n width={width}\n size={size}\n variant={resolvedVariant}\n options={people.map(personToSelectOption)}\n value={selectedNames[0] ?? null}\n onChange={handleSingleChange}\n searchable\n clearable={clearable}\n placeholder={placeholder}\n // 2026-05-12 Issue 4:placeholder = trigger empty。2026-07-04 Q4:emptyText 走 Select →\n // SelectMenu 接線(search-empty 語意,與 trigger-empty 分離)。\n emptyText={emptyText}\n defaultOpen={defaultOpen}\n onOpenChange={onOpenChange}\n className={className}\n aria-label={ariaLabel}\n selectedItemRenderer={(opt) => <PersonDisplay value={findPerson(people, opt.value)} size={size} />}\n // **codex P2 forward**:Select 原生屬性走 allowlist(`Pick<SelectHTMLAttributes>`,\n // 2026-07-14 API 策展 D,見 select.tsx SelectProps docblock),event handler element\n // 型別跟 PeoplePicker `HTMLAttributes<HTMLDivElement>` 不一致(`onCopy` / `onChange` 等)。\n // Runtime spread 等效 — DOM 收到 attrs 不挑剔(非 allowlist attrs 落 CustomSelect rest,\n // 續 spread 到 trigger div,runtime 行為不變)。\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n // any-allow: rest 含 `onChange: FormEventHandler` 跟 Select onChange signature 衝突 — DOM runtime spread 安全(per codex P2 forward)\n {...(rest as any)}\n />\n )\n }\n\n // ── multi 'pill' → wraps Combobox(對齊 GitHub Reviewers / Combobox idiom)────\n // 觸控多選亦走此分支(effectiveMultiDisplay,見上方 isTouch 註解)。\n if (effectiveMultiDisplay === 'pill') {\n const handleMultiChange = (next: string[]) => {\n onChange?.(next.map(name => findPerson(people, name)))\n }\n return (\n <Combobox\n width={width}\n ref={ref as React.Ref<HTMLDivElement>}\n size={size}\n variant={resolvedVariant}\n options={people.map(personToSelectOption)}\n value={selectedNames}\n onChange={handleMultiChange}\n searchable\n // 2026-07-14 deep-audit fix:searchIn 補 forward(原漏傳 → pill 模式永遠 Combobox default 'menu',\n // documented multi opt-in `searchIn='trigger'` silently 無效;stack branch 已傳,對齊 M30 forward 全 surface)\n searchIn={searchIn}\n clearable={clearable}\n searchPlaceholder={searchPlaceholder}\n searchAriaLabel={searchAriaLabel}\n // 2026-05-12 Stream C Issue 4(codex Q3):placeholder = trigger empty hint('請選擇人員')\n // — semantic clean separation。2026-07-04 Q4 拍板完成 1-cycle 承諾:emptyPlaceholder forward\n // 移除,emptyText 改傳 Combobox → SelectMenu emptyText(search-empty 真住所,菜單空狀態)。\n placeholder={placeholder}\n emptyText={emptyText}\n wrap={pillWrap}\n defaultOpen={defaultOpen}\n onOpenChange={onOpenChange}\n className={className}\n aria-label={ariaLabel}\n // codex P2 forward(see Select branch comment for type-cast rationale)\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n // any-allow: rest 含 `onChange: FormEventHandler` 跟 Combobox onChange signature 衝突 — DOM runtime spread 安全(per codex P2 forward)\n {...(rest as any)}\n // **Tag SSOT canonical**:用 `avatar` prop(不塞 children),Tag 內部統一\n // wrap 進 16×16 圓形 mask container(per Tag tsx line 175)。\n tagRenderer={(item, onRemove) => {\n const p = resolvePerson(findPerson(people, item.value))\n return (\n <Tag\n key={item.value}\n size={size}\n color=\"neutral\"\n // 2026-05-18 7B' fix(per user 拍板「執行」+ Codex Round 3 共識,paired with Combobox L286):\n // 拿掉 `unbounded` 對齊 Tag canonical max-w-40 cap + 內建 ellipsis。PeoplePicker pill 走\n // Combobox tagRenderer slot,SSOT = Tag primitive 視覺(per codex Round 3 verdict)。\n // 人名 99% < 25 chars 不觸發 cap;極端長名(複數姓 + middle name)觸發 ellipsis 是合理 UX。\n avatar={pillShowAvatar\n ? <Avatar src={p.avatarUrl} alt={p.name} size={16} hoverCard={buildPersonProfileCard(p)} />\n : undefined}\n onRemove={onRemove}\n >\n {p.name}\n </Tag>\n )\n }}\n />\n )\n }\n\n // ── multi 'stack' (default) → wraps Combobox 跟 pill mode 同 SSOT,差別在 tagRenderer 視覺。\n //\n // **2026-05-15 Bug 3 fix(Claude+Codex Step 5 比稿 consensus)**:visible count 走 shared\n // `avatar-stack-overflow` primitive deterministic formula(取代 Combobox DOM offsetWidth-based\n // useOverflowCount + 60px fallback 不 deterministic),pass override 給 Combobox bypass internal\n // measurement。`MultiPersonDisplay`(view path)同 primitive,view + edit 結果一致。\n // 對齊 user verbatim SSOT「同 cell width 同 overflow 判斷」+ codex Q3 consensus shared primitive。\n const handleMultiChange = (next: string[]) => {\n onChange?.(next.map(name => findPerson(people, name)))\n }\n // ── edit mode ─────────────────────────────────────────────────────────────\n // (selectedNames 派生已 hoist 至檔上方 hooks 區,見 D3 P0 修註解)\n\n return (\n <Combobox\n width={width}\n ref={mergedStackRef}\n size={size}\n variant={resolvedVariant}\n options={people.map(personToSelectOption)}\n value={selectedNames}\n onChange={handleMultiChange}\n searchable\n searchIn={searchIn}\n clearable={clearable}\n searchPlaceholder={searchPlaceholder}\n searchAriaLabel={searchAriaLabel}\n // 2026-05-12 Stream C Issue 4(codex Q3):placeholder = trigger empty('請選擇人員');emptyText = search-empty(僅 backward-compat forward 1 cycle)\n placeholder={placeholder}\n emptyText={emptyText}\n wrap={false}\n defaultOpen={defaultOpen}\n onOpenChange={onOpenChange}\n // 2026-05-13 (a) fix(user 拍 Path a + Layer A density-drift root-cause):\n // 撤掉 `tagAreaPaddingLeftPx={8}` magic — Combobox `tagPadding[size]` 是 density-dependent\n // calc 公式(`(field-height - icon-size) / 2`),只在 md size + default density 才 = 4px;\n // 其他 size/density 漂 6px / 8px → 4+8=12 spec 公式不成立。\n // (a) fix:form context + 有 tag → 改 inject `!px-[var(--field-px)]`(固定 12px)直接 override `tagPadding[size]`,\n // 達成 GitHub PeoplePicker fixed 12px inset(對齊 cell context 同 13px from cell.left 含 1px border)。\n // - form + 有 tag → `!px-[var(--field-px)]`(12px 固定 inset)+ tagAreaPaddingLeftPx undefined → field.padL=12 ✓\n // - table-cell + 有 tag → naked variant `!px-[var(--table-cell-px)]` 已是 12px,不 inject ✓\n // - isEmpty → 不 inject,走 Combobox 預設文字 inset(`tagPadding[size]` 公式自然 vertical center)\n className={cn(className, !isEmpty && surface === 'form' && '!px-[var(--field-px)]')}\n aria-label={ariaLabel}\n // 2026-05-15 Bug 1 fix(Claude+Codex Step 5 比稿 consensus,user verbatim「就 A」):per-length 動態\n // wrapper class — length=1 降階單人視覺需要 width constraint chain(`flex-1 min-w-0 overflow-hidden`),\n // length>=2 stack 視覺保留 overlap(`-ml-0.5 first:ml-0 relative inline-flex group/avatar`)。對齊\n // spec.md §C row 1(length=1 = avatar+人名+ellipsis)+ §D row 1(length>=2 = stack overlap)。\n // 真根因:Combobox `OverflowTagList` 把所有 tagRenderer 結果包 `shrink-0`,加 `inline-flex` 後\n // wrapper 變 intrinsic content-width → PersonDisplay `w-full` resolves to intrinsic → truncate 無效。\n // 修法:length=1 wrapper 改 `flex-1 min-w-0 overflow-hidden` 提供 width constraint 給 PersonDisplay。\n // SSOT helper `getPeoplePickerTagWrapperClass(count)` 集中,future 改 wrapper 行為改一處。\n tagWrapperClassName={getPeoplePickerTagWrapperClass(selectedNames.length)}\n // 2026-05-16 真 root cause fix:overflow chip wrapper 套同 `-ml-0.5` 讓 chip 物理上\n // 跟 avatar 同 slot(等寬同 step,non-overlapping 多 24px 區塊不再 saw)。對齊 user\n // 「avatars 和 +N 都是同尺寸圓形,空間最多容固定數量圓形」物理模型 directive +\n // MUI AvatarGroup / Primer AvatarStack 共識(`AvatarGroup.js` L54-59 same negative margin)。\n overflowWrapperClassName=\"-ml-0.5 first:ml-0 relative inline-flex\"\n tagAreaGapPx={0}\n tagAreaPaddingLeftPx={undefined}\n // 2026-05-12 Round 7 fix(user 抓 image 2「+N tag 應該圓形不是矩形」+ 對齊 GitHub picker idiom):\n // Combobox default `overflowShape='tag'`(矩形 chip,文字 Combobox 慣例);PeoplePicker stack\n // mode pass `'circle'`(圓形 avatar-shape,跟 avatar 一樣大)。對齊 MultiPersonDisplay readonly path\n // 既有 OverflowIndicator default 'circle' SSOT。\n overflowShape=\"circle\"\n // 2026-05-15 Bug 3 fix:formula-based visible count override(避免 Combobox DOM measurement +\n // 60px fallback 不 deterministic)。SSOT in `./avatar-stack-overflow.ts`,view + edit 共用。\n visibleCountOverride={stackVisibleCount}\n // 2026-05-14 I4 fix(per codex+Layer A 共識):hidden items 在 `+N` overflow popover 顯\n // Tag with avatar(對齊 view path MultiPersonDisplay popover SSOT,user 抓 display vs edit\n // overflow 視覺不一致)。\n renderHiddenTag={(item) => {\n const p = resolvePerson(findPerson(people, item.value))\n return (\n <Tag\n key={item.value}\n color=\"neutral\"\n size=\"sm\"\n avatar={\n <Avatar\n src={p.avatarUrl}\n alt={p.name}\n size={16}\n hoverCard={buildPersonProfileCard(p)}\n />\n }\n onRemove={() => {\n onChange?.(selectedNames.filter(n => n !== item.value).map(n => findPerson(people, n)))\n }}\n >\n {p.name}\n </Tag>\n )\n }}\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n // any-allow: rest 含 `onChange: FormEventHandler` 跟 Combobox onChange signature 衝突 — DOM runtime spread 安全(per codex P2 forward)\n {...(rest as any)}\n tagRenderer={(item, onRemove) => {\n const p = resolvePerson(findPerson(people, item.value))\n // 2026-05-12 Q2 fix(user 拍板「multi 只選 1 人時 trigger = avatar + name,跟 single mode 同」):\n // selectedNames.length === 1 → PersonDisplay(avatar + name)代替 PersonAvatarTag(avatar only)。\n // SSOT 對齊 PeoplePicker single mode line 201 selectedItemRenderer。多選 1 人時視覺等同單選,\n // 只在 length > 1 才走 stack(各 avatar 純 chip)。多選 + inline 搜尋場景拿掉 name 改 cursor\n // 走 `searchIn='trigger'` opt-in(2026-05-12 規則 3 ship,已轉傳 Combobox;default 'menu' 走 panel-top search)。\n if (selectedNames.length === 1) {\n return <PersonDisplay key={item.value} value={p} size={size} onRemove={onRemove} />\n }\n return (\n <PersonAvatarTag\n key={item.value}\n person={p}\n size={size}\n onRemove={onRemove}\n />\n )\n }}\n />\n )\n})\nPeoplePicker.displayName = 'PeoplePicker'\n\n// Story auto-compile metadata\nexport const peoplePickerMeta = {\n component: 'PeoplePicker',\n family: 4,\n variants: {},\n sizes: {},\n // 'active' 移除 — trigger 走 Field chrome、menu 走 MenuItem,皆無自有按壓視覺(2026-07-07 詞彙統一 DS-wide 按壓訊號盤點:檔內 0 active: utility / 0 *-active token)。\n states: ['default', 'hover', 'focus-visible', 'disabled'],\n tokens: {\n bg: [],\n fg: ['text-fg-disabled', 'text-fg-muted'],\n ring: [],\n },\n} as const\n\nexport { PeoplePicker }\n// 2026-07-10 C14 export 缺口修(R2 同類:定義存在但無 export 面,consumer 想守 PersonValue 鏈\n// import 不到 → 被逼手刻)。person-display 表面隨 PeoplePicker subpath + root barrel 一併公開。\nexport {\n PersonDisplay,\n MultiPersonDisplay,\n PersonAvatarTag,\n buildPersonProfileCard,\n resolvePerson,\n type PersonValue,\n type PersonData,\n} from './person-display'\n"],"names":["SelectPublic","ComboboxPublic","PeoplePicker","handleMultiChange"],"mappings":"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"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
2
|
import * as React from "react";
|
|
3
3
|
import { cva } from "class-variance-authority";
|
|
4
|
-
import {
|
|
4
|
+
import { lineClampClass, cn } from "../../lib/utils.js";
|
|
5
5
|
import { Avatar } from "../../components/Avatar/avatar.js";
|
|
6
6
|
import { Tooltip, TooltipTrigger, TooltipContent } from "../../components/Tooltip/tooltip.js";
|
|
7
7
|
import { useTruncated } from "../../hooks/use-truncated.js";
|
|
@@ -33,7 +33,7 @@ Claude Code 專案級 subagent 現在會讀取 `.claude/agents/*.md`。共用 wo
|
|
|
33
33
|
| 可能含產品/UI/UX SSOT 決策的 workflow | `packages/design-system/ds-canonical/skills/`(runtime 使用 generated provider view) | agent 返回一次 summary;真正 P2H checkpoint 需在 main AI 端處理 |
|
|
34
34
|
| 一次性 script | `.claude/commands/` | agent 是 AI worker,不是 script |
|
|
35
35
|
| 每 session signal rule | `CLAUDE.md` | agent 只在 invoke 時載入 |
|
|
36
|
-
| 機械 tool-level 檢查 |
|
|
36
|
+
| 機械 tool-level 檢查 | `packages/design-system/ds-canonical/hooks/` | hook 是 pre/post tool event,不是 AI agent(`.claude/hooks/` 只是 generated view)|
|
|
37
37
|
|
|
38
38
|
## 新 agent 的 criteria
|
|
39
39
|
|
|
@@ -13,7 +13,7 @@ Claude Code 已將 custom commands 併入 skills,新增 workflow 一律寫入
|
|
|
13
13
|
| 疑似要放這但其實不是 | 正確去處 | 為什麼 |
|
|
14
14
|
|---------------------|---------|--------|
|
|
15
15
|
| 任何可重複 workflow(單步或多階段) | `packages/design-system/ds-canonical/skills/` | Agent Skills 是跨 provider 共用面 |
|
|
16
|
-
| 機械 tool-event 檢查 |
|
|
16
|
+
| 機械 tool-event 檢查 | `packages/design-system/ds-canonical/hooks/` | hook 是 pre/post tool 觸發,不是 user 觸發(`.claude/hooks/` 只是 generated view)|
|
|
17
17
|
| 需要 isolated context / scoped tools | `.claude/agents/` | agent 是 AI worker,command 是 quick action |
|
|
18
18
|
|
|
19
19
|
## 相容保留原則
|
|
@@ -5,7 +5,7 @@ description: Batch-end-verify rhythm + parallel tool batch + user-listed N-rule
|
|
|
5
5
|
|
|
6
6
|
<!-- _generated: canonical provider skill projection; source: skills/bug-fix-rhythm/SKILL.md; provider: codex; do not edit this adapter view. -->
|
|
7
7
|
|
|
8
|
-
<!-- provider-binding: profile=repository-legacy-surfaces-v1; provider=codex; strategy=generated-binding-header; assumptionCount=
|
|
8
|
+
<!-- provider-binding: profile=repository-legacy-surfaces-v1; provider=codex; strategy=generated-binding-header; assumptionCount=4; assumptionFingerprint=sha256:45202f237adbed348e84e4979ac466eb73182b7c790475672b99a7b158317a33; evidence=packages/governance/canonical/providers.json#codex -->
|
|
9
9
|
|
|
10
10
|
## Provider binding contract
|
|
11
11
|
|
|
@@ -21,7 +21,7 @@ This canonical workflow contains a committed inventory of legacy assumptions. Re
|
|
|
21
21
|
**目的**:當 session 有 ≥ 2 fixes(常見 visual bug session / user 列 N 條 numbered 規則),走 batch-end-verify rhythm,而非 per-fix verify cycle waste。**M32 split 後 (c)(d)(h1)(h2) 的 home**。
|
|
22
22
|
|
|
23
23
|
**對齊**:
|
|
24
|
-
-
|
|
24
|
+
- AGENTS.md mindset #6「大原則吸收瑣碎」+ `node_modules/@qijenchen/design-system/ds-canonical/rules/meta-patterns.md` M14「對話結論 AUTO integrate」
|
|
25
25
|
- Bazel incremental-build / GitHub Actions matrix-batch idiom(per-step incremental = waste,batch-end = canonical)
|
|
26
26
|
- provider-neutral parallel execution canonical
|
|
27
27
|
- Toyota TPS jidoka(per-station per-item verify,不可 skip 任何 item)
|
|
@@ -103,7 +103,7 @@ Read / Grep / Glob 多檔需求 → **single message multi-tool-call**:
|
|
|
103
103
|
|
|
104
104
|
每 fix 落地前 inline 判斷:
|
|
105
105
|
|
|
106
|
-
- **Surgical**(
|
|
106
|
+
- **Surgical**(AGENTS.md「Scope classifier — Surgical visual bug」):pixel / token / class adjustment, no new SSOT / API / cross-component → AUTO ship 此 fix
|
|
107
107
|
- **P2E engineering substantive**:治理/架構 SSOT、refactor、test、migration、adapter 或
|
|
108
108
|
不改 public 行為的 cross-component invariant → **AUTO**,依 evidence + independent
|
|
109
109
|
review + hard gates 自主完成。
|
|
@@ -105,7 +105,7 @@ canonical 手動分類。未掛載的 registered repo 是 unobserved,不可寫
|
|
|
105
105
|
- **mindset #2 不憑直覺發明**:新數值(gap / padding / font-size)前有 grep 既有?
|
|
106
106
|
- **mindset #3 改一處看三處**:若改 DS 的 cva defaultVariants,spec / docblock / anatomy 三方同步?
|
|
107
107
|
- **mindset #4 真實業務場景**:stories / examples 用真實場景 (Jira / Stripe / Notion...)?無 `Option A/B/C` / `按鈕一` / `Rule A`?
|
|
108
|
-
- **mindset #5
|
|
108
|
+
- **mindset #5 先證據、再分權**:code 裡有 TODO-未確認留白?兩類都 flag——屬產品/UI/UX SSOT 真取捨 → 列入 user 決策清單;屬純工程 → 違反「依證據自主收斂」(工程不確定性不得留 TODO,應當場收斂)
|
|
109
109
|
|
|
110
110
|
### Dim 5 — 視覺幾何(Mindset #1 視覺擴充 + `node_modules/@qijenchen/design-system/ds-canonical/references/ui-dev-rules.md`「同 flex 列互動 slot 幾何鐵律」)
|
|
111
111
|
|
|
@@ -5,7 +5,7 @@ description: Auto-invoke when listing options / 建議 / 候選方案. Forces in
|
|
|
5
5
|
|
|
6
6
|
<!-- _generated: canonical provider skill projection; source: skills/propose-options/SKILL.md; provider: codex; do not edit this adapter view. -->
|
|
7
7
|
|
|
8
|
-
<!-- provider-binding: profile=repository-legacy-surfaces-v1; provider=codex; strategy=generated-binding-header; assumptionCount=
|
|
8
|
+
<!-- provider-binding: profile=repository-legacy-surfaces-v1; provider=codex; strategy=generated-binding-header; assumptionCount=3; assumptionFingerprint=sha256:3f2f72c912df95fa56c51f9620f6a76f5ce9fd59b3b1ab07c4e7d9b35fc939dd; evidence=packages/governance/canonical/providers.json#codex -->
|
|
9
9
|
|
|
10
10
|
## Provider binding contract
|
|
11
11
|
|
|
@@ -21,7 +21,7 @@ This canonical workflow contains a committed inventory of legacy assumptions. Re
|
|
|
21
21
|
**目的**:Claude 對 user 提建議 / 列 option list 時,**先跑 7 題原則自檢**,通過才寫進回覆。Reject 的不列 OR 列出時標 fail 原因。
|
|
22
22
|
|
|
23
23
|
**對齊**:
|
|
24
|
-
-
|
|
24
|
+
- AGENTS.md mindset #1(不取巧)+ #2(消費既有)+ #5(先證據、再分權)+ #6(meta 抽象)
|
|
25
25
|
- M8 benchmark / M17 SSOT / M12 binary rule / 治理「加規則前 3 題」
|
|
26
26
|
- 本 skill 是上述 meta 的**propose-time 落地** — meta 寫成文字不夠,要 mechanical workflow 釘住
|
|
27
27
|
|
|
@@ -174,7 +174,7 @@ User 已就「為什麼會給錯誤建議」糾正 ≥ 3 次:
|
|
|
174
174
|
- (b) 是否有 reject option 曾被 list?(reject 不該被列出)
|
|
175
175
|
- (c) 是否 user 仍質疑 propose 品質?(若是 → 7-Q 沒抓到的 gap → 加 Q5)
|
|
176
176
|
|
|
177
|
-
回填到本 SKILL.md 或
|
|
177
|
+
回填到本 SKILL.md 或 `node_modules/@qijenchen/design-system/ds-canonical/rules/meta-patterns.md` M18(若需 escalate)。
|
|
178
178
|
|
|
179
179
|
---
|
|
180
180
|
|
|
@@ -11,7 +11,7 @@ description: Build UI prototypes / MVPs via a structured UX workflow — benchma
|
|
|
11
11
|
|
|
12
12
|
Purpose: embody the UX designer's mental model for BUILDING PROTOTYPES — never design by gut; benchmark world-class, filter against DS + business, build multiple shortlisted proposals, self-audit, let stakeholders decide.
|
|
13
13
|
|
|
14
|
-
This skill is the **structured version** of shared-governance Mindset #1「對標世界級」+ #4「真實業務場景」+ #5
|
|
14
|
+
This skill is the **structured version** of shared-governance Mindset #1「對標世界級」+ #4「真實業務場景」+ #5「先證據、再分權」,and the orchestrator for `src/explorations/` folder usage.
|
|
15
15
|
|
|
16
16
|
## When to run
|
|
17
17
|
|
|
@@ -97,7 +97,7 @@ Phase 2 將 Phase 1 scan 結果變成可決策的評分表。
|
|
|
97
97
|
- 不是只比視覺表皮;若 candidate 實際上是同個 object model 的 UI shape 變體,narrative 需明點出
|
|
98
98
|
- 若候選會變更 object definition(拆分 / 合併 object),**扣 DS 一致性分**(代表與 product canonical object model 漂移)
|
|
99
99
|
|
|
100
|
-
4. **對齊 Mindset #5
|
|
100
|
+
4. **對齊 Mindset #5「先證據、再分權」**:
|
|
101
101
|
- 若 candidate 的某部分無法評(資訊不足),**flag 而非猜**
|
|
102
102
|
|
|
103
103
|
---
|
|
@@ -5,15 +5,6 @@ description: Auto-invoke after fix commits — extracts root-cause anti-pattern,
|
|
|
5
5
|
|
|
6
6
|
<!-- _generated: canonical provider skill projection; source: skills/scan-similar-bugs/SKILL.md; provider: codex; do not edit this adapter view. -->
|
|
7
7
|
|
|
8
|
-
<!-- provider-binding: profile=repository-legacy-surfaces-v1; provider=codex; strategy=generated-binding-header; assumptionCount=2; assumptionFingerprint=sha256:69580682e9ebafb279438e8b2aaae9aa4dba1b18dbc490aa971e281f0f0e27c7; evidence=packages/governance/canonical/providers.json#codex -->
|
|
9
|
-
|
|
10
|
-
## Provider binding contract
|
|
11
|
-
|
|
12
|
-
This canonical workflow contains a committed inventory of legacy assumptions. Resolve them exactly as follows; an unavailable resolution or inventory drift is `ADAPTER-BLOCKED`:
|
|
13
|
-
|
|
14
|
-
- `provider-identity`: Treat historical provider names as provenance labels, never as the current runtime identity.
|
|
15
|
-
- `provider-surface-path`: Resolve legacy repository paths through generated views while treating ds-canonical as the semantic owner.
|
|
16
|
-
|
|
17
8
|
> **Product-role projection(build 自動)**:本 skill 的執行步驟已只保留本 product repo 實際存在的指令。DS-author-only executor 會被改寫為 browser/manual/product-CI 等價驗證,不得在 product 中嘗試不存在的腳本。
|
|
18
9
|
|
|
19
10
|
# /scan-similar-bugs — Fix-time DS-wide Exhaustive Scan
|
|
@@ -21,7 +12,7 @@ This canonical workflow contains a committed inventory of legacy assumptions. Re
|
|
|
21
12
|
**目的**:任何 bug fix 提交後,**機械化掃 DS-wide 找同 pattern**,而非靠 model 記得 M10。
|
|
22
13
|
|
|
23
14
|
**對齊**:
|
|
24
|
-
-
|
|
15
|
+
- `node_modules/@qijenchen/design-system/ds-canonical/rules/meta-patterns.md` M10 「Proactive exhaustive scan」mechanical 落地
|
|
25
16
|
- mindset #1「不取巧」+ #2「優先消費既有」+ #6「meta 抽象」
|
|
26
17
|
- 對齊 IDE「find similar / find references」+ Chrome DevTools「find usages」+ GitHub Copilot「related code」3+ 家世界級 idiom
|
|
27
18
|
|
|
@@ -5,13 +5,12 @@ description: Pixel-level visual audit for design-system components based on user
|
|
|
5
5
|
|
|
6
6
|
<!-- _generated: canonical provider skill projection; source: skills/visual-audit/SKILL.md; provider: codex; do not edit this adapter view. -->
|
|
7
7
|
|
|
8
|
-
<!-- provider-binding: profile=repository-legacy-surfaces-v1; provider=codex; strategy=generated-binding-header; assumptionCount=
|
|
8
|
+
<!-- provider-binding: profile=repository-legacy-surfaces-v1; provider=codex; strategy=generated-binding-header; assumptionCount=4; assumptionFingerprint=sha256:0fd67f1adf4fc31b0756f12522a1dfd7f0af40f3e4b08002f1ee18cac446f7fe; evidence=packages/governance/canonical/providers.json#codex -->
|
|
9
9
|
|
|
10
10
|
## Provider binding contract
|
|
11
11
|
|
|
12
12
|
This canonical workflow contains a committed inventory of legacy assumptions. Resolve them exactly as follows; an unavailable resolution or inventory drift is `ADAPTER-BLOCKED`:
|
|
13
13
|
|
|
14
|
-
- `provider-identity`: Treat historical provider names as provenance labels, never as the current runtime identity.
|
|
15
14
|
- `provider-surface-path`: Resolve legacy repository paths through generated views while treating ds-canonical as the semantic owner.
|
|
16
15
|
|
|
17
16
|
> **Product-role projection(build 自動)**:本 skill 的執行步驟已只保留本 product repo 實際存在的指令。DS-author-only executor 會被改寫為 browser/manual/product-CI 等價驗證,不得在 product 中嘗試不存在的腳本。
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
- `實測: {量到的值}`
|
|
15
15
|
- `預期: {token 值或 ratio}`
|
|
16
16
|
- `差異: {若 FAIL,寫出 diff 和推測破壞規則}`
|
|
17
|
-
- `對應規則: {
|
|
17
|
+
- `對應規則: {AGENTS.md 章節 or spec.md 段落 or token 名}`
|
|
18
18
|
|
|
19
19
|
---
|
|
20
20
|
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
|
|
35
35
|
**FAIL 範例**:DatePicker 量到 top=12 / right=12 / bottom=8 / left=12,spec 未宣告 bottom asymmetric → FAIL,建議討論是對齊 12 還是補 spec rationale
|
|
36
36
|
|
|
37
|
-
**對應規則**:
|
|
37
|
+
**對應規則**:AGENTS.md `# 稽核 canonical`「Consistency 類稽核必 Phase 0 全掃再判」(actual == canonical OR rationale-for-deviation;design-system-audit Dim 13 enforce)
|
|
38
38
|
|
|
39
39
|
---
|
|
40
40
|
|
|
@@ -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.125",
|
|
7
|
+
"storybookConfig": "0.1.0-beta.125"
|
|
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": "30c877a2da6a1ddff199fbb5f38f17081dd0e24b5febd0b65ca1d0b623b786c4",
|
|
46
46
|
"discoveryPolicySha256": "fba289aeeb5a3f11139b9589f7ab08b5e8c8799c7bdf8f0ea57ebfeb8e7c9da6",
|
|
47
47
|
"providerMaterializersSha256": "732e85b01a12e7dd19469e26e4427f0e0cfe5117a78fc9f69c2f0f89702f463a",
|
|
48
|
-
"providerLifecycleSha256": "
|
|
48
|
+
"providerLifecycleSha256": "73ac6182c2e146a46015d18183370b8ed7ed20c04055b4682e4ca0e8d6594d33",
|
|
49
49
|
"providerInventorySha256": "00adf1c4f6b3367055a9cc3fdfe658660771b4c9931f696e12761a95076ad92c",
|
|
50
50
|
"executionRuntimeSha256": "d9377cc45308731f287d7b1711b7c38fc4e02ff8f84729bcbbf01bb52d136904",
|
|
51
51
|
"commonInstructionSha256": "a72c51a412d6cdb3961d691744013c8362e826626a330cecf4a296a6f4d93a5c",
|
|
@@ -53,13 +53,13 @@
|
|
|
53
53
|
"claude": {
|
|
54
54
|
"instructionSha256": "f56a44c730506258cc7e2581dbdd31bfd1e6d13d60dc8a8ee8bcc398228453fb",
|
|
55
55
|
"hookSha256": "5853d1fa036ce174cd67f19ea7e4971797f145e47e32c5201b8556146f895244",
|
|
56
|
-
"skillsSha256": "
|
|
56
|
+
"skillsSha256": "fe4a632bdd7ce5f474cadcd6ed5c4c1c54c9f8c4fbeddb3f9a5375ee0219d5a9",
|
|
57
57
|
"treeDigests": {}
|
|
58
58
|
},
|
|
59
59
|
"codex": {
|
|
60
60
|
"instructionSha256": "a72c51a412d6cdb3961d691744013c8362e826626a330cecf4a296a6f4d93a5c",
|
|
61
61
|
"hookSha256": "d834c4cfa5a38d13660e5f1a17f80c71e2c83a4394f6858468230cae4d08a104",
|
|
62
|
-
"skillsSha256": "
|
|
62
|
+
"skillsSha256": "611ee0bf8e283c622458009dfd0b9f3bfba64de10d76d1d7817e1c8cdb524a1f",
|
|
63
63
|
"treeDigests": {}
|
|
64
64
|
},
|
|
65
65
|
"generic": {
|
|
@@ -74,7 +74,7 @@
|
|
|
74
74
|
},
|
|
75
75
|
"governanceCheckSha256": "1c4c4455910f1e88aebb84add9f756717395519d84913beafaa654a9f1c3126e",
|
|
76
76
|
"utilityRegistrySha256": "ac430d50ee82c19eb678f725c00eec23a0e25b33ec4bad81d3bf6e2fa9fa64ff",
|
|
77
|
-
"sharedSkillsSha256": "
|
|
77
|
+
"sharedSkillsSha256": "46a3e1f3e7a7263e1c0032724eb6f91eae14f388087096a83904c3d135ef65b7",
|
|
78
78
|
"sharedSkillCount": 11,
|
|
79
79
|
"requiredScripts": {
|
|
80
80
|
"audit:a11y": "node scripts/audit-consumer-a11y.mjs",
|