threads: order topic chips newest-first beneath General
The gateway already stored a created timestamp per channel/thread but never sent it over the wire. Now: - protocol.py: _channel_payload() includes created (unix seconds) - Protocol.kt: ChannelInfo.created (default 0.0 for legacy gateways) - ChatScreen: topic switcher sorts threads created-desc (newest right after General, swipe new -> old), name as tie-break - frames.schema.json: document the created field - ChannelCreatedWireTest: wire deserialization + ordering tests
This commit is contained in:
1 parent
83a67f6fb1
commit
573291fc1e
5 files changed
+64
-2
No files matched your search
@@ -150,6 +150,9 @@ data class ChannelInfo(
|
||||
@SerialName("is_default") val isDefault: Boolean = false,
|
||||
@SerialName("parent_chat_id") val parentChatId: String? = null,
|
||||
val archived: Boolean = false,
|
||||
/** Unix timestamp (seconds) when the channel/thread was created; 0.0 if
|
||||
* the gateway predates the field. Used to order threads newest-first. */
|
||||
val created: Double = 0.0,
|
||||
/** Gateway minted this thread for an incoming message (auto-threading);
|
||||
* the name is a derived title, upgraded by the LLM via channel.renamed. */
|
||||
val auto: Boolean = false,
|
||||
|
||||
@@ -175,7 +175,12 @@ fun ChatScreen(controller: IrisController) {
|
||||
val channels by controller.channels.channels.collectAsState()
|
||||
val (currentChatId, currentThreadId) = controller.chat.parseLane(currentLane)
|
||||
val currentChannel = channels.firstOrNull { it.chatId == currentChatId }
|
||||
val threads = channels.filter { it.kind == "thread" && it.parentChatId == currentChatId }
|
||||
// Threads newest-first (right after "General") so the user swipes from
|
||||
// new to old; ties (e.g. created==0 from an old cache) fall back to name.
|
||||
val threads =
|
||||
channels
|
||||
.filter { it.kind == "thread" && it.parentChatId == currentChatId }
|
||||
.sortedWith(compareByDescending<ChannelInfo> { it.created }.thenBy { it.name.lowercase() })
|
||||
|
||||
// M8: unread counts. Per-lane from the store; per-channel aggregated
|
||||
// (flat lane + all threads) for the drawer/rail badges.
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
package iris.protocol
|
||||
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
/** Wire tests for the channel directory `created` field (thread ordering). */
|
||||
class ChannelCreatedWireTest {
|
||||
@Test
|
||||
fun channelInfoDeserializesCreated() {
|
||||
val raw =
|
||||
"""
|
||||
{"v":1,"type":"channel.created","payload":{"chat_id":"t_9","name":"New topic",
|
||||
"kind":"thread","parent_chat_id":"default","created":1787648374.37}}
|
||||
""".trimIndent()
|
||||
val frame = IrisJson.instance.decodeFromString(Frame.serializer(), raw)
|
||||
val info = frame.payloadAs<ChannelInfo>()
|
||||
assertEquals("t_9", info?.chatId)
|
||||
assertEquals(1787648374.37, info?.created)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun channelInfoDefaultsCreatedToZero() {
|
||||
// Legacy gateways omit the field; the app falls back to name ordering.
|
||||
val raw =
|
||||
"""{"v":1,"type":"channel.created","payload":{"chat_id":"t_1","name":"Old","kind":"thread"}}"""
|
||||
val frame = IrisJson.instance.decodeFromString(Frame.serializer(), raw)
|
||||
assertEquals(0.0, frame.payloadAs<ChannelInfo>()?.created)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun threadsSortNewestFirst() {
|
||||
// Mirrors the topic-switcher ordering in ChatScreen: created desc,
|
||||
// name asc as the tie-break (e.g. for legacy entries with created==0).
|
||||
val threads =
|
||||
listOf(
|
||||
ChannelInfo(chatId = "t_2", name = "Capital of Romania", kind = "thread", parentChatId = "default", created = 1787232736.0),
|
||||
ChannelInfo(chatId = "t_1", name = "Capital of France", kind = "thread", parentChatId = "default", created = 1787232221.0),
|
||||
ChannelInfo(
|
||||
chatId = "t_9",
|
||||
name = "Test file operations",
|
||||
kind = "thread",
|
||||
parentChatId = "default",
|
||||
created = 1787422924.0,
|
||||
),
|
||||
ChannelInfo(chatId = "t_0", name = "Legacy b", kind = "thread", parentChatId = "default"),
|
||||
ChannelInfo(chatId = "t_0a", name = "Legacy a", kind = "thread", parentChatId = "default"),
|
||||
)
|
||||
val ordered =
|
||||
threads.sortedWith(compareByDescending<ChannelInfo> { it.created }.thenBy { it.name.lowercase() })
|
||||
assertEquals(listOf("t_9", "t_2", "t_1", "t_0a", "t_0"), ordered.map { it.chatId })
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user