Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 65 additions & 33 deletions .agent/rules/frontend/frontend.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ Use browser MCP tools to test at `https://localhost:9000`. Use `UNLOCK` as OTP v
- **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)
- **Validation errors**: Pass to forms via `validationErrors={mutation.error?.errors}`
- **`onError` is for UI cleanup only** (resetting loading states, closing dialogs), not for showing errors
- **Toast notifications**: Show success toasts in mutation `onSuccess` callbacks, not in `useEffect` watching `isSuccess` (avoids React effect scheduling delays)

4. Responsive design utilities:
- Use `useViewportResize()` hook to detect mobile viewport (returns `true` when mobile)
Expand All @@ -92,16 +93,21 @@ Use browser MCP tools to test at `https://localhost:9000`. Use `UNLOCK` as OTP v
- `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:
6. DirtyModal close handlers:
- **X button**: Use Dialog's `close` from render prop (shows unsaved warning if dirty)
- **Cancel button**: Use `handleCancel` that clears state and closes immediately (bypasses warning)
- Always clear dirty state in `onSuccess` and `onCloseComplete`

7. 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

7. Build and format your changes:
8. 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

8. Verify your changes:
9. Verify your changes:
- When a feature is complete, run these MCP tools for frontend in sequence: **build**, **format**, **inspect**
- **ALL inspect findings are blocking** - CI pipeline fails on any result marked "Issues found"
- Severity level (note/warning/error) is irrelevant - fix all findings before proceeding
Expand All @@ -111,39 +117,49 @@ Use browser MCP tools to test at `https://localhost:9000`. Use `UNLOCK` as OTP v

