Commit graph

21 commits

Author SHA1 Message Date
39b2787484 fix(hero): remove floating screen label above phone mockup
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 10:39:29 +05:30
261a9cbbcb feat(hero): CSS-animated 3-screen phone mockup carousel
Replaces the static placeholder with an auto-cycling mockup that shows
three real app screens — Home/Quick Log, Vaccinations (IAP), and
Memories — mirroring the actual app card/row UI patterns.

- 2500ms auto-advance, pauses on hover
- Smooth opacity crossfade (duration-500) between screens
- Active dot indicator stretches to pill shape (w-4)
- Floating label pill above phone changes with active screen
- All pure CSS/Tailwind — zero external assets, static page unchanged

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 10:32:39 +05:30
e5a59c5191 feat(blog): 3-col layout, breadcrumbs, TOC + footer 3-col bottom bar
Blog listing (/blog):
- Breadcrumb: Home › Blog
- Left sidebar: chronological timeline with dot + date + category badge
- Right sidebar: "Browse by topic" category counts, quick reads, CTA card
- Still single column on mobile (sidebars hidden)

Blog post (/blog/[slug]):
- Breadcrumb: Home › Blog › Post title
- Left sidebar: numbered Table of Contents (section headings as anchor links)
  with "← All articles" back link below
- Right sidebar: "More articles" list + "Filed under" category + CTA
- scroll-mt-28 on headings so sticky nav doesn't cover anchor targets
- Both back-to-listing links visible (sidebar + article footer)

Footer bottom bar:
- Split into 3-column grid: © left · privacy tagline center · ❤️ India right

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 10:06:14 +05:30
e7a332cacd tweak: install prompt snooze — Later=2d, No thanks=7d
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 01:05:00 +05:30
093903162e improve: smarter install prompt — visit gate + snooze instead of permanent dismiss
Old behavior: dismiss once -> never shown again forever.
New behavior:
- Only shows after 3+ sessions (user has seen value first)
- "Later" -> snoozes for 7 days, re-asks after that
- "No thanks" -> snoozes for 30 days
- Once actually installed (Android accepted) -> permanently hidden
- iOS: two buttons (Later / No thanks) instead of bare X, so intent is clear
- Android: tapping X now snoozes rather than permanently suppressing
- Dark mode support added to both banners

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 01:00:52 +05:30
3cfcbdc0ca fix: garment upload MIME/proxy, log edit time pre-fill, date-ist hardening, wardrobe camera+gallery
- /api/img: add garments/ to ALLOWED_PREFIXES so garment images proxy correctly
- garments upload: resolve Android empty MIME type from file extension; return
  /api/img proxy URLs instead of raw pub-*.r2.dev (blocked by Cloudflare Bot Mgmt)
- garments route + [id] route: toDto() now builds /api/img?key= proxy URLs
- date-ist.ts: add toUTCDate() helper -- strings without Z/offset treated as UTC,
  preventing browser local-time misinterpretation; used in fmtTime, fmtDate, dateIST
- LogModal: add editTime to SmartDefault; pre-fill time picker (custom preset) when
  editing an existing log instead of defaulting to now
- activity page: pass editTime: log.loggedAt in handleEdit so LogModal pre-fills
- wardrobe/add: explicit Camera and Gallery buttons via separate hidden inputs
  (one with capture=environment for direct camera, one without for media picker)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 00:42:04 +05:30
cfb0f4b2eb Fix timestamp timezone — logs now always show in IST regardless of server TZ
Root cause: postgres.js v3 parses `timestamp without timezone` columns as
`new Date("YYYY-MM-DD HH:mm:ss")` (space format, no Z). V8 treats this as
*local time*, so on a non-UTC server (Dokploy host = Europe/Helsinki UTC+3)
the parsed Date object is 3 hours off, making logged times show as server
time instead of the user's IST.

Fixes:
- db/index.ts: add custom `timestamp` type parser that forces UTC by
  converting space-format to ISO with 'Z' before calling new Date().
  Also set `connection: { TimeZone: "UTC" }` so PostgreSQL sessions always
  store/return timestamps in UTC regardless of server OS timezone.
- CalendarView.tsx: use `dateIST()` for day grouping (fixes midnight-boundary
  bug where a 12:30 AM IST entry appeared on the previous UTC day) and
  `fmtTime()` for time display (replaces toLocaleTimeString without timezone).
