mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-01-25 02:08:11 +00:00
6.3 KiB
6.3 KiB
Prowler UI - AI Agent Ruleset
Skills Reference: For detailed patterns, use these skills:
prowler-ui- Prowler-specific UI patternsprowler-test-ui- Playwright E2E testing (comprehensive)typescript- Const types, flat interfacesreact-19- No useMemo/useCallback, compilernextjs-15- App Router, Server Actionstailwind-4- cn() utility, no var() in classNamezod-4- New API (z.email(), z.uuid())zustand-5- Selectors, persist middlewareai-sdk-5- UIMessage, sendMessageplaywright- Page Object Model, selectors
Auto-invoke Skills
When performing these actions, ALWAYS invoke the corresponding skill FIRST:
| Action | Skill |
|---|---|
| Add changelog entry for a PR or feature | prowler-changelog |
| App Router / Server Actions | nextjs-15 |
| Building AI chat features | ai-sdk-5 |
| Create PR that requires changelog entry | prowler-changelog |
| Creating Zod schemas | zod-4 |
| Creating/modifying Prowler UI components | prowler-ui |
| Review changelog format and conventions | prowler-changelog |
| Update CHANGELOG.md in any component | prowler-changelog |
| Using Zustand stores | zustand-5 |
| Working on Prowler UI structure (actions/adapters/types/hooks) | prowler-ui |
| Working with Prowler UI test helpers/pages | prowler-test-ui |
| Working with Tailwind classes | tailwind-4 |
| Writing Playwright E2E tests | playwright |
| Writing Prowler UI E2E tests | prowler-test-ui |
| Writing React components | react-19 |
| Writing TypeScript types/interfaces | typescript |
CRITICAL RULES - NON-NEGOTIABLE
React
- ALWAYS:
import { useState, useEffect } from "react" - NEVER:
import React,import * as React,import React as * - NEVER:
useMemo,useCallback(React Compiler handles optimization)
Types
- ALWAYS:
const X = { A: "a", B: "b" } as const; type T = typeof X[keyof typeof X] - NEVER:
type T = "a" | "b"
Interfaces
- ALWAYS: One level depth only; object property → dedicated interface (recursive)
- ALWAYS: Reuse via
extends - NEVER: Inline nested objects
Styling
- Single class:
className="bg-slate-800 text-white" - Merge multiple classes:
className={cn(BASE_STYLES, variant && "variant-class")} - Dynamic values:
style={{ width: "50%" }} - NEVER:
var()in className, hex colors
Scope Rule (ABSOLUTE)
- Used 2+ places →
lib/ortypes/orhooks/(components go incomponents/{domain}/) - Used 1 place → keep local in feature directory
- This determines ALL folder structure decisions
DECISION TREES
Component Placement
New/Existing UI? → shadcn/ui + Tailwind (NEVER HeroUI for new code)
Used 1 feature? → features/{feature}/components | Used 2+? → components/{domain}/
Needs state/hooks? → "use client" | Server component? → No directive
Code Location
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
shadcn components → components/shadcn/
PATTERNS
Server Component
export default async function Page() {
const data = await fetchData();
return <ClientComponent data={data} />;
}
Server Action
"use server";
export async function updateProvider(formData: FormData) {
const validated = schema.parse(Object.fromEntries(formData));
await updateDB(validated);
revalidatePath("/path");
}
Form + Validation (Zod 4)
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: z.email() not z.string().email()
id: z.uuid(), // Zod 4: z.uuid() not z.string().uuid()
});
const form = useForm({ resolver: zodResolver(schema) });
Zustand 5
const useStore = create(
persist(
(set) => ({
value: 0,
increment: () => set((s) => ({ value: s.value + 1 })),
}),
{ name: "key" },
),
);
Playwright Test
export class FeaturePage extends BasePage {
readonly submitBtn = this.page.getByRole("button", { name: "Submit" });
async goto() { await super.goto("/path"); }
async submit() { await this.submitBtn.click(); }
}
test("action works", { tag: ["@critical", "@feature"] }, async ({ page }) => {
const p = new FeaturePage(page);
await p.goto();
await p.submit();
await expect(page).toHaveURL("/expected");
});
TECH STACK
Next.js 15.5.9 | React 19.2.2 | Tailwind 4.1.13 | 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
Note
: HeroUI exists in
components/ui/as legacy code. Do NOT add new components there.
PROJECT STRUCTURE
ui/
├── app/(auth)/ # Auth pages
├── app/(prowler)/ # Main app: compliance, findings, providers, scans
├── components/shadcn/ # shadcn/ui components (USE THIS)
├── components/ui/ # HeroUI (LEGACY - do not add here)
├── actions/ # Server actions
├── types/ # Shared types
├── hooks/ # Shared hooks
├── lib/ # Utilities
├── store/ # Zustand state
├── tests/ # Playwright E2E
└── styles/ # Global CSS
COMMANDS
pnpm install && pnpm run dev
pnpm run typecheck
pnpm run lint:fix
pnpm run healthcheck
pnpm run test:e2e
pnpm run test:e2e:ui
QA CHECKLIST BEFORE COMMIT
npm run typecheckpassesnpm run lint:fixpassesnpm run format:writepasses- Relevant E2E tests pass
- All UI states handled (loading, error, empty)
- No secrets in code (use
.env.local) - Error messages sanitized
- Server-side validation present