fix(c8): match native icon family — outline glyphs at native scale
CI / ci (push) Canceled after 0s
CI / ci-windows (push) Canceled after 0s
CI / plugin-mount (push) Canceled after 0s

The apes tab glyphs (智能体/应用/画像) were fill-only paths spanning only
10-12 of the 16 viewBox units, so they rendered visibly smaller and lighter
than the DSH native glyphs (which fill ~92-96% of the box) in the + new-tab
menu and the right-panel tile grid.

Rewrite them in this repo's own outline recipe (src/client/icons.tsx): 16x16
viewBox, artwork on 1.5-14.5, 1.5px currentColor stroke, round caps/joins.
Renamed IconAgentFill16/IconGaugeFill16/IconUserFill16 ->
IconAgentOutline16/IconAppsOutline16/IconUserOutline16.
This commit is contained in:
figmar
2026-10-03 20:05:45 +08:00
parent 2f3944a8bd
commit 9ea9e9b9fa
2 changed files with 32 additions and 33 deletions
+28 -29
View File
@@ -1,51 +1,50 @@
/**
* Custom fill-style icons for apes tabs — matching the visual weight of
* DSH native tabs (which use `fill: currentColor` paths, not outline/mask).
* Tab glyphs for the apes tabs (智能体 / 应用 / 画像).
*
* DSH's `@deepseek-ai/dsh-client-ui-primitives` only ships outline variants
* for agent/gauge/user, so we define our own fill variants here to ensure
* the apes tabs look "official" in the + menu.
* These follow the SAME recipe as this repo's own icon set in
* `src/client/icons.tsx` — the one the sidebar already uses for 终端 /
* 浏览器 / 文件变动: a 16×16 viewBox whose artwork spans 1.5–14.5, a 1.5px
* `currentColor` stroke, round caps/joins, `fill: none`.
*
* That recipe is what makes an icon sit at the native visual weight in the
* `+` new-tab menu and the right-panel tile grid: DSH's own glyphs fill
* ~92–96% of the box, so artwork padded any tighter renders visibly small
* next to them.
*
* (An earlier revision drew these as fill-only paths spanning only 10–12 of
* the 16 units — that is why they read as a separate, lighter family.)
*/
import type { ReactNode } from 'react'
/** Agent icon: a simple robot/agent face (fill style). */
export function IconAgentFill16({ size = 16, className }: { size?: number; className?: string }): ReactNode {
/** Agent glyph: a rounded robot head with two fill eyes. */
export function IconAgentOutline16({ size = 16, className }: { size?: number; className?: string }): ReactNode {
return (
<svg width={size} height={size} viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" className={className}>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M8 1.5C4.686 1.5 2 4.186 2 7.5V12.5C2 13.328 2.672 14 3.5 14H12.5C13.328 14 14 13.328 14 12.5V7.5C14 4.186 11.314 1.5 8 1.5ZM8 3C10.485 3 12.5 5.015 12.5 7.5V12.5H3.5V7.5C3.5 5.015 5.515 3 8 3ZM6 6.5C6.828 6.5 7.5 7.172 7.5 8C7.5 8.828 6.828 9.5 6 9.5C5.172 9.5 4.5 8.828 4.5 8C4.5 7.172 5.172 6.5 6 6.5ZM10 6.5C10.828 6.5 11.5 7.172 11.5 8C11.5 8.828 10.828 9.5 10 9.5C9.172 9.5 8.5 8.828 8.5 8C8.5 7.172 9.172 6.5 10 6.5Z"
fill="currentColor"
/>
<rect x="1.5" y="3" width="13" height="11.5" rx="4.2" stroke="currentColor" strokeWidth="1.5" />
<circle cx="5.8" cy="8.6" r="1.25" fill="currentColor" />
<circle cx="10.2" cy="8.6" r="1.25" fill="currentColor" />
</svg>
)
}
/** Gauge/app icon: a simple app grid (fill style). */
export function IconGaugeFill16({ size = 16, className }: { size?: number; className?: string }): ReactNode {
/** Apps glyph: a 2×2 grid of rounded squares. */
export function IconAppsOutline16({ size = 16, className }: { size?: number; className?: string }): ReactNode {
return (
<svg width={size} height={size} viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" className={className}>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M3 3H7V7H3V3ZM9 3H13V7H9V3ZM3 9H7V13H3V9ZM9 9H13V13H9V9Z"
fill="currentColor"
/>
<rect x="1.5" y="1.5" width="5.5" height="5.5" rx="1.8" stroke="currentColor" strokeWidth="1.5" />
<rect x="9" y="1.5" width="5.5" height="5.5" rx="1.8" stroke="currentColor" strokeWidth="1.5" />
<rect x="1.5" y="9" width="5.5" height="5.5" rx="1.8" stroke="currentColor" strokeWidth="1.5" />
<rect x="9" y="9" width="5.5" height="5.5" rx="1.8" stroke="currentColor" strokeWidth="1.5" />
</svg>
)
}
/** User/profile icon: a simple user silhouette (fill style). */
export function IconUserFill16({ size = 16, className }: { size?: number; className?: string }): ReactNode {
/** Profile glyph: a head over shoulders. */
export function IconUserOutline16({ size = 16, className }: { size?: number; className?: string }): ReactNode {
return (
<svg width={size} height={size} viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" className={className}>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M8 2.5C5.519 2.5 3.5 4.519 3.5 7C3.5 9.481 5.519 11.5 8 11.5C10.481 11.5 12.5 9.481 12.5 7C12.5 4.519 10.481 2.5 8 2.5ZM8 4C9.657 4 11 5.343 11 7C11 8.657 9.657 10 8 10C6.343 10 5 8.657 5 7C5 5.343 6.343 4 8 4ZM2 13.5C2 12.672 2.672 12 3.5 12H12.5C13.328 12 14 12.672 14 13.5C14 14.328 13.328 15 12.5 15H3.5C2.672 15 2 14.328 2 13.5Z"
fill="currentColor"
/>
<circle cx="8" cy="4.9" r="3.4" stroke="currentColor" strokeWidth="1.5" />
<path d="M1.5 14.5c0-3.3 2.8-5.3 6.5-5.3s6.5 2 6.5 5.3" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
</svg>
)
}
+4 -4
View File
@@ -26,7 +26,7 @@
*/
import { useCallback, useMemo, useState, type CSSProperties } from 'react'
import { useSyncExternalStore } from 'react'
import { IconAgentFill16, IconGaugeFill16, IconUserFill16 } from './icons.tsx'
import { IconAgentOutline16, IconAppsOutline16, IconUserOutline16 } from './icons.tsx'
import type { TabComponentProps, TabDescriptor } from '../service.ts'
import type {
SidebarSessionList,
@@ -231,7 +231,7 @@ function ApesAppsView(props: TabComponentProps) {
* to `service.registerTab`. Information-layer tabs are single-instance.
*/
export const apesTabs: TabDescriptor[] = [
{ id: 'apes:agents', title: () => t('apesAgentsTitle'), icon: (size: number) => <IconAgentFill16 size={size} />, order: 60, single: true, component: ApesAgentsView },
{ id: 'apes:apps', title: () => t('apesAppsTitle'), icon: (size: number) => <IconGaugeFill16 size={size} />, order: 65, single: true, component: ApesAppsView },
{ id: 'apes:profile', title: () => t('apesProfileTitle'), icon: (size: number) => <IconUserFill16 size={size} />, order: 70, single: true, component: ApesProfileView },
{ id: 'apes:agents', title: () => t('apesAgentsTitle'), icon: (size: number) => <IconAgentOutline16 size={size} />, order: 60, single: true, component: ApesAgentsView },
{ id: 'apes:apps', title: () => t('apesAppsTitle'), icon: (size: number) => <IconAppsOutline16 size={size} />, order: 65, single: true, component: ApesAppsView },
{ id: 'apes:profile', title: () => t('apesProfileTitle'), icon: (size: number) => <IconUserOutline16 size={size} />, order: 70, single: true, component: ApesProfileView },
]