Avatar (baby card):
- Switch tia-portrait.jpg → tia-portrait.png (white/transparent bg, no dark edges)
- bg-white on container so PNG transparency shows correctly
- object-position: 65% top to shift face right and center it in the circle
Memories grid (all local, no external URLs):
- memory-bath.jpg: baby feet in towel (downloaded from Unsplash, stored locally)
- memory-milestone.jpg: baby in pool float with sunglasses (local)
- Captions updated: 'Tiny toes 🛁' and 'Summer fun 😎'
About page P.S.:
- tia-portrait.jpg → tia-portrait.png (clean white background)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Content: complete "A Letter to Every New Parent" letter by Yashika &
Manohar — four named sections (Why We Named the App, Modern Families,
What We Believe, To the Parent Reading This) + signature
Images:
- /images/family-illustration.jpg — watercolour illustration of the
family in the hero, 2-col layout at lg (copy left, image right)
- /images/tia-portrait.jpg — Tia's portrait cropped to circle in
the closing signature
Design:
- Hero: rose→amber gradient, 2-col at lg, family illustration bottom-aligned
- Breadcrumb below hero intro
- Letter body max-w-2xl, generous leading, section borders
- "Nani in Lucknow / Dadu in Jaipur" in amber callout card
- Beliefs rendered as rose dot list
- Key line "Tia is here to help you remember your baby" in rose-700
- Closing parenthetical about Tia in a rose-50 italic card
- CTA: Get started → with hover lift
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
document.currentScript is always null for async scripts (which is how
next/script afterInteractive works). Umami's first check was:
const{currentScript:l}=c; if(!l)return;
This caused an immediate exit — zero tracking.
Fix: fall back to querySelector('script[data-website-id]') when
currentScript is null, so Umami finds its own script tag and reads
the data-website-id / data-host-url attributes correctly.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
analytics.manohargupta.com/script.js returns 503 when loaded as a
browser sub-resource from tia.manohargupta.com (same Cloudflare Bot
Management issue as R2 images). Fix: serve the script from /umami.js
(same origin, no cross-origin block) and use data-host-url to tell it
where to POST events back to the Umami instance.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add metadataBase to root layout so OG/Twitter/canonical URLs resolve
to absolute https URLs (fixes broken social previews)
- New src/lib/seo.ts with SITE_URL + JSON-LD builders
- New robots.ts (disallow api/admin/private app paths) and sitemap.ts
(marketing pages + blog posts with real lastmod dates)
- JSON-LD: Organization/WebSite/SoftwareApplication on home,
Blog+Breadcrumb on blog list, BlogPosting+Breadcrumb on posts
- Per-page canonical + Open Graph on all marketing pages; article OG
+ Twitter cards on blog posts; per-post dynamic OG image
- noindex on (app) and admin layouts; richer PWA manifest
- Fix CSP to allow plausible.io in script-src/connect-src (analytics
was silently blocked)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Wardrobe add page:
- Remove capture="environment" so Android shows the Camera/Gallery chooser
instead of opening the camera directly
- Vision AI no longer blocks the UI: photo preview + form appear instantly
after selecting an image; upload spinner shows on the thumbnail while R2
upload runs; vision AI fires in the background and fills in tags when done
without interrupting the user (they can pick size/occasions in parallel)
- "AI tagging…" pulsing badge in header while vision runs; "✨ AI pre-filled"
badge when done; form fields are only overwritten if the user hasn't already
typed/selected something (functional state updater with prev-value guard)
- Save button is disabled (with "Uploading photo…" label) until R2 upload
completes — prevents saving a garment with no imageKey
/api/time endpoint (GET, no auth):
- Returns { utc, istDate, istTime, ist, offsetMinutes } for the current server
time in Asia/Kolkata so the app can verify server clock and surface IST time
reliably (can be called from browser console at /api/time)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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>
- 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>
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>
Memories "count 2 but no images" root cause:
- DB rows exist with processing_status='failed'/'uploading' from aborted uploads
whose R2 objects never actually landed. The img onError fires and hides the
tile, but the count still includes these orphaned rows.
- Fix: GET /api/memories now excludes failed rows and uploading rows older than
30 min from both the SELECT and the count. Also fires a background DELETE to
clean up orphaned rows so they stop accumulating.
Profile / memories upload silent failures:
- Some Android cameras return file.type="" which caused the avatar API to reject
the upload with a 400 error. Error was caught but shown in a small text node
buried below the form — invisible when looking at the avatar area.
- Fix: added resolveContentType() helper (used in profile, memories, home) that
falls back to extension-based detection when file.type is empty/octet-stream.
- Fix: profile page now uses a separate uploadMsg state rendered immediately
below the avatar so errors/success are always visible on mobile without scroll.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Two-pronged fix for Android PWA shell launching to the wrong page:
1. middleware.ts: if a logged-in user (valid tia_session cookie) visits /,
immediately redirect them to /home — catches all existing installs whose
cached start_url still points to /?source=pwa
2. manifest.ts: change start_url from /?source=pwa to /home?source=pwa
so any fresh install or reinstall opens directly to the app home
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Bug 2: raise all FABs (activity, memories, circle) to z-50 and bottom-24
so they sit above the bottom nav (z-40, ~56px tall)
- Bug 1: add onError handlers to baby photo (home) and parent avatar (profile)
so a broken R2 URL shows the 👶 / initials placeholder instead of a broken
browser icon; reset error flag after a successful upload
- Bug 3: replace ⏳ emoji spinner with a proper CSS spinner on home page baby
photo; add a fixed upload-progress toast to the memories page that appears
at the top of the screen for the full duration of the upload
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- 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>