Skip to content

refactor(csp): #176 B 案 PR 8 (scope 縮小) — Gs1Databar SVG cleanup + Astro 検出網 + setProperty CSP3 制約記録 ([067]) - #303

Merged
fumtas1k merged 7 commits into
developfrom
feature/issue-176-b8-final-flip
May 8, 2026
Merged

refactor(csp): #176 B 案 PR 8 (scope 縮小) — Gs1Databar SVG cleanup + Astro 検出網 + setProperty CSP3 制約記録 ([067])#303
fumtas1k merged 7 commits into
developfrom
feature/issue-176-b8-final-flip

Conversation

@fumtas1k

@fumtas1k fumtas1k commented May 8, 2026

Copy link
Copy Markdown
Owner

概要

#176 B 案 PR 8 — scope 縮小版。当初は style-src 'unsafe-inline' 削除の最終 flip を予定していたが、実装後の親直接 E2E で ResultTableel.style.setProperty('--col-width', ...) (PR 1.5 由来) が CSP3 仕様で style-src の制御対象であることが判明し、strict 化で 11 件 violation。連続値ゆえ hash 列挙不可のため、ResultTable refactor を別 PR に分離して延期 することにした。

詳細経緯と対応方針は docs/decisions.md [067] を参照。

主な変更 (4 commit)

  1. Gs1Databar SVG currentColor (commit 1/4): dangerouslySetInnerHTML 経由で DOM 注入される SVG <text>style="fill:var(--color-text)"fill="currentColor" + 親 .gs1-svg-container { color: var(--color-text) } に置換。将来の strict 化時に silent drop されるのを防ぐ事前 refactor。
  2. Astro inline style 検出網追加 (commit 2/4): inline-style-migration.test.tssrc/{components,layouts,pages}/**/*.astro glob を並列追加 + 陽性対照 1 件。新規 .astro ファイルで style="..." 属性を持ち込むと検出。
  3. docs/decisions.md [067] 追加 (commit 3/4): ResultTable setProperty の CSP3 制約発覚 + B 案最終 flip を別 PR に延期する design decision を記録。3 つの解 (Constructable Stylesheets / CSS class swap / 'unsafe-hashes') の評価と採用方針 (PR 9 で Constructable Stylesheets 検証 → PR 10 で B 案最終 flip) を含む。
  4. SoT 進捗 table 同期 (commit 4/4): docs/projects/issue-176-b-plan-progress.md を更新し PR 7b ✅ merged + PR 8 (scope 縮小) + 新規 PR 9 (ResultTable refactor) / PR 10 (B 案最終 flip) を追加。

当初計画から削除 (rebase で除外)

backup/pr8-full-original branch で保全 (push しないので消える可能性に注意):

  • _headers + csp.tsstyle-src 'unsafe-inline' 削除 → PR 10 へ延期
  • astro.config.mjs から stripMetaStyleSrc() integration 撤去 → PR 10 へ延期
  • headers.test.ts / meta-csp.test.ts / astro-config-csp.test.ts 群の strict 化 → PR 10 へ延期

関連 PR / issue

#176 B 案進捗 (PR 1〜7b 完了 / PR 8 部分達成):

検証

  • 親直接 npm run test 全 pass (780 tests)
  • 親直接 npm run build 成功 (18 pages)
  • 親直接 npm run test:e2e 全 pass (146 passed / 1 skipped、style-src 'unsafe-inline' 維持のため violation 不発)
  • npx astro check エラーなし
  • npx prettier --check . clean
  • grep -c "style={{" src/ = 0
  • grep -rEn 'style="[^"]*"' src/components/ src/layouts/ src/pages/ = 0

