Skip to content

feat(mobile): UI rework P1-P6 — 5 pages Expo SDK 54 (mocks only)#2

Open
simlirette wants to merge 74 commits into
mainfrom
chore/downgrade-sdk54
Open

feat(mobile): UI rework P1-P6 — 5 pages Expo SDK 54 (mocks only)#2
simlirette wants to merge 74 commits into
mainfrom
chore/downgrade-sdk54

Conversation

@simlirette

Copy link
Copy Markdown
Owner

Résumé technique

Rework complet de l'app mobile Expo (SDK 54) depuis un scaffold Vague 1.
5 pages livrées et testées manuellement sur iPhone via Expo Go SDK 54.
Toutes les pages utilisent des mocks locaux — pas de wiring backend dans cette PR.
SDK downgrade 55→54 nécessaire pour compatibilité binaire Expo Go (reanimated worklets
absents du binaire SDK 54, remplacés par Animated.Value + PanResponder).


Pages livrées ✅

Page Route Notes
Auth /(auth)/login, /(auth)/signup, /(auth)/forgot-password FloatingLabelInput, Button Wave 1
Onboarding /onboarding 5 étapes, slide Animated.Value
Home Dashboard /(tabs)/ P6 rewrite, ReadinessRing 160px, MetricsStrip
Training History /(tabs)/training Calendrier + liste + drawer détail
Coach Chat /(tabs)/chat Conversation + HITL bottom sheet (single/multi/rank)

Pages placeholder (non bloquantes) 🚧

  • Today's Session (/session/live) — route non créée
  • Metric Detail (/metric/[id])
  • Nutrition Log (/nutrition)
  • Profile / Settings (/(tabs)/profile) — scaffold vide
  • Connectors (/connectors)

