Architecture

Stack

LayerChoice
FrameworkNext.js 16, App Router, Turbopack
UIReact 19.2 Server Components, TypeScript, Tailwind v4
Databasenode:sqlite: Node 24's built-in SQLite
AuthClerk hosted sign-in, mapped to a local users row
PDFpdf-lib, standard fonts only
Validationzod at action boundaries

The whole application runs with no external services. No database server, no native modules to compile, no CDN, no web fonts. npm install && npm run dev and it works.

Data access

A thin query layer in src/lib/db.ts exposes all, get and run. Two details matter:

  • Rows are re-spread into plain objects. node:sqlite returns null-prototype objects, which React Server Components refuse to serialise into Client Components. Fixing it once at the boundary means no caller ever has to think about it.
  • Schema is applied idempotently on first connection, with an additive migration step for columns introduced after a database was created, CREATE TABLE IF NOT EXISTS cannot add a column to an existing table.

Authorization

Ownership is proven by the query, not by comparing a fetched row. Every scoped read and write includes the owning id in its WHERE clause, so a valid id belonging to someone else simply does not match. There is no separate permission check to forget.

Cross-account viewing, counselor and contributor, funnels through one shared rule in src/lib/access.ts that returns who the viewer is, how they reach the student, and what they may see. The PDF route, the counselor view and the contributor view all consume it, so they cannot disagree.

There is deliberately no middleware. Next.js 16 renamed middleware to proxy and it is the wrong place for auth; every route checks the session in its own layout and every server action re-checks independently.

Mutations

All writes are Server Actions. Each one re-authenticates rather than trusting that the page rendered for the right person, and revalidates the paths it affects.

Multi-row money movement, marketplace purchases, is wrapped in an explicit BEGIN IMMEDIATE / COMMIT with rollback, because a partial write means a student pays for an essay they cannot read.

Design system

Three visual layers over one component set:

  • Default: soft, rounded, playful. The student product.
  • .pro: tighter, flatter, denser. The counselor console.
  • .docs: long measure, quiet surface, real typographic hierarchy. These pages.

Colour is expressed as semantic CSS custom properties that flip for dark mode, so components carry no dark: variants at all. Dark mode is opt-in via data-theme="dark" rather than following the OS, because the light palette is the intended look.

Assets

Avatars and university crests are generated inline SVG, no image files, no external requests, no broken images. University crests derive their colours and monogram deterministically from the institution's name via an FNV-1a hash.

Configuration

VariableEffect
CLERK_SECRET_KEYClerk server key. Must be set in production.
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEYClerk browser key. Publishable by design.
FLEDGY_DB_PATHSQLite file location. Defaults to data/fledgy.db.
RESEND_API_KEYEnables outbound email.
FLEDGY_MAIL_FROMSender address. Required alongside the API key.
FLEDGY_BACKUP_DIRWhere nightly backups are written. Defaults to backups/ beside the database.
FLEDGY_DISABLE_BACKUPSSet to 1 to skip the nightly backup. Used by CI.
NEXT_PUBLIC_LOGO_BASESelf-hosted university logo path. Crests remain the fallback.

Names from before the rename

The product was Unidash, then Dashly, and is now Fledgy AI. Each rename renamed the variables with it, and every older name is still read as a fallback — FLEDGY_ first, then DASHLY_, then UNIDASH_. This is deliberate: an environment still setting an old name would otherwise boot against an empty database, which presents as total data loss rather than as a misconfiguration.

The same applies to the database file. If fledgy.db does not exist and dashly.db or unidash.db does, it is moved into place on boot, WAL and shared-memory sidecars included, rather than a second empty database being created beside a full one.

Known gaps and limitations are catalogued in Data & accuracy.