# Save Waist No Feedback Fix — Bugfix Design

## Overview

The Track Visceral Fat Screen's BlocListener uses an `_isLoading` flag to gate state processing. When the user saves a waist measurement, the BLoC emits `HealthDataLoading` → `HealthDataOperationSuccess` → (then `HealthDataLoading` again from the `LoadRecentEntriesEvent` refresh). The first `HealthDataLoading` state triggers the listener, which resets `_isLoading` to `false`. When `HealthDataOperationSuccess` subsequently arrives, the `if (_isLoading)` guard is already `false`, so the success handler (snackbar + navigation) is never reached.

The fix replaces the fragile `_isLoading` gate with a pattern that directly checks the state type and filters by `operationType`, consistent with the approach used on the add_mood_screen and similar to the pattern on add_weight_screen.

## Glossary

- **Bug_Condition (C)**: The condition that triggers the bug — `_isLoading` is reset to `false` by an intermediate `HealthDataLoading` state before `HealthDataOperationSuccess` arrives in the BlocListener
- **Property (P)**: The desired behavior — when the BLoC emits `HealthDataOperationSuccess` with `operationType == 'add_waist'`, the listener SHALL show a success snackbar and navigate back
- **Preservation**: Existing behaviors that must remain unchanged — validation errors, duplicate detection, height/gender profile saves, and other screen BlocListeners
- **`_isLoading`**: A boolean flag in `_TrackVisceralFatScreenState` that gates whether the BlocListener processes incoming states
- **`HealthDataOperationSuccess`**: A BLoC state emitted after a successful database write, carrying a `message` and `operationType`
- **`HealthDataLoading`**: An intermediate BLoC state emitted at the start of every database operation, carrying an optional `message`
- **`AddWaistEntryEvent`**: The BLoC event dispatched when the user submits a valid waist form
- **`LoadRecentEntriesEvent`**: The BLoC event dispatched after a successful add to refresh dashboard data — this triggers a second `HealthDataLoading` emission

## Bug Details

### Bug Condition

The bug manifests when a user submits a valid waist measurement on the Track Visceral Fat Screen. The BlocListener's `_isLoading` guard is consumed (set to `false`) by the intermediate `HealthDataLoading` state that the BLoC emits before emitting `HealthDataOperationSuccess`. By the time the success state arrives, `_isLoading` is already `false` and the listener short-circuits.

**Formal Specification:**
```
FUNCTION isBugCondition(input)
  INPUT: input of type {event: HealthDataEvent, stateSequence: List<HealthDataState>}
  OUTPUT: boolean

  RETURN input.event IS AddWaistEntryEvent
         AND input.stateSequence CONTAINS HealthDataLoading BEFORE HealthDataOperationSuccess
         AND listenerGuard._isLoading IS SET TO false BY HealthDataLoading
         AND HealthDataOperationSuccess IS SUBSEQUENTLY IGNORED
END FUNCTION
```

### Examples

- **Normal save flow**: User enters 80 cm, presses "Save Waist Measurement". BLoC emits `HealthDataLoading("Adding waist entry...")` → listener fires, sets `_isLoading = false` → BLoC emits `HealthDataOperationSuccess("add_waist")` → listener fires but `_isLoading` is now `false` → snackbar and navigation are skipped.
- **Double-press reveals the bug**: User presses save again → BLoC detects duplicate → emits `HealthDataError("Duplicate waist entry detected")` → this confirms data was actually saved on the first press.
- **Other screens work correctly**: `add_weight_screen.dart` resets `_isLoading` on every state change unconditionally, so `HealthDataOperationSuccess` is still processed after the guard reset. `add_mood_screen.dart` uses no `_isLoading` guard at all — it checks `state is HealthDataOperationSuccess && state.operationType == 'add_mood'` directly.

## Expected Behavior

### Preservation Requirements

**Unchanged Behaviors:**
- Client-side validation of waist measurement values (range, required field) must continue to prevent invalid submissions
- The `HealthDataBloc._onAddWaistEntry` handler logic (validation, duplicate check, insert, emit success, refresh) must remain untouched
- Height and gender profile prompt saves (which use `ScaffoldMessenger` directly, not via BlocListener) must continue showing their own snackbars without navigating away
- Other screens (add_weight, add_blood_pressure, add_mood) must continue showing success feedback and navigating correctly
- The submit button must continue to be disabled while a submission is in-flight
- The `HealthDataError` state must continue to show an error snackbar and keep the user on the screen

