Skip to main content

Frontend Module Atlas — What Is This & Every Module in Detail

kxinspect_frontend_flutter is a student-facing Flutter app for the KxInspections Contesting Charges interview assignment. A student lives in a booked property (BKG-001), receives an inspection report, is charged for damage/cleaning (CHG-001…), and must Accept or Contest (reason 10–2000 graphemes + 0–5 attachments) before a 30-day deadline auto-accepts. This atlas maps every Dart module — 187 files, ~22k LOC excluding generated kx_database.g.dart — so you can jump to the owner of any behavior.

Build profile: Flutter 3.41.9 / Dart 3.11.5, bloc 9.1 + go_router 17.4 + drift 2.28 + dio 5.9, fixtures in assets/fixtures/, two DataSource modes (fixture default, remote over FastAPI). One command dart format → flutter analyze → flutter test → goldens is the canonical gate.

Product snapshot (what is this)

Visual state → routeProves
Maintenance Hub /maintenance?tab=open (HubTab.open)booking selector, Outstanding blue banner, inventory/inspection/task/charge cards, bottom nav
Maintenance Hub History ?tab=historyaccepted/contested/resolved/paid with contest reason + statement link
Inspection /inspections/:idtype•code•room title, general notes, ItemAction (charge?) vs ItemUpdate
Charge /charges/:iddeadline copy, read-only fields, KxPhotoStrip /photo/:i viewer, Contest/Accept/Pay
Contest /charges/:id/contestmultiline Reason (UAX-29), file picker, optimistic submit, draft persistence
Statement /statementper-currency totals of unpaid, mock Pay accepted→paid
Notifications /notificationschargeRaised local/browser notification + unread badge

