From c7b4001c3dc4d2016345f3b9ac1f0daf8731cedd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Malfait?= Date: Tue, 17 Jun 2025 07:54:02 +0200 Subject: [PATCH] Migrate cursor rules (#12646) Migrating rules to new format but they should be re-written entirely, I don't think they help much and are not auto-included (except architecture) --- .cursor/rules/README.md | 65 ---- .cursor/rules/README.mdc | 137 +++++++++ .cursor/rules/architecture.md | 97 ------ .cursor/rules/architecture.mdc | 35 +++ .cursor/rules/code-style-guidelines.md | 259 ---------------- .cursor/rules/code-style.mdc | 103 +++++++ .cursor/rules/file-structure-guidelines.md | 207 ------------- .cursor/rules/file-structure.mdc | 107 +++++++ .cursor/rules/nx-rules.mdc | 90 ++++-- .cursor/rules/react-general-guidelines.md | 220 ------------- .cursor/rules/react-general-guidelines.mdc | 86 ++++++ .../react-state-management-guidelines.md | 219 ------------- .cursor/rules/react-state-management.mdc | 81 +++++ .cursor/rules/testing-guidelines.md | 253 --------------- .cursor/rules/testing-guidelines.mdc | 89 ++++++ .cursor/rules/translations.md | 162 ---------- .cursor/rules/translations.mdc | 291 ++++++++++++++++++ ...uidelines.md => typescript-guidelines.mdc} | 13 +- ...deLocale.test.ts => isValidLocale.test.ts} | 0 19 files changed, 1011 insertions(+), 1503 deletions(-) delete mode 100644 .cursor/rules/README.md create mode 100644 .cursor/rules/README.mdc delete mode 100644 .cursor/rules/architecture.md create mode 100644 .cursor/rules/architecture.mdc delete mode 100644 .cursor/rules/code-style-guidelines.md create mode 100644 .cursor/rules/code-style.mdc delete mode 100644 .cursor/rules/file-structure-guidelines.md create mode 100644 .cursor/rules/file-structure.mdc delete mode 100644 .cursor/rules/react-general-guidelines.md create mode 100644 .cursor/rules/react-general-guidelines.mdc delete mode 100644 .cursor/rules/react-state-management-guidelines.md create mode 100644 .cursor/rules/react-state-management.mdc delete mode 100644 .cursor/rules/testing-guidelines.md create mode 100644 .cursor/rules/testing-guidelines.mdc delete mode 100644 .cursor/rules/translations.md create mode 100644 .cursor/rules/translations.mdc rename .cursor/rules/{typescript-guidelines.md => typescript-guidelines.mdc} (92%) rename packages/twenty-shared/src/utils/validation/__tests__/{isValideLocale.test.ts => isValidLocale.test.ts} (100%) diff --git a/.cursor/rules/README.md b/.cursor/rules/README.md deleted file mode 100644 index c59f9cf06..000000000 --- a/.cursor/rules/README.md +++ /dev/null @@ -1,65 +0,0 @@ -# Twenty Development Rules - -This directory contains Twenty's development guidelines and best practices. The rules are organized into several key categories: - -## Guidelines Structure - -### 1. Architecture and Structure -- `architecture.md`: Project overview, technology stack, and infrastructure setup -- `file-structure-guidelines.md`: File and directory organization patterns - -### 2. Code Style and Development -- `typescript-guidelines.md`: TypeScript best practices and conventions -- `code-style-guidelines.md`: General coding standards and style guide - -### 3. React Development -- `react-general-guidelines.md`: Core React development principles and patterns -- `react-state-management-guidelines.md`: State management approaches and best practices - -### 4. Testing -- `testing-guidelines.md`: Testing strategies, patterns, and best practices - -### 5. Internationalization -- `translations.md`: Translation workflow, i18n setup, and string management - -## Common Development Commands - -### Frontend Commands -```bash -# Testing -npx nx test twenty-front # Run unit tests -npx nx storybook:build twenty-front # Build Storybook -npx nx storybook:serve-and-test:static # Run Storybook tests - -# Development -npx nx lint twenty-front # Run linter -npx nx typecheck twenty-front # Type checking -npx nx run twenty-front:graphql:generate # Generate GraphQL types -``` - -### Backend Commands -```bash -# Database -npx nx database:reset twenty-server # Reset database -npx nx run twenty-server:database:init:prod # Initialize database -npx nx run twenty-server:database:migrate:prod # Run migrations - -# Development -npx nx run twenty-server:start # Start the server -npx nx run twenty-server:lint # Run linter (add --fix to auto-fix) -npx nx run twenty-server:typecheck # Type checking -npx nx run twenty-server:test # Run unit tests -npx nx run twenty-server:test:integration:with-db-reset # Run integration tests - -# Migrations -npx nx run twenty-server:typeorm migration:generate src/database/typeorm/core/migrations/[name] -d src/database/typeorm/core/core.datasource.ts - -# Workspace -npx nx run twenty-server:command workspace:sync-metadata -f # Sync metadata -``` - -## Usage - -These rules are automatically attached to relevant files in your workspace through Cursor's context system. They help maintain consistency and quality across the Twenty codebase. - -For the most up-to-date version of these guidelines, always refer to the files in this directory. \ No newline at end of file diff --git a/.cursor/rules/README.mdc b/.cursor/rules/README.mdc new file mode 100644 index 000000000..c9ae911da --- /dev/null +++ b/.cursor/rules/README.mdc @@ -0,0 +1,137 @@ +--- +description: +globs: +alwaysApply: false +--- +# Twenty Development Rules + +This directory contains Twenty's development guidelines and best practices in the modern Cursor Rules format (MDC). These rules are automatically applied based on file patterns and provide context-aware guidance to AI assistants. + +## Rules Overview + +### Core Guidelines +- **architecture.mdc** - Project overview, technology stack, and infrastructure setup (Always Applied) +- **nx-rules.mdc** - Nx workspace guidelines and best practices (Auto-attached to Nx files) + +### Code Quality +- **typescript-guidelines.mdc** - TypeScript best practices and conventions (Auto-attached to .ts/.tsx files) +- **code-style.mdc** - General coding standards and style guide (Auto-attached to code files) +- **file-structure.mdc** - File and directory organization patterns (Auto-attached to config files) + +### React Development +- **react-general-guidelines.mdc** - Core React development principles (Auto-attached to React files) +- **react-state-management.mdc** - State management approaches with Recoil (Auto-attached to state files) + +### Testing & Quality +- **testing-guidelines.mdc** - Testing strategies and best practices (Auto-attached to test files) + +### Internationalization +- **translations.mdc** - Translation workflow and i18n setup (Auto-attached to locale files) + +## How Rules Work + +### Automatic Attachment +Rules are automatically included in your AI context based on file patterns (globs). When you work on TypeScript files, the TypeScript guidelines are automatically loaded. + +### Manual Reference +You can manually reference any rule using the `@ruleName` syntax: +- `@nx-rules` - Include Nx-specific guidance +- `@react-general-guidelines` - Load React best practices +- `@testing-guidelines` - Get testing recommendations + +### Rule Types Used +- **Always Applied** - Loaded in every context (architecture.mdc, README.mdc) +- **Auto Attached** - Loaded when matching file patterns are referenced +- **Agent Requested** - Available for AI to include when relevant +- **Manual** - Only included when explicitly mentioned + +## Development Commands + +### Frontend Commands +```bash +# Testing +npx nx test twenty-front # Run unit tests +npx nx storybook:build twenty-front # Build Storybook +npx nx storybook:serve-and-test:static # Run Storybook tests + +# Development +npx nx lint twenty-front # Run linter +npx nx typecheck twenty-front # Type checking +npx nx run twenty-front:graphql:generate # Generate GraphQL types +``` + +### Backend Commands +```bash +# Database +npx nx database:reset twenty-server # Reset database +npx nx run twenty-server:database:init:prod # Initialize database +npx nx run twenty-server:database:migrate:prod # Run migrations + +# Development +npx nx run twenty-server:start # Start the server +npx nx run twenty-server:lint # Run linter (add --fix to auto-fix) +npx nx run twenty-server:typecheck # Type checking +npx nx run twenty-server:test # Run unit tests +npx nx run twenty-server:test:integration:with-db-reset # Run integration tests + +# Migrations +npx nx run twenty-server:typeorm migration:generate src/database/typeorm/core/migrations/[name] -d src/database/typeorm/core/core.datasource.ts + +# Workspace +npx nx run twenty-server:command workspace:sync-metadata -f # Sync metadata +``` + +## Usage Guidelines + +### For Developers +- Rules are automatically applied based on file context +- Check rule descriptions to understand when they're activated +- Use manual references (`@ruleName`) for additional context +- Keep rules updated as the codebase evolves + +### For AI Assistants +- Rules provide consistent guidance across conversations +- Use rule context to maintain coding standards +- Reference specific rules when making recommendations +- Apply rule principles in code suggestions and reviews + +## Contributing to Rules + +### Adding New Rules +1. Create a new `.mdc` file in this directory +2. Include proper metadata headers with description and globs +3. Write clear, actionable guidelines with examples +4. Test the rule with relevant file patterns +5. Update this README if needed + +### Updating Existing Rules +1. Modify the rule content while preserving metadata +2. Test changes with affected file patterns +3. Ensure consistency with other rules +4. Update examples and best practices as needed + +## Rule Format Reference + +Each rule file uses the MDC format with metadata: + +```markdown +--- +description: Brief description of the rule's purpose +globs: ["**/*.ts", "**/*.tsx"] # File patterns for auto-attachment +alwaysApply: false # Whether to always include this rule +--- + +# Rule Title + +Rule content in Markdown format... +``` + +## Migration from Legacy Format + +The rules have been migrated from the legacy `.md` format to the modern `.mdc` format, providing: +- Better context awareness through file pattern matching +- Improved organization with metadata headers +- More flexible rule application strategies +- Enhanced integration with Cursor's AI features + +For the most up-to-date version of these guidelines, always refer to the files in this directory. diff --git a/.cursor/rules/architecture.md b/.cursor/rules/architecture.md deleted file mode 100644 index 4ed78dd4d..000000000 --- a/.cursor/rules/architecture.md +++ /dev/null @@ -1,97 +0,0 @@ -# Twenty Project Architecture - -## Overview -Twenty is an open-source CRM built with modern technologies, using TypeScript for both frontend and backend development. This document outlines the core architectural decisions and structure of the project. - -## Monorepo Structure -The project is organized as a monorepo using nx, with the following main packages: - -### Main Packages -- `packages/twenty-front`: Main Frontend application - - Technology: React - - Purpose: Provides the main user interface for the CRM - - Key responsibilities: User interactions, state management, data display - -- `packages/twenty-server`: Main Backend application - - Technology: NestJS - - Purpose: Handles business logic, data persistence, and API - - Key responsibilities: Data processing, authentication, API endpoints - -- `packages/twenty-website`: Marketing Website and Documentation - - Technology: NextJS - - Purpose: Public-facing website and documentation - - Key responsibilities: Marketing content, documentation, SEO - -- `packages/twenty-ui`: UI Component Library - - Technology: React - - Purpose: Shared UI components and design system - - Key responsibilities: Reusable components, design consistency - -- `packages/twenty-shared`: Shared Utilities - - Purpose: Cross-package shared code between frontend and backend - - Contents: Utils, constants, types, interfaces - -## Core Technology Stack - -### Package Management -- Package Manager: yarn -- Monorepo Tool: nx -- Benefits: Consistent dependency management, shared configurations - -### Database Layer -- Primary Database: PostgreSQL -- Schema Structure: - - Core schema: Main application data - - Metadata schema: Configuration and customization data - - Workspace schemas: One schema per tenant, containing tenant-specific data -- ORM Layer: - - TypeORM: For core and metadata schemas - - Purpose: Type-safe database operations for system data - - Benefits: Strong typing, migration support - - TwentyORM: For workspace schemas - - Purpose: Manages tenant-specific entities and customizations - - Benefits: Dynamic entity management, per-tenant customization - - Example: Entities like CompanyWorkspaceEntity are managed per workspace - -### State Management -- Frontend State: Recoil - - Purpose: Global state management - - Use cases: User preferences, UI state, cached data - -### Data Layer -- API Technology: GraphQL -- Client: Apollo Client - - Purpose: Data fetching and caching - - Benefits: Type safety, efficient data loading - -### Infrastructure -- Cache: Redis - - Purpose: High-performance caching layer - - Use cases: Session data, frequent queries - -- Authentication: JWT - - Purpose: Secure user authentication - - Implementation: Token-based auth flow - -- Queue System: BullMQ - - Purpose: Background job processing - - Use cases: Emails, exports, imports - -- Storage: S3/Local Filesystem - - Purpose: File storage and management - - Flexibility: Configurable for cloud or local storage - -### Testing Infrastructure -- Backend Testing: - - Framework: Jest - - API Testing: Supertest - - Coverage: Unit tests, integration tests - -- Frontend Testing: - - Framework: Jest - - Component Testing: Storybook - - API Mocking: MSW (Mock Service Worker) - -- End-to-End Testing: - - Framework: Playwright - - Coverage: Critical user journeys \ No newline at end of file diff --git a/.cursor/rules/architecture.mdc b/.cursor/rules/architecture.mdc new file mode 100644 index 000000000..203b91049 --- /dev/null +++ b/.cursor/rules/architecture.mdc @@ -0,0 +1,35 @@ +--- +description: +globs: +alwaysApply: false +--- +--- +description: Twenty CRM architecture overview - monorepo structure, tech stack, and development principles +globs: [] +alwaysApply: true +--- + +# Twenty Architecture + +## Tech Stack +- **Frontend**: React 18, TypeScript, Recoil, Styled Components, Vite +- **Backend**: NestJS, TypeORM, PostgreSQL, Redis, GraphQL +- **Monorepo**: Nx workspace with yarn + +## Package Structure +``` +packages/ +├── twenty-front/ # React app +├── twenty-server/ # NestJS API +├── twenty-ui/ # Shared components +├── twenty-shared/ # Common types/utils +└── twenty-emails/ # Email templates +``` + +## Key Principles +- **Functional components only** (no classes) +- **Named exports only** (no default exports) +- **Types over interfaces** (except for extending third-party) +- **String literals over enums** (except GraphQL) +- **No 'any' type allowed** +- **Event handlers over useEffect** for state updates diff --git a/.cursor/rules/code-style-guidelines.md b/.cursor/rules/code-style-guidelines.md deleted file mode 100644 index b8fe184a2..000000000 --- a/.cursor/rules/code-style-guidelines.md +++ /dev/null @@ -1,259 +0,0 @@ -# Code Style Guidelines - -## Core Code Style Principles -Twenty emphasizes clean, readable, and maintainable code. This document outlines our code style conventions and best practices. - -## Control Flow - -### Early Returns -- Use early returns to reduce nesting -- Handle edge cases first - ```typescript - // ✅ Correct - const processUser = (user: User | null) => { - if (!user) return null; - if (!user.isActive) return null; - - return { - id: user.id, - name: user.name, - }; - }; - - // ❌ Incorrect - const processUser = (user: User | null) => { - if (user) { - if (user.isActive) { - return { - id: user.id, - name: user.name, - }; - } - } - return null; - }; - ``` - -### No Nested Ternaries -- Avoid nested ternary operators -- Use if statements or early returns - ```typescript - // ✅ Correct - const getUserDisplay = (user: User) => { - if (!user.name) return 'Anonymous'; - if (!user.isActive) return 'Inactive User'; - return user.name; - }; - - // ❌ Incorrect - const getUserDisplay = (user: User) => - user.name - ? user.isActive - ? user.name - : 'Inactive User' - : 'Anonymous'; - ``` - -### No Else-If Chains -- Use switch statements or lookup objects -- Keep conditions flat - ```typescript - // ✅ Correct - const getStatusColor = (status: Status): string => { - switch (status) { - case 'success': - return 'green'; - case 'warning': - return 'yellow'; - case 'error': - return 'red'; - default: - return 'gray'; - } - }; - - // Or using a lookup object - const statusColors: Record = { - success: 'green', - warning: 'yellow', - error: 'red', - default: 'gray', - }; - - // ❌ Incorrect - const getStatusColor = (status: Status): string => { - if (status === 'success') { - return 'green'; - } else if (status === 'warning') { - return 'yellow'; - } else if (status === 'error') { - return 'red'; - } else { - return 'gray'; - } - }; - ``` - -## Operators and Expressions - -### Optional Chaining Over && -- Use optional chaining for null/undefined checks -- Clearer intent and better type safety - ```typescript - // ✅ Correct - const userName = user?.name; - const userAddress = user?.address?.street; - - // ❌ Incorrect - const userName = user && user.name; - const userAddress = user && user.address && user.address.street; - ``` - -## Function Design - -### Small Focused Functions -- Keep functions small and single-purpose -- Extract complex logic into helper functions - ```typescript - // ✅ Correct - const validateUser = (user: User) => { - if (!isValidName(user.name)) return false; - if (!isValidEmail(user.email)) return false; - if (!isValidAge(user.age)) return false; - return true; - }; - - const isValidName = (name: string) => { - return name.length >= 2 && /^[a-zA-Z\s]*$/.test(name); - }; - - const isValidEmail = (email: string) => { - return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email); - }; - - const isValidAge = (age: number) => { - return age >= 18 && age <= 120; - }; - - // ❌ Incorrect - const validateUser = (user: User) => { - if (user.name.length < 2 || !/^[a-zA-Z\s]*$/.test(user.name)) return false; - if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(user.email)) return false; - if (user.age < 18 || user.age > 120) return false; - return true; - }; - ``` - -## Naming and Documentation - -### Clear Variable Names -- Use descriptive, intention-revealing names -- Avoid abbreviations unless common - ```typescript - // ✅ Correct - const isUserActive = user.status === 'active'; - const hasRequiredPermissions = user.permissions.includes('admin'); - const userDisplayName = user.name || 'Anonymous'; - - // ❌ Incorrect - const active = user.status === 'active'; - const hasPerm = user.permissions.includes('admin'); - const udn = user.name || 'Anonymous'; - ``` - -### No Console.logs in Commits -- Remove all console.logs before committing -- Use proper logging/error tracking in production - ```typescript - // ❌ Incorrect - Don't commit these - console.log('user:', user); - console.log('debug:', someValue); - - // ✅ Correct - Use proper logging - logger.info('User action completed', { userId: user.id }); - logger.error('Operation failed', { error }); - ``` - -### Minimal Comments -- Write self-documenting code -- Use comments only for complex business logic - ```typescript - // ✅ Correct - // Calculate pro-rated amount based on billing cycle - const calculateProRatedAmount = (amount: number, daysLeft: number, totalDays: number) => { - return (amount * daysLeft) / totalDays; - }; - - // ❌ Incorrect - Unnecessary comments - // Get the user's name - const getUserName = (user: User) => user.name; - - // Check if user is active - const isUserActive = (user: User) => user.status === 'active'; - ``` - -## Error Handling - -### Proper Error Handling -- Use try-catch blocks appropriately -- Provide meaningful error messages - ```typescript - // ✅ Correct - const fetchUserData = async (userId: string) => { - try { - const response = await api.get(`/users/${userId}`); - return response.data; - } catch (error) { - logger.error('Failed to fetch user data', { - userId, - error: error instanceof Error ? error.message : 'Unknown error', - }); - throw new UserFetchError('Failed to fetch user data'); - } - }; - - // ❌ Incorrect - const fetchUserData = async (userId: string) => { - try { - const response = await api.get(`/users/${userId}`); - return response.data; - } catch (error) { - console.log('error:', error); - throw error; - } - }; - ``` - -## Code Organization - -### Logical Grouping -- Group related code together -- Maintain consistent organization - ```typescript - // ✅ Correct - class UserService { - // Properties - private readonly api: Api; - private readonly logger: Logger; - - // Constructor - constructor(api: Api, logger: Logger) { - this.api = api; - this.logger = logger; - } - - // Public methods - public async getUser(id: string): Promise { - // Implementation - } - - public async updateUser(user: User): Promise { - // Implementation - } - - // Private helpers - private validateUser(user: User): boolean { - // Implementation - } - } - ``` \ No newline at end of file diff --git a/.cursor/rules/code-style.mdc b/.cursor/rules/code-style.mdc new file mode 100644 index 000000000..4b0d124af --- /dev/null +++ b/.cursor/rules/code-style.mdc @@ -0,0 +1,103 @@ +--- +description: +globs: +alwaysApply: false +--- +# Code Style Guidelines + +## Formatting Standards +- **Prettier**: 2-space indentation, single quotes, trailing commas, semicolons +- **Print width**: 80 characters +- **ESLint**: No unused imports, consistent import ordering, prefer const over let + +## Naming Conventions +```typescript +// ✅ Variables and functions - camelCase +const userAccountBalance = 1000; +const calculateMonthlyPayment = () => {}; + +// ✅ Constants - SCREAMING_SNAKE_CASE +const API_ENDPOINTS = { + USERS: '/api/users', + ORDERS: '/api/orders', +} as const; + +// ✅ Types and Classes - PascalCase +class UserService {} +type UserAccountData = {}; +type ButtonProps = {}; // Component props suffix with 'Props' + +// ✅ Files and directories - kebab-case +// user-profile.component.tsx +// user-profile.styles.ts +``` + +## Import Organization +```typescript +// ✅ Correct import order +// 1. External libraries +import React from 'react'; +import { useCallback } from 'react'; +import styled from 'styled-components'; + +// 2. Internal modules (absolute paths) +import { Button } from '@/components/ui'; +import { UserService } from '@/services'; + +// 3. Relative imports +import { UserCardProps } from './types'; +``` + +## Function Structure +```typescript +// ✅ Small, focused functions +// ✅ Required parameters first, optional last +const processUserData = ( + user: User, + options: ProcessingOptions, + callback?: (result: ProcessedUser) => void +): ProcessedUser => { + const processedUser = transformUserData(user); + applyOptions(processedUser, options); + + if (callback) { + callback(processedUser); + } + + return processedUser; +}; +``` + +## Comments +```typescript +// ✅ Explain business logic and non-obvious intentions +// Apply 15% discount for premium users with orders > $100 +const discount = isPremiumUser && orderTotal > 100 ? 0.15 : 0; + +// TODO: Replace with proper authentication service +const isAuthenticated = localStorage.getItem('token') !== null; + +/** + * JSDoc for public APIs + * @param basePrice - The base price before modifications + * @returns The final price after tax and discount + */ +const calculateTotalPrice = (basePrice: number): number => { + // Implementation +}; +``` + +## Error Handling +```typescript +// ✅ Proper error types and meaningful messages +try { + const user = await userService.findById(userId); + if (!user) { + throw new UserNotFoundError(`User with ID ${userId} not found`); + } + return user; +} catch (error) { + logger.error('Failed to fetch user', { userId, error }); + throw error; +} +``` diff --git a/.cursor/rules/file-structure-guidelines.md b/.cursor/rules/file-structure-guidelines.md deleted file mode 100644 index 617b9b5e6..000000000 --- a/.cursor/rules/file-structure-guidelines.md +++ /dev/null @@ -1,207 +0,0 @@ -# File Structure Guidelines - -## Core File Structure Principles -Twenty follows a modular and organized file structure that promotes maintainability and scalability. This document outlines our file organization conventions and best practices. - -## Component Organization - -### One Component Per File -- Each component should have its own file -- File name should match component name - ```typescript - // ✅ Correct - // UserProfile.tsx - export const UserProfile = () => { - return
...
; - }; - - // ❌ Incorrect - // users.tsx - export const UserProfile = () => { - return
...
; - }; - - export const UserList = () => { - return
...
; - }; - ``` - -## Directory Structure - -### Feature Modules -- Place features in `modules/` directory -- Group related components and logic - ``` - modules/ - ├── users/ - │ ├── components/ - │ │ ├── UserList.tsx - │ │ ├── UserCard.tsx - │ │ └── UserProfile.tsx - │ ├── hooks/ - │ │ └── useUser.ts - │ ├── states/ - │ │ └── userStates.ts - │ └── types/ - │ └── user.ts - ├── workspace/ - │ ├── components/ - │ ├── hooks/ - │ └── states/ - └── settings/ - ├── components/ - ├── hooks/ - └── states/ - ``` - -### Hooks Organization -- Place hooks in `hooks/` directory -- Group by feature or global usage - ``` - hooks/ - ├── useClickOutside.ts - ├── useDebounce.ts - └── features/ - ├── users/ - │ ├── useUserActions.ts - │ └── useUserData.ts - └── workspace/ - └── useWorkspaceSettings.ts - ``` - -### State Management -- Place state definitions in `states/` directory -- Organize by feature - ``` - states/ - ├── global/ - │ ├── theme.ts - │ └── navigation.ts - ├── users/ - │ ├── atoms.ts - │ └── selectors.ts - └── workspace/ - ├── atoms.ts - └── selectors.ts - ``` - -### Types Organization -- Place types in `types/` directory -- Group by domain or feature - ``` - types/ - ├── common.ts - ├── api.ts - └── features/ - ├── user.ts - ├── workspace.ts - └── settings.ts - ``` - -## Naming Conventions - -### Component Files -- Use PascalCase for component files -- Use descriptive, feature-specific names - ``` - components/ - ├── UserProfile.tsx - ├── UserProfileHeader.tsx - └── UserProfileContent.tsx - ``` - -### Non-Component Files -- Use camelCase for non-component files -- Use clear, descriptive names - ``` - hooks/ - ├── useClickOutside.ts - └── useDebounce.ts - - utils/ - ├── dateFormatter.ts - └── stringUtils.ts - ``` - -## Module Structure - -### Feature Module Organization -- Consistent structure across features -- Clear separation of concerns - ``` - modules/users/ - ├── components/ - │ ├── UserList/ - │ │ ├── UserList.tsx - │ │ ├── UserListItem.tsx - │ │ └── UserListHeader.tsx - │ └── UserProfile/ - │ ├── UserProfile.tsx - │ └── UserProfileHeader.tsx - ├── hooks/ - │ ├── useUserList.ts - │ └── useUserProfile.ts - ├── states/ - │ ├── atoms.ts - │ └── selectors.ts - ├── types/ - │ └── user.ts - └── utils/ - └── userFormatter.ts - ``` - -## Best Practices - -### Import Organization -- Group imports by type -- Maintain consistent order - ```typescript - // External dependencies - import { useState } from 'react'; - import { styled } from '@emotion/styled'; - - // Internal modules - import { useUser } from '~/modules/users/hooks'; - import { userState } from '~/modules/users/states'; - - // Local imports - import { UserAvatar } from './UserAvatar'; - import { type UserProfileProps } from './types'; - ``` - -### Path Aliases -- Use path aliases for better imports -- Avoid deep relative paths - ```typescript - // ✅ Correct - import { Button } from '~/components/Button'; - import { useUser } from '~/modules/users/hooks'; - - // ❌ Incorrect - import { Button } from '../../../components/Button'; - import { useUser } from '../../../modules/users/hooks'; - ``` - -### Component Co-location -- Keep related files close together -- Use index files for public APIs - ``` - components/UserProfile/ - ├── UserProfile.tsx - ├── UserProfileHeader.tsx - ├── UserProfileContent.tsx - ├── styles.ts - ├── types.ts - └── index.ts - ``` - -### Test File Location -- Place test files next to implementation -- Use `.test.ts` or `.spec.ts` extension - ``` - components/ - ├── UserProfile.tsx - ├── UserProfile.test.tsx - ├── UserProfile.stories.tsx - └── types.ts - ``` \ No newline at end of file diff --git a/.cursor/rules/file-structure.mdc b/.cursor/rules/file-structure.mdc new file mode 100644 index 000000000..fbdf6b502 --- /dev/null +++ b/.cursor/rules/file-structure.mdc @@ -0,0 +1,107 @@ +--- +description: +globs: +alwaysApply: false +--- +# File Structure Guidelines + +## Directory Organization +``` +packages/twenty-front/src/ +├── components/ # Reusable UI components +├── pages/ # Route components +├── modules/ # Feature modules +├── hooks/ # Custom hooks +├── services/ # API services +└── types/ # Type definitions + +packages/twenty-server/src/ +├── modules/ # Feature modules +├── entities/ # Database entities +├── dto/ # Data transfer objects +└── utils/ # Helper functions +``` + +## File Naming +- **kebab-case** for all files and directories +- **Descriptive suffixes** for clarity +``` +// ✅ Correct naming +user-profile.component.tsx +user-profile.styles.ts +user-profile.test.tsx +user.service.ts +user.entity.ts +create-user.dto.ts +``` + +## Index Files & Barrel Exports +```typescript +// ✅ Clean barrel exports in index.ts +export { UserCard } from './user-card.component'; +export { UserList } from './user-list.component'; +export type { UserCardProps, UserListProps } from './types'; + +// ✅ Usage - clean imports +import { UserCard, UserList } from '@/components/user'; +``` + +## Module Structure +``` +src/modules/user/ +├── components/ # Module-specific components +├── hooks/ # Module hooks +├── services/ # API services +├── types/ # Type definitions +└── index.ts # Module exports +``` + +## Import/Export Patterns +```typescript +// ✅ Import organization +// 1. External libraries +import React from 'react'; +import styled from 'styled-components'; + +// 2. Internal modules (absolute paths) +import { Button } from '@/components/ui'; +import { UserService } from '@/services'; + +// 3. Relative imports +import { UserCardProps } from './types'; + +// ✅ Named exports only (no default exports) +export const UserComponent = ({ user }: UserProps) => { + // Component implementation +}; +``` + +## File Size Guidelines +- **Components**: Under 300 lines +- **Services**: Under 500 lines +- **Extract logic** into hooks/utilities when files grow large +- **Use composition** over large monolithic components + +## Configuration Files + +### Project Configuration +``` +.vscode/ # VSCode settings +├── settings.json +├── extensions.json +└── launch.json + +.github/ # GitHub workflows +├── workflows/ +└── templates/ + +.cursor/ # Cursor rules +├── rules/ +└── environment.json +``` + +### Build Configuration +- Keep build configs in root or package directories +- Use consistent naming for config files +- Comment complex configurations +- Version control all configuration files diff --git a/.cursor/rules/nx-rules.mdc b/.cursor/rules/nx-rules.mdc index 1f10d0cb3..613a579b5 100644 --- a/.cursor/rules/nx-rules.mdc +++ b/.cursor/rules/nx-rules.mdc @@ -3,31 +3,81 @@ description: Guidelines and best practices for working with Nx in the Twenty wor globs: alwaysApply: false --- +--- +description: Guidelines and best practices for working with Nx in the Twenty workspace, including workspace architecture understanding, configuration management, and generator usage. +globs: ["**/nx.json", "**/project.json", "**/workspace.json"] +alwaysApply: false +--- -// This file is automatically generated by Nx Console +# Nx Guidelines -You are in an nx workspace using Nx 18.3.3 and yarn as the package manager. +## Core Commands +```bash +# Run target for specific project +npx nx run twenty-front:build +npx nx run twenty-server:test -You have access to the Nx MCP server and the tools it provides. Use them. Follow these guidelines in order to best help the user: +# Run target for all projects +npx nx run-many --target=build --all +npx nx run-many --target=test --projects=twenty-front,twenty-server -# General Guidelines -- When answering questions, use the nx_workspace tool first to gain an understanding of the workspace architecture -- For questions around nx configuration, best practices or if you're unsure, use the nx_docs tool to get relevant, up-to-date docs!! Always use this instead of assuming things about nx configuration -- If the user needs help with an Nx configuration or project graph error, use the 'nx_workspace' tool to get any errors -- To help answer questions about the workspace structure or simply help with demonstrating how tasks depend on each other, use the 'nx_visualize_graph' tool +# Generate/modify projects +npx nx g @nx/react:app my-app +npx nx g @nx/react:component my-component +``` + +## Project Structure +- Each package has a `project.json` with targets +- Dependencies managed through `tsconfig.json` path mappings +- Shared libraries in `packages/` directory + +## Build Targets +```json +// project.json +{ + "targets": { + "build": { + "executor": "@nx/vite:build", + "options": { "outputPath": "dist/packages/twenty-front" } + }, + "test": { + "executor": "@nx/jest:jest", + "options": { "jestConfig": "packages/twenty-front/jest.config.ts" } + } + } +} +``` + +## Dependency Graph +```bash +# View project dependencies +npx nx graph + +# Check what's affected by changes +npx nx affected --target=test +npx nx affected --target=build --base=main +``` + +## Library Management +- Use `npx nx g @nx/workspace:library` generator for shared libs +- Internal imports use `@/` path mapping +- Libraries must export through index.ts barrel files + +## Cache Configuration +- Nx caches build outputs and test results +- Configure `outputs` in project.json targets +- Use `inputs` to define what invalidates cache + +```json +{ + "build": { + "outputs": ["dist/packages/my-app"], + "inputs": ["source", "^source"], + "cache": true + } +} +``` -# Generation Guidelines -If the user wants to generate something, use the following flow: -- learn about the nx workspace and any specifics the user needs by using the 'nx_workspace' tool and the 'nx_project_details' tool if applicable -- get the available generators using the 'nx_generators' tool -- decide which generator to use. If no generators seem relevant, check the 'nx_available_plugins' tool to see if the user could install a plugin to help them -- get generator details using the 'nx_generator_schema' tool -- you may use the 'nx_docs' tool to learn more about a specific generator or technology if you're unsure -- decide which options to provide in order to best complete the user's request. Don't make any assumptions and keep the options minimalistic -- open the generator UI using the 'nx_open_generate_ui' tool -- wait for the user to finish the generator -- read the generator log file using the 'nx_read_generator_log' tool -- use the information provided in the log file to answer the user's question or continue with what they were doing diff --git a/.cursor/rules/react-general-guidelines.md b/.cursor/rules/react-general-guidelines.md deleted file mode 100644 index 13d739cf1..000000000 --- a/.cursor/rules/react-general-guidelines.md +++ /dev/null @@ -1,220 +0,0 @@ -# React Guidelines - -## Core React Principles -Twenty follows modern React best practices with a focus on functional components and clean, maintainable code. This document outlines our React conventions and best practices. - -## Component Structure - -### Functional Components Only -- Use functional components exclusively -- No class components allowed - ```typescript - // ✅ Correct - export const UserProfile = ({ user }: UserProfileProps) => { - return ( - -