**Scope:**
All inputs that do NOT involve the BlocListener success/error handling path for waist entry submission should be completely unaffected by this fix. This includes:
- Form validation logic
- Unit selection and conversion
- Date/time picker behavior
- Notes field behavior
- Profile prompt save flows (height, gender)
- Visual styling and layout

## Hypothesized Root Cause

Based on the code analysis, the root cause is confirmed:

1. **Incorrect listener guard pattern**: The `BlocListener` in `track_visceral_fat_screen.dart` wraps all state processing inside `if (_isLoading) { setState(() { _isLoading = false; }); ... }`. This means the **first** state emitted after form submission consumes the flag, regardless of whether that state is the success/error state or an intermediate loading state.

2. **BLoC emits HealthDataLoading before HealthDataOperationSuccess**: In `_onAddWaistEntry`, the handler calls `emit(const HealthDataLoading(message: 'Adding waist entry...'))` before the database insert and `emit(const HealthDataOperationSuccess(...))` after. The BlocListener receives `HealthDataLoading` first, enters the `if (_isLoading)` block, resets the flag to `false`, finds the state is neither `HealthDataOperationSuccess` nor `HealthDataError`, and does nothing meaningful.

3. **Pattern inconsistency across screens**: The `add_weight_screen.dart` avoids this bug by resetting `_isLoading` unconditionally (not gated inside `if (_isLoading)`) and then checking state type. The `add_mood_screen.dart` avoids it by not using an `_isLoading` guard at all — it filters on `state.operationType`. The visceral fat screen is the only screen that gates the entire handler on `_isLoading`.

## Correctness Properties

Property 1: Bug Condition - Success Feedback After Waist Save

_For any_ valid waist measurement submission where the BLoC emits `HealthDataOperationSuccess` with `operationType == 'add_waist'`, the BlocListener SHALL display a success snackbar with "Waist measurement saved successfully" and navigate back to the previous screen, regardless of any intermediate `HealthDataLoading` states emitted before the success state.

**Validates: Requirements 2.1, 2.2, 2.3**

Property 2: Preservation - Non-Waist-Success State Handling

_For any_ BLoC state transition that is NOT `HealthDataOperationSuccess` with `operationType == 'add_waist'` (including `HealthDataError`, `HealthDataLoading`, `HealthDataLoaded`, profile saves, and other operation types), the fixed BlocListener SHALL produce the same behavior as before — specifically, errors still show error snackbars without navigation, and non-waist states do not trigger waist-specific success feedback.

**Validates: Requirements 3.1, 3.2, 3.3, 3.4, 3.5**

## Fix Implementation

### Changes Required

**File**: `lib/screens/track_visceral_fat_screen.dart`

**Function**: `BlocListener.listener` callback (lines ~236-252)

**Specific Changes**:

1. **Remove `_isLoading` gate around state processing**: The listener should not wrap its logic inside `if (_isLoading)`. Instead, it should check the state type directly.

2. **Filter success by `operationType`**: Check `state is HealthDataOperationSuccess && state.operationType == 'add_waist'` to avoid reacting to success states from profile saves or other operations.

3. **Reset `_isLoading` on terminal states only**: Set `_isLoading = false` when the state is `HealthDataOperationSuccess` (with matching operationType) or `HealthDataError`, not on `HealthDataLoading`.

4. **Preserve error handling**: Keep the existing `HealthDataError` handling (show error snackbar, stay on screen) but move it outside the `_isLoading` gate as well, or guard it with a `_isLoading` check that is only reset by terminal states.

5. **Keep submit button disabled during loading**: The `_isLoading` flag should still be set to `true` in `_submitForm()` to disable the button, but the BlocListener should only reset it on terminal states.

**Proposed listener replacement:**
```dart
listener: (context, state) {
  if (state is HealthDataOperationSuccess &&
      state.operationType == 'add_waist') {
    setState(() {
      _isLoading = false;
    });
    ScaffoldMessenger.of(context).showSnackBar(
      const SnackBar(
        content: Text('Waist measurement saved successfully'),
        backgroundColor: Colors.green,
      ),
    );
    Navigator.of(context).pop(true);
  } else if (state is HealthDataError && _isLoading) {
    setState(() {
      _isLoading = false;
    });
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(
        content: Text(ErrorHandler.getErrorMessage(state.message)),
        backgroundColor: Colors.red,
      ),
    );
  }
},
```

This matches the pattern from `add_mood_screen.dart` which is the cleanest implementation in the codebase.

## Testing Strategy

