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:
@@ -143,7 +143,7 @@ sealed class WsEvent {
|
|||||||
/**
|
/**
|
||||||
* Self-improvement background review summary event.
|
* Self-improvement background review summary event.
|
||||||
* Emitted when background review patches a skill or saves a memory.
|
* Emitted when background review patches a skill or saves a memory.
|
||||||
* Payload: `{ text: "💾 Self-improvement review: ..." }`
|
* Payload: `{ text: "Self-improvement review: ..." }`
|
||||||
*/
|
*/
|
||||||
data class ReviewSummary(
|
data class ReviewSummary(
|
||||||
val text: String,
|
val text: String,
|
||||||
|
|||||||
@@ -25,10 +25,12 @@ import androidx.compose.foundation.shape.RoundedCornerShape
|
|||||||
import androidx.compose.foundation.text.selection.SelectionContainer
|
import androidx.compose.foundation.text.selection.SelectionContainer
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.filled.InsertDriveFile
|
import androidx.compose.material.icons.automirrored.filled.InsertDriveFile
|
||||||
|
import androidx.compose.material.icons.filled.Build
|
||||||
import androidx.compose.material.icons.filled.Check
|
import androidx.compose.material.icons.filled.Check
|
||||||
import androidx.compose.material.icons.filled.Close
|
import androidx.compose.material.icons.filled.Close
|
||||||
import androidx.compose.material.icons.filled.ContentCopy
|
import androidx.compose.material.icons.filled.ContentCopy
|
||||||
import androidx.compose.material.icons.filled.Download
|
import androidx.compose.material.icons.filled.Download
|
||||||
|
import androidx.compose.material.icons.filled.Psychology
|
||||||
import androidx.compose.material3.ButtonDefaults
|
import androidx.compose.material3.ButtonDefaults
|
||||||
import androidx.compose.material3.Card
|
import androidx.compose.material3.Card
|
||||||
import androidx.compose.material3.CardDefaults
|
import androidx.compose.material3.CardDefaults
|
||||||
@@ -51,6 +53,7 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.luminance
|
import androidx.compose.ui.graphics.luminance
|
||||||
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
import androidx.compose.ui.platform.ClipEntry
|
import androidx.compose.ui.platform.ClipEntry
|
||||||
import androidx.compose.ui.platform.LocalClipboard
|
import androidx.compose.ui.platform.LocalClipboard
|
||||||
import androidx.compose.ui.platform.LocalConfiguration
|
import androidx.compose.ui.platform.LocalConfiguration
|
||||||
@@ -65,7 +68,6 @@ import androidx.compose.ui.text.font.FontWeight
|
|||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.text.withStyle
|
import androidx.compose.ui.text.withStyle
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
|
||||||
import com.m57.hermescontrol.R
|
import com.m57.hermescontrol.R
|
||||||
import com.m57.hermescontrol.data.model.Attachment
|
import com.m57.hermescontrol.data.model.Attachment
|
||||||
import com.m57.hermescontrol.theme.DarkOnSurface
|
import com.m57.hermescontrol.theme.DarkOnSurface
|
||||||
@@ -257,7 +259,7 @@ private fun SelfImprovementReviewCard(
|
|||||||
val isSkill =
|
val isSkill =
|
||||||
cleanText.contains("skill", ignoreCase = true) ||
|
cleanText.contains("skill", ignoreCase = true) ||
|
||||||
cleanText.contains("SKILL.md", ignoreCase = true)
|
cleanText.contains("SKILL.md", ignoreCase = true)
|
||||||
val icon = if (isSkill) "🛠" else "🧠"
|
val icon: ImageVector = if (isSkill) Icons.Filled.Build else Icons.Filled.Psychology
|
||||||
val title =
|
val title =
|
||||||
if (isSkill) {
|
if (isSkill) {
|
||||||
"Self-Improvement Review • Skill Patched"
|
"Self-Improvement Review • Skill Patched"
|
||||||
@@ -279,7 +281,12 @@ private fun SelfImprovementReviewCard(
|
|||||||
) {
|
) {
|
||||||
Column(modifier = Modifier.padding(horizontal = 12.dp, vertical = 10.dp)) {
|
Column(modifier = Modifier.padding(horizontal = 12.dp, vertical = 10.dp)) {
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
Text(text = icon, fontSize = 16.sp)
|
Icon(
|
||||||
|
imageVector = icon,
|
||||||
|
contentDescription = null,
|
||||||
|
tint = MaterialTheme.colorScheme.primary,
|
||||||
|
modifier = Modifier.size(18.dp),
|
||||||
|
)
|
||||||
Spacer(Modifier.width(8.dp))
|
Spacer(Modifier.width(8.dp))
|
||||||
Text(
|
Text(
|
||||||
text = title,
|
text = title,
|
||||||
|
|||||||
@@ -1112,7 +1112,7 @@ class ChatViewModel(
|
|||||||
val map = result as? Map<*, *>
|
val map = result as? Map<*, *>
|
||||||
val resolved = (map?.get("resolved") as? Number)?.toInt() ?: 0
|
val resolved = (map?.get("resolved") as? Number)?.toInt() ?: 0
|
||||||
if (resolved > 0) {
|
if (resolved > 0) {
|
||||||
addSystemMessage("✅ Approval submitted")
|
addSystemMessage("Approval submitted")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1308,7 +1308,7 @@ class ChatViewModel(
|
|||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "Failed to upload attachment ${attachment.name}", e)
|
Log.e(TAG, "Failed to upload attachment ${attachment.name}", e)
|
||||||
_uiState.update {
|
_uiState.update {
|
||||||
it.copy(errorMessage = "⚠️ Upload failed: ${attachment.name}")
|
it.copy(errorMessage = "Upload failed: ${attachment.name}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1424,7 +1424,7 @@ class ChatViewModel(
|
|||||||
// (before any RPC fires) with a clear message instead of a doomed call.
|
// (before any RPC fires) with a clear message instead of a doomed call.
|
||||||
if (CommandBlocklist.contains(command)) {
|
if (CommandBlocklist.contains(command)) {
|
||||||
addAssistantMessage(
|
addAssistantMessage(
|
||||||
"⚠️ ${command.split(" ", limit = 2)[0]} is not supported on mobile",
|
"${command.split(" ", limit = 2)[0]} is not supported on mobile",
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1608,11 +1608,11 @@ class ChatViewModel(
|
|||||||
val output = (result as? Map<*, *>)?.get("output") as? String
|
val output = (result as? Map<*, *>)?.get("output") as? String
|
||||||
if (!output.isNullOrBlank()) addAssistantMessage(output)
|
if (!output.isNullOrBlank()) addAssistantMessage(output)
|
||||||
} catch (e2: HermesWsClient.HermesRpcException) {
|
} catch (e2: HermesWsClient.HermesRpcException) {
|
||||||
addAssistantMessage("⚠️ /$name: ${e2.message}")
|
addAssistantMessage("/$name: ${e2.message}")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Legit error from command.dispatch (busy, no history, etc.)
|
// Legit error from command.dispatch (busy, no history, etc.)
|
||||||
addAssistantMessage("⚠️ /$name: ${e.message}")
|
addAssistantMessage("/$name: ${e.message}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3315,7 +3315,7 @@ class ChatViewModel(
|
|||||||
|
|
||||||
private fun handleApprovalRequest(event: WsEvent.ApprovalRequest) {
|
private fun handleApprovalRequest(event: WsEvent.ApprovalRequest) {
|
||||||
val description = event.description ?: event.command ?: "Unknown command"
|
val description = event.description ?: event.command ?: "Unknown command"
|
||||||
val content = "⚠️ **Approval Required**\n$description"
|
val content = "**Approval Required**\n$description"
|
||||||
val msg =
|
val msg =
|
||||||
ChatMessage(
|
ChatMessage(
|
||||||
role = MessageRole.SYSTEM,
|
role = MessageRole.SYSTEM,
|
||||||
|
|||||||
@@ -682,7 +682,7 @@ object ChatWsEventReducer {
|
|||||||
state =
|
state =
|
||||||
state.copy(
|
state.copy(
|
||||||
isLoading = false,
|
isLoading = false,
|
||||||
errorMessage = "⚠️ Backend error: ${event.message ?: "Unknown gateway error"}",
|
errorMessage = "Backend error: ${event.message ?: "Unknown gateway error"}",
|
||||||
),
|
),
|
||||||
streamingState = streamingState,
|
streamingState = streamingState,
|
||||||
)
|
)
|
||||||
@@ -700,8 +700,8 @@ object ChatWsEventReducer {
|
|||||||
when (val label = event.data?.get("label")) {
|
when (val label = event.data?.get("label")) {
|
||||||
is String -> label
|
is String -> label
|
||||||
else -> event.data?.get("name") as? String
|
else -> event.data?.get("name") as? String
|
||||||
}?.let { "✅ Background job finished: $it" }
|
}?.let { "Background job finished: $it" }
|
||||||
?: "✅ Background job finished"
|
?: "Background job finished"
|
||||||
return ReducerResult(
|
return ReducerResult(
|
||||||
state = state.copy(backgroundCompleteMessage = message),
|
state = state.copy(backgroundCompleteMessage = message),
|
||||||
streamingState = streamingState,
|
streamingState = streamingState,
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import androidx.compose.foundation.layout.height
|
|||||||
import androidx.compose.foundation.layout.offset
|
import androidx.compose.foundation.layout.offset
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.foundation.text.selection.SelectionContainer
|
import androidx.compose.foundation.text.selection.SelectionContainer
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
@@ -26,6 +27,7 @@ import androidx.compose.material.icons.filled.Build
|
|||||||
import androidx.compose.material.icons.filled.CheckCircle
|
import androidx.compose.material.icons.filled.CheckCircle
|
||||||
import androidx.compose.material.icons.filled.ContentCopy
|
import androidx.compose.material.icons.filled.ContentCopy
|
||||||
import androidx.compose.material.icons.filled.Error
|
import androidx.compose.material.icons.filled.Error
|
||||||
|
import androidx.compose.material.icons.filled.Warning
|
||||||
import androidx.compose.material3.Card
|
import androidx.compose.material3.Card
|
||||||
import androidx.compose.material3.CardDefaults
|
import androidx.compose.material3.CardDefaults
|
||||||
import androidx.compose.material3.CircularProgressIndicator
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
@@ -79,7 +81,6 @@ import kotlinx.coroutines.launch
|
|||||||
internal fun composeToolSummaryLines(
|
internal fun composeToolSummaryLines(
|
||||||
view: ToolView,
|
view: ToolView,
|
||||||
toolName: String?,
|
toolName: String?,
|
||||||
emoji: String,
|
|
||||||
): Pair<String, String?>? {
|
): Pair<String, String?>? {
|
||||||
val titleIsGeneric = view.title.equals(ToolViewBuilder.genericTitleFor(toolName), ignoreCase = true)
|
val titleIsGeneric = view.title.equals(ToolViewBuilder.genericTitleFor(toolName), ignoreCase = true)
|
||||||
val subtitle = view.subtitle.takeIf { it.isNotBlank() && it != view.title }
|
val subtitle = view.subtitle.takeIf { it.isNotBlank() && it != view.title }
|
||||||
@@ -89,9 +90,8 @@ internal fun composeToolSummaryLines(
|
|||||||
if (titleIsGeneric) {
|
if (titleIsGeneric) {
|
||||||
firstLine =
|
firstLine =
|
||||||
buildString {
|
buildString {
|
||||||
append(emoji)
|
|
||||||
if (subtitle != null) {
|
if (subtitle != null) {
|
||||||
append(" $subtitle")
|
append(subtitle)
|
||||||
}
|
}
|
||||||
view.countLabel?.let { append(" ($it)") }
|
view.countLabel?.let { append(" ($it)") }
|
||||||
}
|
}
|
||||||
@@ -99,13 +99,13 @@ internal fun composeToolSummaryLines(
|
|||||||
} else {
|
} else {
|
||||||
firstLine =
|
firstLine =
|
||||||
buildString {
|
buildString {
|
||||||
append("$emoji ${view.title}")
|
append(view.title)
|
||||||
view.countLabel?.let { append(" ($it)") }
|
view.countLabel?.let { append(" ($it)") }
|
||||||
}
|
}
|
||||||
secondLine = subtitle
|
secondLine = subtitle
|
||||||
}
|
}
|
||||||
|
|
||||||
return firstLine.takeIf { it.trim() != emoji }?.let { it to secondLine }
|
return firstLine.takeIf { it.isNotBlank() }?.let { it to secondLine }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -180,21 +180,32 @@ internal fun ToolBubble(
|
|||||||
SecurityRiskChip(riskData, contentColor)
|
SecurityRiskChip(riskData, contentColor)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Collapsed summary: emoji + title, then subtitle ──
|
// ── Collapsed summary: icon + title, then subtitle ──
|
||||||
if (!expanded && view != null) {
|
if (!expanded && view != null) {
|
||||||
composeToolSummaryLines(view, message.toolName, config.iconEmoji)?.let { (firstLine, secondLine) ->
|
composeToolSummaryLines(view, message.toolName)?.let { (firstLine, secondLine) ->
|
||||||
Text(
|
Row(
|
||||||
text = firstLine,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
style =
|
|
||||||
MaterialTheme.typography.bodySmall.copy(
|
|
||||||
color = contentColor.copy(alpha = 0.7f),
|
|
||||||
fontFamily = FontFamily.Monospace,
|
|
||||||
fontSize = 11.sp,
|
|
||||||
),
|
|
||||||
maxLines = 1,
|
|
||||||
overflow = TextOverflow.Ellipsis,
|
|
||||||
modifier = Modifier.padding(top = 4.dp, start = 22.dp),
|
modifier = Modifier.padding(top = 4.dp, start = 22.dp),
|
||||||
)
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = config.icon,
|
||||||
|
contentDescription = null,
|
||||||
|
tint = contentColor.copy(alpha = 0.7f),
|
||||||
|
modifier = Modifier.size(13.dp),
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(4.dp))
|
||||||
|
Text(
|
||||||
|
text = firstLine,
|
||||||
|
style =
|
||||||
|
MaterialTheme.typography.bodySmall.copy(
|
||||||
|
color = contentColor.copy(alpha = 0.7f),
|
||||||
|
fontFamily = FontFamily.Monospace,
|
||||||
|
fontSize = 11.sp,
|
||||||
|
),
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
)
|
||||||
|
}
|
||||||
if (secondLine != null) {
|
if (secondLine != null) {
|
||||||
Text(
|
Text(
|
||||||
text = secondLine,
|
text = secondLine,
|
||||||
@@ -450,7 +461,7 @@ private fun ExpandedToolContent(
|
|||||||
}
|
}
|
||||||
if (hit.url.isNotEmpty()) {
|
if (hit.url.isNotEmpty()) {
|
||||||
Text(
|
Text(
|
||||||
text = "🔗 ${hit.url}",
|
text = hit.url,
|
||||||
style =
|
style =
|
||||||
MaterialTheme.typography.bodySmall.copy(
|
MaterialTheme.typography.bodySmall.copy(
|
||||||
color = contentColor.copy(alpha = 0.7f),
|
color = contentColor.copy(alpha = 0.7f),
|
||||||
@@ -567,10 +578,11 @@ internal fun SecurityRiskChip(
|
|||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||||
) {
|
) {
|
||||||
Text(
|
Icon(
|
||||||
text = "⚠",
|
imageVector = Icons.Filled.Warning,
|
||||||
style = MaterialTheme.typography.labelSmall,
|
contentDescription = null,
|
||||||
color = chipColor,
|
tint = chipColor,
|
||||||
|
modifier = Modifier.size(14.dp),
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
text = label,
|
text = label,
|
||||||
|
|||||||
@@ -1,5 +1,36 @@
|
|||||||
package com.m57.hermescontrol.ui.chat
|
package com.m57.hermescontrol.ui.chat
|
||||||
|
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.automirrored.filled.MenuBook
|
||||||
|
import androidx.compose.material.icons.automirrored.filled.Send
|
||||||
|
import androidx.compose.material.icons.automirrored.filled.VolumeUp
|
||||||
|
import androidx.compose.material.icons.filled.AccountTree
|
||||||
|
import androidx.compose.material.icons.filled.AutoStories
|
||||||
|
import androidx.compose.material.icons.filled.Build
|
||||||
|
import androidx.compose.material.icons.filled.ChatBubble
|
||||||
|
import androidx.compose.material.icons.filled.Checklist
|
||||||
|
import androidx.compose.material.icons.filled.Computer
|
||||||
|
import androidx.compose.material.icons.filled.Description
|
||||||
|
import androidx.compose.material.icons.filled.Edit
|
||||||
|
import androidx.compose.material.icons.filled.Forum
|
||||||
|
import androidx.compose.material.icons.filled.Image
|
||||||
|
import androidx.compose.material.icons.filled.Keyboard
|
||||||
|
import androidx.compose.material.icons.filled.Language
|
||||||
|
import androidx.compose.material.icons.filled.Memory
|
||||||
|
import androidx.compose.material.icons.filled.Movie
|
||||||
|
import androidx.compose.material.icons.filled.Photo
|
||||||
|
import androidx.compose.material.icons.filled.PlayArrow
|
||||||
|
import androidx.compose.material.icons.filled.Psychology
|
||||||
|
import androidx.compose.material.icons.filled.Public
|
||||||
|
import androidx.compose.material.icons.filled.Schedule
|
||||||
|
import androidx.compose.material.icons.filled.Search
|
||||||
|
import androidx.compose.material.icons.filled.Settings
|
||||||
|
import androidx.compose.material.icons.filled.Terminal
|
||||||
|
import androidx.compose.material.icons.filled.ThumbUp
|
||||||
|
import androidx.compose.material.icons.filled.TouchApp
|
||||||
|
import androidx.compose.material.icons.filled.Visibility
|
||||||
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Display configuration for a Hermes tool — controls the summary line,
|
* Display configuration for a Hermes tool — controls the summary line,
|
||||||
* icon, and which arg field is shown when the bubble is collapsed.
|
* icon, and which arg field is shown when the bubble is collapsed.
|
||||||
@@ -11,10 +42,10 @@ data class ToolDisplayConfig(
|
|||||||
val name: String,
|
val name: String,
|
||||||
/** Key into the `args` dict for the one-line summary (e.g. "command" for terminal). */
|
/** Key into the `args` dict for the one-line summary (e.g. "command" for terminal). */
|
||||||
val summaryArgKey: String? = null,
|
val summaryArgKey: String? = null,
|
||||||
/** Prefix for the summary line (e.g. "$ " for terminal, "📄 " for read_file). */
|
/** Prefix for the summary line (e.g. "$ " for terminal). Emoji-free. */
|
||||||
val summaryPrefix: String = "",
|
val summaryPrefix: String = "",
|
||||||
/** Emoji icon for this tool type. */
|
/** Material icon for this tool type. */
|
||||||
val iconEmoji: String = "🔧",
|
val icon: ImageVector = Icons.Filled.Build,
|
||||||
)
|
)
|
||||||
|
|
||||||
/** Maps tool names to their display config. Ordered by expected frequency of use. */
|
/** Maps tool names to their display config. Ordered by expected frequency of use. */
|
||||||
@@ -26,240 +57,230 @@ object ToolSchemaRegistry {
|
|||||||
name = "terminal",
|
name = "terminal",
|
||||||
summaryArgKey = "command",
|
summaryArgKey = "command",
|
||||||
summaryPrefix = "$ ",
|
summaryPrefix = "$ ",
|
||||||
iconEmoji = "💻",
|
icon = Icons.Filled.Terminal,
|
||||||
),
|
),
|
||||||
"read_file" to
|
"read_file" to
|
||||||
ToolDisplayConfig(
|
ToolDisplayConfig(
|
||||||
name = "read_file",
|
name = "read_file",
|
||||||
summaryArgKey = "path",
|
summaryArgKey = "path",
|
||||||
summaryPrefix = "📄 ",
|
icon = Icons.Filled.Description,
|
||||||
iconEmoji = "📄",
|
|
||||||
),
|
),
|
||||||
"write_file" to
|
"write_file" to
|
||||||
ToolDisplayConfig(
|
ToolDisplayConfig(
|
||||||
name = "write_file",
|
name = "write_file",
|
||||||
summaryArgKey = "path",
|
summaryArgKey = "path",
|
||||||
summaryPrefix = "✏️ ",
|
icon = Icons.Filled.Edit,
|
||||||
iconEmoji = "✏️",
|
|
||||||
),
|
),
|
||||||
"patch" to
|
"patch" to
|
||||||
ToolDisplayConfig(
|
ToolDisplayConfig(
|
||||||
name = "patch",
|
name = "patch",
|
||||||
summaryArgKey = "path",
|
summaryArgKey = "path",
|
||||||
summaryPrefix = "🔧 ",
|
icon = Icons.Filled.Build,
|
||||||
iconEmoji = "🔧",
|
|
||||||
),
|
),
|
||||||
"search_files" to
|
"search_files" to
|
||||||
ToolDisplayConfig(
|
ToolDisplayConfig(
|
||||||
name = "search_files",
|
name = "search_files",
|
||||||
summaryArgKey = "pattern",
|
summaryArgKey = "pattern",
|
||||||
summaryPrefix = "🔍 ",
|
icon = Icons.Filled.Search,
|
||||||
iconEmoji = "🔍",
|
|
||||||
),
|
),
|
||||||
"web_search" to
|
"web_search" to
|
||||||
ToolDisplayConfig(
|
ToolDisplayConfig(
|
||||||
name = "web_search",
|
name = "web_search",
|
||||||
summaryArgKey = "query",
|
summaryArgKey = "query",
|
||||||
summaryPrefix = "🌐 ",
|
icon = Icons.Filled.Public,
|
||||||
iconEmoji = "🌐",
|
|
||||||
),
|
),
|
||||||
"browser_navigate" to
|
"browser_navigate" to
|
||||||
ToolDisplayConfig(
|
ToolDisplayConfig(
|
||||||
name = "browser_navigate",
|
name = "browser_navigate",
|
||||||
summaryArgKey = "url",
|
summaryArgKey = "url",
|
||||||
summaryPrefix = "🌍 ",
|
icon = Icons.Filled.Language,
|
||||||
iconEmoji = "🌍",
|
|
||||||
),
|
),
|
||||||
"browser_click" to
|
"browser_click" to
|
||||||
ToolDisplayConfig(
|
ToolDisplayConfig(
|
||||||
name = "browser_click",
|
name = "browser_click",
|
||||||
summaryArgKey = "ref",
|
summaryArgKey = "ref",
|
||||||
summaryPrefix = "🖱 ",
|
icon = Icons.Filled.TouchApp,
|
||||||
iconEmoji = "🖱",
|
|
||||||
),
|
),
|
||||||
"browser_snapshot" to
|
"browser_snapshot" to
|
||||||
ToolDisplayConfig(
|
ToolDisplayConfig(
|
||||||
name = "browser_snapshot",
|
name = "browser_snapshot",
|
||||||
summaryArgKey = null,
|
summaryArgKey = null,
|
||||||
iconEmoji = "📋",
|
icon = Icons.Filled.Photo,
|
||||||
),
|
),
|
||||||
"clarify" to
|
"clarify" to
|
||||||
ToolDisplayConfig(
|
ToolDisplayConfig(
|
||||||
name = "clarify",
|
name = "clarify",
|
||||||
summaryArgKey = "question",
|
summaryArgKey = "question",
|
||||||
summaryPrefix = "💬 ",
|
icon = Icons.Filled.ChatBubble,
|
||||||
iconEmoji = "💬",
|
|
||||||
),
|
),
|
||||||
"delegate_task" to
|
"delegate_task" to
|
||||||
ToolDisplayConfig(
|
ToolDisplayConfig(
|
||||||
name = "delegate_task",
|
name = "delegate_task",
|
||||||
summaryArgKey = "goal",
|
summaryArgKey = "goal",
|
||||||
summaryPrefix = "🔄 ",
|
icon = Icons.Filled.AccountTree,
|
||||||
iconEmoji = "🔄",
|
|
||||||
),
|
),
|
||||||
"execute_code" to
|
"execute_code" to
|
||||||
ToolDisplayConfig(
|
ToolDisplayConfig(
|
||||||
name = "execute_code",
|
name = "execute_code",
|
||||||
summaryArgKey = "code",
|
summaryArgKey = "code",
|
||||||
summaryPrefix = "▶️ ",
|
icon = Icons.Filled.PlayArrow,
|
||||||
iconEmoji = "▶️",
|
|
||||||
),
|
),
|
||||||
"todo" to
|
"todo" to
|
||||||
ToolDisplayConfig(
|
ToolDisplayConfig(
|
||||||
name = "todo",
|
name = "todo",
|
||||||
summaryArgKey = null,
|
summaryArgKey = null,
|
||||||
iconEmoji = "📋",
|
icon = Icons.Filled.Checklist,
|
||||||
),
|
),
|
||||||
"fact_store" to
|
"fact_store" to
|
||||||
ToolDisplayConfig(
|
ToolDisplayConfig(
|
||||||
name = "fact_store",
|
name = "fact_store",
|
||||||
summaryArgKey = null,
|
summaryArgKey = null,
|
||||||
iconEmoji = "🧠",
|
icon = Icons.Filled.Psychology,
|
||||||
),
|
),
|
||||||
"session_search" to
|
"session_search" to
|
||||||
ToolDisplayConfig(
|
ToolDisplayConfig(
|
||||||
name = "session_search",
|
name = "session_search",
|
||||||
summaryArgKey = null,
|
summaryArgKey = null,
|
||||||
iconEmoji = "🔍",
|
icon = Icons.Filled.Search,
|
||||||
),
|
),
|
||||||
// ── Action-based tools ──
|
// ── Action-based tools ──
|
||||||
"cronjob" to
|
"cronjob" to
|
||||||
ToolDisplayConfig(
|
ToolDisplayConfig(
|
||||||
name = "cronjob",
|
name = "cronjob",
|
||||||
summaryArgKey = "action",
|
summaryArgKey = "action",
|
||||||
iconEmoji = "🔄",
|
icon = Icons.Filled.Schedule,
|
||||||
),
|
),
|
||||||
"memory" to
|
"memory" to
|
||||||
ToolDisplayConfig(
|
ToolDisplayConfig(
|
||||||
name = "memory",
|
name = "memory",
|
||||||
summaryArgKey = "action",
|
summaryArgKey = "action",
|
||||||
iconEmoji = "💾",
|
icon = Icons.Filled.Memory,
|
||||||
),
|
),
|
||||||
"fact_feedback" to
|
"fact_feedback" to
|
||||||
ToolDisplayConfig(
|
ToolDisplayConfig(
|
||||||
name = "fact_feedback",
|
name = "fact_feedback",
|
||||||
summaryArgKey = "action",
|
summaryArgKey = "action",
|
||||||
iconEmoji = "👍",
|
icon = Icons.Filled.ThumbUp,
|
||||||
),
|
),
|
||||||
"process" to
|
"process" to
|
||||||
ToolDisplayConfig(
|
ToolDisplayConfig(
|
||||||
name = "process",
|
name = "process",
|
||||||
summaryArgKey = "action",
|
summaryArgKey = "action",
|
||||||
iconEmoji = "⚙️",
|
icon = Icons.Filled.Settings,
|
||||||
),
|
),
|
||||||
"skill_manage" to
|
"skill_manage" to
|
||||||
ToolDisplayConfig(
|
ToolDisplayConfig(
|
||||||
name = "skill_manage",
|
name = "skill_manage",
|
||||||
summaryArgKey = "action",
|
summaryArgKey = "action",
|
||||||
iconEmoji = "🛠️",
|
icon = Icons.Filled.Build,
|
||||||
),
|
),
|
||||||
// ── Web / Browser tools ──
|
// ── Web / Browser tools ──
|
||||||
"web_extract" to
|
"web_extract" to
|
||||||
ToolDisplayConfig(
|
ToolDisplayConfig(
|
||||||
name = "web_extract",
|
name = "web_extract",
|
||||||
summaryArgKey = "urls",
|
summaryArgKey = "urls",
|
||||||
iconEmoji = "🕸️",
|
icon = Icons.Filled.Language,
|
||||||
),
|
),
|
||||||
"browser_type" to
|
"browser_type" to
|
||||||
ToolDisplayConfig(
|
ToolDisplayConfig(
|
||||||
name = "browser_type",
|
name = "browser_type",
|
||||||
summaryArgKey = null,
|
summaryArgKey = null,
|
||||||
iconEmoji = "⌨️",
|
icon = Icons.Filled.Keyboard,
|
||||||
),
|
),
|
||||||
"browser_cdp" to
|
"browser_cdp" to
|
||||||
ToolDisplayConfig(
|
ToolDisplayConfig(
|
||||||
name = "browser_cdp",
|
name = "browser_cdp",
|
||||||
summaryArgKey = "action",
|
summaryArgKey = "action",
|
||||||
iconEmoji = "🔧",
|
icon = Icons.Filled.Build,
|
||||||
),
|
),
|
||||||
"browser_dialog" to
|
"browser_dialog" to
|
||||||
ToolDisplayConfig(
|
ToolDisplayConfig(
|
||||||
name = "browser_dialog",
|
name = "browser_dialog",
|
||||||
summaryArgKey = "action",
|
summaryArgKey = "action",
|
||||||
iconEmoji = "💬",
|
icon = Icons.Filled.ChatBubble,
|
||||||
),
|
),
|
||||||
// ── Media / Vision tools ──
|
// ── Media / Vision tools ──
|
||||||
"vision_analyze" to
|
"vision_analyze" to
|
||||||
ToolDisplayConfig(
|
ToolDisplayConfig(
|
||||||
name = "vision_analyze",
|
name = "vision_analyze",
|
||||||
summaryArgKey = "image_url",
|
summaryArgKey = "image_url",
|
||||||
iconEmoji = "👁️",
|
icon = Icons.Filled.Visibility,
|
||||||
),
|
),
|
||||||
"text_to_speech" to
|
"text_to_speech" to
|
||||||
ToolDisplayConfig(
|
ToolDisplayConfig(
|
||||||
name = "text_to_speech",
|
name = "text_to_speech",
|
||||||
summaryArgKey = null,
|
summaryArgKey = null,
|
||||||
iconEmoji = "🔊",
|
icon = Icons.AutoMirrored.Filled.VolumeUp,
|
||||||
),
|
),
|
||||||
"video_generate" to
|
"video_generate" to
|
||||||
ToolDisplayConfig(
|
ToolDisplayConfig(
|
||||||
name = "video_generate",
|
name = "video_generate",
|
||||||
summaryArgKey = null,
|
summaryArgKey = null,
|
||||||
iconEmoji = "🎬",
|
icon = Icons.Filled.Movie,
|
||||||
),
|
),
|
||||||
"image_generate" to
|
"image_generate" to
|
||||||
ToolDisplayConfig(
|
ToolDisplayConfig(
|
||||||
name = "image_generate",
|
name = "image_generate",
|
||||||
summaryArgKey = null,
|
summaryArgKey = null,
|
||||||
iconEmoji = "🎨",
|
icon = Icons.Filled.Image,
|
||||||
),
|
),
|
||||||
// ── Social / Messaging tools ──
|
// ── Social / Messaging tools ──
|
||||||
"x_search" to
|
"x_search" to
|
||||||
ToolDisplayConfig(
|
ToolDisplayConfig(
|
||||||
name = "x_search",
|
name = "x_search",
|
||||||
summaryArgKey = "query",
|
summaryArgKey = "query",
|
||||||
iconEmoji = "𝕏",
|
icon = Icons.Filled.Forum,
|
||||||
),
|
),
|
||||||
"send_message" to
|
"send_message" to
|
||||||
ToolDisplayConfig(
|
ToolDisplayConfig(
|
||||||
name = "send_message",
|
name = "send_message",
|
||||||
summaryArgKey = null,
|
summaryArgKey = null,
|
||||||
iconEmoji = "📨",
|
icon = Icons.AutoMirrored.Filled.Send,
|
||||||
),
|
),
|
||||||
// ── Skills tools ──
|
// ── Skills tools ──
|
||||||
"skills_list" to
|
"skills_list" to
|
||||||
ToolDisplayConfig(
|
ToolDisplayConfig(
|
||||||
name = "skills_list",
|
name = "skills_list",
|
||||||
summaryArgKey = null,
|
summaryArgKey = null,
|
||||||
iconEmoji = "📚",
|
icon = Icons.AutoMirrored.Filled.MenuBook,
|
||||||
),
|
),
|
||||||
"skill_view" to
|
"skill_view" to
|
||||||
ToolDisplayConfig(
|
ToolDisplayConfig(
|
||||||
name = "skill_view",
|
name = "skill_view",
|
||||||
summaryArgKey = "name",
|
summaryArgKey = "name",
|
||||||
iconEmoji = "📖",
|
icon = Icons.Filled.AutoStories,
|
||||||
),
|
),
|
||||||
// ── On-demand tools ──
|
// ── On-demand tools ──
|
||||||
"tool_search" to
|
"tool_search" to
|
||||||
ToolDisplayConfig(
|
ToolDisplayConfig(
|
||||||
name = "tool_search",
|
name = "tool_search",
|
||||||
summaryArgKey = "query",
|
summaryArgKey = "query",
|
||||||
iconEmoji = "🔍",
|
icon = Icons.Filled.Search,
|
||||||
),
|
),
|
||||||
"tool_describe" to
|
"tool_describe" to
|
||||||
ToolDisplayConfig(
|
ToolDisplayConfig(
|
||||||
name = "tool_describe",
|
name = "tool_describe",
|
||||||
summaryArgKey = "name",
|
summaryArgKey = "name",
|
||||||
iconEmoji = "📖",
|
icon = Icons.Filled.AutoStories,
|
||||||
),
|
),
|
||||||
"tool_call" to
|
"tool_call" to
|
||||||
ToolDisplayConfig(
|
ToolDisplayConfig(
|
||||||
name = "tool_call",
|
name = "tool_call",
|
||||||
summaryArgKey = "name",
|
summaryArgKey = "name",
|
||||||
iconEmoji = "🔧",
|
icon = Icons.Filled.Build,
|
||||||
),
|
),
|
||||||
// ── Misc ──
|
// ── Misc ──
|
||||||
"read_terminal" to
|
"read_terminal" to
|
||||||
ToolDisplayConfig(
|
ToolDisplayConfig(
|
||||||
name = "read_terminal",
|
name = "read_terminal",
|
||||||
summaryArgKey = "session_id",
|
summaryArgKey = "session_id",
|
||||||
iconEmoji = "🖥️",
|
icon = Icons.Filled.Computer,
|
||||||
),
|
),
|
||||||
"computer_use" to
|
"computer_use" to
|
||||||
ToolDisplayConfig(
|
ToolDisplayConfig(
|
||||||
name = "computer_use",
|
name = "computer_use",
|
||||||
summaryArgKey = "action",
|
summaryArgKey = "action",
|
||||||
iconEmoji = "🖥️",
|
icon = Icons.Filled.Computer,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -414,7 +414,7 @@
|
|||||||
<string name="profiles_action_auto_describe">자동 설명</string>
|
<string name="profiles_action_auto_describe">자동 설명</string>
|
||||||
<string name="profiles_action_setup_command">설정 명령</string>
|
<string name="profiles_action_setup_command">설정 명령</string>
|
||||||
<string name="profiles_action_copy">복사</string>
|
<string name="profiles_action_copy">복사</string>
|
||||||
<string name="profiles_action_copied">복사됨 ✓</string>
|
<string name="profiles_action_copied">복사됨</string>
|
||||||
<string name="profiles_title_rename">프로필 이름 변경: %1$s</string>
|
<string name="profiles_title_rename">프로필 이름 변경: %1$s</string>
|
||||||
<string name="profiles_title_delete">프로필 삭제: %1$s</string>
|
<string name="profiles_title_delete">프로필 삭제: %1$s</string>
|
||||||
<string name="profiles_title_setup_command">설정 명령: %1$s</string>
|
<string name="profiles_title_setup_command">설정 명령: %1$s</string>
|
||||||
|
|||||||
@@ -557,7 +557,7 @@
|
|||||||
<string name="profiles_action_auto_describe">自动描述</string>
|
<string name="profiles_action_auto_describe">自动描述</string>
|
||||||
<string name="profiles_action_setup_command">安装命令</string>
|
<string name="profiles_action_setup_command">安装命令</string>
|
||||||
<string name="profiles_action_copy">复制</string>
|
<string name="profiles_action_copy">复制</string>
|
||||||
<string name="profiles_action_copied">已复制 ✓</string>
|
<string name="profiles_action_copied">已复制</string>
|
||||||
<string name="profiles_title_rename">重命名配置:%1$s</string>
|
<string name="profiles_title_rename">重命名配置:%1$s</string>
|
||||||
<string name="profiles_title_delete">删除配置:%1$s</string>
|
<string name="profiles_title_delete">删除配置:%1$s</string>
|
||||||
<string name="profiles_title_setup_command">安装命令:%1$s</string>
|
<string name="profiles_title_setup_command">安装命令:%1$s</string>
|
||||||
|
|||||||
@@ -643,7 +643,7 @@
|
|||||||
<string name="profiles_action_auto_describe">Auto-Describe</string>
|
<string name="profiles_action_auto_describe">Auto-Describe</string>
|
||||||
<string name="profiles_action_setup_command">Setup Command</string>
|
<string name="profiles_action_setup_command">Setup Command</string>
|
||||||
<string name="profiles_action_copy">Copy</string>
|
<string name="profiles_action_copy">Copy</string>
|
||||||
<string name="profiles_action_copied">Copied ✓</string>
|
<string name="profiles_action_copied">Copied</string>
|
||||||
<string name="profiles_title_rename">Rename Profile: %1$s</string>
|
<string name="profiles_title_rename">Rename Profile: %1$s</string>
|
||||||
<string name="profiles_title_delete">Delete Profile: %1$s</string>
|
<string name="profiles_title_delete">Delete Profile: %1$s</string>
|
||||||
<string name="profiles_title_setup_command">Setup Command: %1$s</string>
|
<string name="profiles_title_setup_command">Setup Command: %1$s</string>
|
||||||
|
|||||||
@@ -299,7 +299,7 @@ class SlashCommandDispatchRpcTest {
|
|||||||
val last =
|
val last =
|
||||||
vm.uiState.value.messages
|
vm.uiState.value.messages
|
||||||
.lastOrNull()
|
.lastOrNull()
|
||||||
assertEquals("⚠️ /help: session busy — /interrupt first", last?.content)
|
assertEquals("/help: session busy — /interrupt first", last?.content)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -422,7 +422,7 @@ class SlashCommandDispatchRpcTest {
|
|||||||
val last =
|
val last =
|
||||||
vm.uiState.value.messages
|
vm.uiState.value.messages
|
||||||
.lastOrNull()
|
.lastOrNull()
|
||||||
assertEquals("⚠️ /status: slash worker start failed: boom", last?.content)
|
assertEquals("/status: slash worker start failed: boom", last?.content)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -24,69 +24,69 @@ class ToolSummaryLinesTest {
|
|||||||
)
|
)
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `generic title merges into the emoji line`() {
|
fun `generic title merges into the summary line`() {
|
||||||
val lines = composeToolSummaryLines(view("Fact Store", "Fact added (ID: 5)"), "fact_store", "🧠")
|
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)
|
assertNull(lines.second)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `todo shows counts on one line`() {
|
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)
|
assertNull(lines.second)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `descriptive title stays with subtitle on line two`() {
|
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)
|
assertEquals("Query: cats", lines.second)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `terminal title stays with output preview on line two`() {
|
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)
|
assertEquals("wiring.tsx", lines.second)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `read file title stays with path on line two`() {
|
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)
|
assertEquals("/repo/src/a.kt", lines.second)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `generic title with no subtitle and no count is hidden`() {
|
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
|
@Test
|
||||||
fun `unknown tool generic title merges with engine first line`() {
|
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)
|
assertNull(lines.second)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `null tool name resolves to the generic tool title`() {
|
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)
|
assertNull(lines.second)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `subtitle identical to title is not duplicated`() {
|
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)
|
assertNull(lines)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user