{user.name}

-
- ); - }; - - // ❌ Incorrect - export class UserProfile extends React.Component { - render() { - return ( - -

{this.props.user.name}

-
- ); - } - } - ``` - -### Named Exports -- Use named exports exclusively -- No default exports - ```typescript - // ✅ Correct - export const Button = ({ label }: ButtonProps) => { - return ; - }; - - // ❌ Incorrect - export default function Button({ label }: ButtonProps) { - return ; - } - ``` - -## State and Effects - -### Event Handlers Over useEffect -- Prefer event handlers for state updates -- Avoid useEffect for state synchronization - ```typescript - // ✅ Correct - const UserForm = () => { - const handleSubmit = async (data: FormData) => { - await updateUser(data); - refreshUserList(); - }; - - return
; - }; - - // ❌ Incorrect - const UserForm = () => { - useEffect(() => { - if (formData) { - updateUser(formData); - } - }, [formData]); - - return ; - }; - ``` - -## Component Design - -### Small, Focused Components -- Keep components small and single-purpose -- Extract reusable logic into custom hooks - ```typescript - // ✅ Correct - const UserCard = ({ user }: UserCardProps) => { - return ( - - - - - - ); - }; - - // ❌ Incorrect - const UserCard = ({ user }: UserCardProps) => { - return ( - - {/* Too much logic in one component */} - -
{user.name}
-
{user.email}
- - - {/* More complex logic... */} -
- ); - }; - ``` - -## Props - -### Prop Naming -- Use clear, descriptive prop names -- Follow React conventions (onClick, isActive, etc.) - ```typescript - // ✅ Correct - type ButtonProps = { - onClick: () => void; - isDisabled?: boolean; - isLoading?: boolean; - }; - - // ❌ Incorrect - type ButtonProps = { - clickHandler: () => void; - disabled?: boolean; - loading?: boolean; - }; - ``` - -### Prop Destructuring -- Destructure props with proper typing -- Use TypeScript for prop types - ```typescript - // ✅ Correct - const Button = ({ onClick, isDisabled, children }: ButtonProps) => { - return ( - - ); - }; - - // ❌ Incorrect - const Button = (props: ButtonProps) => { - return ( - - ); - }; - ``` - -## Performance Optimization - -### Memoization -- Use memo for expensive computations -- Avoid premature optimization - ```typescript - // ✅ Correct - Complex computation - const MemoizedChart = memo(({ data }: ChartProps) => { - // Complex rendering logic - return ; - }); - - // ❌ Incorrect - Unnecessary memoization - const MemoizedText = memo(({ text }: { text: string }) => { - return {text}; - }); - ``` - -### Event Handlers -- Use callback refs for DOM manipulation -- Memoize callbacks when needed - ```typescript - // ✅ Correct - const UserList = () => { - const handleScroll = useCallback((event: UIEvent) => { - // Complex scroll handling - }, []); - - return
{/* content */}
; - }; - ``` - -## Error Handling - -### Error Boundaries -- Use error boundaries for component error handling -- Provide meaningful fallback UIs - ```typescript - // ✅ Correct - const ErrorFallback = ({ error }: { error: Error }) => ( - -

