# Technical documentation — datafy_app (Datafy Ai)

Evidence date: 2026-10-18. Source: `git@bitbucket.org:overdrive/datafy_app.git`, `main` at `dba98cc7aa6f9868140511a98ef37240887539d0`.

This file describes the tree that was inspected. It does not prescribe a target architecture.

## Application shape

FlutterFlow-generated Flutter application. Package name `datafy_ai`. Version in `pubspec.yaml`: `1.0.54+84`. SDK constraint: `>=3.0.0 <4.0.0`. `README.md` says to use the Flutter stable channel.

Top-level directories that are part of the product: `android/`, `ios/`, `web/`, `lib/`, `assets/`, `dependencies/`, `test/`.

Also tracked: `.dart_tool/` build outputs (`git ls-files` includes them). The working tree has 583 tracked paths. `private_keys/` and `sonarqube_scan.bat` were named by OVER-79's scan-host listing and are not in `git ls-files` at this commit.

There is no `windows/`, `macos/`, or `linux/` runner directory.

## `lib/` layout

| Path | Contents |
|---|---|
| `lib/main.dart` | Entry. Path URL strategy, environment values, PowerSync URL and schema, `SupaFlow.initialize`, providers |
| `lib/app_state.dart` | `FFAppState`, including `userRole` and `userFullName` in `flutter_secure_storage` |
| `lib/index.dart` | Barrel of routed pages. 51 widgets are exported |
| `lib/flutter_flow/nav/nav.dart` | `GoRouter` via `FFRoute` |
| `lib/pages/includes/auth/` | Routed login and verify |
| `lib/pages/rep/` | Rep screens |
| `lib/pages/admin/` | Admin screens |
| `lib/pages/includes/` | Nav, empty states, profile, WhatsApp, image helpers |
| `lib/backend/supabase/` | Client, storage helper, one Dart file per table |
| `lib/backend/schema/` | FlutterFlow structs used as route parameters |
| `lib/custom_code/actions/` | Hand-written actions listed below |
| `lib/custom_code/widgets/` | Survey form widgets |
| `lib/auth/supabase_auth/` | Auth manager over `supabase_flutter` |
| `lib/environment_values.dart` | Loads `assets/environment_values/environment.json` |

Unexported page folders: `lib/pages/login/`, `news/`, `profile/`, `sites/`, `task/`, `dashboard/`. See `features.md`.

## Frameworks and languages

- Dart and Flutter. Material icons on. Localisations `en` and `af`.
- State: `provider` (`ChangeNotifierProvider` for app state and the PowerSync library state).
- Routing: `go_router` 12.1.3. `GoRouter.optionURLReflectsImperativeAPIs = true`. `usePathUrlStrategy()`.
- Local database: `powersync` 1.14.1, `sqflite` 2.3.3+1, vendored library `power_sync_b0w5r9` 0.0.9.
- HTTP: `http` 1.4.0 (also a dependency override).
- UI kits typical of FlutterFlow: `google_fonts`, `flutter_animate`, `cached_network_image`, `dropdown_button2`, `carousel_slider`, and others pinned in `pubspec.yaml`.

Path dependencies:

- `dependencies/ff_commons`
- `dependencies/ff_theme`
- `dependencies/power_sync_b0w5r9`

`flutter_keyboard_visibility` is pulled from the FlutterFlow GitHub fork at ref `b39c1d0ca1671458e4daf3eea2ebe11741f9bcf3`.

## Supabase

`lib/backend/supabase/supabase.dart`:

- URL: `https://datafy.appmonitor.co.za`
- Header: `X-Client-Info: flutterflow`
- Auth flow: `AuthFlowType.implicit`
- Anon key: present as a string literal in that file. The value is not repeated in Silicon Ark.

Generated table bindings under `lib/backend/supabase/database/tables/` include the names below. A binding is evidence the FlutterFlow project knew the table. It is not evidence that a screen queries it.

People and access: `app_user`, `user`, `user_view`, `user_email_view`, `user_store`, `user_sign_in_info`, `user_activity`, `auth_user_view`, `customer`, `customer_user`, `customer_client`, `customer_brand`, `customer_store`, `client`, `rep_list`.

Stores and products: `store`, `store_user_link`, `store_view`, `store_view_new`, `store_view_all`, `store_view_admin`, `store_user_flattened`, `view_store_user_link`, `store_customer_link`, `store_visibility`, `store_updates_log`, `product`, `product_store`, `product_images`, `product_full_list`, `product_grouped`, `product_ml`, `producy_view`, `brand`, `category_images`, `channel_data`.

