diff --git a/prowler-client/android/.planning/phases/07-design-system-polish/designer-delivery-6-ux-team-v4.2-reshoot/EXECUTION-LOG.md b/prowler-client/android/.planning/phases/07-design-system-polish/designer-delivery-6-ux-team-v4.2-reshoot/EXECUTION-LOG.md index 6bc5e4d..7fc87b8 100644 --- a/prowler-client/android/.planning/phases/07-design-system-polish/designer-delivery-6-ux-team-v4.2-reshoot/EXECUTION-LOG.md +++ b/prowler-client/android/.planning/phases/07-design-system-polish/designer-delivery-6-ux-team-v4.2-reshoot/EXECUTION-LOG.md @@ -316,3 +316,119 @@ reshoot brief. `--allow-empty` flag IF needed. 2. **`docs(07-13): designer-delivery-6 EXECUTION-LOG.md — v4.3 update`** — appends this section to the v4.2 EXECUTION-LOG. + +## v4.4 reshoot follow-up (2026-05-17, same-day post-v4.3) + +After v4.3 shipped, the user pointed out the dial-caption gap: the +detector fired the ErrorBanner correctly from the real bogus-IP path, +but the dial caption stayed `Tap to prowl` instead of transitioning to +`Tap to retry`. The UX team's letter explicitly asked for BOTH halves +("surface the dialError state — red-tinted glow ring, 'Tap to retry' +caption, AND below the dial, surface an ErrorBanner"). v4.3 only +delivered the banner half. + +### Goal A — single atomic commit closing the gap (`dffcc50`) + +`feat(07-13): detector also feeds synthetic Status(error) into +connection_state (close dial-caption gap)` (~15 LOC of production +code + ~85 LOC of test): + +1. **Detector second emitter** — + `flutter/lib/src/state/connect_failure_detector.dart` now exposes a + second static broadcast `StreamController` (and getter + `syntheticStatusStream`) alongside the existing + `outputController`/`outputStream` for `ConnectError`. When + `_consumeLine` trips the threshold, it pushes BOTH the synthetic + `ConnectError(connectFailed)` (existing path, unchanged) AND a + synthetic `Status(state: error)` (new path) via the new emitter. + +2. **Status field cloning** — the detector also snapshots the most-recent + observed platform `Status` (via `_resetOnTransition`, which already + subscribes to `pigeon.onStatusChanged()` for window reset) so the + synthesised error frame clones `activeMode`/`exitCode`/`exitCountry`/ + `latencyMs` from the most-recent platform frame. This keeps the + dial-meta chips (LAYER / EXIT / latency) from blanking out on the + error transition. + +3. **Status stream merge** — + `flutter/lib/src/state/connection_state.dart` now imports + `package:async/async.dart` for `StreamGroup` and the detector + provider, calls `ref.watch(connectFailureDetectorProvider)` for the + lifecycle anchor, and replaces the direct + `pigeon.onStatusChanged().transform(errorLatchTransformer())` with + `StreamGroup.merge([platformStream, syntheticStatusStream]) + .transform(errorLatchTransformer())`. The existing latch transformer + stays in series (applied AFTER the merge) so the synthetic error + frame isn't clobbered by any trailing platform `idle` + (defense-in-depth intact). + +4. **+2 widget test cases (55 → 57)** — + - `connect_failure_detector_test.dart` augmented "fires on threshold" + case to also assert the synthetic Status emit, plus a new test + "synthesised Status clones non-state fields from the most recently + observed platform Status" that verifies the field-cloning behavior + end-to-end. + - `connect_dial_test.dart` adds "v4.4 dial-caption gap closure" + REGRESSION GATE: drives the production-shape merged stream + end-to-end (real `StreamGroup.merge` + real + `errorLatchTransformer` + real + `ConnectFailureDetector.syntheticStatusStream`) and asserts the + dial caption transitions to `Tap to retry` when the detector + trips. If either the StreamGroup.merge call in + connection_state.dart OR the `_statusEmitter.add` call in + connect_failure_detector.dart is removed/regressed, this test + fires. + +### Test coverage delta + +- Widget tests: 55 → 57 (+1 detector unit + 1 dial widget regression gate) +- Integration tests: unchanged (1 from v4.3 carries over) +- All 57 passing on `flutter test test/widget/`. +- `flutter analyze` clean on the new code (the 9 pre-existing infos/ + warnings are all in `integration_test/round*.dart` — unrelated to + this task). + +### Goal B — v4.4 reshoot + +| Step | Result | +| ------------------------------------------ | --------------------------------------------------------------------------------- | +| Rebuild APK (multi-ABI) | `972f26a62d3904a1546a1166dbf49b5ba21dd5436b00bd3f9892f01e7fe6a2d1` (230 MB) | +| Install on emulator-5554 (headless AVD) | Streamed install OK after launching AVD with `-no-window -gpu swiftshader_indirect` (SystemUI ANR dismissed via uiautomator-dumped `Wait` button tap; pre-existing for headless AVD) | +| Write bogus-IP override | `{"l1":{"server_addr":"203.0.113.99:443"}}` (RFC 5737 documentation IP) | +| Launch + verify baseline | Pre-tap dump confirms `Tap to prowl` content-desc on the dial | +| Tap dial (540, 1008) | ProwlerVpnService: state=connecting → state=connected (Kotlin fast-path) | +| Wait ~95s for xray retry-loop accumulation | xray-core logcat shows sustained `dialing TCP to tcp:203.0.113.99:443` retries | +| Screencap + uiautomator dump | UI dump confirms BOTH `content-desc="...Tap to retry. Tap to retry"` on the dial node AND ErrorBanner heading `Connection failed.` | +| Visual confirm (Read PNG) | dial caption `Tap to retry` + red glow ring around cat + `tunnel dropped` dial-meta + ErrorBanner `Connection failed.` / `We tried 3 times. Switch layer or check your network.` / [Retry, Switch layer] all visible in single frame | + +### Pixel-spot-check v4.3 baseline vs v4.4 (per memory `feedback_validate_artifacts_before_human_handoff`) + +- v4.3 `02d-home-dial-error.png` sha256: `bc4b8350204d1e119fdf0abc1cff9ac307738aa41cefc072c9fe55530243ce1e` +- v4.4 `02d-home-dial-error.png` sha256: `f6a7097fe0b4f75ca05867a63509063074e3fb5bf29fc1f25421a8fd6c155c16` +- Visual delta confirmed via PNG Read: dial caption changes from "Tap to + prowl" (greyed cat, no red glow) → "Tap to retry" (red-tinted glow ring, + dial-meta "tunnel dropped"). Both bundles still show the ErrorBanner. + +### Bundle artifact paths (v4.4) + +- `/tmp/prowler-phase07-ux-team-v4.4.zip` — 16 MB, + sha256 `9a10b94034ae9090300914ea09afdbd58ef9134a35b96cbb29b888707d7adeef` + (21 PNGs + README.md, dumps excluded per bundle convention) +- `/tmp/uat-07-ux-v4.4/` — capture staging dir (20 PNGs carryover from + v4.3 + 1 NEW `02d-home-dial-error.png` + `_dumps/` for forensics) +- `/tmp/uat-07-ux-v4.4/README.md` — full per-item closure status (B-3 + promoted to FULL with dial-caption gap closure delta documented at the + top, UNTRIGGERED rationale for B-4 unchanged) +- `/tmp/prowler-phase07-ux-team-v4.4-ping.md` — operator handoff + message body for the unconditional sign-off ask + +### Two final commits for this v4.4 follow-up + +1. **`feat(07-13): detector also feeds synthetic Status(error) into + connection_state (close dial-caption gap)`** (`dffcc50`) — single + atomic Goal A commit (production + tests). +2. **`chore(07-13): v4.4 reshoot bundle — dial-caption gap closed (B-3 + widening FULL)`** (`f67e656`) — bundle metadata commit (empty repo + delta; bundle lives in /tmp/). +3. **`docs(07-13): designer-delivery-6 EXECUTION-LOG.md — v4.4 update`** — + appends this section to the v4.2/v4.3 EXECUTION-LOG.