How dipto0321/spendrift-fe works
1. Overall shape
- Spendrift is a TanStack Start frontend that renders React on the server/client and delegates all business data to an external FastAPI Spendrift API.
- The repo is organized as a feature-based, domain-oriented frontend rather than by technical layer alone.
2. Primary architectural boundary
- The core boundary is the repository seam inside each feature:
- presentation talks to React Query hooks
- hooks call data/repository.ts
- repositories call shared/api/client.ts
- This keeps pages and UI from touching fetch or wire formats directly.
3. Feature structure
- Each major product area lives in src/features/<feature>/:
- auth
- trackers
- expenses
- budgets
- dashboard
- reports
- Each feature is split into:
- domain/ for types, schemas, and pure services
- data/ for DTO mapping, API calls, and query keys
- presentation/ for pages, React Query hooks, and feature UI
4. Domain layer responsibilities
- The domain/ layer contains the business rules that should stay stable even if the API or UI changes.
- Examples:
- budget calculations and savings-health logic
- expense categorization and report aggregation
- feature-level schema validation and typed domain models
- These services are unit-tested with Vitest in the feature folders.
5. Data layer responsibilities
- The data/ layer is the translation layer between API and domain.
- It handles:
- DTO mapping from snake_case to camelCase
- money values as wire strings to domain numbers
- query-key definitions for TanStack Query caching and invalidation
- This is where the app adapts to backend contracts without polluting domain logic.
6. API and auth flow
- src/shared/api/client.ts is the single HTTP client used by repositories.
- Authentication uses JWT access + refresh tokens stored in localStorage.
- The client implements a single-flight refresh-on-401 retry flow, which is important for the architecture because it centralizes auth recovery instead of spreading it across features.
7. Route and shell composition
- Routes are file-based under src/routes/ and map to the major application screens.
- src/routes/__root.tsx acts as the application shell and likely wires:
- shared layout
- provider setup
- auth/workspace gating
- The root-level gate is necessary because SSR cannot read the localStorage-based auth tokens.
8. Tracker as a cross-cutting concept
- A tracker is the app’s key workspace boundary: each tracker is an independent financial context with its own currency, expenses, budgets, and reports.
- The active tracker is carried in the URL as ?tracker=<id>.
- This means tracker selection influences almost every feature and is not just a page-level filter; it is a core application context.
9. Feature interactions
- Expenses is the transactional source of truth for spending data and category management.
- Budgets consumes spending data to compare monthly limits vs actual spend.
- Dashboard aggregates current-month budget, spend, cashflow, and recent expenses.
- Reports consumes historical expense data for time-based analytics and comparisons.
- Auth and Trackers sit underneath those features as access/context providers.
10. UI composition
- The repo uses ShadCN UI / Radix primitives in src/components/ui/ for reusable low-level controls.
- Feature screens build on top of shared app-level components in src/shared/ui/ such as:
- sidebar and page chrome
- stat cards
- theme toggle
- devtools panel
- This separates generic UI primitives from feature-specific controls like expense tables, budget forms, and charts.
11. Design system and styling
- Styling is driven by TailwindCSS with theme tokens defined in src/styles.css.
- The app is intentionally dark-theme-first with emerald-oriented semantic colors.
- public/theme-init.js helps establish the theme early, before React hydration, to avoid flashes of incorrect theme.
12. Charts, analytics, and reporting
- Dashboard and reports rely on chart components built around Recharts through ShadCN chart wrappers.
- These are used for spending trends, category breakdowns, needs-vs-wants views, and multi-year comparisons.
- This makes analytics a first-class UI subsystem rather than a secondary detail.
13. Internationalization and environment
- Translation files live in messages/en.json and messages/de.json, with Inlang project config under project.inlang/.
- Environment handling is centralized in src/env.ts, with the API base URL and Sentry settings coming from .env.local / Vite env vars.
- These are supporting boundaries that affect bootstrapping rather than business logic.
14. Observability and tooling
- instrument.server.mjs indicates server-side instrumentation, aligned with the README’s Sentry integration.
- The repo uses:
- Biome for formatting/linting
- Vitest for domain tests
- TanStack Query devtools in the app for cache inspection
- These tools support the architecture by making the feature/repository split observable and testable.
15. What to draw in an architecture diagram
- Draw the app as a TanStack Start shell on top, with route pages feeding into feature presentation layers.
- Show each feature as a self-contained vertical slice: presentation → data → API, with domain logic beside the data boundary.
- Place shared API client, shared UI, TanStack Query, and tracker context as cross-cutting services.
- External dependencies are primarily the Spendrift FastAPI backend, plus Sentry and the translation/theme infrastructure.
Main entry points: repository.ts Feature data · index.tsx Routes · TrackerContext.tsx Trackers · client.ts API client
Components 22
App Shell
Routing, providers, and bootstrapping
- Root shell · Composes providers, layout, and app-wide gating
src/routes/__root.tsx - Routes · Maps major screens to feature pages
src/routes/index.tsx
Features
Domain-oriented vertical slices
- Trackers · Carries the active tracker across the app
src/features/trackers/presentation/TrackerContext.tsx - Auth · Sign-in, sign-up, profile, and route guards
src/features/auth/presentation/SignInPage.tsx - Expenses · Transactions, categories, and expense management
src/features/expenses/presentation/ExpensePage.tsx - Budgets · Limits, status, and savings-health logic
src/features/budgets/presentation/BudgetPage.tsx - Dashboard · Current-month summary and recent activity
src/features/dashboard/presentation/DashboardPage.tsx - Reports · Historical charts and comparisons
src/features/reports/presentation/ReportsPage.tsx - Feature data · Maps API contracts to domain models and query keys
src/features/expenses/data/repository.ts - Feature domain · Schemas, pure services, and typed models
src/features/budgets/domain/services.ts
Shared Core
Cross-cutting client, UI, and utilities
- Query provider · Cache and server-state coordination
src/integrations/tanstack-query/root-provider.tsx - Query devtools · Inspects client cache and requests
src/integrations/tanstack-query/devtools.tsx - API client · Single fetch boundary with token refresh
src/shared/api/client.ts - Token store · Holds access and refresh tokens
src/shared/api/tokens.ts - UI primitives · Reusable low-level controls
src/components/ui/button.tsx - App UI · Sidebar, headers, stat cards, and theme toggle
src/shared/ui/PageHeader.tsx - Charts · Reusable chart components for analytics screens
src/components/ui/chart.tsx
Platform
Supporting infrastructure and external services
- Theme init · Sets theme before React hydration
public/theme-init.js - Env config · API base URL and observability settings
src/env.ts - Translations · Locale resources and project config
messages/en.json - Spendrift API · FastAPI service providing business data
- Sentry · Error and performance monitoring
Connections 35
- Root shell → Query provider: provides · Sets up TanStack Query and app providers
- Root shell → Routes: hosts · Renders file-based routes inside the app shell
- Root shell → Trackers: gates · Initializes workspace context for the active tracker
- Routes → Auth: loads · Routes to auth screens and profile page
- Routes → Expenses: loads · Routes to expense management screens
- Routes → Budgets: loads · Routes to budget screens
- Routes → Dashboard: loads · Routes to the overview dashboard
- Routes → Reports: loads · Routes to analytics screens
- Auth → API client: uses · Repositories call the shared HTTP client
- Expenses → Feature data: queries · Presentation hooks depend on feature repositories
- Budgets → Feature data: queries · Budget screens consume repository-backed hooks
- Dashboard → Feature data: queries · Dashboard hooks fetch aggregated data
- Reports → Feature data: queries · Report views fetch analytics data
- Feature data → Feature domain: maps to · Repositories adapt DTOs into domain models
- Feature domain → API client: depends on · Domain-facing repositories still read through the API client
- API client → Token store: reads/writes · Uses localStorage tokens for auth and refresh
- API client → Spendrift API: requests · Single transport boundary to the FastAPI API
- API client → Sentry: reports · Surface client-side errors and failures
- Trackers → Feature data: scopes · Active tracker influences repository queries
- Trackers → Dashboard: filters · Dashboard data is bound to the selected tracker
- Trackers → Expenses: filters · Expense operations are tracker-scoped
- Trackers → Budgets: filters · Budget calculations depend on tracker context
- Trackers → Reports: filters · Reports aggregate data per tracker
- UI primitives → App UI: supports · Feature screens are built from shared primitives and chrome
- App UI → Dashboard: composes · Dashboard and other pages use shared layout components
- App UI → Expenses: composes · Expense screens use shared page chrome
- App UI → Budgets: composes · Budget screens use shared page chrome
- App UI → Reports: composes · Report screens use shared page chrome
- Charts → Dashboard: renders · Dashboard cards embed charts and trend visualizations
- Charts → Reports: renders · Reports rely on chart visualizations
- Theme init → Root shell: boots · Pre-hydration theme selection
- Env config → API client: configures · Provides API base URL and runtime settings
- Translations → Root shell: localizes · Message catalog supports app bootstrapping
- Query provider → Feature data: caches · Feature repositories expose query keys and cached requests
- Query devtools → Query provider: inspects · Provides cache visibility during development