merge: native agent brand icons on both platforms

This commit is contained in:
weishu
2026-08-18 17:06:06 +08:00
41 changed files with 757 additions and 22 deletions
@@ -37,6 +37,7 @@ import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SnackbarHost
@@ -84,6 +85,7 @@ import app.hapi.companion.feature.chat.composer.QueuedMessagesBar
import app.hapi.companion.feature.files.FolderGlyph
import app.hapi.companion.feature.sessions.DeleteSessionDialog
import app.hapi.companion.feature.sessions.RenameSessionDialog
import app.hapi.companion.ui.components.AgentFlavorIcon
import app.hapi.companion.ui.markdown.LocalMarkdownLinkHandler
import app.hapi.companion.ui.theme.hapi
import app.hapi.protocol.chat.VisibleChatBlock
@@ -557,13 +559,25 @@ private fun ChatTitle(header: ChatHeaderUi) {
overflow = TextOverflow.Ellipsis,
)
header.subtitle?.let { subtitle ->
Text(
text = subtitle,
style = MaterialTheme.typography.labelSmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
Row(verticalAlignment = Alignment.CenterVertically) {
header.flavor?.let { flavor ->
// Hint-colored like the meta text (web: currentColor
// under --app-hint); color variants ignore the tint.
CompositionLocalProvider(
LocalContentColor provides MaterialTheme.colorScheme.onSurfaceVariant,
) {
AgentFlavorIcon(flavor, modifier = Modifier.size(14.dp))
}
Spacer(modifier = Modifier.width(4.dp))
}
Text(
text = subtitle,
style = MaterialTheme.typography.labelSmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
}
}
}
}
@@ -90,6 +90,8 @@ data class ChatHeaderUi(
val title: String,
/** "Flavor · machine · worktree/path" meta line; null when nothing known. */
val subtitle: String?,
/** Raw `metadata.flavor` — drives the brand icon next to the meta line. */
val flavor: String? = null,
/** Raw custom `metadata.name` (rename-dialog prefill; the title cascade may show more). */
val name: String? = null,
val active: Boolean,
@@ -1472,6 +1474,7 @@ class ChatViewModel(
return ChatHeaderUi(
title = title,
subtitle = subtitle,
flavor = flavor,
name = detail?.metadata?.name ?: summary?.metadata?.name,
active = detail?.active ?: summary?.active ?: false,
thinking = detail?.thinking ?: summary?.thinking ?: false,
@@ -9,6 +9,7 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.verticalScroll
@@ -50,6 +51,7 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import app.hapi.companion.R
import app.hapi.companion.ui.components.AgentFlavorIcon
import app.hapi.companion.ui.theme.HapiTheme
/**
@@ -447,6 +449,7 @@ private fun AgentSection(state: NewSessionUiState, onAgentSelected: (String) ->
selected = state.form.agent == agent.value,
onClick = { onAgentSelected(agent.value) },
enabled = !state.isSpawning,
leadingIcon = { AgentFlavorIcon(agent.value, modifier = Modifier.size(16.dp)) },
label = { Text(agent.label) },
)
}
@@ -61,8 +61,8 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import app.hapi.companion.R
import app.hapi.companion.ui.components.AgentFlavorIcon
import app.hapi.companion.ui.theme.HapiTheme
import app.hapi.protocol.catalog.Flavors
import app.hapi.protocol.wire.PendingRequest
import app.hapi.protocol.wire.SessionSummary
import app.hapi.protocol.wire.SessionSummaryMetadata
@@ -78,9 +78,9 @@ import app.hapi.protocol.wire.TodoProgress
* pull-to-refresh, empty state;
* - pinned section first (the sort already puts globalPinned/pinned rows on
* top; a header + divider make the boundary visible);
* - per row: status dot (active / thinking pulse), title, flavor + machine
* labels, summary/path line, relative `updatedAt`, pending-request badge,
* todo-progress chip, unread dot;
* - per row: status dot (active / thinking pulse), flavor brand icon +
* title, machine + worktree labels, summary/path line, relative
* `updatedAt`, pending-request badge, todo-progress chip, unread dot;
* - long-press → actions sheet: pin (none/project/global), rename (dialog),
* reopen (inactive rows; navigates into the possibly-superseding id),
* archive, delete (confirm; 409 while active) — optimistic store updates;
@@ -312,6 +312,8 @@ private fun SessionRow(
Spacer(modifier = Modifier.width(10.dp))
Column(modifier = Modifier.weight(1f)) {
Row(verticalAlignment = Alignment.CenterVertically) {
AgentFlavorIcon(row.flavor, modifier = Modifier.size(16.dp))
Spacer(modifier = Modifier.width(6.dp))
// One weighted element only: the title takes ALL leftover
// width (start-aligned, ellipsis on true overflow). Splitting
// the slack with a weighted trailing spacer truncated even
@@ -350,12 +352,12 @@ private fun SessionRow(
}
}
// The agent flavor moved into the title line as a brand icon (web parity:
// `SessionRowSummary`), so the meta line carries machine + worktree only.
@Composable
private fun MetaLine(row: SessionRowUi) {
val flavorLabel = row.flavor?.let(Flavors::label)
val worktree = row.summary.metadata?.worktree
val parts = buildList {
flavorLabel?.let(::add)
row.machineLabel?.let(::add)
worktree?.let { add(it.name.ifBlank { it.branch }) }
}
@@ -0,0 +1,136 @@
package app.hapi.companion.ui.components
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Icon
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import app.hapi.companion.R
import app.hapi.companion.ui.theme.HapiTheme
import app.hapi.companion.ui.theme.hapi
/**
* Brand logo per agent flavor — port of `web/src/components/AgentFlavorIcon.tsx`
* (brand SVGs via @lobehub/icons, the same source the web ships).
*
* Color variants (agy/claude/codex/gemini) keep their literal brand fills and
* render untinted so they stay visible on both light and dark surfaces — the
* reason the web picked Color for them. Mono variants (cursor/grok/kimi/
* opencode/pi) are currentColor upstream and tint with [LocalContentColor];
* copilot mirrors the web's fixed GitHub-mark tint (#24292F light /
* #E6EDF3 dark). Unknown flavors fall back to the web's "Un" badge.
*/
@Composable
fun AgentFlavorIcon(flavor: String?, modifier: Modifier = Modifier.size(16.dp)) {
when (val normalized = flavor?.trim()?.lowercase().orEmpty()) {
in COLOR_FLAVOR_ICONS -> Image(
painter = painterResource(COLOR_FLAVOR_ICONS.getValue(normalized)),
contentDescription = null,
modifier = modifier,
)
in MONO_FLAVOR_ICONS -> Icon(
painter = painterResource(MONO_FLAVOR_ICONS.getValue(normalized)),
contentDescription = null,
modifier = modifier,
tint = LocalContentColor.current,
)
"copilot" -> Icon(
painter = painterResource(R.drawable.ic_agent_copilot),
contentDescription = null,
modifier = modifier,
tint = if (MaterialTheme.hapi.isDark) Color(0xFFE6EDF3) else Color(0xFF24292F),
)
else -> Box(
modifier = modifier.background(
color = MaterialTheme.colorScheme.surfaceContainerHigh,
shape = RoundedCornerShape(3.dp),
),
contentAlignment = Alignment.Center,
) {
Text(
text = "Un",
color = MaterialTheme.colorScheme.onSurfaceVariant,
fontSize = 8.sp,
lineHeight = 8.sp,
fontWeight = FontWeight.SemiBold,
maxLines = 1,
softWrap = false,
)
}
}
}
private val COLOR_FLAVOR_ICONS: Map<String, Int> = mapOf(
"agy" to R.drawable.ic_agent_agy,
"claude" to R.drawable.ic_agent_claude,
"codex" to R.drawable.ic_agent_codex,
"gemini" to R.drawable.ic_agent_gemini,
)
private val MONO_FLAVOR_ICONS: Map<String, Int> = mapOf(
"cursor" to R.drawable.ic_agent_cursor,
"grok" to R.drawable.ic_agent_grok,
"kimi" to R.drawable.ic_agent_kimi,
"opencode" to R.drawable.ic_agent_opencode,
"pi" to R.drawable.ic_agent_pi,
)
// -------------------------------------------------------------- preview --
@Composable
private fun AgentFlavorIconStrip() {
Column(
modifier = Modifier.padding(12.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
) {
val flavors = listOf(
"agy", "claude", "codex", "copilot", "cursor",
"gemini", "grok", "kimi", "opencode", "pi", "mystery",
)
flavors.chunked(6).forEach { chunk ->
Row(
horizontalArrangement = Arrangement.spacedBy(10.dp),
verticalAlignment = Alignment.CenterVertically,
) {
chunk.forEach { flavor -> AgentFlavorIcon(flavor) }
}
}
}
}
@Preview(showBackground = true, name = "Agent icons · light")
@Composable
private fun AgentFlavorIconPreviewLight() {
HapiTheme(darkTheme = false, dynamicColor = false) {
Surface { AgentFlavorIconStrip() }
}
}
@Preview(showBackground = true, name = "Agent icons · dark")
@Composable
private fun AgentFlavorIconPreviewDark() {
HapiTheme(darkTheme = true, dynamicColor = false) {
Surface { AgentFlavorIconStrip() }
}
}
@@ -0,0 +1,40 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Antigravity brand mark (Color variant). Upstream renders the wing as a
mask over 11 gaussian-blurred color blobs (SVG filters) which neither
VectorDrawable nor Xcode SVG supports; approximated with a left-to-right
gradient matching the blob layout (green wing tip, yellow crest, red
right arm, blue tip). iOS flattens further to the dominant brand blue.
Brand SVG via @lobehub/icons v5.4.0 - same source as
web/src/components/AgentFlavorIcon.tsx. Color variant: literal brand
fills, render untinted (Image, not Icon). -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M 21.751 22.607 c 1.34 1.005 3.35 0.335 1.508 -1.508 C 17.73 15.74 18.904 1 12.037 1 5.17 1 6.342 15.74 0.815 21.1 c -2.01 2.009 0.167 2.511 1.507 1.506 5.192 -3.517 4.857 -9.714 9.715 -9.714 4.857 0 4.522 6.197 9.714 9.715 z">
<aapt:attr name="android:fillColor">
<gradient
android:type="linear"
android:startX="0"
android:startY="12"
android:endX="24"
android:endY="12">
<item
android:color="#FF00B95C"
android:offset="0" />
<item
android:color="#FFFBBC04"
android:offset="0.35" />
<item
android:color="#FFFC413D"
android:offset="0.68" />
<item
android:color="#FF3186FF"
android:offset="0.92" />
</gradient>
</aapt:attr>
</path>
</vector>
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Claude brand mark (Color variant): flat brand fill #D97757. Brand SVG
via @lobehub/icons v5.4.0 - same source as
web/src/components/AgentFlavorIcon.tsx. Color variant: literal brand
fills, render untinted (Image, not Icon). -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FFD97757"
android:pathData="M 4.709 15.955 l 4.72 -2.647 0.08 -0.23 -0.08 -0.128 H 9.2 l -0.79 -0.048 -2.698 -0.073 -2.339 -0.097 -2.266 -0.122 -0.571 -0.121 L 0 11.784 l 0.055 -0.352 0.48 -0.321 0.686 0.06 1.52 0.103 2.278 0.158 1.652 0.097 2.449 0.255 h 0.389 l 0.055 -0.157 -0.134 -0.098 -0.103 -0.097 -2.358 -1.596 -2.552 -1.688 -1.336 -0.972 -0.724 -0.491 -0.364 -0.462 -0.158 -1.008 0.656 -0.722 0.881 0.06 0.225 0.061 0.893 0.686 1.908 1.476 2.491 1.833 0.365 0.304 0.145 -0.103 0.019 -0.073 -0.164 -0.274 -1.355 -2.446 -1.446 -2.49 -0.644 -1.032 -0.17 -0.619 a 2.97 2.97 0 0 1 -0.104 -0.729 L 6.283 0.134 6.696 0 l 0.996 0.134 0.42 0.364 0.62 1.414 1.002 2.229 1.555 3.03 0.456 0.898 0.243 0.832 0.091 0.255 h 0.158 V 9.01 l 0.128 -1.706 0.237 -2.095 0.23 -2.695 0.08 -0.76 0.376 -0.91 0.747 -0.492 0.584 0.28 0.48 0.685 -0.067 0.444 -0.286 1.851 -0.559 2.903 -0.364 1.942 h 0.212 l 0.243 -0.242 0.985 -1.306 1.652 -2.064 0.73 -0.82 0.85 -0.904 0.547 -0.431 h 1.033 l 0.76 1.129 -0.34 1.166 -1.064 1.347 -0.881 1.142 -1.264 1.7 -0.79 1.36 0.073 0.11 0.188 -0.02 2.856 -0.606 1.543 -0.28 1.841 -0.315 0.833 0.388 0.091 0.395 -0.328 0.807 -1.969 0.486 -2.309 0.462 -3.439 0.813 -0.042 0.03 0.049 0.061 1.549 0.146 0.662 0.036 h 1.622 l 3.02 0.225 0.79 0.522 0.474 0.638 -0.079 0.485 -1.215 0.62 -1.64 -0.389 -3.829 -0.91 -1.312 -0.329 h -0.182 v 0.11 l 1.093 1.068 2.006 1.81 2.509 2.33 0.127 0.578 -0.322 0.455 -0.34 -0.049 -2.205 -1.657 -0.851 -0.747 -1.926 -1.62 h -0.128 v 0.17 l 0.444 0.649 2.345 3.521 0.122 1.08 -0.17 0.353 -0.608 0.213 -0.668 -0.122 -1.374 -1.925 -1.415 -2.167 -1.143 -1.943 -0.14 0.08 -0.674 7.254 -0.316 0.37 -0.729 0.28 -0.607 -0.461 -0.322 -0.747 0.322 -1.476 0.389 -1.924 0.315 -1.53 0.286 -1.9 0.17 -0.632 -0.012 -0.042 -0.14 0.018 -1.434 1.967 -2.18 2.945 -1.726 1.845 -0.414 0.164 -0.717 -0.37 0.067 -0.662 0.401 -0.589 2.388 -3.036 1.44 -1.882 0.93 -1.086 -0.006 -0.158 h -0.055 L 4.132 18.56 l -1.13 0.146 -0.487 -0.456 0.061 -0.746 0.231 -0.243 1.908 -1.312 -0.006 0.006 z" />
</vector>
@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Codex brand mark (Color variant): white rounded tile + glyph. Upstream
fills the glyph with a vertical 3-stop gradient (#B1A7FF > #7A9DFF >
#3941FF); Android ports it, iOS flattens to the middle stop #7A9DFF.
Brand SVG via @lobehub/icons v5.4.0 - same source as
web/src/components/AgentFlavorIcon.tsx. Color variant: literal brand
fills, render untinted (Image, not Icon). -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M 19.503 0 H 4.496 A 4.496 4.496 0 0 0 0 4.496 v 15.007 A 4.496 4.496 0 0 0 4.496 24 h 15.007 A 4.496 4.496 0 0 0 24 19.503 V 4.496 A 4.496 4.496 0 0 0 19.503 0 z" />
<path
android:pathData="M 9.064 3.344 a 4.578 4.578 0 0 1 2.285 -0.312 c 1 0.115 1.891 0.54 2.673 1.275 0.01 0.01 0.024 0.017 0.037 0.021 a 0.09 0.09 0 0 0 0.043 0 4.55 4.55 0 0 1 3.046 0.275 l 0.047 0.022 0.116 0.057 a 4.581 4.581 0 0 1 2.188 2.399 c 0.209 0.51 0.313 1.041 0.315 1.595 a 4.24 4.24 0 0 1 -0.134 1.223 0.123 0.123 0 0 0 0.03 0.115 c 0.594 0.607 0.988 1.33 1.183 2.17 0.289 1.425 -0.007 2.71 -0.887 3.854 l -0.136 0.166 a 4.548 4.548 0 0 1 -2.201 1.388 0.123 0.123 0 0 0 -0.081 0.076 c -0.191 0.551 -0.383 1.023 -0.74 1.494 -0.9 1.187 -2.222 1.846 -3.711 1.838 -1.187 -0.006 -2.239 -0.44 -3.157 -1.302 a 0.107 0.107 0 0 0 -0.105 -0.024 c -0.388 0.125 -0.78 0.143 -1.204 0.138 a 4.441 4.441 0 0 1 -1.945 -0.466 4.544 4.544 0 0 1 -1.61 -1.335 c -0.152 -0.202 -0.303 -0.392 -0.414 -0.617 a 5.81 5.81 0 0 1 -0.37 -0.961 4.582 4.582 0 0 1 -0.014 -2.298 0.124 0.124 0 0 0 0.006 -0.056 0.085 0.085 0 0 0 -0.027 -0.048 4.467 4.467 0 0 1 -1.034 -1.651 3.896 3.896 0 0 1 -0.251 -1.192 5.189 5.189 0 0 1 0.141 -1.6 c 0.337 -1.112 0.982 -1.985 1.933 -2.618 0.212 -0.141 0.413 -0.251 0.601 -0.33 0.215 -0.089 0.43 -0.164 0.646 -0.227 a 0.098 0.098 0 0 0 0.065 -0.066 4.51 4.51 0 0 1 0.829 -1.615 4.535 4.535 0 0 1 1.837 -1.388 z m 3.482 10.565 a 0.637 0.637 0 0 0 0 1.272 h 3.636 a 0.637 0.637 0 1 0 0 -1.272 h -3.636 z M 8.462 9.23 a 0.637 0.637 0 0 0 -1.106 0.631 l 1.272 2.224 -1.266 2.136 a 0.636 0.636 0 1 0 1.095 0.649 l 1.454 -2.455 a 0.636 0.636 0 0 0 0.005 -0.64 L 8.462 9.23 z">
<aapt:attr name="android:fillColor">
<gradient
android:type="linear"
android:startX="12"
android:startY="3"
android:endX="12"
android:endY="21">
<item
android:color="#FFB1A7FF"
android:offset="0" />
<item
android:color="#FF7A9DFF"
android:offset="0.5" />
<item
android:color="#FF3941FF"
android:offset="1" />
</gradient>
</aapt:attr>
</path>
</vector>
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- GitHub mark for Copilot CLI (web ships the same stand-in, see
web/src/components/icons/CopilotIcon.tsx); fixed light/dark tint at the
call site (#24292F / #E6EDF3). Brand SVG via @lobehub/icons v5.4.0 -
same source as web/src/components/AgentFlavorIcon.tsx. Mono glyph ships
black; the AgentFlavorIcon composable applies the tint so dark mode
works. -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="16"
android:viewportHeight="16">
<path
android:fillColor="#FF000000"
android:pathData="M 8 0 C 3.58 0 0 3.58 0 8 c 0 3.54 2.29 6.53 5.47 7.59 0.4 0.07 0.55 -0.17 0.55 -0.38 0 -0.19 -0.01 -0.82 -0.01 -1.49 -2.01 0.37 -2.53 -0.49 -2.69 -0.94 -0.09 -0.23 -0.48 -0.94 -0.82 -1.13 -0.28 -0.15 -0.68 -0.52 -0.01 -0.53 0.63 -0.01 1.08 0.58 1.23 0.82 0.72 1.21 1.87 0.87 2.33 0.66 0.07 -0.52 0.28 -0.87 0.51 -1.07 -1.78 -0.2 -3.64 -0.89 -3.64 -3.95 0 -0.87 0.31 -1.59 0.82 -2.15 -0.08 -0.2 -0.36 -1.02 0.08 -2.12 0 0 0.67 -0.21 2.2 0.82 0.64 -0.18 1.32 -0.27 2 -0.27 0.68 0 1.36 0.09 2 0.27 1.53 -1.04 2.2 -0.82 2.2 -0.82 0.44 1.1 0.16 1.92 0.08 2.12 0.51 0.56 0.82 1.27 0.82 2.15 0 3.07 -1.87 3.75 -3.65 3.95 0.29 0.25 0.54 0.73 0.54 1.48 0 1.07 -0.01 1.93 -0.01 2.2 0 0.21 0.15 0.46 0.55 0.38 A 8.013 8.013 0 0 0 16 8 c 0 -4.42 -3.58 -8 -8 -8 z" />
</vector>
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Cursor mark (Mono variant): currentColor upstream; tint at the call
site. Brand SVG via @lobehub/icons v5.4.0 - same source as
web/src/components/AgentFlavorIcon.tsx. Mono glyph ships black; the
AgentFlavorIcon composable applies the tint so dark mode works. -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M 22.106 5.68 L 12.5 0.135 a 0.998 0.998 0 0 0 -0.998 0 L 1.893 5.68 a 0.84 0.84 0 0 0 -0.419 0.726 v 11.186 c 0 0.3 0.16 0.577 0.42 0.727 l 9.607 5.547 a 0.999 0.999 0 0 0 0.998 0 l 9.608 -5.547 a 0.84 0.84 0 0 0 0.42 -0.727 V 6.407 a 0.84 0.84 0 0 0 -0.42 -0.726 z m -0.603 1.176 L 12.228 22.92 c -0.063 0.108 -0.228 0.064 -0.228 -0.061 V 12.34 a 0.59 0.59 0 0 0 -0.295 -0.51 l -9.11 -5.26 c -0.107 -0.062 -0.063 -0.228 0.062 -0.228 h 18.55 c 0.264 0 0.428 0.286 0.296 0.514 z"
android:fillType="evenOdd" />
</vector>
@@ -0,0 +1,71 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Gemini brand mark (Color variant): the same star stacked 4x - solid
#3186FF base plus three fade-to-transparent gradient overlays
(green/red/yellow). Android ports the stack; iOS flattens to the blue
base. Brand SVG via @lobehub/icons v5.4.0 - same source as
web/src/components/AgentFlavorIcon.tsx. Color variant: literal brand
fills, render untinted (Image, not Icon). -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF3186FF"
android:pathData="M 20.616 10.835 a 14.147 14.147 0 0 1 -4.45 -3.001 14.111 14.111 0 0 1 -3.678 -6.452 0.503 0.503 0 0 0 -0.975 0 14.134 14.134 0 0 1 -3.679 6.452 14.155 14.155 0 0 1 -4.45 3.001 c -0.65 0.28 -1.318 0.505 -2.002 0.678 a 0.502 0.502 0 0 0 0 0.975 c 0.684 0.172 1.35 0.397 2.002 0.677 a 14.147 14.147 0 0 1 4.45 3.001 14.112 14.112 0 0 1 3.679 6.453 0.502 0.502 0 0 0 0.975 0 c 0.172 -0.685 0.397 -1.351 0.677 -2.003 a 14.145 14.145 0 0 1 3.001 -4.45 14.113 14.113 0 0 1 6.453 -3.678 0.503 0.503 0 0 0 0 -0.975 13.245 13.245 0 0 1 -2.003 -0.678 z" />
<path
android:pathData="M 20.616 10.835 a 14.147 14.147 0 0 1 -4.45 -3.001 14.111 14.111 0 0 1 -3.678 -6.452 0.503 0.503 0 0 0 -0.975 0 14.134 14.134 0 0 1 -3.679 6.452 14.155 14.155 0 0 1 -4.45 3.001 c -0.65 0.28 -1.318 0.505 -2.002 0.678 a 0.502 0.502 0 0 0 0 0.975 c 0.684 0.172 1.35 0.397 2.002 0.677 a 14.147 14.147 0 0 1 4.45 3.001 14.112 14.112 0 0 1 3.679 6.453 0.502 0.502 0 0 0 0.975 0 c 0.172 -0.685 0.397 -1.351 0.677 -2.003 a 14.145 14.145 0 0 1 3.001 -4.45 14.113 14.113 0 0 1 6.453 -3.678 0.503 0.503 0 0 0 0 -0.975 13.245 13.245 0 0 1 -2.003 -0.678 z">
<aapt:attr name="android:fillColor">
<gradient
android:type="linear"
android:startX="7"
android:startY="15.5"
android:endX="11"
android:endY="12">
<item
android:color="#FF08B962"
android:offset="0" />
<item
android:color="#0008B962"
android:offset="1" />
</gradient>
</aapt:attr>
</path>
<path
android:pathData="M 20.616 10.835 a 14.147 14.147 0 0 1 -4.45 -3.001 14.111 14.111 0 0 1 -3.678 -6.452 0.503 0.503 0 0 0 -0.975 0 14.134 14.134 0 0 1 -3.679 6.452 14.155 14.155 0 0 1 -4.45 3.001 c -0.65 0.28 -1.318 0.505 -2.002 0.678 a 0.502 0.502 0 0 0 0 0.975 c 0.684 0.172 1.35 0.397 2.002 0.677 a 14.147 14.147 0 0 1 4.45 3.001 14.112 14.112 0 0 1 3.679 6.453 0.502 0.502 0 0 0 0.975 0 c 0.172 -0.685 0.397 -1.351 0.677 -2.003 a 14.145 14.145 0 0 1 3.001 -4.45 14.113 14.113 0 0 1 6.453 -3.678 0.503 0.503 0 0 0 0 -0.975 13.245 13.245 0 0 1 -2.003 -0.678 z">
<aapt:attr name="android:fillColor">
<gradient
android:type="linear"
android:startX="8"
android:startY="5.5"
android:endX="11.5"
android:endY="11">
<item
android:color="#FFF94543"
android:offset="0" />
<item
android:color="#00F94543"
android:offset="1" />
</gradient>
</aapt:attr>
</path>
<path
android:pathData="M 20.616 10.835 a 14.147 14.147 0 0 1 -4.45 -3.001 14.111 14.111 0 0 1 -3.678 -6.452 0.503 0.503 0 0 0 -0.975 0 14.134 14.134 0 0 1 -3.679 6.452 14.155 14.155 0 0 1 -4.45 3.001 c -0.65 0.28 -1.318 0.505 -2.002 0.678 a 0.502 0.502 0 0 0 0 0.975 c 0.684 0.172 1.35 0.397 2.002 0.677 a 14.147 14.147 0 0 1 4.45 3.001 14.112 14.112 0 0 1 3.679 6.453 0.502 0.502 0 0 0 0.975 0 c 0.172 -0.685 0.397 -1.351 0.677 -2.003 a 14.145 14.145 0 0 1 3.001 -4.45 14.113 14.113 0 0 1 6.453 -3.678 0.503 0.503 0 0 0 0 -0.975 13.245 13.245 0 0 1 -2.003 -0.678 z">
<aapt:attr name="android:fillColor">
<gradient
android:type="linear"
android:startX="3.5"
android:startY="13.5"
android:endX="17.5"
android:endY="12">
<item
android:color="#FFFABC12"
android:offset="0" />
<item
android:color="#00FABC12"
android:offset="0.46" />
</gradient>
</aapt:attr>
</path>
</vector>
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Grok mark (Mono variant): currentColor upstream; tint at the call site.
Brand SVG via @lobehub/icons v5.4.0 - same source as
web/src/components/AgentFlavorIcon.tsx. Mono glyph ships black; the
AgentFlavorIcon composable applies the tint so dark mode works. -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M 9.27 15.29 l 7.978 -5.897 c 0.391 -0.29 0.95 -0.177 1.137 0.272 0.98 2.369 0.542 5.215 -1.41 7.169 -1.951 1.954 -4.667 2.382 -7.149 1.406 l -2.711 1.257 c 3.889 2.661 8.611 2.003 11.562 -0.953 2.341 -2.344 3.066 -5.539 2.388 -8.42 l 0.006 0.007 c -0.983 -4.232 0.242 -5.924 2.75 -9.383 0.06 -0.082 0.12 -0.164 0.179 -0.248 l -3.301 3.305 v -0.01 L 9.267 15.292 M 7.623 16.723 c -2.792 -2.67 -2.31 -6.801 0.071 -9.184 1.761 -1.763 4.647 -2.483 7.166 -1.425 l 2.705 -1.25 a 7.808 7.808 0 0 0 -1.829 -1 A 8.975 8.975 0 0 0 5.984 5.83 c -2.533 2.536 -3.33 6.436 -1.962 9.764 1.022 2.487 -0.653 4.246 -2.34 6.022 -0.599 0.63 -1.199 1.259 -1.682 1.925 l 7.62 -6.815"
android:fillType="evenOdd" />
</vector>
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Kimi mark (Mono variant): currentColor upstream (KimiColor hard-codes a
white glyph that vanishes on light surfaces - same call as the web).
Brand SVG via @lobehub/icons v5.4.0 - same source as
web/src/components/AgentFlavorIcon.tsx. Mono glyph ships black; the
AgentFlavorIcon composable applies the tint so dark mode works. -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M 21.846 0 a 1.923 1.923 0 1 1 0 3.846 H 20.15 a 0.226 0.226 0 0 1 -0.227 -0.226 V 1.923 C 19.923 0.861 20.784 0 21.846 0 z"
android:fillType="evenOdd" />
<path
android:fillColor="#FF000000"
android:pathData="M 11.065 11.199 l 7.257 -7.2 c 0.137 -0.136 0.06 -0.41 -0.116 -0.41 H 14.3 a 0.164 0.164 0 0 0 -0.117 0.051 l -7.82 7.756 c -0.122 0.12 -0.302 0.013 -0.302 -0.179 V 3.82 c 0 -0.127 -0.083 -0.23 -0.185 -0.23 H 3.186 c -0.103 0 -0.186 0.103 -0.186 0.23 V 19.77 c 0 0.128 0.083 0.23 0.186 0.23 h 2.69 c 0.103 0 0.186 -0.102 0.186 -0.23 v -3.25 c 0 -0.069 0.025 -0.135 0.069 -0.178 l 2.424 -2.406 a 0.158 0.158 0 0 1 0.205 -0.023 l 6.484 4.772 a 7.677 7.677 0 0 0 3.453 1.283 c 0.108 0.012 0.2 -0.095 0.2 -0.23 v -3.06 c 0 -0.117 -0.07 -0.212 -0.164 -0.227 a 5.028 5.028 0 0 1 -2.027 -0.807 l -5.613 -4.064 c -0.117 -0.078 -0.132 -0.279 -0.028 -0.381 z"
android:fillType="evenOdd" />
</vector>
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- OpenCode mark (Mono variant): currentColor upstream; tint at the call
site. Brand SVG via @lobehub/icons v5.4.0 - same source as
web/src/components/AgentFlavorIcon.tsx. Mono glyph ships black; the
AgentFlavorIcon composable applies the tint so dark mode works. -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M 16 6 H 8 v 12 h 8 V 6 z m 4 16 H 4 V 2 h 16 v 20 z"
android:fillType="evenOdd" />
</vector>
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Pi logo (web's inline PiLogo SVG): currentColor; tint at the call site.
Brand SVG via @lobehub/icons v5.4.0 - same source as
web/src/components/AgentFlavorIcon.tsx. Mono glyph ships black; the
AgentFlavorIcon composable applies the tint so dark mode works. -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="800"
android:viewportHeight="800">
<path
android:fillColor="#FF000000"
android:pathData="M 165.29 165.29 h 352.07 V 400 H 400 v 117.36 H 282.65 v 117.36 H 165.29 z M 282.65 282.65 V 400 H 400 V 282.65 z"
android:fillType="evenOdd" />
<path
android:fillColor="#FF000000"
android:pathData="M 517.36 400 h 117.36 v 234.72 H 517.36 z" />
</vector>
@@ -0,0 +1,6 @@
{
"info": {
"author": "xcode",
"version": 1
}
}
@@ -0,0 +1,16 @@
{
"images": [
{
"filename": "icon.svg",
"idiom": "universal"
}
],
"info": {
"author": "xcode",
"version": 1
},
"properties": {
"preserves-vector-representation": true,
"template-rendering-intent": "original"
}
}
@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<path fill="#3186FF" d="M 21.751 22.607 c 1.34 1.005 3.35 0.335 1.508 -1.508 C 17.73 15.74 18.904 1 12.037 1 5.17 1 6.342 15.74 0.815 21.1 c -2.01 2.009 0.167 2.511 1.507 1.506 5.192 -3.517 4.857 -9.714 9.715 -9.714 4.857 0 4.522 6.197 9.714 9.715 z"/>
</svg>

After

Width:  |  Height:  |  Size: 346 B

@@ -0,0 +1,16 @@
{
"images": [
{
"filename": "icon.svg",
"idiom": "universal"
}
],
"info": {
"author": "xcode",
"version": 1
},
"properties": {
"preserves-vector-representation": true,
"template-rendering-intent": "original"
}
}
@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<path fill="#D97757" d="M 4.709 15.955 l 4.72 -2.647 0.08 -0.23 -0.08 -0.128 H 9.2 l -0.79 -0.048 -2.698 -0.073 -2.339 -0.097 -2.266 -0.122 -0.571 -0.121 L 0 11.784 l 0.055 -0.352 0.48 -0.321 0.686 0.06 1.52 0.103 2.278 0.158 1.652 0.097 2.449 0.255 h 0.389 l 0.055 -0.157 -0.134 -0.098 -0.103 -0.097 -2.358 -1.596 -2.552 -1.688 -1.336 -0.972 -0.724 -0.491 -0.364 -0.462 -0.158 -1.008 0.656 -0.722 0.881 0.06 0.225 0.061 0.893 0.686 1.908 1.476 2.491 1.833 0.365 0.304 0.145 -0.103 0.019 -0.073 -0.164 -0.274 -1.355 -2.446 -1.446 -2.49 -0.644 -1.032 -0.17 -0.619 a 2.97 2.97 0 0 1 -0.104 -0.729 L 6.283 0.134 6.696 0 l 0.996 0.134 0.42 0.364 0.62 1.414 1.002 2.229 1.555 3.03 0.456 0.898 0.243 0.832 0.091 0.255 h 0.158 V 9.01 l 0.128 -1.706 0.237 -2.095 0.23 -2.695 0.08 -0.76 0.376 -0.91 0.747 -0.492 0.584 0.28 0.48 0.685 -0.067 0.444 -0.286 1.851 -0.559 2.903 -0.364 1.942 h 0.212 l 0.243 -0.242 0.985 -1.306 1.652 -2.064 0.73 -0.82 0.85 -0.904 0.547 -0.431 h 1.033 l 0.76 1.129 -0.34 1.166 -1.064 1.347 -0.881 1.142 -1.264 1.7 -0.79 1.36 0.073 0.11 0.188 -0.02 2.856 -0.606 1.543 -0.28 1.841 -0.315 0.833 0.388 0.091 0.395 -0.328 0.807 -1.969 0.486 -2.309 0.462 -3.439 0.813 -0.042 0.03 0.049 0.061 1.549 0.146 0.662 0.036 h 1.622 l 3.02 0.225 0.79 0.522 0.474 0.638 -0.079 0.485 -1.215 0.62 -1.64 -0.389 -3.829 -0.91 -1.312 -0.329 h -0.182 v 0.11 l 1.093 1.068 2.006 1.81 2.509 2.33 0.127 0.578 -0.322 0.455 -0.34 -0.049 -2.205 -1.657 -0.851 -0.747 -1.926 -1.62 h -0.128 v 0.17 l 0.444 0.649 2.345 3.521 0.122 1.08 -0.17 0.353 -0.608 0.213 -0.668 -0.122 -1.374 -1.925 -1.415 -2.167 -1.143 -1.943 -0.14 0.08 -0.674 7.254 -0.316 0.37 -0.729 0.28 -0.607 -0.461 -0.322 -0.747 0.322 -1.476 0.389 -1.924 0.315 -1.53 0.286 -1.9 0.17 -0.632 -0.012 -0.042 -0.14 0.018 -1.434 1.967 -2.18 2.945 -1.726 1.845 -0.414 0.164 -0.717 -0.37 0.067 -0.662 0.401 -0.589 2.388 -3.036 1.44 -1.882 0.93 -1.086 -0.006 -0.158 h -0.055 L 4.132 18.56 l -1.13 0.146 -0.487 -0.456 0.061 -0.746 0.231 -0.243 1.908 -1.312 -0.006 0.006 z"/>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

@@ -0,0 +1,16 @@
{
"images": [
{
"filename": "icon.svg",
"idiom": "universal"
}
],
"info": {
"author": "xcode",
"version": 1
},
"properties": {
"preserves-vector-representation": true,
"template-rendering-intent": "original"
}
}
@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<path fill="#FFFFFF" d="M 19.503 0 H 4.496 A 4.496 4.496 0 0 0 0 4.496 v 15.007 A 4.496 4.496 0 0 0 4.496 24 h 15.007 A 4.496 4.496 0 0 0 24 19.503 V 4.496 A 4.496 4.496 0 0 0 19.503 0 z"/>
<path fill="#7A9DFF" d="M 9.064 3.344 a 4.578 4.578 0 0 1 2.285 -0.312 c 1 0.115 1.891 0.54 2.673 1.275 0.01 0.01 0.024 0.017 0.037 0.021 a 0.09 0.09 0 0 0 0.043 0 4.55 4.55 0 0 1 3.046 0.275 l 0.047 0.022 0.116 0.057 a 4.581 4.581 0 0 1 2.188 2.399 c 0.209 0.51 0.313 1.041 0.315 1.595 a 4.24 4.24 0 0 1 -0.134 1.223 0.123 0.123 0 0 0 0.03 0.115 c 0.594 0.607 0.988 1.33 1.183 2.17 0.289 1.425 -0.007 2.71 -0.887 3.854 l -0.136 0.166 a 4.548 4.548 0 0 1 -2.201 1.388 0.123 0.123 0 0 0 -0.081 0.076 c -0.191 0.551 -0.383 1.023 -0.74 1.494 -0.9 1.187 -2.222 1.846 -3.711 1.838 -1.187 -0.006 -2.239 -0.44 -3.157 -1.302 a 0.107 0.107 0 0 0 -0.105 -0.024 c -0.388 0.125 -0.78 0.143 -1.204 0.138 a 4.441 4.441 0 0 1 -1.945 -0.466 4.544 4.544 0 0 1 -1.61 -1.335 c -0.152 -0.202 -0.303 -0.392 -0.414 -0.617 a 5.81 5.81 0 0 1 -0.37 -0.961 4.582 4.582 0 0 1 -0.014 -2.298 0.124 0.124 0 0 0 0.006 -0.056 0.085 0.085 0 0 0 -0.027 -0.048 4.467 4.467 0 0 1 -1.034 -1.651 3.896 3.896 0 0 1 -0.251 -1.192 5.189 5.189 0 0 1 0.141 -1.6 c 0.337 -1.112 0.982 -1.985 1.933 -2.618 0.212 -0.141 0.413 -0.251 0.601 -0.33 0.215 -0.089 0.43 -0.164 0.646 -0.227 a 0.098 0.098 0 0 0 0.065 -0.066 4.51 4.51 0 0 1 0.829 -1.615 4.535 4.535 0 0 1 1.837 -1.388 z m 3.482 10.565 a 0.637 0.637 0 0 0 0 1.272 h 3.636 a 0.637 0.637 0 1 0 0 -1.272 h -3.636 z M 8.462 9.23 a 0.637 0.637 0 0 0 -1.106 0.631 l 1.272 2.224 -1.266 2.136 a 0.636 0.636 0 1 0 1.095 0.649 l 1.454 -2.455 a 0.636 0.636 0 0 0 0.005 -0.64 L 8.462 9.23 z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

@@ -0,0 +1,16 @@
{
"images": [
{
"filename": "icon.svg",
"idiom": "universal"
}
],
"info": {
"author": "xcode",
"version": 1
},
"properties": {
"preserves-vector-representation": true,
"template-rendering-intent": "template"
}
}
@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
<path fill="#000000" d="M 8 0 C 3.58 0 0 3.58 0 8 c 0 3.54 2.29 6.53 5.47 7.59 0.4 0.07 0.55 -0.17 0.55 -0.38 0 -0.19 -0.01 -0.82 -0.01 -1.49 -2.01 0.37 -2.53 -0.49 -2.69 -0.94 -0.09 -0.23 -0.48 -0.94 -0.82 -1.13 -0.28 -0.15 -0.68 -0.52 -0.01 -0.53 0.63 -0.01 1.08 0.58 1.23 0.82 0.72 1.21 1.87 0.87 2.33 0.66 0.07 -0.52 0.28 -0.87 0.51 -1.07 -1.78 -0.2 -3.64 -0.89 -3.64 -3.95 0 -0.87 0.31 -1.59 0.82 -2.15 -0.08 -0.2 -0.36 -1.02 0.08 -2.12 0 0 0.67 -0.21 2.2 0.82 0.64 -0.18 1.32 -0.27 2 -0.27 0.68 0 1.36 0.09 2 0.27 1.53 -1.04 2.2 -0.82 2.2 -0.82 0.44 1.1 0.16 1.92 0.08 2.12 0.51 0.56 0.82 1.27 0.82 2.15 0 3.07 -1.87 3.75 -3.65 3.95 0.29 0.25 0.54 0.73 0.54 1.48 0 1.07 -0.01 1.93 -0.01 2.2 0 0.21 0.15 0.46 0.55 0.38 A 8.013 8.013 0 0 0 16 8 c 0 -4.42 -3.58 -8 -8 -8 z"/>
</svg>

After

Width:  |  Height:  |  Size: 872 B

@@ -0,0 +1,16 @@
{
"images": [
{
"filename": "icon.svg",
"idiom": "universal"
}
],
"info": {
"author": "xcode",
"version": 1
},
"properties": {
"preserves-vector-representation": true,
"template-rendering-intent": "template"
}
}
@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<path fill="#000000" fill-rule="evenodd" d="M 22.106 5.68 L 12.5 0.135 a 0.998 0.998 0 0 0 -0.998 0 L 1.893 5.68 a 0.84 0.84 0 0 0 -0.419 0.726 v 11.186 c 0 0.3 0.16 0.577 0.42 0.727 l 9.607 5.547 a 0.999 0.999 0 0 0 0.998 0 l 9.608 -5.547 a 0.84 0.84 0 0 0 0.42 -0.727 V 6.407 a 0.84 0.84 0 0 0 -0.42 -0.726 z m -0.603 1.176 L 12.228 22.92 c -0.063 0.108 -0.228 0.064 -0.228 -0.061 V 12.34 a 0.59 0.59 0 0 0 -0.295 -0.51 l -9.11 -5.26 c -0.107 -0.062 -0.063 -0.228 0.062 -0.228 h 18.55 c 0.264 0 0.428 0.286 0.296 0.514 z"/>
</svg>

After

Width:  |  Height:  |  Size: 619 B

@@ -0,0 +1,16 @@
{
"images": [
{
"filename": "icon.svg",
"idiom": "universal"
}
],
"info": {
"author": "xcode",
"version": 1
},
"properties": {
"preserves-vector-representation": true,
"template-rendering-intent": "original"
}
}
@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<path fill="#3186FF" d="M 20.616 10.835 a 14.147 14.147 0 0 1 -4.45 -3.001 14.111 14.111 0 0 1 -3.678 -6.452 0.503 0.503 0 0 0 -0.975 0 14.134 14.134 0 0 1 -3.679 6.452 14.155 14.155 0 0 1 -4.45 3.001 c -0.65 0.28 -1.318 0.505 -2.002 0.678 a 0.502 0.502 0 0 0 0 0.975 c 0.684 0.172 1.35 0.397 2.002 0.677 a 14.147 14.147 0 0 1 4.45 3.001 14.112 14.112 0 0 1 3.679 6.453 0.502 0.502 0 0 0 0.975 0 c 0.172 -0.685 0.397 -1.351 0.677 -2.003 a 14.145 14.145 0 0 1 3.001 -4.45 14.113 14.113 0 0 1 6.453 -3.678 0.503 0.503 0 0 0 0 -0.975 13.245 13.245 0 0 1 -2.003 -0.678 z"/>
</svg>

After

Width:  |  Height:  |  Size: 663 B

@@ -0,0 +1,16 @@
{
"images": [
{
"filename": "icon.svg",
"idiom": "universal"
}
],
"info": {
"author": "xcode",
"version": 1
},
"properties": {
"preserves-vector-representation": true,
"template-rendering-intent": "template"
}
}
@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<path fill="#000000" fill-rule="evenodd" d="M 9.27 15.29 l 7.978 -5.897 c 0.391 -0.29 0.95 -0.177 1.137 0.272 0.98 2.369 0.542 5.215 -1.41 7.169 -1.951 1.954 -4.667 2.382 -7.149 1.406 l -2.711 1.257 c 3.889 2.661 8.611 2.003 11.562 -0.953 2.341 -2.344 3.066 -5.539 2.388 -8.42 l 0.006 0.007 c -0.983 -4.232 0.242 -5.924 2.75 -9.383 0.06 -0.082 0.12 -0.164 0.179 -0.248 l -3.301 3.305 v -0.01 L 9.267 15.292 M 7.623 16.723 c -2.792 -2.67 -2.31 -6.801 0.071 -9.184 1.761 -1.763 4.647 -2.483 7.166 -1.425 l 2.705 -1.25 a 7.808 7.808 0 0 0 -1.829 -1 A 8.975 8.975 0 0 0 5.984 5.83 c -2.533 2.536 -3.33 6.436 -1.962 9.764 1.022 2.487 -0.653 4.246 -2.34 6.022 -0.599 0.63 -1.199 1.259 -1.682 1.925 l 7.62 -6.815"/>
</svg>

After

Width:  |  Height:  |  Size: 802 B

@@ -0,0 +1,16 @@
{
"images": [
{
"filename": "icon.svg",
"idiom": "universal"
}
],
"info": {
"author": "xcode",
"version": 1
},
"properties": {
"preserves-vector-representation": true,
"template-rendering-intent": "template"
}
}
@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<path fill="#000000" fill-rule="evenodd" d="M 21.846 0 a 1.923 1.923 0 1 1 0 3.846 H 20.15 a 0.226 0.226 0 0 1 -0.227 -0.226 V 1.923 C 19.923 0.861 20.784 0 21.846 0 z"/>
<path fill="#000000" fill-rule="evenodd" d="M 11.065 11.199 l 7.257 -7.2 c 0.137 -0.136 0.06 -0.41 -0.116 -0.41 H 14.3 a 0.164 0.164 0 0 0 -0.117 0.051 l -7.82 7.756 c -0.122 0.12 -0.302 0.013 -0.302 -0.179 V 3.82 c 0 -0.127 -0.083 -0.23 -0.185 -0.23 H 3.186 c -0.103 0 -0.186 0.103 -0.186 0.23 V 19.77 c 0 0.128 0.083 0.23 0.186 0.23 h 2.69 c 0.103 0 0.186 -0.102 0.186 -0.23 v -3.25 c 0 -0.069 0.025 -0.135 0.069 -0.178 l 2.424 -2.406 a 0.158 0.158 0 0 1 0.205 -0.023 l 6.484 4.772 a 7.677 7.677 0 0 0 3.453 1.283 c 0.108 0.012 0.2 -0.095 0.2 -0.23 v -3.06 c 0 -0.117 -0.07 -0.212 -0.164 -0.227 a 5.028 5.028 0 0 1 -2.027 -0.807 l -5.613 -4.064 c -0.117 -0.078 -0.132 -0.279 -0.028 -0.381 z"/>
</svg>

After

Width:  |  Height:  |  Size: 962 B

@@ -0,0 +1,16 @@
{
"images": [
{
"filename": "icon.svg",
"idiom": "universal"
}
],
"info": {
"author": "xcode",
"version": 1
},
"properties": {
"preserves-vector-representation": true,
"template-rendering-intent": "template"
}
}
@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<path fill="#000000" fill-rule="evenodd" d="M 16 6 H 8 v 12 h 8 V 6 z m 4 16 H 4 V 2 h 16 v 20 z"/>
</svg>

After

Width:  |  Height:  |  Size: 193 B

@@ -0,0 +1,16 @@
{
"images": [
{
"filename": "icon.svg",
"idiom": "universal"
}
],
"info": {
"author": "xcode",
"version": 1
},
"properties": {
"preserves-vector-representation": true,
"template-rendering-intent": "template"
}
}
@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="800" height="800" viewBox="0 0 800 800">
<path fill="#000000" fill-rule="evenodd" d="M 165.29 165.29 h 352.07 V 400 H 400 v 117.36 H 282.65 v 117.36 H 165.29 z M 282.65 282.65 V 400 H 400 V 282.65 z"/>
<path fill="#000000" d="M 517.36 400 h 117.36 v 234.72 H 517.36 z"/>
</svg>

After

Width:  |  Height:  |  Size: 329 B

+4
View File
@@ -8,6 +8,8 @@ struct ChatHeaderUI: Equatable {
var title: String
/// "Flavor · machine · worktree" meta line; nil when nothing is known.
var subtitle: String?
/// Raw `metadata.flavor` — drives the brand icon next to the meta line.
var flavor: String?
var active: Bool
var thinking: Bool
}
@@ -110,6 +112,7 @@ final class ChatModel {
self.header = ChatHeaderUI(
title: String(sessionId.prefix(8)),
subtitle: nil,
flavor: nil,
active: false,
thinking: false
)
@@ -356,6 +359,7 @@ final class ChatModel {
return ChatHeaderUI(
title: title,
subtitle: parts.isEmpty ? nil : parts.joined(separator: " · "),
flavor: flavor,
active: detail?.active ?? summary?.active ?? false,
thinking: detail?.thinking ?? summary?.thinking ?? false
)
+12 -4
View File
@@ -324,10 +324,18 @@ struct ChatView: View {
.font(.headline)
.lineLimit(1)
if let subtitle = model.header.subtitle {
Text(subtitle)
.font(.caption2)
.foregroundStyle(.secondary)
.lineLimit(1)
HStack(spacing: 4) {
if model.header.flavor != nil {
// Inherits .secondary like the meta text (web:
// currentColor under --app-hint); color variants
// ignore the tint.
AgentFlavorIconView(flavor: model.header.flavor, size: 12)
}
Text(subtitle)
.font(.caption2)
.lineLimit(1)
}
.foregroundStyle(.secondary)
}
}
}
@@ -189,7 +189,19 @@ struct NewSessionView: View {
Section("Agent") {
Picker("Agent", selection: agentBinding) {
ForEach(model.agents, id: \.value) { agent in
Text(agent.label).tag(agent.value)
// Plain `Image` icon so the menu representation keeps it
// (UIMenu drops custom icon views); template monos take
// the menu tint, color marks render as-is.
if let asset = AgentFlavorIconView.assetName(forFlavor: agent.value) {
Label {
Text(agent.label)
} icon: {
Image(asset)
}
.tag(agent.value)
} else {
Text(agent.label).tag(agent.value)
}
}
}
}
@@ -0,0 +1,90 @@
import SwiftUI
/// Brand logo per agent flavor — port of `web/src/components/AgentFlavorIcon.tsx`
/// (brand SVGs via @lobehub/icons, the same source the web ships; assets live
/// in `Assets.xcassets/AgentIcons`).
///
/// Color variants (agy/claude/codex/gemini) carry `original` rendering intent
/// so the literal brand fills stay visible on light and dark — the reason the
/// web picked Color for them. Mono variants (cursor/grok/kimi/opencode/pi)
/// are template images and inherit the environment foreground style (the
/// web's currentColor); copilot mirrors the web's fixed GitHub-mark tint
/// (#24292F light / #E6EDF3 dark). Unknown flavors fall back to the web's
/// "Un" badge.
struct AgentFlavorIconView: View {
let flavor: String?
var size: CGFloat = 16
@Environment(\.colorScheme) private var colorScheme
/// Flavors with a bundled `AgentIcons/<flavor>` image set.
private static let bundledFlavors: Set<String> = [
"agy", "claude", "codex", "copilot", "cursor",
"gemini", "grok", "kimi", "opencode", "pi",
]
/// Asset name for a raw `metadata.flavor` value; nil when no icon ships.
static func assetName(forFlavor flavor: String?) -> String? {
guard let flavor else { return nil }
let normalized = flavor.trimmingCharacters(in: .whitespaces).lowercased()
return bundledFlavors.contains(normalized) ? normalized : nil
}
var body: some View {
content
.frame(width: size, height: size)
.accessibilityHidden(true)
}
@ViewBuilder
private var content: some View {
if let name = Self.assetName(forFlavor: flavor) {
let icon = Image(name)
.resizable()
.scaledToFit()
if name == "copilot" {
icon.foregroundStyle(colorScheme == .dark
? Color(red: 230 / 255, green: 237 / 255, blue: 243 / 255)
: Color(red: 36 / 255, green: 41 / 255, blue: 47 / 255))
} else {
icon
}
} else {
Text("Un")
.font(.system(size: 8, weight: .semibold))
.foregroundStyle(.secondary)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.secondary.opacity(0.15), in: RoundedRectangle(cornerRadius: 3))
}
}
}
#Preview("Agent icons") {
AgentFlavorIconPreviewGrid()
}
#Preview("Agent icons · dark") {
AgentFlavorIconPreviewGrid()
.preferredColorScheme(.dark)
}
private struct AgentFlavorIconPreviewGrid: View {
private let flavors: [String?] = [
"agy", "claude", "codex", "copilot", "cursor",
"gemini", "grok", "kimi", "opencode", "pi", "mystery",
]
var body: some View {
VStack(alignment: .leading, spacing: 12) {
ForEach(flavors, id: \.self) { flavor in
HStack(spacing: 8) {
AgentFlavorIconView(flavor: flavor)
Text(flavor ?? "nil")
.font(.footnote)
.foregroundStyle(.secondary)
}
}
}
.padding()
}
}
@@ -10,9 +10,9 @@ import SwiftUI
/// pull-to-refresh, empty/loading states;
/// - pinned section first (the sort already puts globalPinned/pinned rows on
/// top; a header makes the boundary visible);
/// - per row: status dot (active / thinking pulse), title, flavor + machine +
/// worktree meta line, summary/path line, relative `updatedAt`,
/// pending-request badge, todo-progress chip, unread dot;
/// - per row: status dot (active / thinking pulse), flavor brand icon +
/// title, machine + worktree meta line, summary/path line, relative
/// `updatedAt`, pending-request badge, todo-progress chip, unread dot;
/// - long-press context menu → pin (none/project/global) + archive with
/// optimistic store updates; failures land in an alert.
struct SessionListView: View {
@@ -219,6 +219,7 @@ struct SessionRowView: View {
private var titleLine: some View {
HStack(spacing: 6) {
AgentFlavorIconView(flavor: row.flavor)
Text(row.title)
.font(.body)
.fontWeight(row.unread ? .semibold : .regular)
@@ -236,6 +237,8 @@ struct SessionRowView: View {
}
}
// The agent flavor moved into the title line as a brand icon (web parity:
// `SessionRowSummary`), so the meta line carries machine + worktree only.
@ViewBuilder
private var metaLine: some View {
let worktree = row.summary.metadata?.worktree
@@ -243,7 +246,6 @@ struct SessionRowView: View {
tree.name.trimmingCharacters(in: .whitespaces).isEmpty ? tree.branch : tree.name
}
let parts: [String] = [
row.flavor.map { flavorLabel(forFlavor: $0) },
row.machineLabel,
worktreeLabel,
].compactMap { $0 }