# Mobile Builder Agent Memory ## Design System - Shimmer Primitives - Shimmer widgets are in `packages/design_system/lib/src/widgets/shimmer/` - Available: `UiShimmer`, `UiShimmerBox`, `UiShimmerCircle`, `UiShimmerLine`, `UiShimmerListItem`, `UiShimmerStatsCard`, `UiShimmerSectionHeader`, `UiShimmerList` - `UiShimmerList.itemBuilder` takes `(int index)` -- single parameter, not `(BuildContext, int)` - `UiShimmerBox.borderRadius` accepts `BorderRadius?` (nullable), uses `UiConstants.radiusMd` as default - All shimmer shapes render as solid white containers; the parent `UiShimmer` applies the animated gradient - Exported via `design_system.dart` barrel ## Staff App Feature Locations - Shifts: `packages/features/staff/shifts/` -- has ShiftsPage (tabbed: MyShifts/Find/History) + ShiftDetailsPage - Home: `packages/features/staff/home/` -- WorkerHomePage with sections (TodaysShifts, TomorrowsShifts, Recommended, Benefits, QuickActions) - Payments: `packages/features/staff/payments/` -- PaymentsPage with gradient header + stats + payment history - Home cubit: `HomeStatus` enum (initial, loading, loaded, error) - Shifts bloc: `ShiftsStatus` enum + sub-loading flags (`availableLoading`, `historyLoading`) - Payments bloc: uses sealed state classes (`PaymentsLoading`, `PaymentsLoaded`, `PaymentsError`) ## UiConstants Spacing Tokens - Use `UiConstants.space1` through `UiConstants.space24` for spacing - Radius: `UiConstants.radiusSm`, `radiusMd`, `radiusLg`, `radiusFull`, `radiusBase`, `radiusMdValue` (double) - `UiConstants.radiusFull` is a `BorderRadius`, `UiConstants.radiusMdValue` is a `double` ## Barrel Files (Staff Features) - Shifts: `lib/staff_shifts.dart` exports modules only - Payments: `lib/staff_payements.dart` (note: typo in filename) exports module only - Home: `lib/staff_home.dart` exports module only - These barrel files only export modules, not individual widgets -- skeleton widgets don't need to be added ## Client App Feature Locations - Coverage: `packages/features/client/client_coverage/` - Home: `packages/features/client/home/` (no loading spinner -- renders default data during load) - Billing: `packages/features/client/billing/` (billing_page, pending_invoices_page, invoice_ready_page) - Reports: `packages/features/client/reports/` (reports_page with metrics_grid, plus 6 sub-report pages) - Reports barrel: `widgets/reports_page/index.dart` - Hubs: `packages/features/client/hubs/` (client_hubs_page + hub_details_page + edit_hub_page) ## Staff Profile Sections (shimmer done) - Compliance: certificates, documents, tax_forms -- all have shimmer skeletons - Finances: staff_bank_account, time_card -- all have shimmer skeletons - Onboarding: attire, profile_info (personal_info_page only) -- have shimmer skeletons - Support: faqs, privacy_security (including legal sub-pages) -- have shimmer skeletons - Pages that intentionally keep CircularProgressIndicator (action/submit spinners): - form_i9_page, form_w4_page (submit button spinners) - experience_page (save button spinner) - preferred_locations_page (save button + overlay spinner) - certificate_upload_page, document_upload_page, attire_capture_page (form/upload pages, no initial load) - language_selection_page (no loading state, static list) - LegalDocumentSkeleton is shared between PrivacyPolicyPage and TermsOfServicePage ## Key Patterns Observed - BenefitsOverviewPage also has CircularProgressIndicator (not shimmer-ified yet) - ShiftDetailsPage has a dialog-level spinner in the "applying" dialog -- this is intentional, not a page loading state - Hub details/edit pages use CircularProgressIndicator as action overlays (save/delete) -- keep as-is, not initial load - Client home page uses shimmer skeleton during loading (ClientHomePageSkeleton + ClientHomeHeaderSkeleton) ## V2 API Migration Patterns - `BaseApiService` is registered in `CoreModule` as a lazy singleton (injected as `i.get()`) - `BaseApiService` type lives in `krow_domain`; `ApiService` impl lives in `krow_core` - V2 endpoints: `V2ApiEndpoints.staffDashboard` etc. from `krow_core/core.dart` - V2 domain shift entities: `TodayShift`, `AssignedShift`, `OpenShift` (separate from core `Shift`) - V2 `Benefit`: uses `targetHours`/`trackedHours`/`remainingHours` (int) -- old used `entitlementHours`/`usedHours` (double) - Staff dashboard endpoint returns all home data in one call (todaysShifts, tomorrowsShifts, recommendedShifts, benefits, staffName) - Navigator has `toShiftDetailsById(String shiftId)` for cases where only the ID is available - `StaffDashboard` entity updated to use typed lists: `List`, `List`, `List` - Staff home feature migrated (Phase 2): removed krow_data_connect, firebase_data_connect, staff_shifts deps - [V2 Profile Migration](project_v2_profile_migration.md) -- entity mappings and DI patterns for all profile sub-packages - Staff clock-in migrated (Phase 3): repo impl → V2 API, removed Data Connect deps - V2 `Shift` entity: `startsAt`/`endsAt` (DateTime), `locationName` (String?), no `startTime`/`endTime`/`clientName`/`hourlyRate`/`location` - V2 `AttendanceStatus`: `isClockedIn` getter (not `isCheckedIn`), `clockInAt` (not `checkInTime`), no `checkOutTime`/`activeApplicationId` - `AttendanceStatus` constructor requires `attendanceStatus: AttendanceStatusType.notClockedIn` for default - Clock-out uses `shiftId` (not `applicationId`) -- V2 API resolves assignment from shiftId - `listTodayShifts` endpoint returns `{ items: [...] }` with TodayShift-like shape (no lat/lng, hourlyRate, clientName) - `getCurrentAttendanceStatus` returns flat object `{ activeShiftId, attendanceStatus, clockInAt }` - Clock-in/out POST endpoints return `{ attendanceEventId, assignmentId, sessionId, status, validationStatus }` -- repo re-fetches status after - Geofence: lat/lng not available from listTodayShifts or shiftDetail endpoints (lives on clock_points table, not returned by BE) - `BaseApiService` not exported from `krow_core/core.dart` -- must import from `krow_domain/krow_domain.dart` ## Staff Shifts Feature Migration (Phase 3 -- completed) - Migrated from `krow_data_connect` + `DataConnectService` to `BaseApiService` + `V2ApiEndpoints` - Removed deps: `krow_data_connect`, `firebase_auth`, `firebase_data_connect`, `geolocator`, `google_maps_flutter`, `meta` - State uses 5 typed lists: `List`, `List`, `List`, `List`, `List` - ShiftDetail (not Shift) used for detail page -- loaded by BLoC via API, not passed as route argument - Money: `hourlyRateCents` (int) / 100 for display -- all V2 shift entities use cents - Dates: All V2 entities have `DateTime` fields (not `String`) -- no more `DateTime.parse()` in widgets - AssignmentStatus enum drives bottom bar logic (accepted=clock-in, assigned=accept/decline, null=apply) - Old `Shift` entity still exists in domain but only used by clock-in feature -- shift list/detail pages use V2 entities - ShiftDetailsModule route no longer receives `Shift` data argument -- uses `shiftId` param only - `toShiftDetailsById(String)` is the standard navigation for V2 (no entity passing) - Profile completion: moved into feature repo impl via `V2ApiEndpoints.staffProfileCompletion` + `ProfileCompletion.fromJson` - Find Shifts tab: removed geolocator distance filter and multi-day grouping (V2 API handles server-side) - Renamed use cases: `GetMyShiftsUseCase` → `GetAssignedShiftsUseCase`, `GetAvailableShiftsUseCase` → `GetOpenShiftsUseCase`, `GetHistoryShiftsUseCase` → `GetCompletedShiftsUseCase`, `GetShiftDetailsUseCase` → `GetShiftDetailUseCase` ## Client Home Feature Migration (Phase 4 -- completed) - Migrated from `krow_data_connect` + `DataConnectService` to `BaseApiService` + `V2ApiEndpoints` - Removed deps: `krow_data_connect`, `firebase_data_connect`, `intl` - V2 entities: `ClientDashboard` (replaces `HomeDashboardData`), `RecentOrder` (replaces `ReorderItem`) - `ClientDashboard` contains nested `SpendingSummary`, `CoverageMetrics`, `LiveActivityMetrics` - Money: `weeklySpendCents`, `projectedNext7DaysCents`, `averageShiftCostCents` (int) / 100 for display - Two API calls: `GET /client/dashboard` (all metrics + user/biz info) and `GET /client/reorders` (returns `{ items: [...] }`) - Removed `GetUserSessionDataUseCase` -- user/business info now part of `ClientDashboard` - `LiveActivityWidget` rewritten from StatefulWidget with direct DC calls to StatelessWidget consuming BLoC state - Dead code removed: `ShiftOrderFormSheet`, `ClientHomeSheets`, `CoverageDashboard` (all unused) - `RecentOrder` entity: `id`, `title`, `date` (DateTime?), `hubName` (String?), `positionCount` (int), `orderType` (OrderType) - Module imports `CoreModule()` (not `DataConnectModule()`), injects `BaseApiService` into repo - State has `dashboard` (ClientDashboard?) with computed getters `businessName`, `userName` - No photoUrl in V2 dashboard response -- header shows letter avatar only ## Client Billing Feature Migration (Phase 4 -- completed) - Migrated from `krow_data_connect` + `BillingConnectorRepository` to `BaseApiService` + `V2ApiEndpoints` - Removed deps: `krow_data_connect`, `firebase_data_connect` - Deleted old presentation models: `BillingInvoice`, `BillingWorkerRecord`, `SpendingBreakdownItem` - V2 domain entities used directly: `Invoice`, `BillingAccount`, `SpendItem`, `CurrentBill`, `Savings` - Old domain types removed: `BusinessBankAccount`, `InvoiceItem`, `InvoiceWorker`, `BillingPeriod` enum - Money: all amounts in cents (int). State has computed `currentBillDollars`, `savingsDollars`, `spendTotalCents` getters - `Invoice` V2 entity: `invoiceId`, `invoiceNumber`, `amountCents` (int), `status` (InvoiceStatus enum), `dueDate`, `paymentDate`, `vendorId`, `vendorName` - `BillingAccount` V2 entity: `accountId`, `bankName`, `providerReference`, `last4`, `isPrimary`, `accountType` (AccountType enum) - `SpendItem` V2 entity: `category`, `amountCents` (int), `percentage` (double) -- server-side aggregation by role - Spend breakdown: replaced `BillingPeriod` enum with `BillingPeriodTab` (local) + `SpendBreakdownParams` (startDate/endDate ISO strings) - API response shapes: list endpoints return `{ items: [...] }`, scalar endpoints spread data (`{ currentBillCents, requestId }`) - Approve/dispute: POST to `V2ApiEndpoints.clientInvoiceApprove(id)` / `clientInvoiceDispute(id)` - Completion review page: `BillingInvoice` replaced with `Invoice` -- worker-level data not available in V2 (widget placeholder) - `InvoiceStatus` enum has `.value` property for display and `fromJson` factory with safe fallback to `unknown` ## Client Reports Feature Migration (Phase 4 -- completed) - Migrated from `krow_data_connect` + `ReportsConnectorRepository` to `BaseApiService` + `V2ApiEndpoints` - Removed deps: `krow_data_connect` - 7 report endpoints: summary, daily-ops, spend, coverage, forecast, performance, no-show - Old `ReportsSummary` entity replaced with V2 `ReportSummary` (different fields: totalShifts, totalSpendCents, averageCoveragePercentage, averagePerformanceScore, noShowCount, forecastAccuracyPercentage) - `businessId` removed from all events/repo -- V2 API resolves from auth token - DailyOps: old `DailyOpsShift` replaced with `ShiftWithWorkers` (from coverage_domain). `TimeRange` has `startsAt`/`endsAt` (not `start`/`end`) - Spend: `SpendReport` uses `totalSpendCents` (int), `chart` (List with `bucket`/`amountCents`), `breakdown` (List with `category`/`amountCents`/`percentage`) - Coverage: `CoverageReport` uses `averageCoveragePercentage`, `filledWorkers`, `neededWorkers`, `chart` (List with `day`/`needed`/`filled`/`coveragePercentage`) - Forecast: `ForecastReport` uses `forecastSpendCents`, `averageWeeklySpendCents`, `totalWorkerHours`, `weeks` (List with `week`/`shiftCount`/`workerHours`/`forecastSpendCents`/`averageShiftCostCents`) - Performance: V2 uses int percentages (`fillRatePercentage`, `completionRatePercentage`, `onTimeRatePercentage`) and `averageFillTimeMinutes` (double) -- convert to hours: `/60` - NoShow: `NoShowReport` uses `totalNoShowCount`, `noShowRatePercentage`, `workersWhoNoShowed`, `items` (List with `staffId`/`staffName`/`incidentCount`/`riskStatus`/`incidents`) - Module injects `BaseApiService` via `i.get()` -- no more `DataConnectModule` import ## Client Hubs Feature Migration (Phase 5 -- completed) - Migrated from `krow_data_connect` + `HubsConnectorRepository` + `DataConnectService` to `BaseApiService` + `V2ApiEndpoints` - Removed deps: `krow_data_connect`, `firebase_auth`, `firebase_data_connect`, `http` - V2 `Hub` entity: `hubId` (not `id`), `fullAddress` (not `address`), `costCenterId`/`costCenterName` (flat, not nested `CostCenter` object) - V2 `CostCenter` entity: `costCenterId` (not `id`), `name` only (no `code` field) - V2 `HubManager` entity: `managerAssignmentId`, `businessMembershipId`, `managerId`, `name` - API response shapes: `GET /client/hubs` returns `{ items: [...] }`, `GET /client/cost-centers` returns `{ items: [...] }` - Create/update return `{ hubId, created: true }` / `{ hubId, updated: true }` -- repo returns hubId String - Delete: soft-delete (sets status=INACTIVE). Backend rejects if hub has active orders (409 HUB_DELETE_BLOCKED) - Assign NFC: `POST /client/hubs/:hubId/assign-nfc` with `{ nfcTagId }` - Module no longer imports `DataConnectModule()` -- `BaseApiService` available from parent `CoreModule()` - `UpdateHubArguments.id` renamed to `UpdateHubArguments.hubId`; `CreateHubArguments.address` renamed to `.fullAddress` - `HubDetailsDeleteRequested.id` renamed to `.hubId`; `EditHubAddRequested.address` renamed to `.fullAddress` - Navigator still passes full `Hub` entity via route args (not just hubId) ## Client Orders Feature Migration (Phase 5 -- completed) - 3 sub-packages migrated: `orders_common`, `view_orders`, `create_order` - Removed deps: `krow_data_connect`, `firebase_data_connect`, `firebase_auth` from all; kept `intl` in create_order and orders_common - V2 `OrderItem` entity: `itemId`, `orderId`, `orderType` (OrderType enum), `roleName`, `date` (DateTime), `startsAt`/`endsAt` (DateTime), `requiredWorkerCount`, `filledCount`, `hourlyRateCents`, `totalCostCents` (int cents), `locationName` (String?), `status` (ShiftStatus enum), `workers` (List) - Old entities deleted: `OneTimeOrder`, `RecurringOrder`, `PermanentOrder`, `ReorderData`, `OneTimeOrderHubDetails`, `RecurringOrderHubDetails` - `AssignedWorkerSummary`: `applicationId` (String?), `workerName` (String? -- nullable!), `role` (String?), `confirmationStatus` (ApplicationStatus?) - V2 `Vendor` entity: field is `companyName` (not `name`) -- old code used `vendor.name` - V2 `ShiftStatus` enum: only has `draft`, `open`, `pendingConfirmation`, `assigned`, `active`, `completed`, `cancelled`, `unknown` -- no `filled`/`confirmed`/`pending` - `OrderType` enum has `unknown` variant -- must handle in switch statements - View orders: removed `GetAcceptedApplicationsForDayUseCase` -- V2 returns workers inline with order items - View orders cubit: date filtering now uses `_isSameDay(DateTime, DateTime)` instead of string comparison - Create order BLoCs: build `Map` V2 payloads instead of old entity objects - V2 create endpoints: `POST /client/orders/one-time` (requires `orderDate`), `/recurring` (requires `startDate`/`endDate`/`recurrenceDays`), `/permanent` (requires `startDate`/`daysOfWeek`) - V2 edit endpoint: `POST /client/orders/:orderId/edit` -- creates edited copy, cancels original - V2 cancel endpoint: `POST /client/orders/:orderId/cancel` with optional `reason` - Reorder uses `OrderPreview` (from `V2ApiEndpoints.clientOrderReorderPreview`) instead of old `ReorderData` - `OrderPreview` has nested `OrderPreviewShift` > `OrderPreviewRole` structure - Query repo: `getHubs()` replaces `getHubsByOwner(businessId)` -- V2 resolves business from auth token - `OneTimeOrderPosition` is now a typedef for `OrderPositionUiModel` from `orders_common` - `OrderEditSheet` (1700 lines) fully rewritten: delegates to `IViewOrdersRepository` instead of direct DC calls ## Staff Authentication Feature Migration (Phase 6 -- completed) - Migrated from `krow_data_connect` + `DataConnectService` + `firebase_data_connect` to `BaseApiService` + `V2ApiEndpoints` - Removed deps: `krow_data_connect`, `firebase_data_connect`, `firebase_core` - KEPT `firebase_auth` -- V2 backend `startStaffPhoneAuth` returns `CLIENT_FIREBASE_SDK` mode for mobile, meaning phone verification stays client-side via Firebase SDK - Auth flow: Firebase SDK phone verify (client-side) -> get idToken -> `POST /auth/staff/phone/verify` with `{ idToken, mode }` -> V2 hydrates session (upserts user, loads actor context) - V2 verify response: `{ sessionToken, refreshToken, expiresInSeconds, user: { id, email, displayName, phone }, staff: { staffId, tenantId, fullName, ... }, tenant, requiresProfileSetup }` - `requiresProfileSetup` boolean replaces old signup logic (create user/staff via DC mutations) - Profile setup: `POST /staff/profile/setup` with `{ fullName, bio, preferredLocations, maxDistanceMiles, industries, skills }` - Sign out: `POST /auth/sign-out` (server-side token revocation) + local `FirebaseAuth.signOut()` - `AuthInterceptor` in `DioClient` stays as-is -- attaches Firebase Bearer tokens to all V2 API requests - `AuthInterceptor` in `DioClient` stays as-is -- attaches Firebase Bearer tokens to all V2 API requests - Pre-existing issue: `ExperienceSkill` and `Industry` enums deleted from domain but still referenced in `profile_setup_experience.dart` ## Client Authentication Feature Migration (Phase 6 -- completed) - Migrated from `krow_data_connect` + `DataConnectService` + `firebase_data_connect` to `BaseApiService` + `V2ApiEndpoints` - Removed deps: `firebase_data_connect`, `firebase_core` from pubspec - KEPT `firebase_auth` -- client-side sign-in needed so `AuthInterceptor` can attach Bearer tokens - KEPT `krow_data_connect` -- only for `ClientSessionStore`/`ClientSession`/`ClientBusinessSession` (not yet extracted) - Auth flow (Option A -- hybrid): 1. Firebase Auth client-side `signInWithEmailAndPassword` (sets `FirebaseAuth.instance.currentUser`) 2. `GET /auth/session` via V2 API (returns user + business + tenant context) 3. Populate `ClientSessionStore` from V2 session response - Sign-up flow: 1. `POST /auth/client/sign-up` via V2 API (server-side: creates Firebase account + user/tenant/business/memberships in one transaction) 2. Local `signInWithEmailAndPassword` (sets local auth state) 3. `GET /auth/session` to load context + populate session store - V2 session response shape: `{ user: { userId, email, displayName, phone, status }, business: { businessId, businessName, businessSlug, role, tenantId, membershipId }, tenant: {...}, vendor: null, staff: null }` - Sign-out: `POST /auth/client/sign-out` (server-side revocation) + `FirebaseAuth.instance.signOut()` + `ClientSessionStore.instance.clear()` - V2 sign-up error codes: `AUTH_PROVIDER_ERROR` with message containing `EMAIL_EXISTS` or `WEAK_PASSWORD`, `FORBIDDEN` for role mismatch - Old Data Connect calls removed: `getUserById`, `getBusinessesByUserId`, `createBusiness`, `createUser`, `updateUser`, `deleteBusiness` - Old rollback logic removed -- V2 API handles rollback server-side in one transaction - Domain `User` entity: V2 uses `status: UserStatus` (not `role: String`) -- constructor: `User(id:, email:, displayName:, phone:, status:)` - Module: `CoreModule()` (not `DataConnectModule()`), injects `BaseApiService` into `AuthRepositoryImpl` ## Client Settings Feature Migration (Phase 6 -- completed) - Migrated sign-out from `DataConnectService.signOut()` to V2 API + local Firebase Auth - Removed `DataConnectModule` import from module, replaced with `CoreModule()` - `SettingsRepositoryImpl` now takes `BaseApiService` (not `DataConnectService`) - Sign-out: `POST /auth/client/sign-out` + `FirebaseAuth.instance.signOut()` + `ClientSessionStore.instance.clear()` - `settings_profile_header.dart` still reads from `ClientSessionStore` (now from `krow_core`) ## V2SessionService (Final Phase -- completed) - `V2SessionService` singleton at `packages/core/lib/src/services/session/v2_session_service.dart` - Replaces `DataConnectService` for session state management in both apps - Uses `SessionHandlerMixin` from core (same interface as old DC version) - `fetchUserRole()` calls `GET /auth/session` via `BaseApiService` (not DC connector) - `signOut()` calls `POST /auth/sign-out` + `FirebaseAuth.signOut()` + `handleSignOut()` - Registered in `CoreModule` via `i.addLazySingleton()` -- calls `setApiService()` - Both `main.dart` files use `V2SessionService.instance.initializeAuthListener()` instead of `DataConnectService` - Both `SessionListener` widgets subscribe to `V2SessionService.instance.onSessionStateChanged` - `staff_main` package migrated: local repo/usecase via `V2ApiEndpoints.staffProfileCompletion` + `ProfileCompletion.fromJson` - `krow_data_connect` removed from: staff app, client app, staff_main package pubspecs - Session stores (`StaffSessionStore`, `ClientSessionStore`) now live in core, not data_connect