Files
deepseek-harness/examples/desktop/test/lane-ctx-deep-dom.test.js
ZiyaZhang fc558c2db8 feat(desktop): context page deepening — window bar + compact Config + intervention marker + subagent drilldown
Four Context-page enhancements (lane-ctx-deep, F1-F4):

- F1 context window breakdown: replace percentage-only card header with
  a stacked-bar breakdown of input / cached / output token buckets,
  plus a right-side gauge showing the live window occupancy ratio.
- F2 compact Config tab: fold the sprawling profile Config editor into
  a Config tab on the Context page card, with the same yml-leaf
  ordering as the top-of-window profile picker.
- F3 intervention marker: on the intervention timeline, emit a marker
  glyph at each user-intervention row (turn-flow-glyph-style) so the
  card scans as a single stream instead of a header + separate list.
- F4 subagent drilldown: when a turn's tool trace hits a subagent, the
  Trace panel's Config + Output tabs get a second row of Subagent
  Config / Subagent Output tabs immediately below, driven by the same
  fold-in-place shape the parent panel already uses.

4 new renderer modules (compact-config-model / context-window-breakdown /
intervention-timeline / subagent-drilldown), 6 new test files (41 tests,
all node --test style), 4 QA shoot scripts for CDP-driven regression
screenshots.
2026-07-19 01:22:28 -07:00

200 lines
7.9 KiB
JavaScript

