feat(browser): link the DNA toggle with the trend card — one switch, both cards
The DNA on/off state lifts no further than it already sat: the Context tab's `dna` state now feeds the Context browser through a controlled pair of props (dna + onDnaChange), so toggling either card's switch moves the other — the trend bars and the browser's composition bar become fingerprints together, and drop back together. The browser stays self-contained where no parent links it: the pair is controlled only when BOTH props arrive, a lone `dna` prop is ignored, and the /context modal keeps its mount-local toggle (the established optional-seam idiom).
This commit is contained in:
1 parent
8f2fcf7903
commit
dc11a0bb9c
4 files changed
+69
-9
No files matched your search
@@ -47,6 +47,13 @@ export interface ContextBrowserProps {
|
||||
previewSeq?: number | null
|
||||
/** Pin-seq: a pin selects that step; pinSeq null returns the browser to the live surface. */
|
||||
pinSeq?: number | null
|
||||
/**
|
||||
* DNA mode's on/off, LINKED across cards: the Context tab passes its trend card's toggle state
|
||||
* so both DNA switches move as one. Controlled only when BOTH props arrive; absent (the
|
||||
* /context modal) — the browser keeps its own mount-local toggle.
|
||||
*/
|
||||
dna?: boolean
|
||||
onDnaChange?: (on: boolean) => void
|
||||
/**
|
||||
* One-shot reveal request (the step brief's locate, a trend-DNA band pick): select the step, open the
|
||||
* item's category and element, scroll it into view. `key` is the band key both DNA surfaces share —
|
||||
@@ -681,8 +688,20 @@ export function makeContextBrowser(
|
||||
// Mount-time default from the plugin settings card; in-toolbar toggling
|
||||
// stays mount-local and never writes back.
|
||||
const [toolSort, setToolSort] = useState<DefaultToolSort>(() => settings.defaultToolSort())
|
||||
// DNA mode: the composition bar redraws as ONE band per context item in prompt order (dna.ts), hovered/clicked per item.
|
||||
const [dna, setDna] = useState(false)
|
||||
// DNA mode: the composition bar redraws as ONE band per context item in prompt order (dna.ts), hovered/clicked per
|
||||
// item. Parent-linked when both props arrive — the Context tab's trend toggle moves this one too; a lone
|
||||
// prop is ignored — otherwise mount-local (the /context modal).
|
||||
const [dnaLocal, setDnaLocal] = useState(false)
|
||||
const dnaLinked = props.dna !== undefined && props.onDnaChange !== undefined
|
||||
const dna = dnaLinked ? props.dna === true : dnaLocal
|
||||
const setDna = (on: boolean): void => {
|
||||
if (dnaLinked) {
|
||||
/* v8 ignore next 1 -- `dnaLinked` requires both props, so the handler is always present. */
|
||||
props.onDnaChange?.(on)
|
||||
} else {
|
||||
setDnaLocal(on)
|
||||
}
|
||||
}
|
||||
const [dnaKey, setDnaKey] = useState<string | null>(null)
|
||||
// δ baseline toggle: 'step' diffs against the immediately preceding record, 'turn' against the
|
||||
// previous turn's last step. Mount default from the plugin settings card; in-toolbar toggling
|
||||
@@ -1229,7 +1248,7 @@ export function makeContextBrowser(
|
||||
<button
|
||||
type="button"
|
||||
className={'lc-gran-btn' + (dna ? ' lc-gran-on' : '')}
|
||||
onClick={() => { setDna(on => !on) }}
|
||||
onClick={() => { setDna(!dna) }}
|
||||
>
|
||||
{t('browser.dna')}
|
||||
</button>
|
||||
|
||||
@@ -111,8 +111,9 @@ export function makeContextView(
|
||||
// like granularity, the default is read at mount and in-chart toggling never writes back.
|
||||
const [trendMode, setTrendMode] = useState<'total' | 'delta'>(() => settings.defaultTrendMode())
|
||||
// DNA mode: the trend bars become per-item fingerprints of each request's context (dna.ts); like the
|
||||
// toggles above, mount-local and never written back. Orthogonal to the Total/Delta switch — delta
|
||||
// diffs the bands against the previous bar, so the two combine into a per-item change view.
|
||||
// toggles above, mount-local and never written back — and SHARED with the Context browser, whose DNA
|
||||
// toggle rides this same state so both switches move as one. Orthogonal to the Total/Delta switch —
|
||||
// delta diffs the bands against the previous bar, so the two combine into a per-item change view.
|
||||
const [dna, setDna] = useState(false)
|
||||
// Adaptive scale (the title-adjacent toggle): the trend bars rescale to the visible window; like the two
|
||||
// toggles above, mount-local and never written back.
|
||||
@@ -566,6 +567,9 @@ export function makeContextView(
|
||||
loadImage={loadImage}
|
||||
detailState={source.detailState}
|
||||
onDetailRetry={source.retryDetail}
|
||||
// The DNA switch is shared with the trend card: both toggles move as one.
|
||||
dna={dna}
|
||||
onDnaChange={setDna}
|
||||
/>
|
||||
)
|
||||
|
||||
|
||||
@@ -1987,8 +1987,27 @@ describe('ContextBrowser DNA mode and the open-category bar pin', () => {
|
||||
return queryAll(m.container, '.lc-stacked-seg')
|
||||
}
|
||||
|
||||
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 })))
|
||||
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)
|
||||
|
||||
@@ -532,8 +532,26 @@ describe('ContextView — interactions', () => {
|
||||
await m.unmount()
|
||||
})
|
||||
|
||||
test('clicking a DNA band reveals that item in the Context browser', async () => {
|
||||
const m = await mountRich('sv-dna-reveal')
|
||||
test('the trend and browser DNA toggles move as one', async () => {
|
||||
const m = await mountRich('sv-dna-link')
|
||||
const trendDna = () => query(m.container, '.lc-trend-dna .lc-gran-btn')
|
||||
const browserDna = () => query(m.container, '.lc-br-dna-ctl .lc-gran-btn')
|
||||
assert.ok(!trendDna().className.includes('lc-gran-on') && !browserDna().className.includes('lc-gran-on'), 'both off at mount')
|
||||
|
||||
// The browser follows the trend toggle — its composition bar redraws as a fingerprint too.
|
||||
await click(trendDna())
|
||||
assert.ok(browserDna().className.includes('lc-gran-on'), 'the browser follows the trend toggle')
|
||||
assert.equal(queryAll(m.container, '.lc-br-bar-dna').length, 1)
|
||||
|
||||
// And the trend follows the browser toggle — the chart's strips drop away.
|
||||
await click(browserDna())
|
||||
assert.ok(!trendDna().className.includes('lc-gran-on'), 'the trend follows the browser toggle')
|
||||
assert.equal(queryAll(m.container, '.lc-bar-dna').length, 0)
|
||||
assert.equal(queryAll(m.container, '.lc-br-bar-dna').length, 0)
|
||||
await m.unmount()
|
||||
})
|
||||
|
||||
test('clicking a DNA band reveals that item in the Context browser', async () => { const m = await mountRich('sv-dna-reveal')
|
||||
await click(buttonByText(m.container, DICT_EN['trend.dna']))
|
||||
// Give the strip a real 112px stack area, then click its very top: the hit is the LAST band of
|
||||
// bar seq 4 — the 'file output' tool result (system and the header lead the strip below it).
|
||||
|
||||
Reference in new issue
Block a user