One control per kind of setting, and no paragraphs under any of them

Three explanatory paragraphs were left on the session settings screen: what
auto-resume does, what Move costs, what changing the thinking level costs.
The last two are consequences of an action, so they are `RestartDialog` now --
the same question the machines tab already asks before it reloads a model, and
now in one file rather than private to that screen. The first is gone; the
switch beside it says what it is.

Thinking was two controls: a row of chips for a llama session, where it is a
provider-declared choice, and a picker button for a Claude session, where it
is the session's own effort. One kind of information drawn two ways, decided
by which code path the value came down. A choice is `PickerRow` in a list of
settings and `ChipGroup` on a form being filled in, and which of the two is
the screen's to say rather than the provider's.

`warnAboutRestart` went with them. It marked a control "(on restart)" and
wrote a sentence under the form, and it had nothing to mark: every session
param is `restart: false` and every model param is `restart: true`. The field
now decides whether saving a model's settings stops to ask, which is the
question it was always about.

The wait's bar keeps the status row's own margin instead of running to the
edges of the glass.

Checked on the emulator against the sandbox: an echo, a claude-cli and a
llama session, each with no paragraph left under a setting and thinking drawn
the same way in all three; "Think high?", "Move to /tmp?" and the model
settings dialog all stop to ask. ktfmt, compile, lint and the unit tests are
clean.
This commit is contained in:
iris-ai committed 2026-09-21 12:25:49 -04:00
1 parent 5626a7d595
commit 3dbf04f5ec
7 files changed
+178 -132

No files matched your search

@@ -2813,12 +2813,17 @@ private fun SessionStatusRow(
) {
DebugStats.count("status row recomposed")
Column(modifier.fillMaxWidth()) {
// The wait's bar, the whole width and above the words. In the width left over beside
// them it stood where the context figure goes, which is what a reader wants to keep
// seeing while a turn is read. The height is held whether or not there is a bar: this
// sits above the transcript and the box, and one that came and went would move both
// under the reader's thumb every time a turn started.
Box(Modifier.fillMaxWidth().height(PROGRESS_BAR_HEIGHT)) {
// The wait's bar, above the words and across the width they have -- the row's own margin,
// so it lines up with the rest of the screen rather than running to the edges of the
// glass. In the width left over beside the words it stood where the context figure goes,
// which is what a reader wants to keep seeing while a turn is read. The height is held
// whether or not there is a bar: one that came and went would move the transcript and the
// box under the reader's thumb every time a turn started.
Box(
Modifier.fillMaxWidth()
.padding(horizontal = STATUS_ROW_MARGIN)
.height(PROGRESS_BAR_HEIGHT)
) {
when {
// Indeterminate for a compaction, which is a statement rather than an omission:
// the CLI says one has begun and then nothing at all until it has finished --
@@ -2855,6 +2860,9 @@ private fun SessionStatusRow(
/** How tall a wait's bar is, and the space kept for one when there is no wait. */
private val PROGRESS_BAR_HEIGHT = 4.dp
/** What the status row keeps clear at each side, which its bar lines up with. */
private val STATUS_ROW_MARGIN = 12.dp
/** The words of [SessionStatusRow]: what the session is doing, and what it is holding. */
@Composable
private fun StatusWords(
@@ -2868,7 +2876,7 @@ private fun StatusWords(
) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxWidth().padding(horizontal = 12.dp, vertical = 4.dp),
modifier = Modifier.fillMaxWidth().padding(horizontal = STATUS_ROW_MARGIN, vertical = 4.dp),
) {
when (status) {
// No spinner: a compaction's bar is above, and nothing arrives in the transcript
@@ -3054,6 +3062,23 @@ private const val ONE_TAP_MS = 250L
* The button *is* the current setting rather than a label beside one, so the row says what the
* session is set to without spending a second line on saying it.
*/
/**
* One setting that is a choice from a list: what it is on the left, what it is set to on the right.
*
* The one shape for a choice in a list of settings, wherever the choice comes from -- a session's
* model, its permission mode, how hard it thinks, and every `ParamKind::Choice` a provider
* declares. They were two controls until 2026-09-21, so how hard a llama session thought was a row
* of chips and how hard a Claude session thought was this, which is two appearances for one kind of
* information.
*/
@Composable
fun PickerRow(label: String, current: String, options: List<String>, onPick: (String) -> Unit) {
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth()) {
Text(label, modifier = Modifier.weight(1f))
PickerButton(current = current, options = options, onPick = onPick)
}
}
@Composable
fun PickerButton(
current: String,