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:
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user