@qijenchen/design-system 0.1.0-beta.72 → 0.1.0-beta.74

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.
Files changed (43) hide show
  1. package/dist/components/AppShell/_demo-helpers.d.ts.map +1 -1
  2. package/ds-canonical/fork/governance.lock +106 -2
  3. package/ds-canonical/fork/launchers/inject_fork_governance_preamble.sh +8 -0
  4. package/ds-canonical/fork/manifest.json +15 -1
  5. package/ds-canonical/fork/skills/bug-fix-rhythm/SKILL.md +183 -0
  6. package/ds-canonical/fork/skills/code-quality-audit/SKILL.md +65 -0
  7. package/ds-canonical/fork/skills/delivery-handoff/SKILL.md +229 -0
  8. package/ds-canonical/fork/skills/delivery-handoff/references/flow-diagram.md +180 -0
  9. package/ds-canonical/fork/skills/delivery-handoff/references/handoff-template.md +177 -0
  10. package/ds-canonical/fork/skills/delivery-handoff/references/inventory-checklist.md +196 -0
  11. package/ds-canonical/fork/skills/performance-audit/SKILL.md +107 -0
  12. package/ds-canonical/fork/skills/product-ui-audit/SKILL.md +232 -0
  13. package/ds-canonical/fork/skills/product-ui-audit/references/audit-checks.md +246 -0
  14. package/ds-canonical/fork/skills/product-ui-audit/references/common-misuses.md +329 -0
  15. package/ds-canonical/fork/skills/product-ui-audit/references/report-template.md +159 -0
  16. package/ds-canonical/fork/skills/propose-options/SKILL.md +177 -0
  17. package/ds-canonical/fork/skills/prototype/SKILL.md +244 -0
  18. package/ds-canonical/fork/skills/prototype/references/audit-checks.md +38 -0
  19. package/ds-canonical/fork/skills/prototype/references/benchmark-sources.md +94 -0
  20. package/ds-canonical/fork/skills/prototype/references/checkpoints.md +191 -0
  21. package/ds-canonical/fork/skills/prototype/references/evaluation-matrix.md +141 -0
  22. package/ds-canonical/fork/skills/prototype/references/ooux-template.md +198 -0
  23. package/ds-canonical/fork/skills/prototype/references/proposal-template.md +229 -0
  24. package/ds-canonical/fork/skills/scan-similar-bugs/SKILL.md +200 -0
  25. package/ds-canonical/fork/skills/ux-audit/SKILL.md +130 -0
  26. package/ds-canonical/fork/skills/visual-audit/SKILL.md +247 -0
  27. package/ds-canonical/fork/skills/visual-audit/output/.gitkeep +0 -0
  28. package/ds-canonical/fork/skills/visual-audit/references/audit-architecture.md +101 -0
  29. package/ds-canonical/fork/skills/visual-audit/references/visual-checklist.md +297 -0
  30. package/ds-canonical/fork/skills/visual-audit/references/world-class-benchmarks.md +198 -0
  31. package/ds-canonical/hooks/check_plugin_fork_health.sh +2 -2
  32. package/ds-canonical/hooks/lib/_app_shell_primary_header_consistency.sh +36 -6
  33. package/ds-canonical/hooks/session_start_governance_check.sh +1 -1
  34. package/ds-canonical/hooks/tests/test_check_app_shell_primary_header_consistency.sh +58 -2
  35. package/ds-canonical/skills/design-system-audit/SKILL.md +2 -2
  36. package/llms-full.txt +1 -1
  37. package/llms.txt +1 -1
  38. package/package.json +1 -1
  39. package/src/components/AppShell/_demo-helpers.tsx +25 -1
  40. package/src/components/AppShell/app-shell.principles.stories.tsx +3 -2
  41. package/src/components/AppShell/app-shell.spec.md +12 -7
  42. package/src/components/AppShell/app-shell.stories.tsx +6 -0
  43. package/src/components/Sidebar/sidebar.spec.md +2 -0
@@ -4,7 +4,7 @@
4
4
  # 2026-05-21 ship per user directive「該程式化的就程式化」+「確認當有 global header 時,
5
5
  # sidebar 內的 header 應該要拿掉」+ world-class GitHub/Gmail/Figma 共識。
6
6
  #
7
- # Detects 2 violations in AppShell consumer code:
7
+ # Detects 3 violations in AppShell consumer code:
8
8
  # V1) `layout="primary-header"` without `globalHeader=...` prop
9
9
  # → 缺 globalHeader 而 layout=primary-header 是邏輯矛盾(per app-shell.spec.md
10
10
  # 「primary-header = primary-sidebar + 一條 global header」)
@@ -12,6 +12,14 @@
12
12
  # V2) `layout="primary-header"` + 任何 `<SidebarHeader>...</SidebarHeader>` 在同 file
13
13
  # → WorkspaceBrand 已該在 globalHeader,sidebar 內不該再有 SidebarHeader
14
14
  # (per app-shell.spec.md「WorkspaceBrand 放置 SSOT」+ world-class GitHub/Gmail/Figma 一致)
15
+ # 例外:同 file 用 useSidebar/isMobile = mobile-only 補品牌的正確 responsive fork(豁免)
16
+ #
17
+ # V3) `layout="primary-header"` + 任何 `<SidebarFooter>...</SidebarFooter>` 在同 file(2026-06-18 beta.74)
18
+ # → primary-header 帳號入口家在 globalHeader 右(收成 Sheet 鏡像到 Sheet header 右),**不該**用
19
+ # sidebar footer〔那是 primary-sidebar 帳號家慣例〕→ 誤用 = 模式混淆
20
+ # (per app-shell.spec.md「帳號入口(Account entry)放置 SSOT」+ Material modal nav drawer
21
+ # 「account switcher 放 drawer header」)。與 V2 不同:primary-header 任何 breakpoint(含 mobile
22
+ # Sheet)帳號家都在 header 區 → V3 不設 isMobile 豁免;非帳號用途 footer 走 escape allowlist。
15
23
  #
16
24
  # 對齊 .claude/rules/self-verify.md「Pre-edit」階段 + check_chrome_header_handcraft.sh /
17
25
  # check_overlay_handcraft.sh 等既有 SSOT-enforcement hook idiom。
@@ -40,8 +48,12 @@ esac
40
48
  # Escape allowlist
41
49
  if grep -q "@app-shell-primary-header-allow:" "$TARGET"; then exit 0; fi
42
50
 
