| name | cacomi-ui |
| description | Utilícese para diseñar componentes visuales e interfaces en el proyecto Cacomi. Trigger: When working inside ui/ on specific conventions (shadcn, folder placement, actions/adapters, shared types/hooks/lib).
|
| license | Apache-2.0 |
| metadata | {"author":"ant-gravity","version":"1.0","scope":["root","ui"],"auto_invoke":["Creating/modifying UI components","Working on UI structure (actions/adapters/types/hooks)"]} |
| allowed-tools | Read, Edit, Write, Glob, Grep, Bash, WebFetch, WebSearch, Task |
Related Generic Skills
typescript - Const types, flat interfaces
react-19 - No useMemo/useCallback, compiler
astro-6 - Astro Pages, API endpoints, Island Architecture
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)
Astro 6.0 | 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
HeroUI 2.8.4 (LEGACY - do not add new components)
CRITICAL: Component Library Rule
- ALWAYS: Use
shadcn/ui + Tailwind (components/shadcn/)
- NEVER: Add new HeroUI components (
components/ui/ is legacy only)
DECISION TREES
Component Placement
New feature UI? → shadcn/ui + Tailwind
Existing HeroUI feature? → Keep HeroUI (don't mix)
Used 1 feature? → features/{feature}/components/
Used 2+ features? → components/shared/
Needs state/hooks? → "use client"
Server component? → No directive needed
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 → components/ui/ (LEGACY)
Styling Decision
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
ui/
├── app/
│ ├── (auth)/ # Auth pages (login, signup)
│ └── (prowler)/ # Main app
│ ├── compliance/
│ ├── findings/
│ ├── providers/
│ ├── scans/
│ ├── services/
│ └── integrations/
├── components/
│ ├── shadcn/ # shadcn/ui (USE THIS)
│ ├── ui/ # HeroUI (LEGACY)
│ ├── {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:
const CHART_COLORS = {
primary: "var(--color-primary)",
secondary: "var(--color-secondary)",
text: "var(--color-text)",
gridLine: "var(--color-border)",
};
<XAxis tick={{ fill: CHART_COLORS.text }} />
<CartesianGrid stroke={CHART_COLORS.gridLine} />
Navigation UI Rules
CRITICAL RULE: Whenever you create or modify navigation components (like Navbars, Sidebar menus, or Tabs):
- Disable active links: If a link's
href matches the current active path, you MUST add pointer-events-none (coupled with an active state style like text color change) to prevent redundant navigation/page reloads.
- Scroll to top on current page: For the "Home" or "Logo" button, if the user is already on the homepage (
'/'), DO NOT reload the page. Instead, smoothly scroll to top: window.scrollTo({ top: 0, behavior: 'smooth' }).
CRITICAL: I18N Translation Rule
ABSOLUTELY NO HARDCODED STRINGS ARE ALLOWED IN UI COMPONENTS.
- Every new or existing visible text MUST be mapped to the
SettingsContext.jsx translation dictionary.
- If you are creating a new feature or button, you MUST add the translation keys to ALL SUPPORTED LANGUAGES (
es, en, fr) in SettingsContext.jsx. Do not just add Spanish.
- Access translations using
const { t } = useSettings() and utilize fallback values t?.section?.key || 'Fallback'. The fallback is ONLY for safety, you still MUST add the key to the context.
Form + Validation Pattern
"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(),
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>
);
}
Form Dirty Checking (Performance & UX)
CRITICAL RULE: Before making an API request to UPDATE/PATCH an existing resource, you MUST verify if the user actually made changes.
- Store original state: Keep a copy of the initial state (via
useRef or isDirty from React Hook Form).
- Compare on submit: If the current payload matches the initial state exactly, DO NOT send the request.
- Graceful exit: Simply show a success toast (e.g., "No se detectaron cambios") or close the modal immediately.
- Why?: Saves unnecessary backend load, prevents triggering "Updated At" timestamps when nothing changed, and is faster for the user.
Commands
cd ui && pnpm install
cd ui && pnpm run dev
cd ui && pnpm run typecheck
cd ui && pnpm run lint:fix
cd ui && pnpm run format:write
cd ui && pnpm run healthcheck
cd ui && pnpm run test:e2e
cd ui && pnpm run test:e2e:ui
cd ui && pnpm run test:e2e:debug
cd ui && pnpm run build
cd ui && pnpm start
QA Checklist Before Commit
Migrations Reference
| From | To | Key Changes |
|---|
| React 18 | 19.1 | Async components, React Compiler (no useMemo/useCallback) |
| Next.js 14 | 15.5 | Improved App Router, better streaming |
| NextUI | HeroUI 2.8.4 | Package rename only, same API |
| Zod 3 | 4 | z.email() not z.string().email(), error not message |
| AI SDK 4 | 5 | @ai-sdk/react, sendMessage not handleSubmit, parts not content |
Resources
- Documentation: See references/ for links to local developer guide
[!CAUTION]
AVOID accessing global context properties (like user or settings) directly without optional chaining ?. or explicit loading checks in components that hydrate on the client.
BECAUSE during initial hydration, context values might be null or in a loading state, leading to a TypeError and breaking the entire UI.
CORRECT APPROACH: Implement robust loading states and use isLoading flags or optional chaining for all external data access.
[!CAUTION]
AVOID naming files or components CookieConsent, Ads, or Tracking as they are frequently blocked by client-side browser extensions.
BECAUSE this results in ERR_BLOCKED_BY_CLIENT and the component failing to load/mount.
CORRECT APPROACH: Use naming like Legals, Compliance, or Privacy to ensure visibility.