Architecture overview

The directory layout and dependency rule every change in this codebase follows.

Ahvaan is a Next.js App Router app on top of Supabase (Postgres + Auth + Storage), with Resend for email. The full narrative review lives in docs/02-architecture-review.md; this page is the quick map.

Directory layout

  • src/app/ — routes. A public tier (/, /login, /signup, /privacy, /terms, /docs), the auth-gated dashboard/ tree (host UI — every page and action assumes an authenticated host), and events/[slug]/ (the public guest-facing event page and RSVP flow).
  • src/components/ — shared UI: ui/ (design-system primitives), guest-dashboard/, builder/ (page-builder editor chrome, dashboard-only), docs/(this site’s own nav/content primitives).
  • src/lib/data/ — the only layer that touches the Supabase client. Every host-scoped read/write goes through a named function here (events.ts, invites.ts, rsvps.ts, forms.ts, form-submissions.ts, email-log.ts, host-profile.ts, storage.ts, rate-limit.ts, custom-components.ts).
  • src/lib/blocks/ — the page-builder block system: the registry, types, the sandbox builder, shortcodes, starter layouts, layout controls, and one file per block under blocks/*.tsx. See adding a new block type.
  • src/lib/forms/ — the generic multi-form field-type system, modeled on lib/blocks: a registry, one config type + validator + Edit/Input pair per field kind.
  • src/lib/schemas/ — Zod validators; the single source of truth for both TS types and runtime validation of every JSONB column. See JSONB validation.
  • src/lib/supabase/ — server.ts (service-role client) and auth-server.ts (SSR auth client).
  • src/lib/cache/keyed-cache.ts— a custom per-key cache used instead of Next’s unstable_cache. See caching.
  • src/proxy.ts — Next middleware: generates the per-request CSP nonce and gates /dashboard/* and the auth pages.

The dependency rule

app/* may import from lib/data, lib/schemas, and components/*. lib/data may import from lib/schemas and the Supabase client. Nothing imports from app/. lib/blocks is pure (usable by the public guest page); components/builder/ is the editor-only UI on top of it and is a dashboard-only import.

No mutation API routes

The only two files under src/app/api/ (forms/[formId], rsvp) still route through the same lib/data/lib/schemas layers as everything else. Every host-side mutation is a server action, not a REST endpoint — this is a deliberate decision, not an oversight.

Good to know

Two invariants are load-bearing enough to have their own pages: every host-scoped query filters host_id (see the host_id invariant), and every JSONB column is parsed through a Zod schema, never as-cast (see JSONB validation).