Hardened the app and overhauled journaling UX
2026-03-01
Production hardening
- Security and reliability sweep: root 404, PWA manifest, route loading skeletons, runtime env validation, staging Basic Auth warning, and security headers (CSP/HSTS/XFO). Shipped as a bundle in PR #303.
- Added error boundaries (
global-error.tsxand route-levelerror.tsx) so runtime crashes have recovery paths. - Tightened API resilience: wrap fetch in
safeFetch(network/timeout), guard JSON parse, parse RFC 7807 Problem Details, and swapped silent catches for visible toast errors. Also fixed rollback for failed optimistic notification updates and added fallbacks inuseMarkdown. - Graceful timeouts: if journal regeneration polling hits the max attempts, show a clear toast and stop pretending it’s still working.
- Cleanup and compliance: removed stale TODOs and dead code, and wired up real account deletion. PR #304.
- Volume: 78 commits, 15 PRs across frontend and brand — big hardening push.
"9 of 13 'unimplemented backend' TODOs were already implemented."
Journals UX and navigation
- Replaced “On This Day” with the Time Jump system, then refined it: stepped intervals (1mo/3mo/6mo/1y/… up to 5y), dynamic empty-state copy, and moved it to the daily detail page backed by a
/time-jumpAPI. - Added prev/next day nav and auto-polling when landing directly on pending/generating journals for a smoother wait state. Shipped in PR #268.
- Failure UX: surfaced failed entries (no more invisible gaps), then simplified to a display-only error with auto-retry messaging and removed inline regenerate buttons to avoid quota traps.
- Visual polish: ink dividers between cards, fade-in-on-scroll animations, body text clamped to 3 lines in lists, timezone fix for date math, and the age-based overgrowth flora on detail/share pages.
- Sharing: branded not-found for expired/invalid share links.
Billing, subscriptions, and data export
- Subscription flows got a full pass: inline confirm panels for upgrade/period switch/resume/cancel-downgrade; unified pending-downgrade/canceling UI; disabled plan changes during pending states; clearer pricing and “Valid until”; hid canceled cards and “Manage billing” on Free; and added short-term polling to sync post-webhook state.
- Adapted to backend’s downgrade semantics (no immediate charge, resume/cancel-downgrade supported).
- Data export landed in settings, then ungated to meet GDPR portability (available to all users, no plan wall).
Analytics overhaul
- Introduced an in-app analytics SDK with auto pageviews and custom events, working for both anonymous and authenticated users (provider moved to root).
- Instrumented key transactions across auth, onboarding, journals, subscriptions, integrations, and settings.
- Delivery path iterated quickly: from “await before nav” → queue with timed/threshold flush + keepalive and visibility/pagehide flushing → per-event fallback to match endpoint → bulk POST to the new
/analytics/events/bulk. Added tests for flush listeners and queue behavior.
Brand polish
- Synced design tokens/components with the app: switched JP serif to Klee One, added multilingual CJK support, adjusted journal body size, and removed
next/fontCSS vars for static use. - Expanded the overgrowth system to 6 stages (fresh → ancient) with updated inline SVGs.
- Tracked in the brand repo via PR #1.