The input composer floated a full navigation bar above where it belonged whenever the
device used 3-button navigation.
Cause was a duplicated inset. Scaffold's default contentWindowInsets is systemBars,
which already carries the navigation bar, and that reached the content through
Box(Modifier.padding(pad)). Every child then applied the same inset again for itself --
Header takes the status bar, the composer and shell row take navigation bar + IME,
settings takes the navigation bar -- so the bottom inset was counted twice. Measured on
a 1080x2400 screen at density 420: 152px of dead panel under the text field where only
the 10dp of padding belonged, the excess being exactly the 48dp navigation bar. It went
unnoticed on gesture navigation, where the same bug costs ~16dp.
Scaffold now declares WindowInsets(0, 0, 0, 0) and each child keeps owning the inset it
already handled. The composer's panel also paints behind the navigation bar now instead
of stopping short of it.
Also folded in …
v1.18.0 - JSON null read as "null" was discarding every main-turn stream frame
POST_NOTIFICATIONS was declared in the manifest but never requested at runtime, so on Android 13+ every notification the app posted - including the agent's own `notify` command - was dropped silently unless the user had granted it by hand. Now asked for on first launch.
Turn-complete notification: the whole point of a phone agent is to send a long task and put the phone down, but nothing told you when it landed. On the result frame, if the app is backgrounded, it posts 'Claude finished - success - 42s - $0.03' with the first line of the reply.
Foreground is tracked in onStart/onStop rather than from a composable's lifecycle, so rotation and the drawer - which recompose without the app leaving the screen - don't count as looking away. Toggle in Setup; reuses the existing agent-ping channel.
A spawned agent used to be a black box: the Agent tool call went out and nothing came back until it finished, however long that took.
- passes --forward-subagent-text so a spawned agent's output is streamed at all
- frames from a subagent carry the tool_use id that created it (parent_tool_use_id); those are routed to that agent instead of the main response counter, so the two turns' totals don't merge
- Agent/Task calls register a Subagent; its result marks it done or failed
- collapsible panel above the composer: count, running count, total tokens when shut; name, type, live token count and status per agent when open
- the list is capped at 8 with '+ n more'. A workflow can fan out to dozens and an unbounded list would push the composer off screen - the same bug as the header overflow
- token counts throttled to 25-token steps, as the thinking counter is
v1.9.1 - highlight the ultracode keyword as you type
ultracode has two routes in: the toggle, and typing the word. Only the first was visible, so a typed keyword armed a multi-agent run with no indication at all.
- every occurrence of 'ultracode' in the composer is painted purple and bold, via a VisualTransformation with an identity offset mapping so the cursor doesn't move
- the field border turns purple while the word is present
- the banner now appears for a typed keyword too, reading 'ultracode keyword in your message - it will run'. It offers no skip in that case: the word is the user's own text and quietly stripping it would be worse than leaving it
update button beside clean: reads `claude --version`, runs `claude update`, reads the version again. Comparing the two readings is the ground truth - `claude update`'s wording is not a stable interface. Same version -> 'cli is already on the latest version (x.y.z)'; different -> reports the bump and restarts the engine, since the binary is read at launch.
ultracode in the effort dropdown, below a divider because it is not an effort level: there is no --ultracode flag, the CLI enables it from the keyword in the prompt. So it is a toggle that prefixes what you send, which is also why it takes hold on the next turn with no restart.
Shown in purple wherever it is armed, and while it is on a banner sits above the composer so the next prompt is never silently a multi-agent run - one tap skips it for that turn without turning the mode off.
1.8.0 put the effort picker in the title row, which already had seven children. Compose rows don't wrap, so on a phone the status text was squeezed to a sliver and wrapped one character per line - the header grew into a tall column mid-response, exactly when the status string changed width.
- effort picker moved to the path line, which has room; cwd ellipsizes
- maxLines=1 on the title and status so neither can ever wrap
- thinking token count is throttled to 25-token steps: it was rewriting a transcript list entry on every streamed token, which made the LazyColumn thrash
The transcript drew nothing while the model was reasoning: Event.Thinking only fired once the whole block completed, so a high-effort turn looked frozen for as long as it thought.
- Engine watches content_block_start for thinking (earliest signal) and splits reasoning deltas from text deltas
- Turn.Thinking carries live/effort/startedAt/tokens; the marker appears the instant reasoning starts and ticks while it streams
- settleThinking() closes the marker on every turn-end path so it can never outlive its turn
- passes --effort to the CLI, with a picker beside the model picker
v1.7.9: name ScheduleWakeup loop wakeups instead of 'session started'