diff --git a/.agent/rules/end-to-end-tests/end-to-end-tests.md b/.agent/rules/end-to-end-tests/end-to-end-tests.md index f230c165a..7480f5146 100644 --- a/.agent/rules/end-to-end-tests/end-to-end-tests.md +++ b/.agent/rules/end-to-end-tests/end-to-end-tests.md @@ -80,34 +80,59 @@ These rules outline the structure, patterns, and best practices for writing end- - Don't add timeouts to `.click()`, `.waitForSelector()`, etc. - Global timeout configuration is handled in the shared Playwright—don't change this -8. Write deterministic tests for reliable testing: +8. Dropdown Menu Animation Handling: + - When clicking dropdown menu items (DropdownMenu, user profile menu, etc.), use `.dispatchEvent("click")` instead of regular `.click()` + - This is required because the DropdownMenu component has a 100ms animation and under parallel load Firefox doesn't reliably process regular clicks + - Pattern: Click trigger button, wait for menu visible, click menu item with dispatchEvent, verify menu closes + - Always wait for the menu to be visible before clicking: `await expect(page.getByRole("menu")).toBeVisible();` + - Don't use `waitForTimeout()` for menu animations - it causes focus loss which closes the dropdown + +9. Write deterministic tests for reliable testing: - Each test should have a clear, linear flow of actions and assertions - Don't use if statements, custom error handling, or try/catch blocks in tests - Don't use regular expressions in tests—use simple string matching instead -9. What to test: - - Enter invalid values such as empty strings, only whitespace characters, long strings, negative numbers, Unicode, etc. - - Tooltips, keyboard navigation, accessibility, validation messages, translations, responsiveness, etc. +10. What to test: + - Enter invalid values such as empty strings, only whitespace characters, long strings, negative numbers, Unicode, etc. + - Tooltips, keyboard navigation, accessibility, validation messages, translations, responsiveness, etc. -10. Test Fixtures and Page Management: +11. Test Fixtures and Page Management: - Use appropriate fixtures: `{ page }` for basic tests, `{ anonymousPage }` for tests with existing tenant/owner but not logged in, `{ ownerPage }`, `{ adminPage }`, `{ memberPage }` for authenticated tests - Destructure anonymous page data: `const { page, tenant } = anonymousPage; const existingUser = tenant.owner;` - Pre-logged in users (`ownerPage`, `adminPage`, `memberPage`) are isolated between workers and will not conflict between tests - When using pre-logged in users, do not put the tenant or user into an invalid state that could affect other tests -11. Test Data and Constants: +12. Test Data and Constants: - Use underscore separators: `const timeout = 30_000; // 30 seconds` - Generate unique data: `const email = uniqueEmail();` - Use faker.js to generate realistic test data: `const firstName = faker.person.firstName(); const email = faker.internet.email();` - Long string testing: `const longEmail = \`${"a".repeat(90)}@example.com\`; // 101 characters total` -12. Memory Management in End-to-End Tests: +13. Memory Management in End-to-End Tests: - Playwright automatically handles browser context cleanup after tests - Manual cleanup steps are unnecessary—focus on test clarity over micro-optimizations - End-to-End test suites have minimal memory leak concerns due to their limited scope and duration ## Examples +### Dropdown Menu Clicks +```typescript +// ✅ DO: Use dispatchEvent for menu item clicks +await page.getByRole("button", { name: "User profile menu" }).click(); +const menu = page.getByRole("menu"); +await expect(menu).toBeVisible(); +const menuItem = page.getByRole("menuitem", { name: "Log out" }); +await expect(menuItem).toBeVisible(); +await menuItem.dispatchEvent("click"); + +// ❌ DON'T: Use waitForTimeout - causes focus loss and menu closure +await page.getByRole("button", { name: "User profile menu" }).click(); +const menu = page.getByRole("menu"); +await expect(menu).toBeVisible(); +await page.waitForTimeout(200); // Focus is lost, menu closes during wait +await page.getByRole("menuitem", { name: "Log out" }).click(); // Fails - menu is closed +``` + ### ✅ Good Step Naming Examples ```typescript // ✅ DO: Business action + details & expected outcome @@ -126,10 +151,12 @@ await step("Sign up with valid credentials & verify account creation")(async () await step("Update user role to admin & verify permission change")(async () => { const userRow = page.locator("tbody tr").first(); - + await userRow.getByLabel("User actions").click(); - await page.getByRole("menuitem", { name: "Change role" }).click(); - + const menu = page.getByRole("menu"); + await expect(menu).toBeVisible(); + await page.getByRole("menuitem", { name: "Change role" }).dispatchEvent("click"); + await expect(page.getByRole("alertdialog", { name: "Change user role" })).toBeVisible(); })(); ``` diff --git a/.agent/rules/frontend/form-with-validation.md b/.agent/rules/frontend/form-with-validation.md index a9d4ae5c0..69bce6903 100644 --- a/.agent/rules/frontend/form-with-validation.md +++ b/.agent/rules/frontend/form-with-validation.md @@ -1,7 +1,7 @@ --- trigger: glob globs: **/*.tsx -description: Rules for forms with validation using React Aria Components +description: Rules for forms with validation using ShadCN 2.0 components --- # Form With Validation @@ -9,11 +9,11 @@ Guidelines for implementing forms with validation in the frontend, covering UI c ## Implementation -1. Use React Aria Components from `@repo/ui/components` for form elements +1. Use ShadCN components from `@repo/ui/components` for form elements 2. Use `api.useMutation` or TanStack's `useMutation` for form submissions 3. Use the custom `mutationSubmitter` to handle form submission and data mapping 4. Handle validation errors using the `validationErrors` prop from the mutation error -5. Show loading state in submit buttons +5. Show loading state in submit buttons using `disabled={mutation.isPending}` 6. For complex scenarios with multiple API calls, create a custom mutation with a `mutationFn` ## Anti-patterns @@ -66,7 +66,7 @@ export function UserProfileForm({ user }) { defaultValue={user?.title} /> - @@ -105,7 +105,7 @@ function BadUserProfileForm({ user }) { - @@ -160,7 +160,7 @@ export function UserProfileWithAvatarForm({ user, onSuccess, onClose }) { > {/* Form fields */} - diff --git a/.agent/rules/frontend/frontend.md b/.agent/rules/frontend/frontend.md index f93261a11..737801c16 100644 --- a/.agent/rules/frontend/frontend.md +++ b/.agent/rules/frontend/frontend.md @@ -37,6 +37,20 @@ Use browser MCP tools to test at `https://localhost:9000`. Use `UNLOCK` as OTP v - Server state lives in TanStack Query only - Use `queryClient.invalidateQueries()` to refresh data after mutations +4. **ShadCN 2.0 with BaseUI** (not Radix UI): + - **BaseUI** (`@base-ui/react`): Headless primitives providing accessibility and behavior + - **ShadCN 2.0**: Pre-styled components built on BaseUI, using class-variance-authority (cva) + - Install components via `npx shadcn add ` - never copy manually + - After installing: change `@/utils` to `../utils` and rename file to PascalCase (e.g., `button.tsx` to `Button.tsx`) + - **Never use `*:` or `**:` variants**: These Tailwind child/descendant variants generate `:is()` CSS selectors that the module federation CSS scoping plugin cannot scope, causing specificity bugs in production. In shared components, use `[&>*]:` or `[&_*]:` selectors instead. In application code, prefer putting the utility class directly on each child element (e.g., `className="max-sm:grow"` on each button instead of `max-sm:*:grow` on the parent) + - **Focus ring**: Replace ShadCN's default `focus-visible:ring-*` / `focus-visible:border-ring` utilities with `outline-ring focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2`. Use `outline-primary` or `outline-destructive` for colored variants instead of `outline-ring` + - **Apple HIG compliance**: Interactive controls must use CSS variable heights: `h-[var(--control-height)]` (44px default), `h-[var(--control-height-sm)]` (36px), `h-[var(--control-height-xs)]` (28px). For controls like checkboxes/switches where 44px visual size is too large, ensure a 44px minimum tap target using `after:absolute after:-inset-3` + - Import from `@repo/ui/components/`, never from BaseUI directly + - Only create custom components when no ShadCN equivalent exists (edge cases) + - **Cursor pointer**: Replace `cursor-default` with `cursor-pointer` on clickable elements + - **Active state feedback**: Add press feedback to interactive elements using `active:` pseudo-class with background color changes. Buttons/triggers: `active:bg-primary/70` (or variant-specific active backgrounds). Menu/list items: `active:bg-accent`. Small controls (checkbox, radio): `active:border-primary` + - **Use BaseUI `render` prop** to customize underlying elements (not Radix's `asChild`): `}>Close` + ## Implementation 1. Follow these code style and pattern conventions: @@ -51,27 +65,39 @@ Use browser MCP tools to test at `https://localhost:9000`. Use `UNLOCK` as OTP v - Don't use acronyms (e.g., use `errorMessage` not `errMsg`, `button` not `btn`, `authentication` not `auth`) - Prioritize code readability and maintainability - Don't introduce new npm dependencies - - Use React Aria Components instead of native HTML elements like ``, `