Security

prowler-ui - Claude MCP Skill

Prowler UI-specific patterns. For generic patterns, see: typescript, react-19, nextjs-16, tailwind-4. Trigger: When working inside ui/ on Prowler-specific conventions (shadcn, folder placement, actions/adapters, shared types/hooks/lib).

SEO Guide: Enhance your AI agent with the prowler-ui tool. This Model Context Protocol (MCP) server allows Claude Desktop and other LLMs to prowler ui-specific patterns. for generic patterns, see: typescript, react-19, nextjs-16, tailwind-4... Download and configure this skill to unlock new capabilities for your AI workflow.

🌟378 stars β€’ 2376 forks
πŸ“₯0 downloads

Documentation

SKILL.md
## Related Generic Skills

- `typescript` - Const types, flat interfaces
- `react-19` - No useMemo/useCallback, compiler
- `nextjs-16` - App Router, Server Actions
- `tailwind-4` - cn() utility, styling rules
- `zod-4` - Schema validation
- `zustand-5` - State management
- `ai-sdk-5` - Chat/AI features
- `playwright` - E2E testing (see also `prowler-test-ui`)

## Tech Stack (Versions)

```text
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
```

## CRITICAL: Component Library Rule

- **ALWAYS**: Use `shadcn/ui` + Tailwind (`components/shadcn/`)
- **NEVER**: Add components to `components/ui/` (temporary re-export shims for the prowler-cloud overlay only)

## Design System Discipline (REQUIRED)

Applies to ALL UI work. The design system is the single source of truth β€” reuse it exactly, extend it deliberately.

- **Reuse first, never reinvent.** Before building anything, search `components/shadcn/` and existing usages in the codebase for an equivalent. Do NOT create a custom component, modal wrapper, or primitive when one already exists.
- **Use exactly the defined variants/styles β€” no more, no less.** At the call site, drive appearance through the component's `variant`/`size`/`tone` props. Never add ad-hoc visual `className` (color, opacity, hover/focus/disabled, spacing-for-looks) to shared controls (`Button`, `SelectTrigger`, `SelectItem`, `Modal`, badges…), and never skip the correct semantic variant.
- **Modals**: only `@/components/shadcn/modal`. **Selects**: `components/shadcn/select`.
- **Colors**: reuse existing semantic tokens from `ui/styles/globals.css`. No raw Tailwind color utilities (e.g. `bg-blue-950/40`), no hex. If no token fits, STOP and ask the design owner β€” do not invent or near-duplicate tokens.
- **Need a genuinely new variant/token?** That is a design-system change: add it to the shared component API (with design sign-off), then consume it. It is never a call-site decision.

When reviewing UI PRs, flag: custom modals/primitives that duplicate shadcn, call-site visual `className` on shared controls, raw color utilities, and new variants/tokens introduced without going through the shared component API.

## DECISION TREES

### Component Placement

```text
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
```

### Code Location

```text
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}/
```

> **Deprecated:** `components/ui/` is a temporary re-export shim that maps
> legacy import paths to `components/shadcn/` for the prowler-cloud overlay.
> HeroUI is fully removed. Never add or import components here β€” use
> `@/components/shadcn` (primitives) or `@/components/{domain}` instead.
> Delete the shim once the cloud repo migrates to `@/components/shadcn`.

### Styling Decision

```text
Tailwind class exists? β†’ className
Dynamic value?         β†’ style prop
Conditional styles?    β†’ cn()
Static only?           β†’ className (no cn())
Recharts/library?      β†’ CHART_COLORS constant + var()
```

### Scope Rule (ABSOLUTE)

- Used 2+ places β†’ `lib/` or `types/` or `hooks/` (components go in `components/{domain}/`)
- Used 1 place β†’ keep local in feature directory
- **This determines ALL folder structure decisions**

## Project Structure

```text
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 (Special Case)

For Recharts props that don't accept className:

```typescript
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} />
```

## Form + Validation Pattern

```typescript
"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>
  );
}
```

## Commands

```bash
# 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
```

## Batch vs Instant Component API (REQUIRED)

When a component supports both **batch** (deferred, submit-based) and **instant** (immediate callback) behavior, model the coupling with a discriminated union β€” never as independent optionals. Coupled props must be all-or-nothing.

```typescript
// ❌ 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;
```

This makes invalid prop combinations a compile error, not a runtime surprise.

## Reuse Shared Display Utilities First (REQUIRED)

Before adding **local** display maps (labels, provider names, status strings, category formatters), search `ui/types/*` and `ui/lib/*` for existing helpers.

```typescript
// βœ… 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";
```

If a helper doesn't exist and will be used in 2+ places, add it to `ui/lib/` or `ui/types/` and reuse it. Keep local only if used in exactly one place.

## Derived State Rule (REQUIRED)

Avoid `useState` + `useEffect` patterns that mirror props or searchParams β€” they create sync bugs and unnecessary re-renders. Derive values directly from the source of truth.

```typescript
// ❌ 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
```

If local state is genuinely needed (e.g., optimistic UI, pending edits before submit), add a short comment:

```typescript
// Local state needed: user edits are buffered until "Apply" is clicked
const [pending, setPending] = useState(initialValues);
```

## Strict Key Typing for Label Maps (REQUIRED)

Avoid `Record<string, string>` when the key set is known. Use an explicit union type or a const-key object so typos are caught at compile time.

```typescript
// ❌ 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 Checklist Before Commit

- [ ] `pnpm run typecheck` passes
- [ ] `pnpm run lint:fix` passes
- [ ] `pnpm run format:write` passes
- [ ] Relevant E2E tests pass
- [ ] All UI states handled (loading, error, empty)
- [ ] No secrets in code (use `.env.local`)
- [ ] Error messages sanitized (no stack traces to users)
- [ ] Server-side validation present (don't trust client)
- [ ] Accessibility: keyboard navigation, ARIA labels
- [ ] Mobile responsive (if applicable)

## Pre-Re-Review Checklist (Review Thread Hygiene)

Before requesting re-review from a reviewer:

- [ ] Every unresolved inline thread has been either fixed or explicitly answered with a rationale
- [ ] If you agreed with a comment: the change is committed and the commit hash is mentioned in the reply
- [ ] If you disagreed: the reply explains why with clear reasoning β€” do not leave threads silently open
- [ ] Re-request review only after all threads are in a clean state

## Resources

- **Documentation**: See [references/](references/) for links to local developer guide

Signals

Avg rating⭐ 0.0
Reviews0
Favorites0

Information

Repository
prowler-cloud/prowler
Author
prowler-cloud
Last Sync
9/5/2026
Repo Updated
9/4/2026
Created
1/12/2026

Reviews (0)

No reviews yet. Be the first to review this skill!