Work: `task`, `task_file`, `tasks_create_logs`, `promos`, `promos_linked_users`, `store_promo`, `promo_gallery`, `promo_uploads`, `promo_kaching_uploads`, `kaching`, `kaching_linked_users`, `kaching_gallery`, `kaching_uploads`.

Surveys: `survey`, `survey_questions`, `survey_multiple_choice`, `survey_store_link`, `survey_responses`.

Other: `notification`, `sites`, `file`, `file_upload`, `sjreport`, `sjreport_spar`, `sjreport_summary`, `sjreport_uploads`, `sjreport_archive`, `callsheet`, `version`, `model_versions`, `archive_settings`, `logs`, `log`, `main_issue`, `linked_issue`, `queue_processing_log`, `store_processing_queue`, `product_store_processing_queue`.

Names that look like leftovers or fixtures: `store_backup_20241118`, `store_product_backup_20241128`, `task_john`, `john_data`, `temp_products`, `test_table`, `test_tenant`. Provenance: **Implemented** as generated files. Product use is **Unknown**.

Storage helper `lib/backend/supabase/storage/storage.dart` uploads bytes and returns `getPublicUrl`. Call sites pass bucket `uploads` or, for profile images, `profile`.

## PowerSync

Configured in `lib/main.dart` before `runApp`:

- `PowerSyncUrl`: `https://67c1c2c9586c8d282a0669f2.powersync.journeyapps.com`
- `PowerSyncSchema`: a JSON document of tables, decoded by `dependencies/power_sync_b0w5r9/lib/custom_code/actions/initialize_power_sync.dart`

Local tables in that JSON:

`survey`, `survey_multiple_choice`, `survey_questions`, `category_images`, `customer_user`, `app_user`, `store_user_link`, `kaching_gallery`, `survey_store_link`, `survey_responses`, `kaching_linked_users`, `store_promo`, `promo_gallery`, `promos_linked_users`, `product_images`, `product_store`, `task`, `attachments_queue`.

`attachments_queue` is `local_only: true`. The others are not marked local-only or insert-only.

`initializePowerSync` is called from the rep dashboard's first frame (`lib/pages/rep/dashboard/dashboard/dashboard_widget.dart`), together with `setUpAttachments`. Admin dashboard code that was sampled uses Supabase `queryRows` and does not call `initializePowerSync`.

Writes from screens use `powersyncWrite(sql, params)` in the vendored library. Sync rules (which rows a user receives) are not in this repository. They would live on the PowerSync service. **Unknown / Requires Confirmation** which service config matches this client. `silicon_ark/datafy_powersync/` documents a different repository and must not be assumed to be this URL's config.

## Auth

`lib/auth/supabase_auth/supabase_auth_manager.dart` implements sign-out, `resetPasswordForEmail`, `createAccountWithEmail`, and email sign-in. The interface in `auth_manager.dart` also declares `signInWithApple` and `signInWithGoogle`. The routed login widget uses `signInWithEmail` only.

Session updates flow through `datafyAiSupabaseUserStream` into `AppStateNotifier` (`lib/main.dart`). `loggedIn` is `user?.loggedIn ?? false`.

Secure storage keys observed: `ff_userRole`, and the rest of `FFAppState` persistence in `app_state.dart`.

## Custom actions

Exported from `lib/custom_code/actions/index.dart`:

| Action | Behaviour that was read |
|---|---|
| `checkGpsStatus` | `Geolocator.isLocationServiceEnabled()` |
| `openLocationSettings` | Opens OS location settings (named action) |
| `turnOnGPS` | Named action; body not quoted here |
| `saveSurveyResponse` | Upsert into `survey_responses` |
| `shareToWhatsApp` | `wa.me` on web, share sheet on mobile |
| `exportProductImagesToPDF`, `exportPromoImagesToPDF`, `exportKachingImagesToPDF` | PDF export actions |
| `getStoreCategoriesAction` | Named action |
| `setUpAttachments`, `resolveTaskPicture`, `setTaskPicture`, `setTempImageFromUrl` | Image and attachment helpers |

## Routing

`createRouter` in `nav.dart`. Initial location `/`. Error builder and `/` both show `BrandsWidget` when `loggedIn` is true and `LoginWidget` otherwise.

`FFRoute.requireAuth` defaults to false. A repository search found no `requireAuth: true`. The redirect that sends anonymous users to `/login` is therefore inactive for these routes.

