plan-ui-change について
複雑な Blazor UI の変更を計画・実装するスキルです。コーディングエージェントが UI を役割ごとのコンポーネントに分け、状態とデータフローを定めて、末端から構築できるよう支援します。
主な機能
- 01画面領域をコンポーネントに分割
- 02レンダーモードと状態所有者の分類
- 03パラメーターとイベントのデータフロー設計
- 04コンポーネント再利用機会の確認
- 05末端からのコンポーネント実装
活用シーン
- 01複数領域のダッシュボード計画
- 02大規模ページのコンポーネント分割
- 03親子間の状態とイベントの整理
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ dotnet/ skills --skill plan-ui-changeインストール後、Claude Code で/plan-ui-changeを入力して読み込まれたか確認します。
詳細
Blazor UI 変更の計画
複雑な UI 機能の構築を依頼された場合は、まずコンポーネントの分割を計画し、その後すぐに実装してください。単一のモノリシックなページコンポーネントが適切な答えとなることはほとんどありません。UI を、焦点を絞った組み合わせ可能なコンポーネントに分割してください。
計画ワークフロー
ステップ 1 — 視覚的な領域を整理する
依頼内容を読み、個別の視覚的な領域をすべて特定します。それぞれに独自のデータ、動作、またはレイアウト上の責任がある領域は、コンポーネントの候補です。
コンポーネントツリーを作成します。
InventoryDashboard (page — owns data, orchestrates layout)
├── StockSummaryBar (read-only stats: total items, low-stock count, value)
├── InventoryFilters (search box, category dropdown, stock-level toggle)
├── InventoryTable (sortable table of products)
│ └── InventoryRow (single product row with inline edit/delete)
└── AddProductForm (slide-out form for new products)
コンポーネントを特定する際のルール:
- 明確な責任 — 領域が独自の状態や動作を持つ場合 → 個別のコンポーネントにする
- 繰り返し構造 — リスト内のアイテム、グリッド内のカード → アイテムのテンプレートを抽出する
- 独立したインタラクティブ性 — 兄弟要素とは別にユーザー入力を処理するセクション → 個別のコンポーネントにする
- サイズ — 単独でマークアップが約 150 行を超えるセクション → 分割する
ステップ 2 — 各コンポーネントを分類する
ツリー内の各コンポーネントについて、次の項目を決めます。
| コンポーネント | アクション | レンダーモード | 所有する状態 | 行数(概算) |
|---|---|---|---|---|
| InventoryDashboard | 作成 | InteractiveServer | 商品リスト、フィルター状態 | ~80 |
| StockSummaryBar | 作成 | (継承) | なし — データを受け取る | ~30 |
| InventoryFilters | 作成 | (継承) | 検索テキスト、選択中のカテゴリ | ~60 |
| InventoryTable | 作成 | (継承) | ソート列、ソート方向 | ~50 |
| InventoryRow | 作成 | (継承) | インライン編集モードのフラグ | ~60 |
| AddProductForm | 作成 | (継承) | フォームモデル | ~80 |
マークアップとコードを合わせて約 200 行を超えるページコンポーネントは大きすぎます。 見積もりで単一のコンポーネントがそれを超える場合は、さらに分割してください。
ステップ 3 — データフローを設計する
各データの状態を所有するコンポーネントを特定し、データの流れを整理します。
InventoryDashboard (owns: products[], filters)
│
├─ [Parameter] products ──→ StockSummaryBar (reads aggregate stats)
│
├─ [Parameter] filters ──→ InventoryFilters
│ └─ EventCallback<Filters> OnFiltersChanged ──→ InventoryDashboard
│
├─ [Parameter] filteredProducts ──→ InventoryTable
│ └─ [Parameter] product ──→ InventoryRow
│ ├─ EventCallback<Product> OnSave ──→ InventoryTable ──→ InventoryDashboard
│ └─ EventCallback<Product> OnDelete ──→ InventoryTable ──→ InventoryDashboard
│
└─ EventCallback<Product> OnProductAdded ←── AddProductForm
ルール:
- データは常に
[Parameter]を通じて下方向に流れる - イベントは常に
EventCallback<T>を通じて上方向に流れる - ページ/親コンポーネントがデータを所有し、フィルターや変換を適用したビューを子に渡す
- 子コンポーネントはパラメーターを変更しない。コールバックを介して親に通知する
- 中間コンポーネントが必要としないデータを 2 階層を超えて渡す必要がある場合は、カスケード値またはスコープ付きサービスを使用する
ステップ 4 — 再利用の機会を特定する
新しいコンポーネントを作成する前に、プロジェクト内の既存コンポーネントを目的に利用できるか確認します。次の項目を探してください。
- 構造が一致する既存のリストアイテムコンポーネント
- プロジェクト内にすでにある共有フィルター/検索コンポーネント
- テンプレートを受け取る汎用コンポーネント(例:
DataTable<T>、Pagination)
複数のページで使用するコンポーネントは、Shared/ または Components/ フォルダーに配置します。
ステップ 5 — 実装の順序を決める
下から上へ構築します。まずリーフコンポーネントを作成し、次にそれらを組み合わせる親コンポーネントを作成します。
- モデル/DTO — データの形を定義する
- サービス — データアクセス、ビジネスロジック(インターフェイス+実装)
- リーフコンポーネント — 子を持たないコンポーネント(InventoryRow、StockSummaryBar)
- コンテナーコンポーネント — リーフを組み合わせるコンポーネント(InventoryTable、InventoryFilters)
- ページコンポーネント — すべてをつなぎ合わせ、ルートを登録する
- 設定 — DI 登録、レンダーモードのセットアップ
各コンポーネントは単独でコンパイルできる必要があります。まだ存在しないコンポーネントを参照してはいけません。
出力形式
計画を簡潔に提示してから、すぐに実装に進んでください。計画だけで終わったり、コードを書く前に確認を求めたりしてはいけません。計画は思考のためのツールであり、成果物ではありません。
## Component Plan: [Feature Name]
### Component Tree
[ASCII tree showing parent-child relationships]
### Component Table
| Component | Action | Render Mode | Purpose | Est. Lines |
|-----------|--------|-------------|---------|------------|
| ... | ... | ... | ... | ... |
### Data Flow
[State owner] → [Parameters down] → [EventCallbacks up]
### Implementation Order
1. [First file to create — why]
2. [Second file — why]
...
計画を出力した後、記載した順序でコンポーネントの実装をすぐに開始してください。承認を待ったり、「続けてもよいですか?」と尋ねたりしてはいけません。計画はユーザーに承認してもらうための提案ではなく、あなたが従うためのガイドです。
避けるべきアンチパターン
| アンチパターン | 望ましくない理由 | 正しいアプローチ |
|---|---|---|
| 500行を超える単一のページコンポーネント | テスト、再利用、保守ができません | 役割を絞ったコンポーネントに分割します |
| 中間コンポーネントを介して10個以上のパラメーターを渡す | パラメーターの受け渡しが意図を分かりにくくします | カスケード値またはスコープ付き状態サービスを使用します |
| 子コンポーネントが独自にAPIからデータを取得する | 複数のコンポーネントが重複した呼び出しを行います | 親がデータを所有し、パラメーターで渡します |
| 複雑なマークアップを使ってリスト項目をインラインでレンダリングする | ロジックが重複し、再利用できず、テストも困難です | 項目テンプレートを独立したコンポーネントに切り出します |
| すべてを1つのファイルで構築してから「後でリファクタリングする」 | リファクタリングはほとんど行われず、モノリスのままリリースされます | あらかじめ分割を計画します |
| 一度限りの用途に汎用コンポーネントを使う | 過剰な設計によって複雑さが増します | 再利用の実績が確認できた場合にのみ、ジェネリックとして切り出します |
ガイドライン
- 簡潔に計画してから、実装します。 コンポーネントの表とデータフロー図を簡潔に作成し、すぐに
.razorファイルを作成します。計画だけで終わらせないでください。 - 大きなコンポーネントを1つ作るより、小さなコンポーネントを複数作ることを優先します。 目的が明確なコンポーネントは理解、テスト、再利用が容易です。
- 最初に決めるべきことは、状態をどのコンポーネントが所有するかです。 データ取得のロジックを書く前に、どのコンポーネントがデータを所有するかを決めます。
- 下から上へ構築します。 まず末端のコンポーネントを作成し、親コンポーネントからすぐに参照できるようにします。
- コンポーネント名には、内部で何をするかではなく、何をレンダリングするかを使います。
ProductRendererではなくProductCard、FilterHandlerではなくOrderFiltersとします。