Commit graph

307 commits

Author SHA1 Message Date
5235e26cad feat(homepage): remove baby photo option + fix R2 orphan cleanup
- Tapping avatar when a photo exists shows a mini menu: Change photo / Remove photo
- Tapping avatar when no photo exists goes straight to file picker (no menu)
- handleRemovePhoto: PATCHes imageUrl=null, deletes file from R2, clears UI
- PATCH /api/children/[id]: fetches old image_url before update, deletes the
  old R2 object (profiles/ prefix only) when it changes — no more orphaned files
- updateChildImage in FamilyProvider now accepts string | null

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 13:44:10 +05:30
afae041208 fix(homepage): fix baby profile photo upload failing due to CORS
Direct PUT to R2 presigned URL is cross-origin — browser blocks it.
Route the upload through the existing PUT /api/upload server proxy instead,
same pattern used for memories. Also return `key` from children POST so
the proxy call has the R2 object key without needing the presigned URL.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 13:37:36 +05:30
fa5e27bfd9 feat(profile): working profile photo upload for parent users (mama/daddy)
- New POST /api/auth/avatar — accepts multipart FormData, uploads image to
  R2 under avatars/{userId}/{ts}.ext, saves URL to users.avatar_url
- GET /api/auth/profile now returns avatarUrl field
- /profile page: real avatar display (image or initials fallback), hidden
  file input wired to "Change Photo" button, spinner overlay while uploading,
  inline success/error message; name save and photo upload are independent

NOTE: This is the parent user avatar (mama/daddy). The baby profile photo
on the homepage card is separate.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 13:32:10 +05:30
f37e5bfad4 feat(activity): hover effect on 4-day strip individual rows
- Non-today rows: hover:bg-rose-50 / dark:hover:bg-gray-700
- Today (rose) rows: hover:bg-white/20 overlay so highlight still reads on rose bg
- transition-all for smooth fade

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 13:19:50 +05:30
1ae0986767 feat(activity): hover effect on log items to signal editability
- Add group + hover:bg-rose-50 + hover:shadow-sm to timeline log rows
- Add group + hover:bg-rose-50 to day-sheet log rows
- Chevron › turns rose-400 on hover (group-hover) in both places
- transition-all for smooth background + shadow animation

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 13:14:10 +05:30
2c016dbc8e fix: replace Google favicon img with inline SVG on login page
External favicon URL (www.google.com/favicon.ico) fails to load in
production due to CSP/network restrictions. Inline SVG has no external
dependency and renders the correct Google logo at all sizes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 13:04:27 +05:30
b91595316d fix: use APP_URL for OAuth/verify redirects behind Dokploy proxy
Behind Traefik, request.url resolves to http://0.0.0.0:3000/... (the
internal Docker address). Using that as the redirect base sent browsers
to 0.0.0.0, causing ERR_SSL_PROTOCOL_ERROR. Switch all server-side
redirects in the Google callback and verify-email routes to use
NEXT_PUBLIC_APP_URL (with tia.manohargupta.com fallback).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 13:01:01 +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
23a365309b feat: admin orphaned family management + user journey analytics
- Orphaned families (0 members) now visible with amber badge in /admin/families
  with filter tab and explicit "Delete Family + Data" cascade delete button
- Delete confirmation shows exact counts: children, logs, memories
- Delete child button added to /admin/children with count confirmation
- New DELETE /api/admin/families/[id] — full cascade delete (children, feeds,
  diapers_logs, sleeps, vaccinations, growth, memories, chat, etc.)
- New GET/DELETE /api/admin/children/[id] — cascade child delete with counts
- Extended families GET to include logCount + memoryCount per family
- New /api/admin/engagement — feature adoption %, per-family engagement table,
  AI usage stats (30d), daily activity chart using correct table names
- /admin/analytics fully redesigned: adoption funnel bars, per-family engagement
  table (sortable, filterable by activity), AI cost tab with INR breakdown
- Fixes wrong table names in old analytics (activity_logs, growth_records → real tables)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 09:38:34 +05:30
c2695435f2 Fix broken admin actions: delete cascade, error feedback, loading states
Users page:
- Delete was silently failing with FK constraint violations — sessions,
  accounts, support_tickets and family_members all have ON DELETE no action
  refs to users. Now cascades in correct order before deleting the user.
- Added error/success toast notifications so failures are visible
- Delete button shows loading spinner while in-flight; all buttons
  disabled during operation to prevent double-submit
- Always optimistically removes row on success (no full refetch needed)

