Pin the transcript to its tail, and stop asking about every command

**The scroll.** The transcript scrolled on new items and nothing else,
which missed the two cases that matter most. An imported session's history
arrived and left the view wherever it landed; the keyboard opening shrank
the viewport and slid the newest messages under the IME, so typing meant
typing into a view showing the middle of something.

The view is now pinned to the tail, and it is the reader's scroll that
decides: settling anywhere above the bottom releases the pin, settling
back at the bottom re-arms it. The pin is written only when a scroll
*ends*, so it survives the moment when new content has just pushed the
bottom away but the reader never moved -- deriving it continuously from
"is the bottom visible" would release it on every append, which is the
race that makes naive follow-the-tail implementations let go at random.
New items and viewport resizes both re-scroll; the jump is instant rather
than animated, because an imported session appends hundreds of items at
once and animating through them is a light show.

**The input field gets a row of its own**, above the buttons. Sharing one
row put the full width behind three controls, so the thing being typed
into was the narrowest thing on the row.

**Permissions default to auto, and importing can choose.** The spawn
screen defaulted to "manual" and imports passed no mode at all, so the
CLI asked about everything -- and on a phone every ask is a round trip to
a question card, which is how "allow Bash?" became the most-answered
question in the app. Both paths now default to auto, with the other modes
one tap away for a session that warrants caution.

Looked at running, all three: an imported session opens at its bottom,
the tail stays visible while typing with the keyboard open, and the mode
picker shows auto selected.
This commit is contained in:
iris committed 2026-08-28 22:24:37 -04:00
1 parent 233689ced6
commit c3e7f07a5d
3 files changed
+67 -24

No files matched your search

@@ -67,7 +67,11 @@ fun SpawnScreen(
var title by remember { mutableStateOf("") }
var model by remember { mutableStateOf("") }
var cwd by remember { mutableStateOf("") }
var permissionMode by remember { mutableStateOf("manual") }
// "auto" rather than "manual": on a phone every ask is a round trip to
// a question card, and answering "allow Bash?" dozens of times per task
// is what this app exists to avoid. Manual stays one tap away for a
// session that warrants it.
var permissionMode by remember { mutableStateOf("auto") }
var busy by remember { mutableStateOf(false) }
// Only the spawn's own failure. The fetch's lives in `options`: this
// one leaves a filled-in form worth keeping, and that one leaves
@@ -338,7 +342,7 @@ fun SpawnScreen(
*/
@OptIn(ExperimentalLayoutApi::class)
@Composable
private fun ChipGroup(
fun ChipGroup(
label: String,
options: List<String>,
selected: String?,