Commit graph

18 commits

Author SHA1 Message Date
38bb5af01c feat: collect optional user phone number (onboarding + profile)
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>
2026-05-31 21:25:00 +05:30
7a60132bb2 Add admin observability: error tracking, audit viewer, AI metrics, health
Turns the admin panel into a real monitoring tool so production bugs are
visible instead of silent.

- Error & crash tracking: error_events table (migration 0010) + logError()
  helper + /api/errors ingest; global-error.tsx and (app)/error.tsx report
  crashes automatically; /admin/errors viewer (recent + grouped, filters).
- Full audit-log viewer at /admin/audit over the existing audit_log (all
  actions, not just auth) with action/resource/family/user/text filters.
- AI observability at /admin/ai over ai_usage: per-intent latency (avg/p95),
  tokens, cost, daily trend, slowest calls, medical-redirect count.
- System health at /admin/health: DB latency, migration status, recent error
  volume, and integration config presence.
- Sidebar updated with Health / Errors / Audit Log / AI Usage.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-30 00:27:07 +05:30
678cf65d70 feat: DB-backed notification system with vaccine + activity nudges
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>
2026-05-29 09:33:51 +05:30
90c8d13814 feat(settings): add Pediatrician Name field alongside phone
- 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>
2026-05-28 00:49:05 +05:30
0c7f37fd12 feat(quota): storage quota + family-member limits for free tier
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>
2026-05-27 23:21:11 +05:30
b01e0596c1 feat(invites): auto-send invite email via Resend + register migration in journal
- Add sendFamilyInviteEmail() to email.ts using existing Resend setup
- Wire into POST /api/invites — fetches inviter name + family name, sends
  warm invite email with Accept Invitation button linking to /invite/{token}
- Email is non-fatal: invite is created even if email send fails
- Register 0006_family_invites_missing_cols in _journal.json so Dokploy
  auto-applies the migration on next deploy

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 14:24:31 +05:30
0b67631fda fix(db): add missing display_name and accepted_at columns to family_invites
Both columns are referenced by the invite API but were never in the table,
causing "column does not exist" errors when inviting family members.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 14:21:57 +05:30
6bdaade777 feat: email verification + Google OAuth
- Signup now creates unverified users and sends a verification email
  (Resend); dev falls back to [VERIFY-LINK] console log
- /api/auth/verify-email: single-use token handler, mints tia_session
  on success, redirects to /onboarding
- /api/auth/resend-verification: rate-limited (3/hr), enumeration-safe
- Sign-in gated on email_verified — unverified accounts get 403 with
  needsVerification flag so the UI can show the resend button
- Google OAuth via arctic v3: PKCE + state anti-CSRF, find-or-create
  user, writes accounts row, mints tia_session
- Login page: Google button, check-email screen, resend link on 403
- drizzle/0005_email_verification.sql: creates email_verifications
  table + backfills all existing users as verified (runs automatically
  on container start before app boots)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 12:56:02 +05:30
c24392f0a1 feat: email-based circle invites with in-app notifications
- Admin invites by entering email instead of copying a link
- If email matches existing Tia user → creates pending invite visible
  on their Circles page with Accept/Decline buttons
- If email is not registered → sends Resend email with signup link
  that lands them directly in the circle after account creation
- DB migration adds invited_email + invited_family_id to circle_invites
- New GET /api/circles/invites endpoint for pending invite banners
- Remove clipboard-copy approach entirely

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 02:05:10 +05:30
d8bda20887 Fix migration: add statement-breakpoints + use superuser URL
Two issues prevented 0003_circles.sql from running:
1. Missing -->statement-breakpoint markers (Drizzle splits SQL by these)
2. migrate.ts used DATABASE_URL (tia_app, no DDL privileges) instead of
   DATABASE_URL_SUPERUSER — now prefers superuser URL with fallback to
   DATABASE_URL for local dev

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 01:23:06 +05:30
ee3f8b4507 Register 0003_circles in Drizzle migration journal
The SQL file existed but was missing from _journal.json so the
migrator skipped it on deploy. Adding the journal entry ensures
the circles tables are created on next container boot.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 01:14:38 +05:30
5fdb69679d Circle feature: C0–C9 multi-tenant social groups (Sprint 9 + 10)
Adds full circle functionality — private social groups for trusted families
to share milestones, memories, and posts with reactions and comments.

- 7-table DB migration: circles, members, invites, posts, comments, reactions, reports
- 11 API routes: create/list circles, posts feed, comments, emoji reactions, invite tokens, join flow, member management, reporting
- 3 new pages: /circle (list), /circle/[id] (feed + PostCard + CreatePostModal), /circle/join/[token]
- Copy-on-share for memory photos (independent R2 objects, never references originals)
- Admin controls: invite generation, member promote/demote/remove, last-admin guard
- C9 privacy consent screen before first post
- Menu entry added

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 01:04:50 +05:30
1994725101 feat(wardrobe): add complete wardrobe feature (W0–W9)
Schema (W0):
- Add garments, garment_wears, outfits tables with Drizzle migrations
- Drizzle migrations 0001 (garments/wears) and 0002 (outfits) auto-apply on deploy
- RLS policies in drizzle/manual/06-wardrobe-rls.sql (apply via superuser in prod)

