Skip to content

Roadmap

Last audited: 2026-05-23

Legend

Symbol Meaning
Completed
🔄 In Progress
📋 Planned
🎯 High Priority
💡 Nice to Have

Phase 1: Bootstrap ✅

Core infrastructure, backend, and initial frontend shell.

Phase 1.1: Foundation ✅

  • Docker Compose dev + prod environments
  • PostgreSQL + Redis + MinIO + Mailpit services
  • pnpm monorepo workspace (backend / frontend / shared / docs)
  • ESLint + Prettier + Husky pre-commit hooks
  • TypeScript strict mode across all packages
  • Jest configuration with 70%+ coverage threshold
  • CI/CD pipeline (GitHub Actions)
  • 🎯 Mobile identifier consistency guardrail — pnpm check:ios-identifiers validates Expo app IDs against native iOS (PRODUCT_BUNDLE_IDENTIFIER + URL scheme) and Android (applicationId) config; enforced in local frontend/frontend:ios scripts and CI
  • MkDocs documentation site
  • 🎯 Comprehensive API reference documentation — dedicated docs/api/ section with per-resource pages covering all endpoints, query params, request/response schemas, role requirements, and error codes; agent instructions added to AGENTS.md to keep docs in sync with router changes

Phase 1.2: Core Backend ✅

  • Hono API server (backend/src/)
  • Drizzle ORM with PostgreSQL
  • Better-auth (session-based auth, email/password)
  • Session keep-alive on web and native (visibilitychange, AppState, 30-min interval)
  • Role-based access control (profile.role)
  • Organizations CRUD (/api/organizations)
  • User management (/api/users)
  • File upload via MinIO (/api/files)
  • Email via Nodemailer + Mailpit
  • 🎯 Org membership soft-disable — DELETE /api/organizations/:id/users/:userId sets membershipStatus = 'disabled' instead of removing the row; tenant middleware blocks org-scoped access for disabled members; re-enable via PATCH { membershipStatus: 'active' }; UI shows Disabled badge in admin user lists

Phase 1.3: Frontend Shell 🔄

  • Expo app scaffolding (web + iOS + Android)
  • NativeWind (Tailwind) styling
  • Platform-aware typography tokens (FONT_ROLE) for web/native header + content fonts
  • TanStack Query v5 for server state
  • Zustand v5 for client state
  • Auth screens (login, register, password reset)
  • 🎯 📋 App-wide screen layout/style standardization program — enforce shared layout primitives and navigation helpers across all feature areas so new screens do not introduce isolated per-view scaffolds; migrate existing screens incrementally (admin, mentorships, settings, profile, messaging) to shared structures and tokens
  • 🎯 🔄 Platform Admin dashboard (core UI and APIs implemented; session metrics still incomplete)
  • 🎯 🔄 Organization Admin dashboard (core UI and APIs implemented; analytics and session metrics still incomplete)
  • 🎯 📋 Session booking flow (not implemented end-to-end yet)
  • 🎯 🔄 Coach / Teacher home screens (Settings tab + route added; Inicio/Mensajes improved; Libreria removed pending content)
  • 📋 Push notifications (token registration + delivery pipeline fully implemented: chat messages notify recipients, session scheduling/cancellation notifies both participants; notification taps navigate to the relevant screen)
  • 🎯 Admin user-edit avatar flow with crop support — EditUserScreen now supports upload from camera/library, crop/adjust confirmation, and avatar persistence via PATCH /api/users/:id
  • 📋 In-app messaging (screen scaffold exists; backend and conversation flow not implemented)
  • 🎯 Invitation-first onboarding gate (users with pending invites are routed to invitation handling before accessing app functionality)
  • 🎯 Public register flow and access restoration — added dedicated Register route/screen on web and native, restored the button-based Welcome screen as the default unauthenticated entry point, wired Welcome CTAs to login/register paths, shows a dedicated RegisterSuccess verification screen ("Verifica tu correo") after successful account creation, includes POST /api/auth/resend-activation with a cooldown timer, routes verification completion to /email-verification with in-app success/error feedback when opened from email links, and now hardens activation callbacks so emails always resolve to frontend routes (with mandatory FRONTEND_URL/TRUSTED_ORIGINS deployment config)
  • 🎯 Invite signup visual refresh — restyled invite onboarding states (loading, invalid invite, existing account, active invite form) with a shared auth shell while preserving existing invite token, signup, and acceptance behavior
  • 📋 Conditional social login visibility on Welcome — Apple/Google buttons now render only when provider env toggles or client IDs are configured
  • 🎯 📋 Organization choice and unassigned-user onboarding gate — after login, users with more than one active organization must stop at a dedicated chooser that lists each organization with its role before entering the app; users with exactly one active organization continue normally; users with no active organization or no resolved role see a blocking screen with contact-admin guidance and logout; applies on web and native and must be enforced before role-based app routing

