- Plan 1 — Foundation & auth (invite-only, sessions, roles, app shell)
- Warm Washi design system — themed UI (washi-paper light + "smoke" dark, toggle), reusable components
- Plan 2 — Shared catalog + per-user reviews
- Shared catalog CRUD (add / browse / view / edit incense; format + scent-family)
- Per-user multi-axis reviews (scent, throw/smoke, longevity, value, overall) + free-text notes
- Compare everyone's ratings on an item; average + review counts on the grid
- Burn log (dated entries under a review)
- Collection status (owned / wishlist / sample / used-up)
- Tags
- Browse / search / filter (text search, multi-select format & scent facets, sort)
- Plan 3 — Paste-URL importer: Shopify JSON → schema.org JSON-LD → Open Graph → meta, SSRF guard, local image caching, de-dup by URL + name
- Collection status (owned / wishlist / sample / used-up) — pick catalog items into your personal collection
- Burn log (dated entries under a review)
- Tags
- Browse / search / filter
Deferred as acceptable for a v1 self-hosted foundation, tracked here so they stay conscious decisions:
- Deployment story (done):
@sveltejs/adapter-nodeNode server, multi-stageDockerfile, and a composeappservice that auto-migrates on startup; cookiesecurenow derives from!dev. Runs behind a TLS reverse proxy (ORIGIN, configurableAPP_PORT). - Defense-in-depth (done): add
(app)/+layout.server.tscallingrequireUser, so any future(app)/*route is guarded by default (currently/invitesself-guards only). - Bootstrap seed (done):
scripts/seed-invite.mjs+ theseed:invitenpm script already exist; documented inREADME.md. - Test isolation (done): per-worker test databases provisioned from
DATABASE_URLwith per-test truncation, ensuring tests are order-independent and the first-user-becomes-admin path is covered; supersedes the.env.testidea. - Resilience (done): wrap
validateSessionTokeninhooks.server.tsin try/catch so a transient DB error degrades to logged-out instead of a 500. - Abuse controls (done): rate-limit
/loginand/register(per-IP accuracy requires the reverse proxy's forwarded header be trusted via adapter-nodeADDRESS_HEADER/XFF_DEPTH; otherwise the limiter degrades to a coarse global limit); normalize usernames (case-insensitive). - Polish: only re-issue the session cookie when the sliding refresh actually extends expiry (avoid
Set-Cookieon every request); aGET /logoutcurrently 500s (POST-only) — add a redirectingloador leave POST-only by design.
- Per-shop import enhancers
- Bulk / background import
- Barcode scanning
- Stats & charts
- Public sharing
- PWA / mobile polish
- Recommendations