# 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>()`)
- `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<TodayShift>`, `List<AssignedShift>`, `List<OpenShift>`
- 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<AssignedShift>`, `List<OpenShift>`, `List<PendingAssignment>`, `List<CancelledShift>`, `List<CompletedShift>`
- 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<SpendDataPoint> with `bucket`/`amountCents`), `breakdown` (List<SpendItem> with `category`/`amountCents`/`percentage`)
- Coverage: `CoverageReport` uses `averageCoveragePercentage`, `filledWorkers`, `neededWorkers`, `chart` (List<CoverageDayPoint> with `day`/`needed`/`filled`/`coveragePercentage`)
- Forecast: `ForecastReport` uses `forecastSpendCents`, `averageWeeklySpendCents`, `totalWorkerHours`, `weeks` (List<ForecastWeek> 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<NoShowWorkerItem> with `staffId`/`staffName`/`incidentCount`/`riskStatus`/`incidents`)
- Module injects `BaseApiService` via `i.get<BaseApiService>()` -- 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<AssignedWorkerSummary>)
- 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<String, dynamic>` 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<V2SessionService>()` -- 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
