Google OAuth cannot provide phone numbers (no scope returns them reliably),
so we collect it ourselves. Optional, stored unverified.
- Migration 0011: users.phone text column (+ debug-migration hot-apply step)
- schema/auth.ts: add phone field
- onboarding: optional phone input on step 1; saved to users.phone via the
onboarding API (normalised: leading + then digits, 8-15 digit validation)
- profile page: editable Phone field; loaded from + saved to /api/auth/profile
- /api/auth/profile: GET returns phone; POST accepts & normalises it
(empty string clears, undefined leaves untouched)
Capture point covers both Google and email/password signups since both land
on onboarding. Verification (OTP) and marketing-consent flag intentionally
deferred per product decision.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Each probe now runs independently (pgvector check first and standalone) and
reads the drizzle journal from the correct "drizzle" schema, so the endpoint
returns a usable diagnostic instead of crashing on the first missing relation.
Also reports whether error_events exists.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Migration 0010 (error_events) didn't land via the drizzle journal on prod, so
add an idempotent CREATE TABLE + indexes to the /api/debug-migration steps so
the table can be created instantly via the documented hot-apply endpoint.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The baseline schema needs the `vector` extension (memories.vision_embedding),
but nothing created it on deploy — a fresh DB hit "could not access file
vector" and migrations failed.
- migrate.ts now runs CREATE EXTENSION IF NOT EXISTS vector (superuser) before
applying migrations, with a clear error if the Postgres image lacks pgvector.
- /api/debug-migration GET now reports pgvector status (binaryAvailable /
installed) so the image/extension can be checked from the browser.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Migration (0009): notifications table with unique daily/weekly slots,
is_read column, metadata JSONB for vaccine info. No more localStorage
for read state — syncs across devices.
API GET /api/notifications?childId=:
- Generates vaccine notifications (upsert, filtered by given vaccines at query time)
- log_nudge: if no feed/diaper/sleep logged today after noon IST
- memory_nudge: if no photo added to memories today
- garment_nudge: if wardrobe < 10 items (once per week slot)
- Returns unread first, then recent read, limit 60
API PATCH /api/notifications — mark all read for family+child
API PATCH /api/notifications/[id] — mark single notification read
Page /notifications:
- Fetches from real API (no hardcoded mock data)
- Optimistic mark-read on tap, navigates to actionUrl
- Colored cards per type (red=vaccine, amber=log, purple=memory, pink=garment)
- Unread badge + Mark all read button in sticky header
- Legend row at bottom
debug-migration: added notifications table CREATE IF NOT EXISTS for hot-apply
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- New families.pediatrician_name column (migration 0008)
- Settings card: Name input above Phone input, single Save
- Emergency page: shows doctor's name above the call button
- AI medical redirects: personalised "Call Dr. X: +91…" message
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Feature A — Storage quota (1 GiB per family):
- src/lib/quota.ts: enforcement library with pure functions (fully unit-tested)
and DB-bound helpers; isPaidFamily() is the single payment abstraction gate
- src/lib/format-bytes.ts: extracted formatBytes() — safe for client imports
- POST /api/upload: quota check before presigned URL issuance (HTTP 402 + reason code)
- POST /api/memories/[id]/confirm: HeadObject reconciles actual R2 size; deletes
over-quota objects and marks row failed rather than silently exceeding limit
- GET /api/storage-usage: storage info endpoint for UI meter
- src/components/StorageMeter.tsx: meter bar + StorageQuotaBanner + MemberLimitBanner
- memories/page.tsx: quota banner, FAB disabled (⊘) when exceeded, compact meter in header
- settings/page.tsx: always-visible StorageMeter + MemberLimitBanner in invite section
Feature B — Member limit (2 per family, free tier):
- invites/route.ts: replaced ad-hoc inline check with checkMemberLimit() from quota lib
Structured 403 response: { reason, currentCount, limit }
- Freeze rule: paid→free downgrade leaves all members intact; only new invites blocked
Migration:
- drizzle/0007_subscription_status.sql: ADD COLUMN subscription_status varchar(20)
- debug-migration/route.ts: step added for hot-apply without full redeploy
- src/db/schema/family.ts: subscriptionStatus field added to Drizzle schema
Tests: 44 unit tests in src/__tests__/quota.test.ts, all passing
- Pure function tests (no DB): isPaidFamily, wouldExceedQuota, isAtMemberLimit, formatBytes
- DB-bound tests (mocked @/db): getFamilyStorageUsage, checkStorageQuota,
checkMemberLimit, getStorageInfo, tenant isolation
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add ALTER TABLE steps to debug-migration POST so the columns can be applied
immediately via the running app without waiting for a full Dokploy redeploy.
Also revert invites routes to use display_name/accepted_at now that the
hot-fix path exists.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>