Repository navigation
ADFA-514: Update contact dialog with send email button and new email address - #91
Closed
Daniel-ADFA wants to merge 2 commits into
Closed
Daniel-ADFA wants to merge 2 commits into
Daniel-ADFA wants to merge 2 commits into
Conversation
Daniel-ADFA
marked this pull request as draft
April 16, 2025 19:04
fryanpan
added a commit
that referenced
this pull request
Jul 30, 2026
Decision input for the release conversation: what a user actually experiences, the mechanism with file:line, the device evidence, severity and frequency, and the options with their tradeoffs. Tasks #87-#91. None is fixed. None of the five is in the README's "Known limitations (v1)" list, and that is the point: the documented limitations are boundaries the design chose, while these are cases where the feature behaves worse than its own design intends. The never-stale invariant holds in all five - the gap is reliability and honesty of feedback, not speed or correctness. Ship-blocking for v1: the install-confirm fast-fail (180 s of silence ending in a message that says the user failed to confirm something never shown to them), launch-and-retry after a rebaseline reinstall (deterministic today; the fast path is dead until the user guesses), and the daemon-death reclassification (a one-line body edit becomes a ~200 s rebaseline in any Room/KSP project, 3/3 on the A56). Three corrections to the framing this doc started from, all substantiated: - shrinkDaemonForMemory() does not no-op during a mid-start respawn. Its guard is on Building, so in Degraded it runs, and its daemonEpoch++ is a CAUSE of the respawn being discarded. A memory trim during a respawn can strand the session permanently with only a log.info. - "Android never delivers PENDING_USER_ACTION to a backgrounded app" is wrong, and five places in the tree assert it. The broadcast is explicit to our own manifest receiver and is delivered (deferred at worst - our own run-2 findings say so). What is lost is the lifecycle-bound EventBus subscriber that owns the dialog, plus the Android 10+ background startActivity block. Fixing the platform theory points at the wrong repair. - The Degraded session is not entirely silent - the toolbar icon does go to the ATTENTION shape and colour. What is missing is any text, and any feedback that the tap was swallowed. Defect 3 has no device reproduction; the doc says so rather than implying one. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MkdFG7t9oNJtiayQzUQf89
fryanpan
added a commit
that referenced
this pull request
Aug 27, 2026
The page proposed that #87, #89, #91 and the relink-crash gap go to v1.1, then left the table's "Blocks v1?" column reading TBD on all four rows. A proposal in a title and a TBD in a table say different things to a reader, and CodeRabbit flagged the pair as an internal inconsistency. Decision confirmed 2026-08-25: none of the four block v1. The four cells now read "No - v1.1", the title states the answer rather than asking it, and the prose no longer describes itself as awaiting confirmation. No change to any gap's evidence, root cause, or fix. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FstXxJ5cwWPcvmhZ9vJgJ7
fryanpan
added a commit
that referenced
this pull request
Aug 29, 2026
The page proposed that #87, #89, #91 and the relink-crash gap go to v1.1, then left the table's "Blocks v1?" column reading TBD on all four rows. A proposal in a title and a TBD in a table say different things to a reader, and CodeRabbit flagged the pair as an internal inconsistency. Decision confirmed 2026-08-25: none of the four block v1. The four cells now read "No - v1.1", the title states the answer rather than asking it, and the prose no longer describes itself as awaiting confirmation. No change to any gap's evidence, root cause, or fix. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FstXxJ5cwWPcvmhZ9vJgJ7
fryanpan
added a commit
that referenced
this pull request
Aug 29, 2026
The page proposed that #87, #89, #91 and the relink-crash gap go to v1.1, then left the table's "Blocks v1?" column reading TBD on all four rows. A proposal in a title and a TBD in a table say different things to a reader, and CodeRabbit flagged the pair as an internal inconsistency. Decision confirmed 2026-08-25: none of the four block v1. The four cells now read "No - v1.1", the title states the answer rather than asking it, and the prose no longer describes itself as awaiting confirmation. No change to any gap's evidence, root cause, or fix. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FstXxJ5cwWPcvmhZ9vJgJ7
fryanpan
added a commit
that referenced
this pull request
Sep 1, 2026
The page proposed that #87, #89, #91 and the relink-crash gap go to v1.1, then left the table's "Blocks v1?" column reading TBD on all four rows. A proposal in a title and a TBD in a table say different things to a reader, and CodeRabbit flagged the pair as an internal inconsistency. Decision confirmed 2026-08-25: none of the four block v1. The four cells now read "No - v1.1", the title states the answer rather than asking it, and the prose no longer describes itself as awaiting confirmation. No change to any gap's evidence, root cause, or fix. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FstXxJ5cwWPcvmhZ9vJgJ7
fryanpan
added a commit
that referenced
this pull request
Sep 1, 2026
The page proposed that #87, #89, #91 and the relink-crash gap go to v1.1, then left the table's "Blocks v1?" column reading TBD on all four rows. A proposal in a title and a TBD in a table say different things to a reader, and CodeRabbit flagged the pair as an internal inconsistency. Decision confirmed 2026-08-25: none of the four block v1. The four cells now read "No - v1.1", the title states the answer rather than asking it, and the prose no longer describes itself as awaiting confirmation. No change to any gap's evidence, root cause, or fix. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FstXxJ5cwWPcvmhZ9vJgJ7
fryanpan
added a commit
that referenced
this pull request
Sep 1, 2026
The page proposed that #87, #89, #91 and the relink-crash gap go to v1.1, then left the table's "Blocks v1?" column reading TBD on all four rows. A proposal in a title and a TBD in a table say different things to a reader, and CodeRabbit flagged the pair as an internal inconsistency. Decision confirmed 2026-08-25: none of the four block v1. The four cells now read "No - v1.1", the title states the answer rather than asking it, and the prose no longer describes itself as awaiting confirmation. No change to any gap's evidence, root cause, or fix. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FstXxJ5cwWPcvmhZ9vJgJ7
fryanpan
added a commit
that referenced
this pull request
Sep 3, 2026
The page proposed that #87, #89, #91 and the relink-crash gap go to v1.1, then left the table's "Blocks v1?" column reading TBD on all four rows. A proposal in a title and a TBD in a table say different things to a reader, and CodeRabbit flagged the pair as an internal inconsistency. Decision confirmed 2026-08-25: none of the four block v1. The four cells now read "No - v1.1", the title states the answer rather than asking it, and the prose no longer describes itself as awaiting confirmation. No change to any gap's evidence, root cause, or fix. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FstXxJ5cwWPcvmhZ9vJgJ7
fryanpan
added a commit
that referenced
this pull request
Sep 4, 2026
…by a planning-doc number The backgrounded-deploy comment in QuickBuildRuntime deferred to "gap #91", a number from quickbuild/docs/reliability-gaps.md that a reader of the comment cannot follow. State the gap in words and cite ADFA-5466, filed for it today; the gaps table carries the same key. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017o3nPrBbGi2XYkMUGavG2A
fryanpan
added a commit
that referenced
this pull request
Sep 4, 2026
The page proposed that #87, #89, #91 and the relink-crash gap go to v1.1, then left the table's "Blocks v1?" column reading TBD on all four rows. A proposal in a title and a TBD in a table say different things to a reader, and CodeRabbit flagged the pair as an internal inconsistency. Decision confirmed 2026-08-25: none of the four block v1. The four cells now read "No - v1.1", the title states the answer rather than asking it, and the prose no longer describes itself as awaiting confirmation. No change to any gap's evidence, root cause, or fix. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FstXxJ5cwWPcvmhZ9vJgJ7
fryanpan
added a commit
that referenced
this pull request
Sep 5, 2026
…by a planning-doc number The backgrounded-deploy comment in QuickBuildRuntime deferred to "gap #91", a number from quickbuild/docs/reliability-gaps.md that a reader of the comment cannot follow. State the gap in words and cite ADFA-5466, filed for it today; the gaps table carries the same key. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017o3nPrBbGi2XYkMUGavG2A
fryanpan
added a commit
that referenced
this pull request
Sep 5, 2026
The page proposed that #87, #89, #91 and the relink-crash gap go to v1.1, then left the table's "Blocks v1?" column reading TBD on all four rows. A proposal in a title and a TBD in a table say different things to a reader, and CodeRabbit flagged the pair as an internal inconsistency. Decision confirmed 2026-08-25: none of the four block v1. The four cells now read "No - v1.1", the title states the answer rather than asking it, and the prose no longer describes itself as awaiting confirmation. No change to any gap's evidence, root cause, or fix. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FstXxJ5cwWPcvmhZ9vJgJ7
fryanpan
added a commit
that referenced
this pull request
Sep 6, 2026
…by a planning-doc number The backgrounded-deploy comment in QuickBuildRuntime deferred to "gap #91", a number from quickbuild/docs/reliability-gaps.md that a reader of the comment cannot follow. State the gap in words and cite ADFA-5466, filed for it today; the gaps table carries the same key. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017o3nPrBbGi2XYkMUGavG2A
fryanpan
added a commit
that referenced
this pull request
Sep 6, 2026
The page proposed that #87, #89, #91 and the relink-crash gap go to v1.1, then left the table's "Blocks v1?" column reading TBD on all four rows. A proposal in a title and a TBD in a table say different things to a reader, and CodeRabbit flagged the pair as an internal inconsistency. Decision confirmed 2026-08-25: none of the four block v1. The four cells now read "No - v1.1", the title states the answer rather than asking it, and the prose no longer describes itself as awaiting confirmation. No change to any gap's evidence, root cause, or fix. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FstXxJ5cwWPcvmhZ9vJgJ7
fryanpan
added a commit
that referenced
this pull request
Sep 6, 2026
The page proposed that #87, #89, #91 and the relink-crash gap go to v1.1, then left the table's "Blocks v1?" column reading TBD on all four rows. A proposal in a title and a TBD in a table say different things to a reader, and CodeRabbit flagged the pair as an internal inconsistency. Decision confirmed 2026-08-25: none of the four block v1. The four cells now read "No - v1.1", the title states the answer rather than asking it, and the prose no longer describes itself as awaiting confirmation. No change to any gap's evidence, root cause, or fix. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FstXxJ5cwWPcvmhZ9vJgJ7
fryanpan
added a commit
that referenced
this pull request
Sep 8, 2026
…by a planning-doc number The backgrounded-deploy comment in QuickBuildRuntime deferred to "gap #91", a number from quickbuild/docs/reliability-gaps.md that a reader of the comment cannot follow. State the gap in words and cite ADFA-5466, filed for it today; the gaps table carries the same key. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017o3nPrBbGi2XYkMUGavG2A
fryanpan
added a commit
that referenced
this pull request
Sep 8, 2026
The page proposed that #87, #89, #91 and the relink-crash gap go to v1.1, then left the table's "Blocks v1?" column reading TBD on all four rows. A proposal in a title and a TBD in a table say different things to a reader, and CodeRabbit flagged the pair as an internal inconsistency. Decision confirmed 2026-08-25: none of the four block v1. The four cells now read "No - v1.1", the title states the answer rather than asking it, and the prose no longer describes itself as awaiting confirmation. No change to any gap's evidence, root cause, or fix. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FstXxJ5cwWPcvmhZ9vJgJ7
fryanpan
added a commit
that referenced
this pull request
Sep 8, 2026
The page proposed that #87, #89, #91 and the relink-crash gap go to v1.1, then left the table's "Blocks v1?" column reading TBD on all four rows. A proposal in a title and a TBD in a table say different things to a reader, and CodeRabbit flagged the pair as an internal inconsistency. Decision confirmed 2026-08-25: none of the four block v1. The four cells now read "No - v1.1", the title states the answer rather than asking it, and the prose no longer describes itself as awaiting confirmation. No change to any gap's evidence, root cause, or fix. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FstXxJ5cwWPcvmhZ9vJgJ7
fryanpan
added a commit
that referenced
this pull request
Sep 11, 2026
The page proposed that #87, #89, #91 and the relink-crash gap go to v1.1, then left the table's "Blocks v1?" column reading TBD on all four rows. A proposal in a title and a TBD in a table say different things to a reader, and CodeRabbit flagged the pair as an internal inconsistency. Decision confirmed 2026-08-25: none of the four block v1. The four cells now read "No - v1.1", the title states the answer rather than asking it, and the prose no longer describes itself as awaiting confirmation. No change to any gap's evidence, root cause, or fix. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FstXxJ5cwWPcvmhZ9vJgJ7
fryanpan
added a commit
that referenced
this pull request
Sep 12, 2026
The page proposed that #87, #89, #91 and the relink-crash gap go to v1.1, then left the table's "Blocks v1?" column reading TBD on all four rows. A proposal in a title and a TBD in a table say different things to a reader, and CodeRabbit flagged the pair as an internal inconsistency. Decision confirmed 2026-08-25: none of the four block v1. The four cells now read "No - v1.1", the title states the answer rather than asking it, and the prose no longer describes itself as awaiting confirmation. No change to any gap's evidence, root cause, or fix. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FstXxJ5cwWPcvmhZ9vJgJ7
fryanpan
added a commit
that referenced
this pull request
Sep 12, 2026
The page proposed that #87, #89, #91 and the relink-crash gap go to v1.1, then left the table's "Blocks v1?" column reading TBD on all four rows. A proposal in a title and a TBD in a table say different things to a reader, and CodeRabbit flagged the pair as an internal inconsistency. Decision confirmed 2026-08-25: none of the four block v1. The four cells now read "No - v1.1", the title states the answer rather than asking it, and the prose no longer describes itself as awaiting confirmation. No change to any gap's evidence, root cause, or fix. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FstXxJ5cwWPcvmhZ9vJgJ7
fryanpan
added a commit
that referenced
this pull request
Sep 12, 2026
The page proposed that #87, #89, #91 and the relink-crash gap go to v1.1, then left the table's "Blocks v1?" column reading TBD on all four rows. A proposal in a title and a TBD in a table say different things to a reader, and CodeRabbit flagged the pair as an internal inconsistency. Decision confirmed 2026-08-25: none of the four block v1. The four cells now read "No - v1.1", the title states the answer rather than asking it, and the prose no longer describes itself as awaiting confirmation. No change to any gap's evidence, root cause, or fix. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FstXxJ5cwWPcvmhZ9vJgJ7
fryanpan
added a commit
that referenced
this pull request
Sep 14, 2026
…by a planning-doc number The backgrounded-deploy comment in QuickBuildRuntime deferred to "gap #91", a number from quickbuild/docs/reliability-gaps.md that a reader of the comment cannot follow. State the gap in words and cite ADFA-5466, filed for it today; the gaps table carries the same key. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017o3nPrBbGi2XYkMUGavG2A
fryanpan
added a commit
that referenced
this pull request
Sep 14, 2026
The page proposed that #87, #89, #91 and the relink-crash gap go to v1.1, then left the table's "Blocks v1?" column reading TBD on all four rows. A proposal in a title and a TBD in a table say different things to a reader, and CodeRabbit flagged the pair as an internal inconsistency. Decision confirmed 2026-08-25: none of the four block v1. The four cells now read "No - v1.1", the title states the answer rather than asking it, and the prose no longer describes itself as awaiting confirmation. No change to any gap's evidence, root cause, or fix. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FstXxJ5cwWPcvmhZ9vJgJ7
fryanpan
added a commit
that referenced
this pull request
Sep 15, 2026
The page proposed that #87, #89, #91 and the relink-crash gap go to v1.1, then left the table's "Blocks v1?" column reading TBD on all four rows. A proposal in a title and a TBD in a table say different things to a reader, and CodeRabbit flagged the pair as an internal inconsistency. Decision confirmed 2026-08-25: none of the four block v1. The four cells now read "No - v1.1", the title states the answer rather than asking it, and the prose no longer describes itself as awaiting confirmation. No change to any gap's evidence, root cause, or fix. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FstXxJ5cwWPcvmhZ9vJgJ7
fryanpan
added a commit
that referenced
this pull request
Sep 16, 2026
The page proposed that #87, #89, #91 and the relink-crash gap go to v1.1, then left the table's "Blocks v1?" column reading TBD on all four rows. A proposal in a title and a TBD in a table say different things to a reader, and CodeRabbit flagged the pair as an internal inconsistency. Decision confirmed 2026-08-25: none of the four block v1. The four cells now read "No - v1.1", the title states the answer rather than asking it, and the prose no longer describes itself as awaiting confirmation. No change to any gap's evidence, root cause, or fix. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FstXxJ5cwWPcvmhZ9vJgJ7
fryanpan
added a commit
that referenced
this pull request
Sep 16, 2026
…by a planning-doc number The backgrounded-deploy comment in QuickBuildRuntime deferred to "gap #91", a number from quickbuild/docs/reliability-gaps.md that a reader of the comment cannot follow. State the gap in words and cite ADFA-5466, filed for it today; the gaps table carries the same key. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017o3nPrBbGi2XYkMUGavG2A
fryanpan
added a commit
that referenced
this pull request
Sep 16, 2026
The page proposed that #87, #89, #91 and the relink-crash gap go to v1.1, then left the table's "Blocks v1?" column reading TBD on all four rows. A proposal in a title and a TBD in a table say different things to a reader, and CodeRabbit flagged the pair as an internal inconsistency. Decision confirmed 2026-08-25: none of the four block v1. The four cells now read "No - v1.1", the title states the answer rather than asking it, and the prose no longer describes itself as awaiting confirmation. No change to any gap's evidence, root cause, or fix. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FstXxJ5cwWPcvmhZ9vJgJ7
fryanpan
added a commit
that referenced
this pull request
Sep 20, 2026
…by a planning-doc number The backgrounded-deploy comment in QuickBuildRuntime deferred to "gap #91", a number from quickbuild/docs/reliability-gaps.md that a reader of the comment cannot follow. State the gap in words and cite ADFA-5466, filed for it today; the gaps table carries the same key. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017o3nPrBbGi2XYkMUGavG2A
fryanpan
added a commit
that referenced
this pull request
Sep 20, 2026
The page proposed that #87, #89, #91 and the relink-crash gap go to v1.1, then left the table's "Blocks v1?" column reading TBD on all four rows. A proposal in a title and a TBD in a table say different things to a reader, and CodeRabbit flagged the pair as an internal inconsistency. Decision confirmed 2026-08-25: none of the four block v1. The four cells now read "No - v1.1", the title states the answer rather than asking it, and the prose no longer describes itself as awaiting confirmation. No change to any gap's evidence, root cause, or fix. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FstXxJ5cwWPcvmhZ9vJgJ7
fryanpan
added a commit
that referenced
this pull request
Sep 24, 2026
…by a planning-doc number The backgrounded-deploy comment in QuickBuildRuntime deferred to "gap #91", a number from quickbuild/docs/reliability-gaps.md that a reader of the comment cannot follow. State the gap in words and cite ADFA-5466, filed for it today; the gaps table carries the same key. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017o3nPrBbGi2XYkMUGavG2A
fryanpan
added a commit
that referenced
this pull request
Sep 25, 2026
The page proposed that #87, #89, #91 and the relink-crash gap go to v1.1, then left the table's "Blocks v1?" column reading TBD on all four rows. A proposal in a title and a TBD in a table say different things to a reader, and CodeRabbit flagged the pair as an internal inconsistency. Decision confirmed 2026-08-25: none of the four block v1. The four cells now read "No - v1.1", the title states the answer rather than asking it, and the prose no longer describes itself as awaiting confirmation. No change to any gap's evidence, root cause, or fix. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FstXxJ5cwWPcvmhZ9vJgJ7
fryanpan
added a commit
that referenced
this pull request
Oct 1, 2026
…by a planning-doc number The backgrounded-deploy comment in QuickBuildRuntime deferred to "gap #91", a number from quickbuild/docs/reliability-gaps.md that a reader of the comment cannot follow. State the gap in words and cite ADFA-5466, filed for it today; the gaps table carries the same key. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017o3nPrBbGi2XYkMUGavG2A
fryanpan
added a commit
that referenced
this pull request
Oct 1, 2026
The page proposed that #87, #89, #91 and the relink-crash gap go to v1.1, then left the table's "Blocks v1?" column reading TBD on all four rows. A proposal in a title and a TBD in a table say different things to a reader, and CodeRabbit flagged the pair as an internal inconsistency. Decision confirmed 2026-08-25: none of the four block v1. The four cells now read "No - v1.1", the title states the answer rather than asking it, and the prose no longer describes itself as awaiting confirmation. No change to any gap's evidence, root cause, or fix. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FstXxJ5cwWPcvmhZ9vJgJ7
fryanpan
added a commit
that referenced
this pull request
Oct 1, 2026
The page proposed that #87, #89, #91 and the relink-crash gap go to v1.1, then left the table's "Blocks v1?" column reading TBD on all four rows. A proposal in a title and a TBD in a table say different things to a reader, and CodeRabbit flagged the pair as an internal inconsistency. Decision confirmed 2026-08-25: none of the four block v1. The four cells now read "No - v1.1", the title states the answer rather than asking it, and the prose no longer describes itself as awaiting confirmation. No change to any gap's evidence, root cause, or fix. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FstXxJ5cwWPcvmhZ9vJgJ7
fryanpan
added a commit
that referenced
this pull request
Oct 2, 2026
The page proposed that #87, #89, #91 and the relink-crash gap go to v1.1, then left the table's "Blocks v1?" column reading TBD on all four rows. A proposal in a title and a TBD in a table say different things to a reader, and CodeRabbit flagged the pair as an internal inconsistency. Decision confirmed 2026-08-25: none of the four block v1. The four cells now read "No - v1.1", the title states the answer rather than asking it, and the prose no longer describes itself as awaiting confirmation. No change to any gap's evidence, root cause, or fix. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FstXxJ5cwWPcvmhZ9vJgJ7
fryanpan
added a commit
that referenced
this pull request
Oct 2, 2026
…pp (#1716) * ADFA-4128: qb 04/12 runtime — Inside the proxy app: swaps code, resources and assets into the running process Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Kj9YeCDHGp9DU8LPtfWJ7W * ADFA-4128: qb 04 review fixes — reload failure attribution + surfacing - Stale pendingReloadGeneration mis-blaming later crashes: the backgrounded apply now assigns the pending slot too (Generations.pendingAfterApply), and BootProbation.generationToBlame refuses a pending value the store has moved past. Covered by BootProbationTest.aPendingReloadTheStoreMovedPastIsNotBlamed (fails without the fix) and GenerationsTest.aBackgroundedApplyClearsThePendingSlotItAlreadyAcked. - failReload swallowing every pre-apply failure: the newer-generation guard is now a three-way Generations.onReloadFailure — never-applied failures skip the rollback/quarantine but still reportCrash + banner; only a failure superseded by a newer live generation stays silent. Covered by GenerationsTest.aFailureTheStoreNeverAdoptedStillReports. - Binder-thread setProviders + immediate provider close racing main-thread inflation: ResourceStore now performs the field swap, setProviders and the close of the replaced provider on the main thread (inline when already there, so the boot restore path still lands before first inflation; Looper FIFO keeps a posted swap ahead of the posted recreate). Pure threading with no JVM seam — justified in swapProvidersOnMain's doc; device-covered. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Kj9YeCDHGp9DU8LPtfWJ7W * ADFA-4128 (4/11): address CodeRabbit review - F1716-2 heal a half-finished asset merge on the next run - F1716-5 stop answering a VirtualMachineError with another allocation - F1716-7 take the asset length from the descriptor already open - F1716-8 un-commit a resource provider swap that failed to install Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FstXxJ5cwWPcvmhZ9vJgJ7 * ADFA-4128 (4/11): address Akash's review Fixes for every finding on the runtime module, plus two changes that came out of reviewing them. Crash banner. The copy said "New code crashed", which named the one event this banner cannot observe: the CRASHED state is set only from failReload, so it is always the reload machinery that failed, never the user's own code. It also carried a stack summary it had no room for. It now reads Live reload crashed. App is on the last working version. For more info, see Build Output in Code on the Go. and points at the pane where the full text already goes unchanged. At the narrowest width measured on an A56 at 2x font scale that is five rendered lines, four from 28 characters up; MAX_BANNER_LINES stays at 6, one line of slack, because the line a tighter cap drops is the tail of the pointer - which leaves the reader told to look somewhere without the name of the place. Crash report. It walks up to three causes and prints each one's frames, not just its toString. An Android lifecycle crash always arrives wrapped, so the top frames are ActivityThread's every time and the line naming the developer's bug sits in the cause; reporting the message alone named the exception without ever placing it. markGood retry. lastMarkedGoodGeneration was set before the write was attempted, so a failed markGood was never retried and its latch blocked every later one for the process lifetime, and the KDoc's justification was inverted. Clearing the latch on a bare false is not safe either - markGood answers several situations with one false, and persist runs before apply, so meta.json is briefly ahead of the live generation on every deploy. markGoodCanSucceed separates a failed write from a store that moved on, and only the failed write clears. Payload overtake. onPayload is oneway, so a slower older deploy can be overtaken while it reads its payload and then publish itself over the newer one, leaving disk a generation behind the running process until the next cold boot adopts it. PayloadStore.apply already refuses a generation that is not strictly newer, so this could never reach the screen - only disk. PayloadPersistence now keeps the highest generation this process has published and refuses anything older, throwing StalePayloadException so the deploy path can tell a lost race from a broken store and stay silent about it. The bar rises only after the publishing rename, so a persist that threw part-way does not block its own retry. That guard also separates the two cases a generation number alone conflates. A restarted host counter - the project's state dir wiped while the app stays installed - always arrives in a process that has published nothing, so the mark is zero and the low generation is adopted as before. Both counter-restart tests now build a fresh store object over the same directory, which is the only shape that case has on a device. Payload memory. The resource apk and the assets zip were read whole into memory and written straight back out to files that are reopened as files afterwards, so a cold deploy held two payload-sized arrays live for no benefit on the devices least able to spare them. persist now takes both as streams and copies them through a 16 KB buffer into the same temp-then-fsync-then-rename write. Only the dex stays a byte array, because InMemoryDexClassLoader needs one. The 64 MB cap is unchanged and now guards the streaming path; the parameter types are what keep it that way. Also from Akash: the manifest-merger comment, the KDoc corrections, and the test helper that divided length by width - it modelled a renderer that breaks mid-word, so it read the banner's six real lines as five and could not have caught the overflow it existed for. It wraps on words now, and was watched failing at the old cap before the cap moved. Both new gates were watched red first: the payload cap with its check stubbed out, the overtake refusal with its condition forced false. Only the intended test failed each time. 248 tests green. Banner inset. Photographing the new banner at 2x font scale showed it drawing over the status bar: getRootWindowInsets() comes back null on the first render after a config-change recreate, and the overlay took that as a 0 inset. A null read now means "not measured yet" - the margin is left alone and re-read after the next layout, once, by a listener that removes itself. The decision is a pure static so it can be unit-tested; the deferred re-read firing is checked on a device (A56: banner flush below the 101 px bar at 1.0 and 2.0). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xsc7AMGBVyEMfrwpZX87iC * ADFA-4128 (4/11): drop the unused IQuickBuildHost.disconnect AIDL method The runtime only ever disconnected by process death, which ProxyAppConnections.onDisconnected already handles. Asked for in review on #1718. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xsc7AMGBVyEMfrwpZX87iC * ADFA-4128: qb-04 review fixes - overlay observer capture, BUILD_FAILED clamp, streamed apk write, resource-attach latch, log overload Akash's 08-31 review of #1716, items 1/2/3/5/6 (4 and 7 deferred to followup tickets per the triage): - StatusOverlay: capture the ViewTreeObserver at add time; on GIVE_UP the banner is detached and a re-fetch returns a floating observer, making the removal a silent no-op. isAlive() guard with a decor-observer fallback. - OverlayState/CrashSummary: BUILD_FAILED detail clamped to a one-line budget (BUILD_FAILED_DETAIL_CHARS) and the Build Output pointer appended, same shape as CRASHED; budget arithmetic documented from both ends. - LegacyResourceSwap: stream the resource apk to disk instead of buffering it in heap (small-heap API 28/29 devices); partial file deleted on a failed copy so the throw-means-nothing-written contract holds. - ResourceStore: attachedAppResources latched only when the attach succeeded, so a swallowed failure is retried on the next deploy. - QuickBuildClient: two-arg RuntimeLog.w keeps the stack trace. Tests: clamp test verified red before the fix; partial-file-delete test added; three edge tests updated for the pointer line. 255 green. Also: plain-language pass over the comments added by these fixes Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01STCsdMzx9daNBcqMN424Ci * ADFA-4128: 0902 review round on quickbuild:runtime Ordering and lifetime fixes from Akash's 2 September round, plus the nitpick sweep. - The failed reload's decision and its rollback now happen under one lock (PayloadStore.restoreIfCurrent), so a deploy that applies between the two is no longer rolled back by a decision taken before it existed. #1716 (comment) - A generation whose resource swap failed is marked before the failure is dispatched off-thread, and the posted recreate skips it. Moving failReload off the looper had left the recreate free to render the generation the rollback was undoing, and the mark also covers the inline swap, which fails before the recreate is posted at all. #1716 (comment) #1716 (comment) - Provider swaps carry their generation and drop an overtaken one instead of installing it, so a slower deploy's swap landing last cannot put the older table back under the newer generation's label. #1716 (comment) - The connect() handshake - the one non-oneway host call - runs off the binding callback's thread, so a cold CoGo no longer blocks the proxy app's main thread. #1716 (comment) - Asset extraction is capped cumulatively at MAX_PAYLOAD_BYTES, matching every other payload path. #1716 (comment) - The five fallback catches in the component factory rethrow fatal errors, so an OOM is grouped as itself rather than under the payload's throwable. #1716 (comment) - writeAtomic no longer short-circuits on the delete: a first write, where there is nothing to delete, skipped the retry and leaked the temp file. #1716 (comment) - PayloadStore's last throwable-concatenation site takes the two-arg log. #1716 (comment) - LegacyResourceSwap's KDoc names the test that exists. #1716 (comment) - The fail-reload dispatch test's KDoc claims what the test pins - the helper's contract - and says what it does not. #1716 (comment) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017o3nPrBbGi2XYkMUGavG2A * style: spotless reformat of QuickBuildClient, no functional change The Eclipse formatter's member sorting moves connectToHost below its caller. Standalone so it does not read as a behavioural change. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017o3nPrBbGi2XYkMUGavG2A * ADFA-4128: hold a generation blamable until its first frame has drawn Answers review thread 3926539435 (CRITICAL) on PR #1716. onResume precedes the first traversal, so nothing that vouches for a payload could legitimately happen there. The runtime acked the generation, cleared the pending slot and wrote good.json from onActivityResumed, so a payload whose activity resumed and then threw in measure, layout or draw reached the crash guard with nothing pending and nothing unproven. generationToBlame returned -1, quarantine then refused to name a generation already recorded good, and every relaunch adopted it and died the same way with no in-app escape. The ack, the pending-clear and the good-marking now move together into onFirstFrameDrawn, released by a ViewTreeObserver.OnDrawListener whose completion is posted rather than run inline: the listener fires at the start of a draw pass, so only the posted message runs after the traversal that drew the frame returns. An activity with no live view tree completes inline as before, since waiting for a frame that will never arrive would strand the deploy unacked. The pending slot moves out of QuickBuildRuntime into FirstFrameGate, which is plain Java and JVM-testable; FirstFrameGateTest pins that the generation stays blamable across the undrawn window and is released only by a drawn frame. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017o3nPrBbGi2XYkMUGavG2A * ADFA-4128: serialize and order the API 28/29 resource swap Answers review threads 3926539450 and 3926539457 (both IMPORTANT) on PR #1716. applyTableLegacy mutated the live AssetManager and flushed the Resources caches straight off the binder thread the deploy arrived on, which is exactly what swapProvidersOnMain's own KDoc says must not happen: addAssetPath re-tables the AssetManager and flushCaches drops the drawable and typed-value caches, and either can race an inflation already in progress. The path is live rather than dead - CoGo's classifier routes resource edits with no SDK gate, so a res/ edit on a 28/29 device lands here. It also compared nothing before mounting, while both loader swaps drop an overtaken generation against swappedGeneration. Two binder threads could interleave so that the older table was the last one added, which wins the lookup: the screen resolved gen N-1 while the store reported gen N, and legacyTableZip then handed that apk to every activity created afterwards. The write stays on the calling thread; only the mount is posted, under the store's monitor, behind the same generation comparison its two siblings use. A mount failure now travels back through the swap-failure listener instead of being thrown synchronously, so the deploy still fails rather than acking a table that never mounted. Not JVM-tested: ResourceStore needs a Context, a Resources and a live main Looper, and is one of the classes this module's coverage gate excludes as device-only glue. Both siblings' identical guards are untested for the same reason. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017o3nPrBbGi2XYkMUGavG2A * ADFA-4128: a stale handshake failure must not tear down a live binding Answers review thread 3926539468 (IMPORTANT) on PR #1716. Moving the connect handshake onto its own thread left both failure branches writing the shared host field unconditionally. The handshake can outlive its binding: CoGo's service dies, onServiceDisconnected nulls the host, the framework reconnects and a second handshake succeeds against a new proxy. The first thread's connect() then fails and nulls that live host, unbinds a healthy channel and schedules a rebind - and until the rebind lands, reportReloaded and reportCrash only log "not connected", so every deploy in the window can end only in the host's own timeout. Both branches now go through abandonHandshake, which takes the monitor and returns early unless the proxy that failed is still the live one. The test and the teardown have to be one step because host is also written from the framework's callback thread. Not JVM-tested: QuickBuildClient is binder and ServiceConnection glue over a Context, an IBinder and a main-thread Handler, and is one of the classes this module's coverage gate excludes as device-only. The interleaving the reviewer describes is a plausible reading of the code rather than one reproduced here; what is verified is that the guard was absent and is now present. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017o3nPrBbGi2XYkMUGavG2A * ADFA-4128: delete a temp file on every failure, not just a failed rename Answers review thread 3926539487 (MINOR) on PR #1716. AssetExtractor.writeFile deleted its .qb-tmp only from the rename fallback, so a throw out of the copy - the payload cap, a write failure, a truncated entry - left the partial file under current/assets/, which is the tree DirectoryAssetsProvider resolves names against, and the app could open it by name. Both write paths now delete the temp from one finally that covers the copy, the close and the rename alike. The reviewer cites two siblings as already correct. Only one is: LegacyResourceSwap.writeResourceApk does delete its partial file when the copy fails. PayloadPersistence.writeAtomic has the same gap being fixed here - its finally only closed the stream, and its temp.delete() sat in the rename fallback - so an oversize payload or a full disk left a .tmp in the store directory that nothing sweeps. Both are fixed here. Two tests, each verified to fail without the fix and for its own reason - a leftover temp file, not an unexpected throw: AssetExtractorFailurePathTest.aCopyThatFailsMidEntryLeavesNoTempFile drives a truncated zip entry, and PayloadPersistenceAtomicWriteTest.aStreamThatFailsMidCopyLeavesNoTempFile drives a payload stream that dies mid-copy. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017o3nPrBbGi2XYkMUGavG2A * ADFA-4128: drop the no-op test-heap override Answers review thread 3926539494 (NITPICK) on PR #1716. The root build already sets maxHeapSize = "1g" on every Test task in every subproject (build.gradle.kts, the subprojects tasks.withType<Test> block), so this module never saw Gradle's 512 MB default and the override changed nothing. Its comment also compared against neither the real cap - Streams.MAX_PAYLOAD_BYTES is 64 MB - nor the heap actually in force, so a reader trimming test memory later would have trusted it twice over. Verified against the root build before removing: the subprojects block does set it, so dropping this leaves the same 1g in effect. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017o3nPrBbGi2XYkMUGavG2A * ADFA-4128: style: spotless reformat, no functional change The Eclipse Java formatter sorts members, so files this round touched came under the ratchet and had their declarations reordered. Kept standalone so the behavioural commits around it stay readable. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017o3nPrBbGi2XYkMUGavG2A * ADFA-4128: hold a backgrounded deploy's ack until its resource swaps commit Answers review threads 3926539472 (IMPORTANT) and, in part, 3926539480 (IMPORTANT) on PR #1716. The earlier swap-failure fix reached only the foreground branch. applyTable posts its swap to the main looper and returns, and the backgrounded branch then acked on the binder thread while that swap was still queued. When the swap failed, onSwapFailed ran later, rolled the store back to gen N-1, quarantined N and reported the crash - after CoGo had already been told N reloaded. DeployChannel resolves a deploy on the first report naming the generation, so the ack won: the build was recorded as a successful reload with a timing number, the Crashed branch never ran, and the session manager's separate collector still raised RELOAD_CRASHED, so one save produced both signals. The comment on that branch says the backgrounded case is the normal edit loop, so it is the branch a failing resource swap usually takes. SwapFailure becomes SwapOutcome and gains the success counterpart the reviewer points at. Its contract is that exactly one of the two fires per applyTable or applyAssets call that returns normally - including the calls that queue nothing because this SDK level has no swap to make, since a deploy waiting on one of those would wait forever. A swap dropped as overtaken reports committed rather than failed: it returned normally, and the generation that overtook it owns the screen and its own ack. SwapAckGate counts a deploy's posted swaps down to the one ack it owes, and a failure cancels it for good so a second swap landing afterwards cannot turn a rolled-back deploy back into a success. A dex-only deploy - the commonest one - posts nothing and still acks immediately, through noSwapPosted rather than through committed, so a deploy with one swap in flight cannot mistake that call for its swap's own commit. The resumed check moves above the applies, because the commit callback is free to fire before handlePayload returns and has to know which branch it is completing. Arming the first-frame gate moves with it, which also closes a smaller hole: an apply that threw used to leave an older generation's value in the pending slot. The abandoned-generation half of 3926539480 comes with it: an applyAssets failure left the table swap applyTable had already queued live under the rolled-back dex, and nothing marked the generation abandoned, since only onSwapFailed did that. The recreate then rendered gen N's table over gen N-1's classes while the banner said the app was on the last working version. handlePayload's catch now marks it. DEFERRED, deliberately: the other half of 3926539480 - having failReload restore the provider set alongside the payload. A resource rollback is a new capability, not a guard: ResourceStore keeps no per-generation provider history, the API 28/29 path cannot unmount an added asset path at all, and the store's own KDoc already documents "a swap that already took is not undone" as the contract. Adding one belongs in its own change with its own device verification, not folded into a review fix. Marking the generation abandoned already stops the recreate, which is what makes the banner honest. SwapAckGateTest pins the rule and was verified to fail without it: with the gate mutated to ack regardless of queued swaps, and with failed() made a no-op, three of its seven tests go red on exactly the assertions they are named for. The call site itself - handlePayload counting its swaps - needs a binder thread, a main looper and a Context, so it is checked on device; QuickBuildRuntime and ResourceStore are both in this module's device-only coverage exclusion list. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017o3nPrBbGi2XYkMUGavG2A * ADFA-4128: name the unreported-relaunch-crash gap by its ticket, not by a planning-doc number The backgrounded-deploy comment in QuickBuildRuntime deferred to "gap #91", a number from quickbuild/docs/reliability-gaps.md that a reader of the comment cannot follow. State the gap in words and cite ADFA-5466, filed for it today; the gaps table carries the same key. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017o3nPrBbGi2XYkMUGavG2A * ADFA-4128: pin the first-frame call site, and refuse an abandoned generation's swap Two review gaps on the runtime, both about a generation that is no longer supposed to be believed. The first-frame gate had a test for the gate class but nothing for its caller, so reverting the routing - completing the reload at onResume again, which is the pre-fix behaviour - left every test green. onActivityResumed needs an Activity, a Window and a live ViewTreeObserver, so the routing moves into a package-private seam, completeOnResume, the same shape as startFailReloadThread. The new test drives that seam and asserts what the resume must NOT do: with a frame still coming it completes nothing, so the generation stays pending in the gate and BootProbation still names it. The second is a swap the store used to commit after the deploy that queued it had already been rolled back. A swap is posted to main and commits after applyPayload returns, so a deploy that throws in a later step - applyTable posts before applyAssets can throw - had its rollback run with its own table swap still queued. The store already drops an OVERTAKEN swap in all three swap bodies; this adds the sibling case, an ABANDONED one, through the same guard. Undoing a committed swap is not available: the store keeps single provider slots and closes the previous provider after each swap, and the API 28/29 path cannot unmount an added asset path at all, so refusing the commit is the whole remedy. The runtime calls abandon() from both places it already gives up on a generation. The three swap bodies run on the main looper, so their call to the guard is not pinned by a JVM test; what is pinned is the decision they take. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017o3nPrBbGi2XYkMUGavG2A * ADFA-4128: say why the banner copy is inline English The runtime AAR is injected into the user's app and carries no res/ of its own, so the banner cannot use a string resource; REVIEW.md asks for that opt-out to be stated, not inferred. MAX_BANNER_LINES is derived from the literals' character counts, so its KDoc now says the arithmetic assumes the English copy. Review thread: #1716 (comment) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XkGof8cLt23LkxZ8MKzin2 * ADFA-4128: report a mixed state when the failed swap had already committed failReload rolls the dex back, but a resource swap that committed before the failure stays live: the store keeps single provider slots and the API 28/29 path cannot unmount an asset path. That is the common ordering, since applyTable posts and returns while applyAssets merges on the binder thread. The banner then said "App is on the last working version" while the screen served the failed generation's table under the previous generation's classes. Now the failure path reads ResourceStore.swappedGeneration() after the generation has been abandoned (so a still-queued swap is refused rather than committing later) and, when it equals the failed generation, shows OverlayState.mixed() - "Restart the app - it is running mixed versions" - and prefixes the report to CoGo so Build Output carries the restart instruction in full. The decision lives in Generations.leavesMixedState so it is JVM-tested; the wiring in failReloadNow is device-only. Review thread: #1716 (comment) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XkGof8cLt23LkxZ8MKzin2 * ADFA-4128: run the boot resource restore off the main thread and report its failure applyPendingBootResources is dispatched from onActivityPreCreated, inside the first activity's creation on the main thread, and ran the whole restore inline: the asset merge (a recursive delete plus an unzip) and, on API 28/29, the relinked apk copy, both bounded only by the 64 MB payload cap. Every cold start that adopts a persisted generation with resources - every save after a restart deploy, and every process death - paid that as launch jank or an ANR on the low-end devices the legacy path exists for. The extraction now runs on a qb-boot-restore thread and only the swap is posted; the first activity inflates against the baseline table and is recreated once the last swap lands, counted by the same SwapAckGate a backgrounded deploy uses. markLiveGenerationGood waits for the restore, since a frame drawn against the baseline proves the code half only. The restore also had the one remaining null outcome listener, so a corrupt store file or a full disk inside the merge left the process on this generation's code over the installed resources with one log line and nothing else. It now shows the mixed banner and reports to CoGo with a boot-specific first line; the report is best-effort, since CoGo may not have connected yet. Review threads: #1716 (comment) #1716 (comment) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XkGof8cLt23LkxZ8MKzin2 * ADFA-4128: remove the first-frame draw listener from the captured observer The listener was added to the observer captured before the draw and removed from a re-fetched decor.getViewTreeObserver(). Once the decor is detached - the activity destroyed between the draw and the posted completion - that accessor returns a fresh floating observer that never held the listener, so the removal was a silent no-op. Remove from the captured observer while it is alive, falling back to the decor's when the framework has merged it away, as StatusOverlay.reapplyInsetAfterLayout already does. Review thread: #1716 (comment) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XkGof8cLt23LkxZ8MKzin2 * ADFA-4128: write the client's host proxy under the monitor everywhere abandonHandshake tests host and tears the binding down under the monitor, but the four framework callbacks wrote host as plain volatile writes, so the exclusion held only against other synchronized callers. A disconnect-then-reconnect on the main thread could still land between the handshake thread's read and its write and unbind a healthy binding. onServiceConnected now writes under the monitor and the three null writes go through a synchronized dropHost(); the callbacks themselves stay unsynchronized so the main thread does not wait on a handshake thread's binder round trip. Review thread: #1716 (comment) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XkGof8cLt23LkxZ8MKzin2 * ADFA-4128: document the recreate-into-stopped case FirstFrameGate does not release A recreate that succeeds but relaunches into the stopped state, whose task is then swiped away, never resumes: no draw callback is installed and nothing else releases the slot, so the deploy ends in CoGo's timeout and the generation stays blamable until the next save. The KDoc listed two no-frame fallbacks and not this one. It is recorded rather than wired: recreate() destroys the armed activity on every normal reload, so an onActivityDestroyed release would also need to know a relaunch is still pending, which nothing tracks yet. Review thread: #1716 (comment) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XkGof8cLt23LkxZ8MKzin2 * ADFA-4128: refuse asset payloads below API 30 instead of acking a merge nothing reads Nothing below API 30 reads the merged asset dir: DirectoryAssetsProvider needs a ResourcesLoader and LegacyResourceSwap mounts the resource apk only. The legacy arm still ran the merge and reported the swap committed, which is what settles a backgrounded deploy's ack, so a reload the app could not show was acked. It now reports failed with the reason before touching the fd or the Context, and the comment names the host gate (QuickBuildModule's assetsLiveReloadable, the classifier) that keeps it unreachable today. The applyAssets KDoc also said a partial merge stays live until the next deploy overwrites it; extractCumulative leaves MERGE_PENDING_MARKER and the next merge clears the whole dir. Reworded to say so. Review threads: #1716 (comment) #1716 (comment) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XkGof8cLt23LkxZ8MKzin2 * style: spotless reformat, no functional change Eclipse member sorting over the members the review-fix commits added; no line inside any member changed. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XkGof8cLt23LkxZ8MKzin2 * ADFA-4128: decide what a frame proves on its draw pass, not when its completion runs markLiveGenerationGood read bootRestoreInFlight when the posted first-frame completion ran. The draw listener fires inside the traversal, an async message ahead of the sync barrier, and the completion is posted behind it, so a boot restore's swap message could land in between: the frame drew the baseline table, the swap committed and cleared the flag, and the completion then recorded good a generation whose table never rendered - unblamable if that table fails on the next boot. frameCompletion samples the flag on the draw pass and hands the fixed verdict to onFirstFrameDrawn / markLiveGenerationGood, which no longer re-read it. The seam is static and Android-free so QuickBuildRuntimeFrameCompletionTest can pin the ordering. Adversarial review 2026-09-04, finding #3 on 540eb96dd. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XkGof8cLt23LkxZ8MKzin2 * ADFA-4128: say what a failed boot restore actually leaves behind The catch in restoreBootResources claimed a failed restore leaves the process wholly on the baseline table. abandon() only refuses a swap still queued; a table swap that committed before applyAssets threw stays live, the app runs mixed, and onBootRestoreFailed already reports it as mixed. The comment now says that. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XkGof8cLt23LkxZ8MKzin2 * ADFA-4128: serialize the cumulative asset merge against a second binder dispatch Payloads arrive on a oneway binder callback, whose thread pool can dispatch two at once - the interleaving PayloadPersistenceAtomicSetTest already pins for the persist. extractCumulative merged into the one shared override dir with no lock, so two merges could race entry-for-entry, and the pending marker cannot recover that: the second merge clears it on the way out, leaving the dir holding two generations with nothing left to notice. The new test holds the extractor's monitor and asserts a concurrent merge cannot finish, the same deterministic shape persistSerialisesOnTheStoreMonitor uses. Without the synchronized keyword it fails with "a merge ran to completion while the extractor monitor was held / expected to be false". Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XkGof8cLt23LkxZ8MKzin2 * ADFA-4128: stop two runtime tests passing for a reason they do not test LegacyResourceSwapSweepTest.isBestEffortOverAnApkItCannotDelete assumed setWritable(false) denies deletion. A root worker unlinks regardless, sweeps the apk away, and fails the test for a reason it is not about. It now probes the capability at stake - a sacrificial file in the same locked directory - and skips when that deletes, rather than inferring privilege from a uid or from user.name, which is not tied to the effective uid at all. PayloadPersistenceAtomicSetTest.concurrentDeploysAlwaysLeaveOneWholeLoadable- Generation read failure.get() straight after a timed-out join, so a worker still inside persist could record its failure afterwards and the test would have passed over it. Asserting the threads are not alive first closes that, and making them daemons stops a hung persist outliving the Gradle worker. Both proven by mutation: inverting the sweep test's guard reports it skipped with "this worker deletes despite the directory mode", so the probe reads the real filesystem capability; holding the store monitor across the joins leaves the pre-fix test green with both workers still running, and red on "dex deploy thread did not finish" with the assertions in place. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XkGof8cLt23LkxZ8MKzin2 * ADFA-4128: refuse an abandoned generation's swaps by generation, not by watermark A cold start restores persisted gen 10 on qb-boot-restore while CoGo's catch-up gen 11 arrives and fails; abandon(11) as a high-water mark then refused gen 10's queued swap, and a refused swap reports committed, so the restore logged success and recreated the activity over the baseline table with no banner. Abandonment is now a set of generations, pruned as swaps commit since the overtaken rule already covers everything below the committed one. The dropped-swap log lines name the actual reason. Answers #1716 (comment) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Y6rFuAJin4QFFwwzUTFZ1K * ADFA-4128: sweep the API 28/29 apk cache from the first write, off the main thread onActivityCreated runs on the main thread inside the first activity's creation, and the sweep it called there is a readdir plus one unlink per apk the previous process wrote - the launch-path disk IO the boot restore was just moved off main to avoid. The sweep now lives in ResourceStore, under the lock every legacy write takes, and runs once before this process writes its first relinked apk: that is still ahead of the first mount, it runs on whichever off-main thread the write arrives on, and no latch is needed to keep a deploy's write from racing it. Answers #1716 (comment) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Y6rFuAJin4QFFwwzUTFZ1K * ADFA-4128: stash a persisted generation for boot restore only when it carries resources loadPersisted stashed every adopted generation as pending boot resources, so a dex-only one - the usual case, and what a restart deploy persists - started a restore thread that swapped nothing, reported itself landed and recreated the first activity for it on every cold start. Only a Loaded with an arsc or an assets file is pending now. Answers #1716 (comment) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Y6rFuAJin4QFFwwzUTFZ1K * ADFA-4128: give the boot restore's failure listener the deploy path's two guards A persisted generation carrying a table and assets lands in two swaps. When the first failed, the listener reported the mixed state but never abandoned the generation, so the second swap committed anyway; and when both failed, onBootRestoreFailed ran twice - two banners, two crash reports for one boot. The listener now abandons the generation and reports only the first failure, which SwapAckGate.failed() reports by settling the gate exactly once. The deploy path's listener takes the same first-failure guard. Answers #1716 (comment) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Y6rFuAJin4QFFwwzUTFZ1K * ADFA-4128: report a payload that fails before acceptance instead of rolling back handlePayload's catch passed the pre-apply snapshot to failReload, but that snapshot is still null for a failure before the acceptance check - a dex read that throws, a malformed metadata document. failReload then restored null whenever the live generation equalled the failed one, so a replayed generation whose read failed went inert and quarantined the generation the app was running. A pre-acceptance failure now takes a report-only path: banner and crash report, no restore, no quarantine. Answers #1716 (comment) No JVM test: handlePayload needs ParcelFileDescriptor and SystemClock. Reasoned from the code, not run on a device. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Y6rFuAJin4QFFwwzUTFZ1K * ADFA-4128: keep unbind and rebind outside the client monitor, and drop the host on a null proxy abandonHandshake was synchronized as a whole, so unbindService - a synchronous binder transaction - ran under the monitor the framework's main-thread callbacks take, which is the stall dropHost's KDoc says the design avoids. Only the host test and the null write need the monitor. onServiceConnected's null-proxy branch left host set while it unbound and scheduled a rebind, and the rebind runnable returns early while a host is set; the other failure paths drop the host first, so this one does too. Answers #1716 (comment) and #1716 (comment) Not run on a device; both are reasoned from the code. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Y6rFuAJin4QFFwwzUTFZ1K * ADFA-4128: make the status banner a polite live region The banner is the only in-app trace of a failed deploy and takes no focus, so a screen reader never announced it. Answers #1716 (comment) Not yet checked with TalkBack on a device. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Y6rFuAJin4QFFwwzUTFZ1K * ADFA-4128: clear a persisted payload the stamped baseline rejects The store is keyed on the baseline dex alone, so a dex-identical rebaseline left the superseded epoch's files on disk and the next deploy's persist inherited that epoch's meta as its own history. selectPersisted now clears the store when it rejects a payload. Answers #1716 (comment) PersistedSelectionTest.aRejectedPersistedPayloadIsClearedFromDisk fails without the fix: "value of: load(...) expected: null but was: PayloadPersistence$Loaded@226b143b". Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Y6rFuAJin4QFFwwzUTFZ1K * ADFA-4128: pass the stamp read failure to the logger Answers #1716 (comment) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Y6rFuAJin4QFFwwzUTFZ1K * ADFA-4128: round 5 doc and comment fixes on quickbuild/runtime - FirstFrameGate: cite ADFA-5524 for the unreleased-recreate case. #1716 (comment) - PayloadStore.restore: documented as the test seam it is; production rollback goes through restoreIfCurrent. #1716 (comment) - AssetExtractor.writeFile: temp-deletion note in the description, remaining and the return documented. #1716 (comment) - ActivityTracker.onActivityCreated: the first activity attaches nothing; the boot restore's recreate delivers the restored table. #1716 (comment) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Y6rFuAJin4QFFwwzUTFZ1K * style: spotless reformat, no functional change Eclipse member ordering for the two Java files added in the round 5 fixes. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Y6rFuAJin4QFFwwzUTFZ1K * ADFA-4128: sample the drawn generation on the draw pass, not at completion onFirstFrameDrawn read the store's live generation when the posted completion ran. A deploy on a binder thread can persist, apply and arm a newer generation between a frame's draw pass and that message, so the completion acked the newer generation off a frame that drew the older one and markLiveGenerationGood wrote good.json for a generation that had never rendered - which quarantine() then refused to name. frameCompletion now samples the generation on the draw pass alongside the boot-restore flag, and drawn() and markLiveGenerationGood() take it instead of re-reading the store. A newer generation armed in the gap stays pending until a frame of its own. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0153YfwDnqn7ktHcVgXSNNe8 * ADFA-4128: keep the boot restore's payload files out of the orphan sweep collectOrphans protected only the just-published names and good.json's. On a cold boot the adopted generation has never drawn, so good.json does not name it, and the restore opens its files one at a time (on API 28/29 the whole relinked apk is copied before the assets zip is opened). A catch-up deploy persisting in that window swept the not-yet-opened file, openReadOnly threw, and a deploy that succeeded raised the mixed banner plus a crash report. PayloadStore now retains the boot-pending names in the persistence store when it stashes them, the sweep treats them as referenced, and the runtime releases the hold once the restore has landed or failed. The set is a volatile replaced whole so the main-thread release never contends with a persist for the store's monitor. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0153YfwDnqn7ktHcVgXSNNe8 * ADFA-4128: guard the two apply catches on the swap gate too onSwapFailed returns unless gate.failed() settled the gate, but the catch blocks in handlePayload and restoreBootResources called failed(), discarded the result and reported regardless. A persisted generation carrying both a table and assets could then report twice for one boot: the table swap fails on main and reports, the restore thread throws in applyAssets, and the catch reports again - a second mixed banner and a second crash report. ackGate moves out of handlePayload's try so its catch can ask it, and both catches now report only when they are the first to settle the gate. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0153YfwDnqn7ktHcVgXSNNe8 * ADFA-4128: put both apply catches through one tested report-once seam The previous commit added the two guards but left them unreachable from a JVM test: SwapAckGateTest pins SwapAckGate.failed()'s idempotence in isolation, so deleting either `if` and reporting twice again kept the whole suite green. The defect they fix is user-visible - a second mixed banner and a duplicate crash report for one save - so it needs a test that goes red. handlePayload's catch and restoreBootResources' catch now both call reportApplyFailureOnce(gate, report), which owns the guard AND the report it gates. That is what makes it testable: a bare boolean helper would still leave the `if` at each call site, deletable with the suite green. The Runnable seam follows completeOnResume, the same shape already in this file for the same reason - the real call sites need a binder thread, a main looper and a Context, so the branch is otherwise checkable only on a device. Behaviour is unchanged. handlePayload's negative branch fell through to the `finally` via `return`; it now falls through by not running the body, and the boot restore's unconditional abandon stays ahead of the guard. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015WMhaGYg4sSCcAzQEdNtLa * ADFA-4128: say what PersistedSelection gates instead of naming a test scenario Akash on #1716: "the S7 fix" is our shorthand for a scenario in a test-plan doc, so a later reader of this class has nothing to look it up in. The sentence after it already says what the gate does, so the shorthand was carrying no information the KDoc did not otherwise have. The test's class comment used the same two phrases; it now names the class under test and the failure the mutation reproduces. Comments only. No behaviour change. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XN91S41dghWHoXAoWhJAnm --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
fryanpan
added a commit
that referenced
this pull request
Oct 2, 2026
…the benchmark harness (#1723) * ADFA-4128: style: spotless reformat of the build-status layout, no functional change The Eclipse WTP formatter's own output on layout_editor_build_status.xml, landed standalone as the repo's code-style section prescribes. It reindents the file to tabs and rewraps the license header into one run, which is most of the diff and none of the meaning. Split out so the two-attribute change that follows is readable on its own. https://github.com/appdevforall/CodeOnTheGo/pull/1723#discussion_r3926569470 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017o3nPrBbGi2XYkMUGavG2A * style: spotless reformat of SaveFileAction.kt, no functional change The Spotless ratchet reformats a touched file in full; this commit carries that reformat alone so the three-line change that follows reads as three lines. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XkGof8cLt23LkxZ8MKzin2 * ADFA-4128: qb 11/11 app + bench — wires Quick Build into the IDE and adds the debug-only benchmark harness The app wiring and the bench surface land together because they are mutually dependent: :app's ProjectHandlerActivity and QuickBuildModule call into QuickBuildBenchHooks, and QuickBuildBenchHooks returns AutostartBuild and resolves EnvironmentQuickBuildPaths. Neither ordering of a two-PR split compiles. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Kj9YeCDHGp9DU8LPtfWJ7W * ADFA-4128: qb 11 review fixes — install rotation window, flag-off intent, test gaps Important 1 (daemon idle-timeout/Metaspace tuner un-gated): kept un-gated by design — the 384m Metaspace floor fixes real OOM-killed builds and the tiered idle timeouts keep low-RAM devices from losing the IDE to lmkd; GradleBuildTuner now states this in its KDoc. Ships flag-off; needs Bryan sign-off in the PR body. Important 2 (generateSources narrowing un-gated): judged a genuine all-users improvement, not QB-specific — the old code ran a Gradle generateSources after EVERY save-all (and after any XML save in SaveFileAction), a per-save build tax; flag-off the deferral degenerates to the same immediate call, so the narrowing is the only behavior change. Known trade (manifest-only edits leave generated Manifest/R intermediates stale until the next resource save or build) now stated at both call sites. Ships flag-off; needs sign-off in the PR body. Important 3 (install dropped on rotation, flag on): installApk's async path now re-arms AwaitingInstall (BuildViewModel.reArmInstall, fires only from Idle) from the coroutine's drop path, so a configuration change during the APK-manifest parse makes the recreated activity's collector retry the install instead of silently losing a successful build. Covered by BuildViewModelInstallReArmTest. Test gap (zip-slip guard): extraction loop extracted to QuickBuildArtifactStager.extractDaemonZip(InputStream, File); the guard is watched going red by QuickBuildArtifactStagerTest (a ../ entry throws and nothing lands outside the daemon dir). Test gap (InstallationEventFlow mapping): InstallationEventFlowTest pins the PackageInstaller status mapping, including the ABORTED-vs-FAILURE branch order and the no-extras / no-status paths. Test gap (service-side output capture): suppress/capture/drain routing extracted from GradleBuildService.logOutput into InternalBuildOutputCapture; bounded tail, drain-clears, throwing progress listener and editor-listener routing pinned by InternalBuildOutputCaptureTest. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Kj9YeCDHGp9DU8LPtfWJ7W * ADFA-4128: state the daemon-timeout rationale without a benchmark multiple The Gradle daemon idle-timeout comment quoted a speedup multiple, which put a benchmark figure into shipping production code. The reason the timeout is generous is structural - a warm daemon skips the cold start, which dominates a short rebuild - so the comment now says that instead. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Kj9YeCDHGp9DU8LPtfWJ7W * ADFA-4128 (11/11): address CodeRabbit review - F1723-1 put QuickBuildPipelineTest into the suite that actually runs - F1723-4 guard the deferred prebuild fire() against a throw Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FstXxJ5cwWPcvmhZ9vJgJ7 * ADFA-4128: manual-QA script fixes from the 22-test device walk The script has had one commit and predates several behaviour changes, so a walker following it literally hits steps that cannot reach their stated end state. None of these are product defects - the walk found no product failure. - T7 criteria 4/5 described the pre-b6cddf035 world where rebaseline left the app un-relaunched and a tap was needed. Rebaseline relaunches now. - T7b step 2 and T11 step 3 end at a modal OS install prompt that never times out; "do not tap anything" could not reach the end state. - T1 gains a FAB baseline tap. Five later tests assert through the FAB, so a dead FAB failed them all with no way to tell when it broke. - T1 gains a note that project creation already ran a setup build, so the first tap measures warm provisioning, not cold. - T14's "no reinstall unless the bytes changed" inverted the design: the generation stamp lives in the APK, so a restart always mints new bytes. - T20 names service-app; only 4 of 30 corpus apps declare a Service. - T21 drops "wrap and push sora-editor-full first" - already wrapped, with all 288 source files. Adds a "Traps that make the product look broken" section for the four method errors that produced wrong findings: tapping the geometric centre of a view that extends under a system bar, Find-in-file being a regex search, relaunching CoGo via monkey when it declares two LAUNCHER activities, and selecting a wrapped corpus copy by mtime when the newest is pinned to AGP 9. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FstXxJ5cwWPcvmhZ9vJgJ7 * ADFA-4128: doc corrections from the PR review sweep Six places where a Quick Build doc contradicted the code it describes. Each was re-verified against the source rather than taken from the review comment. - debugging.md: the deploy round-trip row said the 15 s bound covers "one AIDL onPayload call". IQuickBuildTarget is a oneway interface, so that call returns immediately; DeployChannel wraps the call plus the wait for a generation-matched report, which is what its own KDoc already said. - why-not-android-jar.md: listed native libs as hot-loadable. A .so under jniLibs forces a Gradle fallback (ChangeClassifier). Loadable at runtime and changeable via live reload are different properties. - reliability-gaps.md: "five user-facing defects" against three fixed and four open. Seven were surfaced; the fixed three are relink-stuck, #88 and #90. Also states why Blocks v1? reads TBD - the decision at the top is a proposal, and the cells become "No" when it is confirmed. - low-spec-devices.md: stated an inferred mechanism (SerialGC thrashing in a small heap) as the confirmed cause of the 1.9 GB failure. The outcome is measured; the mechanism is not, and the uncapped run that would confirm it is still unmeasured. Retitled to what was actually observed. - concurrency.md: the tap-races-its-own-save section read as current behaviour. It describes the pre-2026-08-13 design that the redesign below it replaced. - perf-roadmap.md: incomplete sentence. Not applied: CodeRabbit's finding that manual-qa.md's screenrecord --time-limit 1740 is invalid because AOSP caps at 180 s. False on our hardware - recordings of 1774 s, 2432 s, 2592 s, 2842 s and 3534 s have all completed on the A56, and the surrounding comment already documents the real 30-minute cap that 1740 sits under. Applying it would break working recordings. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FstXxJ5cwWPcvmhZ9vJgJ7 * ADFA-4128: three small UX fixes found by the manual QA walk None of these is a Quick Build failure - the walk passed every test. They are places where the product is correct and unhelpful. A2 - the bolt read identically to a screen reader in READY and ERROR. Every tone has its own icon shape, so a sighted user can tell them apart; collapsing them all to "Quick Build" hid that distinction from exactly the user who cannot see the icon. ERROR, SLOW and RECONNECTING now announce their state. BUILDING and the standard-build-blocked case already did. A3 - after an undeliverable build the bar read "built, but could not be delivered - see Build Output" on every poll, while the sentence naming the fix ("Your app is not running. Tap Quick Build to start it with your changes.") was only in Build Output. The bar now names the tap when that is the whole problem. Carried as a typed flag rather than matched on the message text, the same way proxyAppNotConnected already is, and kept separate from it because they mean opposite things: appNotRunning is "nobody opened it", proxyAppNotConnected is "we launched it and it still did not arrive". A4 - an orphaned proxy app reported CoGo's expected connect() rejection at W on every attempt of the rebind backoff loop, 14 times in one restart window. The behaviour is right (it continues standalone); repeating an expected rejection at W buries the entries around it. Reported once per streak now, cleared by a successful connect. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FstXxJ5cwWPcvmhZ9vJgJ7 * ADFA-4128: record the v1 blocking decision instead of proposing it The page proposed that #87, #89, #91 and the relink-crash gap go to v1.1, then left the table's "Blocks v1?" column reading TBD on all four rows. A proposal in a title and a TBD in a table say different things to a reader, and CodeRabbit flagged the pair as an internal inconsistency. Decision confirmed 2026-08-25: none of the four block v1. The four cells now read "No - v1.1", the title states the answer rather than asking it, and the prose no longer describes itself as awaiting confirmation. No change to any gap's evidence, root cause, or fix. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FstXxJ5cwWPcvmhZ9vJgJ7 * ADFA-4128: qb 12 review fixes - two doc corrections held on the trailing branch F1713-3 (docs/concurrency.md): the thesis said every expensive thing runs in another process while the table directly below it put the mtime poll and the install call on Dispatchers.IO inside CoGo. Name the exception. F1713-8 (docs/manual-qa.md): files are not killed, processes are - and a teammate follows this runbook literally while holding a half-recorded QA session. Say screenrecord. Both patch text that exists only in the four trailing commits, so they could not ship until those commits had a home. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xsc7AMGBVyEMfrwpZX87iC * ADFA-4128: two review fixes - a repeated clobber prompt and a hard-coded Cancel Both found by running the CodeRabbit CLI over this branch ourselves, since the pull request's 119 files exceed the bot's 100-file cap. - installApk consumed the tap-time clobber answer before entering the coroutine, so the answer left the ViewModel whether or not the install went on to dispatch. A rotation during the manifest parse cancels the coroutine, the finally block re-arms the install, and the retry then ran with no tap-time answer - asking the user to confirm the same overwrite a second time. The method's own KDoc says the re-check is silent unless the answer moved, which is precisely what this broke. The consume now sits inside the coroutine after the destroyed check, on the path that actually dispatches; there is no suspension point between it and the dispatch. Verified with a throwaway harness rather than a committed test: it drove the real BuildViewModel and installTimeClobberConfirmation through both orderings with a real cancellation, showed the old ordering re-asking and the new one silent, and was watched going red under a mutated expectation. It is not committed, because ProjectHandlerActivity is abstract and untested by any of the 64 JVM test files in app/src/test, so a committed version would model the ordering rather than read it and would stay green if the line moved back. The ordering is guarded by review only. - QuickBuildScreen.declineClobberConfirm matched a hard-coded English "cancel" while its sibling acceptClobberConfirm resolved its label from resources, so the decline path alone broke on a non-English device. Both confirm paths reach one builder, which sets android.R.string.cancel, so the framework string is the right resource. Compiles; runtime behaviour on a non-English locale is unverified. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xsc7AMGBVyEMfrwpZX87iC * ADFA-4128: the CoGo reload-crashed notice carries the same text as the phone banner Bryan pinned the wording on 2026-08-28 for the phone banner; the CoGo-side notice for the same event still said "Your app crashed... Fix the crash and save", which sends the user to fix code that was fine. The state is only ever set from failReload, so the reload machinery failed, never their code. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xsc7AMGBVyEMfrwpZX87iC * ADFA-4128: resolve ProvisioningFailedUnexpectedly to a user-facing string qb-08's review fix added QuickBuildMessage.ProvisioningFailedUnexpectedly, the named case for a provisioning throw with no message of its own. The exhaustive when in QuickBuildMessages.resolve had no arm for it, so the restacked qb-11 would not compile. Maps it to quick_build_provisioning_failed_unexpectedly, worded like the neighbouring quick_build_setup_failed, and pins the mapping in QuickBuildMessagesTest alongside the other valueless cases. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01STCsdMzx9daNBcqMN424Ci * ADFA-4128: reliability-gaps #89 is fixed in-stack, not open Entry #89 (failed daemon respawn strands the session) was written against the prototype and went stale: the missing QuickBuildTapped arm in reduceDegraded landed with qb-08's review fixes, a failed respawn now dispatches DaemonRestartFailed and surfaces a message, and qb-07's trim-memory redesign no longer bumps the daemon epoch. Move #89 from the open list to fixed-on-this-branch, update the counts and decision line, and state what remains: no device repro on either side of the fix, so the recovery arms are host-tested only. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VwVV7PzMYinSiq6FwC83Vw * ADFA-4128: 0902 review round on the app wiring Akash's 2 September round on the IDE-side wiring, plus the seven findings from 08-31 that were still open at head. - The long-press dropdown's Quick Build row presents the same state as the button it hangs off: the toolbar's own prepare() already decided whether a build can start and what a tap does, and the row ignored both - so it offered "Quick Build" while the button was a stop button, and a tap there cancelled the build the user was waiting on. https://github.com/appdevforall/CodeOnTheGo/pull/1723#discussion_r3916777814 - The plugin save path gates generateSources on resourceXmlSaved and routes it through GenerateSourcesDeferral, the same as the two UI save paths. It was the sibling the narrowing missed, so a plugin's save still ran a full generateSources on every xml and raced a live session's build. https://github.com/appdevforall/CodeOnTheGo/pull/1723#discussion_r3916777825 - MODE_STANDARD says what it measures: the build only, because the install dialog an unattended run cannot answer is suppressed. Quick Build's arm measures build, deploy and reload, so the two are not like for like from in-app numbers alone. https://github.com/appdevforall/CodeOnTheGo/pull/1723#discussion_r3916777832 - A landed build is announced once, not on every re-subscribe. The status is a StateFlow and the editor re-collects on every return to it, so the first emission was re-announcing "reloaded in 2.0s" over whatever the bar held, minutes after the build. Pinned by a test that fails without the guard. https://github.com/appdevforall/CodeOnTheGo/pull/1723#discussion_r3916762978 - The status text takes three lines instead of one, and the app-not-running line is short enough to read: at 92 characters it was cut mid-sentence on a 360dp phone at the DEFAULT font scale, and the half that was cut was the remedy. https://github.com/appdevforall/CodeOnTheGo/pull/1723#discussion_r3916763766 - The install's clobber dialog is awaited, so the coroutine is alive while it is up and an activity destroyed under it re-arms AwaitingInstall. dispatched moves after the decision. Moving it into the dialog callbacks as suggested does not work: they run long after the coroutine body returns, so the re-arm fires mid-dialog and loops - the re-armed AwaitingInstall shows a second dialog behind the first, and a decline shows a third. https://github.com/appdevforall/CodeOnTheGo/pull/1723#discussion_r3916762606 - Only Needed tears the session down. NeededForUnknownAppId means the APK's own package did not parse, and a transient read cost the user their warm session. https://github.com/appdevforall/CodeOnTheGo/pull/1723#discussion_r3916763243 - A Quick Build tap with no activity refuses rather than building: it needs one to flush the editor buffers and to ask about a clobber, and no caller reaches it today. https://github.com/appdevforall/CodeOnTheGo/pull/1723#discussion_r3916763508 - Three unused imports, deleted by hand - ktlint's detection is substring-based, which is why a green spotlessCheck did not catch them. https://github.com/appdevforall/CodeOnTheGo/pull/1723#discussion_r3916763971 https://github.com/appdevforall/CodeOnTheGo/pull/1723#discussion_r3916764225 - manual-qa gains a font-scale block (T22): the 1.0/2.0 pass over the toolbar, the dropdown, every actionable status line and the clobber dialog. It is the check that would have caught the truncation above. Two repairs the round did not ask for, found running the suite: :app's unit tests did not compile at head (SessionRestartAndReprovisionRequested is a data class and was referenced without its parentheses), and once they did, FeatureFlagsTest failed five ways - it reflects on a `by lazy` property's field, which is named downloadsDir$delegate and holds the Lazy. Both are one-liners and :app:testV8DebugUnitTest is green. The clobber gate follows the PR below it: QuickBuildClobberCheck's two reads became suspend there, so quickBuildClobberConfirmation takes a suspend probe and the two ensure*ClobberConfirmed gates own the coroutine rather than pushing it onto their click-handler callers. And the unused-import sweep keeps models.Position: stage's deep-link work landed a use for it while this branch was out. Not fixed here: requestDowngrade's unwired parameter, the bench re-open against a stale project model, and the run-on GPL header - all deferred with reasons in this round's replies. The PR description's own MODE_STANDARD_E2E claim and the BenchQuickBuildMetricsSink field name are description work, drafted there too. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017o3nPrBbGi2XYkMUGavG2A * ADFA-4128: stop a re-subscribe re-stomping the bar with the start-failed line Answers review threads 3926567255 and, for the ProjectHandlerActivity half, 3926567267. 3926567255 is confirmed. previousStatus lived inside the launch{} that repeatOnLifecycle(STARTED) restarts, so it reset to null on every onStart while status is a replaying StateFlow. Quick Build fails to start, the user runs Standard Run successfully, backgrounds CoGo and returns - and the replayed Hidden(lastStartFailed) writes "Quick Build: could not start" over the standard build's own result line. The suggested remedy - onlyIfOwned = true on this Show - is not taken, because it regresses `a failed start still shows after an activity recreation`. ownsQuickBuildStatus is an activity field, so after a genuine recreation nothing owns the bar and a gated Show would be dropped; that test pins the correct behaviour and stays as it is. Fixed at the caller instead, which is where the defect actually is: the previous status moves into QuickBuildStatusTracker, held by the activity, so a re-subscription is no longer read as a first emission (previous == current short-circuits to None) while an activity recreation still gets a fresh tracker and writes the line. This closes the same class of stomp for every first-emission-writing transition, not only StartFailed. Not closed by this commit: the session manager is a Koin process singleton, so if a NEW activity opens the next project while the sticky flag is still set, that fresh tracker will show the line over "Project initialized". That half needs the flag cleared at session scope and is not attempted here. 3926567267, doc half: quickBuildSessionManager()'s KDoc claimed resolving the Koin singleton is cheap and that nothing spawns until the first quick build. Both sentences were wrong. observeStates() resolves it from onCreate on the main thread (as do onTrimMemory, onBuildServiceConnected and QuickBuildAction.prepare()); the history store's constructor reads shared preferences and the default scratch reads noBackupFilesDir, so it does main-thread disk I/O; and the manager's init block installs the daemon death listener plus FIVE coroutines, not the three the review counted. The KDoc now says that, and names what genuinely still waits for the first tap: the daemon process and the host service binding. The off-main warm-up the review asks for is deferred, not done here. Test: `a re-subscribe does not re-announce a failed start` in QuickBuildStatusBarTest. Verified to fail without the fix by making QuickBuildStatusTracker.record a no-op, which is exactly the pre-fix collector-scoped behaviour. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017o3nPrBbGi2XYkMUGavG2A * ADFA-4128: correct the Koin module's "nothing spawns" claim Continues review thread 3926567267. The module's KDoc said everything is a lazy singleton and nothing spawns until the first lightning-bolt tap resolves the session manager. The lazy half is true of the daemon process and the host service binding only: resolving the session manager itself reads shared preferences and noBackupFilesDir and installs the daemon death listener plus five coroutines on the session executor, and ProjectHandlerActivity resolves it from onCreate on the main thread whenever experiments are on. Say that. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017o3nPrBbGi2XYkMUGavG2A * ADFA-4128: the status line cannot be read by swiping the sheet up Answers review thread 3926567718, the half that is a false claim in our own documentation. Both the layout comment and manual-qa.md T22 told the reader that a status line too long for the collapsed header is read by swiping the sheet up. It is not: EditorBottomSheet.onSlide sets the header container's height to (collapsedHeight + padding) * (1 - sheetOffset), so expanding the sheet drives the header to zero and the line disappears. No other surface shows this text, so anything that overflows the fixed 100dp header is lost. T22 now says to record an overflow as a failure rather than swiping to check, and says plainly that whether the bar actually overflows at font scale 2.0 is unmeasured - nobody has run that step on a device, so the arithmetic (three lines of BodyMedium at 2.0 plus the 11sp hint, against 100dp) is an expectation, not a result. The layout remedy the review asks for - letting the header wrap its content with 100dp as a minHeight - is not in this commit: it changes a shared container that every build status shares, and it wants a device run to confirm. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017o3nPrBbGi2XYkMUGavG2A * ADFA-4128: style: spotless reformat of QuickBuildStatusBar, no functional change spotlessApply's own output, landed standalone as the repo's code-style section prescribes. ktlint converts quickBuildStatusBarUpdate's block body with a single return to an expression body; nothing else changes. Worth recording because it is not collateral from this round's work: the violation is in a function these commits do not touch, and it is present at 8f79f47e, this PR's head before any of them. spotlessCheck was already red on this branch - it is green on the gradle-plugin PR below it because QuickBuildStatusBar.kt is added here, so the ratchet never saw it there. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017o3nPrBbGi2XYkMUGavG2A * ADFA-4128: delete the requestDowngrade parameter and its plumbing Closes ADFA-5462. The parameter threaded a downgrade request from the installer entry point down to a reflective @SystemApi call on PackageInstaller.SessionParams, but no caller ever passed it: every call site took the false default, so the reflection, the API-29 guard and the intent-installer warning branch were unreachable. It was added for a same-app-id Quick Build restore that would install the real app over a higher pinned test versionCode. That restore path is not in the stack, so the plumbing is speculative rather than dormant, and reflection into a hidden API earns its keep only once something calls it. Removed: the parameter on installApk, installUsingSession and createSessionParams, the reflective setRequestDowngrade block, the isAtLeastQ guard and its now-unused import, the intent-installer cannot-downgrade warning, and the pass-through on ApkInstallationViewModel.installApk. No behaviour changes for any existing caller, because none of them set it. A repo-wide grep over Kotlin, Java, Markdown and XML finds no remaining reference to requestDowngrade. :app:compileV8DebugKotlin and spotlessCheck are both green. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017o3nPrBbGi2XYkMUGavG2A * ADFA-4128: give DaemonStartFailed the user-facing string it was missing The core module gained a DaemonStartFailed message, but the app's resolver never grew an arm for it, so the app module did not compile: "'when' expression must be exhaustive. Add the 'is DaemonStartFailed' branch". Adds the string and the arm, and pins the mapping in the resolver test so a future arm pointed at the wrong resource fails rather than compiling. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017o3nPrBbGi2XYkMUGavG2A * ADFA-4128: build the Quick Build graph off the main thread The first resolve of the session manager constructs the whole Quick Build graph: the history store reads shared preferences and the paths object reads noBackupFilesDir, so it is disk I/O, and it ran on the main thread at project open whenever experiments were on. observeStates is already inside a coroutine, so the resolve moves to Dispatchers.IO there. It stays inside repeatOnLifecycle, so a resolve that failed once is still retried on the next return to the editor rather than cached as null. Every other call site runs after it and finds a built singleton, so those pay only a map lookup. Not covered by a JVM test: the change is a dispatcher hop inside an Activity's onCreate coroutine, and asserting it would amount to asserting withContext itself. The check that means anything is on-device - open a project with experiments on under StrictMode's main-thread disk-read detection and confirm no violation is attributed to the Quick Build graph. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017o3nPrBbGi2XYkMUGavG2A * ADFA-4128: pin the failed-start tone to the session's lifetime The review thread asked for the failed-start bar line to be ownership-gated, so a start failure in one project cannot stomp the next project's line. The gate is the wrong lever - the ownership flag is an activity field, so gating would also drop the line after an activity recreation, where it must come back. What actually separates the two cases is the flag's lifetime, and the reducer already ends the failed-start story on a teardown: from Idle in the SessionRestartRequested arm, and from any other state in the top-level teardown guard. Closing a project sends exactly that event. Nothing was missing in production code, only the test that says so. Adds two tests, one per arm. Both were checked against a mutant: removing the Idle arm fails the first with "expected: Clear / but was: Show(...)", and having the top-level guard keep its state instead of resting at Idle fails the second. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017o3nPrBbGi2XYkMUGavG2A * ADFA-4128: size the collapsed sheet header to the status text, not to a fixed 100dp At font scale 2.0 on the A56 the two longest status lines wrapped to two lines and clipped at both the top edge of the collapsed sheet and the swipe hint below them, and the hint truncated with no ellipsis in every state. Swiping up does not help: onSlide scales the header to zero, so a line that does not fit is lost. The collapsed height is now the larger of the dimen and what the status block measures, so ordinary text keeps the familiar height and larger text gets the room it needs. The block is measured against an unbounded height on its own, because the header's height is set explicitly for the slide and so cannot wrap. The re-measure runs after a status change and only applies while the sheet is collapsed; mid-slide the height belongs to onSlide, which reads the same value. The swipe hint gets ellipsize=end with maxLines=1. It is the one disposable string here - it names a gesture rather than a remedy. No JVM test is possible for this: it is view measurement, and the app module has no Robolectric surface for the bottom sheet. The check that means anything is the manual-QA font-scale block, at 1.0 and 2.0 on a device, which this pass did not run. The doc is updated to say the fix itself is unverified there. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017o3nPrBbGi2XYkMUGavG2A * ADFA-4128: give the swipe hint room inside the collapsed sheet Two defects hid the hint. 978ff9b21 measured the build-status block against an UNSPECIFIED height spec, which ConstraintLayout does not support: it answered 146 px for a block whose own children reached 262 px, and that stale figure became the block's laid-out height, clipping the hint away. Measure against a bounded AT_MOST spec instead, and ask for a real layout pass afterwards, since the manual measure ran outside one. Second, and older than that commit: the sheet carries the status bar's height as top padding for its expanded state, so a 100dp header did not fit in a 100dp peek and its bottom 108 px fell below the window. The peek now covers that chrome as well as the header. Measured on the A56 at font scale 1.0, 2.0 and 3.0 in the ready, live-reloaded and BUILD FAILED states: the hint renders at 36, 77 and 115 px, and no content bound passes y=2205, where the navigation bar starts. At 3.0 the status block measures above the dimen floor, so the header and peek grow with it - the growth path 978ff9b21 intended, which the bad measure had made unreachable. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017o3nPrBbGi2XYkMUGavG2A * ADFA-4128: keep the collapsed sheet at its earlier height Drops the peek-height half of aae4bc519 by decision: the sheet's collapsed peek is the header height alone again, so the sheet sits where it did before the hint fix. The measure fix (AT_MOST instead of UNSPECIFIED) stays, so the status block is laid out at its real height; the hint can still clip where the sheet's status-bar padding pushes the header bottom below the window. The QA doc says so and asks the tester to record hint visibility. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017o3nPrBbGi2XYkMUGavG2A * ADFA-4128: request a header layout only when the status height changed refreshCollapsedHeight called header.requestLayout() before its unchanged-height early-out, and setStatus posts it after every write - including every Gradle progress line, for every user, with the Experiments flag off. That was an off-pass measure plus a forced layout per task event during a standard build. The request now sits below the early-out, where it runs only when the measured height differs; the text change itself is already laid out by setText. Review: https://github.com/appdevforall/CodeOnTheGo/pull/1723#discussion_r3934054531 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XkGof8cLt23LkxZ8MKzin2 * ADFA-4128: a manifest save runs generateSources again resourceXmlSaved was modified && isXml && isAndroidResource(), and ProjectManagerImpl.isAndroidResource matches only a module's resource directories. AndroidManifest.xml sits outside all of them, so a manifest-only save no longer refreshed the generated Manifest class or the merged manifest - for every user, flag off - where the old xmlSaved gate did. The flag fold now sets it for AndroidManifest.xml by name, without paying for the resource lookup. Both call-site comments and the SaveResult doc drop the "known trade" wording, and manual-qa.md gets T23 for the manifest walk. Review: https://github.com/appdevforall/CodeOnTheGo/pull/1723#discussion_r3934054548 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XkGof8cLt23LkxZ8MKzin2 * ADFA-4128: spell the product "Code on the Go" in the unknown-app dialog quick_build_switch_unknown_app_message said "Code On The Go" while quick_build_reload_crashed four lines below, app_name and every other user-facing string in this file say "Code on the Go" (44 occurrences; the capitalised form survives only in three legacy strings). Both feed Crowdin, so the mismatch would have shipped into every locale. The new string follows the file, not the docs' spelling. Review: https://github.com/appdevforall/CodeOnTheGo/pull/1723#discussion_r3934054610 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XkGof8cLt23LkxZ8MKzin2 * ADFA-4128: hand status-line ownership to every writer, the debugger included ownsQuickBuildStatus was cleared only in setStatus, but the debugger writes its "Debugger starting" / "started" / "starting failed" lines through doSetStatus directly (BaseEditorActivity). With Quick Build still owning the bar, a passive refresh then overwrote the debugger's line and a session end blanked it. The clear moves into a doSetStatus override, so ownership tracks whoever writes the bar rather than whoever writes it through one overload. setStatus is now a plain forward. Review: https://github.com/appdevforall/CodeOnTheGo/pull/1723#discussion_r3936706719 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XkGof8cLt23LkxZ8MKzin2 * ADFA-4128: arm the bench standard-build latch only once the build holds the slot fireAutostartStandardBuild stamped standardBuildStarted and then called runQuickBuild, which returns without publishing a state when a build is already in progress. The stamp then stayed armed until the user's next build reached a terminal state, and standardBuildEnded read that build as the autostarted one: install suppressed, no message. The stamp now runs in runQuickBuild's beforeBuild, which only executes after the slot was claimed. Debug source set and bench flag only; no release path changes. Review: https://github.com/appdevforall/CodeOnTheGo/pull/1723#discussion_r3936706890 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XkGof8cLt23LkxZ8MKzin2 * ADFA-4128: never fire a bench autostart against another project's workspace A bench open for project B while project A was initialized set ProjectManagerImpl.projectPath = B and started the single-top editor; its onNewIntent (run before EditorHandlerActivity's switch logic) claimed the latch against the already-moved projectPath and tapped Quick Build with A's module model still in the workspace. Two guards: the editor claims only when the intent's project is the one its workspace was synced for, and the bench trampoline refuses a different-project open while one is initialized - the close-and-reopen path needs a dialog an unattended run cannot answer, and the harness force-stops between projects anyway. Debug source set and bench flag only. Review: https://github.com/appdevforall/CodeOnTheGo/pull/1723#discussion_r3934056407 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XkGof8cLt23LkxZ8MKzin2 * ADFA-4128: build the Quick Build graph once, off the main thread, and read it everywhere else observeStates warmed the Koin graph on Dispatchers.IO, but the main-thread callers (the toolbar action, the bench autostart, the stagger) resolved the same singleton themselves through GlobalContext, so whichever ran first paid the graph build on the main thread and the warm-up was ordering-dependent. QuickBuildGraphWarmUp owns the one resolve: warmUp() builds off-main, sessionManagerOrNull is a plain read that never resolves, and the two callers that must not miss the manager await() it. Review thread: https://github.com/appdevforall/CodeOnTheGo/pull/1723#discussion_r3934056178 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XkGof8cLt23LkxZ8MKzin2 * ADFA-4128: never wipe the daemon directory under a live compile daemon Every provision, prebuild and rebaseline re-extracted the 62 MB daemon zip after deleting daemon/. A rebaseline runs while the compile daemon is alive and loading jars from that directory lazily, so the wipe could turn an unopened jar into a NoClassDefFoundError inside the daemon. The extraction is now skipped when a stamp keyed on the installed APK's versionCode and lastUpdateTime matches and the daemon jar is present; the stamp is written last so a crash mid-extract re-stages. An APK update force-stops the app and its children, so the one path that does wipe never runs under a daemon. Review thread: https://github.com/appdevforall/CodeOnTheGo/pull/1723#discussion_r3936707073 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XkGof8cLt23LkxZ8MKzin2 * ADFA-4128: cover the analytics sink's fallbacks, route and outcome names The sink's branches for a completion with no matching start, a success without a start, the outcome and route metric names, an invalidation reason and a failed proxy rebuild had no test. Each case now pins the emitted field values. Review thread: https://github.com/appdevforall/CodeOnTheGo/pull/1723#discussion_r3934054575 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XkGof8cLt23LkxZ8MKzin2 * ADFA-4128: adopt the suspend contracts and surface compile diagnostics in the app GenerationTracker is now opened through its suspend GenerationTracker.open, so the provisioner's baseline allocator becomes a suspend lambda; its one call site already runs inside the suspend proxy app build. A build that landed with warnings now lists them in the Build Output under the "reloaded to generation" line, indented like a failed build's errors, from the diagnostics the orchestration branch carries on QuickBuildStatus.UpToDate. A warning the Gradle build would print no longer disappears because the quick build succeeded. Review threads: https://github.com/appdevforall/CodeOnTheGo/pull/1719#discussion_r3934045941 https://github.com/appdevforall/CodeOnTheGo/pull/1719#discussion_r3934045924 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XkGof8cLt23LkxZ8MKzin2 * ADFA-4128: lay the collapsed header out inside the window so the swipe hint survives 2x fonts The sheet carries the status bar's height as top padding, but its collapsed peek is the header height alone (2748c9ae3), so the bottom paddingTop + divider px of the header (48 on the A06, 108 on the A56) hang below the window. The status block filled the whole header and its spread chain put the hint last: at font scale 1.0 the chain's slack matched the hidden strip and the hint ended at the window edge; at 2.0 the taller text shrank the gaps and the hint's lower 18 px went under the navigation bar (device pass 0904, row C23). The header now carries that strip as bottom padding, so the block is laid out in the part that is on screen, and the collapsed height adds the strip back only when the block measures more than the floor leaves visible. The block is included with match_parent so it fills the header's content box, and its minHeight goes: the floor lives in code. The padding is re-applied on the sheet's layout changes, since the status-bar inset can land after setOffsetAnchor first padded the header (and that call no longer accumulates paddingBottom on a second anchor). Measured on the A06 (300dpi, font scale 1.0 and 2.0, idle and through a Gradle sync): the header stays [1370,1510]; the hint renders in full at [1457,1482] and [1448,1499], above the navigation bar at 1510. The sheet's on-screen height is unchanged. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XkGof8cLt23LkxZ8MKzin2 * ADFA-4128: tidy the stager KDoc and the no-op autostart branch The QuickBuildArtifactStager class doc said a rebaseline provisions while the compile daemon is running and that the stamp keeps that safe. The rebaseline shuts the daemon down before its Gradle build, and no path stages under a live daemon, so the doc now says what the stamp does: skip a 62 MB re-extraction unless the APK changed, and keep the one wipe path behind an APK update that force-stopped the app. The AutostartBuild.NONE arm loses its explicit Unit. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XkGof8cLt23LkxZ8MKzin2 * ADFA-4128: keep the deferral's own generateSources build out of the hand-back The hand-back marks a live session's baseline untrusted after every finished Gradle build, and the resource-save generateSources build the deferral parks is one of them - so the session paid a full recompile for a build that regenerates nothing it compiles against (the daemon's classpath is the payload jars the proxy app build diverted at provisioning, not the intermediates R.jar). The deferral now remembers a dispatched build and the hand-back claims that build's completion once; the next build to finish after a dispatch is the deferral's, since the tooling server runs one build at a time and generateSources refuses while another is in progress. Review: https://github.com/appdevforall/CodeOnTheGo/pull/1723#discussion_r3951660753 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Y6rFuAJin4QFFwwzUTFZ1K * ADFA-4128: size the header for the status block only while it is the child on show The measured status height went onto header_container, a ViewFlipper, so a three-line status line at 2x font scale inflated the symbol-input and install-progress rows too. collapsedHeight now counts the status block only while the flipper shows it, and showChild re-applies the height so the extra rows leave with the status and come back with it. onSoftInputChanged routes through showChild for the same reason. Not device-verified: a JVM test covers the height rule; the keyboard-up and install-progress flips still need a device at font scale 1.0 and 2.0. Review: https://github.com/appdevforall/CodeOnTheGo/pull/1723#discussion_r3951660756 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Y6rFuAJin4QFFwwzUTFZ1K * ADFA-4128: let an unknown-id consent at tap time cover the id the install resolves NeededForUnknownAppId never equals Needed(id), so a Run whose applicationId had not resolved at tap time asked at the tap and again at install once the APK named the package. The tap-time answer already consented to replacing whatever sits under this app's id; the install resolving that id is the same answer with the name filled in, so it is no longer asked twice. The other direction (a named tap, an unresolved install) still asks. Review: https://github.com/appdevforall/CodeOnTheGo/pull/1723#discussion_r3951660761 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Y6rFuAJin4QFFwwzUTFZ1K * ADFA-4128: await the clobber confirmations on the tap path Both tap-time clobber gates ran the callback-shaped dialog from inside a suspend function on lifecycleScope, so their cleanup ran before the user answered; they now await the dialog like the install path does, which also takes the dialog down when the activity goes. The callback form had no callers left and is gone. The accessor KDoc names onPause and onResume, where restartSession, the narrator reset and onHostForegrounded actually live. Answers: https://github.com/appdevforall/CodeOnTheGo/pull/1723#discussion_r3951660765 https://github.com/appdevforall/CodeOnTheGo/pull/1723#discussion_r3951660739 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Y6rFuAJin4QFFwwzUTFZ1K * ADFA-4128: post one Quick Build save event per save operation onFileSaved moved out of the per-editor write into the three save entry points, so a save-all posts one event instead of N identical ones. The dropdown KDoc says what the Help item does without a documentation row: it opens the no-tooltip fallback. Answers: https://github.com/appdevforall/CodeOnTheGo/pull/1723#discussion_r3951660746 https://github.com/appdevforall/CodeOnTheGo/pull/1723#discussion_r3951660738 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Y6rFuAJin4QFFwwzUTFZ1K * ADFA-4128: abort a Run whose pre-build save found the editor gone beforeBuild skipped the save-all for a destroyed activity and let the build go ahead on stale disk content; it now throws, which BuildViewModel's catch turns into an Error state. Answers: https://github.com/appdevforall/CodeOnTheGo/pull/1723#discussion_r3951660769 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Y6rFuAJin4QFFwwzUTFZ1K * ADFA-4128: do not count a Quick Build stop tap as a feature use The analytics call ran before the BUILDING cancel branch, so a stop was tracked as a use. Answers: https://github.com/appdevforall/CodeOnTheGo/pull/1723#discussion_r3951660733 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Y6rFuAJin4QFFwwzUTFZ1K * ADFA-4128: ASCII ellipsis in the truncated Gradle cause Three dots on a three-character budget, with the test asserting the same. Answers: https://github.com/appdevforall/CodeOnTheGo/pull/1723#discussion_r3951660740 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Y6rFuAJin4QFFwwzUTFZ1K * ADFA-4128: pass the rejection to the repeated-connect debug log RuntimeLog.d(String, Throwable) instead of string concatenation, matching the warn two lines below. Answers: https://github.com/appdevforall/CodeOnTheGo/pull/1723#discussion_r3951660749 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Y6rFuAJin4QFFwwzUTFZ1K * ADFA-4128: carry the reinstalled app's uid on a rebuild success The Gradle provisioner fills the proxyAppUid the rebuild outcome now carries, from the install verdict. Answers: https://github.com/appdevforall/CodeOnTheGo/pull/1720#discussion_r3951660396 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Y6rFuAJin4QFFwwzUTFZ1K * ADFA-4128: manual QA case for Run during the eager proxy-app build T24 in Block B covers the refusal window: the msg_build_slot_busy flash and no second build. Answers: https://github.com/appdevforall/CodeOnTheGo/pull/1723#discussion_r3951660729 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Y6rFuAJin4QFFwwzUTFZ1K * ADFA-4128: Run Tasks must not inherit a Run tap's clobber answer runTasks (stage #1803) claims the build slot but never resets clobberAnswerAtTap, so a Run whose clobber check was already answered, followed by a Run Tasks install of the same app, could skip the confirmation that install owes. Reset it when Run Tasks claims the slot; Run Tasks never asks, so the install must. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0153YfwDnqn7ktHcVgXSNNe8 * ADFA-4128: carry a skipped relaunch as an absent relaunchOk through the app sinks Follows the core change that made relaunchOk nullable: the analytics metric omits relaunch_ok when no relaunch was attempted, and the bench JSON line omits relaunchOk, so a skipped relaunch no longer reads as a failed one. Tests: AnalyticsQuickBuildMetricsSinkTest (expected: null / but was: false) and BenchQuickBuildMetricsSinkTest (expected to be false) both watched red with the null coalesced to false. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0153YfwDnqn7ktHcVgXSNNe8 * ADFA-4128: route a created or deleted resource file through the generate-sources deferral ProjectManagerImpl.onFileCreated and onFileDeleted called generateSources directly, bypassing the deferral every save call site goes through. With a live session the undeferred build landed mid-pipeline, nothing had armed the deferral's claim, and the hand-back marked the baseline untrusted, so the next save recompiled everything. The project manager cannot see the deferral (it lives in the app), so it gains a resourceChangeBuild seam that defaults to the direct call; the Quick Build module points it at GenerateSourcesDeferral.notifyResourceSaved when the session manager is created. Test: ProjectManagerImplResourceChangeTest, watched red with the direct call restored (expected: 1 / but was: 0). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0153YfwDnqn7ktHcVgXSNNe8 * ADFA-4128: only grow the collapsed sheet while a build narrates setStatus re-measures the collapsed header on every write, and a Gradle build writes one task name per TaskStartEvent. At a 2x font scale those alternate between one and three rows, so the peek height jumped on every task for the whole build. While a user build is in progress the header now keeps the tallest height it has needed, and re-fits the final status line once the build ends. Test: CollapsedHeaderHeightTest (statusHeightAfterMeasure), watched red with the ratchet removed (expected:<222.0> but was:<108.0>). Not device-verified. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0153YfwDnqn7ktHcVgXSNNe8 * ADFA-4128: gate the Restart session row like the button While a standard Gradle build holds the slot, a restart tore the warm session down and the reprovision was then refused as slot-busy. The dropdown row is disabled under the same QuickBuildAction gate that greys the button. No JVM test: the popup is built inside the activity. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0153YfwDnqn7ktHcVgXSNNe8 * ADFA-4128: hand the outer internal build its progress listener back withInternalBuild kept the listener in a single field and nulled it in its finally, while the bracket around it counts depth. A nested internal build therefore blinded the outer one for the rest of its run. The listener now lives on the bracket, saved on entry and restored on exit. Unreachable today (one caller, behind its own isBuildInProgress check). Test: InternalBuildBracketTest "a nested hold hands the outer build its progress listener back", watched red with the null restored (expected specific instance: ...outer / but was: null). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0153YfwDnqn7ktHcVgXSNNe8 * ADFA-4128: read the saved file, not the editor's, for the resource flag The post-save resource check re-read frag.file off the main thread. That goes through the view binding, which a tab closed after the write has released, so the flag came back false and a layout save left the Java LSP with stale R symbols. savedFile was captured on Main for exactly this. No JVM test: activity code. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0153YfwDnqn7ktHcVgXSNNe8 * ADFA-4128: reset the narrator after the pane unbinds, and drop what follows The reset ran in onPause beside restartSession(), before the teardown narrated anything and while the pane was still bound, so it cleared an empty queue; the lines that arrived after onDestroy unbound the pane queued for the next project. The reset now runs from the same lifecycle observer, after the unbind, when the project is closing - and until the next pane binds the narrator drops lines instead of queueing them, since the teardown narrates asynchronously after the reset. Test: QuickBuildOutputNarratorTest "lines narrated after a reset with no pane are dropped", watched red with the discard removed (expected to be empty / but was: [Quick Build: :app:mergeV8DebugResources]). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0153YfwDnqn7ktHcVgXSNNe8 * ADFA-4128: an empty slot at tap time is not widened by an unparseable APK With no proxy app installed the tap settled NotNeeded. When the built APK then failed to parse the install-time check came back NeededForUnknownAppId and the unknown-occupant dialog was shown after a successful build; a decline dropped the install with no message. NotNeeded at the tap now covers an unknown id at install time. Test: QuickBuildClobberConfirmationTest "an empty slot at tap time is not widened by an APK that fails to parse", watched red with the branch removed (expected: NotNeeded / but was: NeededForUnknownAppId). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0153YfwDnqn7ktHcVgXSNNe8 * ADFA-4128: run the prebuild fire inside the stagger's own catch The activity's fire body only launched a coroutine, so the try/catch the stagger wrapped around fire() saw nothing and the await/onProjectSynced work ran as a bare child of the editor scope - a plain Job with no handler, where a throw cancels the scope and reaches the uncaught handler. fire is now a suspend function the stagger launches on its scope, on both arms, inside the catch. Test: QuickBuildPrebuildStaggerTest "an immediate prebuild that throws does not take the scope down with it", watched red with the guard removed from the immediate arm (IllegalStateException: onProjectSynced blew up). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0153YfwDnqn7ktHcVgXSNNe8 * ADFA-4128: use the intent-extra constant in the bench activity The one call site still writing the "PROJECT_PATH" literal is the one the same-project autostart guard reads back through EditorIntentExtras.EXTRA_PROJECT_PATH. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0153YfwDnqn7ktHcVgXSNNe8 * ADFA-4128: count a Quick Build feature use only when a build starts trackFeatureUsed sat above the save-all and the clobber confirmation, so a failed save and a declined clobber both emitted a quick_build use with no build behind them. It now runs next to onQuickBuildTapped. No JVM test: the action needs an activity. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0153YfwDnqn7ktHcVgXSNNe8 * ADFA-4128: say in GradleBuildTuner's KDoc that it is Experiments-only The class doc claimed the tuner applies to every Gradle build and was deliberately not gated behind the Experiments flag. Its only caller, GradleBuildService.prepareBuild, calls it inside `if (FeatureFlags.isExperimentsEnabled)` and otherwise passes Gradle only the IDE's extra arguments - so with the switch off Gradle runs on its own defaults and neither the Metaspace floor nor the daemon idle timeouts apply. Row 20 of the review round chose to fix the sentence rather than the gate; this is that sentence. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0153YfwDnqn7ktHcVgXSNNe8 * ADFA-4128: gate the won't-stay-up dialog's Restart like the button The dialog offers Restart as the one action that replaces a proxy app which will not stay open, but during a standard Gradle build that restart tears the warm session down and the reprovision behind it is refused as slot-busy - the user loses the session and is told to wait. Same QuickBuildAction.isBlockedByStandardBuild() gate the toolbar button and the Restart session row (d28cf1e6c) already use; Dismiss stays live and the notice is raised again if the streak continues. No JVM test: the dialog is built inside the activity, as with d28cf1e6c. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0153YfwDnqn7ktHcVgXSNNe8 * ADFA-4128: hold the narrator's discard until the old teardown is quiet Closing a project cleared the narrator's queue and set discardUntilBound, which bind() cleared. The next project's activity binds its Build Output pane in onCreate, while the closing session's Gradle cancel is still fire-and-forget (QuickBuildSessionManager.teardown -> GradleQuickBuildProvisioner.cancelProxyAppBuild) and the build's progress listener keeps firing - so late "Quick Build:" lines from the dying project were written into the new project's pane, attributed to a build it never ran. reset() now takes an awaitable for the closing session's teardown, and lines are dropped until that returns whether or not a pane has bound in the meantime. QuickBuildSessionManager.awaitTeardown() exposes the existing teardownWork join; it hops onto the session dispatcher, so called after restartSession() it lands behind that teardown rather than reading the previous one. The wait is capped at 30s so a teardown that never reports quiet cannot silence the pane for the rest of the process, and the call is off the UI thread on the narrator's own scope. The new project's own narration is dropped inside that window too. The window ends at the daemon shutdown plus the scratch-tree removal, and the new project's eager prebuild is staggered well past it, so in practice nothing of the new session lands in it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0153YfwDnqn7ktHcVgXSNNe8 * ADFA-4128: expect the tap's RecordAsk in the prebuild stagger's reducer tests Restack fallout from the PendingAsk work on the core branch (0e8d15a78): a user-initiated QuickBuildTapped now carries SessionEffect.RecordAsk alongside whatever it starts. Both tests here assert a reducer transition directly, so they saw the extra effect. Their subject is unchanged - one pins that a tap in the stagger window provisions in the same transition rather than queueing, the other that a tap mid-prebuild starts no build. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0153YfwDnqn7ktHcVgXSNNe8 * ADFA-4128: scope the narrator's teardown drop to the build that closed The drop reset() arms was global: every line was dropped until the closing project's teardown reported quiet, or for 30 s. But the next project's activity binds its pane in onCreate, so a Quick Build tapped in the project the user just opened narrated "running the initial full build", its Gradle task lines and its timings into nothing - for as long as a daemon shutdown and a scratch-tree removal take. The leak that drop exists to stop is a specific one: the closing project's proxy app build is cancelled fire-and-forget, so its Gradle progress listener keeps firing - and its failure is quoted - after the new pane is bound. That is now decided by naming the build rather than by timing it. A build takes a ProxyAppBuildNarration before it starts; reset() retires every handle taken before it, so the closed project's build is silenced for as long as it keeps talking, while the build the project on screen started narrates throughout. Status and timing lines carry no build of their own, so they keep the drop - but it now also ends when a session announces its own full build. That stream is ordered, so everything the closed session had left to say arrived before that announcement, and the project on screen is never muted by it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015WMhaGYg4sSCcAzQEdNtLa * ADFA-4128: decide the won't-stay-up notice's Restart at the tap, not the show The button's enabled state was sampled once, at show(). The dialog outlives that reading, so the gate was wrong in both directions: raised during a standard Gradle build it kept a dead Restart for the whole life of the dialog - the build finishing never re-enabled it - and raised before one it kept a live Restart that tore the warm session down into a reprovision the build then refused as slot-busy. A device pass on 2026-09-10 hit the second case 5 times out of 5. Worse, a disabled dialog button says nothing about why. The toolbar button at least carries "Standard build in progress" as its content description; this had no such channel, while the message body went on promising restart as the remedy. So the gate is read on every tap. A tap that cannot act flashes the same "Standard build in progress" string and leaves the notice up, so the user can tap it again when the build ends - the only other route back to it is another Quick Build failure streak. The rule lives in QuickBuildWontStayUpRestart, which is JVM-testable; the activity keeps the wiring, and sets the listener on the button rather than through setPositiveButton, since Material dismisses before the builder's listener runs. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015WMhaGYg4sSCcAzQEdNtLa * ADFA-4128: cut the Material alert dialog's vertical insets to 24dp Material's alertDialogStyle defaults backgroundInsetTop and backgroundInsetBottom to 80dp each. On the A56 (1080x2340, density 450) that is 450px of the 2340 spent on margin, leaving the panel 1654px for content that needs about 1720px at 2x font scale -- so the message and the "Not now" button are clipped. 24dp each returns 224px to the panel, which clears the shortfall with room left. Verified in the built APK with aapt2: the style resolves and alertDialogStyle points at it. WIP: kept as its own commit so the stack can be restacked without carrying an uncommitted worktree. Still open with Bryan: whether this lands app-wide or stays contained to the dialogs that clip (39 of 57 alert dialogs inherit this theme). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HZEZKHy8dkDGJM3nUut6hJ * ADFA-4128: give :app a JaCoCo report task, pointed at the ASM-transformed classes :app registered no JaCoCo report task, so every coverage number for this module has come from a one-off invocation that left nothing behind to re-run. The obvious way to write one is also wrong here, and wrong silently: :app runs transformV8DebugClassesWithAsm, so a report pointed at tmp/kotlin-classes -- correct for the quickbuild modules, which run no transform -- makes 138 classes report "Execution data ... do…
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Ticket: https://appdevforall.atlassian.net/browse/ADFA-515
Update contact dialog with send email button and new email address