fix(client): guard the DNA bar against a stale hover key

A snapshot push can drop the band under a resting pointer (compaction,
tail slide out of the served window) without a mouseleave, and the dead
key exact-matched nothing: the whole strip sat dimmed with nothing lit
— the very state the open-category pin's breakdown gate rules out.
Honor a band hover only while its key names a rendered band, so the
mirror/pin fallbacks resume underneath.

Also correct the hover-link comment: DNA hovers stay on the bar and
never report back to the overview (the link is one-way in DNA mode,
as the existing test already pins).
This commit is contained in:
Bowen Liang committed 2026-09-08 05:25:32 +08:00
1 parent 5af019ef02
commit 40a054b99b
2 files changed
+36 -5

No files matched your search

+10 -5
View File
@@ -746,6 +746,10 @@ export function makeContextBrowser(
label: dnaLabel(it),
group: it.cat,
})) ?? null
// A band hover is honored only while its key names a RENDERED band: a push can drop the band under a resting
// pointer (compaction, tail slide) without a mouseleave, and a dead key exact-matches nothing — the bar would
// sit all-dimmed with nothing lit (the same invariant the pin's `breakdown` gate keeps).
const liveDnaKey = dnaKey !== null && dnaByKey.has(dnaKey) ? dnaKey : null
// A band click opens its category + element row below (the same reveal the step brief uses) and scrolls it into view.
const pickDna = (key: string): void => {
const it = dnaByKey.get(key)
@@ -1051,11 +1055,12 @@ export function makeContextBrowser(
parts={dnaParts ?? parts}
height={10}
// Highlight precedence: pointer hover (local in DNA mode, the shared link otherwise) over the open-category pin.
// The mirrored hover link (see `linked` above) works while the browser shows the live surface — in DNA mode an incoming
// category key lights that category's BANDS (the parts' `group`) — and reports hovers back to the overview. Tip stays off
// in category mode: a cross-card hover must not float a second tooltip over a bar the pointer does not rest on — and the
// group-keyed pin never exact-matches a DNA band, so the pinned highlight floats no tooltip either.
hoverKey={dna ? dnaKey ?? linkKey ?? pinKey : linkKey ?? pinKey}
// The mirrored link (see `linked` above) works while the browser shows the live surface; it is ONE-WAY in DNA
// mode — an incoming category key lights that category's BANDS (the parts' `group`), while band hovers stay
// on the bar and never report upward (the overview keeps showing the live category composition). Tip stays
// off in category mode: a cross-card hover must not float a second tooltip over a bar the pointer does not
// rest on — and the group-keyed pin never exact-matches a DNA band, so the pinned highlight floats no tip.
hoverKey={dna ? liveDnaKey ?? linkKey ?? pinKey : linkKey ?? pinKey}
onHoverKey={dna ? setDnaKey : linked ? props.onHoverKey : undefined}
tip={dna}
onPickKey={dna ? pickDna : undefined}
+26
View File
@@ -1754,6 +1754,32 @@ describe('ContextBrowser DNA mode and the open-category bar pin', () => {
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])
// And a fresh hover on a rendered band still wins over the pin.
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))