Route inventory is the `routePath` list in `features.md`. Parameters are FlutterFlow serialised query parameters (`FFParameters`), including Supabase rows and structs.

KaChing detail path is `/taskDetailsKachinga` (trailing `a`). In-store promo path is `/taskDetailsPromoInstore` (singular Promo in the path, plural Promos in the route name).

## Platforms

### Android

- `applicationId` and `namespace`: `coza.datafy.web`
- `minSdkVersion` 23, `targetSdkVersion` 35, `compileSdkVersion` 35
- Label: Datafy
- Permissions: `INTERNET`, `ACCESS_FINE_LOCATION`, `ACCESS_COARSE_LOCATION`, `CAMERA`, read/write external storage with `maxSdkVersion` 32
- Release `signingConfig` is `signingConfigs.debug`. A `signingConfigs.release` block reads `key.properties` and is not what the release build type uses. The file comment says signing with debug keys so `flutter run --release` works.
- Java compatibility 1.8. Core library desugaring enabled.

### iOS

- Display name Datafy, bundle name Datafy Ai, bundle id `coza.datafy.web`
- Localisations `en` and `af`
- URL scheme `datafyai`, URL name `datafyai.com`
- `ios/ImageNotification/` is a second plist target. No Firebase dependency was found in the Android manifest search. How push is delivered is **Unknown / Requires Confirmation**

### Web

- `web/index.html` title metadata "Datafy", description "Built with FlutterFlow.", `noindex`
- Base href `$FLUTTER_BASE_HREF`

## Environment

`assets/environment_values/environment.json` contains only:

```json
{ "appVersion": "1.0.38" }
```

`FFDevEnvironmentValues.appVersion` loads that field. A search for `FFDevEnvironmentValues().appVersion` and `VersionTable` usage in screens did not find a comparison against `version.app_version`. The asset version and the pubspec version disagree. Provenance: **Implemented**.

No `.env` file is part of the documented tree. Supabase and PowerSync endpoints are compile-time strings in `lib/main.dart` and `supabase.dart`.

## CI and quality

`bitbucket-pipelines.yml`:

- Image: `maven:3.3.9` (a Maven image for a Flutter repo)
- Clone depth: full
- Branches: `{main}` only
- One step: `sonarsource/sonarqube-scan:2.0.1` with `SONAR_HOST_URL` and `SONAR_TOKEN`
- Cache: `~/.sonar`

`sonar-project.properties`:

- `sonar.projectKey=overdrive_datafy_app_9e753313-caf8-4d55-b614-caad76ec84e1`
- `sonar.host.url=https://scan.appmonitor.co.za`

There is no Flutter test step, no build step, and no deploy step in the pipeline.

`analysis_options.yaml` exists. `flutter_lints` 4.0.0 is a dev dependency.

`test/widget_test.dart` pumps `MyApp` and has no expectations. It is the Flutter counter-test comment left in place.

## Security notes (observed, not a pentest)

- The Supabase anon key is committed in source. It is a client key by Supabase's model. It is still a credential in git. The value is omitted here.
- Routes do not require auth via `requireAuth`.
- Android release uses the debug signing config.
- `.dart_tool/` artefacts are tracked, including a compiled `main.dart.js` under a flutter build hash directory.
- `key.properties` is read if present and is not described as a committed file. Do not add it.

## Development process visible in the repo

Five commits from 2024-04-22 to 2026-01-19, all on `main`, authored by Jaco Brits for the tip commit. The first commit is a 50-line `.gitignore`. Application code arrives in `5d266b4` ("alpha deploy") and `dba98cc` ("latest update"). There is no CONTRIBUTING file, no Fastlane, and no documented local runbook beyond the Flutter README sentence.

`.gitignore` is still the generic starter (Node, Java, Python, Maven, IDE). It does not ignore `.dart_tool/`, `build/`, or Flutter ephemera. That matches the tracked `.dart_tool/` files.

## Monitoring

No application monitoring SDK (Sentry, Crashlytics, Firebase) was identified in the Android manifest or `pubspec.yaml` dependency names that were read. SonarQube is the CI scanner. Runtime monitoring is **Unknown**.

## Build artefacts in git

`git ls-files` returns paths under `.dart_tool/flutter_build/...` including `main.dart.js`, dill files, and stamp files. Those are compiler outputs. Whether they were committed on purpose is **Unknown / Requires Confirmation**.
