Overview
Four decisions that shaped FX Checker. Each has context, choice, why, and trade-off — no jargon wall.
ADR-001: Server Actions as the Data Boundary
Status: Accepted
Context: The app is Next.js-only and calls a third-party FX API from client components via React Query.
Decision: Mark services/api.ts with "use server" and import its typed functions directly as queryFns — no Route Handlers, no API routes.
Why: Typed, colocated I/O with zero route boilerplate, and the fetch cache (revalidate) applies exactly at the data boundary.
Alternatives: Route Handlers under app/api/ (more boilerplate for one route), direct client fetches (exposes the data layer, loses caching).
Trade-off: Server actions only run when a client triggers them — fine for this single-route app, unsuitable if other services needed the same data.
ADR-002: Zustand + localStorage Instead of a Database
Status: Accepted
Context: Favorites, conversion log, and tab state should survive reloads — but the product has no accounts and no backend.
Decision: One central Zustand store; persist a partialized slice (favorites/log/tab) via the persist middleware to localStorage.
Why: A single store eliminates prop drilling; persistence is a two-line upgrade; user data stays private on-device and the server's attack surface stays zero.
Alternatives: Context + useReducer (rejected for frequent fine-grained reads), Postgres/Supabase or IndexedDB (overkill for zero-login scope).
Trade-off: No cross-device sync; localStorage is opaque to search. Acceptable for a personal tool.
ADR-003: Tailwind v4 Inline Theme Tokens on an 8px Grid
Status: Accepted
Context: A dark palette, lime accent, and tight consistent spacing define the design system.
Decision: Anchor spacing tokens on an 8px grid (p-100 = 8px through p-1600 = 128px, with a few half-steps for fine control) and map raw CSS variables to semantic utilities (bg-currency-field-bg) inside @theme inline.
Why: Token names communicate absolute values at a glance; semantic color names insulate components from palette changes.
Alternatives: Default Tailwind scale, arbitrary values everywhere, per-component CSS files.
Trade-off: New spacing steps require touching the token table; occasional inline values (rounded-[8px]) where no token exists.
ADR-004: Single-Round-Trip Ticker with Business-Day Splitting
Status: Accepted
Context: The marquee needs today's and the previous business day's rates per pair; two ?date= calls returned inconsistent windows after weekends.
Decision: Fetch a 7-day range once, split the flat response by distinct date into "latest" and "second-latest" business days, then join pairs.
Why: One upstream call instead of two, stable cache keys, correct day-over-day deltas across weekends and holidays.
Alternatives: Two ?date= calls (the previous behavior — inconsistent), per-pair history calls (multiplies requests).
Trade-off: Larger range payload than a single-date response — absorbed by the 1-hour server cache and invisible to clients.
Common thread
Cache politely at the boundary, keep state in one place, make tokens carry meaning, and let the data's real shape (business days) drive the logic.