43
- # 偵測 layout="primary-header"
44
- if ! grep -q 'layout="primary-header"\|layout={["\047]primary-header["\047]}' "$TARGET"; then exit 0; fi
51
+ # 偵測 layout="primary-header"(三種 JSX 形式:layout="x" / layout={"x"} / layout={'x'})
52
+ # 2026-06-18 beta.74 fix(adversarial audit P1):舊 `["\047]` 想用 octal \047 表單引號,但 BRE char-class
53
+ # **不** interpret octal → 單引號 JSX `layout={'primary-header'}` 整個 hook 靜默 skip(false-negative)。
54
+ # 改 grep -E + 字面單/雙引號 alternation(`('\''|")`)— 三種引號形式皆match。⚠️ 驗證用 /usr/bin/grep,
55
+ # 互動 shell 的 wrapped grep 對 octal 給相反結果會藏 bug。
56
+ if ! grep -Eq 'layout="primary-header"|layout=\{('\''|")primary-header('\''|")\}' "$TARGET"; then exit 0; fi
45
57
 
46
58
  VIOLATIONS=()
47
59
 
@@ -50,9 +62,27 @@ if ! grep -q 'globalHeader\s*=' "$TARGET"; then
50
62
  VIOLATIONS+=("V1 缺 globalHeader prop:layout=\"primary-header\" 必傳 globalHeader 否則邏輯矛盾(per app-shell.spec.md「primary-header = primary-sidebar + 一條 global header」)")
51
63
  fi
52
64
 
53
- # V2:layout="primary-header" + <SidebarHeader> 同 file → WorkspaceBrand 該在 globalHeader 不重複
54
- if grep -q '<SidebarHeader' "$TARGET"; then
55
- VIOLATIONS+=("V2 Sidebar 內含 SidebarHeader:primary-header mode WorkspaceBrand 該在 globalHeader,sidebar 內不該重複(per spec.md「WorkspaceBrand 放置 SSOT」+ world-class GitHub/Gmail/Figma 共識)。若 sidebar header 是其他內容(非 brand),加 escape allowlist `// @app-shell-primary-header-allow:` 並說明 reason")
65
+ # V2:layout="primary-header" + <SidebarHeader> 同 file → WorkspaceBrand 該在 globalHeader 不重複。
66
+ # 例外(2026-06-18 responsive 精修,M34 hook-intent 對齊):同 file 也用 useSidebar/isMobile =
67
+ # mobile-only 補品牌的「正確 responsive fork」(小螢幕 Sheet 蓋住 globalHeader Sheet 內補同組 primitive,
68
+ # desktop 仍無 SidebarHeader,非真重複)→ 不 flag。見 app-shell.spec.md WorkspaceBrand SSOT「Responsive 精修」子句。
69
+ # ⚠️ 限制(2026-06-18 beta.74 audit 記錄):V2 是 token-presence 啟發式(file 含 useSidebar/isMobile 即豁免),
70
+ # 非「isMobile 真的 guard 該 <SidebarHeader>」的 AST 級判斷 → 罕見假陰性(unguarded SidebarHeader 但 file
71
+ # 因別處用 isMobile → 漏擋)。bash 無法精準 AST;靠 code review + escape allowlist 兜底,目前唯一 consumer(stories)正確。
72
+ # 要嚴格需 TS AST lint rule(future)。
73
+ # tag boundary `([^A-Za-z]|$)`(2026-06-18 audit P2#4):避免 prefix-extended 名(<SidebarHeaderXyz>)誤觸
74
+ if grep -Eq '<SidebarHeader([^A-Za-z]|$)' "$TARGET" && ! grep -qE 'useSidebar|isMobile' "$TARGET"; then
75
+ VIOLATIONS+=("V2 Sidebar 內含 SidebarHeader:primary-header mode WorkspaceBrand 該在 globalHeader,sidebar 內不該重複(per spec.md「WorkspaceBrand 放置 SSOT」+ world-class GitHub/Gmail/Figma 共識)。若是 mobile-only responsive 補品牌請用 useSidebar().isMobile 條件渲染(自動豁免);若 sidebar header 是其他內容(非 brand),加 escape allowlist `// @app-shell-primary-header-allow:` 並說明 reason")
76
+ fi
77
+
78
+ # V3:layout="primary-header" + <SidebarFooter> 同 file → 帳號入口家在 globalHeader 右,不該用 sidebar footer。
79
+ # 與 V2 不同 — primary-header 收成 Sheet 時帳號鏡像到 Sheet **header** 右(非 footer),故任何 breakpoint 都不該
80
+ # 有 SidebarFooter 放帳號 → V3 不設 isMobile 豁免(per app-shell.spec.md「帳號入口(Account entry)放置 SSOT」
81
+ # Responsive 精修「不放 SidebarFooter」+ Material modal nav drawer「account switcher 放 drawer header」)。
82
+ # demo 天然不誤觸:layout="primary-header" 在 stories,<SidebarFooter> 在 _demo-helpers.tsx,分檔。
83
+ # 非帳號用途 footer(storage meter / collapse 等)→ escape allowlist 兜底。
84
+ if grep -Eq '<SidebarFooter([^A-Za-z]|$)' "$TARGET"; then
85
+ VIOLATIONS+=("V3 Sidebar 內含 SidebarFooter:primary-header mode 帳號入口家在 globalHeader 右(收成 Sheet 鏡像到 Sheet header 右),不該用 sidebar footer〔那是 primary-sidebar 慣例〕(per spec.md「帳號入口(Account entry)放置 SSOT」+ Material modal nav drawer)。若 footer 是非帳號用途,加 escape allowlist \`// @app-shell-primary-header-allow:\` 並說明 reason")
56
86
  fi
57
87
 
