Files
dsh-context/tests/client/components/browserDna.spec.ts
T
bowenliang123 1358603759 feat(client): give the composition bar tooltip three named rows
The bar's tooltip was one nowrap string per hover kind, the only tooltip in
the plugin not yet multi-line. Split it into head / figure / note so all four
kinds share one skeleton and wrap:

  Tool Schemas            ≈6.4k · 3% of used context
  Free Window             733.2k · 73% of window
  Compaction headroom     65.5k
                           Auto-compaction fires at 68% of the window
  Max output tokens       256.0k
                           This reply's output is reserved at the window's end

Every figure now names its denominator. The segments share the USED total and
the free track the WINDOW, which the old single line never said; the two masks
had no share at all and now state where they sit in the window instead, read
off the same clamped trackPct the masks lay out with rather than a second ratio.

Replaces the hardcoded 12/88 left clamp — a guess at half the tooltip width that
only held around a 920px pane, and cut the bubble off at narrower ones. The
percentage anchor stays the source of truth; a layout effect measures the tip and
adds a px shift only when the rows overhang. Rects, not clientWidth, whose whole-
pixel truncation lands the residue straight back on the overhang.

Also adds role="tooltip", and box-sizing so the width cap means what it says.
2026-10-13 16:15:14 +08:00

286 lines
16 KiB
TypeScript

