mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-07-18 01:51:50 +00:00
d93c7dcc4d
Co-authored-by: Alan Buscaglia <gentlemanprogramming@gmail.com>
258 lines
6.5 KiB
Markdown
258 lines
6.5 KiB
Markdown
# Prowler UI - AI Agent Ruleset
|
|
|
|
## 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
|
|
|
|
```typescript
|
|
// ✅ CORRECT
|
|
interface UserAddress {
|
|
street: string;
|
|
city: string;
|
|
}
|
|
interface User {
|
|
id: string;
|
|
address: UserAddress;
|
|
}
|
|
interface Admin extends User {
|
|
permissions: string[];
|
|
}
|
|
|
|
// ❌ WRONG
|
|
interface User {
|
|
address: { street: string; city: string };
|
|
}
|
|
```
|
|
|
|
### Styling
|
|
|
|
- Single class: `className="bg-slate-800 text-white"`
|
|
- Merge multiple classes: `className={cn(BUTTON_STYLES.base, BUTTON_STYLES.active, isLoading && "opacity-50")}` (cn() handles Tailwind conflicts with twMerge)
|
|
- Conditional classes: `className={cn("base", condition && "variant")}`
|
|
- Recharts props: `fill={CHART_COLORS.text}` (use constants with var())
|
|
- Dynamic values: `style={{ width: "50%", opacity: 0.5 }}`
|
|
- CSS custom properties: `style={{ "--color": "var(--css-var)" }}` (for dynamic theming)
|
|
- NEVER: `var()` in className strings (use Tailwind semantic classes instead)
|
|
- NEVER: hex colors (use `text-white` not `text-[#fff]`)
|
|
|
|
### Scope Rule (ABSOLUTE)
|
|
|
|
- Used 2+ places → `components/shared/` or `lib/` or `types/` or `hooks/`
|
|
- Used 1 place → keep local in feature directory
|
|
- This determines ALL folder structure decisions
|
|
|
|
### Memoization
|
|
|
|
- NEVER: `useMemo`, `useCallback`
|
|
- React 19 Compiler handles automatic optimization
|
|
|
|
---
|
|
|
|
## DECISION TREES
|
|
|
|
### Component Placement
|
|
|
|
```
|
|
New feature UI? → shadcn/ui + Tailwind | Existing feature? → HeroUI
|
|
Used 1 feature? → features/{feature}/components | Used 2+? → components/shared
|
|
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/ | HeroUI → components/ui/
|
|
```
|
|
|
|
### Styling Decision
|
|
|
|
```
|
|
Tailwind class exists? → className | Dynamic value? → style prop
|
|
Conditional styles? → cn() | Static? → className only
|
|
Recharts? → CHART_COLORS constant + var() | Other? → Tailwind classes
|
|
```
|
|
|
|
---
|
|
|
|
## PATTERNS
|
|
|
|
### Server Component
|
|
|
|
```typescript
|
|
export default async function Page() {
|
|
const data = await fetchData();
|
|
return <ClientComponent data={data} />;
|
|
}
|
|
```
|
|
|
|
### Form + Validation
|
|
|
|
```typescript
|
|
import { useForm } from "react-hook-form";
|
|
import { zodResolver } from "@hookform/resolvers/zod";
|
|
const form = useForm({ resolver: zodResolver(schema) });
|
|
```
|
|
|
|
### Server Action
|
|
|
|
```typescript
|
|
"use server";
|
|
export async function updateProvider(formData: FormData) {
|
|
const validated = schema.parse(Object.fromEntries(formData));
|
|
await updateDB(validated);
|
|
revalidatePath("/path");
|
|
}
|
|
```
|
|
|
|
### Zod v4
|
|
|
|
- `z.email()` not `z.string().email()`
|
|
- `z.uuid()` not `z.string().uuid()`
|
|
- `z.url()` not `z.string().url()`
|
|
- `z.string().min(1)` not `z.string().nonempty()`
|
|
- `error` param not `message` param
|
|
|
|
### Zustand v5
|
|
|
|
```typescript
|
|
const useStore = create(
|
|
persist(
|
|
(set) => ({
|
|
value: 0,
|
|
increment: () => set((s) => ({ value: s.value + 1 })),
|
|
}),
|
|
{ name: "key" },
|
|
),
|
|
);
|
|
```
|
|
|
|
### AI SDK v5
|
|
|
|
```typescript
|
|
import { useChat } from "@ai-sdk/react";
|
|
const { messages, sendMessage } = useChat({
|
|
transport: new DefaultChatTransport({ api: "/api/chat" }),
|
|
});
|
|
const [input, setInput] = useState("");
|
|
const handleSubmit = (e) => {
|
|
e.preventDefault();
|
|
sendMessage({ text: input });
|
|
setInput("");
|
|
};
|
|
```
|
|
|
|
### Testing (Playwright)
|
|
|
|
```typescript
|
|
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", "@TEST-001"] },
|
|
async ({ page }) => {
|
|
const p = new FeaturePage(page);
|
|
await p.goto();
|
|
await p.submit();
|
|
await expect(page).toHaveURL("/expected");
|
|
},
|
|
);
|
|
```
|
|
|
|
Selector priority: `getByRole()` → `getByLabel()` → `getByText()` → other
|
|
|
|
---
|
|
|
|
## TECH STACK
|
|
|
|
Next.js 15.5.3 | React 19.1.1 | Tailwind 4.1.13 | shadcn/ui (new) | HeroUI 2.8.4 (legacy)
|
|
Zod 4.1.11 | React Hook Form 7.62.0 | Zustand 5.0.8 | NextAuth 5.0.0-beta.29 | Recharts 2.15.4
|
|
|
|
---
|
|
|
|
## PROJECT STRUCTURE
|
|
|
|
```
|
|
ui/
|
|
├── app/ (Next.js App Router)
|
|
│ ├── (auth)/ (Auth pages)
|
|
│ └── (prowler)/ (Main app: compliance, findings, providers, scans, services, integrations)
|
|
├── components/
|
|
│ ├── shadcn/ (New shadcn/ui components)
|
|
│ ├── ui/ (HeroUI base)
|
|
│ └── {domain}/ (Domain components)
|
|
├── 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 (Setup & start)
|
|
pnpm run typecheck (Type check)
|
|
pnpm run lint:fix (Fix linting)
|
|
pnpm run format:write (Format)
|
|
pnpm run healthcheck (typecheck + lint)
|
|
pnpm run test:e2e (E2E tests)
|
|
pnpm run test:e2e:ui (E2E with UI)
|
|
pnpm run test:e2e:debug (Debug E2E)
|
|
pnpm run build && pnpm start (Build & start)
|
|
```
|
|
|
|
---
|
|
|
|
## QA CHECKLIST BEFORE COMMIT
|
|
|
|
- [ ] `npm run typecheck` passes
|
|
- [ ] `npm run lint:fix` passes
|
|
- [ ] `npm 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
|
|
- [ ] Server-side validation present
|
|
|
|
---
|
|
|
|
## MIGRATIONS (As of Jan 2025)
|
|
|
|
React 18 → 19.1.1 (async components, compiler)
|
|
Next.js 14 → 15.5.3
|
|
NextUI → HeroUI 2.8.4
|
|
Zod 3 → 4 (see patterns section)
|
|
AI SDK 4 → 5 (see patterns section)
|