Codebase Exploration Path
A sequenced path for getting from "what does this thing even do" to "I can confidently make a change."
Total time: ~1 day if focused, ~3-5 days at a relaxed pace. Each step relies on what came before.
Project at a glance
- Product: Exhale (formerly Palolo) — a fintech offering paycheck-linked financial services: KYC, direct deposits, loans, Earned Wage Access (EWA), Save, Pay Me Now.
- Tech stack: pnpm monorepo, Node 20, TypeScript, Preact (aliased as React via
@preact/compat), Vite, Express, Prisma, Postgres, AWS (SQS, S3, Secrets Manager), Vitest, Terraform, EKS. - Repo size: ~225k LOC, 15,260 commits, 3 years of history (Nov 2021 – Nov 2024).
- BaaS providers: Solid, Liquid (and historically Noble) for money movement; Plaid for account linking; Argyle/Finch for payroll integration; Highline for EWA.
Day 1 — Domain and shape (~3 hours)
Step 1: Understand the product (~30 min)
The domain dictates everything else. Read in this order:
README.md— dev-onboarding view. Skip the setup steps; focus on the "Testing Locally" section — it tells you the real-world entities (BaaS providers, KYC, direct deposits, loans, EWA).PERKS.md— the central domain concept. "Perks" (loans, Save, EWA, Pay Me Now) are the actual product. Eligibility, pre-eligibility, opt-in flows. If you don't understand perks, nothing inpackages/server/src/perks/will make sense.packages/server/prisma/models.prisma— the data model. Skim, don't memorize. Pay attention to:User,Organization,Perk,PerkTemplate,PerkInstance,LinkedAccount,RoutedTransaction,FinancialAccount. The 5,650-line schema is the system.
Step 2: Map the monorepo (~30 min)
Run ls packages/*/src for each package and skim. The mental model:
client/ ← Preact SPA (the user-facing app)
└─ talks to ↓
server/ ← Express API + the Prisma client lives here
└─ enqueues to ↓
worker/ + queue ← async jobs (notifications, payroll syncs, BaaS reconciliation)
└─ all share ↓
shared/ ← types, zod schemas, helpers, the dayjs/decimal/zod wrappers
notifications/ ← email/SMS rendering (separate so it can be previewed locally)
prisma-generator-zod/ ← custom Prisma → Zod schema generator (the "magic")
prisma-superset/ ← Prisma schema preprocessor for mixins
Where activity actually lives: server (6,240 commits) and client (6,001 commits) are nearly tied — full-stack team. Other packages are much smaller.
Step 3: The unusual conventions you must know (~30 min)
These will confuse you if you don't know them upfront:
- Preact masquerades as React. Root
package.jsonaliasesreactandreact-domto@preact/compat. Everywhere you seeimport React from 'react', it's Preact. The patches inautopatch.shexist to make this work. shared/zodis a wrapper, not raw Zod. ESLint enforcesimport { z } from 'shared/zod'(seeeslint.config.mjs). The wrapper addspaloloMeta, customstring()/number(), and integrates with the auth context.shared/dayjsandshared/decimalare also wrappers. Same story — never import the underlying lib directly.- The DB schema is generated.
models.prismais the source;schema.prismais generated byprisma-superset. Top ofschema.prismasays "DO NOT EDIT." - Zod schemas mirror Prisma models automatically via the custom
prisma-generator-zodpackage, output toshared/schema/db/. Sopackages/shared/schema/db/types.ts≈ runtime-validated DB types. - Two BaaS providers exist: Solid and Liquid (and historically Noble — recently removed). The abstraction lives in
packages/server/src/services/baas/. ACH effective-date work in the last 30 commits is all here.
Step 4: Run it (~1 hour)
Skim README.md "Initial Setup" — but don't try to run the full thing if you're just exploring. Instead:
# Just to see code health:
pnpm install
pnpm run types # type-check everything
pnpm run lint
pnpm run test # vitest
The seed data section of the README tells you which test users to log in as if you do bring up the dev server (x go).
Day 2 — Vertical slice through one feature (~3 hours)
Pick EWA (Earned Wage Access) — it's the heart of the product and was the last thing being actively worked on. Trace it end-to-end:
- Schema: find
Perk,PerkInstance,PerkTypeinmodels.prisma. Look forEarnedWageAccess-related models. - Server logic:
packages/server/src/perks/earned-wage-access.ts— has the recentPerkUseReasonsurvey field. - HTTP endpoint:
packages/server/src/endpoints/— find the route that creates an EWA instance. - Routing & money movement:
packages/server/src/routing/index.tsandpackages/server/src/routing/routed-transaction.ts— this is where ACH effective-date logic lives. Recent commits all touched these. - BaaS integration:
packages/server/src/services/baas/solid/index.ts— how transactions actually get sent to the bank. - Worker job:
packages/server/src/queue/jobs/— find the job that processes routed transactions. - Client UI:
packages/client/src/perks/earned-wage-access-submit.tsx. - Tests:
packages/server/src/routing/index.spec.ts— commitf24ed748fadded 171 lines of tests here. Read these. Tests are documentation.
After this slice, you'll know how a single user action becomes money in someone's bank account.
Day 3 — Subsystems by importance (~half-day each, pick one)
By now you understand a vertical slice; pick the subsystem most relevant to whatever you're going to work on.
A. Auth & sessions
packages/server/src/authentication/,packages/server/src/auth.tspackages/client/src/auth/- "magic-login" was the biggest single client effort (PR #1269, 5,575 LOC across 166 files); read its merge diff if you want the design.
B. The async queue
packages/server/src/queue/(jobs, dispatchers)packages/server/src/worker.ts- Uses SQS in prod, LocalStack in dev. Notifications, reminders, BaaS reconciliation all go through here.
C. Notifications (email/SMS/push)
packages/notifications/(renderers — has its own dev server:pnpm run devin that package)packages/server/src/notifications/dispatch/(sending side)- The reminder-predicate fix in PR #1906 lived here.
D. BaaS abstraction
packages/server/src/services/baas/— Solid vs. Liquid, theProviderinterface intypes.ts- This is where the most active recent work has been (effective dates, paycheck routing).
packages/server/src/services/baas/plaid.tsis the account-linking provider, separate from the money-moving providers.
E. Frontend architecture
packages/client/src/router/andpackages/client/src/routes/packages/client/src/flows/— multi-step wizards (KYC, registration, etc.)packages/client/src/forms/— the FormField component used everywhere.packages/client/src/main.tsxis the entry point.
Day 4+ — What to skip or defer
Don't bother with these until you have a reason:
iOS/— nearly dormant since 2023, only 387 total commits, 13 in 2024. If you're not doing mobile work, ignore it.provisioning/terraform/&infra/— only matters if you're deploying or debugging prod.packages/e2e/— only 13 files, 524 LOC, 57 commits ever. Tiny.packages/worker/— empty (the actual worker code lives inserver/src/worker.tsandserver/src/queue/). Don't get confused.packages/prisma-superset/andpackages/prisma-generator-zod/— you almost never need to touch these unless schema generation breaks.
Tactical tips
- Use
git log -p -- <file>aggressively. This codebase is well-commented through commit messages, sometimes more than the code. If a file confuses you, read its history. - Don't read top-down. Pick a real PR (e.g. PR #1907 for paycheck routing) and trace it — fewer files, real context.
- The Prisma schema is the spec. When unsure what a thing is, find it in
models.prisma. Field names are precise. - Tests are concrete docs.
*.spec.tsfiles are your fastest path to "what does this actually do?" - There's no architecture doc, on purpose. This is a startup codebase — read the code, read commits, read tests. Don't expect an
ARCHITECTURE.md.
Suggested 1-week schedule
| Day | Focus | Goal |
|---|---|---|
| 1 | README, PERKS.md, schema, monorepo layout, conventions | Could explain the product to a new hire in 5 min |
| 2 | EWA vertical slice end-to-end | Could trace what happens when a user clicks "Submit EWA" |
| 3 | One subsystem deep dive (whichever you'll work on) | Could make a small change confidently |
| 4 | Run it locally; click through the seeded users | Felt the product, not just the code |
| 5 | Pick a recent PR and read it fully | Understand how the team writes/ships changes |
Recent context (as of last commit Nov 2024)
The project went quiet in mid-November 2024. The last few weeks of activity clustered into four themes:
- ACH effective-date routing (PRs #1904, #1905, #1907) — paychecks now wait for an effective date and are routed at most 1 day before it.
- Onboarding reminder predicate fix (PR #1906) — small bug fix with test.
- "Reason for using perk" survey field (PR #1900) — schema went through enum → strings → JSON object before landing.
- Operational analysis + seed updates — Jupyter notebook for tracking how much capital is fronted from the operational account.
If you're picking up where the team left off, paycheck routing and the BaaS abstraction were the active frontier.