Baseline measured 2026-08-11. 11 pass, 4 fail, and the failing four are exactly the four this issue was filed about. No new breakage was hiding behind the familiar red.
Verified state
Full npm run test:e2e:mock on iPhone 17 Pro Max / iOS 26.1, against a Release build verified current by the #598 staleness guard. Maestro reported 4/15 Flows Failed.
Passing (11): launch, browse, bug6_bottom_bar_inset, codex_parity, settings_qr_scanner, server_drag_reorder, feat1_tree_drill_new_session, feat2_export_in_info_shelf, pty_turn_divider, voice_dictation, 07_conversation_scroll_gaps.
Failing (4):
| Flow |
Fails asserting |
Timeout |
session_lifecycle |
hub-screen visible |
8000 |
feedback_flow |
settings-help-feedback-row visible |
4000 |
05_chat_flow |
first-session-card visible |
10000 |
06_search_anchor |
conversation-row-conv-search-anchor visible |
8000 |
Every one dies on an assertConditionCommand for a visible element. None crashes, and none hits a driver fault. So these are testID or fixture problems rather than the XCUITest-infrastructure flake that has muddied past runs — the tractable kind.
Worth noting separately: session_lifecycle fails at hub-screen, which is setup. Its later steps have therefore never executed, so whatever else may be wrong with that flow is still unmeasured.
What this changes
This issue's premise was that red is the expected state, so new breakage is indistinguishable from the familiar four. That premise now has a measurement behind it: a named known-good set of 11 and a named failing 4, each with the exact assertion that fails.
Flows can now be fixed one at a time with the count as the signal.
Note on how this was measured
Two mistakes worth recording, because both produced confident wrong answers:
- The run was piped through
tail, so the reported process exit code was tail's 0 rather than Maestro's non-zero. That briefly looked like a suite passing with four failures — a broken CI gate that does not exist.
- The same
tail truncated the per-flow verdicts out of the log. The failing set was recovered from Maestro's screenshot-❌-…-(flow).png artifact filenames under e2e/_artifacts/debug/.
Do not pipe a suite run through tail when the per-flow result is the thing you need.
The mock Maestro suite passes 11 of 15 flows, and has done for long enough that red is the expected state.
That means new breakage is indistinguishable from the familiar four, so the suite cannot gate anything.
Verified state
test:e2e:mock in package.json runs 15 flows.
Four are known-failing for reasons documented in docs/e2e-remaining-work.md and docs/followups/mobile/06-mock-suite-remaining-failures.md: session_lifecycle, feedback_flow, 05_chat_flow and 06_search_anchor.
Depends on
This needs a build you can trust first — see the ensure-release-build.js staleness issue in this milestone. A pass set derived from a possibly-stale .app is a guess.
Done looks like
Either the four flows pass, or they are explicitly quarantined with a recorded reason, so that any red result is new information.
Fix the flows, not the app — and stop and report if a genuine product bug turns up rather than editing the flow around it.
Reference
docs/followups/repo-health/03-e2e-suite-signal.md and docs/followups/mobile/06-mock-suite-remaining-failures.md
feedback_flow — diagnosed, not yet fixed (2026-08-11)
The flow reaches Settings successfully; the failure screenshot shows the screen rendered, scrolled to the top. It then asserts settings-help-feedback-row visible and times out.
The element exists and is unconditional — app/settings.tsx:940, a plain TouchableOpacity with no gating, inside the screen's ScrollView (:440-954).
It is off-screen. The Appearance section between the top and it now includes a Language row, a Layout row, a Dark/Light toggle and a grid of nine theme cards. settings_qr_scanner passes on the same screen only because its target sits at :474, above the fold.
But scrolling does not reach it either. Tried scrollUntilVisible at timeout: 20000, speed: 80, visibilityPercentage: 60, centerElement: true. Maestro still reports "No visible element found".
That is the interesting part. The row is inside a real ScrollView and is not conditionally rendered, so a plain vertical scroll should find it. Candidate explanations, none yet confirmed:
- The swipe gesture is being captured by a horizontally-scrollable child — the Language selector and the theme grid both sit in the path.
- The
ScrollView is not receiving the gesture at the coordinates Maestro swipes from.
- A nested scroll container is consuming it.
If it is the first or third, this is an app defect rather than a flow defect — a user dragging from the middle of the Appearance section would fail to scroll too. That crosses the line the brief draws ("fix the flows, not the app, unless a genuine product bug turns up — then stop and report").
Worth checking by hand before writing more Maestro: open Settings on a simulator, drag upward starting over the theme grid, and see whether the screen scrolls. If it does not, the bug is in the app and this flow is only the messenger.
The other three failures have not been diagnosed yet.
Progress — 2026-08-11 (2 of 4 fixed)
feedback_flow — fixed, PR #642 (1ab1cadd). Two defects: the target row was below the fold (and scrollUntilVisible cannot find it even fully tuned, while two plain swipes can), and behind that, a tap on feedback-diagnostics-toggle was reported COMPLETED while landing on the keyboard and typing a stray V into the description.
session_lifecycle — fixed, PR #645. The baseline's "fails at hub-screen" was misleading: setup passes, and the flow dies on the final hub-screen assert after - back. Browse is a presentation: 'modal' screen, iOS modals dismiss by dragging down, and - back is a no-op that Maestro still reports COMPLETED. Replaced with a downward swipe.
06_search_anchor — diagnosed, not fixed
Typing "wombat" reaches the server — the mock logs 3 /api/search requests — and e2e/fixtures/search-results.json does contain conv-search-anchor. The endpoint and the fixture are both correct.
The testID is correct too: conversation-row-<id> is rendered by the Classic list (components/conversation/ConversationList.tsx:68) as well as Tree and Hub, so the repo's default sessionsLayout: 'classic' is not the problem.
What the failure screenshot actually shows is the sessions empty state — "No sessions" — with the query present in the input. So the results are being returned but rendered somewhere other than the pane on screen; the visible pane is sessions, and the match is a conversation.
Next step for whoever picks this up: determine which surface search results render into (a History/conversations pane rather than sessions), and whether the flow needs to switch to it before asserting. Do not lengthen the timeout — the data arrives, it is displayed elsewhere.
A measurement trap worth avoiding
A first attempt at this flow appeared to show the mock server crashing with Assertion failed: (advance > 0), function substitute, file process.c. That was not a repo bug: a leaked mock-server.js from an earlier run still held port 7071, the new instance died with EADDRINUSE, and the native assertion was a secondary artifact.
Run pkill -f mock-server.js and check lsof -ti:7071 before trusting a run.
05_chat_flow
Untouched. Still failing at first-session-card.
06_search_anchor — blocked on a product bug, not fixable as a flow
Traced to the end 2026-08-11. The flow cannot pass in the default configuration, because it asserts behaviour the app does not currently have.
app/index.tsx:510 is the only place server-search results reach a list, and it sits in the non-merged branch of the ternary at :462. stores/settings.ts:133 defaults mergeChats: true, so MergedClassicList renders instead and never receives convSearchData. The merged path filters locally over loaded items (:641-654), and conv-search-anchor is deliberately absent from the loaded set.
The request is made and discarded — the mock logged 3 GET /api/search hits during the failing run.
Filed as #646.
Three fixes were tried and rejected, recorded so nobody repeats them:
- Longer timeout — the data arrives, so waiting cannot help.
- Tap
hub-history-tab — that tab only exists when mergeChats is false; the tap fails outright.
- Add
conv-search-anchor to conversations.json — the app never calls GET /api/conversations, only /api/conversations/count, so the fixture is never read. Reverted.
Recommendation: leave this flow failing until #646 is decided. It is currently the tracker's most accurate signal — a correct assertion against a real gap. Making it green by forcing mergeChats: false would test a non-default configuration and hide the defect.
That puts the honest target for this issue at 13 passing, 2 failing, with both remaining failures explained: 06_search_anchor blocked on #646, 05_chat_flow still to be attempted.
Re-measured 2026-08-12 — 10 pass, 5 fail, and the earlier baseline was an overcount
Full npm run test:e2e:mock against 86eeb2a1 with a freshly built Release app, iPhone 17 Pro Max / iOS 26.1.
Verdicts read from Maestro's own [Passed]/[Failed] lines this time, not inferred.
Passing (10): launch, browse, server_drag_reorder, bug6_bottom_bar_inset, pty_turn_divider, feat1_tree_drill_new_session, settings_qr_scanner, feedback_flow, 05_chat_flow, 07_conversation_scroll_gaps.
Failing (5):
| Flow |
Fails asserting |
Tracked as |
session_lifecycle |
hub-screen visible |
#661 |
feat2_export_in_info_shelf |
"Resume Session" visible |
fixed in PR #658 |
codex_parity |
"Resume Session" visible |
fixed in PR #658 |
voice_dictation |
chat-mic-button visible |
#662 |
06_search_anchor |
search-match-nav visible |
#663 |
05_chat_flow now passes, fixed in #648.
feedback_flow stays fixed from #642.
The 2026-08-11 baseline above listed three of these as passing, and that was wrong
codex_parity, feat2_export_in_info_shelf and voice_dictation are recorded as passing in the 2026-08-11 section, and all three fail now.
That is not a regression.
The <Play weight="fill"/> icon whose accessibility text breaks the two Resume Session assertions was added on 2026-08-02 in #510, and is present at 1ab1cadd, the commit the 08-11 baseline was measured against.
The likelier explanation is the measurement itself.
That run was piped through tail, which truncated the per-flow verdicts, and the failing set was reconstructed from screenshot-❌-… artifact filenames.
That method observes only failures — the passing list was everything left over, so any flow failing without leaving a ❌ screenshot was silently counted as a pass.
Treat the 11/15 figure as unverified. 10/15 is measured directly.
Breakdown now tracked as individual issues
On #646, referenced in the section above
The hub half is fixed in PR #657: merged-mode search now sources conversations from the server, and 06_search_anchor gets past conversation-row-conv-search-anchor and opens the conversation.
#646 stays open for the remaining half — the client still re-filters server results on title and preview, so a conversation matched on message body is fetched and then discarded.
That half is specified in docs/prompts/fix-merged-search-client-refilter.md, and the shared e2e fixture cannot detect it.
Verified state
Full
npm run test:e2e:mockon iPhone 17 Pro Max / iOS 26.1, against a Release build verified current by the #598 staleness guard. Maestro reported4/15 Flows Failed.Passing (11):
launch,browse,bug6_bottom_bar_inset,codex_parity,settings_qr_scanner,server_drag_reorder,feat1_tree_drill_new_session,feat2_export_in_info_shelf,pty_turn_divider,voice_dictation,07_conversation_scroll_gaps.Failing (4):
session_lifecyclehub-screenvisiblefeedback_flowsettings-help-feedback-rowvisible05_chat_flowfirst-session-cardvisible06_search_anchorconversation-row-conv-search-anchorvisibleEvery one dies on an
assertConditionCommandfor avisibleelement. None crashes, and none hits a driver fault. So these are testID or fixture problems rather than the XCUITest-infrastructure flake that has muddied past runs — the tractable kind.Worth noting separately:
session_lifecyclefails athub-screen, which is setup. Its later steps have therefore never executed, so whatever else may be wrong with that flow is still unmeasured.What this changes
This issue's premise was that red is the expected state, so new breakage is indistinguishable from the familiar four. That premise now has a measurement behind it: a named known-good set of 11 and a named failing 4, each with the exact assertion that fails.
Flows can now be fixed one at a time with the count as the signal.
Note on how this was measured
Two mistakes worth recording, because both produced confident wrong answers:
tail, so the reported process exit code wastail's0rather than Maestro's non-zero. That briefly looked like a suite passing with four failures — a broken CI gate that does not exist.tailtruncated the per-flow verdicts out of the log. The failing set was recovered from Maestro'sscreenshot-❌-…-(flow).pngartifact filenames undere2e/_artifacts/debug/.Do not pipe a suite run through
tailwhen the per-flow result is the thing you need.The mock Maestro suite passes 11 of 15 flows, and has done for long enough that red is the expected state.
That means new breakage is indistinguishable from the familiar four, so the suite cannot gate anything.
Verified state
test:e2e:mockinpackage.jsonruns 15 flows.Four are known-failing for reasons documented in
docs/e2e-remaining-work.mdanddocs/followups/mobile/06-mock-suite-remaining-failures.md:session_lifecycle,feedback_flow,05_chat_flowand06_search_anchor.Depends on
This needs a build you can trust first — see the
ensure-release-build.jsstaleness issue in this milestone. A pass set derived from a possibly-stale.appis a guess.Done looks like
Either the four flows pass, or they are explicitly quarantined with a recorded reason, so that any red result is new information.
Fix the flows, not the app — and stop and report if a genuine product bug turns up rather than editing the flow around it.
Reference
docs/followups/repo-health/03-e2e-suite-signal.mdanddocs/followups/mobile/06-mock-suite-remaining-failures.mdfeedback_flow— diagnosed, not yet fixed (2026-08-11)The flow reaches Settings successfully; the failure screenshot shows the screen rendered, scrolled to the top. It then asserts
settings-help-feedback-rowvisible and times out.The element exists and is unconditional —
app/settings.tsx:940, a plainTouchableOpacitywith no gating, inside the screen'sScrollView(:440-954).It is off-screen. The Appearance section between the top and it now includes a Language row, a Layout row, a Dark/Light toggle and a grid of nine theme cards.
settings_qr_scannerpasses on the same screen only because its target sits at:474, above the fold.But scrolling does not reach it either. Tried
scrollUntilVisibleattimeout: 20000,speed: 80,visibilityPercentage: 60,centerElement: true. Maestro still reports "No visible element found".That is the interesting part. The row is inside a real
ScrollViewand is not conditionally rendered, so a plain vertical scroll should find it. Candidate explanations, none yet confirmed:ScrollViewis not receiving the gesture at the coordinates Maestro swipes from.If it is the first or third, this is an app defect rather than a flow defect — a user dragging from the middle of the Appearance section would fail to scroll too. That crosses the line the brief draws ("fix the flows, not the app, unless a genuine product bug turns up — then stop and report").
Worth checking by hand before writing more Maestro: open Settings on a simulator, drag upward starting over the theme grid, and see whether the screen scrolls. If it does not, the bug is in the app and this flow is only the messenger.
The other three failures have not been diagnosed yet.
Progress — 2026-08-11 (2 of 4 fixed)
feedback_flow— fixed, PR #642 (1ab1cadd). Two defects: the target row was below the fold (andscrollUntilVisiblecannot find it even fully tuned, while two plain swipes can), and behind that, a tap onfeedback-diagnostics-togglewas reported COMPLETED while landing on the keyboard and typing a strayVinto the description.session_lifecycle— fixed, PR #645. The baseline's "fails athub-screen" was misleading: setup passes, and the flow dies on the finalhub-screenassert after- back. Browse is apresentation: 'modal'screen, iOS modals dismiss by dragging down, and- backis a no-op that Maestro still reports COMPLETED. Replaced with a downward swipe.06_search_anchor— diagnosed, not fixedTyping "wombat" reaches the server — the mock logs 3
/api/searchrequests — ande2e/fixtures/search-results.jsondoes containconv-search-anchor. The endpoint and the fixture are both correct.The testID is correct too:
conversation-row-<id>is rendered by the Classic list (components/conversation/ConversationList.tsx:68) as well as Tree and Hub, so the repo's defaultsessionsLayout: 'classic'is not the problem.What the failure screenshot actually shows is the sessions empty state — "No sessions" — with the query present in the input. So the results are being returned but rendered somewhere other than the pane on screen; the visible pane is sessions, and the match is a conversation.
Next step for whoever picks this up: determine which surface search results render into (a History/conversations pane rather than sessions), and whether the flow needs to switch to it before asserting. Do not lengthen the timeout — the data arrives, it is displayed elsewhere.
A measurement trap worth avoiding
A first attempt at this flow appeared to show the mock server crashing with
Assertion failed: (advance > 0), function substitute, file process.c. That was not a repo bug: a leakedmock-server.jsfrom an earlier run still held port 7071, the new instance died withEADDRINUSE, and the native assertion was a secondary artifact.Run
pkill -f mock-server.jsand checklsof -ti:7071before trusting a run.05_chat_flowUntouched. Still failing at
first-session-card.06_search_anchor— blocked on a product bug, not fixable as a flowTraced to the end 2026-08-11. The flow cannot pass in the default configuration, because it asserts behaviour the app does not currently have.
app/index.tsx:510is the only place server-search results reach a list, and it sits in the non-merged branch of the ternary at:462.stores/settings.ts:133defaultsmergeChats: true, soMergedClassicListrenders instead and never receivesconvSearchData. The merged path filters locally over loaded items (:641-654), andconv-search-anchoris deliberately absent from the loaded set.The request is made and discarded — the mock logged 3
GET /api/searchhits during the failing run.Filed as #646.
Three fixes were tried and rejected, recorded so nobody repeats them:
hub-history-tab— that tab only exists whenmergeChatsis false; the tap fails outright.conv-search-anchortoconversations.json— the app never callsGET /api/conversations, only/api/conversations/count, so the fixture is never read. Reverted.Recommendation: leave this flow failing until #646 is decided. It is currently the tracker's most accurate signal — a correct assertion against a real gap. Making it green by forcing
mergeChats: falsewould test a non-default configuration and hide the defect.That puts the honest target for this issue at 13 passing, 2 failing, with both remaining failures explained:
06_search_anchorblocked on #646,05_chat_flowstill to be attempted.Re-measured 2026-08-12 — 10 pass, 5 fail, and the earlier baseline was an overcount
Full
npm run test:e2e:mockagainst86eeb2a1with a freshly built Release app, iPhone 17 Pro Max / iOS 26.1.Verdicts read from Maestro's own
[Passed]/[Failed]lines this time, not inferred.Passing (10):
launch,browse,server_drag_reorder,bug6_bottom_bar_inset,pty_turn_divider,feat1_tree_drill_new_session,settings_qr_scanner,feedback_flow,05_chat_flow,07_conversation_scroll_gaps.Failing (5):
session_lifecyclehub-screenvisiblefeat2_export_in_info_shelf"Resume Session"visiblecodex_parity"Resume Session"visiblevoice_dictationchat-mic-buttonvisible06_search_anchorsearch-match-navvisible05_chat_flownow passes, fixed in #648.feedback_flowstays fixed from #642.The 2026-08-11 baseline above listed three of these as passing, and that was wrong
codex_parity,feat2_export_in_info_shelfandvoice_dictationare recorded as passing in the 2026-08-11 section, and all three fail now.That is not a regression.
The
<Play weight="fill"/>icon whose accessibility text breaks the twoResume Sessionassertions was added on 2026-08-02 in #510, and is present at1ab1cadd, the commit the 08-11 baseline was measured against.The likelier explanation is the measurement itself.
That run was piped through
tail, which truncated the per-flow verdicts, and the failing set was reconstructed fromscreenshot-❌-…artifact filenames.That method observes only failures — the passing list was everything left over, so any flow failing without leaving a ❌ screenshot was silently counted as a pass.
Treat the 11/15 figure as unverified. 10/15 is measured directly.
Breakdown now tracked as individual issues
ensure-release-build.jscan never be satisfied by the fix it prescribes, which blocks running this suite the documented way at all. Worth clearing first.session_lifecycle.voice_dictation.06_search_anchor, now failing one step later than before.On #646, referenced in the section above
The hub half is fixed in PR #657: merged-mode search now sources conversations from the server, and
06_search_anchorgets pastconversation-row-conv-search-anchorand opens the conversation.#646 stays open for the remaining half — the client still re-filters server results on
titleandpreview, so a conversation matched on message body is fetched and then discarded.That half is specified in
docs/prompts/fix-merged-search-client-refilter.md, and the shared e2e fixture cannot detect it.