docs(07-13): designer-delivery-6 EXECUTION-LOG.md — v4.4 update

Appends a 'v4.4 reshoot follow-up' section to the v4.3 EXECUTION-LOG
documenting the dial-caption gap closure that landed today
(2026-05-17, same-day post-v4.3).

Goal A: 1 atomic commit (dffcc50) adding the detector's second
broadcast emitter (syntheticStatusStream), Status field cloning from
the most-recent platform frame, connection_state.dart StreamGroup.merge
wiring, and 2 new widget test cases (1 detector unit + 1 dial widget
regression gate).

Goal B: v4.4 reshoot of 02d-home-dial-error.png via the REAL bogus-IP
path (same trigger as v4.3). Dial caption now transitions to
'Tap to retry' with red glow ring alongside the ErrorBanner. uiautomator
+ visual PNG Read confirm BOTH halves rendering as the UX team's letter
requested.

Bundle: /tmp/prowler-phase07-ux-team-v4.4.zip
  sha256 9a10b94034ae9090300914ea09afdbd58ef9134a35b96cbb29b888707d7adeef
APK: 972f26a62d3904a1546a1166dbf49b5ba21dd5436b00bd3f9892f01e7fe6a2d1
Ping: /tmp/prowler-phase07-ux-team-v4.4-ping.md

Test coverage: 55 → 57 widget tests (+1 detector unit + 1 dial
regression gate). 1 integration test from v4.3 carries over.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-17 21:35:58 +02:00
parent f67e656365
commit ef2e1baae5

View File

@@ -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<pigeon.Status>` (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.