From 579689cbb8a018d816cba00aa5b580d20e4dd899 Mon Sep 17 00:00:00 2001 From: iris <2+iris@noreply.localhost> Date: Sun, 13 Sep 2026 12:34:21 -0400 Subject: [PATCH] Keep a thinking level the settings dialog set The dialog held the level for as long as it was open and read it back from the frozen row the session screen was opened with, so reopening it showed the old level until a return to the list refetched the row. The level is the session screen's own datum now, like the title. --- .../kotlin/com/example/aiapp/SessionScreen.kt | 8 ++++++- .../example/aiapp/SessionSettingsDialog.kt | 21 +++++++++++-------- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/app/androidApp/src/main/kotlin/com/example/aiapp/SessionScreen.kt b/app/androidApp/src/main/kotlin/com/example/aiapp/SessionScreen.kt index 80fdfd1..4fdef21 100644 --- a/app/androidApp/src/main/kotlin/com/example/aiapp/SessionScreen.kt +++ b/app/androidApp/src/main/kotlin/com/example/aiapp/SessionScreen.kt @@ -323,6 +323,11 @@ fun SessionScreen( // reads as a rename that did not take. var title by remember(summary.id) { mutableStateOf(summary.title) } var model by remember { mutableStateOf(summary.model) } + // The thinking level, held here for the same reason [title] is: the settings dialog can change + // it, and the row this screen was opened from is a snapshot taken before that. Read straight + // from the summary, a level set here was drawn as the old one again the next time the dialog + // was opened, and only came back right after a return to the list refetched the row. + var effort by remember(summary.id) { mutableStateOf(summary.effort) } var permissionMode by remember { mutableStateOf(summary.permissionMode ?: "auto") } // The models this provider actually offers, asked of the server rather than listed here: a // hardcoded list is a claim about a machine. @@ -2064,8 +2069,9 @@ fun SessionScreen( settings = settings, sessionId = summary.id, title = title, - effort = summary.effort.takeIf { summary.takesEffort }, + effort = effort.takeIf { summary.takesEffort }, takesEffort = summary.takesEffort, + onEffortChanged = { effort = it }, cachedBytes = cachedBytes, // The purge finishes before the epoch moves, because the relaunched opening effect // reads the same directory and would otherwise draw what is about to be deleted. The diff --git a/app/androidApp/src/main/kotlin/com/example/aiapp/SessionSettingsDialog.kt b/app/androidApp/src/main/kotlin/com/example/aiapp/SessionSettingsDialog.kt index 0a68e41..ab50ca6 100644 --- a/app/androidApp/src/main/kotlin/com/example/aiapp/SessionSettingsDialog.kt +++ b/app/androidApp/src/main/kotlin/com/example/aiapp/SessionSettingsDialog.kt @@ -61,13 +61,17 @@ fun SessionSettingsDialog( title: String, onRenamed: (String) -> Unit, /** - * How hard the model thinks, as the session reports it, or null for the CLI's own default. + * How hard the model thinks, or null for the CLI's own default. * - * Taken from the row this dialog was opened over rather than fetched, because unlike the - * notification switch there is nothing else that changes it: the level is this app's to set and - * the server does not resolve it into something else. + * Owned by the screen behind this rather than held here, like [title]: this dialog is what + * changes it, and a level kept only for as long as the dialog is open is the old one again the + * next time it is opened. + * + * Not fetched, because unlike the notification switch there is nothing else that changes it: + * the level is this app's to set and the server does not resolve it into something else. */ effort: String?, + onEffortChanged: (String?) -> Unit, /** Whether a level does anything here; the row is left out entirely where it does not. */ takesEffort: Boolean, /** @@ -85,7 +89,6 @@ fun SessionSettingsDialog( ) { val scope = rememberCoroutineScope() var name by remember(sessionId) { mutableStateOf(title) } - var level by remember(sessionId) { mutableStateOf(effort) } var effortError by remember { mutableStateOf(null) } var saving by remember { mutableStateOf(false) } var error by remember { mutableStateOf(null) } @@ -165,14 +168,14 @@ fun SessionSettingsDialog( * control that stays where it was put after a refusal is stating something untrue. */ fun setEffort(chosen: String?) { - val was = level - level = chosen + val was = effort + onEffortChanged(chosen) effortError = null scope.launch { try { withContext(Dispatchers.IO) { setSessionEffort(settings, sessionId, chosen) } } catch (e: ApiException) { - level = was + onEffortChanged(was) effortError = e.message } } @@ -421,7 +424,7 @@ fun SessionSettingsDialog( ) { Text("Thinking", modifier = Modifier.weight(1f)) PickerButton( - current = level ?: DEFAULT_EFFORT, + current = effort ?: DEFAULT_EFFORT, // The level the CLI picks for itself is in the list as well as in the // button, so leaving a level is not a one-way trip -- the same // correction the model picker carries.