Something went wrong

-
{error.message}
-
- ); - - const SafeComponent = () => ( - - - - ); - ``` - -### Loading States -- Handle loading states gracefully -- Provide meaningful loading indicators - ```typescript - // ✅ Correct - const UserProfile = () => { - const { data: user, isLoading, error } = useUser(); - - if (isLoading) return ; - if (error) return ; - if (!user) return ; - - return ; - }; - ``` \ No newline at end of file diff --git a/.cursor/rules/react-general-guidelines.mdc b/.cursor/rules/react-general-guidelines.mdc new file mode 100644 index 000000000..30575cdc1 --- /dev/null +++ b/.cursor/rules/react-general-guidelines.mdc @@ -0,0 +1,86 @@ +--- +description: +globs: +alwaysApply: false +--- +# React Guidelines + +## Core Rules +- **Functional components only** (no classes) +- **Named exports only** (no default exports) +- **Event handlers over useEffect** for state updates + +## Component Structure +```typescript +// ✅ Correct +export const UserProfile = ({ user, onEdit }: UserProfileProps) => { + const handleEdit = () => onEdit(user.id); + + return ( + +

{user.name}

+ +
+ ); +}; +``` + +## Props & Event Handlers +```typescript +// ✅ Correct - Destructure props +const Button = ({ onClick, isDisabled, children }: ButtonProps) => ( + +); + +// ✅ Correct - Event handlers over useEffect +const UserForm = ({ onSubmit }: UserFormProps) => { + const handleSubmit = async (data: FormData) => { + await onSubmit(data); + // Direct event handling, not useEffect + }; + + return ; +}; +``` + +## Component Design +- **Small, focused components** - Single responsibility +- **Composition over inheritance** - Combine simple components +- **Extract complex logic** into custom hooks + +```typescript +// ✅ Good - Composed from smaller components +const UserCard = ({ user }: UserCardProps) => ( + + + + + +); +``` + +## Performance +```typescript +// ✅ Use memo for expensive components only +const ExpensiveChart = memo(({ data }: ChartProps) => { + // Complex rendering logic + return ; +}); + +// ✅ Memoize callbacks when needed +const UserList = ({ users, onUserSelect }: UserListProps) => { + const handleUserSelect = useCallback((user: User) => { + onUserSelect(user); + }, [onUserSelect]); + + return ( +
+ {users.map(user => ( + + ))} +
+ ); +}; +``` diff --git a/.cursor/rules/react-state-management-guidelines.md b/.cursor/rules/react-state-management-guidelines.md deleted file mode 100644 index 5eb97b24a..000000000 --- a/.cursor/rules/react-state-management-guidelines.md +++ /dev/null @@ -1,219 +0,0 @@ -# State Management Guidelines - -## Core State Management Principles -Twenty uses a combination of Recoil for global state and Apollo Client for server state management. This document outlines our state management conventions and best practices. - -## Global State Management - -### Recoil Usage -- Use Recoil for global application state -- Keep atoms small and focused - ```typescript - // ✅ Correct - // states/theme.ts - export const themeState = atom<'light' | 'dark'>({ - key: 'themeState', - default: 'light', - }); - - // states/user.ts - export const userState = atom({ - key: 'userState', - default: null, - }); - - // ❌ Incorrect - // states/globalState.ts - export const globalState = atom({ - key: 'globalState', - default: { - theme: 'light', - user: null, - settings: {}, - // ... many other unrelated pieces of state - }, - }); - ``` - -### Atom Organization -- Place atoms in the `states/` directory -- Group related atoms in feature-specific files - ```typescript - // states/workspace/atoms.ts - export const workspaceIdState = atom({ - key: 'workspaceIdState', - default: '', - }); - - export const workspaceSettingsState = atom({ - key: 'workspaceSettingsState', - default: defaultSettings, - }); - ``` - -## Server State Management - -### Apollo Client Usage -- Use Apollo Client for all GraphQL operations -- Leverage Apollo's caching capabilities - ```typescript - // ✅ Correct - const { data, loading } = useQuery(GET_USER_QUERY, { - variables: { id }, - fetchPolicy: 'cache-first', - }); - - // ❌ Incorrect - const [user, setUser] = useState(null); - useEffect(() => { - fetch('/api/user/' + id).then(setUser); - }, [id]); - ``` - -### Query Organization -- Separate operation files -- Use fragments for shared fields - ```typescript - // queries/user.ts - export const UserFragment = gql` - fragment UserFields on User { - id - name - email - } - `; - - export const GET_USER = gql` - query GetUser($id: UUID!) { - user(id: $id) { - ...UserFields - } - } - ${UserFragment} - `; - ``` - -## State Management Best Practices - -### Multiple Small Atoms -- Prefer multiple small atoms over prop drilling -- Keep atoms focused on specific features - ```typescript - // ✅ Correct - export const selectedViewState = atom({ - key: 'selectedViewState', - default: '', - }); - - export const viewFiltersState = atom({ - key: 'viewFiltersState', - default: {}, - }); - - // ❌ Incorrect - Prop drilling - const ViewContainer = ({ selectedView, filters, onViewChange }) => { - return ( - - - - - - ); - }; - ``` - -### No useRef for State -- Never use useRef for state management -- Use proper state management tools - ```typescript - // ✅ Correct - const [count, setCount] = useState(0); - // or - const [count, setCount] = useRecoilState(countState); - - // ❌ Incorrect - const countRef = useRef(0); - ``` - -### Data Fetching -- Extract data fetching to sibling components -- Keep components focused on presentation - ```typescript - // ✅ Correct - const UserProfileContainer = () => { - const { data, loading } = useQuery(GET_USER); - if (loading) return ; - return ; - }; - - const UserProfile = ({ user }: UserProfileProps) => { - return
{user.name}
; - }; - - // ❌ Incorrect - const UserProfile = () => { - const { data, loading } = useQuery(GET_USER); - if (loading) return ; - return
{data.user.name}
; - }; - ``` - -### Hook Usage -- Use appropriate hooks for state access -- Choose between useRecoilValue and useRecoilState based on needs - ```typescript - // ✅ Correct - Read-only access - const theme = useRecoilValue(themeState); - - // ✅ Correct - Read-write access - const [theme, setTheme] = useRecoilState(themeState); - - // ❌ Incorrect - Using state setter when only reading - const [theme, _] = useRecoilState(themeState); - ``` - -## Performance Considerations - -### Selector Usage -- Use selectors for derived state -- Memoize complex calculations - ```typescript - // ✅ Correct - const filteredUsersState = selector({ - key: 'filteredUsersState', - get: ({ get }) => { - const users = get(usersState); - const filter = get(userFilterState); - return users.filter(user => - user.name.toLowerCase().includes(filter.toLowerCase()) - ); - }, - }); - - // ❌ Incorrect - Calculating in component - const UserList = () => { - const users = useRecoilValue(usersState); - const filter = useRecoilValue(userFilterState); - const filteredUsers = users.filter(user => - user.name.toLowerCase().includes(filter.toLowerCase()) - ); - return ; - }; - ``` - -### Cache Management -- Configure appropriate cache policies -- Handle cache invalidation properly - ```typescript - // ✅ Correct - const [updateUser] = useMutation(UPDATE_USER, { - update: (cache, { data }) => { - cache.modify({ - id: cache.identify(data.updateUser), - fields: { - name: () => data.updateUser.name, - }, - }); - }, - }); - ``` \ No newline at end of file diff --git a/.cursor/rules/react-state-management.mdc b/.cursor/rules/react-state-management.mdc new file mode 100644 index 000000000..b68f9e5a3 --- /dev/null +++ b/.cursor/rules/react-state-management.mdc @@ -0,0 +1,81 @@ +--- +description: +globs: +alwaysApply: false +--- +# React State Management + +## Recoil Patterns +```typescript +// ✅ Atoms for primitive state +export const currentUserState = atom({ + key: 'currentUserState', + default: null, +}); + +// ✅ Selectors for derived state +export const userDisplayNameSelector = selector({ + key: 'userDisplayNameSelector', + get: ({ get }) => { + const user = get(currentUserState); + return user ? `${user.firstName} ${user.lastName}` : 'Guest'; + }, +}); + +// ✅ Atom families for dynamic atoms +export const userByIdState = atomFamily({ + key: 'userByIdState', + default: null, +}); +``` + +## Local State Guidelines +```typescript +// ✅ Multiple useState for unrelated state +const [isLoading, setIsLoading] = useState(false); +const [error, setError] = useState(null); +const [data, setData] = useState([]); + +// ✅ useReducer for complex state logic +type FormAction = + | { type: 'SET_FIELD'; field: string; value: string } + | { type: 'SET_ERRORS'; errors: Record } + | { type: 'RESET' }; + +const formReducer = (state: FormState, action: FormAction): FormState => { + switch (action.type) { + case 'SET_FIELD': + return { ...state, [action.field]: action.value }; + case 'SET_ERRORS': + return { ...state, errors: action.errors }; + case 'RESET': + return initialFormState; + default: + return state; + } +}; +``` + +## Data Flow Rules +- **Props down, events up** - Unidirectional data flow +- **Avoid bidirectional binding** - Use callback functions +- **Normalize complex data** - Use lookup tables over nested objects + +```typescript +// ✅ Normalized state structure +type UsersState = { + byId: Record; + allIds: string[]; +}; + +// ✅ Functional state updates +const increment = useCallback(() => { + setCount(prev => prev + 1); +}, []); +``` + +## Performance Tips +- Use atom families for dynamic data collections +- Implement proper selector caching +- Avoid heavy computations in selectors +- Batch state updates when possible diff --git a/.cursor/rules/testing-guidelines.md b/.cursor/rules/testing-guidelines.md deleted file mode 100644 index 4ee86d6d1..000000000 --- a/.cursor/rules/testing-guidelines.md +++ /dev/null @@ -1,253 +0,0 @@ -# Testing Guidelines - -## Core Testing Principles -Twenty follows a comprehensive testing strategy across all packages, ensuring high-quality, maintainable code. This document outlines our testing conventions and best practices. - -## Testing Stack - -### Backend Testing -- Primary Framework: Jest -- API Testing: Supertest -- Coverage Requirements: 80% minimum - -### Frontend Testing -- Component Testing: Jest + React Testing Library -- Visual Testing: Storybook -- API Mocking: MSW (Mock Service Worker) - -### End-to-End Testing -- Framework: Playwright -- Coverage: Critical user journeys -- Cross-browser testing - -## Test Organization - -### Test File Location -- Co-locate tests with implementation files -- Use consistent naming patterns - ``` - src/ - ├── components/ - │ ├── UserProfile.tsx - │ ├── UserProfile.test.tsx - │ └── UserProfile.stories.tsx - ``` - -### Test File Naming -- Use `.test.ts(x)` for unit/integration tests -- Use `.spec.ts(x)` for E2E tests -- Use `.stories.tsx` for Storybook stories - -## Unit Testing - -### Component Testing -- Test behavior, not implementation -- Use React Testing Library best practices - ```typescript - // ✅ Correct - test('displays user name when provided', () => { - render(); - expect(screen.getByText('John Doe')).toBeInTheDocument(); - }); - - // ❌ Incorrect - Testing implementation details - test('sets the text content', () => { - const { container } = render(); - expect(container.querySelector('h1').textContent).toBe('John Doe'); - }); - ``` - -### Hook Testing -- Use `renderHook` from @testing-library/react-hooks -- Test all possible states - ```typescript - // ✅ Correct - test('useUser hook manages user state', () => { - const { result } = renderHook(() => useUser()); - - act(() => { - result.current.setUser({ id: '1', name: 'John' }); - }); - - expect(result.current.user).toEqual({ id: '1', name: 'John' }); - }); - ``` - -### Mocking -- Mock external dependencies -- Use jest.mock for module mocking - ```typescript - // ✅ Correct - jest.mock('~/services/api', () => ({ - fetchUser: jest.fn().mockResolvedValue({ id: '1', name: 'John' }), - })); - - test('fetches and displays user', async () => { - render(); - expect(await screen.findByText('John')).toBeInTheDocument(); - }); - ``` - -## Integration Testing - -### API Testing -- Test complete request/response cycles -- Use Supertest for backend API testing - ```typescript - // ✅ Correct - describe('GET /api/users/:id', () => { - it('returns user when found', async () => { - const response = await request(app) - .get('/api/users/1') - .expect(200); - - expect(response.body).toEqual({ - id: '1', - name: 'John Doe', - }); - }); - - it('returns 404 when user not found', async () => { - await request(app) - .get('/api/users/999') - .expect(404); - }); - }); - ``` - -## E2E Testing - -### Test Structure -- Organize by user journey -- Use page objects for reusability - ```typescript - // pages/login.ts - export class LoginPage { - async login(email: string, password: string) { - await this.page.fill('[data-testid="email-input"]', email); - await this.page.fill('[data-testid="password-input"]', password); - await this.page.click('[data-testid="login-button"]'); - } - } - - // tests/auth.spec.ts - test('user can login successfully', async ({ page }) => { - const loginPage = new LoginPage(page); - await loginPage.login('user@example.com', 'password'); - await expect(page).toHaveURL('/dashboard'); - }); - ``` - -### Test Data -- Use dedicated test environments -- Reset state between tests - ```typescript - // ✅ Correct - beforeEach(async () => { - await resetDatabase(); - await seedTestData(); - }); - - test('user workflow', async ({ page }) => { - // Test with clean, predictable state - }); - ``` - -## Visual Testing - -### Storybook Guidelines -- Create stories for all components -- Document component variants - ```typescript - // Button.stories.tsx - export default { - title: 'Components/Button', - component: Button, - } as Meta; - - export const Primary = { - args: { - variant: 'primary', - label: 'Primary Button', - }, - }; - - export const Secondary = { - args: { - variant: 'secondary', - label: 'Secondary Button', - }, - }; - ``` - -### Visual Regression -- Use Storybook's visual regression testing -- Review changes carefully - ```typescript - // jest.config.js - module.exports = { - preset: 'jest-image-snapshot', - setupFilesAfterEnv: ['/setup-tests.ts'], - }; - - // Button.visual.test.tsx - describe('Button', () => { - it('matches visual snapshot', async () => { - const image = await page.screenshot(); - expect(image).toMatchImageSnapshot(); - }); - }); - ``` - -## Test Quality - -### Test Data Attributes -- Use data-testid for test selectors -- Avoid selecting by CSS classes - ```typescript - // ✅ Correct - - - // In tests - const button = screen.getByTestId('submit-button'); - - // ❌ Incorrect - const button = container.querySelector('.submit-btn'); - ``` - -### Assertion Best Practices -- Use explicit assertions -- Test both positive and negative cases - ```typescript - // ✅ Correct - test('form validation', async () => { - render(); - - // Negative case - await userEvent.click(screen.getByText('Submit')); - expect(screen.getByText('Name is required')).toBeInTheDocument(); - - // Positive case - await userEvent.type(screen.getByLabelText('Name'), 'John Doe'); - await userEvent.click(screen.getByText('Submit')); - expect(screen.queryByText('Name is required')).not.toBeInTheDocument(); - }); - ``` - -### Coverage Requirements -- A new feature should have at least 80% coverage -- Focus on critical paths -- Run coverage reports in CI - ```typescript - // jest.config.js - module.exports = { - coverageThreshold: { - global: { - statements: 80, - branches: 80, - functions: 80, - lines: 80, - }, - }, - }; - ``` \ No newline at end of file diff --git a/.cursor/rules/testing-guidelines.mdc b/.cursor/rules/testing-guidelines.mdc new file mode 100644 index 000000000..50a58afb0 --- /dev/null +++ b/.cursor/rules/testing-guidelines.mdc @@ -0,0 +1,89 @@ +--- +description: +globs: +alwaysApply: false +--- +# Testing Guidelines + +## Test Structure (AAA Pattern) +```typescript +describe('UserService', () => { + describe('when getting user by ID', () => { + it('should return user data for valid ID', async () => { + // Arrange + const userId = '123'; + const expectedUser = { id: '123', name: 'John' }; + mockUserRepository.findById.mockResolvedValue(expectedUser); + + // Act + const result = await userService.getUserById(userId); + + // Assert + expect(result).toEqual(expectedUser); + }); + }); +}); +``` + +## React Component Testing +```typescript +// ✅ Test user behavior, not implementation +describe('LoginForm', () => { + it('should display error message for invalid credentials', async () => { + const mockOnSubmit = jest.fn().mockRejectedValue(new Error('Invalid credentials')); + render(); + + await user.type(screen.getByLabelText(/email/i), 'invalid@example.com'); + await user.type(screen.getByLabelText(/password/i), 'wrongpassword'); + await user.click(screen.getByRole('button', { name: /sign in/i })); + + expect(await screen.findByText(/invalid credentials/i)).toBeInTheDocument(); + }); +}); +``` + +## Mocking Patterns +```typescript +// ✅ Service mocking +const mockEmailService = { + sendEmail: jest.fn().mockResolvedValue({ success: true }), + validateEmail: jest.fn().mockReturnValue(true), +}; + +// ✅ Test data factories +const createTestUser = (overrides = {}) => ({ + id: uuid(), + email: 'test@example.com', + name: 'Test User', + ...overrides, +}); + +beforeEach(() => { + jest.clearAllMocks(); +}); +``` + +## Testing Principles +- **Test behavior, not implementation** - Focus on what users see/do +- **Use descriptive test names** - "should [behavior] when [condition]" +- **Query by user-visible elements** - text, roles, labels over test IDs +- **Keep tests isolated** - Independent and repeatable +- **70% unit, 20% integration, 10% E2E** - Test pyramid + +## Common Patterns +```typescript +// Async testing +await waitFor(() => { + expect(screen.getByText('Loading...')).not.toBeInTheDocument(); +}); + +// User interactions +await user.click(screen.getByRole('button')); +await user.type(screen.getByLabelText(/search/i), 'query'); + +// API integration tests +const response = await request(app) + .post('/api/users') + .send(userData) + .expect(201); +``` diff --git a/.cursor/rules/translations.md b/.cursor/rules/translations.md deleted file mode 100644 index e04e75c99..000000000 --- a/.cursor/rules/translations.md +++ /dev/null @@ -1,162 +0,0 @@ -# Translation Guidelines - -## Core Translation Principles -Twenty uses Lingui for internationalization (i18n) and Crowdin for translation management. This document outlines our translation workflow and best practices. - -## Technology Stack - -### Translation Tools -- **Framework**: @lingui/react -- **Translation Management**: Crowdin -- **Workflow**: GitHub Actions for automation - -### Package Structure -Translation files are managed in multiple packages: -- `twenty-front`: Frontend translations -- `twenty-server`: Backend translations -- `twenty-emails`: Email template translations - -## Translation Process - -### Adding New Strings - -#### Using Lingui Macros -- Use `` for components -- Use `t` macro for strings outside JSX - ```typescript - // ✅ Correct - In JSX - import { Trans } from '@lingui/react/macro'; - - const WelcomeMessage = () => ( -

