From cc0babaf02cd0c5359df15ffc7efff5ef996eab5 Mon Sep 17 00:00:00 2001 From: bowenliang123 Date: Thu, 15 Oct 2026 03:06:24 +0800 Subject: [PATCH] feat(client): lead the trend detail panel with the composition card's chips MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The pinned step's per-category rows were a second rendering of the same categories, drifted from the one above: their own grid, dimmed regular-weight labels, a fixed figure column. The panel now renders the composition card's own chip legend — same dot, 600-weight label, right-aligned figures with the secondary share, same hover highlight and 160px grid reflow — in the shared CATS order, so a category reads identically in both cards. Delta mode has no counterpart above, so makeLegend gains a delta variant: the chips print the signed change colored by sign, and both the share and its "of used" hint drop out. The panel's chips report hovers back exactly as the composition legend's do, and the reserved lane mirrors the chip grid so the pending and loaded panels still measure alike. --- src/client/components/contextView.tsx | 14 ++-- src/client/components/currentComposition.tsx | 9 +-- src/client/components/requestDetail.tsx | 32 +++----- src/client/components/stackedBar.tsx | 25 +++--- src/client/styles/requestDetail.css | 14 ---- src/client/styles/stackedBar.css | 5 +- .../contextView.interactions.spec.ts | 16 +++- .../contextView.splitDetail.spec.ts | 7 +- tests/client/components/requestDetail.spec.ts | 78 +++++++++---------- 9 files changed, 95 insertions(+), 105 deletions(-) diff --git a/src/client/components/contextView.tsx b/src/client/components/contextView.tsx index 917c275..b17e6cf 100644 --- a/src/client/components/contextView.tsx +++ b/src/client/components/contextView.tsx @@ -66,14 +66,13 @@ function TrendReserved(props: { children: ReactNode; catLabel: (cat: Category |
-
+
{CATS.map(c => ( -
+ - {props.catLabel(c.key)} - - -
+ {props.catLabel(c.key)} + + ))}
@@ -94,7 +93,7 @@ export function makeContextView( const CurrentComposition = makeCurrentComposition(kit, StackedBar, Legend) const TrendChart = makeTrendChart(kit) const TrendOverview = makeTrendOverview(kit) - const RequestDetail = makeRequestDetail(kit, StackedBar) + const RequestDetail = makeRequestDetail(kit, StackedBar, Legend) const EventList = makeEventList(kit) const FileCard = makeFileCard(kit, settings) // The stats board's subagent-cost cell reads the shared page-scope cold-head cache. @@ -572,6 +571,7 @@ export function makeContextView( stepsOf={stepsOf} onLocate={locateNode} hoverKey={trendHoverCat} + onHoverKey={setHoverCat} />
)} diff --git a/src/client/components/currentComposition.tsx b/src/client/components/currentComposition.tsx index d4cbe33..40dfec2 100644 --- a/src/client/components/currentComposition.tsx +++ b/src/client/components/currentComposition.tsx @@ -1,14 +1,9 @@ import { type ReactElement } from 'react' -import type { PartsPart } from '../categories' import type { Headline } from '../headline' import type { ViewKit } from '../viewkit' -import type { StackedBarProps } from './stackedBar' +import type { StackedBarProps, LegendProps } from './stackedBar' -type LegendFn = (props: { - parts: PartsPart[] - hoverKey?: string | null - onHoverKey?: (key: string | null) => void -}) => ReactElement +type LegendFn = (props: LegendProps) => ReactElement export interface CurrentCompositionProps { head: Headline diff --git a/src/client/components/requestDetail.tsx b/src/client/components/requestDetail.tsx index 33daef1..9d7ee53 100644 --- a/src/client/components/requestDetail.tsx +++ b/src/client/components/requestDetail.tsx @@ -5,7 +5,7 @@ import { cacheHitPercent } from '../format' import type { StepBrief } from '../brief' import { blockSummaryOf, callNamesOf, callSummaryOf } from '../callSummary' import type { ConversationNodeLike } from '../services' -import type { StackedBarProps } from './stackedBar' +import type { StackedBarProps, LegendProps } from './stackedBar' import type { ViewKit } from '../viewkit' export interface RequestDetailProps { @@ -24,16 +24,18 @@ export interface RequestDetailProps { stepsOf?: (turn: number | undefined) => number /** Reveal a brief row's node in the Context browser; absent = rows render inert. */ onLocate?: (node: SurfaceNode, isResponse: boolean) => void - /** Mirrored category hover (shared with the overview/browser): lights the composition bar's matching - * segment; the bar never reports hovers back. */ + /** Category hover shared with the overview/browser and the composition legend: lights the matching bar + * segment and chip, and the chips report hovers back — the bar never does. */ hoverKey?: string | null + onHoverKey?: (key: string | null) => void } export function makeRequestDetail( kit: ViewKit, StackedBar: (props: StackedBarProps) => ReactElement, + Legend: (props: LegendProps) => ReactElement, ): ComponentType { - const { t, fmt, fmtTime, catLabel, eventLabel, eventAt } = kit + const { t, fmt, fmtTime, eventLabel, eventAt } = kit /** One brief row: a fixed-width kind tag plus one glanceable line. The tag carries a styled, instant explanation bubble (the * shared `.lc-tip` chrome); the content span keeps the native title (preview + locate hint), so the two never stack. @@ -213,6 +215,8 @@ export function makeRequestDetail( const parts = delta ? CATS.map((c, i) => ({ key: c.key, color: c.color, value: Math.abs(deltas[i]) })) : partsOf(req) + // Delta's chips print the signed change; the bar's own parts keep only magnitudes. + const legendParts = delta ? parts.map((p, i) => ({ ...p, raw: deltas[i] })) : parts return (
@@ -248,25 +252,7 @@ export function makeRequestDetail( {/* Mirrors the shared category hover with the tip off — a cross-card hover must not float a second tooltip over a bar the pointer does not rest on (same rule as the browser's own bar). */} -
- {CATS.map((c, i) => { - const v = delta ? deltas[i] : req[c.key] || 0 - return ( -
- - {catLabel(c.key)} - {delta ? ( - 0 ? ' lc-detail-num-up' : v < 0 ? ' lc-detail-num-down' : '')}> - {(v > 0 ? '+' : '') + fmt(v)} - - ) : ( - {'≈' + fmt(v)} - )} - {!delta && req.total > 0 ? `${Math.round(v / req.total * 100)}%` : ''} -
- ) - })} -
+
) }) diff --git a/src/client/components/stackedBar.tsx b/src/client/components/stackedBar.tsx index 6a85b52..e0ac29a 100644 --- a/src/client/components/stackedBar.tsx +++ b/src/client/components/stackedBar.tsx @@ -289,17 +289,19 @@ export function makeStackedBar(kit: ViewKit): (props: StackedBarProps) => ReactE } } -export function makeLegend(kit: ViewKit): (props: { +/** The category chips' interface, shared by the composition card's legend and the trend panel. */ +export interface LegendProps { parts: PartsPart[] hoverKey?: string | null onHoverKey?: (key: string | null) => void -}) => ReactElement { + /** Signed figures (the trend panel's Delta mode): the count prints with its sign and the sign's color, + * and the share drops out — a signed change has no share of the used total. */ + delta?: boolean +} + +export function makeLegend(kit: ViewKit): (props: LegendProps) => ReactElement { const { t, fmt, catLabel } = kit - return function Legend(props: { - parts: PartsPart[] - hoverKey?: string | null - onHoverKey?: (key: string | null) => void - }): ReactElement { + return function Legend(props: LegendProps): ReactElement { let total = 0 for (const p of props.parts) total += p.raw ?? p.value return ( @@ -307,19 +309,20 @@ export function makeLegend(kit: ViewKit): (props: { {props.parts.map((p) => { const count = p.raw ?? p.value const on = props.hoverKey !== undefined && props.hoverKey === p.key + const sign = props.delta === true && count !== 0 ? ' lc-chip-nums-' + (count > 0 ? 'up' : 'down') : '' return ( { if (props.onHoverKey !== undefined) props.onHoverKey(p.key) }} onMouseLeave={() => { if (props.onHoverKey !== undefined) props.onHoverKey(null) }} > {catLabel(p.key)} - - {'≈' + fmt(count)} - {total > 0 ? {`${Math.round(count / total * 100)}%`} : null} + + {props.delta === true ? (count > 0 ? '+' : '') + fmt(count) : '≈' + fmt(count)} + {props.delta !== true && total > 0 ? {`${Math.round(count / total * 100)}%`} : null} ) diff --git a/src/client/styles/requestDetail.css b/src/client/styles/requestDetail.css index 6f186d9..f7b514b 100644 --- a/src/client/styles/requestDetail.css +++ b/src/client/styles/requestDetail.css @@ -13,8 +13,6 @@ /* Sign semantics mirror the harness diff blocks. */ .lc-detail-metric-up { color: var(--dsw-alias-state-success-primary); background: color-mix(in srgb, var(--dsw-alias-state-success-primary) 15%, transparent); } .lc-detail-metric-down { color: var(--dsw-alias-state-error-primary); background: color-mix(in srgb, var(--dsw-alias-state-error-primary) 15%, transparent); } -/* auto-fit instead of a fixed pair: a row's min-content is ~196px, below which two columns would spill. */ -.lc-detail-rows { margin-top: 10px; display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 4px 24px; } /* A fixed three-row lane (3 x the 27px row + 2 x the 2px gap): the container exists even when no row resolves, AND every row is a constant height whether or not it carries a chip — a chip is 2px taller than bare text, so without the floor the lane measured 79.5px on an input-less step and 81.5px on one with chips, moving the trend card (and @@ -34,15 +32,3 @@ button.lc-brief-row-link { cursor: pointer; } .lc-brief-chip-text { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .lc-brief-chip-link { cursor: pointer; } .lc-brief-more { flex: none; font-size: 11px; color: var(--dsw-alias-label-secondary); } -.lc-detail-row { display: flex; align-items: center; gap: 8px; border-radius: 4px; /* Padding + matching negative margin: the highlight paints past the text while the layout stays put. */ padding: 1px 4px; margin: -1px -4px; transition: background-color var(--ds-transition-duration, 0.2s) var(--ds-ease-in-out, ease-in-out); } -/* Category hover-link: the shared hover lights the matching detail row. */ -.lc-detail-row i { transition: box-shadow var(--ds-transition-duration, 0.2s) var(--ds-ease-in-out, ease-in-out); } -.lc-detail-row-on { background: var(--dsw-alias-interactive-bg-hover); } -.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); } -/* The figures pin to the row's right edge, so they form one column across the labels' ragged widths. */ -.lc-detail-num { margin-left: auto; width: 52px; text-align: right; font-variant-numeric: tabular-nums; } -.lc-detail-num-up { color: var(--dsw-alias-state-success-primary); } -.lc-detail-num-down { color: var(--dsw-alias-state-error-primary); } -.lc-detail-pct { width: 34px; text-align: right; color: var(--dsw-alias-label-secondary); } diff --git a/src/client/styles/stackedBar.css b/src/client/styles/stackedBar.css index 6cb3a89..1e38620 100644 --- a/src/client/styles/stackedBar.css +++ b/src/client/styles/stackedBar.css @@ -38,8 +38,11 @@ .lc-chip { display: flex; align-items: center; gap: 5px; min-width: 0; color: var(--dsw-alias-label-primary); padding: 1px 6px; border-radius: 6px; cursor: pointer; transition: background-color var(--ds-transition-duration, 0.2s) var(--ds-ease-in-out, ease-in-out); } .lc-chip-label { font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; } .lc-chip-nums { margin-left: auto; flex: none; display: inline-flex; align-items: baseline; gap: 6px; white-space: nowrap; } -.lc-chip i, .lc-detail-row i { display: inline-block; width: 8px; height: 8px; border-radius: 2px; } +.lc-chip i { display: inline-block; width: 8px; height: 8px; border-radius: 2px; } .lc-chip i { transition: box-shadow var(--ds-transition-duration, 0.2s) var(--ds-ease-in-out, ease-in-out); } .lc-chip em { font-style: normal; color: var(--dsw-alias-label-secondary); } +/* Signed figures (the trend panel's Delta mode): the sign semantics mirror the harness diff blocks. */ +.lc-chip-nums-up { color: var(--dsw-alias-state-success-primary); } +.lc-chip-nums-down { color: var(--dsw-alias-state-error-primary); } .lc-chip-on { font-weight: 600; background: var(--dsw-alias-interactive-bg-hover); } .lc-chip-on i { box-shadow: 0 0 0 1px var(--dsw-alias-brand-primary); } diff --git a/tests/client/components/contextView.interactions.spec.ts b/tests/client/components/contextView.interactions.spec.ts index 272d75f..d879fd8 100644 --- a/tests/client/components/contextView.interactions.spec.ts +++ b/tests/client/components/contextView.interactions.spec.ts @@ -142,7 +142,7 @@ describe('ContextView — interactions', () => { const detailBar = query(m.container, '.lc-detail .lc-stacked') assert.ok(detailBar.className.includes('lc-stacked-dim')) assert.ok(queryAll(detailBar, '.lc-stacked-seg-on').length === 1) - assert.ok(queryAll(m.container, '.lc-detail-row-on').length === 1, 'the matching detail row lights too') + assert.ok(queryAll(m.container, '.lc-detail .lc-chip-on').length === 1, 'the matching detail chip lights too') await unhover(toolsRow) assert.equal(query(m.container, '.lc-chart').hasAttribute('data-catdim'), false) @@ -156,6 +156,20 @@ describe('ContextView — interactions', () => { await m.unmount() }) + test('trend panel chip hover feeds the shared category state back to the composition legend', async () => { + const m = await mountRich('sv-link-chip') + // The panel's chips are hover SOURCES, exactly as the composition legend's are; "tools" is the second chip. + const chip = queryAll(m.container, '.lc-detail .lc-legend .lc-chip')[1] + await hover(chip) + assert.equal(query(m.container, '.lc-chart').getAttribute('data-catdim'), 'tools') + const comp = queryAll(m.container, '.lc-card').find(c => text(c).includes(DICT_EN['overview.title'])) + assert.ok(comp !== undefined) + assert.equal(queryAll(comp, '.lc-legend .lc-chip-on').length, 1, 'the composition legend chip lights with it') + await unhover(chip) + assert.equal(query(m.container, '.lc-chart').hasAttribute('data-catdim'), false) + await m.unmount() + }) + test('expanding a browser category focuses the trend bars on it; collapsing restores all', async () => { const m = await mountRich('sv-focus') assert.equal(text(query(m.container, '.lc-axis-top')), '420', 'unfocused: the axis tops at the largest whole-bar total') diff --git a/tests/client/components/contextView.splitDetail.spec.ts b/tests/client/components/contextView.splitDetail.spec.ts index fe38a16..2b8ed23 100644 --- a/tests/client/components/contextView.splitDetail.spec.ts +++ b/tests/client/components/contextView.splitDetail.spec.ts @@ -71,8 +71,11 @@ describe('ContextView — the split generation (slim head + detail channel)', () // Pending: the trend card stands in the same boxes the loaded panel uses, so its arrival changes no card's height. assert.equal(queryAll(m.container, '.lc-trend-lane').length, 1, 'the reserved body renders while the read is in flight') - assert.equal(queryAll(m.container, '.lc-trend-lane .lc-detail-rows .lc-detail-row').length, 7, - 'the lane reserves one row per category') + assert.equal(queryAll(m.container, '.lc-trend-lane .lc-legend .lc-chip').length, 7, + 'the lane reserves one chip per category') + const laneChip = query(m.container, '.lc-trend-lane .lc-legend .lc-chip') + assert.deepEqual(Array.from(laneChip.children).map(el => el.className.split(' ')[0]), ['', 'lc-chip-label', 'lc-chip-nums'], + 'the reserved chips wear the legend chip anatomy, so the panels measure alike') assert.equal(queryAll(m.container, '.lc-trend-lane .lc-detail-metrics').length, 0, 'no fabricated figures in the reserved panel') diff --git a/tests/client/components/requestDetail.spec.ts b/tests/client/components/requestDetail.spec.ts index c3ab918..bf97426 100644 --- a/tests/client/components/requestDetail.spec.ts +++ b/tests/client/components/requestDetail.spec.ts @@ -2,14 +2,14 @@ import { createElement as h } from 'react' import assert from 'node:assert/strict' import { describe, test } from 'vitest' import { makeRequestDetail } from '../../../src/client/components/requestDetail' -import { makeStackedBar } from '../../../src/client/components/stackedBar' +import { makeStackedBar, makeLegend } from '../../../src/client/components/stackedBar' import type { RequestRecord, ContextEventRecord, SurfaceNode } from '../../../src/shared/types' import type { ConversationNodeLike } from '../../../src/client/services' import type { StepBrief } from '../../../src/client/brief' import { makeKit, mount, click, queryAll, query, surfaceNode, text } from '../helpers/kit' const kit = makeKit() -const RequestDetail = makeRequestDetail(kit, makeStackedBar(kit)) +const RequestDetail = makeRequestDetail(kit, makeStackedBar(kit), makeLegend(kit)) function req(over: Partial): RequestRecord { return { @@ -23,12 +23,11 @@ function req(over: Partial): RequestRecord { /** Full-category fixture so every row renders a non-zero figure. */ const FULL = { system: 100, tools: 200, user: 300, inject: 50, assistant: 400, tool: 250, total: 1300 } -/** A detail row's anatomy — dot, label, figure, share — by each child's base class (a delta row's figure adds a - * sign modifier). Pinned structurally, so reintroducing a per-row mini bar fails here in either mode, instead of - * passing an absence check on a deleted class. */ -const ROW_ANATOMY = ['', 'lc-detail-label', 'lc-detail-num', 'lc-detail-pct'] +/** A category chip's anatomy — dot, label, figures — by each child's base class (a delta chip's figures add a + * sign modifier). Pinned structurally: a child added, removed or reordered fails here in either mode. */ +const CHIP_ANATOMY = ['', 'lc-chip-label', 'lc-chip-nums'] const anatomy = (container: HTMLElement): string[][] => - queryAll(container, '.lc-detail-row').map(r => Array.from(r.children).map(el => el.className.split(' ')[0])) + queryAll(container, '.lc-legend .lc-chip').map(c => Array.from(c.children).map(el => el.className.split(' ')[0])) describe('RequestDetail header', () => { test('null request renders nothing', async () => { @@ -37,7 +36,7 @@ describe('RequestDetail header', () => { await m.unmount() }) - test('step detail: title, time, usage chips, per-category rows', async () => { + test('step detail: title, time, usage chips, per-category chips', async () => { const m = await mount(h(RequestDetail, { request: req({ prompt: 1000, output: 120, cacheRead: 500 }), })) @@ -52,13 +51,14 @@ describe('RequestDetail header', () => { const ms = await mount(h(RequestDetail, { request: req({}), stepsOf: (turn) => (turn === 1 ? 7 : 1) })) assert.ok(text(query(ms.container, '.lc-detail-head')).includes('Turn 1 · Step 1 of 7')) await ms.unmount() - const rows = queryAll(m.container, '.lc-detail-row') - assert.equal(rows.length, 7) - assert.ok(text(rows[0]).includes('System Prompt')) - assert.ok(text(rows[0]).includes('≈100')) - const pcts = queryAll(m.container, '.lc-detail-pct').map(el => text(el)) + const chips = queryAll(m.container, '.lc-legend .lc-chip') + assert.equal(chips.length, 7) + assert.ok(text(chips[0]).includes('System Prompt')) + assert.ok(text(chips[0]).includes('≈100')) + const pcts = queryAll(m.container, '.lc-chip-nums em').map(el => text(el)) assert.ok(pcts.some(p => p.endsWith('%')), 'percentages rendered when total > 0') - assert.deepEqual(anatomy(m.container), Array.from({ length: 7 }, () => ROW_ANATOMY)) + assert.equal(pcts[0], '8%', 'the share rides the used total: 100 of 1300') + assert.deepEqual(anatomy(m.container), Array.from({ length: 7 }, () => CHIP_ANATOMY)) await m.unmount() }) @@ -144,7 +144,7 @@ describe('RequestDetail header', () => { }) describe('RequestDetail delta mode', () => { - test('signed deltas vs the previous record: net chip, signed rows, no usage chips', async () => { + test('signed deltas vs the previous record: net chip, signed chips, no usage chips', async () => { const request = req({ system: 150, tools: 200, user: 250, inject: 0, assistant: 300, tool: 350, total: 1250, prompt: 900 }) const prev = req({ ...FULL, inject: 0 }) const m = await mount(h(RequestDetail, { request, prev })) @@ -156,14 +156,14 @@ describe('RequestDetail delta mode', () => { assert.ok(text(metric).includes('Δ 0')) assert.ok(!metric.className.includes('lc-detail-metric-up')) assert.ok(!metric.className.includes('lc-detail-metric-down')) - const nums = queryAll(m.container, '.lc-detail-num') - assert.ok(text(nums[0]).includes('+50')) - assert.ok(nums[0].className.includes('lc-detail-num-up')) - assert.ok(text(nums[2]).includes('-50')) - assert.ok(nums[2].className.includes('lc-detail-num-down')) - assert.ok(!nums[1].className.includes('lc-detail-num-up'), 'zero delta is neutral') - assert.ok(queryAll(m.container, '.lc-detail-pct').every(el => text(el) === ''), 'no percentages in delta mode') - assert.deepEqual(anatomy(m.container), Array.from({ length: 7 }, () => ROW_ANATOMY), 'delta rows carry no diverging bar') + const figs = queryAll(m.container, '.lc-chip-nums') + assert.ok(text(figs[0]).includes('+50')) + assert.ok(figs[0].className.includes('lc-chip-nums-up')) + assert.ok(text(figs[2]).includes('-50')) + assert.ok(figs[2].className.includes('lc-chip-nums-down')) + assert.ok(!figs[1].className.includes('lc-chip-nums-up'), 'zero delta is neutral') + assert.equal(queryAll(m.container, '.lc-chip-nums em').length, 0, 'a signed change carries no share') + assert.deepEqual(anatomy(m.container), Array.from({ length: 7 }, () => CHIP_ANATOMY), 'delta chips carry no diverging bar') await m.unmount() }) @@ -172,8 +172,8 @@ describe('RequestDetail delta mode', () => { const metric = query(m.container, '.lc-detail-metric') assert.ok(metric.className.includes('lc-detail-metric-up')) assert.ok(text(metric).includes('Δ +1.3k')) - assert.equal(queryAll(m.container, '.lc-detail-num-down').length, 0) - assert.equal(queryAll(m.container, '.lc-detail-num-up').length, 6, 'every non-zero category reads up') + assert.equal(queryAll(m.container, '.lc-chip-nums-down').length, 0) + assert.equal(queryAll(m.container, '.lc-chip-nums-up').length, 6, 'every non-zero category reads up') await m.unmount() const m2 = await mount(h(RequestDetail, { @@ -183,25 +183,25 @@ describe('RequestDetail delta mode', () => { const metric2 = query(m2.container, '.lc-detail-metric') assert.ok(metric2.className.includes('lc-detail-metric-down')) assert.ok(text(metric2).includes('Δ -1.3k')) - assert.equal(queryAll(m2.container, '.lc-detail-num-up').length, 0) - assert.equal(queryAll(m2.container, '.lc-detail-num-down').length, 6) + assert.equal(queryAll(m2.container, '.lc-chip-nums-up').length, 0) + assert.equal(queryAll(m2.container, '.lc-chip-nums-down').length, 6) await m2.unmount() }) - test('cumulative rows with a zero total render zero figures and no percentages', async () => { + test('cumulative chips with a zero total render zero figures and no shares', async () => { const m = await mount(h(RequestDetail, { request: req({ system: 0, tools: 0, user: 0, inject: 0, assistant: 0, tool: 0, total: 0 }), })) - const figs = queryAll(m.container, '.lc-detail-num').map(el => text(el)) + const figs = queryAll(m.container, '.lc-chip-nums').map(el => text(el)) assert.equal(figs.length, 7) assert.ok(figs.every(f => f === '≈0')) - assert.ok(queryAll(m.container, '.lc-detail-pct').every(el => text(el) === '')) + assert.equal(queryAll(m.container, '.lc-chip-nums em').length, 0) await m.unmount() }) }) describe('RequestDetail composition bar hover-link', () => { - test('the mirrored hoverKey dims the bar, lights the matching segment and detail row; the tip stays off', async () => { + test('the mirrored hoverKey dims the bar, lights the matching segment and chip; the tip stays off', async () => { const m = await mount(h(RequestDetail, { request: req({}), hoverKey: 'tools' })) const bar = query(m.container, '.lc-stacked') assert.ok(bar.className.includes('lc-stacked-dim'), 'a mirrored hover dims the bar') @@ -209,24 +209,24 @@ describe('RequestDetail composition bar hover-link', () => { assert.equal(segs.length, 6, 'all six categories render') assert.ok(segs[1].className.includes('lc-stacked-seg-on'), 'exactly the tools segment (CATS order) lights') assert.ok(segs.filter(s => s.className.includes('lc-stacked-seg-on')).length === 1) - const rows = queryAll(m.container, '.lc-detail-row') - assert.ok(rows[1].className.includes('lc-detail-row-on'), 'the tools row lights too') - assert.ok(rows.filter(r => r.className.includes('lc-detail-row-on')).length === 1) + const chips = queryAll(m.container, '.lc-legend .lc-chip') + assert.ok(chips[1].className.includes('lc-chip-on'), 'the tools chip lights too') + assert.ok(chips.filter(c => c.className.includes('lc-chip-on')).length === 1) assert.equal(queryAll(m.container, '.lc-bar-tip').length, 0, 'a mirrored hover floats no tooltip') const m2 = await mount(h(RequestDetail, { request: req({}) })) assert.equal(query(m2.container, '.lc-stacked').className, 'lc-stacked') - assert.equal(queryAll(m2.container, '.lc-detail-row-on').length, 0) + assert.equal(queryAll(m2.container, '.lc-chip-on').length, 0) assert.equal(queryAll(m2.container, '.lc-bar-tip').length, 0) await m2.unmount() await m.unmount() }) - test('delta-mode detail rows light the matching category too', async () => { + test('delta-mode detail chips light the matching category too', async () => { const m = await mount(h(RequestDetail, { request: req({}), prev: req({ ...FULL }), hoverKey: 'user' })) - const rows = queryAll(m.container, '.lc-detail-row') - assert.ok(rows[2].className.includes('lc-detail-row-on'), 'the user row lights in delta mode') - assert.ok(rows.filter(r => r.className.includes('lc-detail-row-on')).length === 1) + const chips = queryAll(m.container, '.lc-legend .lc-chip') + assert.ok(chips[2].className.includes('lc-chip-on'), 'the user chip lights in delta mode') + assert.ok(chips.filter(c => c.className.includes('lc-chip-on')).length === 1) await m.unmount() }) })