chore(ui): replace emoji with Material icons across chat UI (#944)

* chore(ui): replace emoji with Material icons across chat UI

Removes emoji glyphs and swaps them for real Material icons / plain text:

- ToolSchemaRegistry: iconEmoji String -> ImageVector icon for all ~35 tools;
  summaryPrefix emoji stripped (terminal keeps '$ '); consumer renders Icon().
- ToolBubble: collapsed summary now shows Icon(config.icon) instead of an emoji
  Text prefix; web-search url drops the link emoji; warning badge uses
  Icons.Filled.Warning instead of a '⚠' Text glyph.
- ChatBubble SelfImprovementReviewCard: 🛠/🧠 String -> Build/Psychology Icon.
- ChatViewModel / ChatWsEventReducer: drop ⚠️/✅ prefixes from user-facing
  status/error messages (severity already conveyed by color/styling).
- SubagentInspectionSheet: ✅/🔄/❌/⭕ status markers -> CheckCircle/Autorenew/
  Cancel/RadioButtonUnchecked (tinted); ElectricBolt section headers ->
  FormatListBulleted + Groups.
- strings.xml (en/zh/ko): drop '✓' from the Copied label.

ktlint clean; compileDebugKotlin SUCCESSFUL. Remaining chat files
(ToolViewBuilder, ChatComposer, MessageCards, ChatReactions, ComposerToolbar,
FullBleedAgentMessage) tracked in follow-up commits on this branch.

* test: align unit tests with emoji-free summary + status messages

composeToolSummaryLines dropped the emoji parameter and no longer prepends
an emoji glyph to the collapsed summary line, so update ToolSummaryLinesTest
call sites + expected strings accordingly.

SlashCommandDispatchRpcTest asserted the old '⚠️ /cmd: ...' error messages;
those prefixes were removed in the UI cleanup, so drop them from the
expected strings.

ChatWsEventReducerTest emoji usages are model-output fixtures, not assertions
on changed code paths — left untouched.
This commit is contained in:
M57 authored and GitHub committed 2026-08-21 00:07:37 +03:00
1 parent fad49b7b66
commit 4549410a45
11 files changed
+148 -108

No files matched your search

@@ -299,7 +299,7 @@ class SlashCommandDispatchRpcTest {
val last =
vm.uiState.value.messages
.lastOrNull()
assertEquals("⚠️ /help: session busy — /interrupt first", last?.content)
assertEquals("/help: session busy — /interrupt first", last?.content)
}
@Test
@@ -422,7 +422,7 @@ class SlashCommandDispatchRpcTest {
val last =
vm.uiState.value.messages
.lastOrNull()
assertEquals("⚠️ /status: slash worker start failed: boom", last?.content)
assertEquals("/status: slash worker start failed: boom", last?.content)
}
@Test
@@ -24,69 +24,69 @@ class ToolSummaryLinesTest {
)
@Test
fun `generic title merges into the emoji line`() {
val lines = composeToolSummaryLines(view("Fact Store", "Fact added (ID: 5)"), "fact_store", "🧠")
fun `generic title merges into the summary line`() {
val lines = composeToolSummaryLines(view("Fact Store", "Fact added (ID: 5)"), "fact_store")
assertEquals("🧠 Fact added (ID: 5)", lines!!.first)
assertEquals("Fact added (ID: 5)", lines!!.first)
assertNull(lines.second)
}
@Test
fun `todo shows counts on one line`() {
val lines = composeToolSummaryLines(view("Todo", "2 items (1 pending, 1 completed)"), "todo", "✅")
val lines = composeToolSummaryLines(view("Todo", "2 items (1 pending, 1 completed)"), "todo")
assertEquals("✅ 2 items (1 pending, 1 completed)", lines!!.first)
assertEquals("2 items (1 pending, 1 completed)", lines!!.first)
assertNull(lines.second)
}
@Test
fun `descriptive title stays with subtitle on line two`() {
val lines = composeToolSummaryLines(view("Searched \"cats\"", "Query: cats", "3 results"), "web_search", "🌐")
val lines = composeToolSummaryLines(view("Searched \"cats\"", "Query: cats", "3 results"), "web_search")
assertEquals("🌐 Searched \"cats\" (3 results)", lines!!.first)
assertEquals("Searched \"cats\" (3 results)", lines!!.first)
assertEquals("Query: cats", lines.second)
}
@Test
fun `terminal title stays with output preview on line two`() {
val lines = composeToolSummaryLines(view("Ran ls", "wiring.tsx"), "terminal", "💻")
val lines = composeToolSummaryLines(view("Ran ls", "wiring.tsx"), "terminal")
assertEquals("💻 Ran ls", lines!!.first)
assertEquals("Ran ls", lines!!.first)
assertEquals("wiring.tsx", lines.second)
}
@Test
fun `read file title stays with path on line two`() {
val lines = composeToolSummaryLines(view("Read a.kt L10-14", "/repo/src/a.kt"), "read_file", "📄")
val lines = composeToolSummaryLines(view("Read a.kt L10-14", "/repo/src/a.kt"), "read_file")
assertEquals("📄 Read a.kt L10-14", lines!!.first)
assertEquals("Read a.kt L10-14", lines!!.first)
assertEquals("/repo/src/a.kt", lines.second)
}
@Test
fun `generic title with no subtitle and no count is hidden`() {
assertNull(composeToolSummaryLines(view("Fact Store"), "fact_store", "🧠"))
assertNull(composeToolSummaryLines(view("Fact Store"), "fact_store"))
}
@Test
fun `unknown tool generic title merges with engine first line`() {
val lines = composeToolSummaryLines(view("Weird Tool", "- Title: Build report"), "weird_tool", "🔧")
val lines = composeToolSummaryLines(view("Weird Tool", "- Title: Build report"), "weird_tool")
assertEquals("🔧 - Title: Build report", lines!!.first)
assertEquals("- Title: Build report", lines!!.first)
assertNull(lines.second)
}
@Test
fun `null tool name resolves to the generic tool title`() {
val lines = composeToolSummaryLines(view("Tool", "raw payload text"), null, "🔧")
val lines = composeToolSummaryLines(view("Tool", "raw payload text"), null)
assertEquals("🔧 raw payload text", lines!!.first)
assertEquals("raw payload text", lines!!.first)
assertNull(lines.second)
}
@Test
fun `subtitle identical to title is not duplicated`() {
val lines = composeToolSummaryLines(view("Fact Store", "Fact Store"), "fact_store", "🧠")
val lines = composeToolSummaryLines(view("Fact Store", "Fact Store"), "fact_store")
assertNull(lines)
}