Families page:
- Replaced browser prompt() for "New Family" with inline form — prompt()
  is blocked in some environments (CSP, iframes, browser settings)
- Fixed role-before-email bug: role dropdown was silently lost when changed
  before typing email, because onChange reset the whole addMember state.
  Now uses per-family form state with stable field updates.
- Remove member button shows loading spinner; disabled during operation
- Tier change button shows loading; disabled during other tier changes
- Added error/success toast notifications for all actions

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 08:59:20 +05:30
23010e9d90 Admin panel overhaul: activity monitor, real DB wiring, tier management
- New /admin/activity page: live login events, failed attempts, active
  sessions from audit_log + sessions tables; auto-refresh toggle
- New /api/admin/activity route: queries audit_log + sessions for
  stats (active sessions, logins/failures 24h, signups 7d) and events
- Fix /api/admin/stats: real growth charts (families/users by day),
  real children-by-age, real conversion rate, active sessions count,
  and login/failure counts — was all hardcoded empty arrays before
- Fix /api/admin/analytics: avg logs per family now divides by actual
  family count instead of hardcoded 1
- Dashboard: 6-card grid adding Active Sessions + Failed Logins 24h
  with links to Activity Monitor; bar charts now show hover counts
- Families: inline tier upgrade/downgrade button (Pro ↑ / Free ↓)
  wired to existing PATCH API; member panel polished
- Support: admin reply thread using support_responses table; Cmd+Enter
  to send; conversation view with original message + admin replies;
  auto-moves ticket to in_progress on first reply
- Settings: honest read-only display for env-var-controlled settings
  (pricing, AI config); editable free-tier limits that write to DB
- New /api/admin/families/limits route for bulk free-tier limit update
- Sidebar: added Activity Monitor nav item

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 08:35:01 +05:30
bcc167d7c2 fix: collapsed post card single-row layout
Collapsed state now fits entirely in one row:
  [avatar] [name] · [truncated text] [tiny thumbnail] [▼]
No second row needed. Expanded state shows the normal two-line
author block (name + time) as before.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 08:18:02 +05:30
ab25b7531d fix: collapsed post preview centered + auto-expand on edit/delete
- Collapsed preview (thumbnail + text) moved to its own row centered
  below the author line instead of being pushed to the right
- Chevron ▼/▲ stays on the author row right side as the only element
- Edit post and Delete post now call setCollapsed(false) first so the
  edit textarea / delete confirmation is always visible when triggered
  from a collapsed card

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 08:13:54 +05:30
560761968b feat: collapsible post cards in circle feed
Posts start collapsed showing author, timestamp, a thumbnail (if image)
and truncated text preview with ▼ chevron. Tap the author row to expand
the full post (body, full image, reactions, comments). Tap again to
collapse. Lets users scan many posts quickly and expand only what
interests them.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 08:01:56 +05:30
581fdb074d fix: post menu clipping, add edit post, fix image display + fullscreen
- Remove overflow-hidden from PostCard root so the ⋯ dropdown menu
  is no longer clipped by the card boundary
- Add Edit Post option to menu (own posts only) with inline textarea
  and Save/Cancel; calls new PATCH /api/circles/[id]/posts/[postId]
- Add PATCH endpoint: author-only text edit
- Fix image display: object-contain (no crop) instead of object-cover
- Add tap-to-fullscreen lightbox: clicking any post image opens a
  full-screen black overlay with the image at natural size, ✕ to close

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 07:56:10 +05:30
27125047bb fix: route circle post image upload through server to avoid R2 CORS
Direct browser PUT to R2's S3 endpoint is blocked by CORS. Replace the
presigned-URL client-side upload with a server-side upload endpoint:
client sends FormData → server uploads to R2 with PutObjectCommand →
returns tmpKey. No browser-to-R2 connection needed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 07:35:59 +05:30
66a765e75f fix: image upload in circle posts - handle empty content type + show errors
- Server: normalise empty/missing MIME type by sniffing file extension so
  iOS HEIC/HEIF and camera photos (which send empty type) are accepted
- Server: add image/heif and image/gif to allowed types
- Server: return normalised contentType in presign response
- Client: check presignRes.ok before uploading; use server contentType
  for the PUT to R2 so the header matches what was signed
