adr-2026-05-17-stack-snapshot

Sun May 17 2026 07:00:00 GMT+0700 (Western Indonesia Time)acceptedtype/adr

ADR 2026-05-17 — Stack snapshot (as-built)

Status: accepted (as-built record, not a forward proposal)

Context

The personal-finance-notion app has been in active development for ~2 months without a written stack rationale. Recording current choices so future-me (or future-collaborator) can tell what was deliberate vs. accidental.

Decision

App layer

  • Next.js 15 (App Router + Turbopack), React 18, TypeScript — single service on port 3000. Turbopack for dev speed.
  • Tailwind + shadcn/ui (Radix) — copy-in components, not a runtime library. Style: default, base color slate, RSC enabled (components.json).
  • React Hook Form + Zod for all forms; schemas centralized in src/lib/validations/.

Data layer

  • MongoDB + Mongoose — document-oriented fits the transaction/category/wishlist shape; no joins needed for the dashboard aggregations. Models in src/lib/models/ (alias @model/*).
  • Hybrid mutation surface: Server Actions (src/lib/actions/, alias @actions/*) for in-app calls; REST under src/app/api/integrations/v1/ for external AI-agent push only. Rationale: Server Actions give type-safe RPC for the UI; REST is required by external bearer-authenticated callers and gives a stable public contract.

Auth

  • JWT via jose + bcryptjs, HTTP-only cookies, Edge middleware (src/middleware.ts) for route protection. Token blacklist + refresh-session models support signout/rotation. Not Auth.js — kept lightweight given solo-user scope.

Offline / PWA

  • next-pwa + IndexedDB outbox under src/lib/offline/. Writes queue locally and reconcile via per-entity merge logic on reconnect. Entity dispatch is centralized (entity-dispatch.ts) so new models slot in without touching the sync loop.

AI / imports

  • OpenRouter as the LLM provider (not a direct vendor) — gives model portability + free-tier fallback chain via OPENROUTER_MODEL. Normalization is chunked + cached + image-downscaled (src/lib/imports/).

Testing

  • Playwright for E2E with POM under tests/pom/; Vitest for unit (mostly the imports pipeline).

Consequences

  • Pros: small surface area for a solo maintainer; Server Actions remove API-route boilerplate for the 95% in-app case; offline outbox makes the PWA genuinely useful on mobile.
  • Cons: hybrid Server-Actions-plus-REST means two patterns to remember (mitigated by REST being scoped to integrations/v1); rolling own auth means rotation/MFA story is manual; LLM normalization has no automated regression coverage.
  • Reversible: stack is conventional Next.js — could swap Mongoose for Prisma, JWT for Auth.js, OpenRouter for direct vendor, without rewriting product surface.
  • Not reversible cheaply: data is Mongo-shaped (embedded sub-docs, no schema migrations beyond Mongoose hooks).

Related

  • [[Projects/personal-finance-notion/context/index]]
  • App repo AGENTS.md, docs/README.md