Core principles
Apps depend on packages
The two applications (apps/web and apps/server) contain no domain logic. They import from shared packages and focus only on their transport concerns: rendering the UI and hosting the HTTP server respectively. If you need to add slideshow validation or a new AI helper, the change goes into a package — not into an app.
Framework-agnostic domain logic
packages/core is pure TypeScript. It has no dependency on React, Elysia, or any other framework. This means the core domain — slideshow schemas, validation rules, AI helpers, patch transactions — can be tested in isolation, reused across contexts, and evolved without touching application code.
End-to-end type safety via ORPC
The frontend and backend share the sameAppRouter type from packages/api. There is no code generation step. When you change a procedure’s input or output in contracts.ts, the TypeScript compiler immediately surfaces type errors wherever that procedure is called on the frontend. Breaking changes are caught at compile time, not at runtime.
Feature-sliced frontend
The React app organizes code by feature (features/slideshow/) rather than by technical layer. Components, hooks, queries, and local state for a feature live together. This makes navigating and extending features straightforward without hunting across a global directory tree.
Single API package
All API surface area — ORPC contracts, endpoint schemas, and orchestration services — lives inpackages/api. There is one place to look when understanding what the backend exposes, what shapes are validated, and how external APIs (like Anthropic) are called.
The three key packages
| Package | Scope | Key exports |
|---|---|---|
@slides/core | Pure domain logic | Slideshow schemas, validation, AI helpers, patch transactions |
@slides/api | API surface | ORPC contracts, endpoint schemas, slideshow services, routers, request context |
@slides/config | Shared configuration | Server env (./server), client env (./client), logger (./logger) |
@slides/core — domain layer
The single source of truth for slideshow shapes and rules. Exports types, TypeBox schemas, validators, AI helper utilities, and transaction helpers. Apps treat this as a read-only contract for what a slideshow is.
@slides/api — API surface layer
Organized domain-first under src/slideshow/. Contracts define what the API exposes. Services define how operations are orchestrated. Routers are thin wires connecting the two.
@slides/config — configuration layer
Validated environment access and environment-aware logging. Never access process.env directly in application code — always import from @slides/config/*.
The two apps
apps/web — React frontend
A React 19 + Vite application. Responsibilities: rendering slides, routing between views, fetching and mutating data via the typed ORPC client, and managing local feature state. It imports domain types only through adapter files in src/domain/ — never directly from @slides/core.
apps/server — Elysia backend
A single-file entry point (src/index.ts) that hosts the ORPC handler on Bun via Elysia. It wires together environment config, a filesystem adapter for slideshow persistence, and the API router. There is no business logic in the server app itself.
The server compiles to a standalone binary (
bun run compile) for portable deployment — a single executable that bundles the frontend assets and serves everything from one process.Dependency direction
packages/api depends on packages/core. The core package has no knowledge of the applications that consume it.
Explore further
Monorepo structure
Full directory layout for apps, packages, and configuration files
Data flow
The complete request/response cycle from React client to Anthropic and back
Tech stack
Every technology in the stack and why it was chosen
API reference
ORPC procedures, input/output schemas, and error types