58
88
  if [[ ${#VIOLATIONS[@]} -gt 0 ]]; then
@@ -265,7 +265,7 @@ fi
265
265
  ENV_SMOKE=""
266
266
  # (a) plugin-mode 完整性 — CLAUDE_PLUGIN_ROOT 在 ds-repo native mode 可能 UNSET → 先 guard 才用
267
267
  if [ -n "${CLAUDE_PLUGIN_ROOT:-}" ] && [ ! -d "${CLAUDE_PLUGIN_ROOT:-}/hooks" ]; then
268
- ENV_SMOKE="${ENV_SMOKE}\n - Plugin mode:\$CLAUDE_PLUGIN_ROOT 有設但 hooks/ 找不到 → plugin install 可能不完整( /plugin marketplace update)。"
268
+ ENV_SMOKE="${ENV_SMOKE}\n - Plugin mode:\$CLAUDE_PLUGIN_ROOT 有設但 hooks/ 找不到 → plugin install 可能不完整(C-prime 主路徑 = committed-config + \`npm run sync-all\`〔npm-only〕;刻意用 plugin 才跑 /plugin marketplace update)。"
269
269
  fi
270
270
  # (b) node 在 PATH — audit scripts 依賴
271
271
  if ! command -v node >/dev/null 2>&1; then
@@ -1,9 +1,10 @@
1
1
  #!/bin/bash
2
2
  # Tests for check_app_shell_primary_header_consistency.sh
3
3
  #
4
- # Hook(PreToolUse Edit/Write):偵測 AppShell consumer 2 violations:
4
+ # Hook(PreToolUse Edit/Write):偵測 AppShell consumer 3 violations:
5
5
  # V1 layout="primary-header" 缺 globalHeader prop
6
- # V2 layout="primary-header" + 同 file 含 <SidebarHeader>
6
+ # V2 layout="primary-header" + 同 file 含 <SidebarHeader>(useSidebar/isMobile 豁免)
7
+ # V3 layout="primary-header" + 同 file 含 <SidebarFooter>(帳號家在 header 右,非 footer;無 isMobile 豁免)
7
8
  #
8
9
  # Hook 透過 stdin 讀 tool_input(INPUT=$(cat) + jq;2026-05-31 改 env→stdin 對齊 sibling helper + 讓 dispatcher 能呼叫)
9
10
  # 且需 TARGET file 真實存在於 disk(`[[ ! -f "$TARGET" ]] && exit 0`)。
@@ -131,6 +132,61 @@ run_hook_on_file "src/other.tsx" '
131
132
  '
132
133
  expect_pass_silent "7. layout != primary-header → skip"
133
134
 
135
+ # 8. responsive mobile-aware fork(useSidebar/isMobile + SidebarHeader + globalHeader)→ silent
136
+ # (2026-06-18 精修豁免:mobile-only 補品牌不是 desktop 重複)
137
+ run_hook_on_file "src/responsive.tsx" '
138
+ const { isMobile } = useSidebar()
139
+ <AppShell layout="primary-header" globalHeader={<GH />}>
140
+ <Sidebar>
141
+ {isMobile && <SidebarHeader>brand</SidebarHeader>}
142
+ </Sidebar>
143
+ </AppShell>
144
+ '
145
+ expect_pass_silent "8. responsive isMobile + SidebarHeader → silent(mobile-only 補品牌豁免)"
146
+
147
+ # 9. layout="primary-header" + <SidebarFooter> → block (V3)
148
+ # (2026-06-18 beta.74:primary-header 帳號家在 header 右,sidebar footer 是 primary-sidebar 慣例;無 isMobile 豁免)
149
+ run_hook_on_file "src/ph-footer.tsx" '
150
+ <AppShell layout="primary-header" globalHeader={<GH />}>
151
+ <Sidebar>
152
+ <SidebarFooter>account</SidebarFooter>
153
+ </Sidebar>
154
+ </AppShell>
155
+ '
156
+ expect_block "9. V3 primary-header + SidebarFooter → block" "V3 Sidebar 內含 SidebarFooter"
157
+
158
+ # 10. layout="primary-header" + mobile header-right account(SidebarHeader 補品牌+帳號,無 SidebarFooter)→ silent
159
+ # (2026-06-18:帳號鏡像 globalHeader 到 Sheet header 右,無 footer = 合規;V2 isMobile 豁免 + V3 無 footer)
160
+ run_hook_on_file "src/ph-header-account.tsx" '
161
+ const { isMobile } = useSidebar()
162
+ <AppShell layout="primary-header" globalHeader={<GH />}>
163
+ <Sidebar>
164
+ {isMobile && <SidebarHeader><WorkspaceBrand /><AccountMenu /></SidebarHeader>}
165
+ </Sidebar>
166
+ </AppShell>
167
+ '
168
+ expect_pass_silent "10. primary-header + header-right account, no footer → silent"
169
+
170
+ # 11. single-quote JSX layout={'primary-header'} missing globalHeader → block (V1)
171
+ # (2026-06-18 beta.74 regression:octal \047 gate bug → 單引號 JSX 整個 hook 靜默 skip;fix 後三種引號形式皆偵測)
172
+ run_hook_on_file "src/single-quote.tsx" "
173
+ <AppShell layout={'primary-header'}>
174
+ <Sidebar />
175
+ </AppShell>
176
+ "
177
+ expect_block "11. single-quote layout JSX gate (octal fix regression) → V1 block" "V1 缺 globalHeader prop"
178
+
179
+ # 12. prefix-extended component name <SidebarFooterPanel> with primary-header → silent (tag-boundary guard, V3 no false-positive)
180
+ # (2026-06-18 beta.74 audit P2#4:`<SidebarFooter` 不該 match `<SidebarFooterPanel`)
181
+ run_hook_on_file "src/prefix-extended.tsx" '
182
+ <AppShell layout="primary-header" globalHeader={<GH />}>
183
+ <Sidebar>
184
+ <SidebarFooterPanel>not the DS SidebarFooter</SidebarFooterPanel>
185
+ </Sidebar>
186
+ </AppShell>
187
+ '
188
+ expect_pass_silent "12. prefix-extended <SidebarFooterPanel> → silent(V3 tag-boundary 不誤觸)"
189
+
134
190
  echo ""
135
191
  echo "=== Summary ==="
136
192
  echo "Passed: $PASS / $((PASS + FAIL))"
@@ -173,7 +173,7 @@ User 2026-05-15 verbatim 抓「DS 深度稽核漏 storybook content quality」+
173
173
  | 53 | **Code-to-spec reverse drift check**(2026-05-17 user 抓 Phase 1 漏抓 FileViewer h-14 spec drift,新加 dim)| 對每 component grep `packages/design-system/src/components/<X>/<X>.tsx` 的 className 硬寫 utility(`h-14` / `w-80` / `px-loose` 類)→ 反向掃對應 `<X>.spec.md` 是否仍寫「固定 h-NN」「寫死」keyword 但 code 已 migrate to token = drift。互補既有 forward Dim 15/20(spec → code)。Hook `check_spec_class_drift.sh` write-time soft P1 warn,本 dim batch verify 既有 60+ 元件 spec.md。錨例:2026-05-17 Phase 1 我 file-viewer.spec.md L103 寫「Known drift:h-14 硬寫不消費 token」但 file-viewer.tsx:333 已 `h-[var(--chrome-header-height)]`,3+ 次 `/design-system-audit --deep` 都沒抓到反向 drift |
174
174
  | 54 | **M35 Nearest same-purpose canonical compliance**(2026-05-20 codify per codex Layer B D4)| 對每 `*.stories.tsx` wrap 既有 primitive(Sidebar / DataTable / ChromeHeader / Dialog / Sheet / Popover)的 file 跑:(a) 檔頭含 `@story-baseline:` cite marker?(b) `.claude/references/story-baseline-registry.json` 內 primitive 的 `requiredHelpers` 全 import?(c) `antiPatterns` regex 任一 match → fail?(d) `variantRules` button variant + size + iconOnly + pressed 全 satisfy?Hook `check_story_invariants.sh R8` write-time soft warn,本 dim batch verify。錨例:2026-05-20 AppShell stories 連 5 round drift 後 codex Layer B 抓 root cause = SSOT 消費被當引用儀式 |
175
175
  | 55 | **Token cross-namespace mapping integrity**(2026-05-20 codify per user 抓 red→deep-orange bug 100+ audit 沒發現)| `tokens/color/semantic.css` 每 hue interaction token(`--blue-hover` / `--red-hover` / ...)必指向**同名**primitive(`--red-hover: var(--color-red-N)`),**禁**跨 hue 混(`--red-hover: var(--color-deep-orange-N)` 違反)。Primitive 12 hue 全該有對應 interaction(blue/red/deep-orange/orange/amber/yellow/lime/green/turquoise/indigo/purple/magenta)。Status semantic(`--error-hover` 等)直指 primitive,**不**透過 hue layer。錨例:2026-05-20 semantic.css:246 `--red-hover: var(--color-deep-orange-5)` cross-namespace bug,100+ audit 沒發現 = audit 沒檢 token mapping integrity |
176
- | 56 | **AppShell primary-header consistency**(2026-05-21 codify per user 抓「primary-header = primary-sidebar + 一條 global header」+「globalHeader 存在時 sidebar 內 header 該拿掉」)| 對每 consumer `.tsx`(stories / app code)grep `layout="primary-header"`,verify:(a) 同 file 含 `globalHeader=` prop(否則邏輯矛盾)/(b) file 不含 `<SidebarHeader>`(WorkspaceBrand 該在 globalHeader,不重複)。World-class cite:GitHub repo sidebar 無 header(org/repo 在 global breadcrumb)+ Gmail / Figma file editor sidebar 無 header(brand 在 global top bar)。Hook `check_app_shell_primary_header_consistency.sh` write-time block(P1 warn,可 escape `// @app-shell-primary-header-allow:`)。對應 `app-shell.spec.md`「WorkspaceBrand 放置 SSOT」段 |
176
+ | 56 | **AppShell primary-header consistency**(2026-05-21 codify;2026-06-18 beta.74 V3 + responsive 豁免)| 對每 consumer `.tsx`(stories / app code)grep `layout="primary-header"`(三種 JSX 引號形式 `layout="x"` / `{"x"}` / `{'x'}` 皆偵測,grep -E 字面引號),verify 3 條:(V1) 同 file 含 `globalHeader=` prop(否則邏輯矛盾)/(V2) 不含裸 `<SidebarHeader>`(WorkspaceBrand 該在 globalHeader,不重複;**例外**:同 file 用 `useSidebar`/`isMobile` = mobile-only Sheet 補品牌的 responsive fork → 豁免)/(V3) 不含 `<SidebarFooter>`(帳號入口家在 globalHeader 右、收成 Sheet 鏡像到 Sheet header 右,**不該**用 sidebar footer〔那是 primary-sidebar 慣例〕;**無** isMobile 豁免)trigger 用 tag-boundary `([^A-Za-z]|$)` 避 prefix-extended 名誤觸。World-class cite:GitHub repo sidebar 無 header + Gmail / Figma brand 在 global top bar + Material modal nav drawer「account switcher 放 drawer header」。Hook `check_app_shell_primary_header_consistency.sh`(V1-V3)write-time exit 2 BLOCKER(via chrome_header_dispatcher,可 escape `// @app-shell-primary-header-allow:`)。對應 `app-shell.spec.md`「WorkspaceBrand 放置 SSOT」+「帳號入口(Account entry)放置 SSOT」段(均含 Responsive 精修子句)|
177
177
  | 57 | **M29 DS Anchor Preflight enforcement coverage**(2026-05-26 codify per user verbatim「該程式化的都沒程式化」)| 對每 `*.tsx`(production code,非 stories / test)grep wrap DS primitive(`<Sidebar>` / `<AppShell>` / `<DataTable>` 等)→ verify 過去 30 turns transcript 含 `Grep`/`Read` tool call hit `packages/design-system/src/**/*.spec.md` 或 `*.stories.tsx`,OR 檔頭含 `@story-baseline:` marker 或 inline 3-column owner table。Hook `check_ds_anchor_preflight.sh` write-time soft BLOCKER。對應 meta-patterns.md M29 + self-verify.md Pre-edit phase。錨例:2026-05-26 App.tsx 漏 SidebarTrigger / collapsible / startIcon mock-drift = M29 hook 不存在使 infra 沒攔 |
178
178
  | 58 | **Fork-user plugin install enforcement**(2026-05-26 codify per user「我們做那麼多 plugin 不就是要避免這件事?」)| SessionStart hook `check_plugin_fork_health.sh(r1,2026-06-11 merge)` 偵測:(a) cwd 不是 DS repo(無 `packages/design-system/src`)/(b) `package.json` 含 `@qijenchen/design-system` dep /(c) `~/.claude/plugins/design-system/` OR `.claude/plugins/design-system/` 不存在 → 三題 YES 印強制提示。互補 product-workspace `scripts/check-plugin-installed.mjs` npm postinstall layer。**+ 2026-05-30 fork-committed bootstrap 層(補 chicken-egg:plugin 硬 hook 隨 plugin 才裝,沒裝前無 mechanical 防線)**:fork 自帶 `template/ds-product-template/.claude/hooks/check_plugin_bootstrap.sh`(SessionStart 每 session 提醒,fail-open)+ `block_production_edit_without_plugin.sh`(PreToolUse 硬攔 `apps/**` production .tsx/.ts/.css edit,沒裝 plugin → exit 2 BLOCK,escape `CLAUDE_BYPASS_PLUGIN_BOOTSTRAP=1`)+ `.claude/settings.json` hooks 註冊;**不依賴 plugin**(committed in fork),mirror allowlist `.claude` ship 給 published fork。對應 product-workspace/CLAUDE.md「第 −1 步」段 |
179
179
  | 59 | **Approval preflight scope coverage**(2026-05-26 extend per user「未來其他人 fork 用其他元件也偏移」)| `check_substantive_edit_approval_preflight.sh` scope 從 `packages/design-system/src/**` 擴大到 `apps/**.{tsx,ts,css}` + `node_modules/@qijenchen/design-system/**`。Audit verify hook regex 涵蓋三 scope + allowlist `*.stories.tsx`/`*.test.*`/`scripts/*`。對應 memory/feedback_ship_then_revert_anti_pattern.md SSOT |
@@ -192,7 +192,7 @@ User 2026-05-15 verbatim 抓「DS 深度稽核漏 storybook content quality」+
192
192
  | 72 | **DS API surface tightening**(2026-05-27 — 治標 vs 治本)| Hook 71 偵測 anti-pattern 是 lint 層攔截;治本要 DS API design 強到 misuse 即 fail tsc。Audit:逐 component review API surface — `size?: number` 該改 `'sm'\|'md'\|'lg'` enum / `columns: Column[]` 該加 min length runtime check / `title` + `description` 該有 type-level XOR / Overlay primitive `defaultOpen` 該 require explicit。配套 codify in `tightening-roadmap.md`(若存在;不存在則以 `props-naming.md` + 各 spec API 段為準,對齊 audit-prompts.md dim 72),分 quarter ship。對應 Dim 71 是攔當前 misuse,本 dim 是消除未來 misuse 可能 |
193
193
  | 73 | **Full-story visual+interaction sweep enforce**(2026-05-27 codex M31 P0 finding)| Audit report JSON `storyResults.length === manifest.totalStories`(916)。Sample < 916 = reject(per user「不准抽樣」)。Hook `check_full_story_visual_interaction_sweep.sh` PostToolUse audit-report.json BLOCKER。Escape `"_sampling_allowed": "<rationale>"`(極罕見)|
194
194
  | 74 | **Overlay open/focus/Escape probe**(2026-05-27 codex M31 P0 finding + user 7-bug 錨點「overlay 沒彈出」)| Consumer story 用 Tooltip / Popover / Dialog / Sheet / DropdownMenu / HoverCard Trigger 必含 `defaultOpen` OR `open={true}` OR `play()` interaction click。Trigger-only catalog = reject(visual snapshot 看不到 content)。Hook `check_overlay_open_focus_escape_probe.sh` BLOCKER。HoverCard exception via `@story-trait-allow: missing-opensnapshot` per codex |
195
- | 75 | **Plugin freshness session-start prompt**(2026-05-27 chain-C ship + user「主動引導」directive)| Fork user session_start hook `check_plugin_fork_health.sh(r2,2026-06-11 merge)` reads local installed plugin.json version → fetch GitHub raw marketplace.json → diff version → if stale prompt run `npm run sync-all`. Sync-all 1-command 整合 npm update + claude plugin marketplace update + claude plugin update + restart prompt(per user 2026-05-27「不需要獨立命令兩次」)|
195
+ | 75 | **Plugin freshness session-start prompt**(2026-05-27 chain-C ship + user「主動引導」directive)| Fork user session_start hook `check_plugin_fork_health.sh(r2,2026-06-11 merge)` reads local installed plugin.json version → fetch GitHub raw marketplace.json → diff version → if stale prompt run `npm run sync-all`. Sync-all 1-command(2026-06-17 npm-only,不再跑 `claude plugin`):npm update 治理本體 + idempotent 刷新接線骨架 + skills;生效分軌(機械即時 / settings 自動 hot-reload / 指引 `/clear` 或下個 session)|
196
196
  | 76 | **Escape marker abuse cap**(2026-05-27 per user「不亂加 escape markers」)| Consumer file 10 escape markers 累計 ≥3 distinct types OR ≥5 total → BLOCK。修法 3 選 1:重構走 DS canonical / 拆 file / env override `CLAUDE_BYPASS_ESCAPE_MARKER_AUDIT`。Hook `check_escape_marker_abuse.sh` enforces escape philosophy「rare per-line documented exception,非 daily tool」|
197
197
  | 77 | **Composition-fidelity:conformance-primary,pixel-identity opt-in**(2026-05-27 ship / **2026-06-02 model 修正**)| Consumer 對 DS 用法正確性**主要由靜態 conformance 驗**(dim 71 含 Pattern 8 + `check_layout_space_magic_numbers` + R7/R8),對齊世界級 static lint(Polaris stylelint-polaris / Atlassian eslint-plugin-design-system / Carbon stylelint-plugin-carbon-tokens;WebFetch verified)。`scripts/composition-fidelity-visual-diff.mjs` 的 pixel/DOM identity diff 改**明確 opt-in**:只比標 `@composition-fidelity-mode` 的 mapping(忠實複製 replica / same-story 跨版本回歸,對齊 Chromatic/Storybook same-story baseline);單獨 `@story-baseline` = conformance 不做 identity diff;0 opt-in → exit 0。**禁** 拿產品範本(內容刻意不同)pixel 比 DS showcase(world-class 公認反 pattern)。SSOT `composition-fidelity.md`。CI `.github/workflows/composition-fidelity.yml` |
198
198
  | 78 | **Codex brief 禁列檔 invariant**(2026-05-27 codify per codex v1+v2 token-burn 2× anchor)| `check_codex_brief_invariants.sh` 4th invariant check:codex CLI brief 必含「禁列檔 / 禁 rg --files / 只讀 N file / 直接出 verdict」keyword,否則 codex 自動跑 `rg --files` 列 1300+ files 燒光 reasoning,無法產出 Step 5 Verdict(M31 Step 4.5 last-verdict gate fail)。Hook PreToolUse Bash codex exec/review 偵測 → BLOCKER。Escape `// @codex-brief-invariant-skip:` |
package/llms-full.txt CHANGED
@@ -1,6 +1,6 @@
1
1
  # @qijenchen/design-system — 完整設計參考(llms-full)
2
2
 
3
- > 全 component / pattern 的 variants / sizes / 禁止事項。build-time 從 spec.md frontmatter 生成,禁手改。v0.1.0-beta.72
3
+ > 全 component / pattern 的 variants / sizes / 禁止事項。build-time 從 spec.md frontmatter 生成,禁手改。v0.1.0-beta.74
4
4
 
5
5
  # Components
6
6
 
package/llms.txt CHANGED
@@ -1,7 +1,7 @@
1
1
  # @qijenchen/design-system
2
2
 
3
3
  > World-class React design system(Radix/shadcn + Tailwind v4 + 自訂 design token)。
4
- > 54 components + 4 public patterns + design tokens。v0.1.0-beta.72
4
+ > 54 components + 4 public patterns + design tokens。v0.1.0-beta.74
5
5
 
6
6
  本檔由 source(spec.md frontmatter + Storybook index)build-time 自動生成,**禁手改**(CI --check drift gate 守)。
7
7
  每元件 / pattern 的完整 variants / sizes / 禁止事項 全文見 [llms-full.txt](./llms-full.txt)。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qijenchen/design-system",
3
- "version": "0.1.0-beta.72",
3
+ "version": "0.1.0-beta.74",
4
4
  "private": false,
5
5
  "description": "World-class design system — components, patterns, tokens, hooks (single source of truth for team distribution).",
6
6
  "type": "module",
@@ -26,7 +26,9 @@ import {
26
26
  SidebarMenuButton,
27
27
  SidebarFooter,
28
28
  SidebarTrigger,
29
+ useSidebar,
29
30
  } from '@/design-system/components/Sidebar/sidebar'
31
+ import { useAppShell } from '@/design-system/components/AppShell/app-shell'
30
32
  import { ChromeHeader } from '@/design-system/patterns/header-canonical/chrome-header'
31
33
  import {
32
34
  ItemAvatar,
@@ -125,11 +127,31 @@ export function AcmeSidebar({
125
127
  includeWorkspaceBrand?: boolean
126
128
  includeUserFooter?: boolean
127
129
  } = {}) {
130
+ // Responsive 品牌/帳號(2026-06-18 v2):小螢幕 sidebar 收成 Sheet(z-50)打開時會「蓋住」globalHeader →
131
+ // primary-header 把品牌+帳號放 globalHeader,Sheet 內就看不到、drawer 變孤兒。故 mobile 時在 sidebar 內補回。
132
+ // **每 mode 鏡像自己桌面的帳號家**(per app-shell.spec.md「帳號入口(Account entry)放置 SSOT」Responsive 精修):
133
+ // - primary-header:桌面帳號在 globalHeader 右(brand 左 / account 右)→ Sheet 內把整條 globalHeader 搬進
134
+ // SidebarHeader(brand + flex-1 + AccountMenu;**SidebarHeader 即 ChromeHeader 基底,與 GlobalHeader 同
135
+ // primitive** = 結構 SSOT,非手刻對齊),**不放 SidebarFooter**(footer 是 primary-sidebar 慣例;
136
+ // 對齊 Material modal navigation drawer「帳號 switcher 放 drawer header,非 footer」)。
137
+ // - primary-sidebar:桌面帳號在 sidebar 底(SidebarFooter)→ Sheet 維持 header=brand / footer=UserFooter(不變)。
138
+ // desktop(globalHeader 可見)維持 headerless/footerless,不重複。「只能出現一次」= 同一時間一次,per breakpoint 判定。
139
+ const { isMobile } = useSidebar()
140
+ const { layout } = useAppShell()
141
+ // primary-header 收成 Sheet:帳號入口鏡像 globalHeader 放 SidebarHeader 右(SidebarHeader = ChromeHeader 基底,
142
+ // 與 GlobalHeader「brand 左 + flex-1 + account 右」完全同結構 → avatar 24px + 右緣距 px-loose 由 construction 保證)。
143
+ const headerHasAccount = layout === 'primary-header' && isMobile
128
144
  return (
129
145
  <Sidebar collapsible="icon" viewportInsetTop={viewportInsetTop}>
130
- {includeWorkspaceBrand && (
146
+ {(includeWorkspaceBrand || isMobile) && (
131
147
  <SidebarHeader>
132
148
  <WorkspaceBrand />
149
+ {headerHasAccount && (
150
+ <>
151
+ <div className="flex-1" />
152
+ <AccountMenu />
153
+ </>
154
+ )}
133
155
  </SidebarHeader>
134
156
  )}
135
157
  <SidebarContent>
@@ -151,6 +173,8 @@ export function AcmeSidebar({
151
173
  </SidebarGroupContent>
152
174
  </SidebarGroup>
153
175
  </SidebarContent>
176
+ {/* Footer 只 primary-sidebar(帳號在 sidebar 底);primary-header 帳號改放 SidebarHeader 右(見上),
177
+ mobile 也不放 footer(誤用他 mode 慣例)。 */}
154
178
  {includeUserFooter && (
155
179
  <SidebarFooter>
156
180
  <UserFooter />
@@ -73,8 +73,9 @@ export const UsageGuidance: Story = {
73
73
  ),
74
74
  }
75
75
 
76
- // 佈局模式選型 deep-dive — 內容錨定 app-shell.spec.md「兩 mode 差異」(L93-94 / L105 常見誤解)
77
- // +「WorkspaceBrand 放置 SSOT(L112-119);category-templates.md component-specific `{Topic}Rule` idiom
76
+ // 佈局模式選型 deep-dive — 內容錨定 app-shell.spec.md 的「Layout mode mode 差異」+「WorkspaceBrand 放置 SSOT」
77
+ // +「帳號入口(Account entry)放置 SSOT」段(均含 Responsive 精修子句;用段名錨定不寫死行號避免 drift)。
78
+ // category-templates.md component-specific `{Topic}Rule` idiom
78
79
  export const LayoutModeRule: Story = {
79
80
  name: '佈局模式怎麼選',
80
81
  render: () => (
@@ -64,7 +64,7 @@ benchmark:
64
64
  </AppShell>
65
65
  ```
66
66
 
67
- Sub-component:`<AppShellAside title={...} width={400}>`(width consumer 自決 — `number` 或 `{ md, xl }` breakpoint-keyed,clamp `min-width: 240` / `max-width: 640`;**title prop required**,modal mode 走 Sheet → `aria-labelledby` 強制,per `sheet.spec.md:98`)。
67
+ Sub-component:`<AppShellAside title={...} width={400}>`(width consumer 自決 — `number` 或 `{ md, xl }` breakpoint-keyed,clamp `min-width: 240` / `max-width: 640`;**title prop required**,modal mode 走 Sheet → `aria-labelledby` 強制,per `sheet.spec.md`「禁止事項」(無 title → aria-labelledby 強制))
68
68
 
69
69
  ### Hook export:`useAppShell()`(2026-05-21 D2 codify per Phase B codex catch)
70
70
 
@@ -118,6 +118,8 @@ function CustomAside() {
118
118
 
119
119
  **Rule:WorkspaceBrand 只能出現一次**(視覺 SSOT)。`primary-header` mode 重複放(同時在 globalHeader + SidebarHeader)= 視覺冗餘 + 跨產品識別混淆 → **禁止**。
120
120
 
121
+ **Responsive 精修(mobile-Sheet 子句,2026-06-18)**:「只能出現一次」= **同一時間一次,「一次」per breakpoint 判定**。Desktop(≥768px)brand 在 globalHeader、sidebar 內無 SidebarHeader。Mobile(<768px)sidebar 收成 Sheet(`sidebar.tsx` mobile 分支,z-50)打開時**蓋住 globalHeader** → brand 看不到、drawer 變孤兒 → 此時在 Sheet 內補 `<SidebarHeader><WorkspaceBrand/>`(globalHeader 既被蓋住,仍「同一時間一次」,非重複)。機制:consumer 讀既有 `useSidebar().isMobile`,mobile 才條件渲染**同一組 primitive**(style SSOT,與 primary-sidebar 外觀一致,非手刻對齊)。Reference:`_demo-helpers.tsx` `AcmeSidebar`(`includeWorkspaceBrand || isMobile`)。**禁止**:desktop primary-header 同時 globalHeader + SidebarHeader 放 brand(那才是真重複)。對齊 Material modal navigation drawer(手機 drawer 從 top app bar 開、蓋住內容、頂放品牌)+ Gmail / GitHub mobile 選單。
122
+
121
123
  **Slack 為 mixed pattern 例外**(thin workspace rail + channels sidebar 有 workspace name)— 不採用此分類,DS 採 **GitHub / Gmail / Figma** 共識(globalHeader 存在 → sidebar 內無 header)。
122
124
 
123
125
  **Consumer 實作 pattern**:
@@ -145,6 +147,8 @@ function CustomAside() {
145
147
 
146
148
  **Rule:帳號入口只能出現一次**(視覺 SSOT,同 WorkspaceBrand)。`primary-header` mode 同時放(globalHeader 右 + sidebar footer)= 視覺冗餘 + 入口混淆 → **禁止**;`primary-header` 的 sidebar **不放** user footer(與上方「globalHeader 存在 → sidebar 不重複 chrome 角色」同源邏輯)。
147
149
 
150
+ **Responsive 精修(mobile-Sheet 子句,per breakpoint;2026-06-18 修正為「鏡像自己桌面的帳號家」)**:mobile(<768px)sidebar 收成 Sheet 蓋住 globalHeader(桌面帳號入口也在 globalHeader 右)→ **帳號入口鏡像桌面位置搬進 Sheet header 右**:在 `<SidebarHeader>` 內放 `<WorkspaceBrand/>`(左)+ `flex-1` + `<AccountMenu/>`(右)= 把整條 globalHeader 原封搬進 Sheet 頂排(SidebarHeader 即 ChromeHeader 基底,與 GlobalHeader 同 primitive → 帳號 avatar 24px + 右緣距 `--layout-space-loose` 由 construction 保證,**非手刻對齊**)。**不在 Sheet 底放 `<SidebarFooter>`**(footer 是 primary-sidebar 的帳號家慣例;primary-header 任何 breakpoint 帳號家都在 header 區)。Consumer 讀 `useSidebar().isMobile` + `useAppShell().layout`(`headerHasAccount = layout === 'primary-header' && isMobile`),desktop 維持無 header/footer。對齊 Material modal navigation drawer 官方「account switcher 放 drawer header 區(與 logo 同區、pinned、最高優先),footer 區放 settings/support 次要項」(<https://m3.material.io/components/navigation-drawer>)+ Gmail / GitHub mobile 選單頂部帳號慣例。**為何不放 footer**:使用者開 Sheet 看到的頂排 = 原本被蓋住的 top bar(brand 左 / 帳號右),空間記憶不斷;把帳號丟到 footer = 誤套 primary-sidebar 慣例(那 mode 桌面帳號本就在底)。Reference:`_demo-helpers.tsx` `AcmeSidebar`。
151
+
148
152
  **為什麼 primary-header 帳號入口在「右上」**:GitHub / Gmail / Slack / Atlassian 的全域標頭一律把「自己的帳號」放右上(品牌在左、帳號在右,左右對稱)= global chrome 標準位置,不是 sidebar footer。
149
153
 
150
154
  **入口開什麼 = 帳號選單(`<DropdownMenu>`),不是 ProfileCard**:
@@ -162,14 +166,14 @@ function CustomAside() {
162
166
 
163
167
  **常見誤解:「multi-workspace 就必須 primary-header 派」** — workspace 多寡跟 layout 派别無相關性(world-class 反證:Linear / Notion / Figma 皆 multi-workspace 卻用 primary-sidebar;Gmail 多 account 用 primary-header)。選 mode = 表態「Header 是 local 還是 global」,**不是**「workspace 是 single 還是 multi」。
164
168
 
165
- **Sidebar toggle 按鈕位置**(消費既有 `sidebar.spec.md:308-360` SidebarTrigger pattern,**不發明新 toggle**):
169
+ **Sidebar toggle 按鈕位置**(消費既有 `sidebar.spec.md`「SidebarTrigger 位置(兩種 canonical pattern)」段,**不發明新 toggle**):
166
170
 
167
171
  | Mode | 對應 Sidebar pattern | Toggle 位置 |
168
172
  |---|---|---|
169
173
  | `primary-sidebar` | `sidebar.spec.md` Pattern A(無 global top bar) | 主內容 header 最左 |
170
174
  | `primary-header` | `sidebar.spec.md` Pattern B(有 global top bar) | global top bar 最左 |
171
175
 
172
- **唯一 invariant**(`sidebar.spec.md:310` 既有):trigger 必在 sidebar 任何 state(offcanvas / icon / expanded)下都可見 — 收合後 sidebar 不見了,toggle 不可能留在 sidebar 內(會跟著消失)。兩 mode 結論都落在 **Header 最左**,只是該 Header 是 local toolbar(Pattern A)還是 global bar(Pattern B)。Consumer 直接 `<SidebarTrigger />` 塞 Header 最左,AppShell 不 wrap / 不發明。
176
+ **唯一 invariant**(`sidebar.spec.md`「SidebarTrigger 位置」段既有):trigger 必在 sidebar 任何 state(offcanvas / icon / expanded)下都可見 — 收合後 sidebar 不見了,toggle 不可能留在 sidebar 內(會跟著消失)。兩 mode 結論都落在 **Header 最左**,只是該 Header 是 local toolbar(Pattern A)還是 global bar(Pattern B)。Consumer 直接 `<SidebarTrigger />` 塞 Header 最左,AppShell 不 wrap / 不發明。
173
177
 
174
178
  **層級語意差異**:`primary-sidebar` 的 Header scope = local(當前頁);`primary-header` 的 Header scope = global(整 app)。兩者是不同 product 角色,**不互通**。Consumer 選 mode = 表態 product 是哪派。
175
179
 
@@ -201,7 +205,7 @@ function CustomAside() {
201
205
 
202
206
  **Modal overlay** 行為:
203
207
  - 消費既有 `sheet.spec.md` canonical(從右滑出 + Esc 關 + click-outside 關 + focus trap + restore focus)
204
- - **title prop required**(per `sheet.spec.md:98` 禁無 title — `aria-labelledby` 強制)
208
+ - **title prop required**(per `sheet.spec.md`「禁止事項」禁無 title — `aria-labelledby` 強制)
205
209
  - 跟 Sidebar mobile fallback 同 SSOT
206
210
 
207
211
  **Breakpoint**:消費既有 `useIsNarrowViewport()` hook(`hooks/use-is-narrow-viewport.ts`,`MOBILE_BREAKPOINT = 768`;與 `sidebar.spec.md`「Mobile 行為」段 768px 同值),**不發明新 breakpoint**(非 CSS token — repo 無 `--sidebar-mobile-breakpoint`)。Sidebar + Aside 同步切 Sheet。
@@ -245,7 +249,7 @@ Main 內塞什麼(table / field / card / page header / list)的 layout + spacing
245
249
 
246
250
  - 所有 modal overlay(Dialog / Sheet / Popover / HoverCard)消費既有 `overlay-surface.spec.md` SSOT
247
251
  - Mask 蓋整個 AppShell(包含 sidebar + header + aside + main)
248
- - Z-index:AppShell shell root **不設 z-index**(`app-shell.tsx` root `<div>` 走正常 flow / stacking context,唯一 z-* 是 SkipToMain `focus:z-50`);overlay 走既有 **`z-50` Tailwind utility**(`Sheet.tsx:53/69` SSOT canonical,**不發明 `--z-overlay` token** — repo 內無此 token,Sheet 直接 `z-50`)
252
+ - Z-index:AppShell shell root **不設 z-index**(`app-shell.tsx` root `<div>` 走正常 flow / stacking context,唯一 z-* 是 SkipToMain `focus:z-50`);overlay 走既有 **`z-50` Tailwind utility**(`sheet.tsx` SheetOverlay / SheetContent `z-50` SSOT canonical,**不發明 `--z-overlay` token** — repo 內無此 token,Sheet 直接 `z-50`)
249
253
  - AppShell **不** export `modalOpen` prop,overlay 自管 open / close state
250
254
 
251
255
  ---
@@ -254,7 +258,7 @@ Main 內塞什麼(table / field / card / page header / list)的 layout + spacing
254
258
 
255
259
  | Shortcut | Action | Cite |
256
260
  |---|---|---|
257
- | **`⌘B`(macOS)/ `Ctrl+B`(Windows)** | Toggle sidebar | `sidebar.spec.md:348` SSOT「industry-standard,已內建,不該改」;`sidebar.tsx:63` code key `"b"` |
261
+ | **`⌘B`(macOS)/ `Ctrl+B`(Windows)** | Toggle sidebar | `sidebar.spec.md`「持久化與快捷鍵」SSOT(⌘B / Ctrl+B industry-standard,已內建,不該改);`sidebar.tsx` code key `"b"` |
258
262
  | **`⌘.`(macOS)/ `Ctrl+.`(Windows)** | Toggle aside | Linear convention(新加) |
259
263
  | **Skip-to-main link** | `Tab` 第一站 focus 「Skip to content」link → `main` | A11y WCAG 2.4.1 bypass blocks;對齊 Atlassian Layout skip-link |
260
264
 
@@ -305,7 +309,7 @@ Main 內塞什麼(table / field / card / page header / list)的 layout + spacing
305
309
  | Header height | `--chrome-header-height`(`tokens/uiSize/uiSize.spec.md`)|
306
310
  | Aside width | consumer 自傳 prop(無 token)|
307
311
  | Layout spacing | `layoutSpace` 全 family(`--layout-space-{tight,loose,bottom}`)|
308
- | Z-index | shell root 不設 z-index;overlay 走 Sheet `z-50` Tailwind utility(`sheet.tsx:53/69`,無 `--z-overlay` token)|
312
+ | Z-index | shell root 不設 z-index;overlay 走 Sheet `z-50` Tailwind utility(`sheet.tsx` SheetOverlay / SheetContent,無 `--z-overlay` token)|
309
313
  | Sheet fallback | `sheet.spec.md` SSOT |
310
314
  | Overlay | `overlay-surface.spec.md` SSOT |
311
315
 
@@ -350,3 +354,4 @@ Main 內塞什麼(table / field / card / page header / list)的 layout + spacing
350
354
  > 本節由 `scripts/add-reciprocal-pointers.mjs` 自動維護,列出在 SSOT 語境下指向本 spec 的其他 spec。若要手動補充,寫在本節之前。
351
355
 
352
356
  - `header-canonical.spec.md`
357
+ - `sidebar.spec.md`
@@ -384,6 +384,12 @@ export const PrimarySidebarWithTabs: Story = {
384
384
  * - **Main col 仍有 local header(PageHeader)** — 當前頁 title / breadcrumb / page actions
385
385
  * 對齊 GitHub repo header / Slack channel header / Gmail email-list toolbar 2-layer 慣例
386
386
  * - Sidebar 內**不 render WorkspaceBrand**(`includeWorkspaceBrand={false}`),avoid 重複(已在 globalHeader)
387
+ * - **小螢幕(<768px)responsive**:sidebar 收成 Sheet 打開時蓋住 globalHeader → AcmeSidebar 偵測
388
+ * `useSidebar().isMobile` + `useAppShell().layout` 把整條 globalHeader 搬進 Sheet 的 SidebarHeader
389
+ * (brand 左 + 帳號 avatar 右,鏡像桌面 global bar;SidebarHeader = ChromeHeader 基底 → 結構 SSOT、非手刻)。
390
+ * **不放 SidebarFooter**(primary-header 帳號家在 header 右,非 footer — footer 是 primary-sidebar 慣例)。
391
+ * desktop 維持無 header/footer。見 app-shell.spec.md「帳號入口(Account entry)放置 SSOT」的「Responsive 精修」子句。
392
+ * (在 Storybook 縮窄 viewport < 768px 即可見 Sheet header 一排品牌 + 帳號。)
387
393
  */
388
394
  export const PrimaryHeader: Story = {
389
395
  name: '主標頭佈局 — 全域+本地兩層(GitHub/Gmail/Slack 派)',
@@ -598,6 +598,8 @@ Consumer 不需要任何額外 code——只要加一個 prop:
598
598
 
599
599
  Sheet 開啟狀態**不持久化**。
600
600
 
601
+ **Sheet 蓋住 global top bar → primary-header consumer 須在 Sheet 內補品牌/帳號**:Sheet(z-50)打開時覆蓋整個左側含 global top bar。若你是 `AppShell` `primary-header` 派(品牌 logo + 帳號入口放在 globalHeader、sidebar 平常無 header/footer),小螢幕 Sheet 打開後 globalHeader 被蓋住 → 使用者看不到品牌也按不到帳號。解法:consumer 讀 `useSidebar().isMobile` + `useAppShell().layout`,**mobile 時才**在 `<SidebarHeader>` 內放 `<WorkspaceBrand/>`(左)+ `flex-1` + `<AccountMenu/>`(右)= 把整條 globalHeader 鏡像進 Sheet 頂排(SidebarHeader 即 ChromeHeader 基底,結構 SSOT);**不放 `<SidebarFooter>`**(primary-header 帳號家在 header 區,footer 是 primary-sidebar 慣例)。**rule owner = `../AppShell/app-shell.spec.md`「帳號入口(Account entry)放置 SSOT」的 Responsive 精修子句**(本處不重述,Rule-of-3)。reference:`AppShell/_demo-helpers.tsx` `AcmeSidebar`。
602
+
601
603
  ---
602
604
 
603
605
  ## 持久化與快捷鍵