Phase 1.4: Production Readiness 🔄

  • 🎯 EAS Build + OTA Update configuration (iOS + Android profiles with channels; expo-updates installed; eas:update:staging / eas:update:production scripts added)
  • 🎯 🔄 Production Docker images + Dokku deployment (core infra in place; deployment flow still being completed)
  • 🎯 First-run bootstrap and migration runbook (added /api/setup/first-run, Dokku migration runner, and staging operational docs)
  • 🎯 🔄 Automated database backup (scripts/backup-to-r2.sh + production runbook documented; infrastructure rollout and live cron verification still pending)
  • 🔄 Sentry error tracking (SDK + app initialization and capture hooks implemented; release environment configuration and artifact verification still pending)
  • 🎯 📋 Android release stack-trace decoding pipeline — follow Android app optimization guidance to make obfuscated release crashes actionable: persist mapping.txt per release, document retrace workflow, and automate symbol/mapping upload for crash reporting
  • 🎯 📋 iOS release symbolication pipeline — persist dSYM artifacts per release, align JS sourcemap upload with native symbols, and document symbolication/runbook steps for actionable iOS crash reports
  • 📋 Analytics integration (dashboard placeholders exist; tracking pipeline not implemented)
  • 💡 🔄 Multi-language support (device-locale detection + AsyncStorage persistence + EN/ES switcher on Settings screen)
  • 🎯 📋 Organization default language setting — each organization configures a default language used for email notifications and as the initial language on invite/signup onboarding
  • 🎯 📋 Database field-level encryption for sensitive PII (e.g. phone numbers, birth dates) using a server-side encryption key; transparent encrypt/decrypt at the ORM layer
  • 🎯 API key authentication for frontend access — global X-Client-Key header validated by Hono middleware before any route; CLIENT_API_KEY env var on backend, EXPO_PUBLIC_CLIENT_KEY on frontend; timing-safe comparison; server refuses to start without the key

Phase 1.5: Advanced Features 💡

  • Video session support
  • 🔄 Custom reporting and analytics dashboard (navigation and placeholder screens exist)
  • 🎯 🔄 Real-time session list updates via SSE (Hono streamSSE + Redis pub/sub; react-native-sse polyfill on native; coach/teacher session list refreshes instantly on peer mutations — no manual refresh)

Phase 2: Real Functionality 📋

Core product features that make the app usable end-to-end.

Phase 2.1: User Profile ✅

  • 🎯 Enhance profile screen with complete user information:
  • Avatar upload via camera roll (implemented in Phase 1)
  • Name and birth date fields with date picker (implemented in Phase 1)
  • Phone field with country dial code picker (flag + dial code, Panama default)
  • Localized country names (EN/ES via country-names.ts locale map)
  • Date picker with Confirm/Cancel buttons on iOS; native dialog on Android
  • Tightened field validation (phone format, birth date not in future, age ≥ 18 years)
  • Country-aware phone validation via libphonenumber-js (PA / US / ES and all 195+ supported countries)
  • Shared Zod schema (updateProfileSchema) used by both backend and frontend
  • Logout button for Coach / Teacher roles
  • 💡 Push notification opt-in toggle, mobile only, ask for permissions if not present.

Phase 2.4: Invitations & Access ✅

  • 🎯 Organization invite creation and resend from org user management
  • 🎯 Role ceiling enforcement on invite creation (OrganizationAdmin all org roles, Manager coach/teacher only)
  • 🎯 Invite token page supports immediate sign in when email already has account
  • 🎯 Invite signup no longer auto-accepts; manual accept/decline required from pending invitations page
  • 🎯 Pending invitations page with accept/decline actions and backend gate on protected routes until all invites are handled