import { createElement as h, useState } from 'react'
import assert from 'node:assert/strict'
import { describe, test } from 'vitest'
import type { ContextBrowserProps } from '../../../src/client/components/browser'
import type { ContextHeaders } from '../../../src/shared/types'
import { click, hover, mount, query, queryAll, surfaceNode, text, tipRows, unhover, type Mounted } from '../helpers/kit'
import { Browser, catRow, elemRows, kit, props, req, tl, typeToolSearch } from './browserHarness'
describe('ContextBrowser DNA mode and the open-category bar pin', () => {
const dnaHeaders: ContextHeaders = {
headers: [{
seq: 1, time: 1000, systemTokens: 100,
tools: [{ name: 'bash', tokens: 50 }, { name: 'write', tokens: 25 }],
}],
}
const dnaData = tl({
current: { system: 100, tools: 75, user: 20, inject: 10, skill: 0, assistant: 40, tool: 30, total: 275 },
requests: [req({ seq: 20, turn: 1, step: 0, system: 100, tools: 75, user: 20, inject: 0, assistant: 40, tool: 30, total: 265 })],
nodes: [
surfaceNode({ seq: 2, tokens: 20, time: 2000, text: 'hi' }),
surfaceNode({ seq: 3, cat: 'tool', tokens: 30, time: 3000, tool: 'bash' }),
surfaceNode({ seq: 4, cat: 'assistant', tokens: 40 }),
surfaceNode({ seq: 5, cat: 'inject', tokens: 10, form: 'notice' }),
],
})
function dnaButton(m: Mounted): HTMLElement {
return query(m.container, '.lc-br-dna-ctl .lc-gran-btn')
}
function bands(m: Mounted): HTMLElement[] {
return queryAll(m.container, '.lc-stacked-seg')
}
test('the DNA toggle is parent-linked when both props arrive; a lone prop is ignored', async () => {
// Linked on: the bar redraws and the button reflects the parent's state without a click.
const m = await mount(h(Browser, props({ data: dnaData, headers: dnaHeaders, dna: true, onDnaChange: () => {} })))
assert.equal(bands(m).length, 7)
assert.ok(dnaButton(m).className.includes('lc-gran-on'))
// A lone `dna` prop without the handler does NOT link: the mount-local toggle still rules.
await m.update(h(Browser, props({ data: dnaData, headers: dnaHeaders, dna: true })))
assert.equal(bands(m).length, 6, 'unlinked: the local toggle is off')
// The click reports to the parent instead of flipping local state; the bar follows the prop.
const calls: boolean[] = []
await m.update(h(Browser, props({
data: dnaData, headers: dnaHeaders, dna: true,
onDnaChange: (on) => { calls.push(on) },
})))
await click(dnaButton(m))
assert.deepEqual(calls, [false])
assert.equal(bands(m).length, 7, 'still on until the parent re-renders the prop')
await m.unmount()
})
test('the toggle redraws the composition bar as one band per item, in prompt order', async () => { const m = await mount(h(Browser, props({ data: dnaData, headers: dnaHeaders })))
// Category mode: one segment per category in CATS order, pick cursor off, tooltip slot unmounted.
assert.equal(bands(m).length, 6)
const normal = bands(m).map(seg => seg.style.width)
assert.deepEqual(normal, [100, 75, 20, 10, 40, 30].map(v => `${v / 275 * 100}%`))
assert.ok(!bands(m)[0].className.includes('lc-stacked-seg-pick'))
assert.ok(!query(m.container, '.lc-br-bar').className.includes('lc-br-bar-dna'))
assert.equal(queryAll(m.container, '.lc-bar-tip').length, 0)
const btn = dnaButton(m)
assert.equal(text(btn), 'DNA Mode')
assert.equal(query(m.container, '.lc-br-dna-ctl').getAttribute('title'), kit.t('browser.dnaTip'))
await click(btn)
// Prompt order: the system prompt, the two tool schemas in producer order, then the message flow by seq —
// NOT the fold's category order and NOT the header event's bookkeeping seq (see dna.ts).
const segs = bands(m)
assert.equal(segs.length, 7)
assert.deepEqual(segs.map(seg => seg.style.width), [100, 50, 25, 20, 30, 40, 10].map(v => `${v / 275 * 100}%`))
assert.ok(segs.every(seg => seg.className.includes('lc-stacked-seg-pick')))
assert.ok(query(m.container, '.lc-br-bar').className.includes('lc-br-bar-dna'))
assert.equal(queryAll(m.container, '.lc-bar-tip').length, 1, 'the per-item tooltip slot mounts')
assert.ok(btn.className.includes('lc-gran-on'))
await click(btn)
assert.equal(bands(m).length, 6, 'toggling off restores the category composition')
assert.ok(!btn.className.includes('lc-gran-on'))
await m.unmount()
})
test('band hover floats the per-item tooltip locally; a mirrored category key lights that category\'s bands', async () => {
const hovers: (string | null)[] = []
const wired = (hoverKey: string | null) => h(Browser, props({ data: dnaData, headers: dnaHeaders, hoverKey, onHoverKey: (k) => { hovers.push(k) } }))
const m = await mount(wired(null))
await click(dnaButton(m))
// A band hover is LOCAL: the tooltip names the item (category label + join time); nothing reports upward.
await hover(bands(m)[3])
const tip = query(m.container, '.lc-bar-tip')
assert.ok(tip.className.includes('lc-bar-tip-on'))
assert.deepEqual(tipRows(tip), [`User Messages · ${kit.fmtTime(2000)}`, '≈20 · 7% of used context', null])
assert.deepEqual(hovers, [], 'DNA hovers stay on the bar — the overview keeps showing the live category composition')
assert.ok(bands(m)[3].className.includes('lc-stacked-seg-on'))
assert.ok(query(m.container, '.lc-stacked').className.includes('lc-stacked-dim'))
// A node without a logged time shows the bare label.
await hover(bands(m)[5])
assert.deepEqual(tipRows(tip), ['Assistant Messages', '≈40 · 15% of used context', null])
await unhover(query(m.container, '.lc-stacked'))
assert.ok(!tip.className.includes('lc-bar-tip-on'))
// The incoming category mirror still works: the open category's bands light as a group (no tooltip for a mirrored hover).
await m.update(wired('tool'))
assert.deepEqual(bands(m).map(seg => seg.className.includes('lc-stacked-seg-on')), [false, false, false, false, true, false, false])
assert.ok(!query(m.container, '.lc-bar-tip').className.includes('lc-bar-tip-on'))
await m.update(wired('free'))
assert.ok(bands(m).every(seg => !seg.className.includes('lc-stacked-seg-on')), "the overview's free key lights nothing here")
await m.unmount()
})
test('band labels: skill tag, nameless tool result, formless injection; a header-less surface shows message bands only', async () => {
const data = tl({
current: { system: 0, tools: 0, user: 0, inject: 40, skill: 0, assistant: 0, tool: 20, total: 60 },
nodes: [
surfaceNode({ seq: 1, cat: 'inject', tokens: 10, skill: 'code' }),
surfaceNode({ seq: 2, cat: 'tool', tokens: 20 }),
surfaceNode({ seq: 3, cat: 'inject', tokens: 30 }),
],
})
const m = await mount(h(Browser, props({ data, headers: null })))
await click(dnaButton(m))
assert.equal(bands(m).length, 3)
const tip = query(m.container, '.lc-bar-tip')
await hover(bands(m)[0])
assert.deepEqual(tipRows(tip), ['Skill · code', '≈10 · 17% of used context', null])
await hover(bands(m)[1])
assert.deepEqual(tipRows(tip), ['?', '≈20 · 33% of used context', null])
await hover(bands(m)[2])
assert.deepEqual(tipRows(tip), ['Context Injection', '≈30 · 50% of used context', null])
await m.unmount()
})
test('clicking a band opens its category + element row below and scrolls it into view; a stale row filter is cleared', async () => {
const scrolls: unknown[] = []
const orig = Element.prototype.scrollIntoView
Element.prototype.scrollIntoView = function (this: Element, arg?: unknown) { scrolls.push(arg) } as never
try {
const m = await mount(h(Browser, props({ data: dnaData, headers: dnaHeaders })))
await click(dnaButton(m))
// Band n3 (index 4) opens the tool-results category on that exact row.
await click(bands(m)[4])
assert.equal(queryAll(m.container, '.lc-br-body').length, 1)
const onRows = queryAll(m.container, '.lc-br-elem-on')
assert.equal(onRows.length, 1)
assert.ok(text(onRows[0]).includes('bash'))
assert.deepEqual(scrolls, [{ block: 'nearest' }])
// A stale row filter cannot hide the clicked band's row.
await click(catRow(m, 'user'))
await typeToolSearch(m, 'zzz')
assert.ok(text(query(m.container, '.lc-br-body')).includes('No rows match'))
await click(bands(m)[3])
assert.equal(query<HTMLInputElement>(m.container, '.lc-br-tool-search').value, '')
assert.equal(queryAll(m.container, '.lc-br-elem-on').length, 1)
assert.ok(text(query(m.container, '.lc-br-body')).includes('hi'))
// A stale kind chip clears the same way — even when the band reopens the SAME category (zero counts render too).
await click(catRow(m, 'assistant'))
const toolsChip = queryAll(m.container, '.lc-br-toolctl .lc-gran-btn')[1]
assert.equal(text(toolsChip), 'Tools0')
await click(toolsChip)
assert.ok(text(query(m.container, '.lc-br-body')).includes('No rows match'))
await click(bands(m)[5])
const kindChips = queryAll(m.container, '.lc-br-toolctl .lc-gran-btn')
assert.equal(kindChips.length, 3)
assert.ok(kindChips.every(b => !b.className.includes('lc-gran-on')), 'the band click clears a stale kind chip')
assert.equal(elemRows(m).length, 1)
assert.equal(queryAll(m.container, '.lc-br-elem-on').length, 1)
// The system band opens the system section (metadata-only note without a header fetcher).
await click(bands(m)[0])
assert.ok(text(query(m.container, '.lc-br-body')).includes('token estimates only'))
await m.unmount()
} finally {
Element.prototype.scrollIntoView = orig
}
})
test('a tiny item stays a hoverable filament (the minBand floor) while the tooltip reports the true share', async () => {
const data = tl({
current: { system: 0, tools: 0, user: 2, inject: 0, skill: 0, assistant: 998, tool: 0, total: 1000 },
nodes: [surfaceNode({ seq: 1, tokens: 2, text: 'ok' }), surfaceNode({ seq: 2, cat: 'assistant', tokens: 998 })],
})
const m = await mount(h(Browser, props({ data, headers: null })))
await click(dnaButton(m))
const segs = bands(m)
assert.equal(segs.length, 2)
// 2/1000 ≈ 0.2% would render sub-pixel: the band pins to the 0.35% floor, the big band rescales.
assert.equal(segs[0].style.width, '0.35%')
assert.ok(Math.abs(parseFloat(segs[1].style.width) - 99.65) < 0.001)
await hover(segs[0])
assert.deepEqual(tipRows(query(m.container, '.lc-bar-tip')), ['User Messages', '≈2 · 0% of used context', null])
// Category mode keeps exact widths (no floor outside DNA mode).
await click(dnaButton(m))
assert.deepEqual(bands(m).map(seg => seg.style.width), ['0.2%', '99.8%'])
await m.unmount()
})
test('category mode: the open category stays lit; a pointer hover overrides the pin and the pin resumes on leave', async () => {
// A parent really holding the shared hover key, so hovers round-trip like the overview link.
const Harness = (p2: Omit<ContextBrowserProps, 'hoverKey' | 'onHoverKey'>) => {
const [hoverKey, setHoverKey] = useState<string | null>(null)
return h(Browser, props({ ...p2, hoverKey, onHoverKey: setHoverKey }))
}
const m = await mount(h(Harness, { data: dnaData, headers: dnaHeaders }))
const stack = query(m.container, '.lc-stacked')
assert.ok(!stack.className.includes('lc-stacked-dim'), 'nothing pinned while no category is open')
// Opening a category pins its segment lit (category order: system, tools, user, inject, assistant, tool).
await click(catRow(m, 'assistant'))
assert.deepEqual(bands(m).map(seg => seg.className.includes('lc-stacked-seg-on')), [false, false, false, false, true, false])
assert.ok(stack.className.includes('lc-stacked-dim'))
assert.equal(queryAll(m.container, '.lc-bar-tip').length, 0, 'category mode mounts no tooltip slot — the pin floats nothing')
await hover(bands(m)[0])
assert.deepEqual(bands(m).map(seg => seg.className.includes('lc-stacked-seg-on')), [true, false, false, false, false, false])
await unhover(stack)
assert.deepEqual(bands(m).map(seg => seg.className.includes('lc-stacked-seg-on')), [false, false, false, false, true, false])
await click(catRow(m, 'assistant'))
assert.ok(!stack.className.includes('lc-stacked-dim'))
assert.ok(bands(m).every(seg => !seg.className.includes('lc-stacked-seg-on')))
await m.unmount()
})
test('DNA mode: the open category lights all its bands as a group (no tooltip); a band hover overrides, the pin resumes', async () => {
// No shared-hover wiring (the /context modal path): the pin works standalone.
const m = await mount(h(Browser, props({ data: dnaData, headers: dnaHeaders })))
await click(dnaButton(m))
await click(catRow(m, 'tools'))
const stack = query(m.container, '.lc-stacked')
// Band order: sys, bash, write, n2, n3, n4, n5 — the tools group is bands 1-2.
assert.deepEqual(bands(m).map(seg => seg.className.includes('lc-stacked-seg-on')), [false, true, true, false, false, false, false])
assert.ok(stack.className.includes('lc-stacked-dim'))
assert.ok(!query(m.container, '.lc-bar-tip').className.includes('lc-bar-tip-on'), 'the group pin exact-matches no band — no tooltip')
await hover(bands(m)[4])
assert.ok(query(m.container, '.lc-bar-tip').className.includes('lc-bar-tip-on'))
assert.deepEqual(bands(m).map(seg => seg.className.includes('lc-stacked-seg-on')), [false, false, false, false, true, false, false])
await unhover(stack)
assert.deepEqual(bands(m).map(seg => seg.className.includes('lc-stacked-seg-on')), [false, true, true, false, false, false, false])
await click(catRow(m, 'tools'))
assert.ok(bands(m).every(seg => !seg.className.includes('lc-stacked-seg-on')))
assert.ok(!stack.className.includes('lc-stacked-dim'))
await m.unmount()
})
test('a push dropping the hovered band does not dim the bar with nothing lit; the pin resumes underneath', async () => {
const m = await mount(h(Browser, props({ data: dnaData, headers: dnaHeaders })))
await click(dnaButton(m))
await hover(bands(m)[3])
assert.ok(bands(m)[3].className.includes('lc-stacked-seg-on'))
assert.ok(query(m.container, '.lc-stacked').className.includes('lc-stacked-dim'))
// The hovered node is compacted out of the live surface while the pointer rests on its band — a removed element
// fires no mouseleave, so the stale key must be dropped instead of dimming the whole bar with nothing lit.
const pushed = tl({
current: { ...dnaData.current, user: 0, total: 255 },
requests: dnaData.requests,
nodes: dnaData.nodes.filter(n => n.seq !== 2),
})
await m.update(h(Browser, props({ data: pushed, headers: dnaHeaders })))
assert.ok(!query(m.container, '.lc-stacked').className.includes('lc-stacked-dim'))
assert.ok(bands(m).every(seg => !seg.className.includes('lc-stacked-seg-on')))
assert.ok(!query(m.container, '.lc-bar-tip').className.includes('lc-bar-tip-on'), 'the dead key floats no tooltip either')
// Band order is now sys, bash, write, n3, n4, n5: the open-category pin still lights its group under the stale hover.
await click(catRow(m, 'assistant'))
assert.deepEqual(bands(m).map(seg => seg.className.includes('lc-stacked-seg-on')), [false, false, false, false, true, false])
await hover(bands(m)[3])
assert.deepEqual(bands(m).map(seg => seg.className.includes('lc-stacked-seg-on')), [false, false, false, true, false, false])
await m.unmount()
})
test('the pin drops while the previewed step holds nothing for the open category', async () => {
const wired = (previewSeq: number | null) => h(Browser, props({ data: dnaData, headers: dnaHeaders, previewSeq, onHoverKey: () => {} }))
const m = await mount(wired(null))
await click(catRow(m, 'inject'))
assert.ok(bands(m)[3].className.includes('lc-stacked-seg-on'), 'inject pins lit on the live surface')
// The retained step carries no injections: the pin drops instead of dimming a bar with nothing lit.
await m.update(wired(20))
assert.ok(bands(m).every(seg => !seg.className.includes('lc-stacked-seg-on')))
assert.ok(!query(m.container, '.lc-stacked').className.includes('lc-stacked-dim'))
await m.update(wired(null))
assert.ok(bands(m)[3].className.includes('lc-stacked-seg-on'), 'back on the live surface the pin returns')
await m.unmount()
})
})