diff --git a/.agent/rules/frontend/frontend.md b/.agent/rules/frontend/frontend.md
index 304ce31a54..4df2520b2f 100644
--- a/.agent/rules/frontend/frontend.md
+++ b/.agent/rules/frontend/frontend.md
@@ -49,7 +49,7 @@ Guidelines for frontend TypeScript and React development, including component st
- Use React Aria Components from `@repo/ui/components/ComponentName`:
- Search [Components](/application/shared-webapp/ui/components) when you need to find a component
- Use existing components rather than creating new ones
- - Use `onPress` instead of `onClick` for event handlers
+ - Use `onPress` instead of `onClick` for event handlers (exception: Dialog close button uses `onClick={close}` from render prop)
- Use `onAction` for menu items and list actions
- Use `...` for JSX translations, `t` macro for strings
- Use TanStack Query for API interactions via `api.useQuery()` and `api.useMutation()`
@@ -58,6 +58,9 @@ Guidelines for frontend TypeScript and React development, including component st
- Colocate state with components—don't lift state unnecessarily
- Use `useCallback` and `useMemo` only for proven performance issues
- Throw errors sparingly and ensure error messages include a period
+ - Include appropriate aria labels for accessibility (e.g., `slot="title"` on Heading in dialogs)
+ - Disable UI during pending operations: `isDisabled={mutation.isPending}` on buttons/fields, `isDismissable={!mutation.isPending}` on modals
+ - Dialog sizing: `sm:w-dialog-md` (simple), `sm:w-dialog-lg` (4-6 fields), `sm:w-dialog-xl` (complex), `sm:w-dialog-2xl` (extra-large)
3. Error handling:
- **Errors are handled globally**—`shared-webapp/infrastructure/http/errorHandler.ts` automatically shows toast notifications with the server's error message (don't manually show toasts for errors)
@@ -69,79 +72,103 @@ Guidelines for frontend TypeScript and React development, including component st
- Use `isTouchDevice()` for touch vs mouse interactions
- Use `isMediumViewportOrLarger()` for desktop-specific features
-5. Always follow these steps when implementing changes:
+5. Z-index layering for fixed-position elements (don't invent new values):
+ - `z-0` to `z-20`: Content layers (sticky headers, table headers)
+ - `z-30` to `z-40`: Navigation (top bar, mobile header)
+ - `z-60`: Side menu collapsed
+ - `z-70`: Side panes (backdrop at `z-[65]`)
+ - `z-80`: Side menu expanded in overlay mode (backdrop at `z-[75]`)
+ - `z-90`: Modal dialogs
+ - `z-100`: High priority modals (nested, confirmations)
+ - `z-[150]`: Toasts (always visible for user feedback)
+ - `z-[200]`: Mobile full-screen menus
+ - Note: Dropdowns, tooltips, and popovers use React Aria's overlay system which manages stacking relative to their context
+
+6. Always follow these steps when implementing changes:
- Consult relevant rule files and list which ones guided your implementation
- Search the codebase for similar code before implementing new code
- Reference existing implementations to maintain consistency
-6. Build and format your changes:
+7. Build and format your changes:
- After each minor change, use the **execute MCP tool** with `command: "build"` for frontend
- This ensures consistent code style across the codebase
-7. Verify your changes:
+8. Verify your changes:
- When a feature is complete, run these MCP tools for frontend in sequence: **build**, **format**, **inspect**
- Fix any compiler warnings or test failures before proceeding
## Examples
-### Example 1 - Component Structure
-
```tsx
-// ✅ DO: Create focused components with clear responsibilities
-import { Trans } from "@lingui/react/macro";
-import { Avatar } from "@repo/ui/components/Avatar";
-import { Button } from "@repo/ui/components/Button";
-import { Menu, MenuHeader, MenuItem, MenuSeparator } from "@repo/ui/components/Menu";
-import { LogOutIcon, UserIcon } from "lucide-react";
-
-export function AvatarMenu({ userInfo, onProfileClick, onLogoutClick }: AvatarMenuProps) {
+// ✅ DO: Correct patterns
+export function UserPicker({ isOpen, isPending, onOpenChange }: UserPickerProps) {
+ const { data } = api.useQuery("get", "/api/account-management/users", { enabled: isOpen });
+ const activeUsers = (data?.users ?? []).filter((u) => u.isActive); // ✅ Compute derived values inline
+
+ const handleChangeSelection = (keys: Selection) => { /* ... */ }; // ✅ handleVerbNoun pattern
+
return (
-
+ // ✅ Prevent dismiss during pending
+
+
);
}
-// ❌ DON'T: Mix unrelated functionality in a single component
-function BadAvatarMenu({ userInfo }) { // Bad: Mixing menu with logout functionality
+// ❌ DON'T: Common anti-patterns
+function BadUserDialog({ users, selectedId, isOpen, onClose }) {
+ const [filteredUsers, setFilteredUsers] = useState([]); // ❌ State for derived values
+ const [isAdmin, setIsAdmin] = useState(false); // ❌ Duplicate state that can be calculated
+
+ useEffect(() => { // ❌ useEffect for calculations - compute inline instead
+ setFilteredUsers(users.filter(u => u.isActive));
+ setIsAdmin(users.some(u => u.id === selectedId && u.role === "admin")); // ❌ Hardcode strings - use API contract types
+ }, [users, selectedId]);
+
+ const getDisplayName = useCallback((user) => { // ❌ Premature useCallback without performance need
+ return `${user.firstName} ${user.lastName}`;
+ }, []);
+
+ const handleSelect = (id) => console.log(id); // ❌ "handle" + noun (use handleSelectUser), console.log
+
return (
-
// ❌ DON'T: Use CSS styles instead of Tailwind
-
-
// ❌ Unnecessary nested
-
- // ❌ DON'T: Use native
-
{userInfo.fullName}
-
-
-
-
+ // ❌ Missing isDismissable={!isPending}
+
+
);
}
```
diff --git a/.agent/rules/frontend/modal-dialog.md b/.agent/rules/frontend/modal-dialog.md
deleted file mode 100644
index cfcac3f518..0000000000
--- a/.agent/rules/frontend/modal-dialog.md
+++ /dev/null
@@ -1,88 +0,0 @@
----
-trigger: glob
-globs: *Dialog.tsx,*Modal.tsx
-description: Rules for modal dialogs using React Aria Components
----
-# Modal Dialog
-
-Guidelines for implementing modal dialogs in the frontend, focusing on accessibility, component usage, and translation patterns.
-
-## Implementation
-
-1. Use React Aria Components from `@repo/ui/components`
-2. Use the `Dialog` component from shared-webapp for all dialogs and modal dialogs
-3. Manage dialog state with React hooks (`useState` or context depending on scope)
-4. Use `onPress` instead of `onClick` for event handlers
-5. Include appropriate aria labels for accessibility
-6. Use `...` or t\`...\` for translations (content should be plain English)
-7. Apply a dialog width class (`sm:w-dialog-md`, `sm:w-dialog-lg`, `sm:w-dialog-xl`, or `sm:w-dialog-2xl`) to every `Dialog` component
-8. Use `isDismissable={!mutation.isPending}` to prevent closing during save operations
-
-## Dialog Sizing
-
-Every dialog must have a fixed width class to ensure consistent sizing across languages:
-
-- `sm:w-dialog-md` — most dialogs: alerts, confirmations, simple forms (1-3 fields)
-- `sm:w-dialog-lg` — standard forms with multiple fields (4-6 fields)
-- `sm:w-dialog-xl` — complex forms with many fields, file uploads, or multi-section content
-- `sm:w-dialog-2xl` — extra-large dialogs with dual-list interfaces or very complex layouts
-
-The `AlertDialog` component defaults to `sm:w-dialog-md` but can be overridden.
-
-## Examples
-
-### Example 1 - Simple Form Dialog
-
-```typescript
-const [isOpen, setIsOpen] = useState(false);
-
-
-
-
-```
-
-### Example 2 - Alert Dialog
-
-```typescript
-
-
- Are you sure you want to delete this user?
-
-
-```
-
diff --git a/.claude/hooks/pre-tool-use-bash.sh b/.claude/hooks/pre-tool-use-bash.sh
index 709d2fea04..1128d2e071 100755
--- a/.claude/hooks/pre-tool-use-bash.sh
+++ b/.claude/hooks/pre-tool-use-bash.sh
@@ -11,7 +11,7 @@ cmd=$(echo "$input" | sed -n 's/.*"command":"\([^"]*\)".*/\1/p')
# Check the command and decide whether to block it
case "$cmd" in
- *"git merge"*|*"git rebase"*|*"git cherry-pick"*|*"git reset"*|*"git revert"*|*"git tag"*|*"git clean"*|*"git push"*|*"git remote"*|*"git config"*) echo "❌ Dangerous git operation. Only 'git add' and 'git commit' allowed. Run this yourself." >&2; exit 2 ;;
+ *"git merge "*|*"git rebase "*|*"git cherry-pick "*|*"git reset "*|*"git revert "*|*"git tag "*|*"git clean "*|*"git push "*|*"git push"*|*"git remote "*|*"git config "*) echo "❌ Dangerous git operation. Run this yourself." >&2; exit 2 ;;
*"dotnet build"*) echo "❌ Use **build MCP tool** instead" >&2; exit 2 ;;
*"dotnet test"*) echo "❌ Use **test MCP tool** instead" >&2; exit 2 ;;
*"dotnet format"*) echo "❌ Use **format MCP tool** instead" >&2; exit 2 ;;
diff --git a/.claude/rules/frontend/frontend.md b/.claude/rules/frontend/frontend.md
index 6316ca51f1..cab5791023 100644
--- a/.claude/rules/frontend/frontend.md
+++ b/.claude/rules/frontend/frontend.md
@@ -50,7 +50,7 @@ Guidelines for frontend TypeScript and React development, including component st
- Use React Aria Components from `@repo/ui/components/ComponentName`:
- Search [Components](/application/shared-webapp/ui/components) when you need to find a component
- Use existing components rather than creating new ones
- - Use `onPress` instead of `onClick` for event handlers
+ - Use `onPress` instead of `onClick` for event handlers (exception: Dialog close button uses `onClick={close}` from render prop)
- Use `onAction` for menu items and list actions
- Use `...` for JSX translations, `t` macro for strings
- Use TanStack Query for API interactions via `api.useQuery()` and `api.useMutation()`
@@ -59,6 +59,9 @@ Guidelines for frontend TypeScript and React development, including component st
- Colocate state with components—don't lift state unnecessarily
- Use `useCallback` and `useMemo` only for proven performance issues
- Throw errors sparingly and ensure error messages include a period
+ - Include appropriate aria labels for accessibility (e.g., `slot="title"` on Heading in dialogs)
+ - Disable UI during pending operations: `isDisabled={mutation.isPending}` on buttons/fields, `isDismissable={!mutation.isPending}` on modals
+ - Dialog sizing: `sm:w-dialog-md` (simple), `sm:w-dialog-lg` (4-6 fields), `sm:w-dialog-xl` (complex), `sm:w-dialog-2xl` (extra-large)
3. Error handling:
- **Errors are handled globally**—`shared-webapp/infrastructure/http/errorHandler.ts` automatically shows toast notifications with the server's error message (don't manually show toasts for errors)
@@ -70,79 +73,103 @@ Guidelines for frontend TypeScript and React development, including component st
- Use `isTouchDevice()` for touch vs mouse interactions
- Use `isMediumViewportOrLarger()` for desktop-specific features
-5. Always follow these steps when implementing changes:
+5. Z-index layering for fixed-position elements (don't invent new values):
+ - `z-0` to `z-20`: Content layers (sticky headers, table headers)
+ - `z-30` to `z-40`: Navigation (top bar, mobile header)
+ - `z-60`: Side menu collapsed
+ - `z-70`: Side panes (backdrop at `z-[65]`)
+ - `z-80`: Side menu expanded in overlay mode (backdrop at `z-[75]`)
+ - `z-90`: Modal dialogs
+ - `z-100`: High priority modals (nested, confirmations)
+ - `z-[150]`: Toasts (always visible for user feedback)
+ - `z-[200]`: Mobile full-screen menus
+ - Note: Dropdowns, tooltips, and popovers use React Aria's overlay system which manages stacking relative to their context
+
+6. Always follow these steps when implementing changes:
- Consult relevant rule files and list which ones guided your implementation
- Search the codebase for similar code before implementing new code
- Reference existing implementations to maintain consistency
-6. Build and format your changes:
+7. Build and format your changes:
- After each minor change, use the **execute MCP tool** with `command: "build"` for frontend
- This ensures consistent code style across the codebase
-7. Verify your changes:
+8. Verify your changes:
- When a feature is complete, run these MCP tools for frontend in sequence: **build**, **format**, **inspect**
- Fix any compiler warnings or test failures before proceeding
## Examples
-### Example 1 - Component Structure
-
```tsx
-// ✅ DO: Create focused components with clear responsibilities
-import { Trans } from "@lingui/react/macro";
-import { Avatar } from "@repo/ui/components/Avatar";
-import { Button } from "@repo/ui/components/Button";
-import { Menu, MenuHeader, MenuItem, MenuSeparator } from "@repo/ui/components/Menu";
-import { LogOutIcon, UserIcon } from "lucide-react";
-
-export function AvatarMenu({ userInfo, onProfileClick, onLogoutClick }: AvatarMenuProps) {
+// ✅ DO: Correct patterns
+export function UserPicker({ isOpen, isPending, onOpenChange }: UserPickerProps) {
+ const { data } = api.useQuery("get", "/api/account-management/users", { enabled: isOpen });
+ const activeUsers = (data?.users ?? []).filter((u) => u.isActive); // ✅ Compute derived values inline
+
+ const handleChangeSelection = (keys: Selection) => { /* ... */ }; // ✅ handleVerbNoun pattern
+
return (
-
+ // ✅ Prevent dismiss during pending
+
+
);
}
-// ❌ DON'T: Mix unrelated functionality in a single component
-function BadAvatarMenu({ userInfo }) { // Bad: Mixing menu with logout functionality
+// ❌ DON'T: Common anti-patterns
+function BadUserDialog({ users, selectedId, isOpen, onClose }) {
+ const [filteredUsers, setFilteredUsers] = useState([]); // ❌ State for derived values
+ const [isAdmin, setIsAdmin] = useState(false); // ❌ Duplicate state that can be calculated
+
+ useEffect(() => { // ❌ useEffect for calculations - compute inline instead
+ setFilteredUsers(users.filter(u => u.isActive));
+ setIsAdmin(users.some(u => u.id === selectedId && u.role === "admin")); // ❌ Hardcode strings - use API contract types
+ }, [users, selectedId]);
+
+ const getDisplayName = useCallback((user) => { // ❌ Premature useCallback without performance need
+ return `${user.firstName} ${user.lastName}`;
+ }, []);
+
+ const handleSelect = (id) => console.log(id); // ❌ "handle" + noun (use handleSelectUser), console.log
+
return (
-
+ // ❌ Missing isDismissable={!isPending}
+
+
);
}
```
diff --git a/.claude/rules/frontend/modal-dialog.md b/.claude/rules/frontend/modal-dialog.md
deleted file mode 100644
index fdd1aceeb1..0000000000
--- a/.claude/rules/frontend/modal-dialog.md
+++ /dev/null
@@ -1,89 +0,0 @@
----
-trigger: glob
-globs: *Dialog.tsx,*Modal.tsx
-description: Rules for modal dialogs using React Aria Components
----
-
-# Modal Dialog
-
-Guidelines for implementing modal dialogs in the frontend, focusing on accessibility, component usage, and translation patterns.
-
-## Implementation
-
-1. Use React Aria Components from `@repo/ui/components`
-2. Use the `Dialog` component from shared-webapp for all dialogs and modal dialogs
-3. Manage dialog state with React hooks (`useState` or context depending on scope)
-4. Use `onPress` instead of `onClick` for event handlers
-5. Include appropriate aria labels for accessibility
-6. Use `...` or t\`...\` for translations (content should be plain English)
-7. Apply a dialog width class (`sm:w-dialog-md`, `sm:w-dialog-lg`, `sm:w-dialog-xl`, or `sm:w-dialog-2xl`) to every `Dialog` component
-8. Use `isDismissable={!mutation.isPending}` to prevent closing during save operations
-
-## Dialog Sizing
-
-Every dialog must have a fixed width class to ensure consistent sizing across languages:
-
-- `sm:w-dialog-md` — most dialogs: alerts, confirmations, simple forms (1-3 fields)
-- `sm:w-dialog-lg` — standard forms with multiple fields (4-6 fields)
-- `sm:w-dialog-xl` — complex forms with many fields, file uploads, or multi-section content
-- `sm:w-dialog-2xl` — extra-large dialogs with dual-list interfaces or very complex layouts
-
-The `AlertDialog` component defaults to `sm:w-dialog-md` but can be overridden.
-
-## Examples
-
-### Example 1 - Simple Form Dialog
-
-```typescript
-const [isOpen, setIsOpen] = useState(false);
-
-
-
-
-```
-
-### Example 2 - Alert Dialog
-
-```typescript
-
-
- Are you sure you want to delete this user?
-
-
-```
-
diff --git a/.cursor/rules/frontend/frontend.mdc b/.cursor/rules/frontend/frontend.mdc
index 4e3d1b47a4..6e734568e3 100644
--- a/.cursor/rules/frontend/frontend.mdc
+++ b/.cursor/rules/frontend/frontend.mdc
@@ -49,7 +49,7 @@ Guidelines for frontend TypeScript and React development, including component st
- Use React Aria Components from `@repo/ui/components/ComponentName`:
- Search [Components](mdc:application/shared-webapp/ui/components) when you need to find a component
- Use existing components rather than creating new ones
- - Use `onPress` instead of `onClick` for event handlers
+ - Use `onPress` instead of `onClick` for event handlers (exception: Dialog close button uses `onClick={close}` from render prop)
- Use `onAction` for menu items and list actions
- Use `...` for JSX translations, `t` macro for strings
- Use TanStack Query for API interactions via `api.useQuery()` and `api.useMutation()`
@@ -58,6 +58,9 @@ Guidelines for frontend TypeScript and React development, including component st
- Colocate state with components—don't lift state unnecessarily
- Use `useCallback` and `useMemo` only for proven performance issues
- Throw errors sparingly and ensure error messages include a period
+ - Include appropriate aria labels for accessibility (e.g., `slot="title"` on Heading in dialogs)
+ - Disable UI during pending operations: `isDisabled={mutation.isPending}` on buttons/fields, `isDismissable={!mutation.isPending}` on modals
+ - Dialog sizing: `sm:w-dialog-md` (simple), `sm:w-dialog-lg` (4-6 fields), `sm:w-dialog-xl` (complex), `sm:w-dialog-2xl` (extra-large)
3. Error handling:
- **Errors are handled globally**—`shared-webapp/infrastructure/http/errorHandler.ts` automatically shows toast notifications with the server's error message (don't manually show toasts for errors)
@@ -69,79 +72,103 @@ Guidelines for frontend TypeScript and React development, including component st
- Use `isTouchDevice()` for touch vs mouse interactions
- Use `isMediumViewportOrLarger()` for desktop-specific features
-5. Always follow these steps when implementing changes:
+5. Z-index layering for fixed-position elements (don't invent new values):
+ - `z-0` to `z-20`: Content layers (sticky headers, table headers)
+ - `z-30` to `z-40`: Navigation (top bar, mobile header)
+ - `z-60`: Side menu collapsed
+ - `z-70`: Side panes (backdrop at `z-[65]`)
+ - `z-80`: Side menu expanded in overlay mode (backdrop at `z-[75]`)
+ - `z-90`: Modal dialogs
+ - `z-100`: High priority modals (nested, confirmations)
+ - `z-[150]`: Toasts (always visible for user feedback)
+ - `z-[200]`: Mobile full-screen menus
+ - Note: Dropdowns, tooltips, and popovers use React Aria's overlay system which manages stacking relative to their context
+
+6. Always follow these steps when implementing changes:
- Consult relevant rule files and list which ones guided your implementation
- Search the codebase for similar code before implementing new code
- Reference existing implementations to maintain consistency
-6. Build and format your changes:
+7. Build and format your changes:
- After each minor change, use the **execute MCP tool** with `command: "build"` for frontend
- This ensures consistent code style across the codebase
-7. Verify your changes:
+8. Verify your changes:
- When a feature is complete, run these MCP tools for frontend in sequence: **build**, **format**, **inspect**
- Fix any compiler warnings or test failures before proceeding
## Examples
-### Example 1 - Component Structure
-
```tsx
-// ✅ DO: Create focused components with clear responsibilities
-import { Trans } from "@lingui/react/macro";
-import { Avatar } from "@repo/ui/components/Avatar";
-import { Button } from "@repo/ui/components/Button";
-import { Menu, MenuHeader, MenuItem, MenuSeparator } from "@repo/ui/components/Menu";
-import { LogOutIcon, UserIcon } from "lucide-react";
-
-export function AvatarMenu({ userInfo, onProfileClick, onLogoutClick }: AvatarMenuProps) {
+// ✅ DO: Correct patterns
+export function UserPicker({ isOpen, isPending, onOpenChange }: UserPickerProps) {
+ const { data } = api.useQuery("get", "/api/account-management/users", { enabled: isOpen });
+ const activeUsers = (data?.users ?? []).filter((u) => u.isActive); // ✅ Compute derived values inline
+
+ const handleChangeSelection = (keys: Selection) => { /* ... */ }; // ✅ handleVerbNoun pattern
+
return (
-
+ // ✅ Prevent dismiss during pending
+
+
);
}
-// ❌ DON'T: Mix unrelated functionality in a single component
-function BadAvatarMenu({ userInfo }) { // Bad: Mixing menu with logout functionality
+// ❌ DON'T: Common anti-patterns
+function BadUserDialog({ users, selectedId, isOpen, onClose }) {
+ const [filteredUsers, setFilteredUsers] = useState([]); // ❌ State for derived values
+ const [isAdmin, setIsAdmin] = useState(false); // ❌ Duplicate state that can be calculated
+
+ useEffect(() => { // ❌ useEffect for calculations - compute inline instead
+ setFilteredUsers(users.filter(u => u.isActive));
+ setIsAdmin(users.some(u => u.id === selectedId && u.role === "admin")); // ❌ Hardcode strings - use API contract types
+ }, [users, selectedId]);
+
+ const getDisplayName = useCallback((user) => { // ❌ Premature useCallback without performance need
+ return `${user.firstName} ${user.lastName}`;
+ }, []);
+
+ const handleSelect = (id) => console.log(id); // ❌ "handle" + noun (use handleSelectUser), console.log
+
return (
-
+ // ❌ Missing isDismissable={!isPending}
+
+
);
}
```
diff --git a/.cursor/rules/frontend/modal-dialog.mdc b/.cursor/rules/frontend/modal-dialog.mdc
deleted file mode 100644
index 7e8e6f7da8..0000000000
--- a/.cursor/rules/frontend/modal-dialog.mdc
+++ /dev/null
@@ -1,88 +0,0 @@
----
-description: Rules for modal dialogs using React Aria Components
-globs: *Dialog.tsx,*Modal.tsx
-alwaysApply: false
----
-# Modal Dialog
-
-Guidelines for implementing modal dialogs in the frontend, focusing on accessibility, component usage, and translation patterns.
-
-## Implementation
-
-1. Use React Aria Components from `@repo/ui/components`
-2. Use the `Dialog` component from shared-webapp for all dialogs and modal dialogs
-3. Manage dialog state with React hooks (`useState` or context depending on scope)
-4. Use `onPress` instead of `onClick` for event handlers
-5. Include appropriate aria labels for accessibility
-6. Use `...` or t\`...\` for translations (content should be plain English)
-7. Apply a dialog width class (`sm:w-dialog-md`, `sm:w-dialog-lg`, `sm:w-dialog-xl`, or `sm:w-dialog-2xl`) to every `Dialog` component
-8. Use `isDismissable={!mutation.isPending}` to prevent closing during save operations
-
-## Dialog Sizing
-
-Every dialog must have a fixed width class to ensure consistent sizing across languages:
-
-- `sm:w-dialog-md` — most dialogs: alerts, confirmations, simple forms (1-3 fields)
-- `sm:w-dialog-lg` — standard forms with multiple fields (4-6 fields)
-- `sm:w-dialog-xl` — complex forms with many fields, file uploads, or multi-section content
-- `sm:w-dialog-2xl` — extra-large dialogs with dual-list interfaces or very complex layouts
-
-The `AlertDialog` component defaults to `sm:w-dialog-md` but can be overridden.
-
-## Examples
-
-### Example 1 - Simple Form Dialog
-
-```typescript
-const [isOpen, setIsOpen] = useState(false);
-
-
-
-
-```
-
-### Example 2 - Alert Dialog
-
-```typescript
-
-
- Are you sure you want to delete this user?
-
-
-```
-
diff --git a/.github/copilot/rules/frontend/frontend.md b/.github/copilot/rules/frontend/frontend.md
index 5ea7e5ebed..8d8fbf65ee 100644
--- a/.github/copilot/rules/frontend/frontend.md
+++ b/.github/copilot/rules/frontend/frontend.md
@@ -44,7 +44,7 @@ Guidelines for frontend TypeScript and React development, including component st
- Use React Aria Components from `@repo/ui/components/ComponentName`:
- Search [Components](/application/shared-webapp/ui/components) when you need to find a component
- Use existing components rather than creating new ones
- - Use `onPress` instead of `onClick` for event handlers
+ - Use `onPress` instead of `onClick` for event handlers (exception: Dialog close button uses `onClick={close}` from render prop)
- Use `onAction` for menu items and list actions
- Use `...` for JSX translations, `t` macro for strings
- Use TanStack Query for API interactions via `api.useQuery()` and `api.useMutation()`
@@ -53,6 +53,9 @@ Guidelines for frontend TypeScript and React development, including component st
- Colocate state with components—don't lift state unnecessarily
- Use `useCallback` and `useMemo` only for proven performance issues
- Throw errors sparingly and ensure error messages include a period
+ - Include appropriate aria labels for accessibility (e.g., `slot="title"` on Heading in dialogs)
+ - Disable UI during pending operations: `isDisabled={mutation.isPending}` on buttons/fields, `isDismissable={!mutation.isPending}` on modals
+ - Dialog sizing: `sm:w-dialog-md` (simple), `sm:w-dialog-lg` (4-6 fields), `sm:w-dialog-xl` (complex), `sm:w-dialog-2xl` (extra-large)
3. Error handling:
- **Errors are handled globally**—`shared-webapp/infrastructure/http/errorHandler.ts` automatically shows toast notifications with the server's error message (don't manually show toasts for errors)
@@ -64,79 +67,103 @@ Guidelines for frontend TypeScript and React development, including component st
- Use `isTouchDevice()` for touch vs mouse interactions
- Use `isMediumViewportOrLarger()` for desktop-specific features
-5. Always follow these steps when implementing changes:
+5. Z-index layering for fixed-position elements (don't invent new values):
+ - `z-0` to `z-20`: Content layers (sticky headers, table headers)
+ - `z-30` to `z-40`: Navigation (top bar, mobile header)
+ - `z-60`: Side menu collapsed
+ - `z-70`: Side panes (backdrop at `z-[65]`)
+ - `z-80`: Side menu expanded in overlay mode (backdrop at `z-[75]`)
+ - `z-90`: Modal dialogs
+ - `z-100`: High priority modals (nested, confirmations)
+ - `z-[150]`: Toasts (always visible for user feedback)
+ - `z-[200]`: Mobile full-screen menus
+ - Note: Dropdowns, tooltips, and popovers use React Aria's overlay system which manages stacking relative to their context
+
+6. Always follow these steps when implementing changes:
- Consult relevant rule files and list which ones guided your implementation
- Search the codebase for similar code before implementing new code
- Reference existing implementations to maintain consistency
-6. Build and format your changes:
+7. Build and format your changes:
- After each minor change, use the **execute MCP tool** with `command: "build"` for frontend
- This ensures consistent code style across the codebase
-7. Verify your changes:
+8. Verify your changes:
- When a feature is complete, run these MCP tools for frontend in sequence: **build**, **format**, **inspect**
- Fix any compiler warnings or test failures before proceeding
## Examples
-### Example 1 - Component Structure
-
```tsx
-// ✅ DO: Create focused components with clear responsibilities
-import { Trans } from "@lingui/react/macro";
-import { Avatar } from "@repo/ui/components/Avatar";
-import { Button } from "@repo/ui/components/Button";
-import { Menu, MenuHeader, MenuItem, MenuSeparator } from "@repo/ui/components/Menu";
-import { LogOutIcon, UserIcon } from "lucide-react";
-
-export function AvatarMenu({ userInfo, onProfileClick, onLogoutClick }: AvatarMenuProps) {
+// ✅ DO: Correct patterns
+export function UserPicker({ isOpen, isPending, onOpenChange }: UserPickerProps) {
+ const { data } = api.useQuery("get", "/api/account-management/users", { enabled: isOpen });
+ const activeUsers = (data?.users ?? []).filter((u) => u.isActive); // ✅ Compute derived values inline
+
+ const handleChangeSelection = (keys: Selection) => { /* ... */ }; // ✅ handleVerbNoun pattern
+
return (
-
+ // ✅ Prevent dismiss during pending
+
+
);
}
-// ❌ DON'T: Mix unrelated functionality in a single component
-function BadAvatarMenu({ userInfo }) { // Bad: Mixing menu with logout functionality
+// ❌ DON'T: Common anti-patterns
+function BadUserDialog({ users, selectedId, isOpen, onClose }) {
+ const [filteredUsers, setFilteredUsers] = useState([]); // ❌ State for derived values
+ const [isAdmin, setIsAdmin] = useState(false); // ❌ Duplicate state that can be calculated
+
+ useEffect(() => { // ❌ useEffect for calculations - compute inline instead
+ setFilteredUsers(users.filter(u => u.isActive));
+ setIsAdmin(users.some(u => u.id === selectedId && u.role === "admin")); // ❌ Hardcode strings - use API contract types
+ }, [users, selectedId]);
+
+ const getDisplayName = useCallback((user) => { // ❌ Premature useCallback without performance need
+ return `${user.firstName} ${user.lastName}`;
+ }, []);
+
+ const handleSelect = (id) => console.log(id); // ❌ "handle" + noun (use handleSelectUser), console.log
+
return (
-
+ // ❌ Missing isDismissable={!isPending}
+
+
);
}
```
diff --git a/.github/copilot/rules/frontend/modal-dialog.md b/.github/copilot/rules/frontend/modal-dialog.md
deleted file mode 100644
index f82c05e414..0000000000
--- a/.github/copilot/rules/frontend/modal-dialog.md
+++ /dev/null
@@ -1,83 +0,0 @@
-# Modal Dialog
-
-Guidelines for implementing modal dialogs in the frontend, focusing on accessibility, component usage, and translation patterns.
-
-## Implementation
-
-1. Use React Aria Components from `@repo/ui/components`
-2. Use the `Dialog` component from shared-webapp for all dialogs and modal dialogs
-3. Manage dialog state with React hooks (`useState` or context depending on scope)
-4. Use `onPress` instead of `onClick` for event handlers
-5. Include appropriate aria labels for accessibility
-6. Use `...` or t\`...\` for translations (content should be plain English)
-7. Apply a dialog width class (`sm:w-dialog-md`, `sm:w-dialog-lg`, `sm:w-dialog-xl`, or `sm:w-dialog-2xl`) to every `Dialog` component
-8. Use `isDismissable={!mutation.isPending}` to prevent closing during save operations
-
-## Dialog Sizing
-
-Every dialog must have a fixed width class to ensure consistent sizing across languages:
-
-- `sm:w-dialog-md` — most dialogs: alerts, confirmations, simple forms (1-3 fields)
-- `sm:w-dialog-lg` — standard forms with multiple fields (4-6 fields)
-- `sm:w-dialog-xl` — complex forms with many fields, file uploads, or multi-section content
-- `sm:w-dialog-2xl` — extra-large dialogs with dual-list interfaces or very complex layouts
-
-The `AlertDialog` component defaults to `sm:w-dialog-md` but can be overridden.
-
-## Examples
-
-### Example 1 - Simple Form Dialog
-
-```typescript
-const [isOpen, setIsOpen] = useState(false);
-
-
-
-
-```
-
-### Example 2 - Alert Dialog
-
-```typescript
-
-
- Are you sure you want to delete this user?
-
-
-```
-
diff --git a/.windsurf/rules/frontend/frontend.md b/.windsurf/rules/frontend/frontend.md
index 6316ca51f1..cab5791023 100644
--- a/.windsurf/rules/frontend/frontend.md
+++ b/.windsurf/rules/frontend/frontend.md
@@ -50,7 +50,7 @@ Guidelines for frontend TypeScript and React development, including component st
- Use React Aria Components from `@repo/ui/components/ComponentName`:
- Search [Components](/application/shared-webapp/ui/components) when you need to find a component
- Use existing components rather than creating new ones
- - Use `onPress` instead of `onClick` for event handlers
+ - Use `onPress` instead of `onClick` for event handlers (exception: Dialog close button uses `onClick={close}` from render prop)
- Use `onAction` for menu items and list actions
- Use `...` for JSX translations, `t` macro for strings
- Use TanStack Query for API interactions via `api.useQuery()` and `api.useMutation()`
@@ -59,6 +59,9 @@ Guidelines for frontend TypeScript and React development, including component st
- Colocate state with components—don't lift state unnecessarily
- Use `useCallback` and `useMemo` only for proven performance issues
- Throw errors sparingly and ensure error messages include a period
+ - Include appropriate aria labels for accessibility (e.g., `slot="title"` on Heading in dialogs)
+ - Disable UI during pending operations: `isDisabled={mutation.isPending}` on buttons/fields, `isDismissable={!mutation.isPending}` on modals
+ - Dialog sizing: `sm:w-dialog-md` (simple), `sm:w-dialog-lg` (4-6 fields), `sm:w-dialog-xl` (complex), `sm:w-dialog-2xl` (extra-large)
3. Error handling:
- **Errors are handled globally**—`shared-webapp/infrastructure/http/errorHandler.ts` automatically shows toast notifications with the server's error message (don't manually show toasts for errors)
@@ -70,79 +73,103 @@ Guidelines for frontend TypeScript and React development, including component st
- Use `isTouchDevice()` for touch vs mouse interactions
- Use `isMediumViewportOrLarger()` for desktop-specific features
-5. Always follow these steps when implementing changes:
+5. Z-index layering for fixed-position elements (don't invent new values):
+ - `z-0` to `z-20`: Content layers (sticky headers, table headers)
+ - `z-30` to `z-40`: Navigation (top bar, mobile header)
+ - `z-60`: Side menu collapsed
+ - `z-70`: Side panes (backdrop at `z-[65]`)
+ - `z-80`: Side menu expanded in overlay mode (backdrop at `z-[75]`)
+ - `z-90`: Modal dialogs
+ - `z-100`: High priority modals (nested, confirmations)
+ - `z-[150]`: Toasts (always visible for user feedback)
+ - `z-[200]`: Mobile full-screen menus
+ - Note: Dropdowns, tooltips, and popovers use React Aria's overlay system which manages stacking relative to their context
+
+6. Always follow these steps when implementing changes:
- Consult relevant rule files and list which ones guided your implementation
- Search the codebase for similar code before implementing new code
- Reference existing implementations to maintain consistency
-6. Build and format your changes:
+7. Build and format your changes:
- After each minor change, use the **execute MCP tool** with `command: "build"` for frontend
- This ensures consistent code style across the codebase
-7. Verify your changes:
+8. Verify your changes:
- When a feature is complete, run these MCP tools for frontend in sequence: **build**, **format**, **inspect**
- Fix any compiler warnings or test failures before proceeding
## Examples
-### Example 1 - Component Structure
-
```tsx
-// ✅ DO: Create focused components with clear responsibilities
-import { Trans } from "@lingui/react/macro";
-import { Avatar } from "@repo/ui/components/Avatar";
-import { Button } from "@repo/ui/components/Button";
-import { Menu, MenuHeader, MenuItem, MenuSeparator } from "@repo/ui/components/Menu";
-import { LogOutIcon, UserIcon } from "lucide-react";
-
-export function AvatarMenu({ userInfo, onProfileClick, onLogoutClick }: AvatarMenuProps) {
+// ✅ DO: Correct patterns
+export function UserPicker({ isOpen, isPending, onOpenChange }: UserPickerProps) {
+ const { data } = api.useQuery("get", "/api/account-management/users", { enabled: isOpen });
+ const activeUsers = (data?.users ?? []).filter((u) => u.isActive); // ✅ Compute derived values inline
+
+ const handleChangeSelection = (keys: Selection) => { /* ... */ }; // ✅ handleVerbNoun pattern
+
return (
-
+ // ✅ Prevent dismiss during pending
+
+
);
}
-// ❌ DON'T: Mix unrelated functionality in a single component
-function BadAvatarMenu({ userInfo }) { // Bad: Mixing menu with logout functionality
+// ❌ DON'T: Common anti-patterns
+function BadUserDialog({ users, selectedId, isOpen, onClose }) {
+ const [filteredUsers, setFilteredUsers] = useState([]); // ❌ State for derived values
+ const [isAdmin, setIsAdmin] = useState(false); // ❌ Duplicate state that can be calculated
+
+ useEffect(() => { // ❌ useEffect for calculations - compute inline instead
+ setFilteredUsers(users.filter(u => u.isActive));
+ setIsAdmin(users.some(u => u.id === selectedId && u.role === "admin")); // ❌ Hardcode strings - use API contract types
+ }, [users, selectedId]);
+
+ const getDisplayName = useCallback((user) => { // ❌ Premature useCallback without performance need
+ return `${user.firstName} ${user.lastName}`;
+ }, []);
+
+ const handleSelect = (id) => console.log(id); // ❌ "handle" + noun (use handleSelectUser), console.log
+
return (
-
+ // ❌ Missing isDismissable={!isPending}
+
+
);
}
```
diff --git a/.windsurf/rules/frontend/modal-dialog.md b/.windsurf/rules/frontend/modal-dialog.md
deleted file mode 100644
index fdd1aceeb1..0000000000
--- a/.windsurf/rules/frontend/modal-dialog.md
+++ /dev/null
@@ -1,89 +0,0 @@
----
-trigger: glob
-globs: *Dialog.tsx,*Modal.tsx
-description: Rules for modal dialogs using React Aria Components
----
-
-# Modal Dialog
-
-Guidelines for implementing modal dialogs in the frontend, focusing on accessibility, component usage, and translation patterns.
-
-## Implementation
-
-1. Use React Aria Components from `@repo/ui/components`
-2. Use the `Dialog` component from shared-webapp for all dialogs and modal dialogs
-3. Manage dialog state with React hooks (`useState` or context depending on scope)
-4. Use `onPress` instead of `onClick` for event handlers
-5. Include appropriate aria labels for accessibility
-6. Use `...` or t\`...\` for translations (content should be plain English)
-7. Apply a dialog width class (`sm:w-dialog-md`, `sm:w-dialog-lg`, `sm:w-dialog-xl`, or `sm:w-dialog-2xl`) to every `Dialog` component
-8. Use `isDismissable={!mutation.isPending}` to prevent closing during save operations
-
-## Dialog Sizing
-
-Every dialog must have a fixed width class to ensure consistent sizing across languages:
-
-- `sm:w-dialog-md` — most dialogs: alerts, confirmations, simple forms (1-3 fields)
-- `sm:w-dialog-lg` — standard forms with multiple fields (4-6 fields)
-- `sm:w-dialog-xl` — complex forms with many fields, file uploads, or multi-section content
-- `sm:w-dialog-2xl` — extra-large dialogs with dual-list interfaces or very complex layouts
-
-The `AlertDialog` component defaults to `sm:w-dialog-md` but can be overridden.
-
-## Examples
-
-### Example 1 - Simple Form Dialog
-
-```typescript
-const [isOpen, setIsOpen] = useState(false);
-
-
-
-
-```
-
-### Example 2 - Alert Dialog
-
-```typescript
-
-
- Are you sure you want to delete this user?
-
-
-```
-
diff --git a/application/account-management/WebApp/federated-modules/common/AuthSyncModal.tsx b/application/account-management/WebApp/federated-modules/common/AuthSyncModal.tsx
index b773d3aaea..6c58bd9906 100644
--- a/application/account-management/WebApp/federated-modules/common/AuthSyncModal.tsx
+++ b/application/account-management/WebApp/federated-modules/common/AuthSyncModal.tsx
@@ -86,7 +86,7 @@ export default function AuthSyncModal({ isOpen, type, newTenantName, onPrimaryAc
return (
-