- Welcome to Twenty -

- ); - - // ✅ Correct - Outside JSX - import { t } from '@lingui/react/macro'; - - const getMessage = () => { - return t`Welcome to Twenty`; - }; - - // ❌ Incorrect - Don't use raw strings - const WelcomeMessage = () => ( -

Welcome to Twenty

- ); - ``` - -### String Guidelines - -#### What to Translate -- User interface text -- Error messages -- Notifications -- Email content - -#### What Not to Translate -- Variables -- Test data/mocks - -### Translation Workflow - -#### 1. Extracting Translations -- Automatically triggered on main branch changes -- Can be manually triggered in GitHub Actions -- Process: - ```bash - # Extract new strings - nx run twenty-front:lingui:extract - nx run twenty-server:lingui:extract - nx run twenty-emails:lingui:extract - ``` - -#### 2. Translation Management -- Translations are managed in Crowdin -- Changes are synced every 2 hours -- Process: - 1. New strings are uploaded to Crowdin - 2. Translators work on translations - 3. Translations are pulled back to the repository - -#### 3. Compiling Translations -- Happens automatically in CI/CD -- Required before running the application - ```bash - # Compile translations - nx run twenty-front:lingui:compile - nx run twenty-server:lingui:compile - nx run twenty-emails:lingui:compile - ``` - -## Best Practices - -### String Management - -#### Use Placeholders -- Use placeholders for dynamic content - ```typescript - // ✅ Correct - Hello {userName}, - - // ❌ Incorrect - String concatenation - Hello {userName}, - ``` - -#### Provide Context -- Lingui provides powerfulway to add context for translators but we don't use them as of today. - -### Code Organization - -#### Translation Files -- Keep translation files organized by feature -- Use consistent naming patterns - ``` - src/ - ├── locales/ - │ ├── en/ - │ │ ├── messages.po - │ │ └── messages.js - │ └── fr/ - │ ├── messages.po - │ └── messages.js - ``` - -### Quality Assurance - -#### Strict Mode -- Use --strict mode when compiling to identify missing translations - - -#### Testing Translations -- Test with different locales -- Verify string interpolation -- Check layout with different language lengths - -## Automation - -### GitHub Actions - -#### Pull Workflow -- Runs every 2 hours -- Downloads new translations from Crowdin -- Creates PR if changes detected -- Can be manually triggered with force pull option - -#### Push Workflow -- Runs on main branch changes -- Extracts and uploads new strings -- Compiles translations -- Creates PR with changes - -### Error Handling - -#### Missing Translations -- Development: Shown in original language -- Production: Falls back to default language -- Strict mode in CI catches missing translations - -#### Compilation Errors -- Addressed before merging -- PR created for fixing missing translations -- Automated testing in CI pipeline \ No newline at end of file diff --git a/.cursor/rules/translations.mdc b/.cursor/rules/translations.mdc new file mode 100644 index 000000000..3f8cb6876 --- /dev/null +++ b/.cursor/rules/translations.mdc @@ -0,0 +1,291 @@ +--- +description: +globs: +alwaysApply: false +--- +# Translation Guidelines + +## Internationalization (i18n) Overview + +### Supported Languages +- English (en) - Primary language +- French (fr) - Secondary language +- German (de) - Planned +- Spanish (es) - Planned +- Additional languages based on community contributions + +### i18n Architecture +- Use react-i18next for React components +- Store translations in JSON files +- Implement namespace-based organization +- Support for interpolation and pluralization + +## File Structure + +### Translation Files +``` +src/locales/ +├── en/ # English translations +│ ├── common.json # Common UI strings +│ ├── auth.json # Authentication strings +│ ├── dashboard.json # Dashboard specific +│ ├── forms.json # Form labels and validation +│ └── errors.json # Error messages +├── fr/ # French translations +│ ├── common.json +│ ├── auth.json +│ └── ... +└── index.ts # i18n configuration +``` + +### Translation Keys +- Use nested objects for organization +- Follow consistent naming patterns +- Include context in key names + ```json + { + "auth": { + "login": { + "title": "Sign In", + "email": "Email Address", + "password": "Password", + "submit": "Sign In", + "forgotPassword": "Forgot Password?" + }, + "register": { + "title": "Create Account", + "confirmPassword": "Confirm Password" + } + } + } + ``` + +## Translation Implementation + +### React Components +- Use useTranslation hook +- Specify namespaces for better organization +- Handle loading states properly + ```typescript + // ✅ Correct + import { useTranslation } from 'react-i18next'; + + const LoginForm = () => { + const { t } = useTranslation('auth'); + + return ( + +