Phase 2.2: Mentorships & 1:1 Calls ✅

  • 🎯 Design mentorship data model:
  • Mentorship relationship (coachId, teacherId, organizationId, status)
  • Session entity (scheduledAt, duration, timezone, notes, status, agoraChannel)
  • Session lifecycle states (scheduled, confirmed, completed, cancelled, missed)
  • 🎯 Implement coach-managed scheduling flow for 1:1 calls with teachers:
  • Coaches can create, reschedule, and cancel sessions for assigned teachers
  • Teachers can confirm availability for sessions
  • Conflict validation prevents overlapping sessions
  • 🎯 Integrate Agora.io for video call sessions:
  • Generate Agora channel/session metadata at booking time
  • Secure token generation endpoint with expiration and role-based access
  • Join-call UI (VideoCallScreen) for Coach and Teacher on native (react-native-agora) and web (agora-rtc-sdk-ng browser SDK)
  • In-call controls: mute/unmute microphone and camera on/off (native + web)
  • Live emoji reactions synchronized through backend SSE (call-reaction event)
  • 🎯 Add incoming sessions to dashboards (Coach + Teacher):
  • Coach dashboard: "Upcoming Sessions" card with next session
  • Teacher dashboard: "Incoming Sessions" card with next sessions
  • Session status actions (join, confirm, cancel) from list screen
  • 🔧 Backend integration tests for /api/mentorships and /api/sessions endpoints
  • 🎯 Video session enhancements:
  • 5-minute early join window — users cannot join more than 5 minutes before scheduled start; UI shows "Starts in X min" countdown on all session list screens (MentorshipDetailScreen, CoachSessionsScreen, UserBookingsScreen)
  • Session completion modal — when coach clicks "End Call" a modal asks to "End & Mark Complete" (sets status → completed) or "End Call Only" (leaves without updating status)
  • Named waiting message — while the other participant has not joined, the overlay shows "Waiting for [Name]..." using the actual peer's display name
  • +15 minutes extra time — coach-only button appears in the last 5 minutes of a scheduled session; extends durationMinutes by 15 and resets the countdown timer
  • Session-ending-soon banner — countdown timer visible during call; warning banner appears when ≤ 5 minutes remain
  • Push notification 15 minutes before session start — cron job (session-reminder.job.ts) runs every minute, sends Expo push notification to coach and teacher; reminderSentAt column prevents duplicate sends; notification tap navigates to mentorship detail screen
  • 📋 Additional notification and reminder features:
  • Email reminders for users who are offline or push-disabled — sendSessionReminderEmail in email service, HTML template, sent alongside push
  • Configurable reminder cadence (15 min, 30 min, 1 hour, 24 hours) — reminderMinutesBefore column on profiles; cron job checks per-user trigger time with ±30s window
  • Missed-session follow-up reminder with reschedule action — session-missed.job.ts runs every 5 minutes; marks session missed, sends push + email to both participants; deep-link session_missed event navigates to mentorship detail
  • Reminder preferences in Settings:
    • Per-channel toggles: email (reminderEmailEnabled) and push (reminderPushEnabled) stored on profiles
    • Per-user cadence selector (15 min / 30 min / 1 hour / 24 hours) in Settings screen
    • Quiet hours / do-not-disturb window (quietHoursStart / quietHoursEnd columns, 0-23 hour values in user's timezone)
  • 🎯 Restructure mentorship navigation:
  • Mentorships menu (/mentorships) shows list of coach-teacher pairs with status
  • Mentorship detail (/mentorships/:id) shows sessions (video calls) for that pair
  • Schedule session form inline on detail screen (coach only)
  • Future Features section on detail (placeholders: reminders, goals, reports)

Phase 2.3: File Library (Mentorships) ✅

Deduplication-aware file storage shared across mentorship objects (sessions, notes, etc.).

  • 🎯 File storage model:
  • files table: id, sha256Hash, mimeType, sizeBytes, storageKey, uploadedBy, createdAt
  • fileAttachments join table: fileId, objectType (enum: session, note, mentorship, …), objectId
  • Deduplication by sha256Hash — if a matching hash already exists in storage, reuse the existing storageKey and insert only a new fileAttachments row; do not upload a second copy to MinIO
  • Orphan cleanup: when the last fileAttachments row for a fileId is deleted, hard-delete the files row and remove the object from MinIO
  • 🎯 Upload constraints:
  • Allowed MIME types: image/*, video/*, text/*, application/pdf, application/zip, application/x-zip-compressed
  • Maximum file size: 100 MB for all types except video
  • Maximum video size: 500 MB (video/* MIME types)
  • Server-side validation before S3 write — reject oversized or disallowed types with 400
  • 🎯 API endpoints (/api/files):
  • POST /api/files/presign — request a presigned PUT URL; client uploads directly to S3/R2 (no backend buffering)
  • POST /api/files/register — create the DB record after the direct S3 PUT
  • POST /api/files/:fileId/attach — attach a registered file to a mentorship object
  • DELETE /api/files/:fileId/attach/:objectType/:objectId — detach file; triggers orphan check
  • GET /api/files/:fileId — metadata + short-lived presigned download URL
  • All endpoints protected by requireAuth + org-scoped ACL (only members of the related org may access)
  • sha256Hash is nullable — presigned uploads bypass the backend so hash is not computed server-side
  • 📋 Frontend file library UI:
  • File picker supporting photos, videos, text files, PDFs, and ZIP archives
  • Per-file progress indicator during upload
  • Inline thumbnail/icon preview per MIME type category
  • Detach and (if orphaned) delete action gated by role (canManageFile)
  • File size validation on the client before upload attempt (show error before hitting the server)
  • 📋 ACL rules for file access:
  • PlatformAdmin: read-only access to any org's files for support purposes
  • OrgAdmin / Manager: full access within their org
  • Coach: access to files attached to their own sessions/mentorships
  • Teacher: read-only access to files shared within their mentorship
  • Backend enforces all ACL checks — frontend gating is UX only

Phase 2.4: Mentorship Chat ✅

1:1 persistent chat for coach-teacher mentorships, delivered via SSE/Redis with Postgres persistence.

  • 🎯 Database schema:
  • chat_messages table: id, mentorshipId, senderId, body, clientMessageId, createdAt, editedAt, deletedAt
  • chat_read_receipts table: (mentorshipId, userId) PK, lastReadMessageId, updatedAt
  • Drizzle migration 0009_add_chat.sql
  • 🎯 Backend API (/api/mentorships/:id/chat):
  • POST /chat/token — mint short-lived Agora RTM token for authenticated user
  • GET /chat/messages — cursor-based paginated history (newest-first, before cursor)
  • POST /chat/messages — send message; idempotent via clientMessageId
  • PATCH /chat/read-receipt — update last-read pointer (UPSERT)
  • GET /chat/unread — count unread messages from others since last receipt
  • ACL: coach, teacher, OrgAdmin, Manager — same-org only; PlatformAdmin read-only
  • Real-time relay: POST /chat/messages publishes to mentorship:{id}:chat Redis channel
  • 🎯 SSE extension:
  • Events router subscribes to mentorship:{id}:chat channel alongside session/reaction channels
  • New chat-message SSE event type relayed to all connected clients
  • Reconnect/resync: useMentorshipSSE invalidates chat.messages cache on app foreground
  • 📋 Shared types and validation:
  • ChatMessage, ChatHistoryResponse, SendMessageInput, AgoraChatTokenResponse, ReadReceiptInput, ChatReadReceipt, UnreadCountResponse types added to shared/src/types/index.ts
  • sendMessageSchema, chatHistoryQuerySchema, readReceiptSchema Zod schemas added to shared/src/validation/chat.ts
  • i18n keys added (chat.*) for English and Spanish
  • 📋 Frontend:
  • ChatScreen.tsx — message list with FlatList (inverted), optimistic send, error/retry, date separators, read-receipt on mount
  • Navigation: Chat screen registered in NativeNavigator (iOS/Android) and WebRouter (web); accessible via Chat button on MentorshipDetailScreen
  • useMentorshipSSE updated to handle chat-message events and pass onChatMessage callback
  • queryClient.tschat.messages, chat.unread, chat.token query key factories
  • api.tsgetChatToken, getChatMessages, sendChatMessage, updateReadReceipt, getChatUnreadCount methods
  • 🧪 Tests:
  • Backend integration: token auth, message CRUD permissions, idempotency, pagination, read receipts, unread count, cross-org access (backend/__tests__/chat.test.ts)
  • Frontend: loading/empty/error states, send flow, SSE subscription, input clearing (frontend/src/screens/__tests__/ChatScreen.test.tsx)

Phase 3: User Experience Improvements

Phase 3.1: Timezone Support ✅

Per-user and per-organization timezone configuration with immediate UI update.

  • 🎯 Database:
  • profiles.timezone text column (default 'UTC') — per-user preference
  • organizations.default_timezone text column (default 'UTC') — org-wide default
  • Migration 0010_add_timezone.sql
  • 🎯 Backend:
  • GET /api/users/me returns timezone field
  • PATCH /api/users/me accepts timezone field (Zod validation with IANA format check)
  • PATCH /api/organizations/:id accepts defaultTimezone field
  • 🎯 Shared:
  • User.timezone?: string and UpdateProfileInput.timezone?: string added to shared types
  • Organization.defaultTimezone?: string added to shared types
  • formatDate, formatDateTime, formatTime utilities updated to accept optional timezone parameter
  • i18n keys: settings.timezone, settings.timezoneSelect, settings.timezoneSearch, settings.timezoneUpdated, settings.timezoneUpdateError, orgAdmin.settings.defaultTimezone* (en + es)
  • 🎯 Frontend:
  • TimezonePicker component — web uses native <select>, native uses searchable modal
  • useTimezone hook — resolves user tz → org tz → 'UTC'; safe outside OrganizationProvider
  • SettingsScreen — timezone row with immediate-save mutation
  • OrgAdminSettingsScreen — dedicated screen with personal + org timezone sections
  • All date/time displays updated to use useTimezone(): sessions, chat timestamps, invite expiry, org details, user details

Phase 3.2: Real-Time Presence & Badges ✅

  • 🎯 Unread message badge on Messages tab — getChatUnreadCount aggregated across all active mentorships; badge updates via global SSE hook without requiring the Chat screen to be mounted
  • 🎯 Global SSE hook (useGlobalSSE) — single persistent SSE connection shared across the app; dispatches chat-message events to invalidate unread counts and refresh chat lists regardless of which screen is active

Phase 3.3: Pull-to-Refresh & Retry ✅

  • Pull-to-refresh (RefreshControl) added to all screens with data queries: PlatformAdminUsersScreen, OrgAdminUsersScreen, PlatformAdminOrganizationsScreen, PendingInvitationsScreen, MentorshipDetailScreen, CoachSessionsScreen, UserBookingsScreen, MentorshipsScreen, MessagesScreen (FlatList), and ChatScreen (FlatList with isFetching && !isFetchingNextPage guard)
  • Retry button added to all error states so users can recover from transient network failures without leaving the screen

Phase 3.4: Chat Enhancements 🔄

Improvements to the existing 1:1 mentorship chat. Schema groundwork (editedAt, deletedAt) is already in place.

  • ✅ File / image attachments — upload-first via /api/files (SHA-256 dedup); polymorphic file_attachments table links files to chat messages; presigned GET URLs returned on GET; inline image previews and doc links in ChatScreen
  • 💡 Message edit — PATCH /api/mentorships/:id/chat/messages/:msgId endpoint (sender-only, within 15 minutes of sending; 422 after window); (edited) label in ChatBubble; long-press action menu in ChatScreen; chat-message-updated SSE event
  • 💡 Message delete — DELETE /api/mentorships/:id/chat/messages/:msgId endpoint (sender-only or admin; soft-delete); tombstone "Message deleted" placeholder in ChatBubble; chat-message-deleted SSE event
  • 💡 Message reactions — chat_reactions table + backend add/remove endpoints + SSE chat-reaction-added / chat-reaction-removed; reaction pills rendered below chat bubbles with toggle support
  • ✅ Message threading / replies — replyToMessageId FK on chat_messages (migration 0013); enriched replyTo preview returned by API; long-press → Reply action in ChatScreen with quoted bubble and cancel bar
  • 💡 Message search — full-text search on GET /api/mentorships/:id/chat/messages?q= using Postgres to_tsvector('simple', body) @@ plainto_tsquery(...); search toggle and query input added to ChatScreen header flow
  • 💡 User presence (online/offline) — SSE heartbeat updates Redis user:{id}:lastSeen; new GET /api/users/:id/presence endpoint with org-safe ACL; online/offline status and dot rendered in chat header peer avatar

Phase 4: Offline-First Reliability 📋

Phase 4.1: Cache, Queue, and Sync 🎯

  • 🎯 📋 Offline-first reliability and sync queue:
  • Device cache for core entities (sessions, mentorships, chat history, profile, organization context) with stale-while-revalidate behavior when reconnecting — via queryPersister.ts + PersistQueryClientProvider
  • Users can always view mentorship data, session data, chat history, and profile data from local cache while offline — TanStack Query cache persisted to AsyncStorage
  • Mutation outbox queue on native devices (iOS/Android) for offline actions (chat sends) with automatic retry on connectivity restore — outboxStore.ts + useOutbox.ts
  • Idempotent sync contract to avoid duplicates — stable clientMessageId used for outbox retries, backend already supports idempotency
  • Queue observability in native UI (pending/sent/failed states, manual retry for failed items) — OutboxStatusBar component in ChatScreen
  • Explicit online-only actions: session creation/rescheduling/cancellation and joining video calls require active connection and show clear offline error messaging — useOnlineAction hook applied in MentorshipDetailScreen, ScheduleSessionModal, VideoCallScreen
  • Connectivity guardrail: when offline, block online-only actions and keep queued chat messages persisted until successful ACK from backend on native — outbox auto-drains on reconnect
  • 🐛 Fix OfflineBanner false-positive on AndroidOfflineBanner is currently hidden because NetInfo reports isConnected: false on Android preview/emulator builds even with working connectivity. Tried: isInternetReachable removal, debounce (4s), HTTP probe (/api/health), grace-period + probe combo. Root cause: NetInfo fires isConnected: false at startup and the network stack is not ready when the probe runs. Needs a more reliable connectivity signal (e.g. NetInfo.fetch() polled after a longer delay, or a native module approach).

Phase 5: Design System Rollout ✅

Gradual, incremental implementation of DESIGN.md tokens and missing components across the codebase. Each sub-phase is independent and can be merged separately.

Phase 5.1: Token Infrastructure 🎯

  • 🎯 Create frontend/src/lib/tokens.ts — typed TS constants for every design token: color map, spacing scale, border-radius scale, shadow definitions, and motion tokens (duration + easing); single source of truth for any code that cannot use a Tailwind class directly (e.g. StyleSheet, Animated values)
  • 🎯 Extend frontend/tailwind.config.js theme — add brand color palette (blue/*, gray/*, bg-app, status colors), spacing aliases, border-radius keys, and shadow utilities matching DESIGN.md exactly; no raw hex values allowed outside this config file
  • 📋 Document token usage convention in AGENTS.md / DESIGN.md — prefer Tailwind class over tokens.ts constant; use constant only when a class is impossible

Phase 5.2: Existing Component Audit ✅

  • 🎯 Audit Button, Input Field, Card, Badge/Tag, Avatar against DESIGN.md tokens — replace all hardcoded hex colors and pixel values with Tailwind token classes or tokens.ts constants (Toast, ModalActionButtons, ModalContainer, StatCard, ScreenHeader, AppHeader, FormInput, RowActionsMenu)
  • 📋 Enforce typography scale across key components — heading and body text classes migrated to brand-gray-* token classes; inline fontSize/fontWeight in Toast replaced with typography.body.md token
  • 📋 Replace all inline shadowColor / elevation values with the canonical shadow/sm, shadow/md, shadow/lg tokens (Toast, RowActionsMenu, StatCard, ModalContainer)

Phase 5.3: Missing Components ✅

  • 🎯 Nav Tab BarTabBadge component extracted; tab bar active/inactive colors migrated to colors.blue[500] / colors.gray[500] tokens in NativeNavigator
  • 📋 Progress BarProgressBar component (size: sm/md/lg × state: default/success/warning/error); animated fill via Animated.Value with duration.slow easing
  • 💡 Toggle / SwitchToggle component with 4 states (enabled-on/off, disabled-on/off); animated thumb + track with duration.fast easing
  • 📋 Notification / ToastToast.tsx upgraded: supports optional title, message, variant, action button, and custom duration; fully backward-compatible with existing showToast('message') calls
  • 💡 Icon systemlucide-react-native documented as the canonical icon library in DESIGN.md; standard sizes and token-based color usage defined; no ad-hoc icon imports from other libraries

Phase 5.4: Motion & Animation ✅

  • 💡 Apply Fade In/Out pattern (200ms ease-out) to all Modal overlays — ModalContainer now animates backdrop (opacity 0→1 in 200ms) and card (translateY 320→0 in 300ms) independently; exit plays before unmount
  • 💡 Apply Skeleton Shimmer loading state — SkeletonPlaceholder, SkeletonList, SkeletonListRow, SkeletonCard components added; replace ActivityIndicator on list screens with <SkeletonList />
  • 💡 Apply Slide Up pattern (300ms ease-out) to bottom sheets and form drawers (deferred — no bottom-sheet component yet)
  • 💡 Apply Scale + Fade (spring easing, 500ms) to any achievement / celebration UI introduced in future phases (deferred — no achievement UI yet)