# Technical documentation — intersect_cloud (Simpleflow Hub)

Evidence date: 2026-10-12. Source: `git@bitbucket.org:overdrive/intersect_cloud.git`, `main` at `6efd925` (2025-01-22). `prod` is called out where it differs.

## Stack

| Item | Value | Evidence |
|---|---|---|
| Language | Dart | `lib/` |
| UI | Flutter, FlutterFlow-generated widgets | README, custom-action file headers |
| SDK constraint | `>=3.0.0 <4.0.0` | `pubspec.yaml` |
| Flutter revision recorded by the tool | `4d7946a68d26794349189cf21b3f68cc6fe61dcb`, channel stable | `.metadata` |
| Pipeline Flutter | Download `flutter_linux_3.10.0-stable`, then `flutter channel stable` and `flutter upgrade` | `bitbucket-pipelines.yml` |
| Package name | `simpleflow_hub` | `pubspec.yaml` |
| Version | `1.0.0+1` | `pubspec.yaml` |
| Router | `go_router` 12.1.3 | `pubspec.yaml`, `lib/flutter_flow/nav/nav.dart` |
| State | `provider` 6.1.2 around `FFAppState` | `lib/main.dart`, `lib/app_state.dart` |
| Backend client | `supabase_flutter` 2.6.0, plus `gotrue` 2.8.4, `postgrest` 2.1.4, `storage_client` 2.0.3, `realtime_client` 2.2.1, `functions_client` 2.3.2 | `pubspec.yaml` |
| Local persistence of session fields | `flutter_secure_storage` 9.2.2 | `app_state.dart` |

`flutter_riverpod`, `hive`, and `sqflite` are in `pubspec.yaml`. No file under `lib/` imports them. Treat them as unused direct dependencies until a caller is found.

`analysis_options.yaml` exists. The active pipeline does not run the analyzer. `bitbucket-pipelines copy 2.yml` does (`flutter analyze`) and is not the file Bitbucket runs.

## Application structure

```text
lib/
  main.dart                 app start, Supabase init, bottom nav
  index.dart                exports of routed pages only
  app_state.dart            FFAppState
  environment_values.dart   loads assets/environment_values/environment.json
  auth/supabase_auth/       email sign-in, reset, user stream
  backend/supabase/         generated tables, storage helpers
  backend/api_requests/     hand-written PostgREST and RPC calls
  flutter_flow/             theme, nav, utilities
  pages/                    screens
  forms/                    additional form widgets (job card variants)
  components/               shared widgets
  custom_code/              actions and widgets written outside the generator
android/  ios/  web/  windows/
assets/environment_values/environment.json
build/                      committed build output, including build/web
```

Routed pages are the exports in `lib/index.dart`. `nav.dart` registers the same set as child routes of `/`.

Page areas that are not those exports include `pages/user_management/`, `pages/authentication/`, `pages/tasks/`, and the Process Hub copies `process_hub_v1`, `process_hub_base`, `process_hub_pest_elite`, and `process_hub_plateau`. Imports of those hub and `user_management` paths were only found inside the same folders.

## Backend

### URL and key

`FFDevEnvironmentValues.currentEnvironment` is the constant `demoQA`. `initialize()` reads `assets/environment_values/environment.json` and sets `SupabaseAPIURL` and `SupabaseAnonKey`.

On `main` the URL value is `https://alb.simpleflowhub.app`. The anon key is a JWT in that JSON file. It is not copied into this document.

`SupaFlow.initialize` passes that URL and key to `Supabase.initialize` with `debug: false` and `authFlowType: AuthFlowType.implicit`.

On `prod` (`f72dda9`) the URL and anon key are string literals in `lib/backend/supabase/supabase.dart`. That commit has no `environment_values.dart`. `Supabase.initialize` there does not set `authFlowType`. The URL literal is the same host, `https://alb.simpleflowhub.app`.

### Generated tables

111 Dart files under `lib/backend/supabase/database/tables/`. Each exposes `tableName`. Examples:

| Area | Tables (file names) |
|---|---|
| People and tenants | `user`, `user_view`, `user_role_link`, `user_process`, `user_notification`, `role`, `role_menu_link`, `permission`, `tenant`, `tenant_user_link`, `tenant_user_list`, `tenant_process`, `tenant_client`, `tenant_client_link`, `tenant_grouped_view`, `tenant_grouped_view_all`, `service_user`, `old_client` |
| Process | `process`, `process_step`, `process_instance`, `process_action` |
| Work | `task`, `task_comments`, `task_duplicate`, `job_card`, `support_ticket`, `support_ticket_files`, `support_ticket_assigned_users`, `support_comments`, `report` |
| Pest Elite | `pest_elite_siteform`, `pest_elite_sitemap`, `pest_elite_service_report`, `pest_elite_action_report`, `pest_elite_recommendation_report`, checklist and trap tables for rodent, cockroach, flies, SPI |
| Plateau | `plateau_service_report`, `plateau_service_report_areas_treated`, `plateau_non_conformance_report`, `plateau_corrective_action_report` |
| Mail | `send_que` |
| Other | `test`, `test_tenant`, `pdf_test`, `total_sum` |

`update_app_colors_post_login.dart` queries `.from('user_profiles')`. There is no `user_profiles` table class in the generated folder. Login uses `UserTable` (`tableName` `user`) and `TenantUserListTable`.

SQL, views, and RLS are not in this repository.

### RPC and REST calls

`lib/backend/api_requests/api_calls.dart`, group `SupabaseAPICallsGroup`. Base URL is the environment URL. Calls include both table REST paths and these RPCs:

| Call | Path |
|---|---|
| Job card sum | `rest/v1/rpc/calculate_total_job_card_new` |
| User tasks | `rest/v1/rpc/get_user_tasks` |
| User documents | `rest/v1/rpc/get_user_documents_details` |
| Document access | `rest/v1/rpc/insert_document_access` |
| Pest Elite traps | `insert_pest_elite_rodent_traps`, `insert_pest_elite_cockroach_traps`, `insert_pest_elite_flies_traps`, `insert_pest_elite_spi_traps` |
| Email | `rest/v1/rpc/send_custom_email` |
| Job card item sum | `rest/v1/rpc/calculate_sum_by_jobcard` |
| Current tenant | `rest/v1/rpc/update_current_tenant` |

Other calls in the same file hit PostgREST resources directly (`list_equipment`, job cards, users, tenants, clients, support tickets, documents). Headers send `apikey` as the anon key. `SendCustomEmailCall` also sends `Authorization`. The map literal sets `Authorization` twice; the later entry (bearer auth token) is the one Dart keeps.

Function bodies are not in this repo.

### Storage

`lib/backend/supabase/storage/storage.dart` uploads bytes to a caller-supplied bucket and returns the public URL. It can delete by parsing a public URL.

| Bucket | Used by |
|---|---|
| `uploads` | `upload_file_jpg.dart`, signature pad widgets |
| `pdfUploads` | `upload_file.dart` |
| `logos`, `files` | Public URLs hardcoded in dashboard, side nav, profile, notifications, and create-task screens, on host `alb.simpleflowhub.app` |

Bucket policies are not in this repo.

## Auth

| Piece | Behaviour |
|---|---|
| Sign in | `emailSignInFunc` → `signInWithPassword` |
| Sign up helper | `emailCreateAccountFunc` returns null when `lastSignInAt` is null (email not yet confirmed) |
| Reset | `resetPasswordForEmail` |
| OTP | `signInWithOtp`, `shouldCreateUser: false` |
| Flow | Implicit on `main` |
| Session fields | Secure storage keys `ff_currentTenantId`, `ff_systemRole`, `ff_tenantRole`, colour ints, `ff_selectedTenantName`, `ff_jobCards`, page title fields |
| Apple | `sign_in_with_apple` is a dependency. Only the `AuthManager` method is declared. No page calls it |
| Router | `requireAuth` true only on `SupportTicketList` |

## Custom code

Actions in `lib/custom_code/actions/`:

- PDF: `create_task_pdf`, `create_job_card_pdf`, `create_job_card_basic_pdf`, `create_building_inspection_pdf`, `create_equipment_inspection_pdf`, Pest Elite site / action / recommendation (two recommendation files), Plateau service / non-conformance / corrective action.
- HTML placeholders: task, support ticket, job card, inspection, booking.
- Files: `upload_file`, `upload_file_jpg`, `download_file`, `open_pdf`.
- Auth and theme: `get_otp`, `confirm_otp`, `check_auth`, `reset_password`, `update_supabase_password`, `on_login_success`, `update_app_colors_post_login`, `update_user_theme_color`.
- Other: `encrypt_string`, `decrypt_string`, `save_signature_action`, `subscribe`, `unsubscribe`, `clear_all_cache`, `update_form_fields`, `update_draggable_form_fields`, `new_custom_action`.