Ce qui N'est PAS dans cette PR

  • Wiring backend (mocks only — apps/mobile/src/mocks/)
  • Authentification réelle (handleLogin/handleSignup = // TODO: real auth call)
  • Apple Sign In (installé, non connecté)
  • Connecteurs Strava / Hevy / Terra
  • Tests unitaires composants Wave 1

Checklist QA manuelle (Expo Go iPhone)

Avant merge, valider sur device physique iOS (iPhone + Expo Go SDK 54):

Navigation

  • Tab bar iOS liquid glass visible (NativeTabs, amber tintColor)
  • Navigation Auth → Onboarding → Home fonctionnelle
  • Back gesture iOS native opérationnel sur toutes les pages

Home Dashboard

  • Ring Readiness affiche valeur 78 (état normal)
  • Tap avatar "SR" (dev toggle) → cycle 3 états: optimal / normal / récupération
  • Ring couleur change (vert / amber / rouge) selon état
  • MetricsStrip 3 colonnes visible, valeurs tabular-nums
  • CTA "Voir la session" visible en footer

Coach Chat

  • Messages s'affichent correctement
  • Input bar visible au-dessus du clavier quand ouvert, au-dessus des NativeTabs quand fermé
  • HITL sheet s'ouvre par-dessus (BlurView backdrop)
  • Type single: radio selection fonctionnel
  • Type multi: checkbox multi-selection fonctionnel
  • Type rank: drag PanResponder (grip dots ⋮⋮), swap avec haptics Light/Medium

Training History

  • Calendrier scroll mois (← →)
  • Dots discipline colorés sur jours d'entraînement
  • Tap jour → drawer slide-up avec détail session

Auth

  • Champs non cachés par le clavier
  • Erreurs inline sous les inputs (pas de toast)

Screenshots à attacher manuellement

Attacher avant merge: Home (3 états readiness), Chat + HITL sheet ouverte, Training History


Stack technique

  • Expo SDK 54 / expo-router v3 / React Native 0.73.x
  • NativeTabs: expo-router/unstable-native-tabs (iOS liquid glass, SDK 54 OK)
  • Animations: Animated.Value + PanResponder (reanimated absent SDK 54 Expo Go)
  • Fonts: Space Grotesk via expo-font
  • Bottom sheets: @gorhom/bottom-sheet + expo-blur
  • Tokens: @resilio/design-tokens (0 hex inline)
  • Mocks: apps/mobile/src/mocks/

Pre-existing issues (out of scope)

  • 1 erreur TypeScript dans packages/ui-web/src/theme/ThemeProvider.tsx:25 (@ts-expect-error devenue inutile, confirmé présent sur main)
  • 37 warnings ESLint pré-existants (energy/cycle + allostatic-gauge)

Simon and others added 30 commits April 18, 2026 18:05
- Remove blue sport-label row (was rendered separately with accent color)
- Move sport icon into header row beside 'SÉANCE DU JOUR' label
- Zone badge now extracts short format: 'Zone 1 (60–74%)' → 'Z1'
- Duration + zone merged into single meta line with '·' separator
- Stub titles francisés: 'Easy Run Z1' → 'Course facile',
  'Muscu — Upper Pull' → 'Musculation haut du corps'
- Tests updated to match new structure

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
State mapping: green→ok, yellow→warn, red→zoneRed (from design tokens).
Previously colors were hardcoded per metric type regardless of value.
Label 'Récup.' renamed to 'Strain' (technical sport science term, per
design decision — consistent with clinical tone of agent outputs).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace classic expo-router Tabs with NativeTabs from expo-router/unstable-native-tabs.
iOS: UITabBarController with systemChromeMaterial blur (liquid glass).
Android: Material 3 bottom navigation.
Web: Radix UI fallback built into expo-router, no Platform.OS branch needed.
SF Symbols: house/house.fill, heart/heart.fill, bolt/bolt.fill, person/person.fill.
tintColor: colors.accent (#3B74C9).

UI-RULES-MOBILE.md: Rule 9 SF Symbols exception, Rule 14 tech terms in EN,
MetricRow state-based color mapping, metric token table updated.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add babel-preset-expo and babel plugins to public-hoist-pattern in .npmrc
- Explicit router.appRoot in babel.config.js to force transform injection
- Resolves expo-router crash on Expo Go (SDK 54)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Remove missing asset refs from app.json (icon/splash/adaptive-icon pending design phase)
- Pin lightningcss to 1.28.2 via pnpm.overrides to fix react-native-css crash
- Unblocks expo start bundle completion on iOS

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add react@19.1.0 and react-dom@19.1.0 to pnpm.overrides
- Resolves "Cannot read property 'useContext' of null" from duplicate
  React instances between apps/mobile (19.1.0) and packages/ui-mobile (19.2.4)
- Unblocks ThemeProvider runtime on iOS Expo Go

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Run expo install to match react-native-svg and other native
  packages with versions embedded in Expo Go SDK 54
- Add extraNodeModules to metro.config.js to force singleton native
  packages (react, react-native, react-native-svg, reanimated, etc.)
  to resolve from apps/mobile/node_modules only
- Resolves "RNSVGCircle must be a function (received undefined)"
  crash on Home screen caused by pnpm creating two Metro module IDs
  for the same native package

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Set disableHierarchicalLookup: true in metro.config.js — prevents
  Metro from walking into packages/ui-mobile/node_modules when resolving
  native packages; forces all resolution through apps/mobile/node_modules
  → single file path → single module ID → single native binding
- Add react-native-svg@15.12.1 to pnpm.overrides to pin version
- Remove incorrect extraNodeModules approach (checked after nodeModulesPaths,
  so it never intercepted pnpm virtual store symlinks)
- Restore newArchEnabled: true (Expo Go always runs New Arch regardless)
- Root cause: pnpm creates separate virtual store entries per peer-dep context
  (peer#bb03 for apps/mobile, peer#0d2d for ui-mobile); Metro bundled both
  → RNSVGCircle native binding registered once → second JS instance undefined

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
disableHierarchicalLookup:true broke resolution of transitive deps
(whatwg-fetch) that only exist in pnpm virtual store sub-paths.

Switch to resolveRequest which is surgical — intercepts only the
singleton native packages, leaves all other resolution untouched.
Fakes originModulePath to apps/mobile/package.json so Metro's
hierarchical lookup starts at apps/mobile/node_modules.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace Clinical Blue (#3B74C9) with Amber/Terracotta (#B8552E light,
#D97A52 dark) across all accent/primary/ring token fields. Update RGB
channels and rgba() derived values. shadcn.dark gets #D97A52,
shadcn.light and top-level get #B8552E.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
PRIMARY constant in logo.tsx: #5b5fef → #B8552E. BRAND.md: update
wordmark, design aesthetic section, logo spec, and absolute rule to
reflect amber/terracotta canonical accent. Also corrects dark bg
reference from #08080e to #131210 (warm near-black).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
tailwind.config.js: accent/primary #3B74C9 → #B8552E, primary-dim rgba
updated to amber RGB channels. app.json: splash backgroundColor
#5b5fef → #131210 (warm near-black) for imperceptible transition into
dark UI — no brand flash on launch.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Button.tsx JSDoc: Clinical Blue → Amber. README usage example: #5b5fef
→ #B8552E. Circle.test.tsx + Icon.test.tsx: arbitrary test color
#5b5fef → #B8552E (tests remain behaviorally identical — color prop is
passed through, not asserted against a specific value).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
frontend/UI-RULES.md is the single source of truth for design rules
(both web and mobile). The mobile-specific file was an outdated fork
containing obsolete color references (#5b5fef, #3B74C9).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Rule 3 forbidden-hex comment: replace #5b5fef (purged) with #3B74C9
(also obsolete) as the illustrative bad example. No logic change.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Lime (#C8FF4D) supprimé définitivement. Amber (#B8552E light / #D97A52 dark)
est l'accent unique pour tous les CTAs y compris "Démarrer" et "Set terminé".
- todays session/SPEC.md: remplacer lime par amber dans palette + CTA labels
- homedashboard/SPEC.md: tab bar V1 = 4 onglets (Métriques = V2)
- frontend/UI-RULES.md: retirer section "Accent action session — Lime électrique"

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…-auth deps

- @gorhom/bottom-sheet — Training History drawer + HITL sheet
- expo-blur — HITL sheet backdrop BlurView
- react-native-draggable-flatlist — HITL type 3 réordonnage
- expo-apple-authentication — Auth Apple Sign In

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ar Training

Fonts:
- _layout.tsx: Inter + SpaceMono → Space Grotesk 400/500/600/700
- typography.ts: fontSans + RN fontFamily constants, Space Grotesk

Tokens:
- colors.ts: add accentDark (#D97A52), physio {green/yellow/red} light+dark
- zoneRed: #ef4444 (cold) → #B64536 (terracotta warm, cohérent amber)
- zoneCritical: #dc2626 → #9C3020

Tab bar:
- 4 onglets: Accueil | Entraînement | Coach | Profil (Métriques = V2)
- Check-in hors tab bar: (tabs)/check-in.tsx → app/check-in.tsx (route non-tab)
- Ajout (tabs)/training.tsx placeholder
- Home: router.push('/check-in') au lieu de '/(tabs)/check-in'

Plan docs:
- docs/ui-rework-diagnostic.md
- docs/ui-rework-plan.md

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…Caps, wordmark

Replace Inter with Space Grotesk across all variants. New variants:
heroNumber (72px/500/tabular), heroPace (80px/700/tracking-3),
heroLarge (40px/700), pageTitle, stepTitle, sectionTitle, wordmark,
navBar, metric, bodyBold, smallCaps. Back-compat aliases preserved.
tabular prop added for forcing fontVariant on any variant.

Debug screen: app/_debug/text-showcase.tsx

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…error state

- Animated floating label: translateY -18 + scale 0.76 (150ms ease)
- Border: 1px neutral → 1.5px accent on focus, -0.5px margin compensation
- Password toggle via showToggle prop (text Voir/Masquer)
- Inline error in physio.red below input
- peerDep: react-native-reanimated >=3.0.0 added to ui-mobile
- Debug screen: app/_debug/inputs-showcase.tsx

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ents, SegmentedControl

Button:
- Variants: primary (amber), secondary (surfaceAlt), ghost (accent border), apple (black/white)
- Radius 12px, height 54px, disabled opacity 0.4, heavyHaptic prop
- Pressable (not TouchableOpacity), accessibilityRole button

HeroNumber:
- Sizes: xl (40px/700), xxl (72px/500), pace (80px/700)
- tabular-nums, optional unit label, accepts string|number

ProgressSegments:
- Thin 3px segments for onboarding (5 total)
- Instantaneous swap, no transition

SegmentedControl:
- 2-4 options, active pill on surfaceAlt container
- radius 10 container / 8 active

Text: style prop → StyleProp<TextStyle> (was TextStyle)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…trix

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Simon and others added 30 commits April 19, 2026 10:15
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Full chat UI matching design spec. No reanimated/gorhom deps (Expo Go SDK 54 safe).

- Chat conversation: HC bubbles (avatar, left), user bubbles (right), summary card
- Typing indicator: 3-dot animated fade via RN Animated.Value loop
- Quick reply chips: horizontal ScrollView
- Input bar: TextInput + send button (accent when text present)
- HITL bottom sheet: Animated.Value spring slide-up, BlurView (expo-blur) + dim backdrop
- Question type 1 — single: numbered rows, selected = dark pill + bold + arrow
- Question type 2 — multi: checkboxes, count in footer, "Autre chose" input
- Question type 3 — rank: ↑↓ buttons (no draggable-flatlist needed)
- Summary card: right-aligned, accent border, Q&A entries
- "Reprendre les questions" button if sheet dismissed without answering
- Auto-open sheet after 900ms; coach reply + typing indicator after submit
- Chat-specific color tokens (light/dark) matching SPEC.md palette
- useSafeAreaInsets for top/bottom padding

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Avoids hardcoded hex in components. Used by HomeSessionCard CTA footer.
- accentText: '#FAFAF7' (warm off-white, readable on amber CTA)
- accentTextDark: '#161412' (warm charcoal, readable on dark amber CTA)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Rich mock data matching design spec (dashboard.jsx DATA.*):
- normal: Readiness 78, Course 52min, Allure/FC/TSS targets
- ideal: Readiness 92, Vélo 2h10, Puissance/NP/TSS targets
- recovery: Readiness 45, Récupération active (no targets)

Includes typed HomeDashData, DashState, nextDashState() cycle helper.
Avatar tap in HomeScreen will cycle between states without rebuild.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Full redesign matching docs/design/homedashboard/ screenshots.

Layout:
- Header: greeting + date small-caps + avatar "SR" (tap = cycle 3 DEV states)
- ReadinessRingHome: 160px, semantic color (green ≥80 / amber 60-79 / red <60)
  + delta "+X vs hier" in ring color
- MetricsStrip: 3-col text card (Nutrition kcal/target + progress bar,
  Strain display value + semantic color, Sommeil duration + score)
- HomeSessionCard: discipline + brief + targets row (Allure/FC/TSS or
  Puissance/NP/TSS) + full-width CTA footer (accent or ghost for recovery)
- CognitiveLoadBar: 24 segments, semantic color, at bottom of scroll

Removed: MetricRow, SessionCard, CognitiveLoadDial, ReadinessStatusBadge
(flagged as deletion candidates in docs/p6-home-plan.md)

Tokens: colors.accentText / colors.accentTextDark (Commit 0) — 0 hex inline
SDK 54 safe: no reanimated, no @gorhom

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ostatique

Bug 3 polish: drop "CHARGE COGNITIVE" label + "7j" badge from CognitiveLoadBar
header — clutters the section without adding information. Change "Charge
allostatique" from textMuted to foreground color for better hierarchy.
Remove unused cogHeader + cogTitle styles.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Bug 1 polish: ring value font too dominant on device (72px covers ~48% of
ring diameter). Reduce to 52px (33% of outer ring), proportional letterSpacing
-2.5 and lineHeight 58. First iteration — sub-commit allowed in 52-60px range
after Expo Go test without new plan cycle.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…IN col

Bug 2 polish: explicit newline caused 3-line orphan on small screens.
Remove hard newline, let text wrap naturally, cap with numberOfLines={2}.
Fallback (widening STRAIN col flex) not needed — natural wrap with cap is clean.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…stats

Bug 4 polish: single-line row (weekLabel left + summary right) replaced with
2-line stacked layout. L1: "SEMAINE DU 13 AVRIL" (full month, foreground).
L2: "7 SÉANCES - 7H05 TOT. - 381 CHARGE" (dash separator, TOT. suffix,
uppercase, textMuted). SpaceGrotesk_600SemiBold L1, _400Regular L2.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Bug 6 polish: reverts regression that replaced NativeTabs with standard Tabs
using an incorrect "SDK 55 only" comment. NativeTabs is available on SDK 54
via expo-router/unstable-native-tabs (confirmed commit e2d1810, build dir present).

4 tabs: index/training/chat/profile. SF Symbols: house.fill, calendar.circle.fill,
message.fill, person.fill. tintColor: colors.accent (amber). blurEffect:
systemChromeMaterial. 0 hex inline. Label+Icon as Trigger children (not
sub-components — API corrected vs e2d1810 draft).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Bug 5 polish: HITL rank type now supports real drag-and-drop via PanResponder.
Row follows finger in real-time (translateY = gs.dy, native driver). Multi-slot
swap in one drag: Math.round(dy / ROW_HEIGHT). Haptics.Light on pickup,
Haptics.Medium on successful swap (zero haptic if no swap). Spring reset after
release (damping 20, stiffness 300). Shadow + zIndex=10 on dragging row.
GripDots (2×3 dots) right-aligned replaces ↑↓ arrows. Hint updated to
"GLISSE POUR RÉORDONNER". Expo Go SDK 54 safe: no reanimated.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add p6-polish-diagnostic.md (v2, 4 corrections: branch confirm, NativeTabs
reopened, Bug 4 verbatim, Bug 1 pixel measurement). Add p6-polish-plan.md
(6 atomic commits, real PanResponder drag spec for Bug 5). Update state.md:
P6 polish ✅ 6/6 commits, NativeTabs API notes, calendar.fill correction.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Correct 2 stale entries claiming NativeTabs is SDK 55 only (it's available
in SDK 54 expo-router v6). Append P6 polish [saved] entry: NativeTabs API,
calendar.fill invalidity, PanResponder drag pattern, 52px ring first iteration.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… clipping

Bug 1 polish #2: Space Grotesk Medium 22px has tall ascenders that clip without
an explicit lineHeight. Default RN line height (~24px) insufficient. 30px =
1.36x ratio gives full ascender clearance without extra whitespace.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…llision

Bug 2 polish #2: when isRecovery, long title + duration share one row causing
overlap. Conditional layout: recovery=column (title L1, duration L2 at 13px
textMuted), non-recovery=existing row layout unchanged.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…HT 49pt)

Bug 3 polish #2: NativeTabs is a native UITabBarController that does not provide
BottomTabBarHeightContext, so useBottomTabBarHeight() would throw. Manual
paddingBottom = safeArea.bottom + 49 + 8 applied inline on inputWrap, replacing
static paddingBottom:8. Chips and TextInput now fully visible above tab bar.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ooks)

Bug 4 polish #2: crash "Rendered more hooks than during previous render" caused
by useState + useRef inside if(q.type==='rank') conditional. When navigating
between questions of different types, hook count changed between renders.

Fix: draggingIndex (useState) and dragY (useRef) moved to top level of
QuestionBody, always initialised. ROW_HEIGHT constant promoted to module level
as RANK_ROW_HEIGHT. Conditionals now only read from pre-existing state.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
HITLSheet container paddingBottom was insetBottom + 16 (~50pt),
only covering the home indicator. Added TAB_BAR_HEIGHT (49pt) so
footer buttons clear the NativeTabs translucent bar.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
InputBar paddingBottom was static (bottom + 49 + 8 ≈ 91pt), causing
a gap above the keyboard when KAV pushed the layout up. Now dynamic:
- Keyboard closed: bottom + TAB_BAR_HEIGHT + 8 (clears NativeTabs)
- Keyboard open: bottom + 8 (glued to keyboard top)

Uses keyboardWillShow/Hide (iOS) and keyboardDidShow/Hide (Android)
via Keyboard.addListener for cross-platform support.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
keyboardVerticalOffset={insets.top + 56} over-compensated by ~103pt.
RN KAV formula: paddingBottom = KAV_bottom - keyboard_screenY + offset.
Since KAV extends to screen bottom and RN already measures its own frame,
offset must be 0. Previous value created a structural gap equal to the offset.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Keyboard height on iOS includes the home indicator area (~34pt).
Using bottom+8 when keyboard open kept a 34pt gap. Now uses 8pt only.
Keyboard closed stays bottom+TAB_BAR_HEIGHT+8 to clear NativeTabs.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant