seatkit

SeatKit - Architectural Decisions Document

Status: ✅ Complete - All Architectural Decisions Made Created: 2025-10-25 Last Updated: 2025-10-25 Original App: KoenjiApp (Swift/iOS) Target: Modern TypeScript Web Application

Phase 1 (Foundation): ✅ Complete Phase 2 (Core Architecture): ✅ Complete Phase 3 (Features & Operations): ✅ Complete


Purpose

This document captures all major architectural decisions for the SeatKit project - a restaurant reservation management system being ported from a Swift iOS app to a modular, open-source TypeScript ecosystem.

Why this matters: These decisions are foundational and expensive to change later. Making them explicit now ensures consistency, prevents technical debt, and enables informed trade-offs.


Table of Contents

  1. Project Structure & Monorepo Strategy
  2. Language & Runtime
  3. Type System & Validation
  4. Database Strategy
  5. API Architecture
  6. Backend Framework
  7. Frontend Framework & Platform
  8. State Management
  9. Authentication & Authorization
  10. Real-Time Synchronization
  11. File Storage & Media
  12. Testing Strategy
  13. Build & Deployment
  14. Internationalization (i18n)
  15. Logging & Monitoring
  16. Development Workflow

1. Project Structure & Monorepo Strategy

✅ Decision Made

Monorepo Tool: Turborepo with pnpm workspaces

Package Structure:

seatkit/
├── packages/
│   ├── types/       - Pure TypeScript types + Zod schemas (built FIRST)
│   ├── utils/       - Shared utilities
│   ├── engine/      - Business logic (reservations, layout, clustering)
│   ├── ui/          - shadcn-based Design System components
│   ├── api/         - Backend API/data layer
│   ├── web/         - Web application (responsive for desktop/mobile)
│   └── config/      - Shared configs (ESLint, TS, Prettier, etc.)
└── package.json     (workspace root)

Configuration Approach: Hybrid - Base configs in @seatkit/config, extended per-package as needed

Rationale

  1. Turborepo: Provides build caching and orchestration for better DX as the project grows. Easy to set up, minimal overhead for solo development, scales well if team grows.

  2. Package organization:
    • Separated concerns for modularity and potential future independent publishing
    • types as foundation ensures type safety across all packages
    • ui as separate package follows design system best practices (shadcn as base)
    • Single web app (no mobile/desktop initially) but architecture allows adding later
  3. Build order: Turborepo automatically handles dependency graph (typesutils/engine/uiapi/web)

Notes


2. Language & Runtime

✅ Decision Made

Language: TypeScript 5.x with maximum strictness

TypeScript Configuration:

{
	"compilerOptions": {
		"strict": true,
		"noUncheckedIndexedAccess": true,
		"noImplicitOverride": true,
		"exactOptionalPropertyTypes": true
	}
}

Runtime: Node.js 22.x (with >=20.0.0 fallback support)

Module System: Pure ESM ("type": "module")

Rationale

  1. Maximum TypeScript strictness: Matches the strong typing discipline from the Swift app. Catches errors at compile time rather than runtime. Non-negotiable for a complex domain model with enums, statuses, and business rules.

  2. Node.js 22: Latest stable release, becomes LTS in October 2025. Fresh start allows using modern Node features. Fallback to >=20.0.0 ensures compatibility with common hosting platforms.

  3. Pure ESM: Modern standard, future-proof. It’s 2025, and most ecosystem supports ESM now. Matches the forward-thinking approach of the original Swift app.

Engine Requirements

All packages will specify:

{
	"type": "module",
	"engines": {
		"node": ">=20.0.0"
	}
}

3. Type System & Validation

✅ Decision Made

Validation Library: Zod

Validation Points:

Error Handling Strategy: Hybrid approach

Rationale

  1. Zod: Best TypeScript integration, type inference from schemas, excellent error messages, works everywhere (Node, browser, edge). Natural successor to Swift’s Codable.

  2. Validation points:
    • HTTP/DB boundaries are critical - untrusted external data must be validated
    • Package boundaries skipped for now - all packages under our control, adds overhead without clear benefit
    • Can revisit if packages are published independently
  3. Hybrid error handling: Best of both worlds
    • At boundaries: Use safeParse() for explicit, type-safe error handling
    • Internally: Use parse() for cleaner code, trusting already-validated data

