refactor(client): adopt Tailwind color tokens for the plugin's palette
Replace the hand-picked hex literals with the nearest standard Tailwind tokens now emitted by the CSS channel: the category/timing/ring accents (indigo, amber, green, purple, blue, teal, pink, violet, red, slate-400) and the neutral chart tracks and zebra fills, recast as color-mix over --color-neutral-500 at the original alphas. Donut and agent-graph ring segments now carry their stroke as an inline style instead of the presentation attribute: the colors are CSS variables and var() is unusable in an attribute. Free segments keep their class rule by omitting the inline stroke. Visual output is unchanged (the v4 values are the nearest equivalents of the former v3 hexes); harness --dsw-* semantic tokens, the logo fills and the language identity colors stay as-is.
This commit is contained in:
1 parent
f1ab3a18c6
commit
75fada4fb3
15 files changed
+58
-49
No files matched your search
@@ -27,12 +27,12 @@ export interface PartsPart {
|
||||
}
|
||||
|
||||
export const CATS: { key: Category | 'system' | 'tools'; color: string }[] = [
|
||||
{ key: 'system', color: '#6366f1' },
|
||||
{ key: 'tools', color: '#f59e0b' },
|
||||
{ key: 'user', color: '#22c55e' },
|
||||
{ key: 'inject', color: '#a855f7' },
|
||||
{ key: 'assistant', color: '#3b82f6' },
|
||||
{ key: 'tool', color: '#14b8a6' },
|
||||
{ key: 'system', color: 'var(--color-indigo-500)' },
|
||||
{ key: 'tools', color: 'var(--color-amber-500)' },
|
||||
{ key: 'user', color: 'var(--color-green-500)' },
|
||||
{ key: 'inject', color: 'var(--color-purple-500)' },
|
||||
{ key: 'assistant', color: 'var(--color-blue-500)' },
|
||||
{ key: 'tool', color: 'var(--color-teal-500)' },
|
||||
]
|
||||
|
||||
/** Category key → bar color, for per-item bands (the browser's DNA mode) that bypass the CATS-order part builders. */
|
||||
@@ -130,5 +130,5 @@ export function billedParts(
|
||||
const prompt = input > 0
|
||||
? anchoredParts(estimated, input)
|
||||
: estimated.map(p => ({ ...p, value: 0 }))
|
||||
return [...prompt, { key: 'output', color: '#ec4899', value: Math.max(0, usage.outputTokens) }]
|
||||
return [...prompt, { key: 'output', color: 'var(--color-pink-500)', value: Math.max(0, usage.outputTokens) }]
|
||||
}
|
||||
@@ -40,9 +40,9 @@ const CAPTION_H = 60
|
||||
/** Fallback arc color for pressure-only nodes (no composition data), by fill ratio. */
|
||||
export function ringColorOf(pct: number | null): string {
|
||||
if (pct === null) return 'var(--dsw-alias-border-l1)'
|
||||
if (pct >= 90) return '#ef4444'
|
||||
if (pct >= 70) return '#f59e0b'
|
||||
return '#22c55e'
|
||||
if (pct >= 90) return 'var(--color-red-500)'
|
||||
if (pct >= 70) return 'var(--color-amber-500)'
|
||||
return 'var(--color-green-500)'
|
||||
}
|
||||
|
||||
export function makeAgentGraph(
|
||||
@@ -269,9 +269,12 @@ function AgentNodeView(props: NodeViewProps): ReactElement {
|
||||
key={seg.key}
|
||||
className={'lc-agent-seg' + (seg.free ? ' lc-agent-free' : '')}
|
||||
r={AGENT_RING_R}
|
||||
stroke={seg.free ? undefined : seg.color}
|
||||
strokeDasharray={`${seg.len} ${ring - seg.len}`}
|
||||
strokeDashoffset={-seg.offset}
|
||||
// Inline style, not the stroke attribute: segment colors are CSS variables
|
||||
// (var() is unusable in a presentation attribute). Free segments carry no
|
||||
// inline stroke so the .lc-agent-free class rule keeps painting the remainder.
|
||||
style={{ stroke: seg.free ? undefined : seg.color }}
|
||||
transform="rotate(-90)"
|
||||
/>
|
||||
))}
|
||||
|
||||
@@ -95,13 +95,14 @@ export function makeDonut(kit: ViewKit): (props: DonutProps) => ReactElement {
|
||||
cy="21"
|
||||
r="15.9155"
|
||||
fill="none"
|
||||
stroke={a.color}
|
||||
strokeWidth="4"
|
||||
strokeDasharray={`${a.len} ${100 - a.len}`}
|
||||
strokeDashoffset={a.offset}
|
||||
// Sweep-in stagger slot (stats.css animates stroke-dasharray from 0 100 up to these attribute
|
||||
// values, so the slices build clockwise from 12 o'clock, one after another).
|
||||
style={{ '--lc-i': i } as CSSProperties}
|
||||
// The stroke rides inline because the colors are CSS variables and SVG
|
||||
// presentation attributes cannot carry var().
|
||||
style={{ '--lc-i': i, stroke: a.color } as CSSProperties}
|
||||
onMouseEnter={() => { if (props.onHoverKey !== undefined) props.onHoverKey(a.key) }}
|
||||
/>
|
||||
))}
|
||||
|
||||
@@ -38,12 +38,12 @@ interface Slice {
|
||||
}
|
||||
|
||||
const COLOR = {
|
||||
ttft: '#3b82f6',
|
||||
reasoning: '#8b5cf6',
|
||||
text: '#ec4899',
|
||||
toolarg: '#f59e0b',
|
||||
tools: '#14b8a6',
|
||||
other: '#94a3b8',
|
||||
ttft: 'var(--color-blue-500)',
|
||||
reasoning: 'var(--color-violet-500)',
|
||||
text: 'var(--color-pink-500)',
|
||||
toolarg: 'var(--color-amber-500)',
|
||||
tools: 'var(--color-teal-500)',
|
||||
other: 'var(--color-slate-400)',
|
||||
} as const
|
||||
|
||||
export function makeStatsTiming(kit: ViewKit, Donut: (props: DonutProps) => ReactElement): (props: {
|
||||
|
||||
@@ -107,7 +107,10 @@ export function makeTrendChart(kit: ViewKit): (props: TrendChartProps) => ReactE
|
||||
const STAGGER_CAP = 20
|
||||
// Neutral zebra, deliberately DISJOINT from the category palette — the strip must read as a partition layer, not a bottom segment of the
|
||||
// composition bars.
|
||||
const TURN_FILLS = ['rgba(128,128,128,0.12)', 'rgba(128,128,128,0.26)']
|
||||
const TURN_FILLS = [
|
||||
'color-mix(in srgb, var(--color-neutral-500) 12%, transparent)',
|
||||
'color-mix(in srgb, var(--color-neutral-500) 26%, transparent)',
|
||||
]
|
||||
// Turn labels render at natural width (a 2-digit "12" is wider than a 14px turn bar) and overflow their block.
|
||||
// Every label must stay on the single line, so the strip shrinks ALL labels to one font size — the largest at
|
||||
// which the tightest adjacent pair still clears the gap (analytic widths below, no measurement) — and the
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
.lc-agent-self-badge { margin-left: 5px; vertical-align: 1px; }
|
||||
.lc-agents-badge-on { color: var(--dsw-alias-state-success-primary); border-color: color-mix(in srgb, var(--dsw-alias-state-success-primary) 40%, transparent); }
|
||||
.lc-agents-inspector-stats { color: var(--dsw-alias-label-secondary); font-variant-numeric: tabular-nums; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.lc-agents-inspector-open { margin-left: auto; flex: none; color: var(--dsw-alias-brand-primary, #3b82f6); }
|
||||
.lc-agents-inspector-open { margin-left: auto; flex: none; color: var(--dsw-alias-brand-primary, var(--color-blue-500)); }
|
||||
.lc-agents-legend { display: flex; flex-wrap: wrap; gap: 4px 14px; margin-top: 8px; font-size: 11px; color: var(--dsw-alias-label-secondary); }
|
||||
.lc-agents-legend-item { display: inline-flex; align-items: center; gap: 5px; }
|
||||
.lc-agents-legend-item i { width: 8px; height: 8px; border-radius: 2px; }
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/* The event log: list rows and kind tint pills (events.tsx). */
|
||||
.lc-event { display: flex; align-items: center; gap: 8px; padding: 3px 0; }
|
||||
.lc-event-icon { width: 18px; text-align: center; color: var(--dsw-alias-state-warn-primary); }
|
||||
.lc-event-icon.lc-event-inject { color: #a855f7; }
|
||||
.lc-event-icon.lc-event-inject { color: var(--color-purple-500); }
|
||||
.lc-event-icon.lc-event-model { color: var(--dsw-alias-brand-primary); }
|
||||
.lc-event-icon.lc-event-mode { color: var(--dsw-alias-label-secondary); }
|
||||
/*
|
||||
@@ -10,7 +10,7 @@
|
||||
.lc-kind { flex: none; font-size: 10px; font-weight: 600; padding: 1px 6px; border-radius: 4px; white-space: nowrap; }
|
||||
.lc-kind-inject { background: color-mix(in srgb, var(--dsw-alias-state-success-primary) 15%, transparent); color: var(--dsw-alias-state-success-primary); }
|
||||
.lc-kind-compaction { background: color-mix(in srgb, var(--dsw-alias-state-error-primary) 15%, transparent); color: var(--dsw-alias-state-error-primary); }
|
||||
.lc-kind-prune { background: color-mix(in srgb, #8b5cf6 15%, transparent); color: #8b5cf6; }
|
||||
.lc-kind-prune { background: color-mix(in srgb, var(--color-violet-500) 15%, transparent); color: var(--color-violet-500); }
|
||||
.lc-kind-model { background: color-mix(in srgb, var(--dsw-alias-brand-primary) 15%, transparent); color: var(--dsw-alias-brand-primary); }
|
||||
.lc-kind-mode { background: color-mix(in srgb, var(--dsw-alias-label-secondary) 15%, transparent); color: var(--dsw-alias-label-secondary); }
|
||||
.lc-event-label { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
|
||||
@@ -52,10 +52,10 @@ button.lc-brief-row-link { cursor: pointer; }
|
||||
.lc-detail-row-on i { box-shadow: 0 0 0 1px var(--dsw-alias-brand-primary); }
|
||||
.lc-detail-row-on .lc-detail-label { font-weight: 600; }
|
||||
.lc-detail-label { min-width: 70px; white-space: nowrap; color: var(--dsw-alias-label-secondary); }
|
||||
.lc-bar-track { flex: 1; height: 5px; border-radius: 3px; background: rgba(128,128,128,0.18); overflow: hidden; display: block; position: relative; }
|
||||
.lc-bar-track { flex: 1; height: 5px; border-radius: 3px; background: color-mix(in srgb, var(--color-neutral-500) 18%, transparent); overflow: hidden; display: block; position: relative; }
|
||||
.lc-bar-fill { display: block; height: 100%; border-radius: 3px; }
|
||||
/* Delta rows: a hairline zero at the middle, growth fills right of it, shrinkage left. */
|
||||
.lc-bar-zero { position: absolute; left: 50%; top: 0; bottom: 0; border-left: 1px solid rgba(128,128,128,0.45); }
|
||||
.lc-bar-zero { position: absolute; left: 50%; top: 0; bottom: 0; border-left: 1px solid color-mix(in srgb, var(--color-neutral-500) 45%, transparent); }
|
||||
.lc-bar-fill-up, .lc-bar-fill-down { position: absolute; top: 0; height: 100%; display: block; }
|
||||
.lc-bar-fill-up { left: 50%; border-radius: 0 3px 3px 0; }
|
||||
.lc-bar-fill-down { right: 50%; border-radius: 3px 0 0 3px; }
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* The composition stacked bar with occupied-window frame, reserve band, legend chips and hover tip (stackedBar.tsx). */
|
||||
.lc-stacked-wrap { position: relative; width: 100%; }
|
||||
.lc-stacked { display: flex; width: 100%; border-radius: 5px; overflow: hidden; background: rgba(128,128,128,0.18); position: relative; }
|
||||
.lc-stacked { display: flex; width: 100%; border-radius: 5px; overflow: hidden; background: color-mix(in srgb, var(--color-neutral-500) 18%, transparent); position: relative; }
|
||||
/*
|
||||
Hover reference frame around the OCCUPIED region of the composition bar: a solid box from the left edge to the used/window boundary so the legend/tooltip 'share of used' percentages visibly map to the boxed part; pointer-events: none keeps hover on the segments/free; the free track echoes it dashed so the two read as one system.
|
||||
*/
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
.lc-donut-row .lc-sl { flex: 1 1 auto; }
|
||||
.lc-donut { position: relative; flex: none; }
|
||||
.lc-donut svg { display: block; }
|
||||
.lc-donut-track { stroke: rgba(128,128,128,0.18); }
|
||||
.lc-donut-track { stroke: color-mix(in srgb, var(--color-neutral-500) 18%, transparent); }
|
||||
/* The legend row ↔ segment hover link: hovering either side dims the ring's
|
||||
other slices to 0.35 (the stacked bars' idiom) while the active one stays
|
||||
solid; the center figures stay at rest. */
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
.lc-grid-q1 { top: 102px; }
|
||||
/* The SOLID zero baseline in BOTH modes — inline-positioned off the up-arm in delta, the chart floor
|
||||
(top 130px, right under the bars) under the '0' label in total; painted after the dashed guides. */
|
||||
.lc-grid-zero { top: 130px; border-top-style: solid; border-top-color: rgba(128,128,128,0.5); }
|
||||
.lc-grid-zero { top: 130px; border-top-style: solid; border-top-color: color-mix(in srgb, var(--color-neutral-500) 50%, transparent); }
|
||||
.lc-bar { position: relative; width: 14px; flex: none; height: 100%; display: flex; align-items: flex-end; cursor: pointer; border-radius: 2px; outline: 1px dashed transparent; outline-offset: 1px; transition: opacity var(--ds-transition-duration, 0.2s) var(--ds-ease-in-out, ease-in-out), background-color var(--ds-transition-duration, 0.2s) var(--ds-ease-in-out, ease-in-out), outline-color var(--ds-transition-duration, 0.2s) var(--ds-ease-in-out, ease-in-out); }
|
||||
.lc-chart-dim .lc-bar { opacity: 0.35; }
|
||||
.lc-chart-dim .lc-bar-in-turn { opacity: 1; }
|
||||
@@ -55,7 +55,7 @@
|
||||
*/
|
||||
.lc-bar-hovered { outline-color: var(--dsw-alias-brand-primary); }
|
||||
.lc-bar-selected { outline: 2px solid var(--dsw-alias-label-primary); outline-offset: 1px; }
|
||||
.lc-bar-in-turn { background: rgba(128,128,128,0.14); }
|
||||
.lc-bar-in-turn { background: color-mix(in srgb, var(--color-neutral-500) 14%, transparent); }
|
||||
.lc-bar-stack { display: flex; flex-direction: column-reverse; width: 100%; }
|
||||
.lc-bar-stack > div { width: 100%; }
|
||||
/*
|
||||
|
||||
@@ -24,12 +24,12 @@ describe('partsOf', () => {
|
||||
test('reads all six categories in CATS order with their colors', () => {
|
||||
const parts = partsOf(current({ system: 1, tools: 2, user: 3, inject: 4, assistant: 5, tool: 6 }))
|
||||
assert.deepEqual(parts.map(p => [p.key, p.color, p.value]), [
|
||||
['system', '#6366f1', 1],
|
||||
['tools', '#f59e0b', 2],
|
||||
['user', '#22c55e', 3],
|
||||
['inject', '#a855f7', 4],
|
||||
['assistant', '#3b82f6', 5],
|
||||
['tool', '#14b8a6', 6],
|
||||
['system', 'var(--color-indigo-500)', 1],
|
||||
['tools', 'var(--color-amber-500)', 2],
|
||||
['user', 'var(--color-green-500)', 3],
|
||||
['inject', 'var(--color-purple-500)', 4],
|
||||
['assistant', 'var(--color-blue-500)', 5],
|
||||
['tool', 'var(--color-teal-500)', 6],
|
||||
])
|
||||
})
|
||||
|
||||
@@ -131,13 +131,13 @@ describe('billedParts', () => {
|
||||
// Input 400 equals the estimated total, so the ratios carry over as-is;
|
||||
// the seven parts sum to the chat line's 460 by construction.
|
||||
assert.deepEqual(parts.map(p => [p.key, p.color, p.value]), [
|
||||
['system', '#6366f1', 50],
|
||||
['tools', '#f59e0b', 50],
|
||||
['user', '#22c55e', 100],
|
||||
['inject', '#a855f7', 0],
|
||||
['assistant', '#3b82f6', 100],
|
||||
['tool', '#14b8a6', 100],
|
||||
['output', '#ec4899', 60],
|
||||
['system', 'var(--color-indigo-500)', 50],
|
||||
['tools', 'var(--color-amber-500)', 50],
|
||||
['user', 'var(--color-green-500)', 100],
|
||||
['inject', 'var(--color-purple-500)', 0],
|
||||
['assistant', 'var(--color-blue-500)', 100],
|
||||
['tool', 'var(--color-teal-500)', 100],
|
||||
['output', 'var(--color-pink-500)', 60],
|
||||
])
|
||||
})
|
||||
|
||||
|
||||
@@ -393,8 +393,8 @@ describe('AgentGraph — the family tree', () => {
|
||||
describe('ringColorOf', () => {
|
||||
test('occupancy thresholds', () => {
|
||||
assert.equal(ringColorOf(null), 'var(--dsw-alias-border-l1)')
|
||||
assert.equal(ringColorOf(95), '#ef4444')
|
||||
assert.equal(ringColorOf(70), '#f59e0b')
|
||||
assert.equal(ringColorOf(12), '#22c55e')
|
||||
assert.equal(ringColorOf(95), 'var(--color-red-500)')
|
||||
assert.equal(ringColorOf(70), 'var(--color-amber-500)')
|
||||
assert.equal(ringColorOf(12), 'var(--color-green-500)')
|
||||
})
|
||||
})
|
||||
@@ -53,7 +53,9 @@ describe('Donut', () => {
|
||||
}))
|
||||
const circles = queryAll(m.container, 'circle')
|
||||
assert.equal(circles.length, 1)
|
||||
assert.equal(circles[0].getAttribute('stroke'), '#ffffff')
|
||||
// The color rides inline style (var() values can't live in a stroke attribute);
|
||||
// jsdom reports the style keyword in its normalized rgb() spelling.
|
||||
assert.equal(circles[0].style.stroke, 'rgb(255, 255, 255)')
|
||||
await m.unmount()
|
||||
})
|
||||
|
||||
|
||||
@@ -179,7 +179,7 @@ describe('TrendChart step granularity, total mode', () => {
|
||||
// Zero-value categories are skipped entirely (r3.user = 0 → five segments, no user-green segment).
|
||||
const segs3 = queryAll(bs[2], '.lc-bar-stack > div')
|
||||
assert.equal(segs3.length, 5)
|
||||
assert.ok(![...segs3].some(s => s.style.background.includes('34, 197, 94') || s.style.background === '#22c55e'))
|
||||
assert.ok(![...segs3].some(s => s.style.background.includes('var(--color-green-500)')))
|
||||
|
||||
// Total-mode axis: full quartile graduation — max, ¾, ½, ¼, 0.
|
||||
assert.equal(query(m.container, '.lc-axis-top').textContent, '600')
|
||||
@@ -194,8 +194,8 @@ describe('TrendChart step granularity, total mode', () => {
|
||||
assert.equal(turns.length, 2)
|
||||
assert.equal(turns[0].style.width, '30px')
|
||||
assert.equal(turns[1].style.width, '14px')
|
||||
assert.ok(turns[0].style.background.includes('0.12'))
|
||||
assert.ok(turns[1].style.background.includes('0.26'))
|
||||
assert.ok(turns[0].style.background.includes('var(--color-neutral-500) 12%'))
|
||||
assert.ok(turns[1].style.background.includes('var(--color-neutral-500) 26%'))
|
||||
assert.deepEqual(turns.map(t => t.textContent), ['1', '0'])
|
||||
await m.unmount()
|
||||
})
|
||||
|
||||
Reference in new issue
Block a user