// DOM-shape tests for the four lane-ctx-deep enhancements (task #51).
//
// These assert on the rendered element trees without booting jsdom or the
// Electron shell. Each test mocks the minimal DOM surface each builder
// touches (createElement + appendChild + attribute + dataset), running
// through the same code paths the production shell exercises.
//
// The four features covered:
// F1 — window occupancy bar: renders 5 stacked segments.
// F2 — compact-card 4th tab "Config": strip includes a `Config` button
// AND the config body is populated with threshold+progress rows.
// F3 — intervention marker strip: emits one marker per intervention.
// F4 — subagent drill-down tabs: emits `Tool defs (N)` + `Inbound query`
// strip and populated bodies.
'use strict'
const test = require('node:test')
const assert = require('node:assert/strict')
// --- Handroll DOM stub ---------------------------------------------------
//
// The builders under test never touch layout, only tree structure + a few
// attrs/dataset entries + textContent + eventListeners. This stub covers
// exactly that.
function makeEl(tag) {
return {
tagName: String(tag).toUpperCase(),
className: '',
textContent: '',
hidden: false,
tabIndex: 0,
style: (function () {
const map = {}
return {
setProperty(k, v) { map[k] = v },
getPropertyValue(k) { return map[k] },
}
})(),
dataset: {},
_attrs: {},
_listeners: {},
_children: [],
ownerDocument: null, // set below
appendChild(c) { this._children.push(c); return c },
append(...kids) { for (const k of kids) this._children.push(k) },
setAttribute(k, v) { this._attrs[k] = String(v) },
getAttribute(k) { return this._attrs[k] },
addEventListener(type, fn) {
(this._listeners[type] = this._listeners[type] || []).push(fn)
},
querySelector() { return null },
remove() { /* no-op */ },
}
}
function makeDoc() {
const doc = {
createElement(tag) {
const el = makeEl(tag)
el.ownerDocument = doc
return el
},
body: null,
getElementById() { return null },
}
doc.body = doc.createElement('body')
return doc
}
// Recursively find children matching a class prefix.
function findAllByClass(root, cls, out) {
out = out || []
if (!root) return out
if (root.className && String(root.className).split(/\s+/).includes(cls)) out.push(root)
for (const c of root._children || []) findAllByClass(c, cls, out)
return out
}
function findFirstByClass(root, cls) {
const all = findAllByClass(root, cls, [])
return all[0] || null
}
// --- F2: compact-card 4-tab shell ----------------------------------------
test('F2: mountTabs with fillConfig adds a Config tab and populates its body', () => {
const { mountTabs } = require('../src/renderer/compact-card.js')
const doc = makeDoc()
const parent = doc.createElement('div')
let filledConfig = null
mountTabs(parent, {
document: doc,
initial: 'post',
fillPre: (body) => { body.textContent = 'pre' },
fillPost: (body) => { body.textContent = 'post' },
fillMeta: (body) => { body.textContent = 'meta' },
fillConfig: (body) => { body.textContent = 'CONFIG_HERE'; filledConfig = body },
})
// Tab buttons: expect four in the strip.
const strip = findFirstByClass(parent, 'compact-card-tabstrip')
assert.ok(strip, 'tabstrip mounted')
const tabButtons = strip._children.filter((c) => c.tagName === 'BUTTON')
assert.equal(tabButtons.length, 4, 'expect 4 tabs when fillConfig is provided')
const labels = tabButtons.map((b) => b.textContent)
assert.deepEqual(labels, ['Diff', 'Summary', 'Policy & accounting', 'Config'])
// Config body populated.
assert.equal(filledConfig.textContent, 'CONFIG_HERE')
})
test('F2: mountTabs without fillConfig stays a 3-tab shell (back-compat)', () => {
const { mountTabs } = require('../src/renderer/compact-card.js')
const doc = makeDoc()
const parent = doc.createElement('div')
const out = mountTabs(parent, {
document: doc,
fillPre() {}, fillPost() {}, fillMeta() {},
})
const strip = findFirstByClass(parent, 'compact-card-tabstrip')
const tabButtons = strip._children.filter((c) => c.tagName === 'BUTTON')
assert.equal(tabButtons.length, 3)
assert.equal(out.configBody, null)
})
// --- F4: subagent drill-down tabs ---------------------------------------
test('F4: appendSubagentDrilldownTabs emits both panels with correct labels', () => {
const { appendSubagentDrilldownTabs } = require('../src/renderer/subagent-view.js')
const doc = makeDoc()
const parent = doc.createElement('div')
const spec = {
childEvents: [
{ type: 'user/message', seq: 1, data: { content: [{ type: 'text', text: 'seed' }], source: { kind: 'plugin', plugin: 'subagent-search' } } },
{ type: 'tool/call', seq: 2, data: { name: 'search', arguments: '{"q":"x"}' } },
{ type: 'tool/call', seq: 3, data: { name: 'read_file', arguments: '{"path":"a"}' } },
],
}
appendSubagentDrilldownTabs(doc, parent, spec)
// Two tab buttons expected.
const strip = findFirstByClass(parent, 'subagent-drilldown-tabstrip')
assert.ok(strip, 'drilldown tabstrip mounted')
const buttons = strip._children.filter((c) => c.tagName === 'BUTTON')
assert.equal(buttons.length, 2)
const btnLabels = buttons.map((b) => b.textContent)
assert.match(btnLabels[0], /Tool defs \(2\)/)
assert.equal(btnLabels[1], 'Inbound query')
// Tool list should have two entries.
const toolRows = findAllByClass(parent, 'subagent-drilldown-toolrow', [])
assert.equal(toolRows.length, 2)
// Inbound query blockquote should carry the seed text.
const query = findFirstByClass(parent, 'subagent-drilldown-query')
assert.ok(query, 'inbound query rendered')
assert.match(query.textContent, /seed/)
})
test('F4: appendSubagentDrilldownTabs skips entirely on empty spec', () => {
const { appendSubagentDrilldownTabs } = require('../src/renderer/subagent-view.js')
const doc = makeDoc()
const parent = doc.createElement('div')
appendSubagentDrilldownTabs(doc, parent, {})
assert.equal(parent._children.length, 0, 'no drilldown wrapper for empty spec')
})
// --- F1 + F3 model-shape sanity ----------------------------------------
// (Full DOM wire-up of context-page.js is exercised by the four real-machine
// screenshots at task-end. Here we lock the shapes the DOM depends on.)
test('F1 model: computeWindowBreakdown returns 5 slices in FAMILY_ORDER', () => {
const M = require('../src/renderer/context-window-breakdown.js')
const events = [
{ type: 'context/message', seq: 1, data: { content: [{ type: 'text', text: 'sys' }], source: { kind: 'system' } } },
{ type: 'assistant/message', seq: 2, data: { content: [{ type: 'text', text: 'ok' }], usage: { inputTokens: 100, outputTokens: 40 } } },
{ type: 'assistant/reasoning', seq: 3, data: { content: [{ type: 'text', text: 'thinking' }] } },
]
const view = M.computeWindowBreakdown(events)
assert.equal(view.slices.length, 5)
const totalPct = view.slices.reduce((s, sl) => s + sl.pct, 0)
assert.ok(totalPct <= 100.5, `sum ${totalPct} <= 100`)
})
test('F3 model: collectInterventions preserves seq order + kind counts', () => {
const M = require('../src/renderer/intervention-timeline.js')
const events = [
{ type: 'steering/message', seq: 5, data: { content: [{ type: 'text', text: 'a' }] } },
{ type: 'session/forked', seq: 10, data: { parentSeq: 8 } },
{ type: 'user/message', seq: 15, data: { content: [{ type: 'text', text: 'r' }], editRerun: { origSeq: 12 } } },
]
const markers = M.collectInterventions(events)
assert.equal(markers.length, 3)
assert.deepEqual(markers.map((m) => m.kind), ['steer', 'fork', 'edit-rerun'])
const roll = M.summariseInterventions(markers)
const rollMap = new Map(roll.map((r) => [r.kind, r.count]))
assert.equal(rollMap.get('steer'), 1)
assert.equal(rollMap.get('fork'), 1)
assert.equal(rollMap.get('edit-rerun'), 1)
})