- Client: show error message in modal instead of silent catch

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 02:18:14 +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
9f7ab870ba fix: pass ISO string not Date object to sql.unsafe for expires_at
postgres.js sql.unsafe() doesn't serialize Date objects in parameterized
queries — caused TypeError crashing the invite creation endpoint.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 01:55:48 +05:30
21f88459d7 fix: wrap entire invite POST handler in top-level try-catch
Catches errors from the circle_members SELECT query and auth
that were escaping the narrower try-catch and returning empty 500s.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 01:54:11 +05:30
3d7ff9adb5 fix: surface invite creation error + fix form field id/name attributes
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 01:51:51 +05:30
b7fb34fdde temp: auth-protected one-shot migration endpoint 2026-05-24 01:45:07 +05:30
12be105af4 chore: trigger redeploy with updated Dokploy start command 2026-05-24 01:41:10 +05:30
7fe60dc1af temp: debug migration status endpoint 2026-05-24 01:36:10 +05:30
acb9d66815 temp: surface DB error in circles POST for diagnostics 2026-05-24 01:31:26 +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
c732d2d7c2 Add baby profile photo change from home screen
- New PATCH /api/children/[id] — updates image_url in children table
- New POST /api/children/[id] — returns presigned R2 URL for profile
  photos (stored under profiles/{childId}/ prefix, no memories row)
- FamilyProvider: expose updateChildImage() so UI updates instantly
  without a full re-fetch after upload
- Home page baby card: photo avatar is now a separate tap target from
  the growth link. Tap photo → file picker → upload to R2 → save URL.
  Camera overlay (📷) appears on hover/tap;  shown while uploading.
  Tapping name/age/arrow still navigates to growth as before.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 00:51:45 +05:30
5297ab76ba Activity: each feed/sleep/diaper row independently tappable in 4-day strip
Each row in a day chip is now its own button. Tapping 🍼×2 on Thursday
opens a sheet scoped to feeds on Thursday only — not all logs for that day.
Sheet shows entries for that specific type, with edit/delete per entry and
a single focused "+ Add [type]" CTA at the bottom.
Rows showing ×0 render dimmed so missing entries stand out at a glance.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 00:26:43 +05:30
3db6fb2710 Activity: guidelines above strip, interactive day chips
- Moved guidelines row ABOVE the 4-day strip (correct order)
- 4-day strip: each chip is now tappable
  → opens a day-detail bottom sheet showing all logs for that day
  → each log row has ‹ › arrow; tap opens edit/delete action sheet
  → empty state tells mama to use Generate sample history to pre-fill
  → quick-add row at bottom (+ Feed / + Sleep / + Diaper) for fast logging

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 23:47:20 +05:30
a14d8e7043 Fix activity page repetition + 4-day strip layout + guideline accuracy
- Remove redundant daily summary bar (Today chip in 4-day strip covers it)
- 4-day strip: reversed to oldest→newest order (Wed→Thu→Yest.→Today)
- 4-day strip: switched from flex to grid grid-cols-4 so all 4 chips
  fill the full row width evenly instead of floating left
- Today chip highlighted in rose-400 to stand out from past days
- Guidelines: corrected 9-12 mo (feeds 3→4, sleep 12→14h, diapers 3→4)
  per AAP; 12-18 mo sleep 11→13h, diapers 3→4; 18-24 mo sleep 11→12h,
  diapers 2→3; all now match mid-range AAP recommendations

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 23:35:32 +05:30
164206c023 Activity page overhaul — 9 UX improvements
- FAB raised to bottom-20 to clear fixed bottom nav
- Branded loading: bouncing 🍼 😴 🚼 emojis
- Back button: white pill with shadow (matches other pages)
- Generate History moved to ⋯ overflow menu (keeps header clean)
- Filter pills: emoji labels (🍼 Feed / 😴 Sleep / 🚼 Diaper) + scrollbar-hide
- Daily summary bar: today's feed/diaper/sleep counts at a glance
- 4-day overview strip: quick multi-day snapshot above timeline
- Collapsible guidelines card: collapsed shows x/target fractions,
  expands to progress bars with actual/target display
- Today/Yesterday/weekday labels in timeline; Today styled in rose
- Better empty state with emoji and "Tap + to start logging" CTA
- Tap any log row → action sheet with Edit and Delete
  Edit: pre-fills LogModal with same values, deletes old log on save
  Delete: inline confirmation (no browser confirm()), then refresh
- New DELETE + PATCH handlers at /api/logs/[id] with family ownership check

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 23:07:02 +05:30
94b80fd862 fix(settings/profile): anchor share dropdowns to their trigger buttons
Reverted fixed viewport positioning back to absolute — the parent cards
no longer have overflow-hidden so absolute works correctly now. Both
dropdowns (profile share and per-product share) appear directly below/above
their trigger button instead of floating at a random screen corner.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 22:37:13 +05:30
f806e4d3bd fix(settings/profile): fix share dropdown invisible + expand click broken
Root cause: parent div had overflow-hidden which clipped absolute-positioned
dropdowns, making them render but be invisible (looked like click did nothing).