Example Pattern

// At API boundary (safeParse)
app.post('/reservations', async (req, res) => {
	const result = ReservationSchema.safeParse(req.body);

	if (!result.success) {
		return res.status(400).json({ error: result.error });
	}

	// Pass validated data internally
	const reservation = await reservationService.create(result.data);
	return res.json(reservation);
});

// Internal service (parse or no validation)
class ReservationService {
	async create(data: Reservation) {
		// Data already validated, just use it
		return await db.insert(data);
	}
}

Schema Organization

Schemas will live in @seatkit/types alongside TypeScript types:

// @seatkit/types/src/reservation.ts
export const ReservationSchema = z.object({...});
export type Reservation = z.infer<typeof ReservationSchema>;

4. Database Strategy

✅ Decision Made

Primary Database: PostgreSQL via Supabase

ORM: Drizzle ORM

Real-time Features: Supabase Realtime (Postgres Changes, Broadcast, Presence)

Offline Support: Server-authoritative initially (Phase 2 feature for offline)

Migrations: Drizzle Kit migrations

Rationale

  1. PostgreSQL via Supabase:
    • Relational model: Perfect fit for reservation ↔ table ↔ sales relationships
    • ACID compliance: Critical for preventing double-booking conflicts
    • All-in-one: Database + Real-time + Auth + Storage in one platform
    • Cost-effective: $0 for development, $25/month for production (vs $20-30/month Firestore with better features)
    • Self-hosting option: Can migrate to self-hosted Postgres later if needed
    • Open source aligned: Postgres is open source, aligns with project goals
  2. Supabase Real-time Features:
    • Postgres Changes: Database change subscriptions (like Firestore snapshots) for reservation updates
    • Broadcast: Pub/sub for “user X is editing reservation Y” notifications
    • Presence: Track which staff members are currently online/active
    • Globally distributed: Low-latency real-time updates
    • All three cover the Swift app’s real-time collaboration requirements
  3. Drizzle ORM:
    • Lightweight: Minimal runtime overhead compared to Prisma
    • SQL-like: Feels like writing SQL, easier to optimize
    • Type-safe: Full TypeScript inference without code generation
    • Relations: Supports complex joins and relations
    • Flexible: Can drop to raw SQL when needed
    • Prisma comparison: Lighter, faster, more control vs Prisma’s simplicity
  4. Server-authoritative (initially):
    • Simpler MVP: Avoids complex offline sync conflicts
    • Real-time sufficient: Supabase real-time provides <1s updates
    • Restaurant context: Staff typically have stable WiFi in restaurant
    • Future-proof: Can add offline support in Phase 2 if needed
  5. Scalability:
    • Current load: 144k reads + 1.4k writes per week = easily handled
    • 15 concurrent users: Well within 200+ connection limit (free tier)
    • Room to grow: Can scale to multiple restaurants without issues

Database Schema Organization

Schemas will be defined in @seatkit/api using Drizzle:

// @seatkit/api/src/db/schema/reservations.ts
import {
	pgTable,
	uuid,
	timestamp,
	varchar,
	integer,
} from 'drizzle-orm/pg-core';

export const reservations = pgTable('reservations', {
	id: uuid('id').defaultRandom().primaryKey(),
	guestName: varchar('guest_name', { length: 255 }).notNull(),
	partySize: integer('party_size').notNull(),
	reservationTime: timestamp('reservation_time').notNull(),
	// ... more fields
});

Migration Strategy

Real-time Architecture

┌─────────────┐
│   Clients   │ ← Supabase Realtime (WebSocket)
│ (15 staff)  │
└──────┬──────┘
       │
       ↓
┌─────────────────────┐
│  Supabase Platform  │
│ ┌─────────────────┐ │
│ │   PostgreSQL    │ │ ← Drizzle ORM
│ │    Database     │ │
│ └─────────────────┘ │
│ ┌─────────────────┐ │
│ │  Realtime API   │ │
│ │ • Postgres Chg  │ │ ← Auto sync data changes
│ │ • Broadcast     │ │ ← Custom pub/sub messages
│ │ • Presence      │ │ ← Online user tracking
│ └─────────────────┘ │
└─────────────────────┘
       ↑
       │
