Vectrum
Sign In

Android QA fixes: follow the transcript's end, name default models, one panel header, fold the composer footer sooner

Also say "1 model" under Legacy models, and send the model Claude's default resolves to.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
JjohannSocommitted 1 parent e0937aacommitd7ac904

Showing 9 changed files with 35 additions and 22 deletions.

+3−2android/app/src/main/java/cc/vectrum/code/data/AppState.ktView File
@@ -293,8 +293,9 @@ class HostState(saved: SavedHost, private val app: AppState) {
293293 fun refreshCatalog() = send(ClientMessage.Catalog)
294294
295295 fun model(instance: String, id: String?): RemoteModel? {
296- val models = instances.find { it.id == instance }?.models ?: return null
297- return models.find { it.id == id } ?: models.find { it.resolved == id }
296+ // Threads saved before instances existed name none; any instance's model will do then.
297+ val models = instances.find { it.id == instance }?.models ?: instances.flatMap { it.models }
298+ return models.find { it.id == id } ?: models.find { it.resolved == id && !it.legacy } ?: models.find { it.resolved == id }
298299 }
299300
300301 // ---- requests --------------------------------------------------------------
+4−1android/app/src/main/java/cc/vectrum/code/ui/chat/Timeline.ktView File
@@ -156,7 +156,10 @@ fun Timeline(app: AppState, nav: Nav, thread: OpenThread, modifier: Modifier = M
156156 snapshotFlow { list.isScrollInProgress }.collect { scrolling -> if (!scrolling) stick = atEnd }
157157 }
158158 LaunchedEffect(thread.id) {
159- snapshotFlow { thread.items.size }.collect { if (stick) list.scrollToItem(0) }
159+ // Rows come and go as turns fold and items arrive; the list keeps its place by key, so follow by hand.
160+ snapshotFlow { list.layoutInfo.totalItemsCount to list.layoutInfo.viewportSize }.collect {
161+ if (stick && (list.firstVisibleItemIndex != 0 || list.firstVisibleItemScrollOffset != 0)) list.scrollToItem(0)
162+ }
160163 }
161164 val scope = rememberCoroutineScope()
162165
+11−4android/app/src/main/java/cc/vectrum/code/ui/chat/ToolCard.ktView File
@@ -4,6 +4,7 @@ package cc.vectrum.code.ui.chat
44
55 import androidx.compose.foundation.background
66 import androidx.compose.foundation.clickable
7+ import androidx.compose.foundation.layout.widthIn
78 import androidx.compose.foundation.layout.Arrangement
89 import androidx.compose.foundation.layout.Box
910 import androidx.compose.foundation.layout.Column
@@ -368,16 +369,22 @@ fun NoticeDivider(text: String) {
368369 horizontalArrangement = Arrangement.spacedBy(8.dp),
369370 verticalAlignment = Alignment.CenterVertically,
370371 ) {
371- Spacer(Modifier.weight(1f).height(1.dp).background(p.rule))
372+ Spacer(Modifier.weight(1f).widthIn(min = 16.dp).height(1.dp).background(p.rule))
372373 Row(
373- Modifier.weight(4f, fill = false).padding(horizontal = 8.dp),
374+ Modifier.weight(6f, fill = false).padding(horizontal = 8.dp),
374375 horizontalArrangement = Arrangement.spacedBy(6.dp),
375376 verticalAlignment = Alignment.CenterVertically,
376377 ) {
377378 if (compacted) LucideIcon(Lucide.Minimize2, size = 12.dp, tint = p.ink3)
378- Txt(if (compacted) "Context compacted" else text, color = p.ink3, size = 11.sp, weight = FontWeight.Medium)
379+ Txt(
380+ if (compacted) "Context compacted" else text,
381+ color = p.ink3,
382+ size = 11.sp,
383+ weight = FontWeight.Medium,
384+ style = androidx.compose.ui.text.TextStyle(textAlign = androidx.compose.ui.text.style.TextAlign.Center),
385+ )
379386 }
380- Spacer(Modifier.weight(1f).height(1.dp).background(p.rule))
387+ Spacer(Modifier.weight(1f).widthIn(min = 16.dp).height(1.dp).background(p.rule))
381388 }
382389 }
383390
+1−1android/app/src/main/java/cc/vectrum/code/ui/composer/Model.ktView File
@@ -285,7 +285,7 @@ fun commandItems(commands: List<RemoteCommand>, word: CommandWord, skillsToo: Bo
285285 // ---- the footer ----------------------------------------------------------------
286286
287287 /** About how wide a character of a footer label is. */
288- const val CharWidth = 7f
288+ const val CharWidth = 7.5f
289289
290290 /**
291291 * How much of the footer's left side shows: blocks (traits, then mode)
+3−3android/app/src/main/java/cc/vectrum/code/ui/composer/Pickers.ktView File
@@ -145,10 +145,10 @@ fun footerFit(target: PickTarget, width: Float, rightWidth: Float, planToggle: B
145145 fun labelWidth(label: String) = label.length * CharWidth
146146 val modelWidth = 52f + minOf(labelWidth(target.modelLabel), 200f)
147147 val blocks = mutableListOf<Pair<Float, Float>>()
148- if (traits != null) blocks.add(54f + labelWidth(traits) to 46f)
148+ if (traits != null) blocks.add(54f + labelWidth(traits) to 58f)
149149 var modeWidth = 54f + labelWidth(accessLabel(target.access))
150150 if (planToggle) modeWidth += 5f + 70f
151- blocks.add(modeWidth to 46f + if (planToggle) 37f else 0f)
151+ blocks.add(modeWidth to 58f + if (planToggle) 37f else 0f)
152152 val layout = footerLayout(width, blocks, modelWidth, rightWidth)
153153 fun shown(ix: Int) = ix < blocks.size - layout.hidden
154154 fun iconOnly(ix: Int) = ix >= blocks.size - minOf(layout.iconOnly, blocks.size)
@@ -500,7 +500,7 @@ fun ColumnScope.ModelPicker(app: AppState, target: PickTarget, close: () -> Unit
500500 Column(Modifier.weight(1f)) {
501501 Txt("Legacy models", size = 12.sp, weight = FontWeight.Medium, maxLines = 1)
502502 Txt(
503- "${legacy.size} models",
503+ "${legacy.size} model${if (legacy.size == 1) "" else "s"}",
504504 Modifier.padding(top = 4.dp),
505505 color = p.ink3.copy(alpha = 0.7f),
506506 size = 12.sp,
+7−1android/app/src/main/java/cc/vectrum/code/ui/composer/State.ktView File
@@ -173,5 +173,11 @@ class PickTarget(
173173 else -> permissionMode
174174 }
175175
176- val modelLabel: String get() = entry?.name ?: model ?: fallbackName ?: "Choose model"
176+ /** On the agent's default, the model it runs, named from the catalog when the host has one. */
177+ val modelLabel: String
178+ get() = entry?.name ?: model ?: fallbackName?.let { host?.model(instance, it)?.name ?: it } ?: defaultName ?: "Choose model"
179+
180+ /** What the agent's default model is, as the desktop's picker names `default`. */
181+ private val defaultName: String?
182+ get() = host?.instances?.find { it.id == instance }?.defaultModel?.let { host.model(instance, it)?.name }
177183 }
+3−7android/app/src/main/java/cc/vectrum/code/ui/panels/RightPanel.ktView File
@@ -79,13 +79,9 @@ fun RightPanel(app: AppState, nav: Nav, ref: ThreadRef) {
7979 when {
8080 nav.surfacePicker || surface == null -> SurfacePicker(nav)
8181 surface == Surface.Subagents -> SubagentsPanel(app, nav, ref)
82- else -> Column(Modifier.fillMaxSize()) {
83- val (icon, title) = if (surface == Surface.Terminal) Lucide.SquareTerminal to "Terminal" else Lucide.Smartphone to "Device"
84- PanelHeader(icon, title) { nav.surface = null }
85- Box(Modifier.weight(1f).fillMaxWidth()) {
86- if (surface == Surface.Terminal) TerminalPanel(app, nav, ref) else DevicePanel(app, nav, ref)
87- }
88- }
82+ // The terminal and device panels draw their own tab bar with a close button.
83+ surface == Surface.Terminal -> TerminalPanel(app, nav, ref)
84+ else -> DevicePanel(app, nav, ref)
8985 }
9086 }
9187 }
+2−2src/remote/mod.rsView File
@@ -285,7 +285,7 @@ pub struct RemoteInstance {
285285 pub status: String,
286286 pub account: Option<String>,
287287 pub plan: Option<String>,
288- /// The id of Claude's `default` entry, when it reports one.
288+ /// The model Claude's `default` entry resolves to (`claude-opus-5-5`), when it reports one.
289289 pub default_model: Option<String>,
290290 pub models: Vec<RemoteModel>,
291291 pub commands: Vec<RemoteCommand>,
@@ -358,7 +358,7 @@ impl RemoteInstance {
358358 status: status.into(),
359359 account: state.account.clone(),
360360 plan: state.plan.clone(),
361- default_model: state.default.as_ref().map(|model| model.id.clone()),
361+ default_model: state.default.as_ref().map(|model| model.resolved.clone()),
362362 models: state.models.iter().map(|model| RemoteModel::from_entry(model, catalog)).collect(),
363363 commands: state
364364 .commands
+1−1src/ui/pickers.rsView File
@@ -756,7 +756,7 @@ impl Render for ModelPicker {
756756 .mt(px(4.))
757757 .text_size(px(12.))
758758 .text_color(p.ink3.opacity(0.7))
759- .child(format!("{legacy_count} models")),
759+ .child(format!("{legacy_count} model{}", if legacy_count == 1 { "" } else { "s" })),
760760 ),
761761 )
762762 .child(