@qijenchen/design-system 0.1.0-beta.59 → 0.1.0-beta.60

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/CLAUDE.md CHANGED
@@ -177,13 +177,13 @@ CLAUDE.md target ≤ 200(Anthropic best-practice)/ transition ≤ 400 / hard cap
177
177
  | shadcn compat alias 回流 | dark mode 不聯動 |
178
178
  | `asChild ? Slot : Native` 內部 JSX 仍渲染多 children | React.Children.only runtime fail — Slot 規範 children 必為單 element,內部 `{icon}{label}{badge}` 多 expression 變 array → throw。asChild 分支 render 只傳 consumer child;tsc/build 過,story 打開才炸 |
179
179
  | `tsc -b` 不 emit declaration | 漏 TS4023「cannot be named」;改 export/型別 surface 的 deploy-safety 必跑 `npm run build:lib`(= Netlify build:dts emit .d.ts),tsc -b 全綠會騙過(2026-06-05 Badge union 連掛 3 Netlify build)|
180
+ | `rsync -a` 對「等長 + 同秒」檔靜默跳過 | `-a` 用 size+mtime quick-check;clone 與 build 同秒寫、版本字串等長(`^…beta.NN` 恆 130B)→ 判定沒變不複製 → ship stale。必 `--checksum` + 寫後 fail-closed 斷言(2026-06-07 mirror beta.56-58 連環 ship stale package.json,3-agent 對抗調查 + 本地 `-a` vs `-ac` 復現才抓到)|
180
181
 
181
182
  新 bug → 歸 Meta-Pattern OR 本表 1 行;> 10 條 = 漏寫,**評估 meta-merge 既有 M-rule 而非無腦新增**(meta-patterns velocity ≤ 3/quarter,單 M-rule 必吸收 ≥ 3 prior bugs)。
182
183
 
183
184
  # 專案 Stack
184
185
 
185
- Vite + React + TypeScript + Tailwind v4 + shadcn/ui + Storybook + 自訂 Design Token
186
- 必要檔案:`index.html` / `src/main.tsx` / `src/globals.css` / `vite.config.ts` / `package.json` / `tsconfig.json`。
186
+ Vite + React + TypeScript + Tailwind v4 + shadcn/ui + Storybook + 自訂 Design Token。必要檔案:`index.html` / `src/main.tsx` / `src/globals.css` / `vite.config.ts` / `package.json` / `tsconfig.json`。
187
187
  完整路徑 + Token 系統 → `packages/design-system/src/tokens/README.md`(charter)— Phase 1 後 DS 內化在 npm workspace。
188
188
 
189
189
  # Path-scoped rules(2026 Anthropic 推薦)
@@ -36,11 +36,14 @@ CMD=$(echo "$INPUT" | jq -r '.tool_input.command // ""' 2>/dev/null)
36
36
  [ "$TOOL" != "Bash" ] && exit 0
37
37
 
38
38
  # Heuristic:detect `git push origin <branch>` patterns
39
- # 2026-06-06 fix:要求 `git push` 出現在「命令邊界」(行首 / ; / && / || / $( ),
40
- # 否則 `P='git push origin'` 賦值、`echo "git push origin"`、`grep 'git push origin'`
41
- # 「字串裡提到 push」會誤觸發 falls-back current branch 404 preview URL 注入 context。
42
- # 命令邊界 = 真的在執行 push,字串提及 = 噪音。對齊「只在真推送時 relay deploy URL」root invariant
43
- if ! echo "$CMD" | grep -qE '(^|[;&|(])[[:space:]]*git[[:space:]]+push[[:space:]]+(-u[[:space:]]+)?origin\b'; then
39
+ # 2026-06-06 fix:要求 `git push` 在「命令邊界」(行首 / ; / && / || / $( )擋字串提及(`P='git push origin'`)。
40
+ # 2026-06-07 fix(對抗 sweep):容忍 env-prefix(`GIT_SSH_COMMAND=… git push`)/ `git -C <dir>` /
41
+ # push 前後任意 flag(`--force-with-lease` / `-f` / `--set-upstream` / `--no-verify` 等)。原本只認 `-u`
42
+ # fire 在這些「真實 push」上 = 該吐 URL 卻沒吐,defeat hook purpose
43
+ _ENVP='([A-Za-z_][A-Za-z0-9_]*=[^[:space:]]+[[:space:]]+)*'
44
+ _GITPRE='git[[:space:]]+(-C[[:space:]]+[^[:space:]]+[[:space:]]+|-[A-Za-z][^[:space:]]*[[:space:]]+)*push'
45
+ _PUSHFLAGS='([[:space:]]+-{1,2}[A-Za-z][^[:space:]]*)*'
46
+ if ! echo "$CMD" | grep -qE "(^|[;&|(])[[:space:]]*${_ENVP}${_GITPRE}${_PUSHFLAGS}[[:space:]]+origin\b"; then
44
47
  exit 0
