Files
prowler/ui/AGENTS.md
Alan Buscaglia c8fab497fd feat(skills): sync AGENTS.md to AI-specific formats (#9751)
Co-authored-by: Alan-TheGentleman <alan@thegentleman.dev>
Co-authored-by: pedrooot <pedromarting3@gmail.com>
Co-authored-by: Andoni A. <14891798+andoniaf@users.noreply.github.com>
2026-01-13 11:44:44 +01:00

6.0 KiB

Prowler UI - AI Agent Ruleset

Skills Reference: For detailed patterns, use these skills:

Auto-invoke Skills

When performing these actions, ALWAYS invoke the corresponding skill FIRST:

Action Skill
App Router / Server Actions nextjs-15
Building AI chat features ai-sdk-5
Creating Zod schemas zod-4
Creating/modifying Prowler UI components prowler-ui
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/ or types/ or hooks/ (components go in components/{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 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