git.iko.soy/opencode-android
on master help sign in
Narrow recomposition scope on chat, diff, and session list screens
commitbe390fbcfc935b793c1daead17966f92b18a2316 authoriko <mail@iko.soy> dateWed 1 Jul 2026, 02:11 (3 months ago) parent2589ee4
3 files changed+38−29
app/src/main/java/soy/iko/opencode/ui/chat/ChatScreen.kt+5−1 app/src/main/java/soy/iko/opencode/ui/components/DiffView.kt+32−27 app/src/main/java/soy/iko/opencode/ui/session/SessionListScreen.kt+1−1
app/src/main/java/soy/iko/opencode/ui/chat/ChatScreen.kt+5−1
@@ -347,6 +347,7 @@ fun ChatScreen(
347347 modifier = Modifier.align(Alignment.Center),
348348 )
349349 } else {
350+ val lastMessageId = messages.lastOrNull()?.info?.id
350351 LazyColumn(
351352 state = listState,
352353 modifier = Modifier.fillMaxSize(),
@@ -354,7 +355,10 @@ fun ChatScreen(
354355 verticalArrangement = Arrangement.spacedBy(16.dp),
355356 ) {
356357 items(messages, key = { it.info.id }, contentType = { it.info::class }) { message ->
357− MessageBubble(message, isRunning = running, imageContext = imageContext)
358+ // Only the last (streaming) message needs isRunning — it drives the
359+ // reasoning-block spinner. Passing the live flag to every bubble
360+ // makes all visible messages recompose whenever a run starts or stops.
361+ MessageBubble(message, isRunning = running && message.info.id == lastMessageId, imageContext = imageContext)
358362 }
359363 if (running) {
360364 item(key = "__typing") {
app/src/main/java/soy/iko/opencode/ui/components/DiffView.kt+32−27
@@ -129,33 +129,38 @@ fun DiffView(diff: String, modifier: Modifier = Modifier) {
129129 }
130130 val visibleLines = if (lines.size <= COLLAPSED_DIFF_LINES || expanded) lines
131131 else lines.subList(0, COLLAPSED_DIFF_LINES)
132− visibleLines.forEach { line ->
133− Row(modifier = Modifier.horizontalScroll(hScrollState).padding(horizontal = 10.dp)) {
134− when (line) {
135− is DiffLine.Hunk -> Text(
136− line.text,
137− style = MaterialTheme.typography.bodySmall,
138− fontFamily = FontFamily.Monospace,
139− color = MaterialTheme.colorScheme.tertiary,
140− modifier = Modifier.padding(vertical = 2.dp),
141− )
142− is DiffLine.FileHeader -> Text(
143− line.text,
144− style = MaterialTheme.typography.bodySmall,
145− fontFamily = FontFamily.Monospace,
146− color = MaterialTheme.colorScheme.onSurfaceVariant,
147− modifier = Modifier.padding(vertical = 1.dp),
148− )
149− is DiffLine.Meta -> Text(
150− line.text,
151− style = MaterialTheme.typography.bodySmall,
152− fontFamily = FontFamily.Monospace,
153− color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.6f),
154− modifier = Modifier.padding(vertical = 1.dp),
155− )
156− is DiffLine.Add -> DiffRow(line.text, "+", addColor, addText)
157− is DiffLine.Remove -> DiffRow(line.text, "-", removeColor, removeText)
158− is DiffLine.Context -> DiffRow(line.text, " ", Color.Transparent, MaterialTheme.colorScheme.onSurface)
132+ // One horizontalScroll on the container instead of one per row: each modifier
133+ // adds a layout node + clip + offset pass, so a 200-line collapsed diff was
134+ // paying for 200 of them. The shared scroll state still synchronizes all rows.
135+ Column(modifier = Modifier.horizontalScroll(hScrollState)) {
136+ visibleLines.forEach { line ->
137+ Row(modifier = Modifier.padding(horizontal = 10.dp)) {
138+ when (line) {
139+ is DiffLine.Hunk -> Text(
140+ line.text,
141+ style = MaterialTheme.typography.bodySmall,
142+ fontFamily = FontFamily.Monospace,
143+ color = MaterialTheme.colorScheme.tertiary,
144+ modifier = Modifier.padding(vertical = 2.dp),
145+ )
146+ is DiffLine.FileHeader -> Text(
147+ line.text,
148+ style = MaterialTheme.typography.bodySmall,
149+ fontFamily = FontFamily.Monospace,
150+ color = MaterialTheme.colorScheme.onSurfaceVariant,
151+ modifier = Modifier.padding(vertical = 1.dp),
152+ )
153+ is DiffLine.Meta -> Text(
154+ line.text,
155+ style = MaterialTheme.typography.bodySmall,
156+ fontFamily = FontFamily.Monospace,
157+ color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.6f),
158+ modifier = Modifier.padding(vertical = 1.dp),
159+ )
160+ is DiffLine.Add -> DiffRow(line.text, "+", addColor, addText)
161+ is DiffLine.Remove -> DiffRow(line.text, "-", removeColor, removeText)
162+ is DiffLine.Context -> DiffRow(line.text, " ", Color.Transparent, MaterialTheme.colorScheme.onSurface)
163+ }
159164 }
160165 }
161166 }
app/src/main/java/soy/iko/opencode/ui/session/SessionListScreen.kt+1−1
@@ -235,7 +235,7 @@ fun SessionListScreen(
235235 singleLine = true,
236236 keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search),
237237 )
238− val sessions = state.filtered
238+ val sessions = remember(state) { state.filtered }
239239 if (sessions.isEmpty()) {
240240 Text(
241241 stringResource(R.string.no_sessions_match, state.query),