45
48
  fi
46
49
 
@@ -50,21 +53,35 @@ if echo "$CMD" | grep -qE 'push\s+origin\s+--delete'; then
50
53
  fi
51
54
 
52
55
  CWD=$(pwd)
56
+ # 2026-06-07 fix:偵測「真實 working dir」作 deploy-target 偵測根,跨 repo push 不吐「錯 repo」URL
57
+ # (anchor:syncing ds-product-template 時吐成 DS 站台 URL)。優先 `git -C <dir>`;否則命令鏈中
58
+ # 「最後一個 cd」(= push 時 effective cwd,**非第一個**;`echo x && cd /other && git push` 必用 /other)。
59
+ _GITC=$(echo "$CMD" | grep -oE 'git[[:space:]]+-C[[:space:]]+[^[:space:]]+' | head -1 | sed -E 's/.*-C[[:space:]]+//; s/^"//; s/"$//')
60
+ _LASTCD=$(echo "$CMD" | grep -oE '(^|[;&|(])[[:space:]]*cd[[:space:]]+[^;&|]+' | tail -1 | sed -E 's/.*cd[[:space:]]+//; s/[[:space:]]+$//; s/^"//; s/"$//')
61
+ for _d in "$_GITC" "$_LASTCD"; do
62
+ if [ -n "$_d" ] && [ -d "$_d" ] && git -C "$_d" rev-parse --git-dir >/dev/null 2>&1; then CWD="$_d"; break; fi
63
+ done
53
64
  URLS_FOUND=""
54
65
  # 2026-06-07 ROOT fix:只從「git push ... origin <ref>」這一段擷取 branch,不是整條 compound command 的
55
66
  # 第一個 `origin X`。否則 `git fetch origin --quiet && git push origin main` 會誤抓 fetch 段的 `--quiet`
56
67
  # 當 branch → 推導 `--quiet--site` 404(此前 5 道 guard 都沒擋到,因 `--quiet` 全是合法 git ref 字元)。
57
68
  # 隔出 push 段(到下個 command 分隔 ; & | 為止)後,取 origin 後第一個 token。
58
- PUSH_SEG=$(echo "$CMD" | grep -oE '(^|[;&|(])[[:space:]]*git[[:space:]]+push[[:space:]][^;&|]*' | head -1)
69
+ # PUSH_SEG 用同一 env/-C/flag-tolerant prefix 隔出 push 段(到下個分隔 ; & | 為止),再取 origin 後第一 token
70
+ PUSH_SEG=$(echo "$CMD" | grep -oE "(^|[;&|(])[[:space:]]*${_ENVP}${_GITPRE}[^;&|]*" | head -1)
59
71
  BRANCH=$(echo "$PUSH_SEG" | grep -oE 'origin[[:space:]]+[^[:space:]]+' | head -1 | awk '{print $2}')
60
72
  # origin 後第一個 token 是 flag(`git push origin --force` = 無顯式 branch)→ 清空走 current-branch fallback
61
73
  case "$BRANCH" in -*) BRANCH="" ;; esac
62
74
  # 2026-06-06 fix:refspec `src:dst` → 取 dst(`HEAD:main` → `main`),否則推導出 `HEAD:main--site` 404 URL
63
75
  case "$BRANCH" in *:*) BRANCH="${BRANCH##*:}" ;; esac