┌──────┴──────┐
│ Fastify API │
│  (Backend)  │
└─────────────┘

Notes


5. API Architecture

Decision Required

How do clients communicate with the backend?

Options

API Style

Real-time Requirements

From Swift app:

Options:

Questions to Answer

  1. Primary API style:
    • REST (most universal)
    • tRPC (best DX for TS full-stack)
    • GraphQL (flexible, complex)
    • Hybrid (REST + WebSockets)
  2. Real-time approach:
    • WebSockets (full bidirectional)
    • SSE (server→client only)
    • Database-native (Supabase/Firebase)
    • Not needed initially
  3. API versioning:
    • URL versioning (/api/v1/...)
    • Header versioning
    • No versioning (breaking changes in major versions)
  4. Documentation:
    • OpenAPI/Swagger (REST)
    • Auto-generated from types (tRPC)
    • GraphQL introspection
    • Manual documentation

Recommendation Needed

Decision: [To be filled in]

Rationale: [Why this choice?]


6. Backend Framework

✅ Decision Made

Framework: Fastify

Validation: Zod + @fastify/type-provider-zod

WebSocket Support: @fastify/websocket

Deployment Target: Traditional servers (Fly.io, Railway) with flexibility for containers

Rationale

  1. Fastify Performance:
    • ~30,000 req/sec: Fastest mature Node.js framework
    • Low overhead: Minimal memory footprint, critical for real-time operations
    • Async-first: Built for async/await, matches modern Node.js
    • Request validation: ~20% faster than Express for typical workloads
  2. TypeScript Integration:
    • Type providers: Native TypeScript support with inference
    • @fastify/type-provider-zod: Perfect integration with our Zod schemas
    • Type-safe routes: Request/response types automatically inferred
    • Better than Express: Express has poor TS support, requires many @types packages
  3. Real-time Requirements:
    • WebSocket support: @fastify/websocket plugin for custom real-time (if needed beyond Supabase)
    • Low latency: Critical for <200ms operation target
    • Concurrent connections: Handles 15+ staff members easily
    • Supabase compatible: Works perfectly with Supabase Realtime alongside custom endpoints
  4. Developer Experience:
    • Plugin ecosystem: Rich ecosystem (auth, CORS, rate limiting, etc.)
    • Schema-first: Matches our validation-heavy approach
    • JSON Schema: Native support, works well with Zod
    • Excellent docs: Comprehensive documentation and examples
  5. Why NOT the alternatives:
    • Express: Too slow, poor TS support, callback-based
    • Hono: Newer, less ecosystem for complex real-time needs
    • NestJS: Too opinionated, heavy for our use case, DI overhead not needed
    • tRPC: Couples frontend/backend too tightly for open source with potential non-TS clients

Example Integration with Zod

// @seatkit/api/src/routes/reservations.ts
import { FastifyPluginAsync } from 'fastify';
import { Type as T } from '@fastify/type-provider-zod';
import { ReservationSchema } from '@seatkit/types';

export const reservationsRoutes: FastifyPluginAsync = async fastify => {
	fastify.post(
		'/reservations',
		{
			schema: {
				body: ReservationSchema,
				response: {
					201: ReservationSchema,
				},
			},
		},
		async (request, reply) => {
			const reservation = await fastify.db.createReservation(request.body);
			return reply.code(201).send(reservation);
		},
	);
};

Plugin Strategy

Core plugins we’ll use:

Notes


7. Frontend Framework & Platform

Decision Required

Which platform(s) and framework(s) for the user interface?

Target Platforms from Swift App

Currently iOS only. Expanding to:

Options

Web Frameworks

Mobile (Cross-Platform)

Desktop

Questions to Answer

  1. Primary frontend framework:
    • React (Next.js for full-stack)
    • Vue (Nuxt)
    • Svelte (SvelteKit)
    • Other: ___
  2. Platform priority (order matters):
    • 1. ___ 2. ___ 3. ___
  3. Mobile strategy:
    • React Native/Expo (native feel)
    • Capacitor (reuse web code)
    • PWA only (no app store)
    • Keep Swift app, add Android later
  4. Desktop strategy:
    • Electron (full desktop app)
    • Tauri (lighter desktop app)
    • Web only (no desktop app)
  5. Code sharing goal:
    • Maximum sharing (web + mobile + desktop same code)
    • Shared logic only (UI differs per platform)
    • Platform-specific (best UX per platform)

