prowler-ui について
prowler-uiは、ProwlerのUI開発者向けコーディングスキルです。既存のデザインシステムに沿ったコンポーネント、スタイル、構成、品質確認の規約をまとめています。
主な機能
- 01shadcn/uiとTailwindの規約
- 02コンポーネントとディレクトリの配置規則
- 03フォーム検証と状態管理のパターン
- 04共有スタイルと表示ユーティリティの再利用
- 05コミット前の品質チェックリスト
活用シーン
- 01Prowler UIへのページやコンポーネントの追加
- 02既存パターンに沿ったフォームと状態の実装
- 03デザインシステムに基づくUI変更のレビュー
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ prowler-cloud/ prowler --skill prowler-uiインストール後、Claude Code で/prowler-uiを入力して読み込まれたか確認します。
同梱ファイルreferences/ui-docs.md
詳細
関連する汎用スキル
typescript- Const 型、フラットなインターフェースreact-19- useMemo/useCallback は使用せず、コンパイラーを使用nextjs-16- App Router、Server Actionstailwind-4- cn() ユーティリティ、スタイル規則zod-4- スキーマ検証zustand-5- 状態管理ai-sdk-5- チャット/AI 機能playwright- E2E テスト(prowler-test-uiも参照)
技術スタック(バージョン)
Next.js 16.2.3 | React 19.2.5 | Tailwind 4.1.18 | shadcn/ui
Zod 4.1.11 | React Hook Form 7.62.0 | Zustand 5.0.8
NextAuth 5.0.0-beta.30 | Recharts 2.15.4
重要:コンポーネントライブラリのルール
- 必ず:
shadcn/ui+ Tailwind(components/shadcn/)を使用する - 禁止:
components/ui/にコンポーネントを追加する(一時的な再エクスポート用シムは prowler-cloud オーバーレイ専用)
デザインシステムの規律(必須)
すべての UI 作業に適用されます。デザインシステムを唯一の信頼できる情報源として、そのまま再利用し、意図を持って拡張してください。
- まず再利用し、決して作り直さない。 何かを実装する前に、
components/shadcn/とコードベース内の既存の使用箇所を検索し、同等のものがないか確認してください。既存のコンポーネントがある場合、カスタムコンポーネント、モーダルラッパー、プリミティブを作成してはいけません。 - 定義済みのバリアント/スタイルを過不足なく使用する。 呼び出し箇所では、コンポーネントの
variant/size/toneプロパティで外観を指定してください。共有コントロール(Button、SelectTrigger、SelectItem、Modal、バッジなど)に場当たり的な視覚用className(色、不透明度、ホバー/フォーカス/無効状態、見た目のための余白)を追加したり、適切なセマンティックバリアントを使わずに済ませたりしてはいけません。 - モーダル:
@/components/shadcn/modalのみを使用します。セレクト:components/shadcn/selectを使用します。 - 色:
ui/styles/globals.cssの既存のセマンティックトークンを再利用します。生の Tailwind 色ユーティリティ(例:bg-blue-950/40)や 16 進カラーは使用しません。適合するトークンがない場合は作業を止め、デザインオーナーに確認してください。新しいトークンを作成したり、既存のものに近いトークンを追加したりしてはいけません。 - 本当に新しいバリアント/トークンが必要な場合: これはデザインシステムの変更です。共有コンポーネント API に追加し(デザイン担当者の承認を得て)、その後に使用してください。呼び出し箇所で独自に決めてはいけません。
UI の PR をレビューする際は、shadcn と重複するカスタムモーダル/プリミティブ、共有コントロールに指定された呼び出し箇所の視覚用 className、生の色ユーティリティ、共有コンポーネント API を通さずに追加された新しいバリアント/トークンを指摘してください。
判断ツリー
コンポーネントの配置
New UI primitive? → components/shadcn/ (shadcn/ui + Tailwind)
Used by 1 domain? → components/{domain}/
Used by 2+ domains? → components/shared/
Needs state/hooks? → "use client"
Server component? → No directive needed
コードの配置場所
Server action → actions/{feature}/{feature}.ts
Data transform → actions/{feature}/{feature}.adapter.ts
Types (shared 2+) → types/{domain}.ts
Types (local 1) → {feature}/types.ts
Utils (shared 2+) → lib/
Utils (local 1) → {feature}/utils/
Hooks (shared 2+) → hooks/
Hooks (local 1) → {feature}/hooks.ts
UI primitive → components/shadcn/
Domain component → components/{domain}/
非推奨:
components/ui/は、レガシーなインポートパスを prowler-cloud オーバーレイ向けにcomponents/shadcn/へ対応付ける一時的な再エクスポート用シムです。 HeroUI は完全に削除されています。ここにコンポーネントを追加したり、ここからインポートしたりしてはいけません。代わりに プリミティブには@/components/shadcnを、ドメイン固有のコンポーネントには@/components/{domain}を使用してください。 cloud リポジトリが@/components/shadcnに移行したら、このシムを削除してください。
スタイルの判断
Tailwind class exists? → className
Dynamic value? → style prop
Conditional styles? → cn()
Static only? → className (no cn())
Recharts/library? → CHART_COLORS constant + var()
スコープのルール(絶対)
- 2 か所以上で使用 →
lib/、types/、hooks/のいずれか(コンポーネントはcomponents/{domain}/に配置) - 1 か所で使用 → 機能ディレクトリ内に置く
- すべてのフォルダー構成の判断にこのルールを適用する
プロジェクト構成
ui/
├── app/
│ ├── (auth)/ # Auth pages (login, signup)
│ └── (prowler)/ # Main app
│ ├── compliance/
│ ├── findings/
│ ├── providers/
│ ├── scans/
│ ├── services/
│ └── integrations/
├── components/
│ ├── shadcn/ # shadcn/ui primitives (USE THIS)
│ ├── shared/ # Cross-domain composed components (2+ domains)
│ ├── ui/ # DEPRECATED shim → re-exports shadcn (do not use)
│ ├── {domain}/ # Domain-specific (compliance, findings, providers, etc.)
│ ├── filters/ # Filter components
│ ├── graphs/ # Chart components
│ └── icons/ # Icon components
├── actions/ # Server actions
├── types/ # Shared types
├── hooks/ # Shared hooks
├── lib/ # Utilities
├── store/ # Zustand state
├── tests/ # Playwright E2E
└── styles/ # Global CSS
Recharts(特別なケース)
className を受け付けない Recharts のプロパティには、次のようにします。
const CHART_COLORS = {
primary: "var(--color-primary)",
secondary: "var(--color-secondary)",
text: "var(--color-text)",
gridLine: "var(--color-border)",
};
// Only use var() for library props, NEVER in className
<XAxis tick={{ fill: CHART_COLORS.text }} />
<CartesianGrid stroke={CHART_COLORS.gridLine} />
フォームとバリデーションのパターン
"use client";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
const schema = z.object({
email: z.email(), // Zod 4 syntax
name: z.string().min(1),
});
type FormData = z.infer<typeof schema>;
export function MyForm() {
const { register, handleSubmit, formState: { errors } } = useForm<FormData>({
resolver: zodResolver(schema),
});
const onSubmit = async (data: FormData) => {
await serverAction(data);
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register("email")} />
{errors.email && <span>{errors.email.message}</span>}
<button type="submit">Submit</button>
</form>
);
}
コマンド
# Development
cd ui && pnpm install
cd ui && pnpm run dev
# Code Quality
cd ui && pnpm run typecheck
cd ui && pnpm run lint:fix
cd ui && pnpm run format:write
cd ui && pnpm run healthcheck # typecheck + lint
# Testing
cd ui && pnpm run test:e2e
cd ui && pnpm run test:e2e:ui
cd ui && pnpm run test:e2e:debug
# Build
cd ui && pnpm run build
cd ui && pnpm start
バッチと即時のコンポーネント API(必須)
コンポーネントが バッチ(遅延実行、送信ベース)と 即時(コールバックをすぐに呼び出す)の両方の動作をサポートする場合、連動するプロパティは判別可能なユニオンで表現してください。独立したオプショナルプロパティとして定義してはいけません。連動するプロパティは、すべて指定するか、すべて省略する必要があります。
// ❌ NEVER: Independent optionals — allows invalid half-states
interface FilterProps {
onBatchApply?: (values: string[]) => void;
onInstantChange?: (value: string) => void;
isBatchMode?: boolean;
}
// ✅ ALWAYS: Discriminated union — one valid shape per mode
type BatchProps = {
mode: "batch";
onApply: (values: string[]) => void;
onCancel: () => void;
};
type InstantProps = {
mode: "instant";
onChange: (value: string) => void;
// onApply/onCancel are forbidden here via structural exclusion
onApply?: never;
onCancel?: never;
};
type FilterProps = BatchProps | InstantProps;
これにより、無効なプロパティの組み合わせを実行時の予期せぬ問題ではなく、コンパイルエラーにできます。
共有表示ユーティリティをまず再利用する(必須)
ローカルな表示用マップ(ラベル、プロバイダー名、ステータス文字列、カテゴリのフォーマッター)を追加する前に、ui/types/* と ui/lib/* を検索して既存のヘルパーがないか確認してください。
// ✅ CHECK THESE FIRST before creating a new map:
// ui/lib/utils.ts → general formatters
// ui/types/providers.ts → provider display names, icons
// ui/types/findings.ts → severity/status display maps
// ui/types/compliance.ts → category/group formatters
// ❌ NEVER add a local map that already exists:
const SEVERITY_LABELS: Record<string, string> = {
critical: "Critical",
high: "High",
// ...duplicating an existing shared map
};
// ✅ Import and reuse instead:
import { severityLabel } from "@/types/findings";
ヘルパーが存在せず、2 か所以上で使用する場合は、ui/lib/ または ui/types/ に追加して再利用してください。1 か所だけで使用する場合に限り、ローカルに配置してください。
派生状態のルール(必須)
props や searchParams を同期するための useState + useEffect パターンは避けてください。同期の不具合や不要な再レンダリングの原因になります。値は信頼できる情報源から直接導出してください。
// ❌ NEVER: Mirror props into state via effect
const [localFilter, setLocalFilter] = useState(filter);
useEffect(() => { setLocalFilter(filter); }, [filter]);
// ✅ ALWAYS: Derive directly
const localFilter = filter; // or compute inline
ローカル状態が本当に必要な場合(楽観的 UI や送信前の編集内容など)は、短いコメントを追加してください。
// Local state needed: user edits are buffered until "Apply" is clicked
const [pending, setPending] = useState(initialValues);
ラベルマップのキーを厳密に型指定する(必須)
キーの集合が既知の場合は、Record<string, string> を避けてください。明示的なユニオン型または const キーのオブジェクトを使用して、コンパイル時にタイプミスを検出できるようにしてください。
// ❌ Loose — typos compile silently
const STATUS_LABELS: Record<string, string> = {
actve: "Active", // typo, no error
};
// ✅ Tight — union key
type Status = "active" | "inactive" | "pending";
const STATUS_LABELS: Record<Status, string> = {
active: "Active",
inactive: "Inactive",
pending: "Pending",
// actve: "Active" ← compile error
};
// ✅ Also fine — const satisfies
const STATUS_LABELS = {
active: "Active",
inactive: "Inactive",
pending: "Pending",
} as const satisfies Record<Status, string>;
コミット前の QA チェックリスト
-
pnpm run typecheckが成功する -
pnpm run lint:fixが成功する -
pnpm run format:writeが成功する - 関連する E2E テストが成功する
- すべての UI 状態(読み込み中、エラー、空)が処理されている
- コードにシークレットが含まれていない(
.env.localを使用する) - エラーメッセージがサニタイズされている(スタックトレースをユーザーに表示しない)
- サーバー側の検証がある(クライアントを信用しない)
- アクセシビリティ:キーボード操作、ARIA ラベル
- モバイル対応(該当する場合)
再レビュー依頼前のチェックリスト(レビューコメントの整理)
レビュアーに再レビューを依頼する前に、次の点を確認してください。
- 未解決のインラインスレッドはすべて、修正するか、根拠を明示して回答している
- コメントに同意した場合:変更をコミットし、返信にコミットハッシュを記載している
- 同意しない場合:明確な根拠を返信で説明している — スレッドを未回答のまま放置しない
- すべてのスレッドが整理された状態になってから、レビューを再依頼する
リソース
- ドキュメント:references/ でローカルの開発者ガイドへのリンクを確認してください