- Removed overflow-hidden from profile card container
- Share dropdowns now use fixed positioning to escape any parent overflow
- Merged chevron into the expand button so the whole left area is one tap target
- Per-product share sheet also changed to fixed bottom-28 right-4

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 22:29:38 +05:30
fb402e9898 feat(settings/profile): move share to per-product, profile share on card header
- Each product row now has an ↗ share button (product URL shared, not profile)
  WhatsApp message: "Found this for our baby — [title]: [url]"
- Only one product share sheet open at a time; closes on backdrop tap
- Profile page share (↗) moved to the profile card header row where it
  contextually belongs — shares the /m/slug link, not a product
- Removed the share button that was on the Products section header

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 19:40:51 +05:30
8598259fb1 feat(settings/profile): collapsible profile card + share sheet
Collapsible profile card:
- Starts collapsed if profile already exists (shows name + URL slug)
- Expands/collapses via chevron toggle row
- Auto-collapses after Save Profile succeeds

Share sheet on Product Recommendations:
- ↗ Share button appears once a valid slug is set
- Options: Copy link (clipboard, shows  feedback), WhatsApp deep link,
  and native Web Share API ("More options") when browser supports it
- Backdrop click closes the sheet

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 19:34:52 +05:30
3b62841bd4 fix(settings/profile): align My Profile page with app theme
- Background: flat gray → rose-to-amber gradient (matches all other pages)
- Header: add back button with rounded-xl pill style + xl font title
- Buttons: bg-pink-500 → bg-rose-400 throughout
- Loading: spinner → branded emoji bounce (consistent with home screen)
- Toggle: checkbox → styled toggle switch matching app UI language
- Layout: remove desktop max-w-lg wrapper; full-width mobile layout
- Input styling: unified inputClass with focus ring + consistent padding
- Empty state: plain text → emoji + two-line message
- Product rows: border div → bg-gray-50 pill card (matches wardrobe style)
- Add pb-24 so content clears the bottom nav bar

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 19:26:38 +05:30
3d0e6ed46c fix(menu): remove absolute positioning that hid Wardrobe on mobile
Settings/Sign Out used absolute bottom-0, overlapping the bottom of the
menu items list on shorter screens. Converted to normal flow with mt-4
and added pb-28 so the list clears the bottom nav bar.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 19:17:45 +05:30
70ff02c930 feat(home): overhaul home screen with bottom nav and UX improvements
- Add persistent bottom navigation bar (Home / Activity / AI / Menu)
- Fix TodaySummary bug: last-log times now show today's events only
- Replace 6 hardcoded AI chips with 3 AI-generated context-aware chips
- Show child's real profile photo in baby card (fallback to 👶 emoji)
- Recent Activity limited to 3 items with "See all →" link to /activity
- "Suggested now" promoted to prominent amber banner with "Log it →" CTA
- Offline pending banner is now a tappable retry button
- Branded loading state with bouncing emoji (🍼 😴 🚼 👶)
- Remove unused Button import from page.tsx
- Expose image_url via /api/children and Child type/FamilyProvider

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 19:08:58 +05:30
8a75adb94e feat(home): horizontal scrollable quick log + wardrobe shortcut
- Convert Quick Log from grid-cols-4 to horizontal scroll strip so
  it scales to any number of actions without layout breakage
- Add Wardrobe 👗 shortcut linking directly to /wardrobe/add,
  saving mama the Menu → Wardrobe → Add Garment 3-tap journey
- Fix: replace non-existent `no-scrollbar` class with `scrollbar-hide`
  across page.tsx, wardrobe pages, memories page, and TabBar component

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 18:21:32 +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
c4304615ec chore(dev): align dev Postgres to pg18 matching production
Production runs Postgres 18; the dev compose file pinned pg16. A pg_dump
from prod (v18) cannot be restored by a v16 pg_restore — the dump header
is rejected. Matching the major version fixes restores and removes a
latent source of dev/prod behaviour drift.

Also adopts the pg18 image's data-directory convention: the volume now
mounts at /var/lib/postgresql (the image places data in a version
subdirectory), and drops the obsolete compose 'version' key.
2026-05-23 14:22:15 +05:30
9b9b551463 feat(db): wire migration runner into the deploy pipeline
Makes schema changes deploy automatically: edit schema -> db:generate ->
commit -> push -> Dokploy redeploys -> migrations apply on container start.
No more Dokploy database terminal.

