Optimized agents rules subfolders, dependencies updates (#355)
* Update AGENTS.md and CLAUDE.md for improved clarity and structure * Added MCP Server * Added missing triggers to tables that should have used them * Updated all dependencies * Fixed rare bug in React present in the Admin layout which prevents navigating to pages (sometimes...)
This commit is contained in:
committed by
GitHub
parent
9fae142f2d
commit
533dfba5b9
610
AGENTS.md
610
AGENTS.md
@@ -1,25 +1,23 @@
|
||||
# AGENTS.md
|
||||
This file provides guidance to Claude Code when working with code in this repository.
|
||||
|
||||
This AGENTS.md file provides comprehensive guidance for OpenAI Codex and other AI agents working with this codebase.
|
||||
## Core Technologies
|
||||
|
||||
### Core Technologies
|
||||
|
||||
- **Next.js 15** with App Router and Turbopack
|
||||
- **Next.js 15** with App Router
|
||||
- **Supabase** for database, auth, and storage
|
||||
- **React 19** with React Compiler
|
||||
- **TypeScript** with strict configuration
|
||||
- **Tailwind CSS 4** for styling
|
||||
- **Turborepo** for monorepo management
|
||||
- **React 19**
|
||||
- **TypeScript**
|
||||
- **Tailwind CSS 4** and Shadcn UI
|
||||
- **Turborepo**
|
||||
|
||||
### Monorepo Structure
|
||||
## Monorepo Structure
|
||||
|
||||
- @apps/web - Main Next.js SaaS application
|
||||
- @apps/dev-tool - Development utilities (port 3010)
|
||||
- @apps/e2e - Playwright end-to-end tests
|
||||
- @packages/ - Shared packages and utilities
|
||||
- @tooling/ - Build tools and development scripts
|
||||
- `apps/web` - Main Next.js SaaS application
|
||||
- `apps/e2e` - Playwright end-to-end tests
|
||||
- `packages/features/*` - Feature packages
|
||||
- `packages/` - Shared packages and utilities
|
||||
- `tooling/` - Build tools and development scripts
|
||||
|
||||
### Multi-Tenant Architecture
|
||||
## Multi-Tenant Architecture
|
||||
|
||||
**Personal Accounts**: Individual user accounts (auth.users.id = accounts.id)
|
||||
**Team Accounts**: Shared workspaces with members, roles, and permissions
|
||||
@@ -33,8 +31,6 @@ Data associates with accounts via foreign keys for proper access control.
|
||||
```bash
|
||||
pnpm dev # Start all apps
|
||||
pnpm --filter web dev # Main app (port 3000)
|
||||
pnpm --filter dev-tool dev # Dev tools (port 3010)
|
||||
pnpm build # Build all apps
|
||||
```
|
||||
|
||||
### Database Operations
|
||||
@@ -49,570 +45,28 @@ pnpm --filter web supabase:db:diff # Create migration
|
||||
### Code Quality
|
||||
|
||||
```bash
|
||||
pnpm lint && pnpm format # Lint and format
|
||||
pnpm typecheck # Type checking
|
||||
pnpm test # Run tests
|
||||
pnpm format:fix
|
||||
pnpm lint:fix
|
||||
pnpm typecheck
|
||||
```
|
||||
|
||||
## Application Structure
|
||||
- Run the typecheck command regularly to ensure your code is type-safe.
|
||||
- Run the linter and the formatter when your task is complete.
|
||||
|
||||
### Route Organization
|
||||
## Typescript
|
||||
|
||||
```
|
||||
app/
|
||||
├── (marketing)/ # Public pages (landing, blog, docs)
|
||||
├── (auth)/ # Authentication pages
|
||||
├── home/
|
||||
│ ├── (user)/ # Personal account context
|
||||
│ └── [account]/ # Team account context ([account] = team slug)
|
||||
├── admin/ # Super admin section
|
||||
└── api/ # API routes
|
||||
```
|
||||
- Write clean, clear, well-designed, explicit TypeScript
|
||||
- Avoid obvious comments
|
||||
- Avoid unnecessary complexity or overly abstract code
|
||||
- Always use implicit type inference, unless impossible
|
||||
- You must avoid using `any`
|
||||
- Handle errors gracefully using try/catch and appropriate error types
|
||||
|
||||
Key Examples:
|
||||
## React
|
||||
|
||||
- Marketing layout: @apps/web/app/(marketing)/layout.tsx
|
||||
- Personal dashboard: @apps/web/app/home/(user)/page.tsx
|
||||
- Team workspace: @apps/web/app/home/[account]/page.tsx
|
||||
- Admin section: @apps/web/app/admin/page.tsx
|
||||
|
||||
### Component Organization
|
||||
|
||||
- **Route-specific**: Use \_components/ directories
|
||||
- **Route utilities**: Use \_lib/ for client, \_lib/server/ for server-side
|
||||
- **Global components**: Root-level directories
|
||||
|
||||
Example:
|
||||
|
||||
- Team components: @apps/web/app/home/[account]/\_components/
|
||||
- Team server utils: @apps/web/app/home/[account]/\_lib/server/
|
||||
- Marketing components: @apps/web/app/(marketing)/\_components/
|
||||
|
||||
## Database Guidelines
|
||||
|
||||
### Security & RLS Implementation
|
||||
|
||||
**Critical Security Guidelines - Read Carefully! ⚠️**
|
||||
|
||||
#### Database Security Fundamentals
|
||||
|
||||
- **Always enable RLS** on new tables unless explicitly instructed otherwise
|
||||
- **NEVER use SECURITY DEFINER functions** without explicit access controls - they bypass RLS entirely
|
||||
- **Always use security_invoker=true for views** to maintain proper access control
|
||||
- **Storage buckets MUST validate access** using account_id in the path structure. See @apps/web/supabase/schemas/16-storage.sql for proper implementation.
|
||||
- **Use locks if required**: Database locks prevent race conditions and timing attacks in concurrent operations. Make sure to take these into account for all database operations.
|
||||
|
||||
#### Security Definer Function - Dangerous Pattern ❌
|
||||
|
||||
```sql
|
||||
-- NEVER DO THIS - Allows any authenticated user to call function
|
||||
CREATE OR REPLACE FUNCTION public.dangerous_function()
|
||||
RETURNS void
|
||||
LANGUAGE plpgsql
|
||||
SECURITY DEFINER AS $
|
||||
BEGIN
|
||||
-- This bypasses all RLS policies!
|
||||
DELETE FROM sensitive_table; -- Anyone can call this!
|
||||
END;
|
||||
$;
|
||||
GRANT EXECUTE ON FUNCTION public.dangerous_function() TO authenticated;
|
||||
```
|
||||
|
||||
#### Security Definer Function - Safe Pattern ✅
|
||||
|
||||
```sql
|
||||
-- ONLY use SECURITY DEFINER with explicit access validation
|
||||
CREATE OR REPLACE FUNCTION public.safe_admin_function(target_account_id uuid)
|
||||
RETURNS void
|
||||
LANGUAGE plpgsql
|
||||
SECURITY DEFINER
|
||||
SET search_path = '' AS $
|
||||
BEGIN
|
||||
-- MUST validate caller has permission FIRST
|
||||
IF NOT public.is_account_owner(target_account_id) THEN
|
||||
RAISE EXCEPTION 'Access denied: insufficient permissions';
|
||||
END IF;
|
||||
|
||||
-- Now safe to proceed with elevated privileges
|
||||
-- Your admin operation here
|
||||
END;
|
||||
$;
|
||||
```
|
||||
|
||||
#### Existing Helper Functions - Use These! 📚
|
||||
|
||||
**DO NOT recreate these functions - they already exist:**
|
||||
|
||||
```sql
|
||||
-- Account Access Control
|
||||
public.has_role_on_account(account_id, role?) -- Check team membership
|
||||
public.has_permission(user_id, account_id, permission) -- Check permissions
|
||||
public.is_account_owner(account_id) -- Verify ownership
|
||||
public.has_active_subscription(account_id) -- Subscription status
|
||||
public.is_team_member(account_id, user_id) -- Direct membership check
|
||||
public.can_action_account_member(target_account_id, target_user_id) -- Member action rights
|
||||
|
||||
-- Administrative Functions
|
||||
public.is_super_admin() -- Super admin check
|
||||
public.is_aal2() -- MFA verification
|
||||
public.is_mfa_compliant() -- MFA compliance
|
||||
|
||||
-- Configuration
|
||||
public.is_set(field_name) -- Feature flag checks
|
||||
```
|
||||
|
||||
Always check @apps/web/supabase/schemas/ before creating new functions!
|
||||
|
||||
#### RLS Policy Best Practices ✅
|
||||
|
||||
```sql
|
||||
-- Proper RLS using existing helper functions
|
||||
CREATE POLICY "notes_read" ON public.notes FOR SELECT
|
||||
TO authenticated USING (
|
||||
account_id = (select auth.uid()) OR
|
||||
public.has_role_on_account(account_id)
|
||||
);
|
||||
|
||||
-- For operations requiring specific permissions
|
||||
CREATE POLICY "notes_manage" ON public.notes FOR ALL
|
||||
TO authenticated USING (
|
||||
public.has_permission(auth.uid(), account_id, 'notes.manage'::app_permissions)
|
||||
);
|
||||
```
|
||||
|
||||
### Schema Management Workflow
|
||||
|
||||
1. Create schemas in @apps/web/supabase/schemas/ as `<number>-<name>.sql`
|
||||
2. After changes: `pnpm supabase:web:stop`
|
||||
3. Run: `pnpm --filter web run supabase:db:diff -f <filename>`
|
||||
4. Restart: `pnpm supabase:web:start` and `pnpm supabase:web:reset`
|
||||
5. Generate types: `pnpm supabase:web:typegen`
|
||||
|
||||
Key schema files:
|
||||
|
||||
- Accounts: @apps/web/supabase/schemas/03-accounts.sql
|
||||
- Memberships: @apps/web/supabase/schemas/05-memberships.sql
|
||||
- Permissions: @apps/web/supabase/schemas/06-roles-permissions.sql
|
||||
|
||||
### Type Generation
|
||||
|
||||
```typescript
|
||||
import { Tables } from '@kit/supabase/database';
|
||||
|
||||
type Account = Tables<'accounts'>;
|
||||
```
|
||||
|
||||
Always prefer inferring types from generated Database types.
|
||||
|
||||
## Development Patterns
|
||||
|
||||
### Data Fetching Strategy
|
||||
|
||||
**Quick Decision Framework:**
|
||||
|
||||
- **Server Components**: Default choice for initial data loading
|
||||
- **Client Components**: For interactive features requiring hooks or real-time updates
|
||||
- **Admin Client**: Only for bypassing RLS (rare cases - requires manual auth/authorization)
|
||||
|
||||
#### Server Components (Preferred) ✅
|
||||
|
||||
```typescript
|
||||
import { getSupabaseServerClient } from '@kit/supabase/server-client';
|
||||
|
||||
async function NotesPage() {
|
||||
const client = getSupabaseServerClient();
|
||||
const { data, error } = await client.from('notes').select('*');
|
||||
|
||||
if (error) return <ErrorMessage error={error} />;
|
||||
return <NotesList notes={data} />;
|
||||
}
|
||||
```
|
||||
|
||||
**Key Insight**: Server Components automatically inherit RLS protection - no additional authorization checks needed!
|
||||
|
||||
#### Client Components (Interactive) 🖱️
|
||||
|
||||
```typescript
|
||||
'use client';
|
||||
import { useSupabase } from '@kit/supabase/hooks/use-supabase';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
|
||||
function InteractiveNotes() {
|
||||
const supabase = useSupabase();
|
||||
const { data, isLoading } = useQuery({
|
||||
queryKey: ['notes'],
|
||||
queryFn: () => supabase.from('notes').select('*')
|
||||
});
|
||||
|
||||
if (isLoading) return <Spinner />;
|
||||
return <NotesList notes={data} />;
|
||||
}
|
||||
```
|
||||
|
||||
#### Performance Optimization - Parallel Data Fetching 🚀
|
||||
|
||||
**Sequential (Slow) Pattern ❌**
|
||||
|
||||
```typescript
|
||||
async function SlowDashboard() {
|
||||
const userData = await loadUserData();
|
||||
const notifications = await loadNotifications();
|
||||
const metrics = await loadMetrics();
|
||||
// Total time: sum of all requests
|
||||
}
|
||||
```
|
||||
|
||||
**Parallel (Optimized) Pattern ✅**
|
||||
|
||||
```typescript
|
||||
async function FastDashboard() {
|
||||
// Execute all requests simultaneously
|
||||
const [userData, notifications, metrics] = await Promise.all([
|
||||
loadUserData(),
|
||||
loadNotifications(),
|
||||
loadMetrics()
|
||||
]);
|
||||
// Total time: longest single request
|
||||
|
||||
return <Dashboard user={userData} notifications={notifications} metrics={metrics} />;
|
||||
}
|
||||
```
|
||||
|
||||
**Performance Impact**: Parallel fetching can reduce page load time by 60-80% for multi-data pages!
|
||||
|
||||
### Authorization Patterns - Critical Understanding 🔐
|
||||
|
||||
#### RLS-Protected Data Fetching (Standard) ✅
|
||||
|
||||
```typescript
|
||||
async function getUserNotes(userId: string) {
|
||||
const client = getSupabaseServerClient();
|
||||
|
||||
// RLS automatically ensures user can only access their own notes
|
||||
// NO additional authorization checks needed!
|
||||
const { data } = await client.from('notes').select('*').eq('user_id', userId); // RLS validates this automatically
|
||||
|
||||
return data;
|
||||
}
|
||||
```
|
||||
|
||||
#### Admin Client Usage (Dangerous - Rare Cases Only) ⚠️
|
||||
|
||||
```typescript
|
||||
async function adminGetUserNotes(userId: string) {
|
||||
const adminClient = getSupabaseServerAdminClient();
|
||||
|
||||
// CRITICAL: Manual authorization required - bypasses RLS!
|
||||
const currentUser = await getCurrentUser();
|
||||
if (!(await isSuperAdmin(currentUser))) {
|
||||
throw new Error('Unauthorized: Admin access required');
|
||||
}
|
||||
|
||||
// Additional validation: ensure current admin isn't targeting themselves
|
||||
if (currentUser.id === userId) {
|
||||
throw new Error('Cannot perform admin action on own account');
|
||||
}
|
||||
|
||||
// Now safe to proceed with admin privileges
|
||||
const { data } = await adminClient
|
||||
.from('notes')
|
||||
.select('*')
|
||||
.eq('user_id', userId);
|
||||
|
||||
return data;
|
||||
}
|
||||
```
|
||||
|
||||
**Rule of thumb**: If using standard Supabase client, trust RLS. If using admin client, validate everything manually.
|
||||
|
||||
### Server Actions Implementation
|
||||
|
||||
Always use `enhanceAction` from @packages/next/src/actions/index.ts:
|
||||
|
||||
```typescript
|
||||
'use server';
|
||||
|
||||
import { enhanceAction } from '@kit/next/actions';
|
||||
|
||||
export const createNoteAction = enhanceAction(
|
||||
async function (data, user) {
|
||||
// data is validated, user is authenticated
|
||||
return { success: true };
|
||||
},
|
||||
{
|
||||
auth: true,
|
||||
schema: CreateNoteSchema,
|
||||
},
|
||||
);
|
||||
```
|
||||
|
||||
Example server actions:
|
||||
|
||||
- Team billing: @apps/web/app/home/[account]/billing/\_lib/server/server-actions.ts
|
||||
- Personal settings: @apps/web/app/home/(user)/settings/\_lib/server/server-actions.ts
|
||||
|
||||
### Forms with React Hook Form & Zod
|
||||
|
||||
```typescript
|
||||
// 1. Schema in separate file
|
||||
export const CreateNoteSchema = z.object({
|
||||
title: z.string().min(1),
|
||||
content: z.string().min(1),
|
||||
});
|
||||
|
||||
// 2. Client component with form
|
||||
'use client';
|
||||
const form = useForm({
|
||||
resolver: zodResolver(CreateNoteSchema),
|
||||
});
|
||||
|
||||
const onSubmit = (data) => {
|
||||
startTransition(async () => {
|
||||
await toast.promise(createNoteAction(data), {
|
||||
loading: 'Creating...',
|
||||
success: 'Created!',
|
||||
error: 'Failed!',
|
||||
});
|
||||
});
|
||||
};
|
||||
```
|
||||
|
||||
Form examples:
|
||||
|
||||
- Contact form: @apps/web/app/(marketing)/contact/\_components/contact-form.tsx
|
||||
- Verify OTP form: @packages/otp/src/components/verify-otp-form.tsx
|
||||
|
||||
### Route Handlers (API Routes)
|
||||
|
||||
Use `enhanceRouteHandler` from @packages/next/src/routes/index.ts:
|
||||
|
||||
```typescript
|
||||
import { enhanceRouteHandler } from '@kit/next/routes';
|
||||
|
||||
export const POST = enhanceRouteHandler(
|
||||
async function ({ body, user, request }) {
|
||||
// body is validated, user available if auth: true
|
||||
return NextResponse.json({ success: true });
|
||||
},
|
||||
{
|
||||
auth: true,
|
||||
schema: ZodSchema,
|
||||
},
|
||||
);
|
||||
```
|
||||
|
||||
## React & TypeScript Best Practices
|
||||
|
||||
### TS
|
||||
|
||||
- Write clean, clear, well-designed, explicit Typescript
|
||||
- Use implicit type inference, unless impossible
|
||||
- `any` and `unknown` are a code smell and must justified if used
|
||||
- Handle errors gracefully using try/catch and appropriate error types.
|
||||
|
||||
### Components
|
||||
|
||||
- Use functional components with TypeScript
|
||||
- Use functional components
|
||||
- Always use 'use client' directive for client components
|
||||
- Destructure props with proper TypeScript interfaces
|
||||
- Name files to match component name (e.g., user-profile.tsx)
|
||||
|
||||
### Conditional Rendering
|
||||
|
||||
Use the `If` component from @packages/ui/src/makerkit/if.tsx:
|
||||
|
||||
```tsx
|
||||
import { If } from '@kit/ui/if';
|
||||
|
||||
<If condition={isLoading} fallback={<Content />}>
|
||||
<Spinner />
|
||||
</If>
|
||||
|
||||
// With type inference
|
||||
<If condition={error}>
|
||||
{(err) => <ErrorMessage error={err} />}
|
||||
</If>
|
||||
```
|
||||
|
||||
### Testing Attributes
|
||||
|
||||
```tsx
|
||||
<button data-test="submit-button">Submit</button>
|
||||
<div data-test="user-profile" data-user-id={user.id}>Profile</div>
|
||||
```
|
||||
|
||||
### Internationalization
|
||||
|
||||
Always use `Trans` component from @packages/ui/src/makerkit/trans.tsx:
|
||||
|
||||
```tsx
|
||||
import { Trans } from '@kit/ui/trans';
|
||||
|
||||
<Trans
|
||||
i18nKey="user:welcomeMessage"
|
||||
values={{ name: user.name }}
|
||||
/>
|
||||
|
||||
// With HTML elements
|
||||
<Trans
|
||||
i18nKey="terms:agreement"
|
||||
components={{
|
||||
TermsLink: <a href="/terms" className="underline" />,
|
||||
}}
|
||||
/>
|
||||
```
|
||||
|
||||
Adding new languages:
|
||||
|
||||
1. Add language code to @apps/web/lib/i18n/i18n.settings.ts
|
||||
2. Create translation files in @apps/web/public/locales/[new-language]/
|
||||
3. Copy structure from English files
|
||||
|
||||
Translation files: @apps/web/public/locales/<locale>/<namespace>.json
|
||||
|
||||
## Security Guidelines 🛡️
|
||||
|
||||
### Authentication & Authorization
|
||||
|
||||
- Authentication enforced by middleware
|
||||
- Authorization handled by RLS at database level
|
||||
- Avoid defensive code - use RLS instead
|
||||
- For admin client usage, enforce both authentication and authorization
|
||||
|
||||
### Data Passing
|
||||
|
||||
- **Never pass sensitive data** to Client Components
|
||||
- **Never expose server environment variables** to client (unless prefixed with NEXT_PUBLIC)
|
||||
- Always validate user input
|
||||
|
||||
### OTP for Sensitive Operations
|
||||
|
||||
Use one-time tokens from @packages/otp/src/api/index.ts:
|
||||
|
||||
```tsx
|
||||
import { VerifyOtpForm } from '@kit/otp/components';
|
||||
|
||||
<VerifyOtpForm
|
||||
purpose="account-deletion"
|
||||
email={user.email}
|
||||
onSuccess={(otp) => {
|
||||
// Proceed with verified operation
|
||||
}}
|
||||
/>;
|
||||
```
|
||||
|
||||
### Super Admin Protection
|
||||
|
||||
For admin routes, use `AdminGuard` from @packages/features/admin/src/components/admin-guard.tsx:
|
||||
|
||||
```tsx
|
||||
import { AdminGuard } from '@kit/admin/components/admin-guard';
|
||||
|
||||
export default AdminGuard(AdminPageComponent);
|
||||
```
|
||||
|
||||
## UI Components 🎨
|
||||
|
||||
### Core UI Library
|
||||
|
||||
Import from @packages/ui/src/:
|
||||
|
||||
```tsx
|
||||
// Shadcn components
|
||||
import { Button } from '@kit/ui/button';
|
||||
import { Card } from '@kit/ui/card';
|
||||
// Makerkit components
|
||||
import { If } from '@kit/ui/if';
|
||||
import { ProfileAvatar } from '@kit/ui/profile-avatar';
|
||||
import { toast } from '@kit/ui/sonner';
|
||||
import { Trans } from '@kit/ui/trans';
|
||||
```
|
||||
|
||||
### Styling
|
||||
|
||||
- Use Tailwind CSS v4 with semantic classes
|
||||
- Prefer Shadcn-ui classes like `bg-background`, `text-muted-foreground`
|
||||
- Use `cn()` utility from @kit/ui/cn for class merging
|
||||
|
||||
## Workspace Contexts 🏢
|
||||
|
||||
### Personal Account Context (@apps/web/app/home/(user))
|
||||
|
||||
```tsx
|
||||
import { useUserWorkspace } from '@kit/accounts/hooks/use-user-workspace';
|
||||
|
||||
function PersonalComponent() {
|
||||
const { user, account } = useUserWorkspace();
|
||||
// Personal account data
|
||||
}
|
||||
```
|
||||
|
||||
Context provider: @packages/features/accounts/src/components/user-workspace-context-provider.tsx
|
||||
|
||||
### Team Account Context (@apps/web/app/home/[account])
|
||||
|
||||
```tsx
|
||||
import { useTeamAccountWorkspace } from '@kit/team-accounts/hooks/use-team-account-workspace';
|
||||
|
||||
function TeamComponent() {
|
||||
const { account, user, accounts } = useTeamAccountWorkspace();
|
||||
// Team account data with permissions
|
||||
}
|
||||
```
|
||||
|
||||
Context provider: @packages/features/team-accounts/src/components/team-account-workspace-context-provider.tsx
|
||||
|
||||
## Error Handling & Logging 📊
|
||||
|
||||
### Structured Logging
|
||||
|
||||
Use logger from @packages/shared/src/logger/logger.ts:
|
||||
|
||||
```typescript
|
||||
import { getLogger } from '@kit/shared/logger';
|
||||
|
||||
async function myServerAction() {
|
||||
const logger = await getLogger();
|
||||
const ctx = { name: 'myOperation', userId: user.id };
|
||||
|
||||
try {
|
||||
logger.info(ctx, 'Operation started');
|
||||
// ...
|
||||
} catch (error) {
|
||||
logger.error({ ...ctx, error }, 'Operation failed');
|
||||
// handle error
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## API Services
|
||||
|
||||
### Account Services
|
||||
|
||||
- Personal accounts API: @packages/features/accounts/src/server/api.ts
|
||||
- Team accounts API: @packages/features/team-accounts/src/server/api.ts
|
||||
- Admin service: @packages/features/admin/src/lib/server/services/admin.service.ts
|
||||
|
||||
### Billing Services
|
||||
|
||||
- Personal billing: @apps/web/app/home/(user)/billing/\_lib/server/user-billing.service.ts
|
||||
- Team billing: @apps/web/app/home/[account]/billing/\_lib/server/team-billing.service.ts
|
||||
- Per-seat billing: @packages/features/team-accounts/src/server/services/account-per-seat-billing.service.ts
|
||||
|
||||
## Key Configuration Files
|
||||
|
||||
- **Feature flags**: @apps/web/config/feature-flags.config.ts
|
||||
- **i18n settings**: @apps/web/lib/i18n/i18n.settings.ts
|
||||
- **Supabase config**: @apps/web/supabase/config.toml
|
||||
- **Middleware**: @apps/web/middleware.ts
|
||||
|
||||
## Quick Reference Checklist ✅
|
||||
|
||||
### Development Workflow
|
||||
|
||||
- [ ] Enable RLS on new tables
|
||||
- [ ] Generate TypeScript types after schema changes and infer types from these
|
||||
- [ ] Implement proper error handling with logging
|
||||
- [ ] Use Zod schemas for parsing all user input (including cookies, query params, etc.)
|
||||
- [ ] Add testing attributes to interactive elements
|
||||
- [ ] Validate permissions before sensitive operations
|
||||
- Add `data-test` for E2E tests where appropriate
|
||||
- `useEffect` is a code smell and must be justified - avoid if possible
|
||||
- Do not write many separate `useState`, prefer single state object (unless required)
|
||||
- Prefer server-side data fetching using RSC
|
||||
Reference in New Issue
Block a user