Synced from the repo — do not edit here
Canonical source: docs/claude/app-redesign-wiring-recipes.md. This page is generated by docs/scripts/sync-handbook.mjs. Edit the source file in the repo; changes appear here on the next build.
App Redesign — Real-Data Wiring Recipes (verified against code 2026-07-10)
The "wire real data" implementation spec for the redesign screens. Every recipe below is verified against actual code (file:line). Companion to app-redesign-data-contracts.md (backend contracts) and app-redesign-scores.md (Biological Age formula).
The one mechanism
context.service<T>()resolves any registered service from theServiceCrate(basiscoreui/lib/src/services/service.dart:121). Pass a nullable type (context.service<ServiceClinic?>()) for feature-gated services →nullinstead of throw.- Registered at root via
CrateProviderinbasishybrid/lib/view/app_client.dart:1018. Service list authoritative inClientAppContainer.create(app_client.dart:41-171). - Data comes back as RxDart streams → feed
StreamBuilder. For the logged-in user useWidgetUserStream(basishybrid/lib/view/widgets/widget_user_stream.dart). FetchableResource<T>(health resources): call.fetch(force: true)to kick, then either listen.stream(Stream<(void, T)>→ take.$2) or read sync getters.dataOptional(T?) /.data(fetchable_resource.dart:489-490).
CLINIC RESOLUTION (gate for all clinic-scoped data)
ServiceClinic.clinic(basishybrid/lib/services/service_clinic.dart:167) is null after custom-token sign-in — nothing auto-resolves it (_updateAuthStatedoes not read membership).- Fix:
await context.service<ServiceClinic>().updateClinic('<clinicsv2-doc-id>')(service_clinic.dart:238) — sets + persists + emits, driving everyServiceClinicStorageCurrent.watch*to switchMap onto the real clinic. Same call the dev tool uses (route_developer.dart:188). - Clinic id = the
clinicsv2doc id. George has SEVERAL clinics; must pass a specific one. Multi-clinic picker normally atroute_intro.dart:683.
Per-screen recipes
Identity (header, profile)
WidgetUserStream(equals: (a,b)=>..., builder: (ctx, snap){ final u = snap.data; })→BasisUserV1(basiscore/lib/src/models/user/user.dart:48):name(String?, no separate firstName — split it),email,profileImage,dateOfBirth(DateTime?,.ageInYears(now):462),gender(BasisGender, may arrive as int →BasisGender.fromInt),height(double? cm). Weight is NOT here — it's a health metric (InsightType/BasisHealthType.weight, kg).- Clinic-side names/sex/membership:
context.service<ServiceClinicStorageCurrent>().watchCurrentClinicUser()→ClinicUserDetails(clinic.dart:1789):firstName,lastName,gender,dateOfBirth(String date-only),membershipType,status.
Wearables today (sleep, HRV, RHR) + biomarkers + labs — ALL via one pipeline
final res = context.service<ServiceDayMemory>().dayResource(today, debugName: 'redesign-health')(service_daymemory.dart:527;todaymust be normalized to day boundary —day.isDayrequired).res.insights→FetchableResource<Map<InsightType, InsightToday>>(wearables/vitals)res.labs→FetchableResource<Map<String, InsightToday>>(keyed by lowercasedanalyteKey)InsightToday(basiscore/lib/src/models/user/insight_type.dart:765):insightType,numericalValue(double? raw),value(String? formatted),inRange(EvaluatedBenchmark= inRange/optimal/outOfRange),time(DateTime? json key "day"),deviceInfo,progress. Lab fields:labAnalyteKey,labDisplayName,labMinRange,labMaxRange,labOptimalLow,labOptimalHigh,labUnit,labCategory.- Key
InsightTypes:sleepDuration(numericalValue=asleep seconds),restingHeartRate('rhr'),hrvSDNN/hrvRMSSD,steps,spo2,calories,glucoseAvg... (map inwidget_trends_today.dart:_insightFromMetricCode). - Needs-attention =
inRange == EvaluatedBenchmark.outOfRange. By-system = group labs bylabCategory. - Reference consumer:
WidgetProgress<K>/WidgetProgressState(widget_trends_today.dart:837+) — callswidget.fetch(day).fetch(force:true)in initState, renders.valuesof the map. Health screen:route_my_health.dart:50. - ⚠ NO sleep score exists (only duration + debt). ⚠ NO biological age exists anywhere — both net-new.
Active protocol + tasks (Plan)
- No whole "Protocol" object client-side. Protocol = individual
BasisEventV1events taggedprotocolId+habitId. context.service<ServiceDayMemory>().scheduledEvents→Stream<List<BasisEventV1>>(service_daymemory.dart:319), ordayResource(day).BasisEventV1(basiscore/lib/src/models/event/event.dart:69):name,type(BasisEventType;type.isHabit= protocol task),completionStatus(BasisEventCompletionStatus,.isCompleted),recurrenceRule,protocolId/habitId/clinicId/clinicianId,coachNotes,workoutPreset/supplementPreset/medicationPreset/mealPreset,start/end.- Completion ratio:
ServiceRecurringEvents.determineCompletionRatio(basiscore/lib/src/services/service_recurring_events.dart:357). Reader:widget_home.dart:1091→WidgetEventList.filter.
Appointments / up-next (Today, Booking)
context.service<ServiceClinicStorageCurrent>().watchAppointmentsForCurrentUser({start,end})→Stream<List<BasisEventV1>>(service_clinic_storage_current.dart:387, auto-scoped to clinic+uid).type == BasisEventType.scheduled.- Fields:
name(title),start/end,status(BasisEventStatus? e.g. canceled),location(SummaryObjectPositionV1?.name/.text),attendees. Coach name from attendees:attendees.where((a)=>a.isOrganiser && !a.isCurrentUser).firstOrNull?.name. - Reader:
route_appointments.dart:43; rowlist_item_appointment_compact.dart.
Care-team chat
context.service<ServiceChat>()(basiscoreui/lib/src/services/service_chat.dart:279). Conversation viaChatMessageSubscription.chat→Stream<BasisChatV2?>. Doc chosen byserviceFirebase.chatDoc(null, uid). Builtroute_chat.dart:2141, consumed:2190.BasisChatV2(service_chat_models.dart:1132):List<BasisChatMessageV2> messages.BasisChatMessageV2(:951):content(String?),author(BasisChatAuthorV2:id,name,role(ChatRole: user/coach/expert/backend(AI)/system),image,title),time,media,readReceipts.
Membership / billing (Profile)
context.service<ServiceClinicStorageCurrent>().watchCurrentClinicUser()→ClinicUserDetails.membershipType(holds membershipId) +.status(active/inactive).- Catalog:
.watchClinicDetails()→ClinicDetails.memberships(List<MembershipType>clinic.dart:3940); resolve name viamemberships.firstWhereOrNull((m)=>m.membershipId==membershipType)?.name. Price:MembershipType.stripePrice['unit_amount'](cents) /['currency']/['recurring']['interval']. - Reader:
widget_profile.dart:76-95. Subscribe:ServiceClinicStripe.subscribeToMembership(service_clinic_stripe.dart:296).
Cross-cutting cautions
- Every clinic-scoped read funnels through
ServiceClinicStorageCurrent(auto-scopes toServiceClinic.clinic+ uid) → needs clinic resolution above. BasisEventV1is the single source for BOTH plan tasks and appointments — discriminate bytype(isHabitvsscheduled).- Firestore types not guaranteed at runtime — guard
name,gender(int),stripePrice['unit_amount'], ranges with fallbacks +String(...)coercion.