Components:
- src/db/migrate.ts: standalone migrator (single short-lived connection,
  fails loud on error so a bad migration crashes the container instead of
  letting the app serve a half-migrated schema)
- scripts/build-migrator.mjs: esbuild bundles migrate.ts -> dist/migrate.mjs
  with drizzle-orm + postgres inlined. Needed because Next.js standalone
  output keeps neither as a separate node_modules package.
- Dockerfile: builder runs db:build-migrator; runner copies migrate.mjs +
  drizzle/; CMD is 'node migrate.mjs && node server.js'
- package.json: db:generate / db:migrate / db:studio / db:pull /
  db:build-migrator scripts; esbuild promoted to an explicit devDependency
- pnpm-lock.yaml resynced

BUG FIX: .dockerignore had 'drizzle/' — migration SQL was excluded from the
build context, so even a correct Dockerfile COPY would have found nothing.
This was the second half (with the .gitignore bug in commit 1) of why the
migration pipeline never worked. Now only _archived/_introspected are
excluded.

Verified: full docker build succeeds; runner image contains migrate.mjs +
drizzle baseline; migrator tested end-to-end against a scratch DB (35
tables created, __drizzle_migrations populated, idempotent on rerun).
2026-05-23 13:40:30 +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
a3d3a140ed refactor(db/schema): align TypeScript schema with production
Path A baseline reconciliation. drizzle-kit pull against tia_prod showed
prod had drifted well past schema.ts because legacy hand-rolled migrations
0003-0015 wrote to the DB but were never reflected back into TypeScript.

Shared-table drift fixed:
- users:          + password_hash, + password_updated_at
- families:       + tier, + max_children, + max_members
- children:       col is 'stage' (kept JS key currentStage -> stage);
                  'image_url' not 'profile_photo_url'; birth_date is DATE;
                  sex nullable; dropped phantom stage_overrides
- family_members: dropped phantom display_name
- family_invites: dropped phantom display_name, accepted_at
- audit_log:      + resource_id, + resource_type; metadata -> jsonb; +5 indexes
- memories:       + vision_tags (text[]), + vision_embedding (vector 1536)
- logs.ts:        'diapers' phantom table renamed to diapersLogs ('diapers_logs')

19 missing tables added across new files:
- admin.ts:     admins, admin_sessions, password_resets
- support.ts:   support_tickets, support_responses
- ai.ts:        chat_sessions, chat_messages, ai_usage
- medical.ts:   medicines, medication_doses, allergies, illness_logs, doctor_visits
- affiliate.ts: member_profiles, recommended_products, product_clicks
- logs.ts:      + milestone_achievements
- audit.ts:     + log_corrections

BUG FIX: schema/index.ts never re-exported ./logs — Drizzle was blind to
feeds/sleeps/vaccinations/growth/medications. Now exported.

Verified: tsc --noEmit has zero non-test errors. Dropped phantom columns
confirmed to have zero references in src/.
2026-05-23 12:17: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
5fe24b8c59 feat(settings): move growth CSV export from growth header to settings page
- Remove 📥 export button from growth page header (less clutter)
- Add "Export Growth Data" row in settings with child name and CSV download
- Fetches growth records on demand, shows loading state while exporting

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 23:24:28 +05:30
acd2adde7e fix(growth): move add form to top, fix empty-state bug, clean up UI
- Add Measurement form now appears directly below the header (not buried
  after WHO card) so it's immediately visible when + Add is tapped
- Removed `&& latest` guard — form now works even with zero records
- Goals and Add are mutually exclusive: opening one closes the other
- 3-column grid for measurement inputs (weight / height / head on one row)
- Sticky header with backdrop-blur, smaller title (text-sm), icon-style
  export and goals buttons
- All cards use rounded-2xl + shadow-sm for consistent look
- "Add First Measurement" in empty state scrolls to top and opens the form

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 22:49:30 +05:30
9c0afa2054 feat(memories): 4-col grid, hover effects, folder label per tile, no blank tiles
- grid-cols-4 (~40% smaller than 3-col) with gap-1 and rounded-xl corners
- Hover: scale-110 image + dark overlay + expand icon (⤢)
- Subtle shadow + ring border on each tile
- Folder emoji + name shown below each tile (when assigned)
- Filter out tiles with no URL and remove tiles that fail to load (onError → null)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 22:45:23 +05:30