Recommendation Needed

Decision: [To be filled in]

Rationale: [Why this choice?]


8. State Management

Decision Required

How do we manage application state, especially with real-time data?

Context from Swift App

Options

React Ecosystem

Universal (Framework-Agnostic)

Real-Time Sync

Questions to Answer

  1. Client state management (UI state, forms, etc.):
    • React Context + hooks (simple)
    • Zustand (recommended for most cases)
    • MobX (similar to Swift’s observables)
    • Other: ___
  2. Server state management (data from API):
    • TanStack Query (highly recommended)
    • SWR
    • Apollo (if GraphQL)
    • Manual fetch + state
  3. Real-time updates approach:
    • WebSocket connection + state updates
    • Server-Sent Events + TanStack Query invalidation
    • Polling with TanStack Query
    • Database-native real-time (Supabase)
  4. State architecture:
    • Multiple stores (like Swift: ReservationStore, TableStore, etc.)
    • Single store (Redux style)
    • Atomic/distributed state (Jotai/Recoil)
    • Feature-based (each feature owns its state)

✅ Decision Made

Decision: TanStack Query + Zustand (Override: Previously planned Redux Toolkit + RTK Query)

Rationale:

  1. Separation of Concerns: TanStack Query handles server state (API data, caching, refetching), Zustand handles UI state (modals, filters, form drafts)
  2. Code Volume: ~63% less boilerplate (~420 LoC vs ~1,120 LoC with Redux Toolkit across 7 entities)
  3. Developer Experience: Simpler learning curve, better TypeScript support, modern patterns
  4. Real-time Ready: Easy integration with Supabase Realtime via query invalidation
  5. Next.js Compatible: Works seamlessly with React Server Components
  6. Modern Standard: Industry momentum toward TanStack Query for React server state

Implementation Details:

See ADR-003 for complete decision rationale and implementation patterns.


9. Authentication & Authorization

Decision Required

How do users sign in? How do we manage permissions?

Context from Swift App

Options

Authentication Providers

Authentication Methods

From Swift app:

Authorization Model

Questions to Answer

  1. Auth provider:
    • Supabase Auth (if using Supabase DB)
    • Clerk (easiest DX)
    • Auth.js (most flexible)
    • Roll your own
  2. Primary auth methods (check all):
    • Email + Password
    • Apple Sign-In
    • Google Sign-In
    • Magic links
    • Other: ___
  3. Session management:
    • JWT tokens (stateless)
    • Session cookies (stateful)
    • Refresh token rotation
    • Device tracking (like Swift app)
  4. Authorization approach:
    • RBAC with roles
    • Database RLS (Postgres)
    • API-level checks
    • Combination
  5. Multi-device support (like Swift app):
    • Yes, track active devices
    • Yes, but limit concurrent sessions
    • No, single device only

Recommendation Needed

Decision: [To be filled in]

Rationale: [Why this choice?]


10. Real-Time Synchronization

Decision Required

How do multiple users see live updates? How do we prevent conflicts?

Context from Swift App

Options

Real-Time Technologies

Conflict Resolution

Presence & Awareness

Questions to Answer

  1. Real-time technology:
    • WebSockets (bidirectional)
    • SSE (server → client)
    • Database real-time (Supabase)
    • Polling (simple fallback)
  2. Conflict resolution strategy:
    • Pessimistic locking (reserve before editing)
    • Optimistic locking (version numbers)
    • CRDTs (automatic merging)
    • Last-write-wins (simple)
  3. Presence system (like Swift’s SessionStore):
    • Yes, show active users
    • Yes, show who’s editing what
    • Basic online/offline only
    • Not needed initially
  4. Offline editing:
    • Yes, sync when reconnected
    • No, require connection
    • Read-only offline

Recommendation Needed

Decision: [To be filled in]

Rationale: [Why this choice?]


11. File Storage & Media

Decision Required

Where do we store profile images, drawings (Scribble), and other media?

Context from Swift App

Options

Storage Services

Image Processing

Drawing/Canvas Data

From Swift app:

Options:

Questions to Answer

  1. Primary storage:
    • Supabase Storage (if using Supabase)
    • Cloudflare R2 (cheap, fast)
    • AWS S3 (standard)
    • Other: ___
  2. Image processing:
    • Server-side (Sharp)
    • Service (Cloudinary, imgix)
    • Client-side only
    • Not needed (store originals)
  3. Drawing/Scribble format:
    • SVG (vector)
    • Canvas + PNG (raster)
    • JSON-based (Excalidraw/similar)
    • Research needed
  4. CDN:
    • Yes, use CDN for assets
    • No, serve directly
    • Built-in (Cloudflare, Vercel)

Recommendation Needed

Decision: [To be filled in]

Rationale: [Why this choice?]


12. Testing Strategy

Decision Required

How do we test the application at different levels?

Testing Pyramid

        ╱╲
       ╱  ╲     E2E Tests (Few)
      ╱────╲
     ╱      ╲   Integration Tests (Some)
    ╱────────╲
   ╱          ╲ Unit Tests (Many)
  ╱────────────╲

Options

Unit Testing

Integration Testing

E2E Testing

Component Testing (if React)

Questions to Answer

  1. Unit test framework:
    • Vitest (recommended)
    • Jest
    • Node:test
  2. E2E test framework:
    • Playwright (recommended)
    • Cypress
    • None initially
  3. Test coverage goals:
    • Business logic: ____%
    • API endpoints: ____%
    • UI components: ____%
  4. Testing approach:
    • TDD (write tests first)
    • Test after implementation
    • Mix based on complexity
  5. CI/CD testing:
    • All tests on every commit
    • Unit tests on commit, E2E on PR
    • Manual testing

Recommendation Needed

Decision: [To be filled in]

Rationale: [Why this choice?]


13. Build & Deployment

Decision Required

How do we build and where do we deploy?

Build Tools

Monorepo Build

Bundlers

Deployment Options

Frontend Hosting

Backend Hosting

Database Hosting

Questions to Answer

  1. Build orchestration:
    • Turborepo (recommended if complex)
    • Just npm workspaces (simple)
    • Nx (enterprise)
  2. Primary bundler:
    • Vite (for apps)
    • tsup (for libraries)
    • Combination
  3. Frontend deployment:
    • Vercel (easiest for Next.js)
    • Cloudflare Pages (fastest)
    • Self-hosted
    • Other: ___
  4. Backend deployment:
    • Fly.io (recommended)
    • Railway (simplest)
    • AWS/GCP (enterprise)
    • Self-hosted
  5. Database hosting:
    • Supabase (all-in-one)
    • Neon (just Postgres)
    • Same as backend (Railway, Fly)
    • Self-hosted
  6. CI/CD:
    • GitHub Actions (free)
    • GitLab CI
    • CircleCI
    • Platform-native (Vercel, Railway auto-deploy)
  7. Environment strategy:
    • Dev → Staging → Production
    • Dev → Production
    • Preview deployments for PRs (Vercel-style)

Recommendation Needed

Decision: [To be filled in]

Rationale: [Why this choice?]


14. Internationalization (i18n)

Decision Required

How do we handle multiple languages?

Context from Swift App

Options

i18n Libraries

Approach

Questions to Answer

  1. i18n library:
    • next-intl (if Next.js)
    • react-i18next (universal React)
    • FormatJS (complex needs)
    • Other: ___
  2. Supported languages (initial):
    • Italian (primary)
    • English
    • Japanese (given restaurant name “Koenji”)
    • Others: ___
  3. Translation management:
    • JSON files in repo
    • Translation service (Lokalise, Phrase, Crowdin)
    • Database-driven
  4. Date/time formatting:
    • date-fns with locales
    • Luxon
    • Day.js
    • Native Intl API

Recommendation Needed

Decision: [To be filled in]

Rationale: [Why this choice?]


15. Logging & Monitoring

Decision Required

How do we track errors, performance, and usage?

Categories

Application Logging

Error Tracking

Performance Monitoring

Analytics