```tsx
// ✅ DO: Correct patterns
export function UserPicker({ isOpen, isPending, onOpenChange }: UserPickerProps) {
export function UserPicker({ isOpen, onOpenChange }: UserPickerProps) {
const [isFormDirty, setIsFormDirty] = useState(false);
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
const inviteMutation = api.useMutation("post", "/api/account-management/users/invite", {
onSuccess: () => { // ✅ Show toast in onSuccess (not useEffect)
setIsFormDirty(false);
toastQueue.add({ title: t`Success`, description: t`User invited`, variant: "success" });
onOpenChange(false);
}
});

const handleCloseComplete = () => setIsFormDirty(false);
const handleCancel = () => { setIsFormDirty(false); onOpenChange(false); }; // ✅ Clear state + close (bypasses warning)

return (
<Modal isOpen={isOpen} onOpenChange={onOpenChange} isDismissable={!isPending}> // ✅ Prevent dismiss during pending
<DirtyModal isOpen={isOpen} onOpenChange={onOpenChange} hasUnsavedChanges={isFormDirty}
isDismissable={!inviteMutation.isPending} onCloseComplete={handleCloseComplete}>
<Dialog className="sm:w-dialog-md"> // ✅ Use dialog width classes (not max-w-lg)
{({ close }) => ( // ✅ Dialog render prop provides close function
<>
<XIcon onClick={close} className="absolute top-2 right-2 h-10 w-10 cursor-pointer p-2 hover:bg-muted" /> // ✅ Close button pattern (onClick is exception)
<XIcon onClick={close} className="absolute top-2 right-2 h-10 w-10 cursor-pointer p-2 hover:bg-muted" /> // ✅ X uses close (shows warning if dirty)
<DialogHeader description={t`Select users from the list.`}>
<Heading slot="title" className="text-2xl"><Trans>Select users</Trans></Heading>
</DialogHeader>
<DialogContent>
<ListBox aria-label={t`Users`} selectionMode="multiple" onSelectionChange={handleChangeSelection}>
{activeUsers.map((user) => (
<ListBoxItem key={user.id} id={user.id}>
<Text>{`${user.firstName} ${user.lastName}`}</Text>
</ListBoxItem>
))}
</ListBox>
</DialogContent>
<DialogFooter>
<Button variant="primary" onPress={handleConfirm} isPending={isPending}> // ✅ Use isPending for loading
<Trans>Confirm</Trans>
</Button>
</DialogFooter>
<Form onSubmit={mutationSubmitter(inviteMutation)}>
<DialogContent>
<TextField name="email" label={t`Email`} onChange={() => setIsFormDirty(true)} />
</DialogContent>
<DialogFooter>
<Button type="reset" onPress={handleCancel} variant="secondary" isDisabled={inviteMutation.isPending}> // ✅ Cancel uses handleCancel
<Trans>Cancel</Trans>
</Button>
<Button type="submit" isDisabled={inviteMutation.isPending}> // ✅ Use isDisabled for pending
{inviteMutation.isPending ? <Trans>Sending...</Trans> : <Trans>Send invite</Trans>}
</Button>
</DialogFooter>
</Form>
</>
)}
</Dialog>
</Modal>
</DirtyModal>
);
}

Expand All @@ -152,11 +168,19 @@ 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

const inviteMutation = api.useMutation("post", "/api/users/invite");

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]);

useEffect(() => { // ❌ useEffect watching isSuccess causes toast timing issues
if (inviteMutation.isSuccess) {
toastQueue.add({ title: "Success", variant: "success" });
}
}, [inviteMutation.isSuccess]);

const getDisplayName = useCallback((user) => { // ❌ Premature useCallback without performance need
return `${user.firstName} ${user.lastName}`;
}, []);
Expand All @@ -166,17 +190,25 @@ function BadUserDialog({ users, selectedId, isOpen, onClose }) {
return (
<Modal isOpen={isOpen} onOpenChange={onClose}> // ❌ Missing isDismissable={!isPending}
<Dialog className="sm:max-w-lg bg-white"> // ❌ max-w-lg (use w-dialog-md), hardcoded colors (use bg-background)
<h1>User Mgmt</h1> // ❌ Native <h1> (use Heading), acronym "Mgmt", missing <Trans>
<ul> // ❌ Native <ul> - use ListBox
{filteredUsers.map(user => (
<li key={user.id} onClick={() => handleSelect(user.id)}> // ❌ Native <li>, onClick (use onAction)
<img src={user.avatarUrl} /> // ❌ Native <img> - use Avatar
<Text className="text-sm">{user.email}</Text> // ❌ text-sm with Text causes blur
{getDisplayName(user)}
</li>
))}
</ul>
<Button onPress={handleSelect}>Submit</Button> // ❌ Missing isDisabled/isPending, missing <Trans>
{({ close }) => ( // ❌ Both X and Cancel use close (Cancel should use handleCancel)
<>
<XIcon onClick={close} />
<h1>User Mgmt</h1> // ❌ Native <h1> (use Heading), acronym "Mgmt", missing <Trans>
<ul> // ❌ Native <ul> - use ListBox
{filteredUsers.map(user => (
<li key={user.id} onClick={() => handleSelect(user.id)}> // ❌ Native <li>, onClick (use onAction)
<img src={user.avatarUrl} /> // ❌ Native <img> - use Avatar
<Text className="text-sm">{user.email}</Text> // ❌ text-sm with Text causes blur
{getDisplayName(user)}
</li>
))}
</ul>
<Button onPress={close}>Cancel</Button> // ❌ Cancel uses close (shows unwanted warning)
<Button type="submit"> // ❌ Missing isDisabled={isPending}
<Trans>Submit</Trans> // ❌ Missing isPending text pattern, generic "Submit" text
</Button>
</>
)}
</Dialog>
</Modal>
);
Expand Down
21 changes: 15 additions & 6 deletions .agent/workflows/process/review-end-to-end-tests.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,12 @@ You are reviewing: **{{{title}}}**
{
"todos": [
{"content": "Read [feature] and [task] to understand requirements", "status": "pending", "activeForm": "Reading feature and task"},
{"content": "Run e2e tests and verify ALL pass with zero tolerance", "status": "pending", "activeForm": "Running E2E tests"},
{"content": "Run feature-specific e2e tests", "status": "pending", "activeForm": "Running feature E2E tests"},
{"content": "Review test file structure and organization", "status": "pending", "activeForm": "Reviewing test structure"},
{"content": "Review each test step for correct patterns", "status": "pending", "activeForm": "Reviewing test steps"},
{"content": "Review test efficiency and speed", "status": "pending", "activeForm": "Reviewing test efficiency"},
{"content": "Make binary decision (approve or reject)", "status": "pending", "activeForm": "Making decision"},
{"content": "If approved, run full regression test suite", "status": "pending", "activeForm": "Running full regression tests"},
{"content": "If approved, commit changes", "status": "pending", "activeForm": "Committing if approved"},
{"content": "Update [task] status to [Completed] or [Active]", "status": "pending", "activeForm": "Updating task status"},
{"content": "MANDATORY: Call CompleteWork", "status": "pending", "activeForm": "Calling CompleteWork"}
Expand Down Expand Up @@ -76,13 +77,13 @@ You are reviewing: **{{{title}}}**
- Read [End-to-End Tests](/.agent/rules/end-to-end-tests/end-to-end-tests.md)
- Ensure engineer followed all patterns

**STEP 2**: Run e2e tests and verify ALL pass with zero tolerance
**STEP 2**: Run feature-specific e2e tests first

**If tests require backend changes, run the run tool first**:
- Use **run MCP tool** to restart server and run migrations
- The tool starts .NET Aspire at https://localhost:9000

**Run E2E tests**:
**Run feature-specific E2E tests**:
- Use **end-to-end MCP tool** to run tests: `end-to-end(searchTerms=["feature-name"])`
- **ALL tests MUST pass with ZERO failures to approve**
- **Verify ZERO console errors** during test execution
Expand Down Expand Up @@ -150,15 +151,23 @@ You are reviewing: **{{{title}}}**

**When rejecting:** Do full review first, then reject with ALL issues listed (avoid multiple rounds).

**STEP 7**: If approved, commit changes
**STEP 7**: If approved, run full regression test suite

**Before committing, run all e2e tests to ensure no regressions:**
- Use **end-to-end MCP tool** WITHOUT searchTerms: `end-to-end()`
- This runs the complete test suite across all browsers
- **ALL tests MUST pass with ZERO failures**
- If ANY test fails: REJECT (do not commit)

**STEP 8**: Commit changes

1. Stage test files: `git add <file>` for each test file
2. Commit: One line, imperative form, no description, no co-author
3. Get hash: `git rev-parse HEAD`

Don't use `git add -A` or `git add .`

**STEP 8**: Update [task] status to [Completed] or [Active]
**STEP 9**: Update [task] status to [Completed] or [Active]

**If `featureId` is NOT "ad-hoc" (regular task from a feature):**
- If APPROVED: Update [task] status to [Completed].
Expand All @@ -167,7 +176,7 @@ Don't use `git add -A` or `git add .`
**If `featureId` is "ad-hoc" (ad-hoc work):**
- Skip [PRODUCT_MANAGEMENT_TOOL] status updates.

**STEP 9**: Call CompleteWork
**STEP 10**: Call CompleteWork

**Call MCP CompleteWork tool**:
- `mode`: "review"
Expand Down
8 changes: 8 additions & 0 deletions .claude/agentic-workflow/system-prompts/qa-engineer.txt
Original file line number Diff line number Diff line change
Expand Up @@ -71,3 +71,11 @@ Do not report (these are not system bugs):
If you recover from a problem: Report problem + solution (2 calls).

Report system bugs only. Every unreported workflow bug makes the agentic system worse.

## Flaky Test Tracking

**Run `/update-flaky-tests` immediately when:**
- Test failures occur that are unrelated to your current changes - log them
- You fix a known flaky test - mark it as fix-applied in the tracker

The flaky test tracker helps the team understand which tests are unreliable. Always update it.
8 changes: 8 additions & 0 deletions .claude/agentic-workflow/system-prompts/qa-reviewer.txt
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,11 @@ Do not report (these are not system bugs):
If you recover from a problem: Report problem + solution (2 calls).

Report system bugs only. Every unreported workflow bug makes the agentic system worse.

## Flaky Test Tracking

**Run `/update-flaky-tests` immediately when:**
- Test failures occur that are unrelated to the engineer's changes - log them
- You commit a fix for a known flaky test - mark it as fix-applied in the tracker

The flaky test tracker helps the team understand which tests are unreliable. Always update it.
21 changes: 15 additions & 6 deletions .claude/commands/process/review-end-to-end-tests.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,12 @@ You are reviewing: **{{{title}}}**
{
"todos": [
{"content": "Read [feature] and [task] to understand requirements", "status": "pending", "activeForm": "Reading feature and task"},
{"content": "Run e2e tests and verify ALL pass with zero tolerance", "status": "pending", "activeForm": "Running E2E tests"},
{"content": "Run feature-specific e2e tests", "status": "pending", "activeForm": "Running feature E2E tests"},
{"content": "Review test file structure and organization", "status": "pending", "activeForm": "Reviewing test structure"},
{"content": "Review each test step for correct patterns", "status": "pending", "activeForm": "Reviewing test steps"},
{"content": "Review test efficiency and speed", "status": "pending", "activeForm": "Reviewing test efficiency"},
{"content": "Make binary decision (approve or reject)", "status": "pending", "activeForm": "Making decision"},
{"content": "If approved, run full regression test suite", "status": "pending", "activeForm": "Running full regression tests"},
{"content": "If approved, commit changes", "status": "pending", "activeForm": "Committing if approved"},
{"content": "Update [task] status to [Completed] or [Active]", "status": "pending", "activeForm": "Updating task status"},
{"content": "MANDATORY: Call CompleteWork", "status": "pending", "activeForm": "Calling CompleteWork"}
Expand Down Expand Up @@ -81,13 +82,13 @@ You are reviewing: **{{{title}}}**
- Read [End-to-End Tests](/.claude/rules/end-to-end-tests/end-to-end-tests.md)
- Ensure engineer followed all patterns

**STEP 2**: Run e2e tests and verify ALL pass with zero tolerance
**STEP 2**: Run feature-specific e2e tests first

**If tests require backend changes, run the run tool first**:
- Use **run MCP tool** to restart server and run migrations
- The tool starts .NET Aspire at https://localhost:9000

**Run E2E tests**:
**Run feature-specific E2E tests**:
- Use **end-to-end MCP tool** to run tests: `end-to-end(searchTerms=["feature-name"])`
- **ALL tests MUST pass with ZERO failures to approve**
- **Verify ZERO console errors** during test execution
Expand Down Expand Up @@ -155,15 +156,23 @@ You are reviewing: **{{{title}}}**

**When rejecting:** Do full review first, then reject with ALL issues listed (avoid multiple rounds).

**STEP 7**: If approved, commit changes
**STEP 7**: If approved, run full regression test suite

**Before committing, run all e2e tests to ensure no regressions:**
- Use **end-to-end MCP tool** WITHOUT searchTerms: `end-to-end()`
- This runs the complete test suite across all browsers
- **ALL tests MUST pass with ZERO failures**
- If ANY test fails: REJECT (do not commit)

**STEP 8**: Commit changes

1. Stage test files: `git add <file>` for each test file
2. Commit: One line, imperative form, no description, no co-author
3. Get hash: `git rev-parse HEAD`

Don't use `git add -A` or `git add .`

**STEP 8**: Update [task] status to [Completed] or [Active]
**STEP 9**: Update [task] status to [Completed] or [Active]

**If `featureId` is NOT "ad-hoc" (regular task from a feature):**
- If APPROVED: Update [task] status to [Completed].
Expand All @@ -172,7 +181,7 @@ Don't use `git add -A` or `git add .`
**If `featureId` is "ad-hoc" (ad-hoc work):**
- Skip [PRODUCT_MANAGEMENT_TOOL] status updates.

**STEP 9**: Call CompleteWork
**STEP 10**: Call CompleteWork

**Call MCP CompleteWork tool**:
- `mode`: "review"
Expand Down
Loading
Loading