fix(icons): say "background work", not "to-do list", and lighten the terminal glyph
Two glyphs read wrong in the live instance: - Tasks shows subagent sessions AND the host's background jobs, so a checklist glyph described a page that does not exist. `VscLayers` (sheets stacked, i.e. work running behind the conversation) says what the page is; the amber token stays, so it still reads as live activity. - The terminal was the widest glyph in the set and the only bare fill in a strip of the plugin's own 1.5px-stroked icons, which made it look like a heavy foreign block. It now renders a step down (0.85x the strip size, floor 10px) and every colored tab glyph gets a hairline outline of its own ink (`stroke: currentColor` + `non-scaling-stroke`, so the line stays one device pixel across the sets' mixed 16- and 24-unit viewBoxes; `paint-order: stroke` centres it on the fill edge instead of fattening the shape). tests/builtins.spec.ts pins both readings by glyph identity (tasks = layers, terminal = the shrunk, outlined VscTerminal), so a future swap has to argue with a failing assertion rather than with a comment. Gates: typecheck / lint green, pnpm test 127 files / 1343 passed / 9 skipped.
This commit is contained in:
@@ -29,6 +29,8 @@
|
||||
}
|
||||
|
||||
.terminal {
|
||||
/* The shell chip is ink, not accent — and it is the widest glyph in the set,
|
||||
so it renders a step smaller than its siblings (see tab-icons.tsx). */
|
||||
color: var(--dsw-alias-label-primary);
|
||||
}
|
||||
|
||||
|
||||
@@ -14,14 +14,14 @@
|
||||
* `files` is the one exception in kind rather than in color: it renders DSH's
|
||||
* own folder artwork (`FileTypeIcon`), matching the file rows this tab shows.
|
||||
*/
|
||||
import type { ReactNode } from 'react'
|
||||
import { cloneElement, type ReactElement, type ReactNode } from 'react'
|
||||
import { FileTypeIcon } from '@deepseek-ai/dsh-client-ui-primitives'
|
||||
import styles from './tab-icons.module.css'
|
||||
import {
|
||||
VscCommentDiscussion,
|
||||
VscGitCommit,
|
||||
VscGlobe,
|
||||
VscTasklist,
|
||||
VscLayers,
|
||||
VscTerminal,
|
||||
} from 'react-icons/vsc'
|
||||
/** The styled wrapper classes; typed so a renamed rule fails the build. */
|
||||
@@ -35,6 +35,35 @@ function themed(className: string, glyph: ReactNode): ReactNode {
|
||||
return <span className={className}>{glyph}</span>
|
||||
}
|
||||
|
||||
/**
|
||||
* Give a filled VSCodicon a hairline of its own ink.
|
||||
*
|
||||
* The plugin's hand-drawn icons are 1.5px strokes on a 16px grid; the
|
||||
* VSCodicon set is filled at that size, and next to the plugin's own chrome a
|
||||
* bare fill reads heavy and slightly out of place — most visibly on the
|
||||
* terminal chip. A `currentColor` stroke in the SVG's own user units keeps the
|
||||
* host's shape and the token's color while lending it the same drawn weight,
|
||||
* and `paint-order: stroke` centres that hairline on the fill's edge instead
|
||||
* of fattening it.
|
||||
* @param icon - the glyph element to outline.
|
||||
* @returns the same glyph with its outline applied.
|
||||
*/
|
||||
function outlined(icon: ReactElement): ReactElement {
|
||||
return cloneElement(icon as ReactElement<{ style?: React.CSSProperties }>, {
|
||||
style: {
|
||||
// `non-scaling-stroke` keeps the hairline one device pixel whatever the
|
||||
// glyph's own grid is (the sets mix 16- and 24-unit viewBoxes and the
|
||||
// strip renders at 14px), and `paint-order: stroke` centres it on the
|
||||
// fill's edge instead of fattening the shape.
|
||||
stroke: 'currentColor',
|
||||
strokeWidth: 1,
|
||||
strokeLinejoin: 'round',
|
||||
paintOrder: 'stroke',
|
||||
vectorEffect: 'non-scaling-stroke',
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
/** The Files tab: the host's folder artwork, like the rows it opens. */
|
||||
export const filesTabIcon: TabIcon = (size) => (
|
||||
<span className={css.files}>
|
||||
@@ -44,20 +73,28 @@ export const filesTabIcon: TabIcon = (size) => (
|
||||
|
||||
/** Changes / diff: the commit glyph, green like the diff affordances. */
|
||||
export const changesTabIcon: TabIcon = (size) =>
|
||||
themed(css.changes, <VscGitCommit size={size} />)
|
||||
themed(css.changes, outlined(<VscGitCommit size={size} />))
|
||||
|
||||
/** Tasks (subagent) — the live-activity amber. */
|
||||
/**
|
||||
* Tasks (subagents and background jobs) — the live-activity amber. The glyph
|
||||
* is layered sheets, not a checklist: this page lists RUNNING work (subagent
|
||||
* sessions plus the host's background jobs), not a to-do list.
|
||||
*/
|
||||
export const tasksTabIcon: TabIcon = (size) =>
|
||||
themed(css.tasks, <VscTasklist size={size} />)
|
||||
themed(css.tasks, outlined(<VscLayers size={size} />))
|
||||
|
||||
/** Side chat — the conversational/secondary accent. */
|
||||
export const sidechatTabIcon: TabIcon = (size) =>
|
||||
themed(css.sidechat, <VscCommentDiscussion size={size} />)
|
||||
themed(css.sidechat, outlined(<VscCommentDiscussion size={size} />))
|
||||
|
||||
/** Terminal — primary ink, the shell is text. */
|
||||
/**
|
||||
* Terminal — primary ink, the shell is text. Drawn one step down from the
|
||||
* strip's 14px and with the outline above: the VSCodicon terminal is a wide
|
||||
* filled rectangle that dominated the chip at full size.
|
||||
*/
|
||||
export const terminalTabIcon: TabIcon = (size) =>
|
||||
themed(css.terminal, <VscTerminal size={size} />)
|
||||
themed(css.terminal, outlined(<VscTerminal size={Math.max(10, Math.round(size * 0.85))} />))
|
||||
|
||||
/** Browser — the same secondary accent as the side chat's sibling surfaces. */
|
||||
export const browserTabIcon: TabIcon = (size) =>
|
||||
themed(css.browser, <VscGlobe size={size} />)
|
||||
themed(css.browser, outlined(<VscGlobe size={size} />))
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
* tab (git lens + session lens, PR #471's file-trace merged in).
|
||||
*/
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import type { ReactElement } from 'react'
|
||||
import { VscCommentDiscussion, VscGitCommit, VscGlobe, VscLayers, VscTerminal } from 'react-icons/vsc'
|
||||
// First import: browser globals before the xterm-carrying builtin graph loads.
|
||||
import './browser-globals.ts'
|
||||
|
||||
@@ -235,6 +237,36 @@ describe('built-in tab registrations', () => {
|
||||
expect(tab.icon, tab.id).toBeDefined()
|
||||
}
|
||||
})
|
||||
|
||||
it('the tab glyphs say what the page shows (colored, token-driven)', () => {
|
||||
const { service } = setup()
|
||||
const iconOf = (id: string): ReactElement => {
|
||||
const icon = service.getTab(id)?.icon
|
||||
expect(icon, id).toBeDefined()
|
||||
return (typeof icon === 'function' ? icon(14) : icon) as ReactElement
|
||||
}
|
||||
// Every colored glyph is [wrapper][glyph]; unwrap the themed wrapper.
|
||||
const glyphOf = (id: string): unknown => {
|
||||
const wrapper = iconOf(id) as ReactElement<{ children?: ReactElement }>
|
||||
return (wrapper.props.children as ReactElement | undefined)?.type ?? wrapper.type
|
||||
}
|
||||
// Tasks lists subagent sessions AND background jobs — layered sheets say
|
||||
// "work running in the background"; a checklist glyph would say "to-do
|
||||
// list", which this page is not.
|
||||
expect(glyphOf('subagent')).toBe(VscLayers)
|
||||
expect(glyphOf('git')).toBe(VscGitCommit)
|
||||
expect(glyphOf('sidechat')).toBe(VscCommentDiscussion)
|
||||
expect(glyphOf('browser')).toBe(VscGlobe)
|
||||
// The terminal glyph is the widest in the set: it renders a step down from
|
||||
// the strip's size and keeps a hairline outline so it matches the plugin's
|
||||
// own drawn chrome instead of reading as a heavy filled block.
|
||||
const wrapper = iconOf('terminal') as ReactElement<{ children?: ReactElement }>
|
||||
const terminal = wrapper.props.children as ReactElement<{ size?: number; style?: Record<string, unknown> }>
|
||||
expect(terminal.type).toBe(VscTerminal)
|
||||
expect(terminal.props.size).toBeLessThan(14)
|
||||
expect(terminal.props.style?.strokeWidth).toBe(1)
|
||||
expect(terminal.props.style?.vectorEffect).toBe('non-scaling-stroke')
|
||||
})
|
||||
})
|
||||
|
||||
describe('built-in file viewer registrations', () => {
|
||||
|
||||
Reference in New Issue
Block a user