Questions to Answer

  1. Logging library:
    • Pino (recommended)
    • Winston
    • Simple console
  2. Error tracking:
    • Sentry (recommended)
    • Self-hosted alternative
    • None initially
  3. Performance monitoring:
    • Sentry (if using for errors)
    • Separate APM tool
    • None initially
  4. Analytics:
    • PostHog (full product analytics)
    • Plausible (simple page views)
    • None initially
    • Custom events only
  5. Log aggregation:
    • Platform logs (Vercel, Fly.io)
    • Dedicated service (Papertrail, Logtail)
    • Self-hosted (Loki)
    • Not needed initially

Recommendation Needed

Decision: [To be filled in]

Rationale: [Why this choice?]


16. Development Workflow

Decision Required

How do we organize day-to-day development?

Topics

Git Workflow

Branch Naming

Commit Convention

Code Review

Release Strategy

Changesets/Changelogs

✅ Decision Made

Package Manager: pnpm

Git Workflow: GitHub Flow (trunk-based development)

Commit Convention: Conventional Commits (enforced with Husky + Commitlint)

Branch Naming:

Code Review: Optional (solo developer, open source project)

Release Management: Changesets for monorepo versioning

Development Environment: Local installation (simple, no Docker overhead)

Rationale

  1. pnpm: Significantly faster than npm, more efficient disk usage, better monorepo support. Industry standard for modern monorepos.

  2. GitHub Flow: Simple workflow perfect for solo/small team:
    • main branch always deployable
    • Create feature branches
    • Open PR (even if solo - good discipline)
    • Merge when ready
    • No complex develop branch
  3. Conventional Commits: Enables automatic changelog generation, clear history, semantic versioning
    • Format: feat:, fix:, chore:, docs:, etc.
    • Example: feat(engine): add table clustering algorithm
    • Enforced with Husky pre-commit hooks
  4. Changesets: Industry standard for monorepo releases, handles:
    • Per-package versioning
    • Interdependencies
    • Automatic changelogs
    • npm publishing
  5. Local development: Simpler than Docker, faster iteration. Docker can be added later for production parity if needed.

Git Configuration

# Husky pre-commit hooks will enforce:
- Conventional commit format
- Lint-staged (ESLint, Prettier on changed files)
- Type checking

# Branch protection (when team grows):
- Require PR before merging to main
- Require status checks to pass

Release Process

# Developer workflow:
pnpm changeset           # Document changes
pnpm changeset version   # Bump versions
pnpm release             # Build + publish to npm

Decision Summary Table

Quick reference for all architectural decisions:

# Category Decision Status
1 Monorepo Tool Turborepo + pnpm workspaces ✅ Decided
2 Language/Runtime TypeScript 5.x (strict) + Node.js 22 + Pure ESM ✅ Decided
3 Validation Zod (HTTP/DB boundaries, safeParse + parse hybrid) ✅ Decided
4 Database PostgreSQL (Supabase) + Drizzle ORM ✅ Decided
5 API Style REST + Supabase Realtime ✅ Decided
6 Backend Framework Fastify + Zod validation ✅ Decided
7 Frontend Framework Next.js 15 + React 19 + shadcn/ui ✅ Decided
8 State Management TanStack Query + Zustand (override: was Redux Toolkit) ✅ Decided
9 Authentication Supabase Auth ✅ Decided
10 Real-Time Supabase Realtime (Postgres Changes + Broadcast + Presence) ✅ Decided
11 File Storage Supabase Storage ✅ Decided
12 Testing Vitest + Playwright + React Testing Library ✅ Decided
13 Deployment Docker Compose (dev) → k3s (production) ✅ Decided
14 i18n next-intl (Italian/English/Japanese) ✅ Decided
15 Monitoring Self-hosted: Prometheus + Grafana + Loki + Sentry ✅ Decided
16 Workflow pnpm + GitHub Flow + Conventional Commits + Changesets ✅ Decided

Phase 1 Complete ✅

Foundation decisions locked in:

Phase 2 Complete ✅

Core architecture decisions locked in:

Phase 3 Complete ✅

Application & operations decisions locked in:


Next Steps

After filling in decisions:

  1. Review & validate - Are decisions consistent? Any conflicts?
  2. Create ADR files - Detailed Architecture Decision Records for each major choice
  3. Update project structure - Align with decisions
  4. Set up tooling - Install and configure chosen tools
  5. Create proof-of-concept - Validate decisions with working code
  6. Document conventions - CONTRIBUTING.md, code style guides

References & Resources


Status Legend: