Mobile-first rebuild of NeedyPet — a warm, pastel pet-care app where owners and caretakers share a pet's daily care tasks. This is the Nuxt 4 successor to the legacy Vue/Vite + Express + MongoDB app (kept read-only next door as reference).
Stack: Nuxt 4 · Vue 3 · TypeScript · Tailwind CSS v4 · Nuxt server routes ·
Drizzle ORM · SQLite (bun:sqlite locally, libSQL/Turso-ready for production) · Bun.
Bun-native and Web-standard throughout: bun:sqlite drives the database,
Bun.password (argon2id) hashes passwords, Bun.write/Bun.file handle uploads,
Web Crypto powers tokens and R2 request signing, and the test suite runs under
bun --bun vitest — no node:crypto, no Node runtime required.
Supported browsers: Chrome/Edge 111+, Firefox 121+, Safari and iOS 16.4+, plus
the Android and Samsung Internet equivalents. The matrix lives in the
browserslist field of package.json and drives the Lightning CSS build
targets (docs/architecture.md).
- Cookie-session auth (nuxt-auth-utils) with email confirmation, revocable
sessions, password reset, account deletion with cascades, and durable rate
limiting on auth and password-verifying endpoints (
docs/security-model.md) - Pets with preset portraits or an uploaded photo (chosen when creating or
editing a pet, magic-byte validated, local disk in dev or Cloudflare R2 in
production, behind one storage abstraction — see
docs/deployment.md) - Daily care tasks (needs): max 10/day, pause/resume, lazy day rollover in the owner's timezone; the dashboard shows each pet's done/total progress for today
- Care records: full and partial logs with auto-completion, manual time-of-day, edit/delete with an owner/caretaker permission matrix
- Caretaker management and caretaker self-removal
- Care diary (paginated history) and a weekly stats page (streak, per-day counts, per-category totals)
- Opt-in daily email digest of unfinished care tasks, sent per user on their
own local evening via a secret-guarded cron endpoint
(
docs/deployment.md) - Legacy JSON bundle importer (
docs/migration.md) - Installable PWA: web manifest + Workbox service worker (auto-updating), maskable
icons, and an offline fallback page — API responses are never cached
(
docs/deployment.md) - English + Finnish UI (vue-i18n), language stored on the user profile and switchable from Profile — see Internationalization below
| Variable | Purpose |
|---|---|
NUXT_SESSION_PASSWORD |
32+ char secret sealing the session cookie (required) |
NUXT_SITE_URL |
canonical HTTPS origin for emailed links (required in production) |
NUXT_RATE_LIMIT_TRUST_PROXY |
true only behind the documented single trusted edge proxy (default false) |
NUXT_DB_FILE |
local SQLite path (default .data/needypet.sqlite) |
NUXT_DB_URL |
set in production (a libsql:// Turso URL) → uses the remote libSQL DB (docs/deployment.md) |
NUXT_DB_AUTH_TOKEN |
Turso auth token, required alongside a libsql:// URL |
NUXT_MAILER_PROVIDER / NUXT_MAILER_API_KEY / NUXT_MAILER_FROM |
production requires resend + key + sender; an unset provider uses console mail only in dev |
NUXT_UPLOADS_PROVIDER |
local (default) or r2 (Cloudflare R2) for pet photos (docs/deployment.md) |
NUXT_UPLOADS_DIR |
pet photo directory for the local storage provider (default .data/uploads) |
NUXT_UPLOADS_R2_ENDPOINT / NUXT_UPLOADS_R2_ACCESS_KEY_ID / NUXT_UPLOADS_R2_SECRET_ACCESS_KEY / NUXT_UPLOADS_R2_BUCKET / NUXT_UPLOADS_R2_PUBLIC_BASE_URL |
Cloudflare R2 config (required when the provider is r2) |
NUXT_DIGEST_SECRET |
secret guarding the daily-digest cron endpoint; empty = disabled |
NUXT_DIGEST_HOUR |
local hour (0–23) each user must reach before their digest sends (default 18) |
Full production setup (build, migrations, storage, digest cron) lives in
docs/deployment.md.
app/— pages, layouts, components (mobile-first, bottom navigation)server/api/— session-cookie-authenticated API routesserver/db/— Drizzle schema (schema.sqlite.ts), SQLite migrations, DB client (index.ts, with a Turso/libSQL seam), seed, legacy importershared/— domain types, date/measurement/stats/pet-image utilities, zod schemas used by both client and servertests/unit/— pure-function vitest tests for the shared utilities;tests/integration/— endpoint tests that drive the built server over HTTP (permission matrix, rollover, record recompute, auth and profile flows)public/— static assets and PWA icons (favicon.ico,pwa-192x192.png,pwa-512x512.png,maskable-512x512.png,apple-touch-icon.png); the manifest and service worker are generated at build time by@vite-pwa/nuxtdocs/—architecture.md(system map and invariants),domain-model.md(entities, permissions, date rules),security-model.md(auth and threat model),testing-strategy.md,deployment.md(production environment, database, storage, digest cron, PWA),migration.md(legacy import contract) anddecisions/(architecture decision records)tasks/—backlog.md(roadmap) andimplementation-order.md
The UI ships in English (default) and Finnish, using vue-i18n directly as a
Nuxt plugin (app/plugins/i18n.ts) rather than @nuxtjs/i18n — this is an auth
app with no need for per-locale routing or SEO, and no language ever appears in
the URL. Messages live in app/i18n/en.ts and app/i18n/fi.ts (namespaced:
common, nav, auth, pets, needs, records, caretakers, profile,
stats, offline, errors); the Finnish copy is a transcreation, keeping
the warm 🐾 tone rather than a literal translation. Finnish plural rules and named
interpolation are handled by vue-i18n (e.g. task counts, care-team announcements).
- The active language is stored on the user's profile (
users.locale, default'en') and changed from the Profile page. There is no browser-language autodetect. - The SSR i18n plugin reads the locale from the session (cached on the session payload), so the server and client render the same language with no hydration-time flicker. Signed-out pages (landing / login / register) are always English.
<html lang>and the in-app date/weekday labels (formatted via Temporal's locale-awaretoLocaleString) follow the active locale; the PWA manifestlangstaysen(it is generated once at build time). The daily digest email is localized to the recipient's locale; confirmation/reset emails stay English.- A unit test (
tests/unit/i18n.spec.ts) enforces en/fi key parity, so a new key added to one locale but not the other fails the suite.
- Date-only values (
birthday, needdateFor) areYYYY-MM-DDstrings on the pet owner's local day and are compared as strings — never shifted through a browser timezone. Care record dates are full UTC timestamps with the acting user's IANA timezone stored for audit. - Every need and care record has exactly one measurement: duration (1–1440 minutes) or quantity (1–100,000 ml/g). A record must match its parent need's measurement type, so that type is fixed once the need is created. Completion is derived from the records, so editing a target recomputes it.
- Owners control everything; caretakers see only their assigned pets and can view, log, and edit/delete only their own records. Rolled-over (archived) days are frozen.
- Pet images are the presets
dog,catorbunny(anything unknown coerces to cat) or an uploaded JPEG/PNG/WebP photo.