This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
pnpm dev # Start development server
pnpm build # Production build
pnpm lint # Run ESLintThere are no test scripts configured. Database commands:
pnpm prisma db push # Push schema.prisma directly to the database
pnpm prisma generate # Regenerate Prisma client after schema changes
pnpm prisma studio # Open Prisma Studio GUIsplit-weiss is a multi-currency expense-sharing app (think Splitwise). Users create groups, add expenses with flexible splitting modes, and track who owes whom across currencies.
- Next.js 15 (App Router) β routing, server components, server actions
- No auth β device-based identity via
DeviceAccess(cookie-linkeddeviceTokenβGroupMember) - Prisma 6 + Neon PostgreSQL β ORM with
@prisma/adapter-neonfor serverless - React Hook Form + Zod β form state and validation
- Decimal.js β precise arithmetic for all financial calculations (2 decimal places, ROUND_DOWN + deterministic remainder distribution)
- shadcn/ui + Tailwind CSS 4 β UI components
Two route groups:
(app)/β public landing and group list (/groups)(group)/β group-scoped routes; layout handles access (password β identity picker β authorized)
Group routes: /groups/[groupId] (expenses), /groups/[groupId]/balances, /groups/[groupId]/settlements, /groups/[groupId]/settings.
All mutations go through server actions in src/app/actions/. Each action:
- Calls
canAccessGroup()to verify device access - Validates input with a Zod schema from
src/lib/validations/ - Performs the DB operation via Prisma
- Calls
revalidatePath()to invalidate Next.js cache - Returns
ActionResult<T>β either{ data }or{ error: string }
Splitting (src/lib/splitting/) β two modes:
PERCENTAGEβ splits by percentage (must sum to 100)LOCKβ fixed amounts, remainder distributed equally among unlocked members
Remainder pennies are distributed deterministically using a seeded xorshift32 PRNG (seeded by total + member IDs).
Balance calculation (src/lib/balances/):
calculator.tsβ aggregates all expenses and settlements in a group, converting to the group's base currency using cached exchange rates, producing a net balance map per usersimplifier.tsβ reduces the balance map to a minimal set of transactions using a greedy debt-simplification algorithm
Currency (src/lib/currency/):
- Exchange rates fetched from the Frankfurter API and cached in the
ExchangeRateCachetable (keyed by base currency + date) converter.tshandles triangulation when direct pairs aren't available
DeviceAccessmaps(groupId, deviceToken)βmemberId- On first visit to a group, user picks "Who are you?" from member list or creates a new member
getCurrentMemberId(groupId)resolves the current device's member for UI highlighting and audit attribution
Schema key points:
Expense.splitModeβ enum:PERCENTAGE | LOCKExpenseandExpenseSplitamounts useDecimal(12,2)for precisionExpenseAuditLogtracks expense mutations with delta-based snapshotsGroupAuditLogtracks member, group, and settlement mutationsExchangeRateCachestores JSON rate snapshots per date to avoid redundant API calls
@/* maps to src/* β use this for all imports.