android, desktop: fix chat console crash on duplicate list keys

Terminal items were keyed by (System.currentTimeMillis(), System.nanoTime()).
nanoTime() never goes backwards but is not unique: two items created within
one clock tick get identical values, and currentTimeMillis() ties as well, so
the whole key collides and LazyColumn throws "Key was already used".

Terminal items are created on several threads - sendCmd runs in
Dispatchers.IO, received events are processed in another coroutine, and rejected
console commands on the main thread - and a command and its response are
created back to back, so ties happen, more often with a coarse clock.

Key by a per-item sequence number instead.
This commit is contained in:
Narasimha-sc committed 2026-10-19 10:30:14 +00:00
1 parent 139ce5f8cc
commit 86fb87e6bb
2 files changed
+5 -2

No files matched your search

@@ -62,6 +62,7 @@ import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import kotlinx.serialization.json.*
import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.atomic.AtomicLong
import java.time.format.DateTimeFormatter
import java.time.format.FormatStyle
import java.util.Date
@@ -7622,7 +7623,7 @@ abstract class TerminalItem {
val date: Instant = Clock.System.now()
abstract val label: String
abstract val details: String
val createdAtNanos: Long = System.nanoTime()
val itemNo: Long = itemNoGenerator.incrementAndGet()
class Cmd(override val id: Long, override val remoteHostId: Long?, val cmd: CC): TerminalItem() {
override val label get() = "> ${cmd.cmdString}"
@@ -7635,6 +7636,8 @@ abstract class TerminalItem {
}
companion object {
private val itemNoGenerator = AtomicLong(0)
val sampleData = listOf(
Cmd(0, null, CC.ShowActiveUser()),
Resp(1, null, API.Result(null, CR.ActiveUser(User.sampleData)))
@@ -165,7 +165,7 @@ fun TerminalLog(floating: Boolean, composeViewHeight: State<Dp>) {
reverseLayout = true,
additionalBarOffset = composeViewHeight
) {
items(reversedTerminalItems, key = { item -> item.id to item.createdAtNanos }) { item ->
items(reversedTerminalItems, key = { item -> item.itemNo }) { item ->
val clipboard = LocalClipboardManager.current
val rhId = item.remoteHostId
val rhIdStr = if (rhId == null) "" else "$rhId "