feat: render display_kind timeline markers as chips, not user bubbles (#904) (#909)

Co-authored-by: M57 <hy4ri@users.noreply.github.com>
This commit is contained in:
M57andM57 authored and GitHub committed 2026-08-15 14:06:51 +03:00
1 parent 85087c70c7
commit 4be1e965f2
16 files changed
+438 -11

No files matched your search

@@ -81,6 +81,45 @@ class FullBleedTurnsTest {
assertEquals(listOf(entries(AgentEntry.SystemEvent(s))), result)
}
@Test
fun `display_kind marker is a SystemEvent entry not a user turn`() {
val marker =
msg("m1", MessageRole.USER, content = "[System: The active model has changed to gpt-5]")
.copy(displayKind = "model_switch")
val result = groupIntoTurns(listOf(marker))
assertEquals(listOf<ChatTurn>(entries(AgentEntry.SystemEvent(marker))), result)
}
@Test
fun `marker between assistant and user does not close the agent turn`() {
val a = msg("a1", MessageRole.ASSISTANT)
val marker =
msg("m1", MessageRole.USER, content = "marker")
.copy(displayKind = "personality_switch")
val u = msg("u1", MessageRole.USER)
val result = groupIntoTurns(listOf(a, marker, u))
assertEquals(
listOf(
entries(AgentEntry.Prose(a), AgentEntry.SystemEvent(marker)),
ChatTurn.User(u),
),
result,
)
}
@Test
fun `marker only closes nothing and trailing marker lands in agent turn`() {
val u = msg("u1", MessageRole.USER)
val marker =
msg("m1", MessageRole.USER, content = "marker")
.copy(displayKind = "auto_continue")
val result = groupIntoTurns(listOf(u, marker))
assertEquals(
listOf(ChatTurn.User(u), entries(AgentEntry.SystemEvent(marker))),
result,
)
}
@Test
fun `user message closes the current agent turn`() {
val a1 = msg("a1", MessageRole.ASSISTANT)
@@ -0,0 +1,59 @@
package com.m57.hermescontrol.ui.chat.fullbleed
import com.m57.hermescontrol.R
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNull
import org.junit.Test
class TimelineMarkerTest {
@Test
fun knownKindsMapToLabels() {
assertEquals(R.string.timeline_marker_model_switch, timelineMarkerLabelRes("model_switch"))
assertEquals(
R.string.timeline_marker_personality_switch,
timelineMarkerLabelRes("personality_switch"),
)
assertEquals(R.string.timeline_marker_auto_continue, timelineMarkerLabelRes("auto_continue"))
assertEquals(
R.string.timeline_marker_delegation_complete,
timelineMarkerLabelRes("async_delegation_complete"),
)
}
@Test
fun unknownAndNullKindsHaveNoLabel() {
assertNull(timelineMarkerLabelRes("hidden"))
assertNull(timelineMarkerLabelRes("some_future_kind"))
assertNull(timelineMarkerLabelRes(null))
assertNull(timelineMarkerLabelRes(""))
}
@Test
fun modelParsedFromMarkerContent() {
assertEquals(
"gpt-5",
markerModelFromContent(
"[System: The active model for this chat has changed to gpt-5 via provider openai. ...]",
),
)
assertEquals(
"deepseek-v4",
markerModelFromContent(
"The active model for this chat has changed to deepseek-v4. From this point forward...",
),
)
assertEquals(
"gpt-4.5",
markerModelFromContent(
"The active model for this chat has changed to gpt-4.5 via provider openai.",
),
)
}
@Test
fun modelParseHandlesMissingOrOddContent() {
assertNull(markerModelFromContent(""))
assertNull(markerModelFromContent("[System: personality switched]"))
assertNull(markerModelFromContent("changed to"))
}
}