post-mortem (要約、詳細は [067])

  • Root cause: PR 1.5 設計時の前提「CSSOM API (setProperty) は CSP style-src 観点で別経路」が誤り。CSP3 仕様 (https://www.w3.org/TR/CSP3/#directive-style-src) では style-src の制御対象に JavaScript 経由の style modification も含まれる。
  • 検出が遅れた理由: inline-style-migration.test.ts は文字列 regex で違反を検出するが、これは CSP の実評価とは独立。E2E applyProductionCsp gate を導入していた spec は 11 件あるが、いずれも strict 化 の状態で書かれており、strict 化を試みて初めて violation が顕在化した。
  • lessons learned (詳細は [067]): (a) CSSOM API ≠ CSP 対象外、(b) migration test の regex は実 violation を保証しない、(c) ブロッカー検出時は「前提崩壊」と overstating せず解決策を冷静に列挙する。

後続作業 (本 PR merge 後)

  • follow-up issue 起票: ResultTable setProperty の Constructable Stylesheets 化検証 + 実装 (PR 9)
  • follow-up issue 起票: B 案最終 flip 再投入 (PR 10、PR 9 完了後)
  • backup/pr8-full-original branch を保全 (PR 10 の reference として使用するか後で判断)

🤖 Generated with Claude Code

fumtas1k added 6 commits May 8, 2026 13:24
最終 flip + cleanup の brainstorming 結果を spec として保存。
Gs1Databar SVG `currentColor` 化 + CSP `style-src 'unsafe-inline'`
削除 + `stripMetaStyleSrc` 撤去 + test 群 strict 化 + Astro 検出網
追加 + decisions.md [067] + SoT 更新 を 7 commit で畳む計画。

参照: docs/projects/issue-176-b-plan-progress.md (PR 6 必須チェックリスト)
spec (commit 900eb84) を bite-sized step に分解した plan を保存。
7 task (commit 1〜7) + Pre-Task Setup + Post-Implementation 検証
で構成、subagent 1 体 (sonnet) 直列 dispatch を前提とする。

参照: docs/superpowers/specs/2026-05-08-issue-176-b8-final-flip-design.md
…ntColor 化

dangerouslySetInnerHTML 経由で DOM 注入される SVG <text> の
style="fill:var(--color-text)" を fill="currentColor" + 親 div の
color: var(--color-text) で表現。CSP style-src strict 化 (B 案 最終
flip 別 PR で実施予定) 後に silent drop されるのを防ぐ事前 refactor。

- src/utils/gs1-databar.ts: fill="#000000" + style="..." 削除、
  fill="currentColor" 化
- src/components/tools/Gs1Databar.tsx: 親要素に .gs1-svg-container 付与
- src/styles/global.css: @layer components に .gs1-svg-container 追加
PR 7a / 7b で Astro inline style 65 件全廃後の永続的回帰防止網。
新規 .astro ファイルで style="..." 属性を持ち込むと CSP violation で
silent drop されるため、自動検出網に組み込む。

- src/{components,layouts,pages}/**/*.astro を glob 対象に
- regex `\sstyle\s*=\s*"[^"]*"`: 前置スペース必須で <style> block と区別
- 陽性対照 1 件追加 (悪意ある style 属性が確実に検出されることを assert)
…CSP3 制約発覚 + 延期記録

PR 8 で `style-src 'self'` strict 化を試みた E2E で 11 件 violation
発覚。ResultTable.tsx の el.style.setProperty('--col-width', ...)
(PR 1.5 由来) が CSP3 仕様で style-src の制御対象であり、'unsafe-inline'
/ hash / nonce が必要。連続値ゆえに hash 列挙不可。

決定: ResultTable refactor (Constructable Stylesheets 候補) を PR 9 で
先行、B 案最終 flip は PR 10 へ延期。本 PR は scope 縮小し Gs1Databar
SVG cleanup + Astro 検出網 + 本 entry 記録のみで merge。

Lessons learned (詳細は [067] 本文):
- CSSOM API ≠ CSP style-src 対象外 という PR 1.5 当時の前提は誤り
- migration test の検出 regex は実 violation を保証しない
- ブロッカー検出時は「前提崩壊」と overstating せず解決策を冷静に列挙
進捗 table を更新し、PR 7b を merged 状態 (#299 87d705a) + PR 8 を
scope 縮小状態 + PR 9 (ResultTable refactor) / PR 10 (B 案最終 flip)
を新規追加。

着手済 PR 履歴セクションに PR 8 (scope 縮小)、PR 9 (新規)、PR 10
(新規) のメモを追記。setProperty CSP3 制約発覚の経緯と post-mortem
は decisions.md [067] へ集約。

本 PR (PR 8) は spec / plan の commit 0/-1 + 4 commit (Gs1Databar /
Astro 検出網 / decisions [067] / 本 SoT 同期) で構成。元 spec / plan
の 7 commit 計画は backup/pr8-full-original branch に保全。
@github-actions

github-actions Bot commented May 8, 2026

Copy link
Copy Markdown
Contributor

🖼️ Visual Regression Test 結果

  • Status: ✅ 全 36 件 pass
  • Workflow run: 25539333797
  • Artifact (diff 画像 / playwright-report): 上記 workflow run の Artifacts セクションから download

diff が 意図的な visual 変更の場合: Update Visual Regression Baseline workflow を本 PR ブランチで workflow_dispatch trigger して baseline を更新。
diff が 意図しない regression の場合: 該当変更を fix。
本 check は required ではないため fail のままでも merge は可能(reviewer 判断)。

@fumtas1k

fumtas1k commented May 8, 2026

Copy link
Copy Markdown
Owner Author

PR #303 レビュー — 多角評価 (security / frontend / logic / 規約)

複数 commit にまたがる変更ですが、実コード差分は実質 3 ファイル / 約 7 行 + テスト 25 行 + ドキュメント大量、という比率です。scope 縮小の判断 + post-mortem の質 が本 PR の主たる価値という認識でレビューしました。

CI: test / e2e / visual-regression 全 green、base = develop 明示済、aria 削除なしを確認済み。結論: approve 寄り、下記の minor 指摘 1〜2 点を任意で取り込み検討。


✅ 高く評価する点

1. scope 縮小の意思決定プロセス

ResultTable.setProperty の CSP3 制約発覚 → 7 commit 中 3 commit を rebase で除外 → PR 9 / PR 10 への分割設計、という流れが [067] に詳細記録されており、再現性が高い。memory feedback_blocker_overstating.md の方針 (「前提崩壊」と過剰評価せず解決策 3 案以上を列挙) を [067] 自身が体現している (Constructable Stylesheets / CSS class swap / 'unsafe-hashes' / per-request nonce の評価表) — これは将来の reviewer が判断を辿る上で価値が高い。

2. Gs1Databar SVG の currentColor

dangerouslySetInnerHTML 経由で DOM 注入される SVG <text> の inline style="fill:..."fill="currentColor" + 親 .gs1-svg-container { color: var(--color-text) } に置換する設計は CSP 観点でも UX 観点でも妥当:

  • fill="currentColor" は SVG 属性であって style-src の対象外
  • currentColor は SVG ↔ CSS 連携の標準パターンで全 modern browser サポート
  • VRT で diff ゼロを確認済 = 視覚的同一性が担保

3. Astro 検出網の 正陽性対照付き 追加

memory feedback_positive_control_for_gates.md 方針通り、Astro detector 追加 (line 54-71) と同時に陽性対照 (line 93-96) を併設しており、regex 自体の正しさが回帰しないこと自体を検証している。

4. gs1-databar.ts の XSS 防御は維持

injectCompositeText() 内で escapeHtml(text) 経由で HTML エスケープしている (line 173-180, 194) ことは pre-existing で本 PR は touch していない。dangerouslySetInnerHTML 経由でも text content は escape 済 = XSS 経路は本 PR で widen していない。


🟡 minor 指摘 (任意取り込み)

M-1: inline-style-migration.test.ts の Astro detector regex がダブルクォートのみ

src/utils/__tests__/inline-style-migration.test.ts:67 の regex:

expect(content).not.toMatch(/\sstyle\s*=\s*"[^"]*"/);

ダブルクォート "..." のみマッチで、以下は silent skip される:

  • style='color: red' (シングルクォート) — Astro / HTML 仕様上は valid
  • style={cssExpr} (Astro の JSX 風 expression、frontmatter 由来の文字列で動的注入)

陽性対照 (line 94) もダブルクォート版のみ。

提案 (どれか 1 つ):

(a) regex を拡張: /\sstyle\s*=\s*(["'][^"']*["']|\{[^}]*\})/ + 陽性対照にシングルクォート / {} 版を追加
(b) 現状維持 + JSDoc に「ダブルクォート形式のみ検出。シングルクォート / expression 形式は別途 lint で扱う」と明記
(c) [067] に「将来 ESLint plugin 化で全形式を検出網に統合」と follow-up 記録

PR 7a/7b の実績では Astro inline は全てダブルクォート形式だったため (a) は必須ではない。過去パターンを踏襲した割り切り であれば (b) で十分。

M-2: SVG <text> の fallback color 喪失

src/utils/gs1-databar.ts:229:

- font-size="${fontSize}" fill="#000000" style="fill:var(--color-text)">
+ font-size="${fontSize}" fill="currentColor">

before は fill="#000000" を fallback として残し、style で上書き という多重防御だった。after は currentColor のみ = 親 color が解決できないと SVG <text> のデフォルト (UA 依存、通常 black) にフォールバックする。

実害シナリオ:

  • --color-text が未定義になる場合 (現状 :root で必ず定義 ∴ 起こらない)
  • .gs1-svg-container が override される CSS 変更
  • ユーザースタイル (custom UA stylesheet) で color が極端値に上書き

提案: コメント 1 行で currentColor 採用根拠 (parent の gs1-svg-containercolor: var(--color-text) を設定済の前提) を gs1-databar.ts:229 付近 or Gs1Databar.tsx:315 に書いておくと、将来 className を消した瞬間に色が壊れることを防げる。

なお barcode 本体の bars は bwip-js が fill="#000000" で出力するため、本 PR で dark mode / 高コントラストモードでの視認性は変化しない (text のみ変化、bars は不変)。これは pre-existing 仕様。

M-3: docs/superpowers/plans/2026-05-08-issue-176-b8-final-flip.md の archival 配慮

1114 行の plan は 当初の 7 commit 全体 を記述しているが、本 PR で実際に commit されたのは 4 commit (rebase で 3 commit 除外)。reviewer が plan と実装を crossref した時、Task 2-4 (CSP flip / stripMetaStyleSrc 撤去 / test 反転) が実装されていないように見える。

提案 (任意): plan ファイル冒頭に scope 縮小 banner を 1 段落追加:

> **更新 2026-05-08**: 本 plan の Task 2/3/4 は ResultTable `setProperty` の CSP3 制約発覚 ([067]) により本 PR から rebase で除外。PR 9 (ResultTable refactor) → PR 10 (B 案最終 flip) で再投入予定。本 PR は Task 1/5/6/7 (Gs1Databar / Astro 検出網 / decisions / SoT) のみ実施。

merge 後に archive value としてのみ残るドキュメントなので、書き換え工数 ≦ 1 分。


🟢 informational (action 不要)

I-1: B 案残作業の前提変化

[067] の評価表で Constructable Stylesheets を「最有力 / 要 Chromium 動作確認」としているが、CSP3 spec の最新 editor draft では adoptedStyleSheets 経由の programmatic stylesheet も style-src に従う方向 で議論が動いている (URL: https://github.com/w3c/webappsec-csp/issues/518)。PR 9 の検証時にこの動向を再確認しておくと、選択肢 (a) が PR 10 段階で機能しない事故を回避できる。fallback の (b) CSS class swap の事前検証も並行で進める価値あり。

I-2: VRT 不発の理由の明示

CI で VRT diff ゼロが green = 視覚的に同一、ということは currentColor 経路でも text の最終色が var(--color-text) と完全一致していることが確認された という意味で、本 PR の最大の検証ゲート。PR description にこの 1 文を追加しておくと、将来の reviewer がこの PR の視覚 risk を 5 秒で把握できる (現状は 「VRT diff ゼロ」という事実は書かれているが、それが「currentColor refactor の正しさ証明」だという意味付けまでは書かれていない)。

I-3: 規約準拠状況の確認結果

項目 状況
base = develop ✅ 明示済
本文 --body-file 経由 ✅ (PR description はバックティック化けなし)
言語 = 日本語
aria-* 削除 ✅ なし (Gs1Databar.tsx の aria-label / aria-live は維持)
pre-create scope 確認 ✅ 8 ファイル全て develop diff 内

まとめ

  • security: CSP 観点で正しい方向の refactor。dangerouslySetInnerHTML 経路は escape 済で widen なし。
  • frontend / UX: currentColor 採用は標準パターン、VRT pass で視覚的同一性担保。
  • logic: refactor は意図通り、テストの陽性対照も付加。Astro regex のシングルクォート未対応は 割り切り として許容範囲。
  • post-mortem の質: [067] の問題分析・解の評価表・lessons learned は将来の reviewer / 別 PC 作業者にとって reference value が高い。
  • PR 規約: 4 点 (base / body-file / 日本語 / aria) いずれも遵守。

approve 推奨。M-1 〜 M-3 は 任意取り込み、merge 前提の指摘ではない。merge 後 PR 9 着手前に I-1 (CSP3 spec の最新動向) は再確認推奨。

PR #303 self-review (approve 寄り、minor 指摘 3 件任意取り込み) のうち
3 件すべてを反映。

M-1: Astro detector regex の検出範囲拡張
- inline-style-migration.test.ts: regex を ダブルクォート / シングルクォート /
  {expression} の 3 形式対応に拡張 (`/\sstyle\s*=\s*("[^"]*"|'[^']*'|\{[^}]*\})/`)
- 陽性対照 2 件追加 (シングルクォート / expression)
- 既存陽性対照 1 件のテスト名を「ダブルクォート」と明記
- 効果: 将来 frontmatter 由来の動的注入や JSX 風 expression 形式の
  inline style が混入した場合の silent skip を防ぐ

M-2: Gs1Databar SVG `currentColor` 採用根拠コメント追加
- src/utils/gs1-databar.ts: `<text>` 生成箇所付近に親要素 `.gs1-svg-container`
  ({ color: var(--color-text) }) との依存関係を明記
- 効果: 将来 Gs1Databar.tsx の親要素 className を変更した瞬間に SVG text
  デフォルト色 (UA 依存、通常 black) にフォールバックする事故を防ぐ

M-3: plan ファイル冒頭に scope 縮小 banner 追加
- docs/superpowers/plans/2026-05-08-issue-176-b8-final-flip.md: 1114 行の
  当初 7-commit 計画に対し、本 PR で実施したのは Task 1/5/6/7 のみで
  Task 2/3/4 は PR 9/10 へ延期した経緯を冒頭で明示
- 効果: reviewer / 将来読者が plan と実装を crossref した際に
  「Task 2-4 未実装」に見える混乱を解消、[067] / #304 / #305 への導線を確立
@fumtas1k

fumtas1k commented May 8, 2026

Copy link
Copy Markdown
Owner Author

レビューありがとうございます。M-1 / M-2 / M-3 の 3 件すべて取り込みました (commit 6d14035)。

対応内容

✅ M-1: Astro detector regex の検出範囲拡張

  • regex を ダブルクォート / シングルクォート / {expression} の 3 形式対応に拡張: /\sstyle\s*=\s*("[^"]*"|'[^']*'|\{[^}]*\})/
  • 陽性対照を 2 件追加 (シングルクォート / expression 版)、既存陽性対照のテスト名を「ダブルクォート」と明記
  • 解決案 (a) を採用。提案された JSDoc / follow-up 記録 ((b) / (c)) は 「将来 frontmatter 由来の動的注入や JSX 風 expression 形式が混入した場合の silent skip を防ぐ」 という防御的価値が regex 拡張で恒久的に確保できるため不要と判断
  • 効果: 782 tests pass (180 → 182、陽性対照 2 件追加分)

✅ M-2: SVG <text>currentColor 採用根拠コメント追加

  • src/utils/gs1-databar.ts:226-232 付近にコメント追加。親要素 .gs1-svg-container ({ color: var(--color-text) }) との依存関係を明記
  • 配置は reviewer 提案 2 候補 (gs1-databar.ts:229 / Gs1Databar.tsx:315) のうち 生成側 (gs1-databar.ts) を選択。理由: 将来 fill 属性を触る reviewer がまず見るのは生成側で、消費側 (Gs1Databar.tsx) で className を変更する場合は IDE / git blame で生成側のコメントに辿り着ける
  • 効果: 将来 Gs1Databar.tsx の親要素 className を変更した瞬間に SVG text デフォルト色にフォールバックする事故を予防

✅ M-3: plan ファイル冒頭の scope 縮小 banner 追加

informational への扱い

検証

  • npm run test: 782 pass (43 files / +2 件 = M-1 陽性対照追加)
  • npx astro check: 0 errors / 0 warnings / 9 hints (pre-existing)
  • npx prettier --check .: clean
  • merge ベースとの diff: 5 commit (4 commit + 本 review follow-up 1 commit)

CI 再走を確認後、改めて approve 判断をお願いします。

@fumtas1k

fumtas1k commented May 8, 2026

Copy link
Copy Markdown
Owner Author

再レビュー — 6d14035 (M-1 / M-2 / M-3 取り込み) 確認結果

3 件すべて適切に取り込まれており、想定以上の追加対応 (#304 / #305 起票, backup branch 言及) も入っています。新規指摘なし、approve 寄りの判断は変わらず


修正内容の評価

✅ M-1: Astro detector regex 拡張

src/utils/__tests__/inline-style-migration.test.ts:69

expect(content).not.toMatch(/\sstyle\s*=\s*("[^"]*"|'[^']*'|\{[^}]*\})/);
  • alternation ("[^"]*"|'[^']*'|\{[^}]*\}) で 3 形式すべてを 1 regex でカバー
  • 陽性対照を 3 件 (double / single / expression) に拡張、既存ダブルクォート版もテスト名に「ダブルクォート」明記で読みやすさ向上
  • 前置スペース必須 (\s) は維持されているので <style> block (auto-hash 経路) は引き続き対象外 = 期待通り

追加検証 (regex 動作確認):

入力 マッチ 妥当性
<div style="color: red" /> double 検出
<div style='color: red' /> single 検出
<div style={cssExpr} /> expression 検出
<div style={{color: 'red'}} /> (JSX) nested は \{[^}]*\} で外側まで部分マッチ → toMatch
<style>...</style> 前置スペースなし、対象外

意図通り。

✅ M-2: gs1-databar.ts 採用根拠コメント

src/utils/gs1-databar.ts:226-232 の 7 行コメントは「何を / どこに依存 / 壊れたらどうフォールバック / なぜそうしたか」の 4 点を網羅:

  • fill="currentColor" が親要素の color を継承する仕組み
  • 親要素は Gs1Databar.tsxdangerouslySetInnerHTML ラッパ <div>.gs1-svg-container クラス経由
  • 維持すべき規約 (className 変更時は同等の color 設定を残す)
  • フォールバック挙動 (UA デフォルト = 通常 black)
  • 参照 [067] / #176 B 案

将来 Gs1Databar.tsx を他者が触る時、grep で gs1-svg-container を辿るだけで意図が拾える設計。

✅ M-3: plan banner 追加 + 想定外の補強

docs/superpowers/plans/2026-05-08-issue-176-b8-final-flip.md:3 の banner は私の提案を超えて以下まで含める:

  • Task 2/3/4 が rebase 除外された経緯 + [067] 参照
  • 後続 PR の issue 番号確定値 (#304 / #305) — 確認したところ両 issue とも実在 / OPEN:
    • #304 refactor(ResultTable): #176 B 案 PR 9 — setProperty を CSP strict 互換 API に refactor
    • #305 refactor(csp): #176 B 案 PR 10 — style-src strict 化最終 flip 再投入 (PR 9 完了後)
  • backup branch backup/pr8-full-original の存在と push しない方針

これにより plan ↔ 実 issue ↔ [067] の 3 方向 crossref が完成。私の提案は 1 文だったが、実装はその 3 倍の情報密度になっており、archival 価値が大きく増している。


状態確認

項目 状態
test CI ✅ SUCCESS
e2e CI ✅ SUCCESS
visual-regression CI ✅ SUCCESS
mergeable CLEAN
base develop (規約遵守)
follow-up issue #304 / #305 起票済 (OPEN)

CI required check は全 green、human review approve まで揃えば merge 可能な状態。残作業 (#304#305) も独立 issue 化済で本 PR が release blocker になる経路はなし。


結論

  • M-1 / M-2 / M-3 はいずれも私の提案範囲を満たしつつ、想定以上の補強あり。
  • 新規指摘なし。
  • approve 寄りの判断は変わらず。merge は self-review approve + human review の方針に従って判断してください。

[067] post-mortem の質と #304 / #305 への引継ぎが整っているため、本 PR は B 案シリーズの valuable failure case のリファレンス としても機能します。

@fumtas1k
fumtas1k merged commit e2efd24 into develop May 8, 2026
3 checks passed
@fumtas1k
fumtas1k deleted the feature/issue-176-b8-final-flip branch May 8, 2026 06:07
fumtas1k added a commit that referenced this pull request May 8, 2026
…リンク反映 (#306)

PR #303 (merged e2efd24) の hash placeholder 置換と、PR 9 / PR 10
セクションへの follow-up issue リンク (#304 / #305) 追加。

進捗 table:
- PR 8 行: 🔄 PR open → ✅ merged + #303 link + e2efd24 hash
- PR 9 行: PR 列を `-` → issue #304 link
- PR 10 行: PR 列を `-` → issue #305 link

着手済 PR 履歴セクション:
- PR 8: (#TBD) → (#303)
- PR 9: (#TBD) → issue #304 link
- PR 10: (#TBD) → issue #305 link

prettier 整形により table 列幅が再 align (substantive な変更は上記のみ)。
fumtas1k added a commit that referenced this pull request May 8, 2026
…tructable Stylesheets に refactor (#304) (#307)

* docs(spec): #176 B 案 PR 9 設計書 — ResultTable / ToggleGroup の setProperty を Constructable Stylesheets に refactor

[067] post-mortem を起点に PR 9 (issue #304) の設計書を起草。

主要な設計判断:
- 採用技術: Constructable Stylesheets ([067] 最有力案)、(b) CSS class swap は Phase 0 NG 時の fallback
- scope 拡張: ResultTable に加え ToggleGroup も refactor (config-converter violation の真の原因 = ToggleGroup と確認、12 ツールで使用、PR 10 strict 化で全違反するため同 PR 必須)
- API 互換: 公開 props (width / minWidth / options) は維持
- 共通 hook: useDynamicStyleSheet (SSR-safe / useId ベース) で Constructable Stylesheet 経路を集約
- defense in depth: assertCssLength で CSS injection 防御

Phase 構成:
- Phase 0: minimal repro spec (陽性 + 陰性対照、永続) で Chromium 動作実機確認
- Phase 1: hook + ResultTable + ToggleGroup refactor
- Phase 2: 12 ツール spec を strict CSP で local 実行 (run-only / commit せず)
- inline-style-migration.test.ts の setProperty 除外を陽性 guard に反転

infra (helpers) と feature の bundle を許容 (PR 5b と同 judgement、helper 30 行規模 + Phase 0 検証は refactor の前提)。

* docs(plan): #176 B 案 PR 9 実装計画 — ResultTable / ToggleGroup CSP refactor

spec (`docs/superpowers/specs/2026-05-08-issue-176-b9-...`) に対応する
task-by-task の実装計画。10 task / 4 commit (C1-C4) + Phase 2 run-only
verification + PR 作成。

各 task は (i) 失敗テスト → (ii) 実装 → (iii) PASS 確認 → (iv) commit の
TDD/checkpoint 形式で記述。すべての code block は完全形 (placeholder
なし)、コマンドは exact、commit message は HEREDOC 形式の雛形を含む。

* test(e2e): #304 Phase 0 — applyStrictStyleSrcCsp + Constructable Stylesheets 検証 spec

PR 9 verification 用の infra を先行投入する。

- helpers.ts: applyProductionCsp の中身を applyCspOverride に切り出し、
  applyStrictStyleSrcCsp (style-src 'self' 強制) を追加。PR 10 で
  PRODUCTION_CSP 自体が strict 化したら本 helper は削除候補。
- csp-constructable-stylesheet.spec.ts (永続): 陽性対照 (インライン <style>
  要素は violation 起こす) + 陰性対照 (Constructable Stylesheet は起こさない /
  CSS 変数経由でも起こさない) の 3 test。PR 10 後も継続 regression 検出網。

陽性対照の probe: el.style.setProperty() を page.evaluate() 経由で呼ぶと
CDP Runtime.evaluate は CSP inline style 制約を受けないことを実機確認。
<style> 要素挿入を代替 probe とした (uuid-v7/config-converter の
陽性対照パターン — <script src external> — と同じ CDP bypass 回避方針)。

memory feedback_positive_control_for_gates.md / feedback_infra_feature_separation.md
の例外条項 (PR 5b と同 judgement、helper 30 行規模 + Phase 0 検証は refactor の前提) 準拠。

* test(e2e): #304 Phase 0 review fix — STRICT_STYLE_SRC_CSP の drift 防止 + 陽性/陰性対照に前提ヘッダ assert

PR 9 Task 1 の code review で指摘された 2 件の Important 修正:

- helpers.ts: STRICT_STYLE_SRC_CSP を PRODUCTION_CSP からの replace 派生に変更
  し、module load 時の sanity check で drift を CI 検知可能に。PR 10 で
  PRODUCTION_CSP 側 style-src が strict 化されたら replace は no-op となり
  本 helper を削除すべき状態になる
- csp-constructable-stylesheet.spec.ts: 3 test 全てで page.goto の Response
  ヘッダに対し strict CSP が乗っていることを事前 assert。route 注入が静かに
  失敗してテストが意味なく pass / fail するのを防ぐ (uuid-v7 / config-converter
  の既存陽性対照と同パターン)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor(ResultTable,ToggleGroup): #304 setProperty を Constructable Stylesheets に refactor

PR 8 ([#303]) の親直接 E2E で発覚した CSP3 style-src 'self' 非互換の
setProperty 経路を Constructable Stylesheets ([067] (a) 案) に置換。
PR 10 ([#305]) の B 案最終 flip を unblock する。

新規:
- src/hooks/useDynamicStyleSheet.ts: SSR-safe (useId ベース) な共通 hook、
  `new CSSStyleSheet()` + `document.adoptedStyleSheets` を React lifecycle に
  integrate
- src/utils/css-length.ts: assertCssLength validator、replaceSync 経由の
  CSS injection 防御 (defense in depth)
- src/test-setup.ts: vitest 用 jsdom polyfill (CSSStyleSheet.replaceSync /
  document.adoptedStyleSheets)。useDynamicStyleSheet を使うコンポーネントを
  render する全 jsdom test で必要

修正:
- src/components/ui/ResultTable.tsx: el.style.setProperty('--result-table-min-width' /
  '--col-width', ...) 2 件を撤去、useDynamicStyleSheet で per-instance scoped
  rule を注入。`.result-table-col` class 付与も不要となり撤去
- src/components/ui/ToggleGroup.tsx: useEffect + gridRef + setProperty('--toggle-cols')
  を撤去、useDynamicStyleSheet で per-instance scoped rule に変更。
  layout='wrap' 経路は sheet 生成 skip
- src/styles/global.css: `.result-table { min-width: var(...) }` /
  `.result-table-col { width: var(...) }` 撤去 (Constructable Stylesheet
  一本化)。`.toggle-grid` の --toggle-cols, 2 fallback は wrap 経路の
  silent guard として保持
- vitest.config.ts: setupFiles に test-setup.ts を追加

API 互換: ResultTable の width / minWidth、ToggleGroup の options は不変。

* test(migration): #304 setProperty を陽性 guard に反転

PR 9 で codebase から `el.style.setProperty(...)` が完全消滅したため、
migration test 内で setProperty を意図的に除外していた filter を撤去し、
新規再導入を陽性検出する guard に反転する。

変更:
- JSDoc に [067] / PR 9 の経緯を追記、useDynamicStyleSheet hook で代替済の旨明記
- TSX violation 検出を `\.style\.X =` 単独から `\.style\.X =`
  + `\.style\.setProperty\(` の両方を陽性検出する形に拡張
- 「setProperty は許容パターンとしてスルーされる」test を削除し、
  「setProperty(...) は違反として検出される」陽性対照 test に置換
- 既存 「style.X = は陽性検出される」test の filter チェーンも簡素化

参照: docs/decisions.md [067]、PR 9 spec § 5.5

* docs: #304 PR 9 outcome を [067] に追補 + SoT 同期 + PR 10 申し送り記録

- decisions.md [067]: PR 9 outcome section 追加 (採用 = Constructable
  Stylesheets / scope 拡張 = ToggleGroup 同梱 / 実装サマリ / Phase 2 で
  発覚した Astro island runtime style hash 取り込み課題を PR 10 申し送り)
- issue-176-b-plan-progress.md: PR 9 行を「実装中 + scope 拡張」に更新、
  詳細 section に ToggleGroup 同梱 / Phase 0 永続検証 / migration test 反転 /
  subagent 委譲方針 / PR 10 申し送り事項を追記

Phase 2 strict CSP local verification で 13 ツール spec が Astro 島ランタイム
の固定 inline style 由来の violation で fail することを確認。PR 9 の React
refactor 自体は無問題 (Phase 0 spec が `/` で pass)、本件は [064] /
stripMetaStyleSrc 構造由来で PR 10 責務範囲のため scope を訂正し、PR 10 で
解決する旨を decisions.md / SoT 両方に明記。

merge 後の hash 反映は別 chore PR で対応 (PR 6 / 7a / 7b / 8 と同パターン)。

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant