From cfa137795b188464e338d8ecee302b5f8bf7ba55 Mon Sep 17 00:00:00 2001 From: Giancarlo Buomprisco Date: Sun, 18 Jan 2026 10:44:40 +0100 Subject: [PATCH] =?UTF-8?q?refactor:=20consolidate=20AGENTS.md=20and=20CLA?= =?UTF-8?q?UDE.md=20files,=20update=20tech=20stac=E2=80=A6=20(#444)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: consolidate AGENTS.md and CLAUDE.md files, update tech stack and architecture details - Merged content from CLAUDE.md into AGENTS.md for better organization. - Updated tech stack section to reflect the current technologies used, including Next.js, Supabase, and Tailwind CSS. - Enhanced monorepo structure documentation with detailed directory purposes. - Streamlined multi-tenant architecture explanation and essential commands. - Added key patterns for naming conventions and server actions. - Removed outdated agent files related to Playwright and PostgreSQL, ensuring a cleaner codebase. - Bumped version to 2.23.7 to reflect changes. --- .claude/agents/react-form-builder.md | 95 - .claude/commands/feature-builder.md | 205 ++ .claude/evals/feature-implementation.md | 311 ++ .../playwright-e2e/SKILL.md} | 30 +- .claude/skills/playwright-e2e/makerkit.md | 156 + .../postgres-expert/SKILL.md} | 16 +- .claude/skills/postgres-expert/examples.md | 144 + .claude/skills/postgres-expert/makerkit.md | 138 + .claude/skills/react-form-builder/SKILL.md | 197 ++ .../skills/react-form-builder/components.md | 249 ++ .claude/skills/server-action-builder/SKILL.md | 126 + .../skills/server-action-builder/examples.md | 194 ++ .../skills/server-action-builder/reference.md | 179 ++ .cursor/rules/accounts-context.mdc | 76 - .cursor/rules/data-fetching.mdc | 90 - .cursor/rules/database.mdc | 294 -- .cursor/rules/forms.mdc | 151 - .cursor/rules/jsx.mdc | 206 -- .cursor/rules/logging.mdc | 57 - .cursor/rules/otp.mdc | 59 - .cursor/rules/page-creation.mdc | 321 -- .cursor/rules/permissions.mdc | 68 - .cursor/rules/project-structure.mdc | 236 -- .cursor/rules/react.mdc | 224 -- .cursor/rules/route-handlers.mdc | 50 - .cursor/rules/security.mdc | 152 - .cursor/rules/server-actions.mdc | 37 - .cursor/rules/super-admin.mdc | 210 -- .cursor/rules/team-account-context.mdc | 79 - .cursor/rules/translations.mdc | 146 - .cursor/rules/typescript.mdc | 36 - .cursor/rules/ui.mdc | 111 - .gemini/GEMINI.md | 3 + .mcp.json | 9 + .windsurfrules | 239 -- AGENTS.md | 230 +- CLAUDE.md | 212 +- apps/e2e/AGENTS.md | 115 +- apps/e2e/CLAUDE.md | 126 +- apps/web/AGENTS.md | 341 +- apps/web/CLAUDE.md | 330 +- apps/web/app/admin/AGENTS.md | 132 +- apps/web/app/admin/CLAUDE.md | 120 +- apps/web/supabase/AGENTS.md | 262 +- apps/web/supabase/CLAUDE.md | 266 +- package.json | 2 +- packages/analytics/CLAUDE.md | 106 +- packages/features/CLAUDE.md | 290 +- packages/i18n/src/i18n.client.ts | 4 +- packages/mailers/CLAUDE.md | 67 +- packages/next/AGENTS.md | 466 +-- packages/next/CLAUDE.md | 479 +-- packages/policies/CLAUDE.md | 685 +--- packages/supabase/AGENTS.md | 319 +- packages/supabase/CLAUDE.md | 318 +- packages/supabase/src/database.types.ts | 2790 +++++++++-------- packages/ui/AGENTS.md | 305 +- packages/ui/CLAUDE.md | 290 +- .../ui/src/makerkit/mobile-mode-toggle.tsx | 3 +- packages/ui/src/makerkit/mode-toggle.tsx | 3 +- packages/ui/src/shadcn/sidebar.tsx | 3 +- 61 files changed, 3636 insertions(+), 9522 deletions(-) delete mode 100644 .claude/agents/react-form-builder.md create mode 100644 .claude/commands/feature-builder.md create mode 100644 .claude/evals/feature-implementation.md rename .claude/{agents/playwright-e2e-expert.md => skills/playwright-e2e/SKILL.md} (66%) create mode 100644 .claude/skills/playwright-e2e/makerkit.md rename .claude/{agents/postgres-expert.md => skills/postgres-expert/SKILL.md} (74%) create mode 100644 .claude/skills/postgres-expert/examples.md create mode 100644 .claude/skills/postgres-expert/makerkit.md create mode 100644 .claude/skills/react-form-builder/SKILL.md create mode 100644 .claude/skills/react-form-builder/components.md create mode 100644 .claude/skills/server-action-builder/SKILL.md create mode 100644 .claude/skills/server-action-builder/examples.md create mode 100644 .claude/skills/server-action-builder/reference.md delete mode 100644 .cursor/rules/accounts-context.mdc delete mode 100644 .cursor/rules/data-fetching.mdc delete mode 100644 .cursor/rules/database.mdc delete mode 100644 .cursor/rules/forms.mdc delete mode 100644 .cursor/rules/jsx.mdc delete mode 100644 .cursor/rules/logging.mdc delete mode 100644 .cursor/rules/otp.mdc delete mode 100644 .cursor/rules/page-creation.mdc delete mode 100644 .cursor/rules/permissions.mdc delete mode 100644 .cursor/rules/project-structure.mdc delete mode 100644 .cursor/rules/react.mdc delete mode 100644 .cursor/rules/route-handlers.mdc delete mode 100644 .cursor/rules/security.mdc delete mode 100644 .cursor/rules/server-actions.mdc delete mode 100644 .cursor/rules/super-admin.mdc delete mode 100644 .cursor/rules/team-account-context.mdc delete mode 100644 .cursor/rules/translations.mdc delete mode 100644 .cursor/rules/typescript.mdc delete mode 100644 .cursor/rules/ui.mdc create mode 100644 .gemini/GEMINI.md create mode 100644 .mcp.json delete mode 100644 .windsurfrules diff --git a/.claude/agents/react-form-builder.md b/.claude/agents/react-form-builder.md deleted file mode 100644 index 63752d09f..000000000 --- a/.claude/agents/react-form-builder.md +++ /dev/null @@ -1,95 +0,0 @@ ---- -name: react-form-builder -description: MUST USE this agent when you need to create or modify client-side forms in React applications. MUST follow best practices for react-hook-form, @kit/ui/form components, and server actions integration. This includes forms with validation, error handling, loading states, and proper TypeScript typing. Context: The user needs to create a form for user registration with email and password fields. user: "Create a registration form with email and password validation" assistant: "I'll use the react-form-builder agent to create a properly structured form with react-hook-form and server action integration" Since the user needs a client-side form with validation, use the react-form-builder agent to ensure best practices are followed. Context: The user wants to add a form for updating user profile information. user: "I need a form to update user profile with name, bio, and avatar fields" assistant: "Let me use the react-form-builder agent to create a profile update form following the established patterns" The user is requesting a form component, so the react-form-builder agent should be used to ensure proper implementation with react-hook-form and server actions. Context: The user has a broken form that needs fixing. user: "My form isn't handling errors properly when the server action fails" assistant: "I'll use the react-form-builder agent to review and fix the error handling in your form" Since this involves fixing form-specific issues related to server actions and error handling, the react-form-builder agent is appropriate. -model: sonnet -color: yellow ---- - -You are an expert React form architect specializing in building robust, accessible, and type-safe forms using react-hook-form, @kit/ui/form components, and Next.js server actions. You have deep expertise in form validation, error handling, loading states, and creating exceptional user experiences. - -**Core Responsibilities:** - -You will create and modify client-side forms that strictly adhere to these architectural patterns: - -1. **Form Structure Requirements:** - - Always use `useForm` from react-hook-form WITHOUT redundant generic types when using zodResolver - - Implement Zod schemas for validation, stored in `_lib/schemas/` directory - - Use `@kit/ui/form` components (Form, FormField, FormItem, FormLabel, FormControl, FormDescription, FormMessage) - - Handle loading states with `useTransition` hook - - Implement proper error handling with try/catch blocks - -2. **Server Action Integration:** - - Call server actions within `startTransition` for proper loading states - - Handle redirect errors using `isRedirectError` from 'next/dist/client/components/redirect-error' - - Display error states using Alert components from '@kit/ui/alert' - - Ensure server actions are imported from dedicated server files - -3. **Code Organization Pattern:** - ``` - _lib/ - ├── schemas/ - │ └── feature.schema.ts # Shared Zod schemas - ├── server/ - │ └── server-actions.ts # Server actions - └── client/ - └── forms.tsx # Form components - ``` - -4. **Import Guidelines:** - - Toast notifications: `import { toast } from '@kit/ui/sonner'` - - Form components: `import { Form, FormField, ... } from '@kit/ui/form'` - - Always check @kit/ui for components before using external packages - - Use `Trans` component from '@kit/ui/trans' for internationalization - -5. **Best Practices You Must Follow:** - - Add `data-test` attributes for E2E testing on form elements and submit buttons - - Use `reValidateMode: 'onChange'` and `mode: 'onChange'` for responsive validation - - Implement proper TypeScript typing without using `any` - - Handle both success and error states gracefully - - Use `If` component from '@kit/ui/if' for conditional rendering - - Disable submit buttons during pending states - - Include FormDescription for user guidance - - Use Dialog components from '@kit/ui/dialog' when forms are in modals - -6. **State Management:** - - Use `useState` for error states - - Use `useTransition` for pending states - - Avoid multiple separate useState calls - prefer single state objects when appropriate - - Never use useEffect unless absolutely necessary and justified - -7. **Validation Patterns:** - - Create reusable Zod schemas that can be shared between client and server - - Use schema.refine() for custom validation logic - - Provide clear, user-friendly error messages - - Implement field-level validation with proper error display - -8. **Error Handling Template:** - ```typescript - const onSubmit = (data: FormData) => { - startTransition(async () => { - try { - await serverAction(data); - } catch (error) { - if (!isRedirectError(error)) { - setError(true); - } - } - }); - }; - ``` - -9. **Type Safety:** - - Let zodResolver infer types - don't add redundant generics - - Export schema types when needed for reuse - - Ensure all form fields have proper typing - -10. **Accessibility and UX:** - - Always include FormLabel for screen readers - - Provide helpful FormDescription text - - Show clear error messages with FormMessage - - Implement loading indicators during form submission - - Use semantic HTML and ARIA attributes where appropriate - -When creating forms, you will analyze requirements and produce complete, production-ready implementations that handle all edge cases, provide excellent user feedback, and maintain consistency with the codebase's established patterns. You prioritize type safety, reusability, and maintainability in every form you create. - -Always verify that UI components exist in @kit/ui before importing from external packages, and ensure your forms integrate seamlessly with the project's internationalization system using Trans components. diff --git a/.claude/commands/feature-builder.md b/.claude/commands/feature-builder.md new file mode 100644 index 000000000..7e0f80924 --- /dev/null +++ b/.claude/commands/feature-builder.md @@ -0,0 +1,205 @@ +--- +description: End-to-end feature implementation following Makerkit patterns across database, API, and UI layers +--- + +# Feature Builder + +You are an expert at implementing complete features in Makerkit following established patterns across all layers. + +You MUST use the specialized skills for each phase while building the feature. + +- Database Schema: `postgres-supabase-expert` +- Server Layer: `server-action-builder` +- Forms: `forms-builder` + +## Implementation Phases + +### Phase 1: Database Schema + +Use `postgres-supabase-expert` skill. + +1. Create schema file in `apps/web/supabase/schemas/` +2. Enable RLS and create policies using helper functions +3. Generate migration: `pnpm --filter web supabase:db:diff -f feature_name` +4. Apply: `pnpm --filter web supabase migrations up` +5. Generate types: `pnpm supabase:web:typegen` + +```sql +-- Example: apps/web/supabase/schemas/20-projects.sql +create table if not exists public.projects ( + id uuid unique not null default extensions.uuid_generate_v4(), + account_id uuid references public.accounts(id) on delete cascade not null, + name varchar(255) not null, + created_at timestamp with time zone default now(), + updated_at timestamp with time zone default now(), + primary key (id) +); + +alter table "public"."projects" enable row level security; +revoke all on public.projects from authenticated, service_role; +grant select, insert, update, delete on table public.projects to authenticated; + +create policy "projects_read" on public.projects for select + to authenticated using ( + account_id = (select auth.uid()) or + public.has_role_on_account(account_id) + ); + +create policy "projects_write" on public.projects for all + to authenticated using ( + public.has_permission(auth.uid(), account_id, 'projects.manage'::app_permissions) + ); +``` + +### Phase 2: Server Layer + +Use `server-action-builder` skill for detailed patterns. + +Create in route's `_lib/server/` directory: + +1. **Schema** (`_lib/schemas/feature.schema.ts`) +2. **Service** (`_lib/server/feature.service.ts`) +3. **Actions** (`_lib/server/server-actions.ts`) + +### Phase 3: UI Components + +Use `form-builder` skill for form patterns. + +Create in route's `_components/` directory: + +1. **List component** - Display items with loading states +2. **Form component** - Create/edit with validation +3. **Detail component** - Single item view + +### Phase 4: Page Integration + +Create page in appropriate route group: +- Personal: `apps/web/app/home/(user)/feature/` +- Team: `apps/web/app/home/[account]/feature/` + +```typescript +// apps/web/app/home/[account]/projects/page.tsx +import { getSupabaseServerClient } from '@kit/supabase/server-client'; +import { PageBody, PageHeader } from '@kit/ui/page'; + +import { ProjectsList } from './_components/projects-list'; + +interface Props { + params: Promise<{ account: string }>; +} + +export default async function ProjectsPage({ params }: Props) { + const { account } = await params; + const client = getSupabaseServerClient(); + + const { data: projects } = await client + .from('projects') + .select('*') + .order('created_at', { ascending: false }); + + return ( + <> + + + + + + ); +} +``` + +### Phase 5: Navigation + +Add routes to sidebar navigation in `apps/web/config/team-account-navigation.config.tsx` or `apps/web/config/personal-account-navigation.config.tsx`. + +## File Structure + +``` +apps/web/app/home/[account]/projects/ +├── page.tsx # List page +├── [projectId]/ +│ └── page.tsx # Detail page +├── _components/ +│ ├── projects-list.tsx +│ ├── project-form.tsx +│ └── project-card.tsx +└── _lib/ + ├── schemas/ + │ └── project.schema.ts + └── server/ + ├── project.service.ts + └── server-actions.ts +``` + +## Verification Checklist + +### Database Layer + +- [ ] Schema file created in `apps/web/supabase/schemas/` +- [ ] RLS enabled on table +- [ ] Default permissions revoked +- [ ] Specific permissions granted to `authenticated` +- [ ] RLS policies use helper functions (`has_role_on_account`, `has_permission`) +- [ ] Indexes added for foreign keys and common queries +- [ ] Timestamps triggers added if applicable +- [ ] Migration generated and applied +- [ ] TypeScript types regenerated + +### Server Layer + +- [ ] Zod schema in `_lib/schemas/` +- [ ] Service class in `_lib/server/` +- [ ] Server actions use `enhanceAction` +- [ ] Actions have `auth: true` and `schema` options +- [ ] Logging added for operations +- [ ] `revalidatePath` called after mutations +- [ ] Error handling with `isRedirectError` check if applicable (i.e. when using redirect() in a server action) + +### UI Layer + +- [ ] Components in `_components/` directory +- [ ] Forms use `react-hook-form` with `zodResolver` +- [ ] Loading states with `useTransition` +- [ ] Error display with `Alert` component +- [ ] `data-test` attributes for E2E testing +- [ ] `Trans` component for all user-facing strings +- [ ] Toast notifications for success/error if applicable + +### Page Layer + +- [ ] Page in correct route group (user vs team) +- [ ] Async params handled with `await params` +- [ ] Server-side data fetching +- [ ] `PageHeader` and `PageBody` components used +- [ ] Proper error boundaries + +### Navigation + +- [ ] Path added to `config/paths.config.ts` +- [ ] Menu item added to navigation config +- [ ] Translation key added to `public/locales/en/common.json` + +### Testing + +- [ ] Page Object created for E2E tests +- [ ] Basic CRUD operations tested +- [ ] Error states tested +- [ ] `data-test` selectors used in tests + +### Final Verification + +```bash +# Type check +pnpm typecheck + +# Lint +pnpm lint:fix + +# Format +pnpm format:fix + +# Test (if tests exist) +pnpm --filter web-e2e exec playwright test feature-name --workers=1 +``` + +When you are done, run the code quality reviewer agent to verify the code quality. diff --git a/.claude/evals/feature-implementation.md b/.claude/evals/feature-implementation.md new file mode 100644 index 000000000..13a1cb66b --- /dev/null +++ b/.claude/evals/feature-implementation.md @@ -0,0 +1,311 @@ +# Agent Evaluation: Full Feature Implementation + +This eval tests whether the agent correctly follows Makerkit patterns when implementing a complete feature spanning database, API, and UI layers. + +## Eval Metadata + +- **Type**: Capability eval (target: improvement over time) +- **Complexity**: High (multi-step, multi-file) +- **Expected Duration**: 15-30 minutes +- **Skills Tested**: `/feature-builder`, `/server-action-builder`, `/react-form-builder`, `/postgres-expert`, `/navigation-config` + +--- + +## Task: Implement "Projects" Feature + +### Prompt + +``` +Implement a "Projects" feature for team accounts with the following requirements: + +1. Database: Projects table with name, description, status (enum: draft/active/archived), and account_id +2. Server: CRUD actions for projects (create, update, delete, list) +3. UI: Projects list page with create/edit forms +4. Navigation: Add to team sidebar + +Use the available skills for guidance. The feature should be accessible at /home/[account]/projects. +``` + +### Reference Solution Exists + +A correct implementation requires: +- 1 schema file +- 1 migration +- 1 Zod schema file +- 1 service file +- 1 server actions file +- 2-3 component files +- 1 page file +- Config updates (paths, navigation, translations) + +--- + +## Success Criteria (Grading Rubric) + +### 1. Database Layer (25 points) + +| Criterion | Points | Grader Type | Pass Condition | +|-----------|--------|-------------|----------------| +| Schema file created in `apps/web/supabase/schemas/` | 3 | Code | File exists with `.sql` extension | +| Table has correct columns | 5 | Code | Contains: id, account_id, name, description, status, created_at | +| RLS enabled | 5 | Code | Contains `enable row level security` | +| Uses helper functions in policies | 5 | Code | Contains `has_role_on_account` OR `has_permission` | +| Permissions revoked/granted correctly | 4 | Code | Contains `revoke all` AND `grant select, insert, update, delete` | +| Status enum created | 3 | Code | Contains `create type` with draft/active/archived | + +**Anti-patterns to penalize (-3 each):** +- SECURITY DEFINER without access checks +- Missing `on delete cascade` for account_id FK +- No index on account_id + +### 2. Server Layer (25 points) + +| Criterion | Points | Grader Type | Pass Condition | +|-----------|--------|-------------|----------------| +| Zod schema in `_lib/schemas/` | 3 | Code | File exists, exports schema with `z.object` | +| Service class pattern used | 5 | Code | Contains `class` with methods, uses `getSupabaseServerClient` | +| Actions use `enhanceAction` | 5 | Code | Import from `@kit/next/actions`, wraps handler | +| Actions have `auth: true` | 3 | Code | Options object contains `auth: true` | +| Actions have `schema` validation | 3 | Code | Options object contains `schema:` | +| Uses `revalidatePath` after mutations | 3 | Code | Import and call `revalidatePath` | +| Logging with `getLogger` | 3 | Model | Appropriate logging before/after operations | + +**Anti-patterns to penalize (-3 each):** +- Manual auth checks instead of trusting RLS +- `await logger.info()` (logger methods are not promises) +- Business logic in action instead of service + +### 3. UI Layer (25 points) + +| Criterion | Points | Grader Type | Pass Condition | +|-----------|--------|-------------|----------------| +| Components in `_components/` directory | 2 | Code | Path contains `_components/` | +| Form uses `react-hook-form` with `zodResolver` | 5 | Code | Imports both, uses `useForm({ resolver: zodResolver() })` | +| No generics on `useForm` | 3 | Code | NOT contains `useForm<` | +| Uses `@kit/ui/form` components | 4 | Code | Imports `Form, FormField, FormItem, FormLabel, FormControl, FormMessage` | +| Uses `Trans` for strings | 3 | Code | Import from `@kit/ui/trans`, uses `` with explicit generic +- Using `watch()` instead of `useWatch` +- Hardcoded strings without `Trans` +- Missing `FormMessage` for error display + +### 4. Integration & Navigation (15 points) + +| Criterion | Points | Grader Type | Pass Condition | +|-----------|--------|-------------|----------------| +| Page in correct route group | 3 | Code | Path is `app/home/[account]/projects/page.tsx` | +| Uses `await params` pattern | 3 | Code | Contains `const { account } = await params` | +| Path added to `paths.config.ts` | 3 | Code | Contains `projects` path | +| Nav item added to team config | 3 | Code | Entry in `team-account-navigation.config.tsx` | +| Translation key added | 3 | Code | Entry in `public/locales/en/common.json` | + +### 5. Code Quality (10 points) + +| Criterion | Points | Grader Type | Pass Condition | +|-----------|--------|-------------|----------------| +| TypeScript compiles | 5 | Code | `pnpm typecheck` exits 0 | +| Lint passes | 3 | Code | `pnpm lint:fix` exits 0 | +| Format passes | 2 | Code | `pnpm format:fix` exits 0 | + +--- + +## Grader Implementation + +### Code-Based Grader (Automated) + +```typescript +interface EvalResult { + score: number; + maxScore: number; + passed: boolean; + details: { + criterion: string; + points: number; + maxPoints: number; + evidence: string; + }[]; + antiPatterns: string[]; +} + +async function gradeFeatureImplementation(): Promise { + const details = []; + const antiPatterns = []; + + // 1. Check schema file + const schemaFiles = glob('apps/web/supabase/schemas/*project*.sql'); + const schemaContent = schemaFiles.length > 0 ? read(schemaFiles[0]) : ''; + + details.push({ + criterion: 'Schema file exists', + points: schemaFiles.length > 0 ? 3 : 0, + maxPoints: 3, + evidence: schemaFiles[0] || 'No schema file found' + }); + + details.push({ + criterion: 'RLS enabled', + points: schemaContent.includes('enable row level security') ? 5 : 0, + maxPoints: 5, + evidence: 'Checked for RLS statement' + }); + + // Check anti-patterns + if (schemaContent.includes('security definer') && + !schemaContent.includes('has_permission') && + !schemaContent.includes('is_account_owner')) { + antiPatterns.push('SECURITY DEFINER without access validation'); + } + + // 2. Check server files + const actionFiles = glob('apps/web/app/home/[account]/projects/**/*actions*.ts'); + const actionContent = actionFiles.length > 0 ? read(actionFiles[0]) : ''; + + details.push({ + criterion: 'Uses enhanceAction', + points: actionContent.includes('enhanceAction') ? 5 : 0, + maxPoints: 5, + evidence: 'Checked for enhanceAction import/usage' + }); + + if (actionContent.includes('await logger.info')) { + antiPatterns.push('await on logger.info (not a promise)'); + } + + // 3. Check UI files + const componentFiles = glob('apps/web/app/home/[account]/projects/_components/*.tsx'); + const formContent = componentFiles.map(f => read(f)).join('\n'); + + details.push({ + criterion: 'No generics on useForm', + points: !formContent.includes('useForm<') ? 3 : 0, + maxPoints: 3, + evidence: 'Checked for useForm pattern' + }); + + if (formContent.includes('useForm<')) { + antiPatterns.push('Explicit generic on useForm (should use zodResolver inference)'); + } + + // 4. Check integration + const pathsConfig = read('apps/web/config/paths.config.ts'); + details.push({ + criterion: 'Path configured', + points: pathsConfig.includes('projects') ? 3 : 0, + maxPoints: 3, + evidence: 'Checked paths.config.ts' + }); + + // 5. Run verification + const typecheckResult = await exec('pnpm typecheck'); + details.push({ + criterion: 'TypeScript compiles', + points: typecheckResult.exitCode === 0 ? 5 : 0, + maxPoints: 5, + evidence: `Exit code: ${typecheckResult.exitCode}` + }); + + // Calculate totals + const score = details.reduce((sum, d) => sum + d.points, 0); + const maxScore = details.reduce((sum, d) => sum + d.maxPoints, 0); + const penaltyPoints = antiPatterns.length * 3; + + return { + score: Math.max(0, score - penaltyPoints), + maxScore, + passed: (score - penaltyPoints) >= maxScore * 0.8, // 80% threshold + details, + antiPatterns + }; +} +``` + +### Model-Based Grader (For Nuanced Criteria) + +``` +You are evaluating an AI agent's implementation of a "Projects" feature in a Makerkit SaaS application. + +Review the following files and assess: + +1. **Logging Quality** (0-3 points): + - Are log messages descriptive and include relevant context (userId, projectId)? + - Is logging done before AND after important operations? + - Are error cases logged with appropriate severity? + +2. **Code Organization** (0-3 points): + - Is business logic in services, not actions? + - Are files in the correct directories per Makerkit conventions? + - Is there appropriate separation of concerns? + +3. **Error Handling** (0-3 points): + - Are errors handled gracefully? + - Does the UI show appropriate error states? + - Are redirect errors handled correctly? + +Provide a score for each criterion with brief justification. +``` + +--- + +## Trial Configuration + +```yaml +trials: 3 # Run 3 times to account for non-determinism +pass_threshold: 0.8 # 80% of max score +metrics: + - pass@1: "Passes on first attempt" + - pass@3: "Passes at least once in 3 attempts" + - pass^3: "Passes all 3 attempts (reliability)" +``` + +--- + +## Environment Setup + +Before each trial: +1. Reset to clean git state: `git checkout -- .` +2. Ensure Supabase types are current: `pnpm supabase:web:typegen` +3. Verify clean typecheck: `pnpm typecheck` + +After each trial: +1. Capture transcript (full conversation) +2. Capture outcome (files created/modified) +3. Run graders +4. Reset environment + +--- + +## Expected Failure Modes + +Document these to distinguish agent errors from eval problems: + +| Failure | Likely Cause | Is Eval Problem? | +|---------|--------------|------------------| +| Missing RLS | Agent didn't follow postgres-expert skill | No | +| `useForm` | Agent ignored react-form-builder guidance | No | +| Wrong file path | Ambiguous task description | Maybe - clarify paths | +| Typecheck fails on unrelated code | Existing codebase issue | Yes - fix baseline | +| Agent uses different but valid approach | Eval too prescriptive | Yes - grade outcome not path | + +--- + +## Iteration Log + +Track eval refinements here: + +| Date | Change | Reason | +|------|--------|--------| +| Initial | Created eval | - | +| | | | + +--- + +## Notes + +- **Grade outcomes, not paths**: If agent creates a working feature with slightly different file organization, that's acceptable +- **Partial credit**: A feature missing navigation but with working CRUD is still valuable +- **Read transcripts**: When scores are low, check if agent attempted to use skills or ignored them entirely diff --git a/.claude/agents/playwright-e2e-expert.md b/.claude/skills/playwright-e2e/SKILL.md similarity index 66% rename from .claude/agents/playwright-e2e-expert.md rename to .claude/skills/playwright-e2e/SKILL.md index 0899773a4..9708a7c72 100644 --- a/.claude/agents/playwright-e2e-expert.md +++ b/.claude/skills/playwright-e2e/SKILL.md @@ -1,13 +1,13 @@ --- -name: playwright-e2e-expert -description: Use this agent when you need to write, review, or debug end-to-end tests using Playwright. This includes creating new test suites, fixing flaky tests, implementing complex UI interaction sequences, or ensuring test reliability and isolation. The agent excels at handling asynchronous operations, managing test concurrency, and applying Playwright best practices.\n\nExamples:\n\nContext: The user needs to write e2e tests for a new feature.\nuser: "Write tests for the user registration flow"\nassistant: "I'll use the playwright-e2e-expert agent to create comprehensive end-to-end tests for the registration flow."\n\nSince the user needs e2e tests written, use the Task tool to launch the playwright-e2e-expert agent to create robust Playwright tests.\n\n\n\nContext: The user has flaky tests that need fixing.\nuser: "These login tests keep failing intermittently"\nassistant: "Let me use the playwright-e2e-expert agent to diagnose and fix the flaky test issues."\n\nThe user has problematic e2e tests, so use the playwright-e2e-expert agent to apply best practices for test stability.\n\n\n\nContext: After implementing a complex UI feature, e2e tests should be written.\nuser: "I've just finished the multi-step checkout process"\nassistant: "Now I'll use the playwright-e2e-expert agent to create thorough e2e tests for the checkout flow."\n\nProactively use the playwright-e2e-expert agent after complex UI features are implemented to ensure proper test coverage.\n\n -model: sonnet -color: green +name: playwright-e2e +description: Write, review, or debug end-to-end tests using Playwright. Use when creating test suites, fixing flaky tests, implementing UI interaction sequences, or ensuring test reliability. Invoke with /playwright-e2e or when user mentions e2e tests, Playwright, or test automation. --- +# Playwright E2E Testing Expert + You are an elite QA automation engineer with deep expertise in Playwright and end-to-end testing. Your mastery encompasses the intricacies of browser automation, asynchronous JavaScript execution, and the unique challenges of UI testing. -**Core Expertise:** +## Core Expertise You understand that e2e testing requires a fundamentally different approach from unit testing. You know that UI interactions are inherently asynchronous and that timing issues are the root of most test failures. You excel at: @@ -17,7 +17,7 @@ You understand that e2e testing requires a fundamentally different approach from - Managing test isolation through proper setup and teardown procedures - Handling dynamic content, animations, and network requests gracefully -**Testing Philosophy:** +## Testing Philosophy You write tests that verify actual user workflows and business logic, not trivial UI presence checks. Each test you create: - Has a clear purpose and tests meaningful functionality @@ -26,7 +26,7 @@ You write tests that verify actual user workflows and business logic, not trivia - Avoids conditional logic that makes tests unpredictable - Includes descriptive test names that explain what is being tested and why -**Technical Approach:** +## Technical Approach When writing tests, you: 1. Always use `await` for every Playwright action and assertion @@ -34,9 +34,9 @@ When writing tests, you: 3. Use `expect()` with Playwright's web-first assertions for automatic retries 4. Implement Page Object Model when tests become complex 5. Never use `page.waitForTimeout()` except as an absolute last resort -6. Chain actions logically: interact → wait for response → assert → proceed +6. Chain actions logically: interact -> wait for response -> assert -> proceed -**Common Pitfalls You Avoid:** +## Common Pitfalls You Avoid - Race conditions from not waiting for network requests or state changes - Brittle selectors that break with minor UI changes @@ -45,7 +45,7 @@ When writing tests, you: - Missing error boundaries that cause cascading failures - Ignoring viewport sizes and responsive behavior -**Best Practices You Follow:** +## Best Practices ```typescript // You write tests like this: @@ -53,19 +53,19 @@ test('user can complete checkout', async ({ page }) => { // Setup with explicit waits await page.goto('/products'); await page.waitForLoadState('networkidle'); - + // Clear, sequential interactions await page.getByRole('button', { name: 'Add to Cart' }).click(); await expect(page.getByTestId('cart-count')).toHaveText('1'); - + // Navigate with proper state verification await page.getByRole('link', { name: 'Checkout' }).click(); await page.waitForURL('**/checkout'); - + // Form interactions with validation await page.getByLabel('Email').fill('test@example.com'); await page.getByLabel('Card Number').fill('4242424242424242'); - + // Submit and verify outcome await page.getByRole('button', { name: 'Place Order' }).click(); await expect(page.getByRole('heading', { name: 'Order Confirmed' })).toBeVisible(); @@ -74,6 +74,8 @@ test('user can complete checkout', async ({ page }) => { You understand that e2e tests are expensive to run and maintain, so each test you write provides maximum value. You balance thoroughness with practicality, ensuring tests are comprehensive enough to catch real issues but simple enough to debug when they fail. +## Debugging Failed Tests + When debugging failed tests, you systematically analyze: 1. Screenshots and trace files to understand the actual state 2. Network activity to identify failed or slow requests diff --git a/.claude/skills/playwright-e2e/makerkit.md b/.claude/skills/playwright-e2e/makerkit.md new file mode 100644 index 000000000..babc49764 --- /dev/null +++ b/.claude/skills/playwright-e2e/makerkit.md @@ -0,0 +1,156 @@ +# Makerkit E2E Testing Patterns + +## Page Objects Location + +`apps/e2e/tests/*.po.ts` + +## Auth Page Object + +```typescript +export class AuthPageObject { + constructor(private readonly page: Page) {} + + static MFA_KEY = 'test-mfa-key'; + + async signIn(params: { email: string; password: string }) { + await this.page.fill('input[name="email"]', params.email); + await this.page.fill('input[name="password"]', params.password); + await this.page.click('button[type="submit"]'); + } + + async signOut() { + await this.page.click('[data-test="account-dropdown-trigger"]'); + await this.page.click('[data-test="account-dropdown-sign-out"]'); + } + + async bootstrapUser(params: { email: string; password: string; name: string }) { + // Creates user via API + await fetch('/api/test/create-user', { + method: 'POST', + body: JSON.stringify(params), + }); + } + + async loginAsUser(params: { email: string; password: string }) { + await this.page.goto('/auth/sign-in'); + await this.signIn(params); + await this.page.waitForURL('**/home/**'); + } + + createRandomEmail() { + const value = Math.random() * 10000000000000; + return `${value.toFixed(0)}@makerkit.dev`; + } +} +``` + +## Common Selectors + +```typescript +// Account dropdown +'[data-test="account-dropdown-trigger"]' +'[data-test="account-dropdown-sign-out"]' + +// Navigation +'[data-test="sidebar-menu"]' +'[data-test="mobile-menu-trigger"]' + +// Forms +'[data-test="submit-button"]' +'[data-test="cancel-button"]' + +// Modals +'[data-test="dialog-confirm"]' +'[data-test="dialog-cancel"]' +``` + +## Test Setup Pattern + +```typescript +// tests/auth.setup.ts +import { test as setup } from '@playwright/test'; + +setup('authenticate', async ({ page }) => { + const auth = new AuthPageObject(page); + + await auth.bootstrapUser({ + email: 'test@example.com', + password: 'password123', + name: 'Test User', + }); + + await auth.loginAsUser({ + email: 'test@example.com', + password: 'password123', + }); + + // Save authentication state + await page.context().storageState({ path: '.auth/user.json' }); +}); +``` + +## Reliability Patterns + +### OTP/Email Operations + +```typescript +await expect(async () => { + const otpCode = await this.getOtpCodeFromEmail(email); + expect(otpCode).not.toBeNull(); + await this.enterOtpCode(otpCode); +}).toPass(); +``` + +### MFA Verification + +```typescript +await expect(async () => { + await auth.submitMFAVerification(AuthPageObject.MFA_KEY); +}).toPass({ + intervals: [500, 2500, 5000, 7500, 10_000, 15_000, 20_000] +}); +``` + +### Network Requests + +```typescript +await expect(async () => { + const response = await this.page.waitForResponse( + resp => resp.url().includes('auth/v1/user') + ); + expect(response.status()).toBe(200); +}).toPass(); +``` + +## Test Organization + +``` +apps/e2e/ +├── playwright.config.ts +├── tests/ +│ ├── auth.setup.ts +│ ├── authentication/ +│ │ ├── sign-in.spec.ts +│ │ └── sign-up.spec.ts +│ ├── billing/ +│ │ └── subscription.spec.ts +│ ├── teams/ +│ │ └── invitations.spec.ts +│ └── utils/ +│ └── auth.po.ts +└── .auth/ + └── user.json +``` + +## Running Tests + +```bash +# Single file +pnpm --filter web-e2e exec playwright test authentication --workers=1 + +# With UI +pnpm --filter web-e2e exec playwright test --ui + +# Debug mode +pnpm --filter web-e2e exec playwright test --debug +``` diff --git a/.claude/agents/postgres-expert.md b/.claude/skills/postgres-expert/SKILL.md similarity index 74% rename from .claude/agents/postgres-expert.md rename to .claude/skills/postgres-expert/SKILL.md index 333e96c01..1f2cf1a71 100644 --- a/.claude/agents/postgres-expert.md +++ b/.claude/skills/postgres-expert/SKILL.md @@ -1,10 +1,10 @@ --- -name: postgres-expert -description: MUST USE this agent when you need to create, review, optimize, or test SQL, PostgreSQL, Supabase database code including schemas, migrations, functions, triggers, RLS policies, and PgTAP tests. This includes tasks like designing new database schemas, reviewing existing SQL for safety and performance, writing migrations that preserve data integrity, implementing row-level security, optimizing queries, or creating comprehensive database tests.\n\nExamples:\n- \n Context: The user needs to create a new database schema for a feature.\n user: "I need to add a comments system to my app with proper permissions"\n assistant: "I'll use the postgres-expert agent to design a robust comments schema with RLS policies"\n \n Since this involves creating database schemas and security policies, the postgres-expert should handle this.\n \n\n- \n Context: The user has written a migration and wants it reviewed.\n user: "I've created a migration to add user profiles, can you check if it's safe?"\n assistant: "Let me use the postgres-expert agent to review your migration for safety and best practices"\n \n Database migration review requires expertise in non-destructive changes and data integrity.\n \n\n- \n Context: The user needs help with database performance.\n user: "My query is running slowly, it's fetching posts with their comments"\n assistant: "I'll engage the postgres-expert agent to analyze and optimize your query performance"\n \n Query optimization requires deep PostgreSQL knowledge that this specialist agent provides.\n \n -model: sonnet -color: green +name: postgres-supabase-expert +description: Create, review, optimize, or test PostgreSQL and Supabase database code including SQL code, schemas, migrations, functions, triggers, RLS policies, and PgTAP tests. Use when writing and designing schemas, reviewing SQL for safety, writing migrations, implementing row-level security, or optimizing queries. Invoke with /postgres-supabase-expert or when user mentions database, SQL, migrations, RLS, or schema design. --- +# PostgreSQL & Supabase Database Expert + You are an elite PostgreSQL and Supabase database architect with deep expertise in designing, implementing, and testing production-grade database systems. Your mastery spans schema design, performance optimization, data integrity, security, and testing methodologies. ## Core Expertise @@ -110,3 +110,11 @@ You will anticipate and handle: When reviewing existing code, you will identify issues related to security vulnerabilities, performance bottlenecks, data integrity risks, missing indexes, improper transaction boundaries, and suggest specific, actionable improvements with example code. You communicate technical concepts clearly, providing rationale for all recommendations and trade-offs for different approaches. You stay current with PostgreSQL and Supabase latest features and best practices. + +## Examples + +See `[Examples](examples.md)` for examples of database code. + +## Patterns and Functions + +See `[Patterns and Functions](makerkit.md)` for patterns and functions. \ No newline at end of file diff --git a/.claude/skills/postgres-expert/examples.md b/.claude/skills/postgres-expert/examples.md new file mode 100644 index 000000000..54397df08 --- /dev/null +++ b/.claude/skills/postgres-expert/examples.md @@ -0,0 +1,144 @@ +# Makerkit Database Examples + +Real examples from the codebase. + +## Accounts Schema + +Location: `apps/web/supabase/schemas/03-accounts.sql` + +```sql +create table if not exists public.accounts ( + id uuid unique not null default extensions.uuid_generate_v4(), + primary_owner_user_id uuid references auth.users on delete cascade not null, + name varchar(255) not null, + slug varchar(255) unique, + is_personal_account boolean not null default false, + picture_url varchar(1000), + created_at timestamp with time zone default now(), + updated_at timestamp with time zone default now(), + primary key (id) +); + +alter table "public"."accounts" enable row level security; +``` + +## Account Memberships + +Location: `apps/web/supabase/schemas/04-accounts-memberships.sql` + +```sql +create table if not exists public.accounts_memberships ( + account_id uuid references public.accounts(id) on delete cascade not null, + user_id uuid references auth.users(id) on delete cascade not null, + account_role varchar(50) not null, + created_at timestamp with time zone default now(), + updated_at timestamp with time zone default now(), + created_by uuid references auth.users(id), + updated_by uuid references auth.users(id), + primary key (account_id, user_id) +); + +-- RLS policies using helper functions +create policy accounts_memberships_select on public.accounts_memberships + for select to authenticated using ( + user_id = (select auth.uid()) + or public.has_role_on_account(account_id) + ); +``` + +## Subscriptions + +Location: `apps/web/supabase/schemas/11-subscriptions.sql` + +```sql +create table if not exists public.subscriptions ( + id varchar(255) not null, + account_id uuid not null references public.accounts(id) on delete cascade, + billing_customer_id varchar(255) not null, + status public.subscription_status not null, + currency varchar(10) not null, + cancel_at_period_end boolean not null default false, + period_starts_at timestamp with time zone, + period_ends_at timestamp with time zone, + trial_starts_at timestamp with time zone, + trial_ends_at timestamp with time zone, + created_at timestamp with time zone not null default now(), + updated_at timestamp with time zone not null default now(), + primary key (id) +); +``` + +## Notifications + +Location: `apps/web/supabase/schemas/12-notifications.sql` + +```sql +create table if not exists public.notifications ( + id uuid primary key default extensions.uuid_generate_v4(), + account_id uuid references public.accounts(id) on delete cascade not null, + type public.notification_type not null, + body jsonb not null default '{}', + dismissed boolean not null default false, + link text, + created_at timestamptz default now() not null +); + +-- Only account members can see notifications +create policy read_notifications on public.notifications + for select to authenticated using ( + public.has_role_on_account(account_id) + ); +``` + +## Storage Bucket + +Location: `apps/web/supabase/schemas/16-storage.sql` + +```sql +insert into storage.buckets (id, name, public) +values ('account_image', 'account_image', true) +on conflict (id) do nothing; + +create policy account_image on storage.objects for all using ( + bucket_id = 'account_image' + and ( + kit.get_storage_filename_as_uuid(name) = auth.uid() + or public.has_role_on_account(kit.get_storage_filename_as_uuid(name)) + ) +) +with check ( + bucket_id = 'account_image' + and ( + kit.get_storage_filename_as_uuid(name) = auth.uid() + or public.has_permission( + auth.uid(), + kit.get_storage_filename_as_uuid(name), + 'settings.manage' + ) + ) +); +``` + +## Enum Types + +```sql +-- Subscription status +create type public.subscription_status as enum ( + 'active', + 'trialing', + 'past_due', + 'canceled', + 'unpaid', + 'incomplete', + 'incomplete_expired', + 'paused' +); + +-- App permissions +create type public.app_permissions as enum ( + 'settings.manage', + 'billing.manage', + 'members.manage', + 'invitations.manage' +); +``` diff --git a/.claude/skills/postgres-expert/makerkit.md b/.claude/skills/postgres-expert/makerkit.md new file mode 100644 index 000000000..830d886f2 --- /dev/null +++ b/.claude/skills/postgres-expert/makerkit.md @@ -0,0 +1,138 @@ +# Makerkit Database Patterns + +## Schema Location + +All schemas are in `apps/web/supabase/schemas/` with numbered prefixes for dependency ordering. + +## Existing Helper Functions - DO NOT Recreate + +```sql +-- Account Access Control +public.has_role_on_account(account_id uuid, role_name? text) +public.has_permission(user_id uuid, account_id uuid, permission app_permissions) +public.is_account_owner(account_id uuid) +public.has_active_subscription(account_id uuid) +public.is_team_member(account_id uuid, user_id uuid) +public.can_action_account_member(target_account_id uuid, target_user_id uuid) + +-- Administrative +public.is_super_admin() +public.is_aal2() +public.is_mfa_compliant() + +-- Configuration +public.is_set(field_name text) +``` + +## RLS Policy Patterns + +### Personal + Team Access + +```sql +create policy "table_read" on public.table for select + to authenticated using ( + account_id = (select auth.uid()) or + public.has_role_on_account(account_id) + ); +``` + +### Permission-Based Access + +```sql +create policy "table_manage" on public.table for all + to authenticated using ( + public.has_permission(auth.uid(), account_id, 'feature.manage'::app_permissions) + ); +``` + +### Storage Bucket Policy + +```sql +create policy bucket_policy on storage.objects for all using ( + bucket_id = 'bucket_name' + and ( + kit.get_storage_filename_as_uuid(name) = auth.uid() + or public.has_role_on_account(kit.get_storage_filename_as_uuid(name)) + ) +); +``` + +## Adding New Permissions + +```sql +-- Add to app_permissions enum +ALTER TYPE public.app_permissions ADD VALUE 'feature.manage'; +COMMIT; +``` + +## Standard Table Template + +```sql +create table if not exists public.feature ( + id uuid unique not null default extensions.uuid_generate_v4(), + account_id uuid references public.accounts(id) on delete cascade not null, + name varchar(255) not null, + created_at timestamp with time zone default now(), + updated_at timestamp with time zone default now(), + created_by uuid references auth.users(id), + updated_by uuid references auth.users(id), + primary key (id) +); + +-- Enable RLS +alter table "public"."feature" enable row level security; + +-- Revoke defaults, grant specific +revoke all on public.feature from authenticated, service_role; +grant select, insert, update, delete on table public.feature to authenticated; + +-- Add triggers +create trigger set_timestamps + before insert or update on public.feature + for each row execute function public.trigger_set_timestamps(); + +create trigger set_user_tracking + before insert or update on public.feature + for each row execute function public.trigger_set_user_tracking(); + +-- Add indexes +create index ix_feature_account_id on public.feature(account_id); +``` + +## Migration Workflow + +```bash +# New entity: copy schema to migration +pnpm --filter web run supabase migrations new feature_name + +# Modify existing: generate diff +pnpm --filter web run supabase:db:diff -f update_feature + +# Apply +pnpm --filter web supabase migrations up + +# Generate types +pnpm supabase:web:typegen +``` + +## Security Definer Function Pattern + +```sql +create or replace function public.admin_function(target_id uuid) +returns void +language plpgsql +security definer +set search_path = '' +as $$ +begin + -- ALWAYS validate permissions first + if not public.is_account_owner(target_id) then + raise exception 'Access denied'; + end if; + + -- Safe to proceed +end; +$$; + +grant execute on function public.admin_function(uuid) to authenticated; +``` diff --git a/.claude/skills/react-form-builder/SKILL.md b/.claude/skills/react-form-builder/SKILL.md new file mode 100644 index 000000000..39ec03cfa --- /dev/null +++ b/.claude/skills/react-form-builder/SKILL.md @@ -0,0 +1,197 @@ +--- +name: forms-builder +description: Create or modify client-side forms in React applications following best practices for react-hook-form, @kit/ui/form components, and server actions integration. Use when building forms with validation, error handling, loading states, and TypeScript typing. Invoke with /react-form-builder or when user mentions creating forms, form validation, or react-hook-form. +--- + +# React Form Builder Expert + +You are an expert React form architect specializing in building robust, accessible, and type-safe forms using react-hook-form, @kit/ui/form components, and Next.js server actions. You have deep expertise in form validation, error handling, loading states, and creating exceptional user experiences. + +## Core Responsibilities + +You will create and modify client-side forms that strictly adhere to these architectural patterns: + +### 1. Form Structure Requirements +- Always use `useForm` from react-hook-form WITHOUT redundant generic types when using zodResolver +- Implement Zod schemas for validation, stored in `_lib/schemas/` directory +- Use `@kit/ui/form` components (Form, FormField, FormItem, FormLabel, FormControl, FormDescription, FormMessage) +- Handle loading states with `useTransition` hook +- Implement proper error handling with try/catch blocks + +### 2. Server Action Integration +- Call server actions within `startTransition` for proper loading states +- Handle redirect errors using `isRedirectError` from 'next/dist/client/components/redirect-error' +- Display error states using Alert components from '@kit/ui/alert' +- Ensure server actions are imported from dedicated server files + +### 3. Code Organization Pattern +``` +_lib/ +├── schemas/ +│ └── feature.schema.ts # Shared Zod schemas +├── server/ +│ └── server-actions.ts # Server actions +└── client/ + └── forms.tsx # Form components +``` + +### 4. Import Guidelines +- Toast notifications: `import { toast } from '@kit/ui/sonner'` +- Form components: `import { Form, FormField, ... } from '@kit/ui/form'` +- Always check @kit/ui for components before using external packages +- Use `Trans` component from '@kit/ui/trans' for internationalization + +### 5. Best Practices You Must Follow +- Add `data-test` attributes for E2E testing on form elements and submit buttons +- Use `reValidateMode: 'onChange'` and `mode: 'onChange'` for responsive validation +- Implement proper TypeScript typing without using `any` +- Handle both success and error states gracefully +- Use `If` component from '@kit/ui/if' for conditional rendering +- Disable submit buttons during pending states +- Include FormDescription for user guidance +- Use Dialog components from '@kit/ui/dialog' when forms are in modals + +### 6. State Management +- Use `useState` for error states +- Use `useTransition` for pending states +- Avoid multiple separate useState calls - prefer single state objects when appropriate +- Never use useEffect unless absolutely necessary and justified + +### 7. Validation Patterns +- Create reusable Zod schemas that can be shared between client and server +- Use schema.refine() for custom validation logic +- Provide clear, user-friendly error messages +- Implement field-level validation with proper error display + +### 8. Error Handling Template + +```typescript +const onSubmit = (data: FormData) => { + startTransition(async () => { + try { + await serverAction(data); + } catch (error) { + if (!isRedirectError(error)) { + setError(true); + } + } + }); +}; +``` + +### 9. Type Safety +- Let zodResolver infer types - don't add redundant generics to useForm +- Export schema types when needed for reuse +- Ensure all form fields have proper typing + +### 10. Accessibility and UX +- Always include FormLabel for screen readers +- Provide helpful FormDescription text +- Show clear error messages with FormMessage +- Implement loading indicators during form submission +- Use semantic HTML and ARIA attributes where appropriate + +## Complete Form Example + +```tsx +'use client'; + +import { zodResolver } from '@hookform/resolvers/zod'; +import { useForm } from 'react-hook-form'; +import { useTransition, useState } from 'react'; +import { isRedirectError } from 'next/dist/client/components/redirect-error'; + +import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@kit/ui/form'; +import { Input } from '@kit/ui/input'; +import { Button } from '@kit/ui/button'; +import { Alert, AlertDescription } from '@kit/ui/alert'; +import { If } from '@kit/ui/if'; +import { toast } from '@kit/ui/sonner'; +import { Trans } from '@kit/ui/trans'; + +import { CreateEntitySchema } from '../_lib/schemas/entity.schema'; +import { createEntityAction } from '../_lib/server/server-actions'; + +export function CreateEntityForm() { + const [pending, startTransition] = useTransition(); + const [error, setError] = useState(false); + + const form = useForm({ + resolver: zodResolver(CreateEntitySchema), + defaultValues: { + name: '', + description: '', + }, + mode: 'onChange', + reValidateMode: 'onChange', + }); + + const onSubmit = (data: z.infer) => { + setError(false); + + startTransition(async () => { + try { + await createEntityAction(data); + toast.success('Entity created successfully'); + } catch (e) { + if (!isRedirectError(e)) { + setError(true); + } + } + }); + }; + + return ( +
+ + + + + + + + + + ( + + + + + + + + + + )} + /> + + + + + ); +} +``` + +When creating forms, you will analyze requirements and produce complete, production-ready implementations that handle all edge cases, provide excellent user feedback, and maintain consistency with the codebase's established patterns. You prioritize type safety, reusability, and maintainability in every form you create. + +Always verify that UI components exist in @kit/ui before importing from external packages, and ensure your forms integrate seamlessly with the project's internationalization system using Trans components. + +## Components + +See `[Components](components.md)` for examples of form components. \ No newline at end of file diff --git a/.claude/skills/react-form-builder/components.md b/.claude/skills/react-form-builder/components.md new file mode 100644 index 000000000..e0dcb8bd2 --- /dev/null +++ b/.claude/skills/react-form-builder/components.md @@ -0,0 +1,249 @@ +# Makerkit Form Components Reference + +## Import Pattern + +```typescript +import { Form, FormField, FormItem, FormLabel, FormControl, FormDescription, FormMessage } from '@kit/ui/form'; +import { Input } from '@kit/ui/input'; +import { Button } from '@kit/ui/button'; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@kit/ui/select'; +import { Textarea } from '@kit/ui/textarea'; +import { Checkbox } from '@kit/ui/checkbox'; +import { Switch } from '@kit/ui/switch'; +import { Alert, AlertDescription } from '@kit/ui/alert'; +import { If } from '@kit/ui/if'; +import { Trans } from '@kit/ui/trans'; +import { toast } from '@kit/ui/sonner'; +``` + +## Form Field Pattern + +```tsx + ( + + + + + + + + + + + + + )} +/> +``` + +## Select Field + +```tsx + ( + + Category + + + + )} +/> +``` + +## Checkbox Field + +```tsx + ( + + + + + + + + + )} +/> +``` + +## Switch Field + +```tsx + ( + +
+ Enable Notifications + Receive email notifications +
+ + + +
+ )} +/> +``` + +## Textarea Field + +```tsx + ( + + Description + +