Commit graph

375 commits

Author SHA1 Message Date
b64226cbd8 Merge pull request 'feature/quota-and-member-limits' (#2) from feature/quota-and-member-limits into main
Reviewed-on: #2
2026-05-27 18:05:20 +00:00
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
b6814579c6 feat(pwa): add Serwist service worker, manifest, icons, install prompt
- Wrap next.config.ts with @serwist/next (webpack mode, disabled in dev)
- Service worker: NetworkOnly for /api/*, offline fallback → /~offline
- Web app manifest via Next.js metadata API (app/manifest.ts)
- PNG icon set generated with sharp (192, 512, maskable-512, apple-180)
- iOS meta tags: appleWebApp, themeColor viewport export
- Middleware: pwaAssets early-return so /sw.js never gets a 302→login
- Offline fallback page at /~offline (static, no auth dependency)
- InstallPrompt component: beforeinstallprompt (Android) + iOS Share sheet instructions
- Logout (menu/page.tsx): purge all SW caches on signout (shared-device safety)
- Fix invite/[token]/page.tsx params type for Next.js 16 (use(params))
- Build script: next build --webpack (Serwist requires webpack, not Turbopack)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 23:20:48 +05:30
942a03d99a fix(footer): 3-col bottom bar with distinct bg-gray-100 shade
Left: © year · Centre: privacy tagline · Right: built with ❤️ in India

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 00:37:13 +05:30
b0423dfea8 fix(nav): always-visible sticky nav, rose pill button without Google G
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 00:21:44 +05:30
2b8312efef polish(marketing): favicon, scroll-reveal nav, Google button, font + hovers
- Favicon → 🌸 cherry blossom SVG (replaces smiley)
- Nav: removed Pricing/Privacy links; nav is now invisible at top and
  slides in after scrolling 75% past the hero (scroll-reveal client component)
- Hero CTA: white background + proper 4-color Google G (matches login page)
- Hero subtitle: font-light, text-xl, leading-loose for a more editorial feel
- Feature cards: hover border highlight + emoji scale on group-hover
- Heirloom vision cards: hover border on hover
- Privacy items: bg-rose-50 on hover
- Final CTA button: hover shadow lift + active:scale-95

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 00:05:10 +05:30
2a09c027fa feat(marketing): public homepage replacing / → /login redirect
- Add (marketing) route group: /, /pricing, /privacy, /terms
- Add (app) route group: moves all authenticated pages, app home → /home
- Root / is now a static marketing page (zero DB imports, zero auth)
- NavAuthButton client component: shows "Open Tia →" if logged in, else "Continue with Google"
- Plausible analytics hook in marketing layout
- Auto-generated OG image via opengraph-image.tsx
- Middleware updated to allowlist marketing routes
- All /-redirects updated to /home (login, onboarding, invite, circle join)
- BottomNav home tab updated: / → /home

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 23:26:26 +05:30
f1d4374609 polish(icons): update nav icons to warmer, more refined set
Replaces generic/robotic emojis with purposeful ones:
🏠🏡 Home, 📋📝 Activity, 🤖🔮 AI, 📈🌿 Growth,
💊🩺 Medical, 👗🧺 Wardrobe, 👨‍👩‍👧💞 Circle

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 16:25:42 +05:30
31b4a9480a fix(email): use tia@tia.manohargupta.com + auto-fallback to shared domain
- Primary sender: tia@tia.manohargupta.com (verified subdomain in Resend)
- sendWithFallback() retries with onboarding@resend.dev if Resend rejects
  the primary domain (covers the window while SPF is still propagating)
- Both sendFamilyInviteEmail() and sendVerificationEmail() use the fallback

Update EMAIL_FROM in Dokploy to: Tia <tia@tia.manohargupta.com>

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 16:01:55 +05:30
8a3da6a5a6 fix(email): fallback to onboarding@resend.dev until custom domain verified in Resend 2026-05-24 15:18:41 +05:30
e2a3e83638 docs+fix: overhaul CLAUDE.md + expose emailStatus in invite response
CLAUDE.md:
- Add RESEND_API_KEY, EMAIL_FROM, NEXT_PUBLIC_APP_URL to required env vars
- Document DB migration pattern (journal + hot-fix via debug-migration POST)
- Document R2 3-step proxy upload pattern (CORS note)
- Document users.image (NOT avatar_url) and two separate photo features
- Document admin auth server-component pattern
- Document family_invites fix, invite flow, cancel invite
- Full data storage table with all localStorage keys

Invite route:
- Return emailStatus in POST response so caller can see if Resend fired
  or why it failed (noKey / error message)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 15:15:53 +05:30
4dcdc5a572 feat(invites): add cancel button to revoke pending invites
- New DELETE /api/invites/[id] endpoint — only the owning family can delete
- Settings page shows expiry date on each pending invite
- Cancel button removes invite instantly from list (optimistic UI)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 14:47:43 +05:30
781dd8f1df fix(invites): hot-apply family_invites migration via debug-migration endpoint
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>
2026-05-24 14:43:15 +05:30
6d7feca397 fix(invites): remove dependency on missing display_name / accepted_at columns
The family_invites migration hasn't run yet on production. Work around by:
- Removing display_name from INSERT and SELECT (optional field anyway)
- Removing accepted_at IS NULL filter from GET and accept queries
- DELETE the invite row on accept instead of marking accepted_at — keeps
  invites single-use without needing the extra column

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 14:38:53 +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
cff17a079d fix(profile): use correct image column on users table (not avatar_url)
users.avatar_url doesn't exist — the column is `image`. Querying/updating
a non-existent column caused a SQL error on every profile load (blank name
& email) and on every avatar save/delete.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 14:07:49 +05:30
7816247073 fix(profile): fix parent avatar upload with 3-step proxy pattern + remove photo
- Switch from broken FormData POST to 3-step flow: POST init → PUT /api/upload proxy → PATCH save
- Add remove photo option that clears DB and deletes R2 object (DELETE /api/auth/avatar)
- Add deleteOldAvatar() helper that cleans up old R2 object on every upload
- No orphaned objects in R2, no wasted storage

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 13:53:49 +05:30
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