# Mobile lead Notes save preserves untouched fields

**Status: fixed and verified locally.** The known score 50 → displayed 0 defect was still present in the current source. It is now repaired in the active mobile lead editor. No deployment is required to consider this local implementation task complete.

## Cause and repair

The Leads route uses `LeadFormScreen` with `BaseModel<Map<String, dynamic>>` and `DataProvider`; the separate legacy `LeadModel`/`LeadsProvider` is not on this edit path. The form constructed a fresh map of visible fields and called `saveData`, which sent it to `/repository/update/:id` as the replacement `data` object. That omitted `score`, custom fields, assignment, tags, and other properties that the form never showed. It also sent default values for untouched visible controls.

The before-change regression reproduced that exact request. Saving only “Maya prefers linen samples” removed the fixture's score 50; reading an absent score with the display's zero fallback produced 0. [Before regression](assets/mobile-lead-preservation/before-test.txt).

[LeadFormScreen](/Users/imzee/projects/appmint_go/appmint_mobile/lib/screens/leads/lead_form_screen.dart:31) now snapshots the normalized values initially displayed by its controls. On an existing lead, it compares the current controls with that snapshot and sends only changed fields. [DataProvider.updateDataFields](/Users/imzee/projects/appmint_go/appmint_mobile/lib/providers/data_provider.dart:113) puts those fields under their `data.*` paths and uses the existing repository partial-update method. A Notes-only edit sends:

```json
{"data.notes":"Maya prefers linen samples"}
```

The destination is `/repository/update-partial/lead/<existing-id>`. The backend controller and Mongo provider were read to verify this exact contract: flat dotted paths are applied through `$set`, with no automatic `data.` prefix. The existing mobile repository method passes this map through unchanged. No backend modification was needed.

This preserves both hidden metadata and untouched visible fields changed elsewhere while the form is open. Saving an unchanged form performs no write. Clearing Notes sends an explicit empty string; clearing an existing Deal Value sends null for that field. An actual name change also updates the derived full name. Existing new-lead behavior is outside this repair and was left unchanged. Two existing analyzer warnings in the touched files were also removed without changing behavior.

## Exact verification

Added [lead_form_preservation_test.dart](/Users/imzee/projects/appmint_go/appmint_mobile/test/lead_form_preservation_test.dart:1). Eight Flutter widget regressions pass:

1. Notes-only save preserves score 50 and the complete untouched data object, including nested custom fields, tags, owner assignment, the original display name, and missing/defaulted properties. The refreshed provider reads the same saved data.
2. Concurrent score 87, company, and source changes survive a later Notes save from an older form snapshot.
3. Clearing Notes patches only Notes to an empty string.
4. Changing name and Deal Value patches those changes while preserving hidden fields.
5. Clearing Deal Value patches only that field to null.
6. An unchanged form makes no write and introduces no defaults.
7. A failed save keeps the entered Notes and editor open, shows the server error, and preserves stored score and Notes.
8. Empty required email blocks submission.

The tests execute the real `LeadFormScreen → DataProvider → RepositoryService → AppengineHttpClient` path, including JSON serialization, and decode server fixtures through the real BaseModel. Only HTTP is replaced with `package:http`'s zone-scoped `MockClient`. Its synthetic repository applies the source-verified replacement/partial-update semantics and rejects unexpected requests. It uses a dummy token and synthetic organisation solely to satisfy the actual client's header checks. No borrowed account, running backend, external application call, or real lead is involved.

[After test output](assets/mobile-lead-preservation/after-test.txt): **8 passed**. [Targeted Dart analysis](assets/mobile-lead-preservation/analyze.txt): **No issues found**. `git diff --check` passes for the edited source. Flutter 3.41.2 / Dart 3.11.0 ran the checks in a temporary copy using already-installed dependencies, avoiding build writes in the shared application checkout.

Commands from the prepared temporary copy:

```sh
flutter test --no-pub --reporter expanded test/lead_form_preservation_test.dart
dart analyze lib/providers/data_provider.dart lib/screens/leads/lead_form_screen.dart test/lead_form_preservation_test.dart
```

[Copy preparation script](assets/mobile-lead-preservation/prepare-isolated-test.py) recreates that isolated environment from the current checkout. [Patch including regression test](assets/mobile-lead-preservation/appmint-mobile.patch), [source hashes](assets/mobile-lead-preservation/source-sha256.json).

## Boundaries

This is a completed local source/widget/service repair. It does not claim a new Android/iOS device run, real backend persistence test, or marketplace distribution change. The test's server storage is an in-memory fixture, while the submitted request is produced by actual application code. Shared auth, environment, API-service, and other existing edits were preserved. Course content, central indexes, and pending-review files were not edited.
