feat: 設定ファイル相互変換ツール (config-converter) を追加 - #133
Conversation
JSON/YAML/TOML/dotenv の相互変換・整形ユーティリティと JSON Schema バリデーション(ajv draft-04/7対応)を追加。 TDD でテストを先行作成し、全 224 テストがパスすることを確認済み。 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- yaml, smol-toml, ajv, ajv-formats を package.json に明示追加 - toml.ts: stringifyToml に入力型ガードを追加 - dotenv.ts: quoteValue ヘルパで " を含む値のエスケープを修正 - yaml.ts: formatYaml にエラーハンドリングを追加 - schema-validator.ts: 非オブジェクトスキーマの入力ガードを追加 - テスト追加: schema-validator.test.ts 新規作成 (4件)、toml/dotenv テスト追加 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
From/To ToggleGroup・InputField/OutputField・警告バナー・ JSON Schema 検証パネル(dynamic import)・DownloadButton を含む Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- src/data/tools.ts に Tool エントリ追加 (category: convert)
- src/pages/tools/config-converter.astro を作成
- src/components/ui/ToolIcon.astro に {} アイコンを追加
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
6ケース: YAML→JSON変換、JSON→YAML変換、ネスト拒否エラー、 クリア動作、JSON Schema 検証成功・失敗 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- README.md: 変換・解析テーブルに設定ファイル相互変換を追加 - SPEC.md: ライブラリテーブル・ツール一覧・ディレクトリ構成・実装チェックリストを更新 - docs/decisions.md: [051] yaml/smol-toml/ajv 選定理由・HCL後送り判断を記録 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- docs/decisions.md: develop で追加された [051] Cloudflare Web Analytics 却下エントリを挿入し、 当ブランチの config-converter ライブラリ選定エントリを [052] に繰り下げ - .gitignore: develop で追加された `.claude/worktrees/` 除外ルールを追加 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- getByLabel('JSON') → getByLabel('JSON', { exact: true }): from=to=JSON 時に
入力ラベル「JSON (整形)」と出力ラベル「JSON」の2要素にマッチする問題を解消
- getByRole('button', { name: '検証する' }) → { exact: true }: 「▶ JSON Schema で検証する」
ボタンにも部分マッチしていた問題を解消
- getByText(/age/) → getByText('/age'): 正規表現がテキストエリア内の "age" を含む
全要素にマッチしていた問題を解消(パスは "/age" とスラッシュ付きなので区別可能)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
| - gzip 約 40KB と大きいため、スキーマ検証パネルを開いた瞬間に `await import()` で遅延ロードし、初期チャンクへの影響をゼロにした | ||
| - draft-04 は `ajv-draft-04` で分岐対応 | ||
|
|
||
| #### HCL: Phase 2 後送り |
There was a problem hiding this comment.
| #### HCL: Phase 2 後送り | |
| #### HCL: Phase 2 先送り |
fumtas1k
left a comment
There was a problem hiding this comment.
レビュー総評
YAML/JSON/TOML/.env の 4 形式相互変換を、純関数ユーティリティ層と React コンポーネント層に分離した構成は読みやすく、既存ツール(JsonXml / EncodingConverter 等)の規約と整合しています。useCodec の利用、colors.* / var(--color-*) の徹底、getByRole 中心の E2E テスト構成、docs/decisions.md への決断記録など、プロジェクト規約 (docs/shared-agent-rules.md) はおおむね遵守されています。Vitest 231 件・E2E 6 件のテストも揃っており、PR 説明文も日本語・章立てされており体裁は良好です。
ただし、以下に 本番ビルドや検証機能の正しさに直結する問題 が複数あるため、マージ前の修正を強く推奨します。
必須対応 (Critical)
1. ajv-draft-04 が package.json の dependencies に明示登録されていない
- 該当:
src/utils/config-converter/schema-validator.ts:3でimport Ajv4 from "ajv-draft-04"; - 現状:
package.jsonにはajv/ajv-formatsは追加されているが、ajv-draft-04は 未登録。package-lock.json上のnode_modules/ajv-draft-04エントリはdev: trueで、これはyaml-language-server(devDep)の transitive 経由で偶然存在しているだけです。 - リスク: クリーンインストール後の
npm install --omit=dev系操作や、Cloudflare Pages 等で本番依存だけ解決される構成ではCannot find module "ajv-draft-04"で 本番ビルドが壊れる可能性 があります。今は dev tree から手繰れているため動いているにすぎません。 - 対応:
package.jsonのdependenciesに"ajv-draft-04": "^1.0.0"を追加しnpm installで lockfile を更新。docs/decisions.md[052] にも追記してください。
2. handleValidate でスキーマ検証対象データの組み立てが壊れている
- 該当:
src/components/tools/ConfigConverter.tsxのhandleValidate内(diff の +407〜+413 付近)
let data: unknown;
try {
data = JSON.parse(output);
} catch {
data = convert(output, to, "json"); // ← ConvertResult ({output, warnings}) がそのまま data になる
}- バグ:
convert()はConvertResult({ output: string; warnings: string[] }) を返すため、outputが JSON でないとき(=toがyaml/toml/dotenvのとき)にdataが{ output: "...", warnings: [...] }という想定外オブジェクトとなり、その後validateWithSchema(data, schema)に渡されてしまいます。 - 結果:
to=yaml/toml/dotenvのとき、ユーザーが渡したスキーマと無関係な内部オブジェクトに対して検証が走り、常に意味のない結果が返ります。 - E2E テスト (
tests/e2e/config-converter.spec.tsの 5 番目・6 番目のケース) は どちらもto=jsonのみで検証パネルを開いている ため、このバグは網羅できていません。 - 対応:
data = JSON.parse(convert(output, to, "json").output);に修正to=yamlで検証パネルを開いた E2E ケースを最低 1 件追加してリグレッション防止
3. .env パーサーがインラインコメントを取り扱っていない
- 該当:
src/utils/config-converter/dotenv.tsのparseDotenv - 現状:
KEY=value # commentのような行を value 部にvalue # commentを含めて取り込んでしまいます。多くの dotenv 実装(dotenv,docker-compose,direnv等)では空白+#以降をコメントとして除去します。 - リスク: 実運用の
.envを貼り付けると値にコメント文字列が混入し、JSON/YAML/TOML 出力が「意図しない値」になります。 - 対応案: クォート未使用かつ
\s+#を含む値は#以降を切り落とす(クォート内の#は保持)。または「インラインコメントは未対応」を README/UI に明記する。挙動どちらにするかは設計判断ですが、現状は仕様ドキュメントにも未記載で、ユーザーに気づきの余地がない のが問題です。
推奨対応 (Important)
4. useCodec のコールバック内と別 useEffect で convert() を二重に呼んでいる
- 該当:
src/components/tools/ConfigConverter.tsxのuseCodec呼び出しと直後のuseEffect - 現状:
useCodec内の transform でconvert(text, from, to).outputを計算し、別useEffectで同じ入力に対してconvert(input, from, to).warningsを計算しています。同じデバウンス境界で両方走るため二重計算となります。 - 対応案:
useCodecの代わりにuseMemoで{output, warnings, error}を一括導出するか、useCodecのtransformから warnings も返せるよう拡張する。MVP としては許容範囲ですが、入力が大きい設定ファイルでは体感差が出る可能性があります。
5. E2E テストでの page.waitForTimeout(500) の利用
- 該当:
tests/e2e/config-converter.spec.tsの検証成功・検証エラーケース - 問題: Playwright 公式が非推奨としているハードウェイト。CI 環境の負荷で flaky 化しやすいです。
- 対応案:
await expect(page.getByText("スキーマ検証成功")).toBeVisible();のような assertion はそれ自体が auto-retry するので、waitForTimeoutを削除して直接expect(...).toBeVisible()だけで十分です。
6. JSON Schema 検証 UI のキーボード操作
- 該当:
ConfigConverter.tsxの検証ボタン周り - スキーマ貼り付け後、textarea にフォーカスがある状態で Enter を押しても検証実行されません(multiline なので Enter は改行)。Cmd/Ctrl+Enter のショートカット等を当てるとアクセシビリティが向上します。MVP としては必須ではありませんが、設定ファイルを試行錯誤するワークフローでは欲しいところです。
7. formatLabel ヘルパーが冗長
- 該当:
ConfigConverter.tsxのconst formatLabel = (f: ConfigFormat) => FORMAT_LABELS[f]; FORMAT_LABELS[from]を直接使えば良く、関数化のメリットが薄いです。KISS 観点で削除推奨。
8. ToolIcon の SVG にラベルが無い
- 該当:
src/components/ui/ToolIcon.astroのconfig-converter分岐 - 装飾アイコンであっても
<svg aria-hidden="true">を付けるとスクリーンリーダーから明示的に除外できます。既存実装も同様であれば本 PR スコープ外で良いですが、今後の改善対象としてdocs/agent-lessons.mdに記録する価値はあります。
提案 (Nice to have)
9. detectFormat が UI から使われていない
- 該当:
src/utils/config-converter/index.tsのdetectFormatおよび__tests__/convert.test.tsのdetectFormatブロック - ヘルパーとしてエクスポートはされているが、
ConfigConverter.tsxから呼ばれていません。「貼り付けたフォーマットを自動判定する UI 補助」として活かせるはずです。または YAGNI 観点で削除してテストごと消すのも一案。今のままだとテスト 7 件が dead code を支えている状態です。
10. ajv 起因のエラーメッセージが英語のままユーザーに表示される
setValidationResult({ ..., message: e instanceof Error ? e.message : "検証中にエラーが発生しました" })のようにユーザーに ajv の生エラー文字列を出すと、英語混じりで一般ユーザーに不親切です。ajv起因のエラーは「JSON Schema の解析に失敗しました」のように要約することを検討してください。
11. decisions.md の HCL 後送り判断について
- バンドルサイズトレードオフを明記している点は良いですが、「Phase 2 で再評価する」トリガー(例: ユーザー要望が N 件たまったら、4MB 以下の代替が出たら 等) を併記しておくと将来の意思決定が再現しやすくなります。
総括
- 必須 1〜3 は マージ前に必ず対応 が必要です。1 は本番ビルドの安全性、2 はスキーマ検証機能の正しさ、3 はパーサー互換性に関わります。
- 推奨 4〜8 は次回イテレーションで取り込む価値があります。
- 提案 9〜11 は将来的な改善案です。
判定としては request-changes 相当 ですが、gh pr review --comment でコメントとして残します。1〜3 を修正後に再レビューを推奨します。
[Critical 1] ajv-draft-04 を dependencies に明示登録 - package.json に "ajv-draft-04": "^1.0.0" を追加 - dev tree の transitive dep に依存していたためクリーンインストール時にビルド失敗の恐れがあった - docs/decisions.md [052] にも理由を追記 [Critical 2] handleValidate: convert() の .output を参照していなかったバグを修正 - data = convert(output, to, 'json') → data = JSON.parse(convert(output, to, 'json').output) - to=yaml/toml/dotenv 時にスキーマ検証対象が ConvertResult オブジェクトになっていた - to=yaml でスキーマ検証が動作することを確認する E2E テストを追加 [Critical 3] .env パーサーがインラインコメントを無視していた問題を修正 - KEY=value # comment → value として取り込むよう \s+# パターンで除去 - クォート内の # は除去しない("value # not a comment" → そのまま保持) [推奨 4] convert() の二重呼び出しを解消 - useRef<string[]> で warnings をキャプチャし、別 useEffect での再 convert() を廃止 [推奨 5] E2E テストの waitForTimeout(500) を削除 - expect(...).toBeVisible() の auto-retry に委ねる(7件全パス確認) [推奨 7] formatLabel ヘルパー関数を削除 - FORMAT_LABELS[f] を直接参照するよう変更 [提案 10] ajv 起因の英語エラーメッセージを日本語に統一 - 日本語文字を含まないエラーメッセージは "JSON Schema の解析に失敗しました" で上書き Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Deploying devtools with
|
| Latest commit: |
d9c30a8
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://b2a0ee8a.devtools-d9w.pages.dev |
| Branch Preview URL: | https://worktree-feature-config-conv.devtools-d9w.pages.dev |
fumtas1k
left a comment
There was a problem hiding this comment.
レビューありがとうございます。指摘いただいた内容を対応しました(c0e8f53)。
必須対応(Critical)
1. ajv-draft-04 の未登録
package.json の dependencies に "ajv-draft-04": "^1.0.0" を追加し npm install で lockfile を更新しました。docs/decisions.md [052] にも登録理由を追記しています。
2. handleValidate のバグ
// before
data = convert(output, to, 'json');
// after
data = JSON.parse(convert(output, to, 'json').output);ConvertResult の .output を取り出すよう修正しました。また to=yaml 時にスキーマ検証が正しく動作することを確認する E2E テストを追加しました(7件目)。
3. .env インラインコメント
クォートなし値に対して \s+# パターンでコメント部を除去するよう parseDotenv を修正しました。クォート内の # は引き続き保持されます。
推奨対応(Important)
4. convert() の二重呼び出し
useRef<string[]> で warnings をキャプチャし、別 useEffect での再 convert() 呼び出しを廃止しました。
5. waitForTimeout の削除
waitForTimeout(500) を全ケースから削除し、expect(...).toBeVisible() の auto-retry に委ねるよう変更しました。
6. キーボードショートカット(Cmd+Enter)
設計判断が必要なため今回スコープ外とし、今後の改善タスクとします。
7. formatLabel の削除
FORMAT_LABELS[f] を直接参照するよう変更しました。
8. SVG の aria-hidden
既存の attrs オブジェクトに 'aria-hidden': true が含まれており、{...attrs} で展開しているため対応済みでした。
提案(Nice to have)
10. ajv エラーメッセージの日本語化
日本語文字を含まないエラーメッセージを "JSON Schema の解析に失敗しました" で上書きするよう変更しました。
9・11 は今後の改善候補として継続検討します。
fumtas1k
left a comment
There was a problem hiding this comment.
再レビュー総評
c0e8f53 の修正コミットで前回指摘の Critical 1〜3、推奨 4・5・7、提案 10 を 本質的に正しく対応 しています。実コードで挙動を verify した結果、修正自体に新たなバグは検出されませんでした。マージ可能と判断しますが、回帰テストの抜けや軽微な観点が残るため以下に記録します。
前回指摘の対応状況サマリ
| 番号 | 指摘内容 | 状態 | 一言コメント |
|---|---|---|---|
| 1 | ajv-draft-04 未登録 | 対応済み | lockfile root に追加、dev: true 解除済み。docs/decisions.md [052] にも理由追記 |
| 2 | handleValidate の convert() バグ | 対応済み | JSON.parse(convert(...).output) に修正。to=YAML の E2E ケース #7 が新規追加されリグレッション防止 |
| 3 | .env インラインコメント未対応 | 部分対応 | 実装は正しく入った(\s+# で除去・クォート内保持)が、ユニットテスト未追加で回帰防止が弱い |
| 4 | convert() 二重呼び出し | 対応済み | useRef<string[]> で warnings をキャプチャし二重計算解消 |
| 5 | page.waitForTimeout(500) | 対応済み | 全削除を確認。auto-retry に委譲 |
| 6 | キーボードショートカット | 後送り | スコープ外として扱う旨が明示されたため許容 |
| 7 | formatLabel 冗長 | 対応済み | 削除し FORMAT_LABELS[f] 直接参照に |
| 8 | SVG aria-hidden | 元から済 | ToolIcon.astro の attrs に元々含まれている(共通対応済み) |
| 9 | detectFormat UI 未使用 | 後送り | 継続検討と説明あり |
| 10 | ajv エラーメッセージの日本語化 | 対応済み | 日本語文字を含むかで分岐し既定メッセージで上書き |
| 11 | HCL 後送り判断の再評価トリガー | 後送り | decisions.md [052] に「Phase 2 で代替手段を検討」とは書いたが、定量条件(バンドルサイズ閾値・要望件数等)の明記はなし |
必須対応(残存・新規)
なし。修正対応で導入された新規 Critical バグは検出されませんでした。
推奨対応(次回でも可)
A. parseDotenv のインラインコメント挙動にユニットテストがない(Critical 3 の回帰防止)
- 該当: src/utils/config-converter/tests/dotenv.test.ts
- 現状: コメント関連のテストは「# comment\nKEY=value で行スキップ」の 1 件のみ。Critical 3 で導入した \s+# のインラインコメント除去ロジックに対するユニットテストが存在しません。
- リスク: 将来 quoteValue リファクタや parseDotenv の整理で本機能がデグレしてもテストが落ちません。E2E にも該当ケースなし。
- 対応案: 以下 4 ケースを追加するだけで充分です。
it('クォートなし値のインラインコメントを除去する', () => {
expect(parseDotenv('KEY=value # comment')).toEqual({ KEY: 'value' });
});
it('ダブルクォート内の # は保持する', () => {
expect(parseDotenv('KEY="value # not a comment"')).toEqual({ KEY: 'value # not a comment' });
});
it('シングルクォート内の # は保持する', () => {
expect(parseDotenv("KEY='val # kept'")).toEqual({ KEY: 'val # kept' });
});
it('空白を伴わない # は値の一部として保持する (URLのフラグメント等)', () => {
expect(parseDotenv('URL=https://example.com#fragment')).toEqual({
URL: 'https://example.com#fragment',
});
});B. クォート付き値で末尾にインラインコメントがあると、クォートが除去されない
- 該当: src/utils/config-converter/dotenv.ts:26-39
- 現状: KEY="hello" # trailing comment のような行を処理すると、
- value = '"hello" # trailing comment'
- value.startsWith('"') && value.endsWith('"') は false(末尾は t)→ クォート除去ブランチに入らない
- unquoted ブランチで \s+# を検出し '"hello"' まで残す
- .trim() → 結果は '"hello"'(リテラル " 込み)
- 影響: 多くの dotenv 実装(dotenv パッケージ、docker-compose、direnv)はクォートを剥がした上でコメントを除去するため、ユーザーの体感と乖離します。Critical 3 の「dotenv 実装互換性を確保する」目的とは少しズレた状態です。
- 対応案: コメント除去をクォート判定の前に行うよう順序を入れ替えるか、/^(["'])(.?)\1\s(#.*)?$/ で先頭クォートをアンカーに値を抽出する実装に切り替える。MVP 内で許容するなら、UI 側の警告 or README に「クォート + 末尾コメント混在は未サポート」を明記するのが筋です。
C. package.json の dependencies 並び順が壊れている
- 該当: package.json:22-51
- 現状: 既存 tailwindcss の後に ajv / ajv-draft-04 / ajv-formats / smol-toml / yaml がアルファベット順を無視して追記されている。ulidx・uuid も tailwindcss 直前に配置されておりずれています。
- リスク: 機能影響なし(npm が内部正規化)。ただしレビューで diff が読みづらく、今後の依存追加で merge conflict を誘発します。lockfile 側は正しくソート済みなので食い違いも発生中。
- 対応案: アルファベット順に並べ替えてコミット 1 本。
提案(任意)
D. 検証パネル開状態でフォーマット切替したとき validationResult が古いまま残る
- 該当: ConfigConverter.tsx:80-90
- handleFromChange / handleToChange で setWarnings([]) のみクリアしており、validationResult は維持されます。from/to を切り替えたあと出力が変わっても古い「スキーマ検証成功」表示が残るため、ユーザーが現在出力に対する結果と誤認する恐れがあります。
- 対応案: 両ハンドラに setValidationResult(null) を追加。
E. docs/decisions.md [052] の HCL 後送り再評価トリガー(前回指摘 11 の継続)
- 「Phase 2 で再評価」とは書いたが、定量条件(例: gzip 1MB 以下の純 JS 実装が出現したら / Issue が N 件溜まったら)の明示があると、将来の意思決定の再現性が上がります。スコープ外で問題ありません。
良い点
- Critical 1 のような「lockfile では動くが本番ビルドで欠落しうる」依存リスクを、docs/decisions.md への明示的な追記まで含めて丁寧に対処している
- Critical 2 の修正に対して to=YAML の E2E ケース #7 を追加し、リグレッションを実環境で検証可能にした点
- useRef<string[]> パターンによる convert() 二重呼び出しの解消は、useCodec 既存 API を変更せずローカル責務で完結しており、影響範囲が最小
- decisions.md [052] の選択肢比較・却下理由・トレードオフの章立てが整っており、将来の Phase 2 検討に再利用しやすい
- E2E テストが getByRole / getByLabel 中心、ハードウェイトを排し auto-retry に委ねた構成で、プロジェクトのテスト規約に沿っている
総括
修正対応は妥当で、マージブロックとなる残存 Critical はありません。推奨 A(dotenv インラインコメントのユニットテスト追加)と推奨 B(クォート+末尾コメント混在ケースの仕様明確化) は次回イテレーション、または本 PR 内で軽く対応してからのマージが望ましいです。推奨 C は merge 後の小 PR で十分です。
判定: approve 寄り(A・B を併せて対応すれば完全 approve)
[推奨 A] parseDotenv インラインコメントのユニットテスト追加 - クォートなし値の # 除去、クォート内保持、URL フラグメント保持、 クォート付き値+末尾コメントの 5 ケースを dotenv.test.ts に追加 - tests: 231 → 236 件 [推奨 B] クォート付き値 + 末尾コメント混在ケースを修正 - KEY="hello" # comment → "hello"(クォート残存)だったバグを修正 - 正規表現で /^"((?:[^"\\]|\\.)*)"\\s*(?:#.*)?$/ により クォート内容抽出と末尾コメント除去を一括処理 - ループ変数 raw とのシャドウイングを避けるため内部変数を rawValue に改名 [推奨 C] package.json dependencies をアルファベット順に整列 - ajv / ajv-draft-04 / ajv-formats / smol-toml を正しい位置に移動 - lockfile との整合性も改善 [提案 D] フォーマット切替時に validationResult をクリア - handleFromChange / handleToChange に setValidationResult(null) を追加 - 切替後の出力に対して古い検証結果が残る誤認を防止 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
fumtas1k
left a comment
There was a problem hiding this comment.
再レビューありがとうございます。推奨 A・B・C と提案 D を beb75a6 で対応しました。
推奨対応
A. parseDotenv インラインコメントのユニットテスト
dotenv.test.ts に 5 ケースを追加しました(236件):
- クォートなし値の
\s+#除去 - ダブルクォート内
#の保持 - シングルクォート内
#の保持 - URL フラグメント(
#前に空白なし)の保持 - クォート付き値 + 末尾コメントの除去とクォート剥がし
B. クォート付き値 + 末尾コメント混在ケースの修正
正規表現 /^"((?:[^"\\]|\\.)*)"\\s*(?:#.*)?$/ でクォード内容の抽出と末尾コメント除去を一括処理するよう書き直しました。KEY="hello" # comment → hello が正しく動作することをテストで確認しています。なお、ループ変数 raw との名前衝突(TDZ エラー)が発生したため、内部変数は rawValue に改名しました。
C. package.json dependencies の並び順
ajv / ajv-draft-04 / ajv-formats / smol-toml をアルファベット順の正しい位置に移動しました。
提案対応
D. フォーマット切替時の validationResult クリア
handleFromChange / handleToChange に setValidationResult(null) を追加し、切替後の出力に古い検証結果が残る問題を解消しました。
未対応(後送り)
E. HCL 後送りトリガーの定量条件
「Phase 2 で代替手段を検討」の記載にとどめます。具体的なトリガー条件(gzip 1MB 以下の純 JS 実装の出現等)は別途 Issue で管理することを検討します。
fumtas1k
left a comment
There was a problem hiding this comment.
3 回目レビュー: 推奨 A・B・C/提案 D の対応確認
beb75a6 を 1 件ずつ実コードで verify しました。4 件すべて適切に修正済みで、CI(test / e2e / Cloudflare Pages)も SUCCESS を確認しています。マージ可能と判断します。
検証結果
推奨 A: parseDotenv インラインコメントのユニットテスト追加 — OK
src/utils/config-converter/__tests__/dotenv.test.ts:30-50 に 5 ケース追加を確認:
| # | ケース | 期待値 |
|---|---|---|
| 1 | KEY=value # comment |
value |
| 2 | KEY="value # not a comment" |
value # not a comment |
| 3 | KEY='val # kept' |
val # kept |
| 4 | URL=https://example.com#fragment |
https://example.com#fragment |
| 5 | KEY="hello" # trailing comment |
hello |
要求された 4 ケース(クォートなし/DQ 内保持/SQ 内保持/URL fragment)に加えて、推奨 B の回帰防止ケース(5)も同テストに含まれており、過不足なし。
推奨 B: クォート + 末尾コメント混在ケースの修正 — OK
src/utils/config-converter/dotenv.ts:24-40 で実装を確認:
const rawValue = line.slice(eqIndex + 1);
const dqMatch = rawValue.match(/^"((?:[^"\\]|\\.)*)"\s*(?:#.*)?$/);
const sqMatch = rawValue.match(/^'((?:[^'\\]|\\.)*)'\s*(?:#.*)?$/);正規表現はクォート内容のキャプチャ(バックスラッシュエスケープ許容)と末尾の \s*(?:#.*)?$ を一括処理しており、KEY="hello" # comment で value: "hello" が得られます。シャドウイング回避のため raw → rawValue へリネームしたコメントもコミットメッセージで明示されており、変数命名も妥当。
正規表現のフォールバック挙動(マッチ失敗時にクォートなし扱い)も健全:
KEY="abc" extra(クォート後に非空白・非#文字)→ DQ マッチ失敗 → クォートなしフォールバック →"abc" extraをそのまま値として保持。これは「壊れたクォート形式」に対する保守的な fallback で、エラーにせず生値を残すという既存方針と整合します。
推奨 C: package.json dependencies アルファベット順 — OK
package.json:22-50 で確認:
@astrojs/react, @astrojs/sitemap, @fontsource/jetbrains-mono, @fontsource/noto-sans-jp,
@tailwindcss/vite, @types/jszip, @types/papaparse, @types/react, @types/react-dom,
ajv, ajv-draft-04, ajv-formats, astro, bwip-js, encoding-japanese, fast-xml-parser,
jsbarcode, jsqr, jszip, papaparse, qrcode-generator, react, react-dom, smol-toml,
tailwindcss, ulidx, uuid, yaml
完全にアルファベット順。package-lock.json の top-level packages 定義(20-38 行目)とも一致しており、lockfile との食い違いも解消されています。
提案 D: フォーマット切替時の validationResult クリア — OK
src/components/tools/ConfigConverter.tsx:80-92 で handleFromChange / handleToChange の双方に setValidationResult(null) が追加済み。これにより「変換元/変換先を切り替えた直後に古い検証 OK/NG 表示が残って誤認する」問題が解消されています。
なお ClearButton 側(301 行目)にも setValidationResult(null) があり、クリア動線が一貫している点は良いです。
新規発見・追加観察
以下は今回の修正で導入されたものではなく、PR 全体の既存仕様に対する観察です。ブロッキングではありません。
観察 1: 入力本文(input)変更時は validationResult が残る(提案 D の延長)
InputField の onChange={setInput} 経由で入力を編集すると output は変わりますが validationResult はクリアされません。今回の D の趣旨はフォーマット切替に閉じていたので対応範囲外と理解していますが、UI として「出力が変わったらスキーマ検証結果は再評価が必要」というメンタルモデルに従うなら、useEffect([output]) で validationResult を null 化する一行を足すことも検討余地あり。
ただし「同じ出力に対してスキーマだけ差し替えたい」ケースもあり、必ずしも自明な改善ではないため判断は実装者に委ねます。
観察 2: parseDotenv にエスケープ解除がない(既存仕様、今回 PR 範囲外)
stringifyDotenv の quoteValue は \\ と " をエスケープして書き出しますが、parseDotenv 側でアンエスケープ(\" → "、\\ → \)していません。テスト 'GREETING="say \\"hello\\""' は書き出し方向のみ検証しており、ラウンドトリップでは:
- 入力:
GREETING="say \"hello\""→ parse 結果:say \"hello\"(バックスラッシュ残存)
となり情報が一致しません。これは今回の PR で初めて入った問題ではなく fb78630 以前から続く既知の制約と読み取れますが、別 PR で parseDotenv 側にエスケープ解除を追加するか、SPEC.md の「.env の制約」セクションに「エスケープシーケンスは保持されない」と明記しておくと将来トラブルになりにくいです。
マージ可否
Approve 相当: 指摘 4 件すべて対応済み、CI 全 SUCCESS、リグレッションなし。マージ可能と判断します。
観察 1・2 は本 PR で対応する必要はなく、必要に応じて別 Issue 化を検討してください。
入力を編集して output が変わっても古いスキーマ検証結果(成功/失敗)が 残り続ける問題を修正。output/error を監視する既存 useEffect に setValidationResult(null) を追加し、出力変化と同時に検証結果を無効化する。 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
fumtas1k
left a comment
There was a problem hiding this comment.
ありがとうございます。2 件の観察についてそれぞれ対応しました。
観察 1: 出力変更時の validationResult 残存(705ca7b)
output/error を監視する既存の useEffect に setValidationResult(null) を 1 行追加しました。入力編集で出力が変わると同時に検証結果が無効化されます。「同じ出力でスキーマだけ差し替えたい」ケースは、変更なしに再度「検証する」ボタンを押せばよいため実用上の支障はないと判断しました。
観察 2: parseDotenv のエスケープ解除欠如
PR #133 より前から存在する既存制約のため、本 PR での修正はスコープ外とし #140 として Issue 化しました。
- src/data/tools.ts: config-converterとqr-readerの両エントリを保持 - README.md: 両ツールのテーブル行を保持 - SPEC.md: ツール一覧・チェックリストに両ツールを追記 - docs/decisions.md: [052]設定ファイル相互変換、[053]QRリーダー(番号を調整) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
fumtas1k
left a comment
There was a problem hiding this comment.
4 回目レビュー: マージコミット d9c30a8 確認
結論
Approve 相当・マージ可能(前回判定を維持)。develop の QR リーダー追加(PR #134)取り込みのみで、機能的なリグレッションは確認されませんでした。
確認したこと
-
マージコミットの変更ファイル一覧
git show d9c30a8 --statで取り込まれた 10 ファイルはすべて QR リーダー関連(QrReader.tsx/qr-reader.astro/qr-reader.ts/qr-reader.test.ts/qr-reader.spec.tsほか)。docs/decisions.md/README.md/SPEC.md/src/data/tools.ts/src/components/ui/ToolIcon.astroの共通 5 ファイルだけが両ブランチで競合し、それ以外は QR 側の単純追加のみです。config-converter 側のファイル(src/components/tools/ConfigConverter.tsx、src/utils/config-converter/*、tests/e2e/config-converter.spec.tsなど)には差分なし。- 親比較
git diff 705ca7b..d9c30a8も 10 ファイル / +772 行のみで、想定範囲内。
-
コンフリクト解消の妥当性
src/data/tools.ts:qr-ticketのあとにconfig-converter(行 91)→qr-reader(行 97)の順で両エントリが共存。category はいずれもconvert。src/components/ui/ToolIcon.astro:slug === 'qr-reader'(行 191)とslug === 'config-converter'(行 228)が両方ブロックとして存在。README.md: ツール一覧テーブルに「設定ファイル相互変換」「QRリーダー」が連続して追記されており、書式・列幅も整合。SPEC.md: ツール表(行 270-271)でナンバリング 13 / 14 が連続、依存関係表(yaml/smol-toml/ajv/ajv-formats)も保持、ディレクトリツリー / チェックリスト([x]行 1015-1016)にも両ツールが登載。docs/decisions.md:[052] 設定ファイル相互変換に yaml / smol-toml / ajv を採用→[053] QRリーダーツールを QRチケットから分離して新設の順で番号調整済み(QR 側はもともと[052]だったため[053]に繰り下げ)。コミットメッセージの記述と一致。
-
観察 1(出力変更時の
validationResult残存)705ca7bのuseEffect追加で対応済み。本マージで該当ロジックに変更なし。- 「同じ出力でスキーマだけ差し替えたいときは検証ボタン再押下で済む」との設計判断も妥当。
-
観察 2(
parseDotenvのエスケープ解除欠如)- 既存仕様として #140 に Issue 化されており、本 PR スコープ外で OK。
補足(必須ではない)
マージコミットには package-lock.json / package.json の差分が含まれていません。develop 側(QR リーダー)が依存追加なしだったためで正常です。マージ後に npm install → npm run test / npm run test:e2e を 1 回通しておけば最終確認として十分です(前回判定の前提通り)。
新規 Critical / Important
なし。マージコミットだけの軽量レビューとして、前回判定(Approve 相当・マージ可能)を維持します。
* docs: Cloudflare Web Analytics 導入を却下した決定を [051] として記録
プロジェクトの「外部送信ゼロ・外部リソースゼロ・トラッキングなし」方針と
Cloudflare Web Analytics(beacon 自動注入)が直接矛盾するため、有効化しない判断とした。
その経緯と却下理由を decisions.md に [051] として追記する。
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* docs: [051] サーバー側集計ログに関する誤記を修正
Cloudflare Pages のトラフィック集計は Web Analytics を有効化しないと
表示されない。beacon なしで見られるという誤った記述を削除し、
正確に「把握する手段が無い」と修正する。
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* chore: dev server / Playwright をサンドボックス除外コマンドに追加
`.claude/settings.json` のサンドボックスは listen() を拒否するため、
`npm run dev` / `npm run test:e2e` 経由の Astro dev server が
EPERM で起動できなかった。`excludedCommands` に dev / preview /
e2e / playwright / astro 系コマンドを追加し、ローカル listen を
必要とするコマンドだけサンドボックス外で実行できるようにする。
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* chore: .claude/worktrees/ を gitignore に追加
Claude Code の worktrees ディレクトリは既に `.worktrees/` を ignore
しているが、Agent Teams 機能で生成される `.claude/worktrees/` が
別パスのため取り残されていた。誤コミット防止のため追加する。
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* feat(tools): QRリーダーツールを新設(qr-reader)
カメラまたは画像ファイルからQRコードを読み取り、テキストを表示するツールを追加。
- `src/utils/qr-reader.ts`: URL/テキスト判別純粋関数 `detectQrContent()`
- `src/components/tools/QrReader.tsx`: カメラ+画像アップロード対応コンポーネント
- `src/pages/tools/qr-reader.astro`: Astro ページラッパー
- `src/data/tools.ts`: `qr-reader` エントリ追加(category: convert)
- ユニットテスト 15件(URL/テキスト判別の境界値を網羅)
- E2E テスト(.claude/settings.json 修正後に実行予定)
- SPEC.md / README.md / docs/decisions.md 更新
HTTP/HTTPS URL のみ検出してフィッシング警告付きリンクを表示。
javascript: 等の危険スキームは text として扱い自動リンク化しない([051])。
既存の useQrCamera フック・jsQR ライブラリを再利用して実装量を最小化。
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* test(e2e): qr-reader の Playwright strict mode 違反を修正
- 'カメラ' ボタンの locator に exact: true を追加('カメラを起動' との多重マッチを解消)
- 'example.com' テキストの locator に exact: true を追加(URL 全体を含む <pre> との多重マッチを解消)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(qr-reader): コピーボタンと再スキャンボタンの高さ・スタイルを統一
- rescanButtonStyle を CopyButton と同じ視覚パラメータに合わせる
(lineHeight 1 / padding 0.5rem 0.75rem / borderRadius 0.25rem / fontWeight 700)
- 両ボタンを flex flex-wrap items-center gap-2 でラップし
横並びレイアウトを明示的に制御
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(qr-reader): ToolIcon.astro に qr-reader SVGアイコンを追加
QRの3つのファインダーパターン + データモジュール2点 + 水平スキャン線で構成。
qr-code(データモジュール4点)と造形を共有しつつ、スキャン線で「読取」を表現。
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(qr-reader): レビュー指摘を修正(必須2件 + 推奨5件)
必須対応:
- canvas.getContext('2d') の non-null assertion を削除し、null 時に
エラーメッセージを表示するガードを追加(iOS Safari OOM 対策)
- 長辺 1600px 超の画像を jsQR 前にダウンスケールし、高解像度
スマホ写真での OOM・フリーズを防止
推奨対応:
- input[type=file] を display:none から visually-hidden に変更し
キーボード・スクリーンリーダーから操作可能にする(A11y)
- モード切替時に decoded / decodeError / cameraError をクリアし
古い結果との並存を防止
- useEffect 依存配列の参照を const { stopCamera } = camera で
分割代入し、exhaustive-deps 警告を回避
- E2E: not.toBeVisible() → toHaveCount(0) に変更(DOM から
削除されることの検証+auto-retry の安定化)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* docs(qr-reader): e.target.value クリアの意図コメントを追加
同名ファイルの再選択を可能にするパターンであることを明示し、
onload 完了前にクリアする理由(File は file 変数で参照済み)を説明。
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat: 設定ファイル相互変換ツール (config-converter) を追加 (#133)
* feat: config-converter ユーティリティ層とユニットテストを実装
JSON/YAML/TOML/dotenv の相互変換・整形ユーティリティと
JSON Schema バリデーション(ajv draft-04/7対応)を追加。
TDD でテストを先行作成し、全 224 テストがパスすることを確認済み。
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat: config-converter 依存パッケージ追加とユーティリティ層の品質改善
- yaml, smol-toml, ajv, ajv-formats を package.json に明示追加
- toml.ts: stringifyToml に入力型ガードを追加
- dotenv.ts: quoteValue ヘルパで " を含む値のエスケープを修正
- yaml.ts: formatYaml にエラーハンドリングを追加
- schema-validator.ts: 非オブジェクトスキーマの入力ガードを追加
- テスト追加: schema-validator.test.ts 新規作成 (4件)、toml/dotenv テスト追加
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat: ConfigConverter React コンポーネントを実装
From/To ToggleGroup・InputField/OutputField・警告バナー・
JSON Schema 検証パネル(dynamic import)・DownloadButton を含む
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat: config-converter をツール一覧・Astro ページ・アイコンに追加
- src/data/tools.ts に Tool エントリ追加 (category: convert)
- src/pages/tools/config-converter.astro を作成
- src/components/ui/ToolIcon.astro に {} アイコンを追加
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(e2e): config-converter の E2E テストを追加
6ケース: YAML→JSON変換、JSON→YAML変換、ネスト拒否エラー、
クリア動作、JSON Schema 検証成功・失敗
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* docs: config-converter のドキュメントを更新
- README.md: 変換・解析テーブルに設定ファイル相互変換を追加
- SPEC.md: ライブラリテーブル・ツール一覧・ディレクトリ構成・実装チェックリストを更新
- docs/decisions.md: [051] yaml/smol-toml/ajv 選定理由・HCL後送り判断を記録
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* chore: develop の変更をマージ(Cloudflare Analytics 決定 [051]、.gitignore 更新)
- docs/decisions.md: develop で追加された [051] Cloudflare Web Analytics 却下エントリを挿入し、
当ブランチの config-converter ライブラリ選定エントリを [052] に繰り下げ
- .gitignore: develop で追加された `.claude/worktrees/` 除外ルールを追加
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(e2e): strict mode violation を修正(exact: true、パス文字列を使用)
- getByLabel('JSON') → getByLabel('JSON', { exact: true }): from=to=JSON 時に
入力ラベル「JSON (整形)」と出力ラベル「JSON」の2要素にマッチする問題を解消
- getByRole('button', { name: '検証する' }) → { exact: true }: 「▶ JSON Schema で検証する」
ボタンにも部分マッチしていた問題を解消
- getByText(/age/) → getByText('/age'): 正規表現がテキストエリア内の "age" を含む
全要素にマッチしていた問題を解消(パスは "/age" とスラッシュ付きなので区別可能)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: レビュー指摘対応(Critical 1-3、推奨 4-5-7、提案 10)
[Critical 1] ajv-draft-04 を dependencies に明示登録
- package.json に "ajv-draft-04": "^1.0.0" を追加
- dev tree の transitive dep に依存していたためクリーンインストール時にビルド失敗の恐れがあった
- docs/decisions.md [052] にも理由を追記
[Critical 2] handleValidate: convert() の .output を参照していなかったバグを修正
- data = convert(output, to, 'json') → data = JSON.parse(convert(output, to, 'json').output)
- to=yaml/toml/dotenv 時にスキーマ検証対象が ConvertResult オブジェクトになっていた
- to=yaml でスキーマ検証が動作することを確認する E2E テストを追加
[Critical 3] .env パーサーがインラインコメントを無視していた問題を修正
- KEY=value # comment → value として取り込むよう \s+# パターンで除去
- クォート内の # は除去しない("value # not a comment" → そのまま保持)
[推奨 4] convert() の二重呼び出しを解消
- useRef<string[]> で warnings をキャプチャし、別 useEffect での再 convert() を廃止
[推奨 5] E2E テストの waitForTimeout(500) を削除
- expect(...).toBeVisible() の auto-retry に委ねる(7件全パス確認)
[推奨 7] formatLabel ヘルパー関数を削除
- FORMAT_LABELS[f] を直接参照するよう変更
[提案 10] ajv 起因の英語エラーメッセージを日本語に統一
- 日本語文字を含まないエラーメッセージは "JSON Schema の解析に失敗しました" で上書き
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: 再レビュー指摘対応(推奨 A・B・C、提案 D)
[推奨 A] parseDotenv インラインコメントのユニットテスト追加
- クォートなし値の # 除去、クォート内保持、URL フラグメント保持、
クォート付き値+末尾コメントの 5 ケースを dotenv.test.ts に追加
- tests: 231 → 236 件
[推奨 B] クォート付き値 + 末尾コメント混在ケースを修正
- KEY="hello" # comment → "hello"(クォート残存)だったバグを修正
- 正規表現で /^"((?:[^"\\]|\\.)*)"\\s*(?:#.*)?$/ により
クォート内容抽出と末尾コメント除去を一括処理
- ループ変数 raw とのシャドウイングを避けるため内部変数を rawValue に改名
[推奨 C] package.json dependencies をアルファベット順に整列
- ajv / ajv-draft-04 / ajv-formats / smol-toml を正しい位置に移動
- lockfile との整合性も改善
[提案 D] フォーマット切替時に validationResult をクリア
- handleFromChange / handleToChange に setValidationResult(null) を追加
- 切替後の出力に対して古い検証結果が残る誤認を防止
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(config-converter): 出力変更時に validationResult をクリアする
入力を編集して output が変わっても古いスキーマ検証結果(成功/失敗)が
残り続ける問題を修正。output/error を監視する既存 useEffect に
setValidationResult(null) を追加し、出力変化と同時に検証結果を無効化する。
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* chore: サブエージェントが PR レビューコメントを投稿できる権限を追加 (#142)
サブエージェントには対話 UI が無いため、未承認の tool 呼び出しは即拒否される。
PR レビュー作業を並列化する際にレビュー本文の投稿および TMPDIR への一時ファイル
書き出しが拒否されていた問題を解消する。
- Bash(gh pr review*) / Bash(gh pr comment*) を allow に追加
- PR メタデータを変更する gh pr edit/create/merge/close は ask に残置
- Write(/tmp/claude*/**) / Write(/private/tmp/claude*/**) を allow に追加
- サンドボックス TMPDIR 配下への一時ファイル書き出しを許可
* docs: PR ベースは必ず develop と CLAUDE.md 最重要ルールに昇格 (#143)
`docs/shared-agent-rules.md §6.3` に既に明記されているが、Claude Code の
system prompt が毎セッション "Main branch (you will usually use this for PRs): main"
と表示してくるため、shared-agent-rules.md を読まずに main 向け PR を作る事故が
繰り返し発生していた。
CLAUDE.md は毎セッション読み込まれるため、最重要ルールの 1 行に昇格させて
即時可視化する。
* fix(qr-reader): 非HTTPS環境で専用エラーメッセージを表示 (#135)
navigator.mediaDevices が undefined の場合に getUserMedia を呼ぶと
TypeError となり「カメラの起動に失敗しました」という汎用メッセージ
が出て原因が特定できなかった。startCamera 冒頭で早期 guard を入れ、
HTTPS または localhost が必要である旨を明示する。
* fix(config-converter): parseDotenv でエスケープシーケンスを解除 (#140)
stringifyDotenv は値の \\ と \" をエスケープして書き出すが、
parseDotenv 側でアンエスケープしておらず、ラウンドトリップが
壊れていた。
unescapeDotenv ヘルパーを追加し、DQ/SQ ブランチで適用。単発走査で
クォート種・バックスラッシュのみアンエスケープするため、
\\\" のような連鎖でも順序問題なく安全に解除できる。
テスト 5 件追加(DQ 内 \"・\\・連鎖、SQ 内 \'、ラウンドトリップ)。
* test(qr-reader): HTTPS 必須メッセージの E2E テストを追加 (#135)
レビュー指摘(feedback_e2e_tests)対応。Playwright の addInitScript で
navigator.mediaDevices を undefined にし、カメラ起動時に新しいエラー
メッセージが表示されることを検証する E2E ケースを追加。
* docs(decisions): .env パーサに expand オプション追加余地を明記
レビューでの指摘を受け、ダブルクォート内のエスケープ方針と
将来 `expand` オプション(POSIX dotenv 相当の `\n` 改行展開)を
追加できる旨を 052 章 .env セクションに 1 行追記。
* docs(rules): 先送り時の issue 化を 6.4 章として追加
「別 PR で対応」「後で追記」と口頭で先送りすると形骸化して
実施されないことがレビューで指摘されたため、共通規約に
「先送り時は必ず issue を起票し PR に番号を貼る」ルールを追加。
* test(qr-reader): HTTPS guard テストの再ナビゲート意図をコメント明記
レビュー任意指摘 (#144) を受け、beforeEach で goto 済みでも
addInitScript は次回ロードから反映されるため再 goto が必要、
という意図を 1 行コメントで補足。挙動変更なし。
* ci: developブランチへのプッシュ時もカバレッジバッジを更新する (#146)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(config-converter): 変換先フォーマット変更時に入力テキストを保持する (#147)
* fix(config-converter): 変換先変更時に入力テキストを保持する
変換先のみ変更した場合、handleToChange から reset() の呼び出しを削除し
入力テキストを保持したまま出力のみ再計算するよう修正。
変換元変更時(handleFromChange)は従来どおり入力をクリアする。
Closes #141
* test(config-converter): E2E テストのナビゲーションパターンを修正
beforeEach が既に /tools/config-converter へ遷移済みのため、
新規テスト内の page.goto('/') と waitForReactHydration を削除。
* test(config-converter): E2E テストのロケータとアサーションをプロジェクト規約に準拠させる
- waitForFunction + querySelectorAll を expect().toHaveValue() に置換
- locator('textarea[readonly]') を getByLabel() に置換
- toContainText を toHaveValue に置換(textarea は value を検証する必要があるため)
docs/ui-conventions.md 3.2 章のロケータ規約に準拠。
* feat(config-converter): JSON Schema 検証パネルに Cmd/Ctrl+Enter ショートカットを追加 (#148)
スキーマ textarea に Cmd+Enter (Mac) / Ctrl+Enter (Win/Linux) で検証を実行するショートカットを追加。InputField に optional な onKeyDown prop を追加して forward する形で実装。isComposing ガード・aria-keyshortcuts・kbd 要素によるアクセシビリティ対応も含む。
Closes #137
* ci: カバレッジバッジ更新を Gist 経由に変更 (#151)
ブランチ保護ルールにより github-actions[bot] からの develop / main への
直接 push が拒否されるため、外部 Gist に JSON を書き込み shields.io の
endpoint バッジで表示する方式に切り替える。
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
* docs: Cloudflare Web Analytics 導入を却下した決定を [051] として記録
プロジェクトの「外部送信ゼロ・外部リソースゼロ・トラッキングなし」方針と
Cloudflare Web Analytics(beacon 自動注入)が直接矛盾するため、有効化しない判断とした。
その経緯と却下理由を decisions.md に [051] として追記する。
* docs: [051] サーバー側集計ログに関する誤記を修正
Cloudflare Pages のトラフィック集計は Web Analytics を有効化しないと
表示されない。beacon なしで見られるという誤った記述を削除し、
正確に「把握する手段が無い」と修正する。
* chore: dev server / Playwright をサンドボックス除外コマンドに追加
`.claude/settings.json` のサンドボックスは listen() を拒否するため、
`npm run dev` / `npm run test:e2e` 経由の Astro dev server が
EPERM で起動できなかった。`excludedCommands` に dev / preview /
e2e / playwright / astro 系コマンドを追加し、ローカル listen を
必要とするコマンドだけサンドボックス外で実行できるようにする。
* chore: .claude/worktrees/ を gitignore に追加
Claude Code の worktrees ディレクトリは既に `.worktrees/` を ignore
しているが、Agent Teams 機能で生成される `.claude/worktrees/` が
別パスのため取り残されていた。誤コミット防止のため追加する。
* feat(tools): QRリーダーツールを新設(qr-reader)
カメラまたは画像ファイルからQRコードを読み取り、テキストを表示するツールを追加。
- `src/utils/qr-reader.ts`: URL/テキスト判別純粋関数 `detectQrContent()`
- `src/components/tools/QrReader.tsx`: カメラ+画像アップロード対応コンポーネント
- `src/pages/tools/qr-reader.astro`: Astro ページラッパー
- `src/data/tools.ts`: `qr-reader` エントリ追加(category: convert)
- ユニットテスト 15件(URL/テキスト判別の境界値を網羅)
- E2E テスト(.claude/settings.json 修正後に実行予定)
- SPEC.md / README.md / docs/decisions.md 更新
HTTP/HTTPS URL のみ検出してフィッシング警告付きリンクを表示。
javascript: 等の危険スキームは text として扱い自動リンク化しない([051])。
既存の useQrCamera フック・jsQR ライブラリを再利用して実装量を最小化。
* test(e2e): qr-reader の Playwright strict mode 違反を修正
- 'カメラ' ボタンの locator に exact: true を追加('カメラを起動' との多重マッチを解消)
- 'example.com' テキストの locator に exact: true を追加(URL 全体を含む <pre> との多重マッチを解消)
* fix(qr-reader): コピーボタンと再スキャンボタンの高さ・スタイルを統一
- rescanButtonStyle を CopyButton と同じ視覚パラメータに合わせる
(lineHeight 1 / padding 0.5rem 0.75rem / borderRadius 0.25rem / fontWeight 700)
- 両ボタンを flex flex-wrap items-center gap-2 でラップし
横並びレイアウトを明示的に制御
* feat(qr-reader): ToolIcon.astro に qr-reader SVGアイコンを追加
QRの3つのファインダーパターン + データモジュール2点 + 水平スキャン線で構成。
qr-code(データモジュール4点)と造形を共有しつつ、スキャン線で「読取」を表現。
* fix(qr-reader): レビュー指摘を修正(必須2件 + 推奨5件)
必須対応:
- canvas.getContext('2d') の non-null assertion を削除し、null 時に
エラーメッセージを表示するガードを追加(iOS Safari OOM 対策)
- 長辺 1600px 超の画像を jsQR 前にダウンスケールし、高解像度
スマホ写真での OOM・フリーズを防止
推奨対応:
- input[type=file] を display:none から visually-hidden に変更し
キーボード・スクリーンリーダーから操作可能にする(A11y)
- モード切替時に decoded / decodeError / cameraError をクリアし
古い結果との並存を防止
- useEffect 依存配列の参照を const { stopCamera } = camera で
分割代入し、exhaustive-deps 警告を回避
- E2E: not.toBeVisible() → toHaveCount(0) に変更(DOM から
削除されることの検証+auto-retry の安定化)
* docs(qr-reader): e.target.value クリアの意図コメントを追加
同名ファイルの再選択を可能にするパターンであることを明示し、
onload 完了前にクリアする理由(File は file 変数で参照済み)を説明。
* feat: 設定ファイル相互変換ツール (config-converter) を追加 (#133)
* feat: config-converter ユーティリティ層とユニットテストを実装
JSON/YAML/TOML/dotenv の相互変換・整形ユーティリティと
JSON Schema バリデーション(ajv draft-04/7対応)を追加。
TDD でテストを先行作成し、全 224 テストがパスすることを確認済み。
* feat: config-converter 依存パッケージ追加とユーティリティ層の品質改善
- yaml, smol-toml, ajv, ajv-formats を package.json に明示追加
- toml.ts: stringifyToml に入力型ガードを追加
- dotenv.ts: quoteValue ヘルパで " を含む値のエスケープを修正
- yaml.ts: formatYaml にエラーハンドリングを追加
- schema-validator.ts: 非オブジェクトスキーマの入力ガードを追加
- テスト追加: schema-validator.test.ts 新規作成 (4件)、toml/dotenv テスト追加
* feat: ConfigConverter React コンポーネントを実装
From/To ToggleGroup・InputField/OutputField・警告バナー・
JSON Schema 検証パネル(dynamic import)・DownloadButton を含む
* feat: config-converter をツール一覧・Astro ページ・アイコンに追加
- src/data/tools.ts に Tool エントリ追加 (category: convert)
- src/pages/tools/config-converter.astro を作成
- src/components/ui/ToolIcon.astro に {} アイコンを追加
* test(e2e): config-converter の E2E テストを追加
6ケース: YAML→JSON変換、JSON→YAML変換、ネスト拒否エラー、
クリア動作、JSON Schema 検証成功・失敗
* docs: config-converter のドキュメントを更新
- README.md: 変換・解析テーブルに設定ファイル相互変換を追加
- SPEC.md: ライブラリテーブル・ツール一覧・ディレクトリ構成・実装チェックリストを更新
- docs/decisions.md: [051] yaml/smol-toml/ajv 選定理由・HCL後送り判断を記録
* chore: develop の変更をマージ(Cloudflare Analytics 決定 [051]、.gitignore 更新)
- docs/decisions.md: develop で追加された [051] Cloudflare Web Analytics 却下エントリを挿入し、
当ブランチの config-converter ライブラリ選定エントリを [052] に繰り下げ
- .gitignore: develop で追加された `.claude/worktrees/` 除外ルールを追加
* fix(e2e): strict mode violation を修正(exact: true、パス文字列を使用)
- getByLabel('JSON') → getByLabel('JSON', { exact: true }): from=to=JSON 時に
入力ラベル「JSON (整形)」と出力ラベル「JSON」の2要素にマッチする問題を解消
- getByRole('button', { name: '検証する' }) → { exact: true }: 「▶ JSON Schema で検証する」
ボタンにも部分マッチしていた問題を解消
- getByText(/age/) → getByText('/age'): 正規表現がテキストエリア内の "age" を含む
全要素にマッチしていた問題を解消(パスは "/age" とスラッシュ付きなので区別可能)
* fix: レビュー指摘対応(Critical 1-3、推奨 4-5-7、提案 10)
[Critical 1] ajv-draft-04 を dependencies に明示登録
- package.json に "ajv-draft-04": "^1.0.0" を追加
- dev tree の transitive dep に依存していたためクリーンインストール時にビルド失敗の恐れがあった
- docs/decisions.md [052] にも理由を追記
[Critical 2] handleValidate: convert() の .output を参照していなかったバグを修正
- data = convert(output, to, 'json') → data = JSON.parse(convert(output, to, 'json').output)
- to=yaml/toml/dotenv 時にスキーマ検証対象が ConvertResult オブジェクトになっていた
- to=yaml でスキーマ検証が動作することを確認する E2E テストを追加
[Critical 3] .env パーサーがインラインコメントを無視していた問題を修正
- KEY=value # comment → value として取り込むよう \s+# パターンで除去
- クォート内の # は除去しない("value # not a comment" → そのまま保持)
[推奨 4] convert() の二重呼び出しを解消
- useRef<string[]> で warnings をキャプチャし、別 useEffect での再 convert() を廃止
[推奨 5] E2E テストの waitForTimeout(500) を削除
- expect(...).toBeVisible() の auto-retry に委ねる(7件全パス確認)
[推奨 7] formatLabel ヘルパー関数を削除
- FORMAT_LABELS[f] を直接参照するよう変更
[提案 10] ajv 起因の英語エラーメッセージを日本語に統一
- 日本語文字を含まないエラーメッセージは "JSON Schema の解析に失敗しました" で上書き
* fix: 再レビュー指摘対応(推奨 A・B・C、提案 D)
[推奨 A] parseDotenv インラインコメントのユニットテスト追加
- クォートなし値の # 除去、クォート内保持、URL フラグメント保持、
クォート付き値+末尾コメントの 5 ケースを dotenv.test.ts に追加
- tests: 231 → 236 件
[推奨 B] クォート付き値 + 末尾コメント混在ケースを修正
- KEY="hello" # comment → "hello"(クォート残存)だったバグを修正
- 正規表現で /^"((?:[^"\\]|\\.)*)"\\s*(?:#.*)?$/ により
クォート内容抽出と末尾コメント除去を一括処理
- ループ変数 raw とのシャドウイングを避けるため内部変数を rawValue に改名
[推奨 C] package.json dependencies をアルファベット順に整列
- ajv / ajv-draft-04 / ajv-formats / smol-toml を正しい位置に移動
- lockfile との整合性も改善
[提案 D] フォーマット切替時に validationResult をクリア
- handleFromChange / handleToChange に setValidationResult(null) を追加
- 切替後の出力に対して古い検証結果が残る誤認を防止
* fix(config-converter): 出力変更時に validationResult をクリアする
入力を編集して output が変わっても古いスキーマ検証結果(成功/失敗)が
残り続ける問題を修正。output/error を監視する既存 useEffect に
setValidationResult(null) を追加し、出力変化と同時に検証結果を無効化する。
---------
* chore: サブエージェントが PR レビューコメントを投稿できる権限を追加 (#142)
サブエージェントには対話 UI が無いため、未承認の tool 呼び出しは即拒否される。
PR レビュー作業を並列化する際にレビュー本文の投稿および TMPDIR への一時ファイル
書き出しが拒否されていた問題を解消する。
- Bash(gh pr review*) / Bash(gh pr comment*) を allow に追加
- PR メタデータを変更する gh pr edit/create/merge/close は ask に残置
- Write(/tmp/claude*/**) / Write(/private/tmp/claude*/**) を allow に追加
- サンドボックス TMPDIR 配下への一時ファイル書き出しを許可
* docs: PR ベースは必ず develop と CLAUDE.md 最重要ルールに昇格 (#143)
`docs/shared-agent-rules.md §6.3` に既に明記されているが、Claude Code の
system prompt が毎セッション "Main branch (you will usually use this for PRs): main"
と表示してくるため、shared-agent-rules.md を読まずに main 向け PR を作る事故が
繰り返し発生していた。
CLAUDE.md は毎セッション読み込まれるため、最重要ルールの 1 行に昇格させて
即時可視化する。
* fix(qr-reader): 非HTTPS環境で専用エラーメッセージを表示 (#135)
navigator.mediaDevices が undefined の場合に getUserMedia を呼ぶと
TypeError となり「カメラの起動に失敗しました」という汎用メッセージ
が出て原因が特定できなかった。startCamera 冒頭で早期 guard を入れ、
HTTPS または localhost が必要である旨を明示する。
* fix(config-converter): parseDotenv でエスケープシーケンスを解除 (#140)
stringifyDotenv は値の \\ と \" をエスケープして書き出すが、
parseDotenv 側でアンエスケープしておらず、ラウンドトリップが
壊れていた。
unescapeDotenv ヘルパーを追加し、DQ/SQ ブランチで適用。単発走査で
クォート種・バックスラッシュのみアンエスケープするため、
\\\" のような連鎖でも順序問題なく安全に解除できる。
テスト 5 件追加(DQ 内 \"・\\・連鎖、SQ 内 \'、ラウンドトリップ)。
* test(qr-reader): HTTPS 必須メッセージの E2E テストを追加 (#135)
レビュー指摘(feedback_e2e_tests)対応。Playwright の addInitScript で
navigator.mediaDevices を undefined にし、カメラ起動時に新しいエラー
メッセージが表示されることを検証する E2E ケースを追加。
* docs(decisions): .env パーサに expand オプション追加余地を明記
レビューでの指摘を受け、ダブルクォート内のエスケープ方針と
将来 `expand` オプション(POSIX dotenv 相当の `\n` 改行展開)を
追加できる旨を 052 章 .env セクションに 1 行追記。
* docs(rules): 先送り時の issue 化を 6.4 章として追加
「別 PR で対応」「後で追記」と口頭で先送りすると形骸化して
実施されないことがレビューで指摘されたため、共通規約に
「先送り時は必ず issue を起票し PR に番号を貼る」ルールを追加。
* test(qr-reader): HTTPS guard テストの再ナビゲート意図をコメント明記
レビュー任意指摘 (#144) を受け、beforeEach で goto 済みでも
addInitScript は次回ロードから反映されるため再 goto が必要、
という意図を 1 行コメントで補足。挙動変更なし。
* ci: developブランチへのプッシュ時もカバレッジバッジを更新する (#146)
* fix(config-converter): 変換先フォーマット変更時に入力テキストを保持する (#147)
* fix(config-converter): 変換先変更時に入力テキストを保持する
変換先のみ変更した場合、handleToChange から reset() の呼び出しを削除し
入力テキストを保持したまま出力のみ再計算するよう修正。
変換元変更時(handleFromChange)は従来どおり入力をクリアする。
Closes #141
* test(config-converter): E2E テストのナビゲーションパターンを修正
beforeEach が既に /tools/config-converter へ遷移済みのため、
新規テスト内の page.goto('/') と waitForReactHydration を削除。
* test(config-converter): E2E テストのロケータとアサーションをプロジェクト規約に準拠させる
- waitForFunction + querySelectorAll を expect().toHaveValue() に置換
- locator('textarea[readonly]') を getByLabel() に置換
- toContainText を toHaveValue に置換(textarea は value を検証する必要があるため)
docs/ui-conventions.md 3.2 章のロケータ規約に準拠。
* feat(config-converter): JSON Schema 検証パネルに Cmd/Ctrl+Enter ショートカットを追加 (#148)
スキーマ textarea に Cmd+Enter (Mac) / Ctrl+Enter (Win/Linux) で検証を実行するショートカットを追加。InputField に optional な onKeyDown prop を追加して forward する形で実装。isComposing ガード・aria-keyshortcuts・kbd 要素によるアクセシビリティ対応も含む。
Closes #137
* ci: カバレッジバッジ更新を Gist 経由に変更 (#151)
ブランチ保護ルールにより github-actions[bot] からの develop / main への
直接 push が拒否されるため、外部 Gist に JSON を書き込み shields.io の
endpoint バッジで表示する方式に切り替える。
---------
Co-authored-by: takasefumiyuki <71715982+fumtas1k@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
概要
`docs/tool-candidates.md` A-2「設定ファイル相互変換」を実装した。YAML・JSON・TOML・.env の 4 形式をブラウザ完結で相互変換するツール。
変更内容
テスト計画
スコープ外(Phase 2 以降)
🤖 Generated with Claude Code