Widgets: `signature_pad`, `signature_pad2`, `signature_pad3`, `signature_pad_new`, `dynamic_form_builder`, `dynamic_draggable_form_builder`, `dynamic_theme_wrapper`, `base64_image_widget`.

`encrypt` is a dependency (`^5.0.3`). The encrypt and decrypt actions were not opened line by line. Do not assume a key-management design from the file names.

## Platforms

| Target | Present | Notes |
|---|---|---|
| Web | Yes | `web/index.html` title metadata "Simpleflow" / "Simpleflow Process Hub". `robots` `noindex`. This is the pipeline artifact |
| Android | Yes | `applicationId` `co.za.intersectcloud.simpleflowhub`. `minSdkVersion` 23. `compileSdkVersion` and `targetSdkVersion` 34. Release `signingConfig` is debug. Deep link `simpleflowhub` / `simpleflowhub.com` |
| iOS | Yes | Bundle id `co.za.intersectcloud.simpleflowhub`. Display name Simpleflow Hub. Same URL scheme. `ImageNotification` extension |
| Windows | Yes | `windows/` Flutter project. No pipeline step builds it |
| Linux, macOS | No | Directories absent |

`flutter_native_splash.yaml` sets splash colour `#E0E3E7` (dark `#262D34`) for Android, iOS, and web.

Launcher icons: `flutter_launcher_icons` in `pubspec.yaml`, image `assets/images/app_launcher_icon.png`, adaptive icon background `#00000000`.

## Deployment

Active file: `bitbucket-pipelines.yml`. Image `atlassian/default-image:3`. OIDC enabled on deploy steps.

| Branch | Deployment name | Role variable | What runs |
|---|---|---|---|
| `main` | `staging` | `$AWS_ARN` | S3 deploy of `./build/web`, then CloudFront invalidation `$DISTRIBUTION_ID` |
| `prod` | `production` | `$AWS_PROD_ARN` | Same |

The `&build` step (install Flutter 3.10.0, `flutter upgrade`, `flutter pub get`, `flutter build web --release`) is commented out in the branch lists. A push therefore publishes whatever `build/web` is already in git.

`commit.bat` is the local path that does compile: `flutter pub get`, `flutter build web`, `git add .`, commit, `git push origin`. It commits the whole worktree, including build output, which matches `.gitignore` leaving `/build/` commented out (`#/build/`).

`git ls-files build` on `main` returns 74 paths.

Region, bucket, distribution id, and role ARNs are Bitbucket deployment variables. Their values are not in git.

## Testing

`test/widget_test.dart` is the Flutter starter. It pumps `MyApp` and does not assert a counter. `MyApp` starts async work (environment load, Supabase) from `main()`, which the test does not call. No other test files are in `test/`.

No CI step runs tests. The unused pipeline copy runs `flutter analyze`.

## Security notes from the tree

These are observations, not a penetration test.

- The Supabase anon key is committed in `environment.json` on `main` and as a literal on `prod`. Anon keys are meant for clients; it is still a credential in git.
- `SendCustomEmailCall` and several list calls authorize with the anon key unless a call passes a user token. Whether RLS allows those calls is not visible here.
- Process Hub deletes `job_card` rows with null `status` without a tenant predicate in that call.
- Android release is signed with the debug keystore in the gradle file that is committed.
- `web/index.html` asks crawlers not to index the site.
- No `sonar-project.properties` in git, despite OVER-160 listing one on the scan host.

## Development process visible in git

Fetched `main` history is 16 commits from `232f179` (2024-11-06) through `6efd925` (2025-01-22). Subjects are short ("updates", "qa deploy", "pestelite deploy", "fixes update"). `232f179` still has parent `0dcad9d`, which was not fetched.

`prod` tip `f72dda9` (2024-10-24, author Dirk) has parent `b0e4416`, which was not fetched.

There is no documented branching model in the repo. The pipeline file is the only map of `main` versus `prod`.

## Related repositories

See `project-plan.md`. Short form: `intersect_cloud_prod` is the same package name pointed at `prod-alb.simpleflowhub.app`. `intersect_cloud_v2` is an empty gitignore. `intersect_lambda` is a different codebase.