API (W1–W9):
- POST /api/garments/upload — direct upload to R2 garments/ prefix with sharp thumbnail
- POST /api/garments/tag — vision tagging via LiteLLM, defensive parse, category validated
- GET/POST /api/garments — list with composable filters, create
- GET/PATCH/DELETE /api/garments/[id] — detail, edit, delete
- POST /api/garments/[id]/wear — log worn date
- GET /api/garments/outgrowth — pure SQL, explicit size ordering (no lexicographic sort)
- GET /api/garments/packing — active garments grouped by category
- GET /api/garments/outfit — Open-Meteo weather + deterministic outfit pairing, no LLM
- GET/POST /api/garments/outfits + DELETE [id] — saved outfits

Pages:
- /wardrobe — grid with status/category/size/season filters + outgrowth nudge
- /wardrobe/add — 3-step capture→vision→form, size required, batch-friendly
- /wardrobe/[id] — detail/edit/status lifecycle + wear history
- /wardrobe/packing — packing checklist by category
- /wardrobe/outfit — weather-aware suggestions with shown basis
- /wardrobe/saved-outfits — view/delete saved combinations

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 18:09:22 +05:30
edd239fa69 chore(db): regenerate baseline migration from corrected schema
drizzle-kit generate against the now-prod-aligned schema produces a
single baseline migration covering all 35 tables.

VERIFIED: 0000_baseline_prod_2026_05_19.sql was compared column-for-column
and type-for-type against the drizzle-kit pull introspection of tia_prod.
Table sets identical, all columns and types match. The baseline is a
faithful representation of production.

This baseline will be marked as already-applied in prod's
__drizzle_migrations table (done out-of-band, not in git), so the migrator
runs nothing on the next deploy. It exists purely as the reference point
for future schema diffs.

Adds drizzle/README.md documenting the baseline reset and the migration
workflow going forward.
2026-05-23 12:25:20 +05:30
e7d68c2fc6 chore(db): archive legacy migrations, stop gitignoring drizzle/
The drizzle/ folder was in .gitignore (line 34) — likely confused with
the build 'out/' dir. Effect: migration SQL never reached the server on
deploy, so the migration pipeline could never have worked. Only 7 of 18
files were ever force-tracked; 0000-0010 + most of manual/ were untracked.

- Remove drizzle/ from .gitignore; document why it must be tracked
- Archive legacy hand-rolled migrations 0000-0015 + manual/ to
  _archived_pre_baseline_2026-05-19/ (kept on disk; history retains old copies)
- Archive stale meta/ (knew of only 3 of 16 migrations)
- Baseline regeneration follows in subsequent commits
2026-05-23 12:05:50 +05:30
291eb4793b feat(g5-g6): age-aware UX + mama affiliate page
G5 — Age-Aware UX:
- useStageCheck hook: maps birth date → BabyStage (newborn/infant/sitter/crawler/toddler/walker)
- Time-of-day fast-log suggestion chip on home page (time × stage matrix)
- Milestones page: 25 WHO/AAP milestones, category filter, progress bar, inline date picker
- Milestones API: GET (merged definitions + achievements), POST (upsert), DELETE (un-mark)
- DB: milestone_achievements table with unique(child_id, milestone_key)
- Milestones 🌟 added to menu

G6 — Mama Affiliate Page:
- member_profiles, recommended_products, product_clicks tables
- /api/profile CRUD (GET/PUT), /api/profile/products (GET/POST/PATCH/DELETE)
- Public routes: /api/profile/[slug] and /api/profile/[slug]/click (IP hashed)
- /settings/profile: slug + bio editor, product list with ↑↓ reorder + click counts
- /m/[slug]: beautiful public page (gradient bg, product grid, Shop → click tracking)
- Settings page link to profile setup

DB migrations: 0014_milestones, 0015_affiliate.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 00:59:17 +05:30
c2cabc01d3 feat(g1-g4): design system, memories pipeline, medical tracking, AI brain
G1 — Design System: 14 UI primitives (Button, Card, Modal, Sheet, Input,
Textarea, Select, EmptyState, LoadingShimmer, ConfirmDialog, WashiTape,
Badge, Avatar, Tabs), PageTransition with Framer Motion, sun/moon CSS vars,
Caveat font, /dev/components visual showcase.

G2 — Memories Pipeline: R2 presigned uploads, Sharp thumbnail generation,
LiteLLM vision captions + pgvector embeddings, CSS masonry gallery with
infinite scroll, private toggle, semantic search fallback to ILIKE.

G3 — Medical: dose log + correction audit trail, IAP vaccine bulk import,
emergency escalation page, pediatrician phone in settings.

G4 — AI Brain: keyword guardrail → LLM classifier → structured DB tool-use
(7 tools) → memory search → general parenting handler; ai_usage table;
22-case medical bypass safety test suite.

DB migrations: 0011_memories, 0012_medical_doses, 0013_ai_usage.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 17:48:34 +05:30
340cf4322e Add audit_log and password_resets migrations
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-17 00:01:07 +05:30