diff --git a/README.md b/README.md index 9b94671..f7c3635 100644 --- a/README.md +++ b/README.md @@ -13,21 +13,23 @@ connection. Alpha.6 corrects that failure and has passed independent CI, permanent signing, in-place update, and physical acceptance. Alpha.7 adds local drafting while a command runs and retained-session Home navigation. Alpha.8 adds isolated child-Bash execution for script-like commands so a strict-mode -failure does not close the persistent SSH shell. Alpha.9 is the current source -candidate and keeps the followed transcript tail visible when the software -keyboard changes the viewport. The remaining product boundary is invited -technical-alpha use sufficient to evaluate whether ten real users can complete -small remote tasks for two weeks and provide useful feedback. +failure does not close the persistent SSH shell. Alpha.9 attempted to keep the +followed transcript tail visible when the software keyboard changed the +viewport, but the signed build failed that check on the owner device. Alpha.10 +is the current source candidate and makes the production activity resize for +the keyboard. The remaining product boundary is invited technical-alpha use +sufficient to evaluate whether ten real users can complete small remote tasks +for two weeks and provide useful feedback. The current accepted signed build is `0.1.0-alpha.8`; the current source -candidate is `0.1.0-alpha.9`. Alpha.5 is an immutable rejected artifact. -Alpha.9 owner-device acceptance is pending. Signed builds are shared directly -with invited testers. +candidate is `0.1.0-alpha.10`. Alpha.5 and alpha.9 are immutable rejected +artifacts. Alpha.10 owner-device acceptance is pending. Signed builds are +shared directly with invited testers. There is no public signed APK or GitHub prerelease while Phase 5 is open. Testers should obtain the APK and its checksum from the owner through the agreed private channel, then follow the [technical-alpha guide](docs/alpha-testing.md). -Do not distribute alpha.5. +Do not distribute alpha.5 or alpha.9. Threadline has no supported production release. Do not use the alpha for privileged or sensitive systems. diff --git a/app/src/androidTest/java/dev/threadline/TranscriptScreenTest.kt b/app/src/androidTest/java/dev/threadline/TranscriptScreenTest.kt index 74941e9..9779d14 100644 --- a/app/src/androidTest/java/dev/threadline/TranscriptScreenTest.kt +++ b/app/src/androidTest/java/dev/threadline/TranscriptScreenTest.kt @@ -1,8 +1,10 @@ package dev.threadline +import android.content.ComponentName import android.os.SystemClock import android.view.View import android.view.ViewGroup +import android.view.WindowManager import androidx.activity.ComponentActivity import androidx.activity.enableEdgeToEdge import androidx.compose.foundation.lazy.LazyListState @@ -989,25 +991,46 @@ class TranscriptScreenTest { } } + @Suppress("DEPRECATION") + @Test + fun mainActivityRequestsResizeForIme() { + val activityInfo = composeRule.activity.packageManager.getActivityInfo( + ComponentName(composeRule.activity, MainActivity::class.java), + 0, + ) + + assertEquals( + WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE, + activityInfo.softInputMode and WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST, + ) + } + @Test fun transcriptKeepsLatestTurnVisibleWhenImeInsetsArrive() { + assertSubmittedTurnVisibleWithIme( + initialTurns = (0 until 6).map { index -> + turn( + id = "command-$index", + command = "printf command-$index", + status = CommandStatus.SUCCEEDED, + output = "result-$index", + ) + }, + ) + } + + @Test + fun transcriptKeepsFirstTurnVisibleWhenImeInsetsArrive() { + assertSubmittedTurnVisibleWithIme(initialTurns = emptyList()) + } + + private fun assertSubmittedTurnVisibleWithIme(initialTurns: List) { composeRule.runOnIdle { composeRule.activity.enableEdgeToEdge() } lateinit var composeView: View val latestTurnId = "ime-command" - val transcript = mutableStateOf( - CommandTranscriptState( - turns = (0 until 6).map { index -> - turn( - id = "command-$index", - command = "printf command-$index", - status = CommandStatus.SUCCEEDED, - output = "result-$index", - ) - }, - ), - ) + val transcript = mutableStateOf(CommandTranscriptState(turns = initialTurns)) composeRule.setContent { MaterialTheme { ConnectedSessionScreen( @@ -1030,8 +1053,10 @@ class TranscriptScreenTest { ) } } - composeRule.onNodeWithTag(TranscriptTags.output("command-5")) - .assertIsDisplayed() + initialTurns.lastOrNull()?.let { lastTurn -> + composeRule.onNodeWithTag(TranscriptTags.output(lastTurn.id.value)) + .assertIsDisplayed() + } composeRule.onNodeWithTag(TranscriptTags.COMPOSER) .performClick() .assertIsFocused() @@ -1060,8 +1085,10 @@ class TranscriptScreenTest { "Composer bottom $composerBottom exceeded IME top $visibleBottom", visibleBottom != null && composerBottom <= visibleBottom, ) - composeRule.onNodeWithTag(TranscriptTags.output("command-5")) - .assertIsDisplayed() + initialTurns.lastOrNull()?.let { lastTurn -> + composeRule.onNodeWithTag(TranscriptTags.output(lastTurn.id.value)) + .assertIsDisplayed() + } composeRule.onNodeWithTag(TranscriptTags.COMPOSER) .performTextInput("printf latest") composeRule.onNodeWithTag(TranscriptTags.SEND).performClick() diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index f6c14b5..1fdc613 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -18,7 +18,8 @@ android:usesCleartextTraffic="false"> + android:exported="true" + android:windowSoftInputMode="adjustResize"> diff --git a/docs/BACKLOG.md b/docs/BACKLOG.md index 56c9655..706047b 100644 --- a/docs/BACKLOG.md +++ b/docs/BACKLOG.md @@ -51,7 +51,8 @@ visual case. ## Transcript viewport with the software keyboard -**Status:** Implemented in source with emulator regression coverage; physical +**Status:** The signed alpha.9 build failed physical validation. An alpha.10 +correction is implemented in source with emulator regression coverage; physical owner-device validation remains pending. On the Galaxy S25 Ultra, focusing the transcript composer and sending commands @@ -61,14 +62,18 @@ user had to scroll back up to recover the new cards and output. Connection, execution, and data remained intact. The recovery scrolling is repeated friction in the primary interaction path. -The transcript followed new output but did not re-anchor the followed tail when -the software keyboard changed the list viewport height. Tail-following now -reacts to viewport resizes while preserving the IME padding that keeps the -composer above the keyboard. An instrumented emulator regression uses the real -Gboard window and the full connected-session layout. It failed against the old -layout and verifies that the composer, previous result, and newly submitted -result all remain visible after the fix. The raw terminal keeps its separate -IME handling. +Alpha.9 made tail-following react to list viewport resizes while preserving the +IME padding that keeps the composer above the keyboard. Its first physical +check exposed a second boundary: the production activity left soft-input +adjustment unspecified, so the Galaxy S25 Ultra panned the whole window instead +of resizing the viewport. A completed first turn existed but remained outside +the visible window until the keyboard closed. The session and command remained +functional. + +Alpha.10 explicitly requests resize behavior for `MainActivity`. An +instrumented contract checks the production manifest, and the real-Gboard +connected-session regression now starts from an empty transcript and submits +the first turn. The raw terminal keeps its separate IME handling. Owner-device acceptance should repeat focus, submission, running output, completion, keyboard dismissal, rotation, and large-font checks. Existing diff --git a/docs/STATUS.md b/docs/STATUS.md index db7080e..4a4d323 100644 --- a/docs/STATUS.md +++ b/docs/STATUS.md @@ -142,15 +142,27 @@ The next persistent command returned the original marker and `/tmp`, with exit 0 and without reconnecting. This closes alpha.8 owner-device acceptance. See the [alpha.8 isolated-execution investigation](investigations/2026-08-17-alpha8-isolated-execution-acceptance.md). -The current source candidate is `0.1.0-alpha.9` (`10009`). It keeps a followed -transcript tail anchored when the software keyboard changes the list viewport -height. A connected-session emulator regression using the real Gboard window -reproduces the prior loss of the newest useful card, then verifies that the -composer, previous result, and a newly submitted result remain visible after -the fix. CI runs the regression on an explicit Pixel 6 AVD profile. Deliberate -user scrolling still disables tail-following, and the raw terminal path is -unchanged. Exact merged-main CI, permanent signing, update installation, and -owner-device acceptance remain pending. +The exact merged-main alpha.9 candidate passed CI, was signed with the permanent +key, and installed on the Galaxy S25 Ultra. Its certificate, v2/v3 signatures, +package identity, version, checksum, and 16 KiB alignment verify. Physical +testing then rejected it: after Gboard opened over an empty transcript, a +completed first turn existed but remained above the visible window until the +keyboard closed. The session and command remained functional. + +The failure exposed a production boundary missing from the emulator regression. +`MainActivity` enabled edge-to-edge layout but left its soft-input adjustment +unspecified. The Pixel emulator resized the generic test activity, while the +Samsung device panned the production window. The viewport-follow effect could +only react to the resize path. + +The current source candidate is `0.1.0-alpha.10` (`10010`). It explicitly makes +the production activity resize for the software keyboard, adds an instrumented +contract for that manifest setting, and starts the Gboard regression from the +same empty-transcript first-command sequence. Deliberate user scrolling still +disables tail-following, and the raw terminal path is unchanged. Exact +merged-main CI, permanent signing, update installation, and owner-device +acceptance remain pending. See the +[alpha.9 IME physical rejection](investigations/2026-08-24-alpha9-ime-physical-rejection.md). ## Remaining Phase 5 boundaries diff --git a/docs/development/release-signing.md b/docs/development/release-signing.md index 1ceb224..3344836 100644 --- a/docs/development/release-signing.md +++ b/docs/development/release-signing.md @@ -109,6 +109,22 @@ was selected through `scripts/sign-latest-alpha.sh`. Android installed it on the Galaxy S25 Ultra, where isolated strict failure, state containment, and persistent-command recovery passed against the disposable fixture. +Permanent-key alpha.9 uses the same certificate. Its verified APK SHA-256 is: + +```text +446b9c048cf720bd35bc2721f43e3ac49ba22660b116fda00da3342fa7862e7d +``` + +The artifact identifies as `io.github.r055le.threadline`, version +`0.1.0-alpha.9` (`10009`), verifies with APK signature schemes v2 and v3, and +is 16 KiB page aligned. Its exact merged-main candidate passed public CI and +was selected through `scripts/sign-latest-alpha.sh`. Android installed it on +the Galaxy S25 Ultra, but the followed transcript tail still moved outside the +visible window when Gboard opened. Alpha.9 is rejected and must not be +distributed or overwritten. Preserve it as immutable evidence and advance the +corrected build to alpha.10. See the +[alpha.9 IME physical rejection](../investigations/2026-08-24-alpha9-ime-physical-rejection.md). + ## Signing-key boundary The long-lived release keystore and its passwords must never enter this diff --git a/docs/investigations/2026-08-24-alpha9-ime-physical-rejection.md b/docs/investigations/2026-08-24-alpha9-ime-physical-rejection.md new file mode 100644 index 0000000..5f1b7b4 --- /dev/null +++ b/docs/investigations/2026-08-24-alpha9-ime-physical-rejection.md @@ -0,0 +1,59 @@ +# Alpha.9 IME physical rejection + +Date: 2026-08-24 + +## Result + +The permanent-key alpha.9 build is rejected. It installed successfully and the +structured shell remained functional, but it did not keep the newest transcript +card visible while Gboard was open on the Galaxy S25 Ultra. + +## Physical evidence + +The owner-device check started with an empty transcript: + +1. Focusing the composer opened Gboard. +2. Submitting a short command completed the turn and enabled command history. +3. The completed card remained above the visible window while the keyboard was + open. +4. Closing the keyboard immediately revealed the successful card. + +This isolates the failure to window and transcript presentation. The command, +captured output, SSH session, and recovery path remained intact. + +## Cause + +Alpha.9 made tail-following react when the `LazyColumn` viewport height changed. +The production `MainActivity` also enables edge-to-edge layout, but its manifest +left `android:windowSoftInputMode` unspecified. The Pixel emulator resized the +generic activity used by the regression. The Samsung device instead panned the +production window to expose the focused composer, so the list viewport did not +provide the resize signal the alpha.9 effect expected. + +Android's current +[edge-to-edge setup guidance](https://developer.android.com/develop/ui/compose/system/setup-e2e) +requires an activity using the software keyboard to declare `adjustResize` so +the app receives IME insets for layout adjustment. + +## Correction boundary + +Alpha.10 declares `adjustResize` on production `MainActivity`. An instrumented +test checks that exact manifest contract. The real-Gboard UI regression now +starts with an empty transcript and submits the first command, matching the +physical failure sequence. + +Before the manifest change, the contract failed with adjustment mode `0` +instead of `16`. After the change, the manifest contract and first-command IME +regression pass on the Pixel 6 API 35 emulator. The full emulator run completed +78 tests with three expected fixture-dependent skips. JVM tests, lint, debug and +minified release assembly, the signing-helper tests, and the release shrinker +contract also pass. + +## Still required + +- Pass the merged-main release gate. +- Sign the exact merged-main alpha.10 candidate with the permanent key. +- Install alpha.10 over alpha.9 and preserve existing app data. +- Repeat the owner-device keyboard sequence, including running output, + completion, keyboard dismissal, rotation, large font, and deliberate manual + scrolling. diff --git a/gradle.properties b/gradle.properties index 22df0eb..ac04926 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,5 +4,5 @@ org.gradle.jvmargs=-Xmx3g -Dfile.encoding=UTF-8 android.useAndroidX=true android.nonTransitiveRClass=true threadline.releaseApplicationId=io.github.r055le.threadline -threadline.versionCode=10009 -threadline.versionName=0.1.0-alpha.9 +threadline.versionCode=10010 +threadline.versionName=0.1.0-alpha.10