:::tip Two invariants Fixture-firstassets/fixtures/app_state.json is the byte-stable export of the Python seed; reviewers flutter run -d chrome --dart-define=DATA_SOURCE=fixture need no server. Thin UI — widgets never contain lifecycle logic; features/*/application + shared/domain own the 30-row transition table and deadlineAt = raisedAt + graceDays inclusive rule, replayed from Python vectors. :::

flowchart TD
main[lib/main.dart → bootstrap.dart] --> app[lib/app\n cubits + DI + shell]
app --> router[lib/router\n go_router + FeatureSlots]
router --> features[lib/features\n charge / inspection / maintenance / statement / notifications]
features --> core[lib/core\n config / database / sync / network]
core --> drift[(Drift\n kx_database + LocalStore)]
core --> api[KxApi\n Dio vs Fixture adapter]
features --> ds[lib/design_system\n tokens + 18 components]
core --> shared[lib/shared\n domain + application]
shared --> l10n[lib/l10n\n en + cy ARB]

Top-level map

lib/
├── main.dart 5 LOC entry → bootstrap()
├── bootstrap.dart 66 parses AppConfig, registerDependencies, runApp(KxApp), diagnostic screen
├── app/ 13 files long-lived cubits, DI, shell
├── core/ 33 files config, Drift, sync, Dio, clocks
├── design_system/ 28 files tokens + theme + 18 components, zero business import
├── features/ 86 files vertical slices (application/data/presentation per feature)
├── router/ 5 files central route table + typed args
├── l10n/ 3 gen + 2 ARB en/cy strings
├── shared/ 14 files pure domain (lifecycle, money, ids) + Result/Failure
└── data/ (reserved)

1 · lib/main.dart & lib/bootstrap.dart

main.dart is 3 lines: void main() => bootstrap(); — keeps test harness import-free.

bootstrap.dart:bootstrap() does environment → graph → render in order:

  1. WidgetsFlutterBinding.ensureInitialized()
  2. AppConfig.parse(environmentValues) — collects all AppConfigIssue{key,detail,rawValue} then throws AppConfigException (one run reports every bad --dart-define). On throw, renders DiagnosticScreen instead of KxApp so bad API_BASE_URL never silently falls back.
  3. await registerDependencies(config) — opens Drift, seeds from fixture bundle or snapshot.
  4. runApp(KxApp(router: createAppRouter(...)))

Ref: documentation/docs/frontend/bootstrap-di.md.


2 · lib/app/ — Application shell

SubmoduleFilesResponsibility
app/kx_app.dartKxAppMaterialApp.router + AppBlocObserver, injects ThemeCubit/LocaleCubit/ConnectivityCubit
app/shell/kx_app_shell.dartKxAppShellScaffold with NavigationBar (compact) vs NavigationRail (≥840) via KxBreakpoints, HubTab badge
app/cubit/bookingbooking_cubit.dartselectedBookingId + HubTab; per-booking rememberHubTab(tab) persisted via PreferencesService
app/cubit/themetheme_cubit.dartThemeMode.system/light/dark + system listener → KxTheme.light()/dark()
app/cubit/localelocale_cubit.dartLocale('en'/'cy') persisted, rebuilds MaterialApp.locale
app/cubit/connectivityconnectivity_cubit.dartonline/offline from connectivity_plus + navigator.onLine web → Outbox drain trigger
app/cubit/syncsync_cubit.dartidle/syncing/error for KxProgressIndicator
app/cubit/notificationsnotifications_cubit.dartunreadCount derived from LocalStore.notificationsStreamKxNavShell badge
app/di/register_dependencies.dartGetIt composition root: AppConfig → KxDatabase → LocalStore → AppScopeBindings
app/di/app_scope_bindings.dartholds Clock, IdGenerator, PreferencesService, AnalyticsStore
app/di/di_module.dartDiModule extension point each feature appends
app/di/fixture_feature_module.dartregisters FixtureHttpClientAdapter + ChargeRepositories.fixture when DataSource.fixture
app/app_bloc_observer.dartglobal onChangemaybeLogBlocTransition when ENABLE_ANALYTICS_LOG=true
app/feature_composition.dartstitches FeatureSlots for router

Rule: app never imports features directly — it depends on ports, features inject via DiModule.


3 · lib/core/ — Platform & infrastructure

3.1 core/config (3 files)

  • app_config.dart (395 LOC) — DataSource.fixture|remote, gracePeriodDays=30, chaosLatency/ErrorRate, enableAnalyticsLog/DevMenu, storageNamespace, apiBaseUrl. Keys DATA_SOURCE, API_BASE_URL, GRACE_PERIOD_DAYS, CHAOS_LATENCY_MS, CHAOS_ERROR_RATE, ENABLE_…. StorageNamespace.remote(uri) hashes URL so fixture and remote(http://…) Drift files never collide.
  • contract_version.dartcontractVersion=1, schemaVersion=1 constants.
  • storage_namespace.dart — namespace fingerprint helper.

3.2 core/database (11 files, ~18.5k LOC inc. generated)

FileRole
kx_database.dart (407)Drift tables: Bookings, InventoryReports, Inspections, Tasks, Charges, ChargePhotos, ContestDrafts, AttachmentBlobs, OutboxRows, SyncMeta (DataClassName, FK cascade)
kx_database.g.dart (15 890)generated companions, DAOs
local_store.dart (898)transactional façade: bookingsStream(), hubSnapshotStream(id), chargesStream(), applySnapshot(), applyRemoteChanges(), enqueueOutbox()
domain_mapper.dart (612)Drift ↔ shared/domain strict mapping
fixture_bundle.dart (241)loads assets/fixtures/app_state.json + manifest.json + media
database_connection.dart (43)drift_flutter Wasm/IndexedDB (web) vs sqlite3_flutter_libs + path_provider (mobile/desktop), file name from StorageNamespace
repository_stream.dart (46)broadcast Stream<List<T>> helpers
persisted_demo_clock.dart (61)pins referenceNow 2026-08-01T12:00:00Z for deadline demo
attachment_bytes*.dart (3)dart:io vs dart:html conditional byte read + SHA-256

Ref: documentation/docs/frontend/data-layer.md.

3.3 core/network (4 files)

  • kx_api.dart (448) — abstract KxApi{ health(), hub(), charges(), charge(), accept(), contest(), pay(), taskCreate(), events() } with Idempotency-Key: uuid per command.
  • api_models.dart (79) — json_serializable DTOs mirroring app/schemas strict lowerCamelCase.
  • fixture_http_client_adapter.dart (101) — replays bundle, fabricates stateEpoch/storeRevision deterministically.
  • failure_mapper.dart + exception_mapper.dartDioException → KxFailure typed union.

Swapping DataSource swaps only this adapter — no widget changes.

3.4 core/sync (12 files)

FileLOCRole
outbox.dart506Drift OutboxRows{ opKind: accept/contest/pay/taskCreate, payloadJson, stateEpoch, version, attempts, syncState}; enqueue is always insert+optimistic Charges flip, drain() on online
sync_service.dart173dequeues createdAt order, fresh Idempotency-Key per attempt, store.epoch_mismatch→SnapshotCoordinator.refresh(), 429→RetryPolicy
retry_policy.dart27exponential 300 ms→8 s + jitter ±20%
live_sync_service.dart85KxApi.events(lastEventId) ResponseType.stream → SSE parse → SseEntityCommitter
sse_entity_committer.dart97patches single Charges row transactionally from charge.updated
snapshot_coordinator.dart113owns SyncMeta{stateEpoch,storeRevision,streamEpoch,streamCursor}, seeds from fixture vs GET /sync-snapshot, refetches on sync.required
server_adjusted_clock.dart96skew = serverTime - now() per meta.serverTime → correct deadlineAt copy
sync_leadership*.dart49+BroadcastChannel leader election (web) vs single owner (native) — one tab drains SSE

Ref: documentation/docs/frontend/offline-sync.md.

3.5 core/time (3 files)

anchored_demo_clock.dart (31), system_clock.dart (13), timer_scheduler.dart (56) — Clock/Scheduler abstraction so tests inject ManualClock at fixed instant for inclusive now ≥ deadlineAt.

3.6 core/analytics + core/error + core/storage + core/platform

  • analytics_store/sinks/validator/route_observer (331) — typed catalog mock.charge.accept, route.view; AppBlocObserver + AnalyticsRouteObserver produce.
  • exception_mapper (47) — contract error.code → KxFailure.
  • preferences_service (243) — SharedPreferences wrapper for cubits.
  • uuid_id_generator / dart_random_source (30) — GetIt IdGenerator deterministic in tests.

4 · lib/design_system/ — Zero-business component kit (barrel design_system.dart)

Foundations (foundations/ 9 files)

kx_colors (semantic ThemeExtension), kx_palette (raw ramps, tealAccentDecorative=#26BFA8 decorative-only), kx_spacing (4-pt), kx_radius, kx_typography (Roboto 400/500/700), kx_breakpoints (compact < 600, medium 600–839, expanded 840–1439, large >= 1440, allowsTwoColumns >= 840), kx_motion (150/250/350 ms, collapses to 0 when disableAnimations), kx_elevation (level1/2/3 + minTouchTarget 48), kx_tone (product tone, no domain meaning).

Theme

theme/kx_theme.dartKxTheme.light()/dark() maps tokens to ThemeData for app bar, card, nav bar/rail, tabs, checkbox/radio/switch, progress, dialog, tooltip, page transitions. Mode owned by ThemeCubit.

Components (components/ 18)

kx_button (filled/outlined/text + loading), kx_status_pill, kx_banner (info/success/warning + liveRegion), kx_card, kx_section_header, kx_icon_text, kx_readonly_field, kx_selector_field, kx_tabs/KxTabItem, kx_photo_strip/KxPhotoItem (the only horizontal scroller), kx_image (bounded decode, fallback), kx_skeleton/KxSkeletonBlock (shimmer stops under reduced motion), kx_message_state (empty/error), kx_content_container, kx_hero_app_bar (sliver — compose with CustomScrollView), kx_nav_shell, kx_focus_ring, kx_progress_indicator.

Guards: test/design_system/design_system_purity_test.dart forbids raw Colors.*, hardcoded prose, and any features import. Goldens test/golden/design_system/ (10 baselines, DPR 1.0, NoSplash) — Linux CI must regenerate once (macOS vs Linux raster drift).

Ref: documentation/docs/frontend/design-system.md.


5 · lib/features/ — Vertical slices

Each feature is application/ (pure → ports) → data/ (repo impl + DTOs) → presentation/ (widgets + blocs/pages). Widgets never contain transition() logic.

5.1 features/maintenance (22 files) — Hub /maintenance?tab=open|history

  • application/ports/maintenance_ports.dart, data/repositories/maintenance_repositories.dartMaintenancePorts{ hubSnapshotStream, tasksForBooking } backed by LocalStore.
  • data/sync/task_command_handler.dart — enqueues taskCreate via Outbox (client UUID authoritative).
  • presentation/bloc/maintenance_hub/, presentation/bloc/report/MaintenanceHubBloc subscribes to hubSnapshotStream and derives Open = outstanding|accepted|contested (by deadlineAt asc) vs History = accepted|contested|resolved|paid (by last event desc, accepted on both), plus reports.
  • presentation/widgets/booking_selector, hub_banner_view, hub_cards, hub_section — maps ChargeStatus→KxTone, uses KxBanner.info / KxCard.
  • presentation/navigation/maintenance_navigator.dartgoNamed(inspection/charge) from chargeId.

Ref: documentation/docs/frontend/features/maintenance-hub.md.

5.2 features/inspection (10 files) — /inspections/:id

  • application/ports + data/repositories — single inspectionStream(id).
  • presentation/bloc/inspection_detail, presentation/view/inspection_view_data — composes ItemAction{ chargeId?+amountMinor? } vs ItemUpdate.
  • presentation/widgets/inspection_cards.dartKxCard is onTap only when chargeId != null; otherwise no semantics button.
  • navigation/inspection_navigator.dart — charged-item route.

Ref: documentation/docs/frontend/features/inspection.md.

5.3 features/charge (32 files) — core lifecycle /charges/:id + /contest + /photo/:i

  • application/contest/contest_policy.dartCharacters(reason).length ∈ [10,2000] (mirrors Python graphemes.py, tested against charge_state_vectors.json including 🇬🇧 flag, ZWJ, CR LF).
  • application/contest/contest_draft.dart — debounce 300 ms → ContestDrafts(chargeId, reason) upsert, survives reload.
  • application/contest/charge_concurrency.dart — snapshots expectedStateEpoch + expectedVersion at tap-time for KxApi.
  • application/ports/charge_ports.dartaccept/contest/pay/contestDraft port; data/repositories/charge_repositories.dart + data/sync/charge_command_handler.dart implement via Outbox + LocalStore optimistic accepted/contested flip.
  • data/attachments/{database_contest_draft_store, file_picker_attachment_gateway}file_picker + MIME whitelist (jpeg/png/webp/mp4/pdf, ≤10 MiB/file, ≤25 MiB total) matched to backend.
  • presentation/bloc/charge_detail + presentation/bloc/contest_formChargeDetailBloc watches chargeStream(id), ContestFormBloc disables Submit until ≥10 graphemes.
  • presentation/pages/{charge_detail_page, contest_charge_page, charge_photo_viewer_page} + widgets/{charge_action_bar, charge_banner, charge_details_section, charge_evidence_section, evidence_viewer, contest_*}KxPhotoStripInteractiveViewer (pinch/web wheel), invalid photoIndex?notice=photoIndexRepaired redirect.

Ref: documentation/docs/frontend/features/charge-contest.md.

5.4 features/statement (3 files) — /statement

application/statement_models.dart (per-currency Map<currency, amountMinor> over unpaid outstanding|contested|accepted), presentation/bloc/statement_bloc.dart, presentation/pages/statement_page.dart — mock pay ( accepted→paid sequential keys, no batch endpoint).

Ref: documentation/docs/frontend/features/statement.md.

5.5 features/notifications (9 files) — /notifications bonus B-02

  • application/{notification_coordinator, notification_event, ports/notification_ports}, data/{notification_feed_repository, notification_store}, platform/{gateway_io, _stub, _web}flutter_local_notifications (native) + Web Notifications API, both foreground-only from SSE charge.created.
  • presentation/bloc/notifications_bloc.dart + presentation/pages/notifications_page.dartread is POST .../read with expectedStateEpoch, naturally idempotent.

Ref: documentation/docs/frontend/features/notifications.md.

5.6 features/settings + features/analytics_debug

s/settings/presentation/pagesThemeCubit/LocaleCubit toggles (+ ENABLE_DEV_MENU dev route list). features/analytics_debug/presentation/pages/analytics_debug_page.dart — log view when ENABLE_ANALYTICS_LOG=true.


6 · lib/router/ — Single route table

FileRole
app_router.dart (244)createAppRouter(config, cubits, FeatureSlots)GoRouter with ShellRoute(KxAppShell), GoRoute(root)→maintenance?tab=open, maintenance tab-repair redirect, report/inspection/charge typed Id.tryParseRouteErrorPage, charge children contest + photoViewer index-repair (-1→?notice=photoIndexRepaired)
route_names.dart (142)KxRoutes{ rootPath, maintenancePath, reportPath, inspectionPath, chargePath, contestRelativePath, photoViewerRelativePath, statementPath } + param constants tabQueryParameter, chargeIdParameter, photoIndexParameter, noticeQueryParameter
route_args.dart (106)KxRouteArgs{ maintenance(tab), report(id), inspection(id), charge(id,notice), contest(id), photoViewer(id,index), statement(id?) } + HubTab.tryParse/fallback, RouteNotice
feature_slots.dart (113)FeatureSlots{ maintenanceHub, report, inspection, charge, contest, photoViewer, statement, notifications, settings } builder map — router never imports feature pages
route_error_page.dart (61)non-localized 404 with heading semantics
Analyticscore/analytics/analytics_route_observer.dart emits route.view

Ref: documentation/docs/frontend/routing.md. Deep links verified via flutter test test/router/ + integration tester.goNamed(KxRoutes.chargeName, {chargeId: CHG-001}).


7 · lib/l10n/ + lib/shared/

  • l10n/app_en.arb + app_cy.arbl10n/app_localizations*.dart (generated, flutter gen-l10n). Every user string in ARB; inspector prose (notes, contestReason) never translated. titleKey/bodyKey/altKey from backend remain keys. Booking selector MM/DD/YYYY is literal; other dates intl DateFormat locale-sensitive. design_system holds no strings (purity test).
  • shared/domain/{ids, enums, money, entities, charge_lifecycle, deadline_policy, hub_snapshot, commands, storage_namespace} — typed BookingId/ChargeId/InspectionId, ChargeStatus, ChargeType, RecordStatus, Money{amountMinor,currency}, charge_lifecycle.transition() total function, deadline_policy inclusive now≥deadlineAt, freezed unions Result/Success/Failure.
  • shared/application/{result, failure, ports}Result<T> / KxFailure used by ports.

Ref: documentation/docs/frontend/l10n-theming.md.


8 · assets/ + tool/ + test/

  • assets/fixtures/{app_state.json, manifest.json, media/att_*.png/.t.png, att_rpt_*.pdf, vectors/{charge_state,deadline,namespace}_vectors.json} — committed bundle copied from kxinspect_backend_python/scripts/export_fixtures.py; verified by verify_fixture_manifest.py parity.
  • assets/fonts/Roboto-{Regular,Medium,Bold}.ttf + ASSET_PROVENANCE.md, assets/images/.gitkeep.
  • tool/test_all.sh (canonical gate), tool/android_runtime_proof.sh, tool/aggregate_evidence.dart / check_evidence.dart (SHA manifest).
  • test/{unit, widget, golden, foundation, router, architecture} + integration_test/app_test.dart — unit: vectors+deadline+graphemes; widget: 360/600/840/1440 × 1.0/1.3/2.0 × LTR/RTL × light/dark, Enter/Space, focus ring, semantics; golden: 10 design baselines (DPR 1.0, pinned harness); integration: fixture boot → hub → inspection → charge → contest → history → statement → pay → reload persistence.

  • New to the codebase? lib/bootstrap.dart → app/di/register_dependencies.dart → router/app_router.dart.
  • Adding a feature? Copy features/charge/{application,data,presentation} vertical slice; keep design_system below.
  • Changing a token? Touch only design_system/foundations + theme/kx_theme.dart and update test/design_system.
  • Swapping datasource? Change only DataSource --dart-define — DI swaps KxApi adapter, Drift streams + widgets unchanged.

Live frontend docs: documentation/docs/frontend/{overview, architecture, bootstrap-di, routing, state-management, design-system, data-layer, offline-sync}.