### Validation Approach

The testing strategy follows a two-phase approach: first, surface counterexamples that demonstrate the bug on unfixed code, then verify the fix works correctly and preserves existing behavior.

### Exploratory Bug Condition Checking

**Goal**: Surface counterexamples that demonstrate the bug BEFORE implementing the fix. Confirm or refute the root cause analysis. If we refute, we will need to re-hypothesize.

**Test Plan**: Write widget tests that simulate submitting a valid waist measurement and assert that the success snackbar appears and navigation occurs. Run these tests on the UNFIXED code to observe failures and confirm the race condition.

**Test Cases**:
1. **Success snackbar test**: Submit valid waist → mock BLoC to emit `HealthDataLoading` then `HealthDataOperationSuccess` → assert snackbar with "Waist measurement saved successfully" appears (will fail on unfixed code)
2. **Navigation test**: Submit valid waist → mock BLoC to emit loading then success → assert `Navigator.pop` is called (will fail on unfixed code)
3. **Intermediate state race**: Submit valid waist → emit `HealthDataLoading` → verify `_isLoading` is consumed → emit `HealthDataOperationSuccess` → assert success handler is NOT reached (demonstrates the bug)

**Expected Counterexamples**:
- Success snackbar never appears because the listener resets `_isLoading` on the intermediate `HealthDataLoading` state
- `Navigator.pop` is never called for the same reason
- Possible causes confirmed: `_isLoading` gate pattern consuming the flag on non-terminal states

### Fix Checking

**Goal**: Verify that for all inputs where the bug condition holds, the fixed function produces the expected behavior.

**Pseudocode:**
```
FOR ALL input WHERE isBugCondition(input) DO
  result := fixedBlocListener(input.stateSequence)
  ASSERT snackbarShown(result, "Waist measurement saved successfully")
  ASSERT navigatedBack(result)
  ASSERT _isLoading == false AFTER result
END FOR
```

### Preservation Checking

**Goal**: Verify that for all inputs where the bug condition does NOT hold, the fixed function produces the same result as the original function.

**Pseudocode:**
```
FOR ALL input WHERE NOT isBugCondition(input) DO
  ASSERT originalBlocListener(input) = fixedBlocListener(input)
END FOR
```

**Testing Approach**: Property-based testing is recommended for preservation checking because:
- It generates many combinations of BLoC state sequences to verify no regressions
- It catches edge cases like rapid successive state emissions
- It provides strong guarantees that non-waist-save states are unaffected

**Test Plan**: Observe behavior on UNFIXED code first for error handling, profile saves, and validation, then write property-based tests capturing that behavior.

**Test Cases**:
1. **Error handling preservation**: Submit invalid waist → BLoC emits `HealthDataError` → verify error snackbar shown, user stays on screen (works on both unfixed and fixed code)
2. **Duplicate detection preservation**: Submit duplicate waist → BLoC emits `HealthDataError("Duplicate...")` → verify error snackbar shown (works on both unfixed and fixed code)
3. **Profile save preservation**: Save height via prompt → verify height-specific snackbar shows without navigation (works on both unfixed and fixed code)
4. **Button disabled during loading**: Submit valid waist → verify button is disabled while `_isLoading` is true (works on both unfixed and fixed code)

### Unit Tests

- Test that `_submitForm` sets `_isLoading` to `true` and dispatches `AddWaistEntryEvent`
- Test that `HealthDataOperationSuccess` with `operationType == 'add_waist'` triggers snackbar and navigation
- Test that `HealthDataOperationSuccess` with `operationType != 'add_waist'` (e.g., `'save_profile'`) does NOT trigger waist success handling
- Test that `HealthDataError` when `_isLoading` is true shows error snackbar and resets flag
- Test that `HealthDataLoading` state does NOT reset `_isLoading` or trigger any user-visible action

### Property-Based Tests

- Generate random sequences of BLoC states after form submission and verify that only `HealthDataOperationSuccess` with `operationType == 'add_waist'` triggers success feedback
- Generate random non-waist operation types and verify they do not trigger waist success handling
- Generate random state sequences without `HealthDataOperationSuccess('add_waist')` and verify no navigation occurs

### Integration Tests

- Full flow: enter valid waist → press save → verify snackbar appears → verify screen pops
- Full flow: enter valid waist → press save → BLoC returns error → verify error snackbar → verify screen remains
- Full flow: enter waist → save height via prompt → verify height snackbar → verify still on screen → then save waist → verify waist snackbar → verify navigation
