Appearance
Deployment & handover
This is the guide for taking over or contributing to NanaSelect — running it, deploying it, and knowing where things live. It's written for a new contributor arriving cold (hello, Aziz 👋).
How to read it: do the Start here section first — that's everything you need to run the app and understand the shape of it. The Deploy and Add a reviewer sections are the day-to-day operations. Everything under Going deeper is ops depth you can safely ignore until you need it. Don't try to read it all at once.
Start here (your first hour)
1. What NanaSelect is
NanaSelect is a standalone product-selection app that guides architects, builders, and homeowners to the right-sized NanaWall system, then hands off into the nanawall.com configurator. It runs on its own and augments the legacy nanawall.com (Drupal) site — it owns its own product database and talks to Drupal over JSON. For the full "why", read the product vision; for the "how it fits together", the data access reference.
Stack: SvelteKit · Cloudflare Workers · D1 (SQLite at the edge, via Drizzle) · Tailwind · Vitest.
2. Run it locally
You don't need a Cloudflare account to run NanaSelect locally — Wrangler runs D1 on your machine.
sh
pnpm install
dev-up # migrate -> seed -> serve -> health-check, in one stepThen open http://localhost:6650. Locally the admin signs you in automatically (a dev bypass badge), so /admin just works. If you'd rather run the steps yourself, the README quick start lists the underlying pnpm commands.
Next, walk the Set up your first track tutorial — it's the fastest way to understand what the app actually does.
3. Where things live
src/routes/ UI pages + server endpoints (guided flow, comparison, admin)
src/lib/server/db/ Drizzle schema + client — the single source of truth
drizzle/ D1 migrations (generated by drizzle-kit)
seed/ CSV -> D1 ingestion (see seed/README.md)
docs/ This documentation siteThe hosting model
NanaSelect deploys to Cloudflare:
- The app is a single Worker named
nanaselect(seewrangler.jsonc), serving the SvelteKit build. - Data lives in a D1 database,
nanaselect-db, bound to the Worker asDB. - The
/adminarea is gated at the edge by Cloudflare Access (Zero Trust) — see admin access for the full model. - This docs site is a separate deploy (Cloudflare Pages), published by CI — see Building & publishing the docs site.
The app deploy is manual today (there is no app CI/CD pipeline — only the docs site auto-deploys). That keeps you in control of when production changes.
Deploy the app
One-time setup:
sh
wrangler login # authenticate to the Cloudflare account
# The D1 database already exists (id is in wrangler.jsonc). If you ever need a
# fresh one: wrangler d1 create nanaselect-db, then put the id in wrangler.jsonc.Each deploy:
sh
pnpm db:apply:remote # apply any new migrations to the remote D1 (first time / after schema changes)
pnpm seed:remote # load/refresh the seed catalogue on remote D1 (when data changes)
pnpm run deploy # deploy gate -> build -> wrangler deploypnpm run deploy runs the deploy gate (#1058, security review F-D1) before anything ships: it refuses to publish a wrangler.jsonc whose vars leave CF_ACCESS_TEAM_DOMAIN/CF_ACCESS_AUD empty or contain ADMIN_DEV_BYPASS in any form — the config combination that would open /admin without Access. Run it alone with pnpm run deploy:gate; a drift-guard spec (deploy-gate.spec.ts) also fails the suite if the committed config ever regresses. Deploying with a raw wrangler deploy skips the gate — don't.
There's also a small companion Worker for session cleanup — deploy it with pnpm run deploy:purge when its code changes.
Production hostname
Production must be served from a .nanawall.com subdomain for consent (Iubenda/GTM) to work — see admin access and the consent model. The public app + docs hostnames and their shared access allowlist are tracked in #560; until they're set up, treat deploys as staging.
Add a reviewer (grant access)
Two things gate who can do what:
- Reaching
/adminat all — the person's email must be allowed by the Cloudflare Access policy in the Zero Trust dashboard (add their email to the Access application in front of/admin*). - What they can do once in — their email must have a role in the
admin_userstable (data_editor/rules_editor/admin). Add them inseed/sql/010-admin-users.sql(or insert directly); emails are stored lower-cased.
Full step-by-step (including the one-time Zero Trust application + AUD/team-domain setup) is in admin access. For the POC review host (nanaselect.iwpi.com) — where the whole site, not just /admin, is behind the allowlist — invite/removal steps are in POC access.
Where to get help
- These docs — how-to guides for every admin task, reference for precise behaviour, and why it works for the rationale.
- The issue tracker — work, bugs, and decisions live in GitHub Issues on the repo.
- The seed & data model —
seed/README.mdin the repo, the corpus data model, and data access.
Going deeper (ops — read when you need it)
You can skip this on day one.
- Migrations. Schema is defined in
src/lib/server/db/schema.ts; generate a migration withpnpm db:generate, apply locally withpnpm db:apply:local, remotely withpnpm db:apply:remote. See audit & validation for how edits are validated. - Seed data.
pnpm seed:local/pnpm seed:remotebuildseed.sqlfrom the CSVs, load it, then apply the catalog fragments through the shared runner — the fragments rebuild whatseed.sql's attributeREPLACEcascades away, so both steps are required. Seed covers all lifecycle states; new features ship seed data in the same change. - Reset local data.
pnpm db:reset:localwipes and rebuilds the local D1. - Consent & retention. Iubenda (via GTM) is the consent source of truth; the
IUBENDA_*andSESSION_TTL_DAYSvars inwrangler.jsoncdrive the 2-tier consent + retention model. The session-purge Worker enforces retention. - Drupal sync.
DRUPAL_BASE_URLpoints at nanawall.com; the app ingests Drupal media/content — see the corpus refresh how-to.
Keep this guide honest: the commands above mirror package.json and wrangler.jsonc. If you change a script or the hosting model, update this page in the same change.