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-identifiersvalidates Expo app IDs against native iOS (PRODUCT_BUNDLE_IDENTIFIER+ URL scheme) and Android (applicationId) config; enforced in localfrontend/frontend:iosscripts 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 toAGENTS.mdto 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/:userIdsetsmembershipStatus = 'disabled'instead of removing the row; tenant middleware blocks org-scoped access for disabled members; re-enable viaPATCH { 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 —
EditUserScreennow supports upload from camera/library, crop/adjust confirmation, and avatar persistence viaPATCH /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
Registerroute/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 dedicatedRegisterSuccessverification screen ("Verifica tu correo") after successful account creation, includesPOST /api/auth/resend-activationwith a cooldown timer, routes verification completion to/email-verificationwith in-app success/error feedback when opened from email links, and now hardens activation callbacks so emails always resolve to frontend routes (with mandatoryFRONTEND_URL/TRUSTED_ORIGINSdeployment 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-updatesinstalled;eas:update:staging/eas:update:productionscripts 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.txtper 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-Keyheader validated by Hono middleware before any route;CLIENT_API_KEYenv var on backend,EXPO_PUBLIC_CLIENT_KEYon 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-ssepolyfill 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.tslocale 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 (
OrganizationAdminall org roles,Managercoach/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-reactionevent) - 🎯 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/mentorshipsand/api/sessionsendpoints - 🎯 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
durationMinutesby 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;reminderSentAtcolumn 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 —
sendSessionReminderEmailin email service, HTML template, sent alongside push - Configurable reminder cadence (15 min, 30 min, 1 hour, 24 hours) —
reminderMinutesBeforecolumn onprofiles; cron job checks per-user trigger time with ±30s window - Missed-session follow-up reminder with reschedule action —
session-missed.job.tsruns every 5 minutes; marks sessionmissed, sends push + email to both participants; deep-linksession_missedevent navigates to mentorship detail - Reminder preferences in Settings:
- Per-channel toggles: email (
reminderEmailEnabled) and push (reminderPushEnabled) stored onprofiles - Per-user cadence selector (15 min / 30 min / 1 hour / 24 hours) in Settings screen
- Quiet hours / do-not-disturb window (
quietHoursStart/quietHoursEndcolumns, 0-23 hour values in user's timezone)
- Per-channel toggles: email (
- 🎯 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:
-
filestable:id,sha256Hash,mimeType,sizeBytes,storageKey,uploadedBy,createdAt -
fileAttachmentsjoin table:fileId,objectType(enum:session,note,mentorship, …),objectId - Deduplication by
sha256Hash— if a matching hash already exists in storage, reuse the existingstorageKeyand insert only a newfileAttachmentsrow; do not upload a second copy to MinIO - Orphan cleanup: when the last
fileAttachmentsrow for afileIdis deleted, hard-delete thefilesrow 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) -
sha256Hashis 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_messagestable:id,mentorshipId,senderId,body,clientMessageId,createdAt,editedAt,deletedAt -
chat_read_receiptstable:(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,beforecursor) -
POST /chat/messages— send message; idempotent viaclientMessageId -
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/messagespublishes tomentorship:{id}:chatRedis channel - 🎯 SSE extension:
- Events router subscribes to
mentorship:{id}:chatchannel alongside session/reaction channels - New
chat-messageSSE event type relayed to all connected clients - Reconnect/resync:
useMentorshipSSEinvalidateschat.messagescache on app foreground - 📋 Shared types and validation:
-
ChatMessage,ChatHistoryResponse,SendMessageInput,AgoraChatTokenResponse,ReadReceiptInput,ChatReadReceipt,UnreadCountResponsetypes added toshared/src/types/index.ts -
sendMessageSchema,chatHistoryQuerySchema,readReceiptSchemaZod schemas added toshared/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 -
useMentorshipSSEupdated to handlechat-messageevents and passonChatMessagecallback -
queryClient.ts—chat.messages,chat.unread,chat.tokenquery key factories -
api.ts—getChatToken,getChatMessages,sendChatMessage,updateReadReceipt,getChatUnreadCountmethods - 🧪 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.timezonetext column (default'UTC') — per-user preference -
organizations.default_timezonetext column (default'UTC') — org-wide default - Migration
0010_add_timezone.sql - 🎯 Backend:
-
GET /api/users/mereturnstimezonefield -
PATCH /api/users/meacceptstimezonefield (Zod validation with IANA format check) -
PATCH /api/organizations/:idacceptsdefaultTimezonefield - 🎯 Shared:
-
User.timezone?: stringandUpdateProfileInput.timezone?: stringadded to shared types -
Organization.defaultTimezone?: stringadded to shared types -
formatDate,formatDateTime,formatTimeutilities updated to accept optional timezone parameter - i18n keys:
settings.timezone,settings.timezoneSelect,settings.timezoneSearch,settings.timezoneUpdated,settings.timezoneUpdateError,orgAdmin.settings.defaultTimezone*(en + es) - 🎯 Frontend:
-
TimezonePickercomponent — web uses native<select>, native uses searchable modal -
useTimezonehook — resolves user tz → org tz →'UTC'; safe outsideOrganizationProvider -
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 —
getChatUnreadCountaggregated 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; dispatcheschat-messageevents 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), andChatScreen(FlatList withisFetching && !isFetchingNextPageguard) - 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); polymorphicfile_attachmentstable links files to chat messages; presigned GET URLs returned on GET; inline image previews and doc links inChatScreen - 💡 Message edit —
PATCH /api/mentorships/:id/chat/messages/:msgIdendpoint (sender-only, within 15 minutes of sending; 422 after window);(edited)label inChatBubble; long-press action menu inChatScreen;chat-message-updatedSSE event - 💡 Message delete —
DELETE /api/mentorships/:id/chat/messages/:msgIdendpoint (sender-only or admin; soft-delete); tombstone "Message deleted" placeholder inChatBubble;chat-message-deletedSSE event - 💡 Message reactions —
chat_reactionstable + backend add/remove endpoints + SSEchat-reaction-added/chat-reaction-removed; reaction pills rendered below chat bubbles with toggle support - ✅ Message threading / replies —
replyToMessageIdFK onchat_messages(migration 0013); enrichedreplyTopreview returned by API; long-press → Reply action inChatScreenwith quoted bubble and cancel bar - 💡 Message search — full-text search on
GET /api/mentorships/:id/chat/messages?q=using Postgresto_tsvector('simple', body) @@ plainto_tsquery(...); search toggle and query input added toChatScreenheader flow - 💡 User presence (online/offline) — SSE heartbeat updates Redis
user:{id}:lastSeen; newGET /api/users/:id/presenceendpoint 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
clientMessageIdused for outbox retries, backend already supports idempotency - Queue observability in native UI (pending/sent/failed states, manual retry for failed items) —
OutboxStatusBarcomponent in ChatScreen - Explicit online-only actions: session creation/rescheduling/cancellation and joining video calls require active connection and show clear offline error messaging —
useOnlineActionhook 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 Android —
OfflineBanneris currently hidden because NetInfo reportsisConnected: falseon Android preview/emulator builds even with working connectivity. Tried:isInternetReachableremoval, debounce (4s), HTTP probe (/api/health), grace-period + probe combo. Root cause: NetInfo firesisConnected: falseat 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.jstheme — addbrandcolor 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 overtokens.tsconstant; 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.tsconstants (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; inlinefontSize/fontWeightinToastreplaced withtypography.body.mdtoken - 📋 Replace all inline
shadowColor/elevationvalues with the canonicalshadow/sm,shadow/md,shadow/lgtokens (Toast,RowActionsMenu,StatCard,ModalContainer)
Phase 5.3: Missing Components ✅¶
- 🎯 Nav Tab Bar —
TabBadgecomponent extracted; tab bar active/inactive colors migrated tocolors.blue[500]/colors.gray[500]tokens inNativeNavigator - 📋 Progress Bar —
ProgressBarcomponent (size: sm/md/lg × state: default/success/warning/error); animated fill viaAnimated.Valuewithduration.sloweasing - 💡 Toggle / Switch —
Togglecomponent with 4 states (enabled-on/off, disabled-on/off); animated thumb + track withduration.fasteasing - 📋 Notification / Toast —
Toast.tsxupgraded: supports optionaltitle,message,variant,actionbutton, and customduration; fully backward-compatible with existingshowToast('message')calls - 💡 Icon system —
lucide-react-nativedocumented as the canonical icon library inDESIGN.md; standard sizes and token-basedcolorusage 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 —
ModalContainernow 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,SkeletonCardcomponents added; replaceActivityIndicatoron 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)