Skip to content

Commit 73d4453

Browse files
replace claude with agents
1 parent 169ad76 commit 73d4453

File tree

6 files changed

+495
-210
lines changed

6 files changed

+495
-210
lines changed

.agent/architecture.md

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
# Architecture
2+
3+
## Data Model (Prisma)
4+
5+
```
6+
Group (id, name, currency, currencyCode)
7+
└── Participant (id, name)
8+
└── Expense (id, title, amount, expenseDate, splitMode, isReimbursement)
9+
├── paidBy → Participant
10+
├── paidFor → ExpensePaidFor[] (participantId, shares)
11+
├── Category (id, grouping, name)
12+
├── ExpenseDocument[] (url, width, height)
13+
└── RecurringExpenseLink (nextExpenseCreatedAt)
14+
└── Activity (time, activityType, data) - audit log
15+
```
16+
17+
### Split Modes
18+
19+
- `EVENLY`: Divide equally, `shares` = 1 per participant
20+
- `BY_SHARES`: Proportional, e.g., shares 2:1:1 = 50%:25%:25%
21+
- `BY_PERCENTAGE`: Basis points (10000 = 100%), e.g., 2500 = 25%
22+
- `BY_AMOUNT`: Direct cents, `shares` = exact amount owed
23+
24+
### Calculations (src/lib/balances.ts)
25+
26+
```typescript
27+
// BY_PERCENTAGE: (expense.amount * shares) / 10000
28+
// BY_SHARES: (expense.amount * shares) / totalShares
29+
// BY_AMOUNT: shares directly
30+
// Rounding: Math.round() at the end
31+
```
32+
33+
## Directory Details
34+
35+
### src/app/
36+
37+
Next.js App Router. Pages, layouts, Server Actions. Group pages under `groups/[groupId]/`.
38+
39+
### src/components/
40+
41+
Reusable components. shadcn/UI primitives in `ui/`. Feature components at root.
42+
43+
### src/trpc/
44+
45+
- `init.ts` - tRPC config, SuperJSON transformer
46+
- `routers/_app.ts` - Root router composition
47+
- `routers/groups/` - Group domain (expenses, balances, stats, activities)
48+
- `routers/categories/` - Category CRUD
49+
50+
### src/lib/
51+
52+
- `api.ts` - Database operations (createExpense, updateExpense, etc.)
53+
- `balances.ts` - Balance calculation logic
54+
- `totals.ts` - Expense total calculations
55+
- `schemas.ts` - Zod validation schemas
56+
- `prisma.ts` - Prisma client singleton
57+
- `featureFlags.ts` - Feature toggles (S3 docs, receipt scanning)
58+
59+
## tRPC Router Hierarchy
60+
61+
```
62+
appRouter
63+
├── groups
64+
│ ├── get, getDetails, list, create, update
65+
│ ├── expenses (list, get, create, update, delete)
66+
│ ├── balances (list)
67+
│ ├── stats (get)
68+
│ └── activities (list)
69+
└── categories
70+
└── list
71+
```
72+
73+
API calls: `trpc.groups.expenses.create()`, `trpc.groups.balances.list()`, etc.
74+
75+
## Feature Flags
76+
77+
Env vars for optional features:
78+
79+
- `NEXT_PUBLIC_ENABLE_EXPENSE_DOCUMENTS` - S3 image uploads
80+
- `NEXT_PUBLIC_ENABLE_RECEIPT_EXTRACT` - GPT-4V receipt scanning
81+
- `NEXT_PUBLIC_ENABLE_CATEGORY_EXTRACT` - AI category suggestions

.agent/database.md

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
# Database
2+
3+
## Setup
4+
5+
```bash
6+
./scripts/start-local-db.sh # Start PostgreSQL container
7+
npx prisma migrate dev # Run migrations
8+
npx prisma studio # GUI for database
9+
npx prisma generate # Regenerate client after schema changes
10+
```
11+
12+
## Prisma Client Singleton
13+
14+
```typescript
15+
// src/lib/prisma.ts
16+
import { PrismaClient } from '@prisma/client'
17+
18+
const globalForPrisma = global as unknown as { prisma: PrismaClient }
19+
20+
export const prisma = globalForPrisma.prisma || new PrismaClient()
21+
22+
if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma
23+
```
24+
25+
Dev mode uses global singleton to survive hot reload.
26+
27+
## Schema Changes
28+
29+
1. Edit `prisma/schema.prisma`
30+
2. Run `npx prisma migrate dev --name descriptive_name`
31+
3. Commit migration file + schema changes together
32+
33+
## Query Patterns
34+
35+
### Create with Relations
36+
37+
```typescript
38+
// src/lib/api.ts - createExpense
39+
await prisma.expense.create({
40+
data: {
41+
groupId,
42+
title,
43+
amount,
44+
paidById: paidBy,
45+
splitMode,
46+
expenseDate,
47+
paidFor: {
48+
createMany: {
49+
data: paidFor.map(({ participant, shares }) => ({
50+
participantId: participant,
51+
shares,
52+
})),
53+
},
54+
},
55+
},
56+
})
57+
```
58+
59+
### Query with Includes
60+
61+
```typescript
62+
// Expenses with payer and split details
63+
await prisma.expense.findMany({
64+
where: { groupId },
65+
include: {
66+
paidBy: true,
67+
paidFor: { include: { participant: true } },
68+
category: true,
69+
},
70+
orderBy: [{ expenseDate: 'desc' }, { createdAt: 'desc' }],
71+
})
72+
```
73+
74+
### Update with Nested Operations
75+
76+
```typescript
77+
// Update expense and replace paidFor entries
78+
await prisma.expense.update({
79+
where: { id: expenseId },
80+
data: {
81+
title,
82+
amount,
83+
paidFor: {
84+
deleteMany: {}, // Remove all existing
85+
createMany: { data: newPaidFor },
86+
},
87+
},
88+
})
89+
```
90+
91+
## Transactions
92+
93+
Used for atomic operations:
94+
95+
```typescript
96+
// src/lib/api.ts - createRecurringExpenses
97+
await prisma.$transaction(async (tx) => {
98+
const expense = await tx.expense.create({ data: expenseData })
99+
await tx.recurringExpenseLink.update({
100+
where: { id: linkId },
101+
data: { nextExpenseCreatedAt: nextDate },
102+
})
103+
return expense
104+
})
105+
```
106+
107+
## Amount Storage
108+
109+
All monetary values stored as **integers in cents**:
110+
111+
- `100` = $1.00
112+
- `15050` = $150.50
113+
114+
Split shares vary by mode:
115+
116+
- `EVENLY`: 1 per participant
117+
- `BY_SHARES`: Weight integers (1, 2, 3...)
118+
- `BY_PERCENTAGE`: Basis points (2500 = 25%)
119+
- `BY_AMOUNT`: Cents directly