- MedicineTab.tsx: replace toLocaleString() with fmtDate/fmtTime (IST-aware).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 23:13:57 +05:30
d8c9500949 Remove UploadProgress debug toast; fix R2 image proxy and memory pipeline
- Delete UploadProgress component (was debug UI, no longer needed)
- All 3 pages (home, memories, profile) now use simple inline error state
  instead of the step-by-step toast
- /api/img proxy: fetch R2 objects server-side to bypass Cloudflare Bot
  Management 503s on pub-xxx.r2.dev cross-origin img requests
- All API responses (memories, children, profile) now return /api/img proxy
  URLs via toProxyUrl() helper in src/lib/r2-proxy.ts
- Fix memory pipeline: vision failure now marks status='ready' instead of
  'failed'; thumbnail failure no longer blocks vision via .catch() separation
- Reset stuck 'processing' memories via debug-migration endpoint
- memories page: replace full-screen overlay with small  badge on tile

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 22:53:16 +05:30
51e36633b9 Add step-by-step upload progress UI across all three upload points
Each upload now shows a persistent card with 3 labelled steps and their
live status (pending → active → done / error). Errors include the exact
HTTP status code + raw response body (handles non-JSON from Traefik,
nginx, etc. that return HTML error pages). The card stays visible after
failure so the user can read the diagnostic before dismissing.

Changes per surface:
- src/components/UploadProgress.tsx — new shared step-tracker component
- profile/page.tsx — step card rendered below avatar; safeResponseText()
  reads raw body so a Traefik 413 shows "HTTP 413: <html>..." not just
  "Upload failed"
- memories/page.tsx — fixed toast expands to show all 3 steps; dismissible
  after done/error; same safeResponseText pattern
- home/page.tsx (baby photo) — same fixed toast as memories; 3 steps with
  HTTP codes and raw body on error

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 11:17:01 +05:30
0a9def36bf feat(quota): enforce 1-baby limit on free plan
- quota.ts: checkChildLimit() mirrors checkMemberLimit() using families.max_children
- POST /api/children: returns 403 child_limit_reached when free family is at limit
- ChildLimitBanner: new banner component for the family page
- /family page: shows banner + hides Add Baby button when at limit

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 00:52:23 +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
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
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
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
96d28cbadc refactor: full codebase sweep — shared types, utilities, component splits
New foundations:
- src/types/index.ts — shared domain types (Child, Log, GrowthRecord, Medicine,
  Dose, Allergy, Visit, Illness, Vaccination, AIChat, ChatSession, Goal)
- src/lib/formatting.ts — calculateAge, formatAge, formatTimeAgo (eliminates
  3 duplicate implementations spread across page.tsx and growth/page.tsx)
- src/lib/api.ts — typed fetch helpers (api.get/post/patch/delete) with
  consistent error handling; replaces manual fetch boilerplate

New shared components:
- src/components/PageHeader.tsx — reusable back-link + title header
- src/components/TabBar.tsx — horizontal pill tab bar
- src/components/CalendarView.tsx — extracted from activity/page.tsx (was ~170 inline lines)
- src/components/medical/ — medical page split into 5 focused tab components:
  VaccineTab, MedicineTab, AllergyTab, VisitTab, IllnessTab

Pages updated:
- medical/page.tsx: 1029 → 42 lines (thin shell wiring the 5 tab components)
- activity/page.tsx: uses CalendarView + shared Log type + api.ts
- growth/page.tsx: uses shared GrowthRecord/Goal types + formatAge; fixes
  `any` catch clauses; fixes undefined → null in Chart.js dataset values
- page.tsx (home): uses shared Log/AIChat/ChatSession types + formatTimeAgo/
  calculateAge from formatting.ts; removes inline type definitions
- ai/page.tsx: uses shared AIChat/ChatSession types
- FamilyProvider.tsx: uses shared Child type; fixes `c: any` mapping

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 21:37:39 +05:30
3ebb3055a5 feat(logging): time presets, FAB on activity, today summary, smart defaults
- Extract offline queue to src/lib/offline-queue.ts
- Extract shared LogModal with time presets (Just now/5/15/30min/Custom)
  and smart default pre-fill from last log of same type
- Replace ActivityScroller with TodaySummary (today's counts + last time)
- Fix activity page: GET /api/logs without type param now returns all logs merged
- Fix field naming: log.loggedAt / log.amount (camelCase throughout)
- Add FAB to activity page for zero-navigation quick logging
- Recent Activity shows 5 most recent entries with correct field names

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 11:21:00 +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