64
- # 2026-06-06 fix:`git push origin HEAD`(或 `@`)= symbolic ref 指向當前 branch → 解析成真 branch 名,
65
- # 否則把字面 "HEAD" 當 branch 推導出 `HEAD--site` 404 URL(real push 常用此式,會誤吐)。
76
+ # 2026-06-07 fix:full-ref dst `HEAD:refs/heads/main` `main`(否則 `refs/heads/main--site` 誤判 preview);
77
+ # `refs/tags/...` tag push,skip
78
+ case "$BRANCH" in refs/tags/*) exit 0 ;; esac
79
+ BRANCH="${BRANCH#refs/heads/}"
80
+ # 2026-06-06 fix:`git push origin HEAD`(或 `@`)= symbolic ref 指向當前 branch → 解析成真 branch 名。
81
+ # 2026-06-07 fix:detached HEAD 時 show-current 回「空字串 + exit 0」→ BRANCH 留空,交給下方 value-based fallback
82
+ # (原 `|| echo ""` 沒用,因 git 不是失敗而是成功回空)。
66
83
  if [ "$BRANCH" = "HEAD" ] || [ "$BRANCH" = "@" ]; then
67
- BRANCH=$(git -C "$CWD" branch --show-current 2>/dev/null || echo "")
84
+ BRANCH=$(git -C "$CWD" branch --show-current 2>/dev/null)
68
85
  fi
69
86
  # 2026-06-06 fix:BRANCH 含非法 git ref 字元(" ' 空白 \ 等)→ 此命令只是「字串裡含 git push origin」
70
87
  # (測試迴圈 / 文件 / echo),非真推送 → skip,避免把 `main"` 等垃圾推導成 404 URL 注入 context。
@@ -74,7 +91,10 @@ if [ -n "$BRANCH" ] && ! echo "$BRANCH" | grep -qE '^[A-Za-z0-9._/-]+$'; then ex
74
91
  # 否則把 tag 名當 branch 推導出 `v0.1.0-beta.56--site` 404 URL(release tag push 每次都誤吐)
75
92
  if echo "$BRANCH" | grep -qE '^v[0-9]'; then exit 0; fi
76
93
  if [ -n "$BRANCH" ] && git -C "$CWD" rev-parse --verify --quiet "refs/tags/$BRANCH" >/dev/null 2>&1; then exit 0; fi
77
- [ -z "$BRANCH" ] && BRANCH=$(git -C "$CWD" branch --show-current 2>/dev/null || echo "main")
94
+ # 2026-06-07 fix:仍空(bare `git push origin` / detached HEAD / 解析不出)→ current branch(value-based);
95
+ # 仍空 → 無法產生合法 URL(會變 `https://--site` malformed)→ skip,不亂猜 main。
96
+ [ -z "$BRANCH" ] && BRANCH=$(git -C "$CWD" branch --show-current 2>/dev/null)
97
+ [ -z "$BRANCH" ] && exit 0
78
98
 
79
99
  # v3 2026-05-27:curl HEAD verify URL before reporting(per user「你確定有做到」complaint)
80
100
  # v4 2026-05-27:add content sniff(防 squat URLs 200 但 unrelated content)
@@ -147,18 +167,28 @@ if [ -z "$URLS_FOUND" ] && [ -f "$CWD/netlify.toml" ]; then
147
167
  if [ -n "$REPO_NAME" ]; then CANDIDATES="$CANDIDATES https://${REPO_NAME}.netlify.app"; fi
148
168
  fi
149
169
 
150
- REAL_URL=""
170
+ REAL_URL=""; REAL_NOTE=""
151
171
  if [ "$BRANCH" = "main" ] || [ "$BRANCH" = "master" ]; then
172
+ # 2026-06-07 fix:優先「200 + Storybook content」;沒有則退「200(內容非 Storybook)」,
173
+ # **不丟棄**已驗 200 的 candidate。anchor:apps/template 是真 product app(非 Storybook)→ 原
174
+ # is_storybook_deploy 把活著的 200 deploy 誤當 squat 報「全 404」,違反 user「不管是否 production 都給連結」。
175
+ FALLBACK_URL=""
152
176
  for candidate in $CANDIDATES; do
153
- if [ "$(verify_url "$candidate")" = "OK" ] && is_storybook_deploy "$candidate"; then
154
- REAL_URL="$candidate"
155
- break
177
+ if [ "$(verify_url "$candidate")" = "OK" ]; then
178
+ if is_storybook_deploy "$candidate"; then
179
+ REAL_URL="$candidate"; REAL_NOTE="✅ verified 200 + Storybook content"; break
180
+ elif [ -z "$FALLBACK_URL" ]; then
181
+ FALLBACK_URL="$candidate"
182
+ fi
156
183
  fi
157
184
  done
185
+ if [ -z "$REAL_URL" ] && [ -n "$FALLBACK_URL" ]; then
186
+ REAL_URL="$FALLBACK_URL"; REAL_NOTE="✅ verified 200(內容非 Storybook — 可能 product app deploy)"
187
+ fi
158
188
  if [ -n "$REAL_URL" ]; then
159
- URLS_FOUND="${URLS_FOUND}🚀 Netlify PRODUCTION(${BRANCH}): ${REAL_URL} ✅ verified 200 + Storybook content\n"
189
+ URLS_FOUND="${URLS_FOUND}🚀 Netlify PRODUCTION(${BRANCH}): ${REAL_URL} ${REAL_NOTE}\n"
160
190
  else
161
- URLS_FOUND="${URLS_FOUND}🚀 Netlify PRODUCTION URL 未驗 — tried: ${CANDIDATES// /, }\n ⚠️ 全 404 OR squat。需要 user 手動 share dashboard URL,OR 創 \$HOME/.claude/local/deploy-targets.json:\n {\"${OWNER_REPO}\": \"https://<actual-site>.netlify.app\"}\n"
191
+ URLS_FOUND="${URLS_FOUND}🚀 Netlify PRODUCTION URL 未驗 — tried: ${CANDIDATES// /, }\n ⚠️ 全 404。需要 user 手動 share dashboard URL,OR 創 \$HOME/.claude/local/deploy-targets.json:\n {\"${OWNER_REPO}\": \"https://<actual-site>.netlify.app\"}\n"
162
192
  fi
163
193
  else
164
194
  # Branch preview:always use `<branch>--<sitename>` pattern,but sitename unknown unless USER_OVERRIDE
@@ -41,8 +41,17 @@ head_leaked() { echo "$STDOUT" | grep -qE 'HEAD(--|:|\))'; }
41
41
  fn7() { echo "$STDOUT" | grep -q "feature-test" && ! head_leaked; }
42
42
  fn8() { ! head_leaked; }
43
43
  fn9() { echo "$STDOUT" | grep -q "feature-test"; }
44
- has_feature() { echo "$STDOUT" | grep -q "feature-test"; }
45
- no_quiet() { ! echo "$STDOUT" | grep -q -- "--quiet"; }
44
+ has_feature() { echo "$STDOUT" | grep -q "feature-test"; }
45
+ no_quiet() { ! echo "$STDOUT" | grep -q -- "--quiet"; }
46
+ no_refsheads() { ! echo "$STDOUT" | grep -q "refs/heads"; }
47
+ # minimal git repo on branch $2 (+ optional netlify.toml if $3=netlify), at dir $1
48
+ mkrepo() {
49
+ ( cd "$1" || exit 1
50
+ git init -q -b "$2" 2>/dev/null || { git init -q; git checkout -q -b "$2"; }
51
+ : > f; [ "${3:-}" = netlify ] && : > netlify.toml
52
+ git add -A 2>/dev/null
53
+ GIT_AUTHOR_NAME=t GIT_AUTHOR_EMAIL=t@t GIT_COMMITTER_NAME=t GIT_COMMITTER_EMAIL=t@t git commit -q -m i 2>/dev/null )
54
+ }
46
55
  fn10() { has_feature && no_quiet; } # compound:fetch 段的 --quiet 不可被誤抓為 branch
47
56
  fn11() { has_feature; } # compound:pull 段的 main 不可被誤抓(否則 grep feature-test 失敗)
48
57
  fn12() { has_feature; } # bare push origin → fallback current branch
@@ -121,6 +130,55 @@ check "12 bare push origin → fallback current branch" fire 'fn12'
121
130
  run_hook "git push origin --force"
122
131
  check "13 flag-after-origin → fallback current branch (no flag-as-branch)" fire 'fn13'
123
132
 
133
+ # 14. cross-repo cd-parse:command `cd <other-repo> && git push` → hook 用 cd'd repo 作偵測根
134
+ # (2026-06-07 anchor:syncing ds-product-template 時 hook 吐成 DS 站台 URL)。
135
+ # TMP2 無 netlify.toml → 該 SKIP;若沒 cd-parse、誤用 $TMP〔有 netlify.toml〕→ 會 inject(=回歸)。
136
+ TMP2=$(mktemp -d)
137
+ (
138
+ cd "$TMP2" || exit 1
139
+ git init -q -b other-branch 2>/dev/null || { git init -q; git checkout -q -b other-branch; }
140
+ : > somefile
141
+ git add -A 2>/dev/null
142
+ GIT_AUTHOR_NAME=t GIT_AUTHOR_EMAIL=t@t GIT_COMMITTER_NAME=t GIT_COMMITTER_EMAIL=t@t git commit -q -m init 2>/dev/null
143
+ )
144
+ run_hook "cd $TMP2 && git push origin other-branch"
145
+ check "14 cross-repo cd-parse → uses cd'd repo (no deploy target there → skip, not \$TMP's URL)" skip
146
+ rm -rf "$TMP2"
147
+
148
+ # 15. flag-before-origin `--force-with-lease`(原 trigger 只認 -u → 漏 fire 真實 push)→ fire
149
+ run_hook "git push --force-with-lease origin feature-test"
150
+ check "15 flag-before-origin (--force-with-lease) → fire" fire 'has_feature'
151
+
152
+ # 16. env-prefix `GIT_SSH_COMMAND=… git push`(原被命令邊界擋掉)→ fire
153
+ run_hook "GIT_SSH_COMMAND=ssh git push origin feature-test"
154
+ check "16 env-prefixed git push → fire" fire 'has_feature'
155
+
156
+ # 17. --set-upstream(-u 的長形,原 trigger 漏)→ fire
157
+ run_hook "git push --set-upstream origin feature-test"
158
+ check "17 --set-upstream → fire" fire 'has_feature'
159
+
160
+ # 18. `git -C <other-repo> push` → 用 -C 的 repo 作偵測根(無 netlify → skip,不吐 \$TMP 的 URL)
161
+ TMP3=$(mktemp -d); mkrepo "$TMP3" ob
162
+ run_hook "git -C $TMP3 push origin ob"
163
+ check "18 git -C <repo> → uses that repo (no deploy target → skip)" skip
164
+ rm -rf "$TMP3"
165
+
166
+ # 19. detached HEAD `git push origin HEAD` → branch 解析空 → skip(不吐 https://--site malformed)
167
+ TMP4=$(mktemp -d); mkrepo "$TMP4" m netlify; ( cd "$TMP4" && git checkout -q --detach 2>/dev/null )
168
+ STDOUT=$( cd "$TMP4" && HOME="$TMP4/home" CLAUDE_PROJECT_DIR="$TMP4" bash "$HOOK" <<<'{"hook_event_name":"PostToolUse","tool_name":"Bash","tool_input":{"command":"git push origin HEAD"}}' 2>&1 ); EXIT=$?
169
+ if [ "$EXIT" = 0 ] && ! injected && ! echo "$STDOUT" | grep -qE -- '--site|\(\)'; then echo " PASS 19 detached HEAD → skip (no malformed URL)"; PASS=$((PASS+1)); else echo " FAIL 19 (exit=$EXIT out=${STDOUT:0:140})"; FAIL=$((FAIL+1)); FAILED="${FAILED}\n - 19"; fi
170
+ rm -rf "$TMP4"
171
+
172
+ # 20. cd-after-command `echo x && cd <other> && git push` → 用該 cd 的 repo(非 session/first)
173
+ TMP5=$(mktemp -d); mkrepo "$TMP5" ob
174
+ run_hook "echo hi && cd $TMP5 && git push origin ob"
175
+ check "20 cd-after-command → uses that cd's repo (no deploy → skip)" skip
176
+ rm -rf "$TMP5"
177
+
178
+ # 21. refspec full-ref `HEAD:refs/heads/main` → 取 main,不洩漏 refs/heads
179
+ run_hook "git push origin HEAD:refs/heads/main"
180
+ check "21 refspec refs/heads → no refs/heads leak" fire 'no_refsheads'
181
+
124
182
  teardown
125
183
 
126
184
  echo ""
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qijenchen/design-system",
3
- "version": "0.1.0-beta.59",
3
+ "version": "0.1.0-beta.60",
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",