.agent/testing.md

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
# Testing
2+
3+
## Jest Unit Tests
4+
5+
```bash
6+
npm test # Run all tests
7+
npm test -- --watch # Watch mode
8+
npm test -- path/to/file.test.ts # Specific file
9+
```
10+
11+
Tests in `src/**/*.test.ts` alongside implementation.
12+
13+
### Test Data Factory Pattern
14+
15+
```typescript
16+
// src/lib/balances.test.ts
17+
const makeExpense = (overrides: Partial<BalancesExpense>): BalancesExpense =>
18+
({
19+
id: 'e1',
20+
expenseDate: new Date('2025-01-01T00:00:00.000Z'),
21+
title: 'Dinner',
22+
amount: 0,
23+
isReimbursement: false,
24+
splitMode: 'EVENLY',
25+
paidBy: { id: 'p0', name: 'P0' },
26+
paidFor: [{ participant: { id: 'p0', name: 'P0' }, shares: 1 }],
27+
...overrides,
28+
}) as BalancesExpense
29+
30+
// Usage
31+
const expenses = [
32+
makeExpense({
33+
amount: 100,
34+
paidBy: { id: 'p0', name: 'P0' },
35+
paidFor: [
36+
{ participant: { id: 'p0', name: 'P0' }, shares: 1 },
37+
{ participant: { id: 'p1', name: 'P1' }, shares: 1 },
38+
],
39+
}),
40+
]
41+
```
42+
43+
### Focus Areas
44+
45+
- `balances.test.ts` - Balance calculations, split modes, edge cases
46+
- `totals.test.ts` - Expense totals, user shares
47+
- `currency.test.ts` - Currency formatting
48+
49+
## Playwright E2E Tests
50+
51+
```bash
52+
npm run test-e2e # Runs against local dev server
53+
```
54+
55+
Tests in `tests/e2e/*.spec.ts` and `tests/*.spec.ts`.
56+
57+
### Test Helpers (`tests/helpers/`)
58+
59+
| Helper | Purpose |
60+
| ----------------------------------------------- | ------------------------ |
61+
| `createGroupViaAPI(page, name, participants)` | Fast group setup via API |
62+
| `createExpense(page, { title, amount, payer })` | Fill expense form |
63+
| `navigateToExpenseCreate(page, groupId)` | Go to expense creation |
64+
| `fillParticipants(page, names)` | Add participants to form |
65+
| `selectComboboxOption(page, label, value)` | Select dropdown value |
66+
67+
### Stability Patterns
68+
69+
```typescript
70+
// Wait after navigation
71+
await page.goto(`/groups/${groupId}`)
72+
await page.waitForLoadState()
73+
74+
// Wait for URL after form submission
75+
await page.getByRole('button', { name: 'Create' }).click()
76+
await page.waitForURL(/\/groups\/[^/]+\/expenses/)
77+
78+
// Use API for fast setup
79+
const groupId = await createGroupViaAPI(page, 'Test Group', ['Alice', 'Bob'])
80+
```
81+
82+
### Example Test
83+
84+
```typescript
85+
import { createExpense } from '../helpers'
86+
import { createGroupViaAPI } from '../helpers/batch-api'
87+
88+
test('creates expense with correct values', async ({ page }) => {
89+
const groupId = await createGroupViaAPI(page, `Test ${randomId(4)}`, [
90+
'Alice',
91+
'Bob',
92+
])
93+
await page.goto(`/groups/${groupId}/expenses`)
94+
95+
await createExpense(page, {
96+
title: 'Dinner',
97+
amount: '150.00',
98+
payer: 'Alice',
99+
})
100+
101+
await expect(page.getByText('Dinner')).toBeVisible()
102+
await expect(page.getByText('$150.00')).toBeVisible()
103+
})
104+
```
105+
106+
### Config Notes
107+
108+
- `fullyParallel: false` in playwright.config.ts prevents DB conflicts
109+
- Runs Chromium, Firefox, WebKit
110+
- `json` reporter when `CLAUDE_CODE` env var detected

0 commit comments

Comments
 (0)