Developers
Project structure
The monorepo
apps/app the Next.js app: marketplace, stores, seller app, API, docs, MCP
packages/db Drizzle schema, migrations (drizzle/), client, migrate helpers
packages/email React Email templates (emails/) and render helpers
packages/mcp the MCP servers: tools, API client, stdio CLI
packages/ui the design system: tokens (styles.css), components, icons
packages/eslint-config shared ESLint configs
packages/typescript-config shared tsconfig files
render.yaml the Render Blueprint
docker-compose.yml local Postgres 17 + pgvector on port 5433Packages are imported by name: @repo/db, @repo/email, @repo/mcp, and design system pieces as @repo/ui/button, @repo/ui/icons and so on. They export their TypeScript source, so there's no build step between changing a package and seeing it in the app. Only @repo/mcp has a build, for its stand-alone dist/stdio.js.
The app's routes
Inside apps/app/app, route groups (the folders in brackets) share a layout without adding to the URL.
| Folder | What it is |
|---|---|
(marketing) | The landing page at / |
(flow) | Sign-up and onboarding (/welcome, /welcome/start), no app shell |
(app) | The seller app inside the shell: home, inbox, listings, offers, sales, shops, stats, tools, me. The layout calls requireUser() |
(workspace) | The listing workspace, /list/[id]/research → details → photos → words → publish, with the agent chat beside it |
(market) | The public marketplace on the root domain: discover, stores, agent, account, messages |
(checkout) | /checkout/[listing] and /offer/[listing] |
store/[store] | One seller's store. proxy.ts rewrites maya.resell.store/x onto /store/maya/x |
docs | This site (docs.resell.store, or /docs) |
mock | The front-end prototype of every designed screen, served at the real URLs in mock mode |
design-system | Every foundation and component from @repo/ui on one page |
screens | An index of every designed screen with live and mock links |
api/v1 | The public API; the routes themselves are in lib/server/api |
api/mcp | The hosted MCP servers |
api/auth | Better Auth's handler |
api/paypal | Checkout and onboarding returns, and the webhook |
api/* | Also: cron/sweep, files/[id], listings/[id]/research and chat, session handoff, track (views), dev/magic-link |
Server actions
app/actions/*.ts are the "use server" entry points the screens call: listings, listing-media, listing-words, listing-publish, commerce (buying, offers, shipping), after-sale (cancellations, problems, refunds), paypal, messages, shops, profile, account, follows, likes, reviews, sidekick, developer (keys, agent links, webhooks) and market. They check who's signed in, validate input with zod, call lib/server, and return { ok: false, error } for anything a person can fix instead of throwing.
Server modules
apps/app/lib/server is where the work happens. Actions, API routes, pages and the workflow all call the same functions, so there is one place to change a rule.
| Area | Files |
|---|---|
| Auth and people | auth.ts, session.ts, dev-links.ts, viewer.ts, account-visit.ts |
| Shops and listings | shops.ts, listings.ts, listing-chat.ts, words.ts, files.ts |
| Research and agents | ai.ts, research.ts, channel3.ts, comps.ts, kernel.ts, sidekick.ts, store-agent.ts, negotiator.ts |
| Marketplace | market.ts (browse and search), embeddings.ts, follows.ts, likes.ts, activity.ts, stats.ts |
| Money | commerce.ts, payout-policy.ts, paypal.ts, paypal-sellers.ts, paypal-webhook.ts, disputes.ts, reviews.ts |
| Messages and email | messages.ts, notify.ts, notify-after-sale.ts, digests.ts, email.ts |
| Timing | sweeps.ts (the timed jobs), later.ts (run after the response) |
| Share images | share.ts, og-images.tsx, og-render.tsx, og-photos.ts, og-fonts.ts |
| Public API | api/: router.ts, routes/*, keys.ts, http.ts, serialize.ts, webhooks.ts, log.ts |
market-logins.ts (signed-in marketplace accounts through Kernel) exists with its table, but nothing calls it yet. Outside lib/server: lib/urls.ts builds every cross-zone URL, lib/money.ts converts dollars and cents, lib/docs holds this site's nav, and lib/mock*.ts the prototype's data.
Components
apps/app/components is grouped by screen or zone: market/ (header, store, listing, discover, checkout, buyer account, agent), listing/ and workspace/ (the listing flow), shell/ (the app shell), home/, inbox/, offers/, shops/, tools/, after-sale/, reviews/, me/, welcome/, landing/, empty/ and loading/, plus docs/ for this site. Generic, reusable pieces belong in @repo/ui instead.
Live and mock
The mock pages under app/mock and the live pages render the same components. Live pages pass real data and server actions as props; mock pages pass nothing and the component falls back to the prototype's behaviour and the data in lib/mock*.ts. So when you change a shared component, keep its live props optional and check both versions.
Where the live version needed a different shape, it has its own folder next to the original: listing-live beside listing-later, inbox-live beside inbox, tools-live beside tools, and seller-live for sales and offers. lib/mock-mode.ts lists which paths have a mock.
Conventions
- Every
lib/serverfile starts withimport "server-only", so it can't end up in a browser bundle. - Pages check auth themselves.
requireUser()sends signed-out people to/welcomeand people who haven't onboarded to/welcome/start;getCurrentUser()returns null instead. The proxy never checks auth. - Money is whole cents in the database (
price_cents,total_cents…) and on the server. Screens show and type dollars (toCents,formatCentsinlib/money.ts), and the public API takes and returns dollars, never cents. - Import Drizzle helpers (
eq,and,sql…) from@repo/db, notdrizzle-orm. - Links between zones use
SiteLink/StoreLinkorsiteUrl()/storeUrl(), never a hard-coded host. See Domains and routing. - Slow or optional work (agents, emails) runs after the response with
after()orrunAfterResponse(), and never fails the action that started it. - Tests sit next to the file they test, as
*.test.ts. - Comments explain why, in plain words, and screen codes from the design file (C2, P4, B3) mark which screen a piece belongs to.
aiConfigured, channel3Configured or paypalEnabled(). New code that calls a service should do the same and give the screen something sensible without it.