{t('login.title')}

+ + + + + ); + }; + ``` + +### Interpolation +- Use interpolation for dynamic content +- Pass variables through t() function +- Keep interpolation simple and readable + ```typescript + // ✅ Correct + const WelcomeMessage = ({ userName }: { userName: string }) => { + const { t } = useTranslation('common'); + + return ( +

{t('welcome.message', { name: userName })}

+ ); + }; + + // Translation file + { + "welcome": { + "message": "Welcome back, {{name}}!" + } + } + ``` + +### Pluralization +- Handle singular/plural forms correctly +- Use count-based pluralization +- Support different plural rules per language + ```typescript + // ✅ Correct + const ItemCount = ({ count }: { count: number }) => { + const { t } = useTranslation('common'); + + return ( + {t('items.count', { count })} + ); + }; + + // Translation file + { + "items": { + "count_one": "{{count}} item", + "count_other": "{{count}} items" + } + } + ``` + +## Translation Management + +### Adding New Strings +1. Add English translation first +2. Use descriptive keys that indicate context +3. Include comments for translators when needed +4. Test with long translations to ensure UI flexibility + ```json + { + "user": { + "profile": { + // Displayed in user profile header + "displayName": "Display Name", + // Used in forms when editing profile + "editDisplayName": "Edit Display Name", + // Confirmation message after profile update + "updateSuccess": "Profile updated successfully" + } + } + } + ``` + +### Translation Validation +- Use TypeScript for translation key validation +- Implement automated checks for missing translations +- Validate interpolation parameters + ```typescript + // ✅ Correct - Type-safe translations + type TranslationKey = + | 'auth.login.title' + | 'auth.login.email' + | 'auth.login.password' + | 'common.welcome.message'; + + const t = (key: TranslationKey, options?: any) => { + // Translation implementation + }; + ``` + +## Best Practices + +### Key Naming +- Use descriptive, hierarchical keys +- Avoid abbreviations +- Group related translations +- Keep keys consistent across languages + ```json + // ✅ Correct + { + "dashboard": { + "header": { + "title": "Dashboard", + "subtitle": "Welcome to your workspace" + }, + "actions": { + "createNew": "Create New", + "refresh": "Refresh Data", + "export": "Export" + } + } + } + + // ❌ Incorrect + { + "dash_title": "Dashboard", + "newBtn": "New", + "refreshData": "Refresh" + } + ``` + +### String Guidelines +- Write clear, concise text +- Use consistent terminology +- Consider character limits for UI elements +- Avoid concatenating translated strings + ```json + // ✅ Correct + { + "user": { + "status": { + "online": "Online", + "offline": "Offline", + "away": "Away" + } + } + } + + // ❌ Incorrect - Don't concatenate + { + "user": { + "statusPrefix": "User is ", + "statusOnline": "online" + } + } + ``` + +### Context Information +- Provide context for translators +- Include character limits when relevant +- Explain when/where text appears +- Note any technical constraints + ```json + { + "button": { + // Primary action button, max 20 characters + "save": "Save Changes", + // Secondary button in modal footer + "cancel": "Cancel", + // Destructive action, should sound cautious + "delete": "Delete Permanently" + } + } + ``` + +## Workflow + +### Development Process +1. Develop features with English translations +2. Use placeholder keys during development +3. Finalize translation keys before feature completion +4. Add translations to all supported languages +5. Test with different language strings + +### Translation Updates +1. Create translation tasks for new features +2. Provide context and screenshots to translators +3. Review translations for consistency +4. Test UI with translated strings +5. Update documentation when needed + +### Quality Assurance +- Review translations in context +- Test with longest expected translations +- Verify formatting with interpolation +- Check for cultural appropriateness +- Ensure accessibility with screen readers + +## Maintenance + +### Regular Tasks +- Review and update outdated translations +- Check for unused translation keys +- Maintain consistency across languages +- Update translation documentation +- Monitor for missing translations in new features + +### Tools and Automation +- Use automated translation validation +- Implement missing translation detection +- Set up continuous integration checks +- Maintain translation coverage reports +- Use translation management platforms when needed diff --git a/.cursor/rules/typescript-guidelines.md b/.cursor/rules/typescript-guidelines.mdc similarity index 92% rename from .cursor/rules/typescript-guidelines.md rename to .cursor/rules/typescript-guidelines.mdc index 56dcc24dc..27befea35 100644 --- a/.cursor/rules/typescript-guidelines.md +++ b/.cursor/rules/typescript-guidelines.mdc @@ -1,3 +1,14 @@ +--- +description: +globs: +alwaysApply: false +--- +--- +description: TypeScript best practices and conventions for the Twenty codebase, including strict typing, naming conventions, and type safety guidelines. +globs: ["**/*.ts", "**/*.tsx"] +alwaysApply: false +--- + # TypeScript Guidelines ## Core TypeScript Principles @@ -169,4 +180,4 @@ Twenty enforces strict TypeScript usage to ensure type safety and maintainable c type NonNullableProperties = { [P in keyof T]: NonNullable; }; - ``` \ No newline at end of file + ``` diff --git a/packages/twenty-shared/src/utils/validation/__tests__/isValideLocale.test.ts b/packages/twenty-shared/src/utils/validation/__tests__/isValidLocale.test.ts similarity index 100% rename from packages/twenty-shared/src/utils/validation/__tests__/isValideLocale.test.ts rename to packages/twenty-shared/src/utils/validation/__tests__/isValidLocale.test.ts