Files
deepseek-harness/examples/desktop/test/style-css-static.test.js
T
ZiyaZhang e8f5c0b51b feat(desktop): DSH Electron desktop shell — harness internals visualized
Minimal Electron shell over the DSH JSON-RPC runtime — a first-look at
what a ChatGPT.app-style host on top of the DeepSeek Harness looks
like, with the harness's normally-invisible internals (trace timeline,
context surface, subagent tree, compaction, plugin registry, rubrics)
brought forward as first-class UI surfaces so plugin authors and
researchers can see what the agent is actually doing.

Runs against three keyless-to-live profiles (stdio-echo works on
master out of the box; daemon-echo / daemon-vibe-echo activate once
the daemon-demo lands; stdio-deepseek and daemon-vibe hit the real
DeepSeek API when you supply a key). HARNESS_DEV auto-resolves to the
in-repo runtime when this shell ships under examples/desktop/, so a
fresh clone launches without config; env DSH_DEV_ROOT overrides for
custom layouts, and a sibling deepseek-harness-dev/ checkout is the
original dev workflow.

Cold-clone gate (P0 fixes for first-time-clone usability):
- HARNESS_DEV: 3-candidate resolver (env → walk-up in-repo marker →
  sibling), unit-tested via mock fs so ordering is locked without
  needing either real layout on disk.
- config yml leaves rewritten at assemble time so the sibling-clone
  paths (../../deepseek-harness-dev/examples/echo-agent/…) become
  the in-repo paths (../../echo-agent/…) in the released tree —
  source yml stays usable for local dev, released tree ships a
  working shape.
- pnpm-workspace.yaml allowBuilds.electron = true (was placeholder).
- missing-key card in stdio-deepseek offers a one-click switch to
  stdio-echo (the keyless profile that works on master) rather than
  daemon-echo (blocked on the not-yet-shipped daemon-demo).
- assemble-oss-release.sh rewrites the source-side breadcrumb name
  'dsh-desktop-demo' → 'dsh-desktop' for the released package.json.

FOUC guard on the onboarding gate (41fc5df carried) keeps the
first-launch splash from flashing before the runtime probe finishes.

Test suite (1634 tests in source, 3990 in the runtime repo) covers
resolver ordering, renderer classifiers, trace timeline shape,
compaction diff rendering, rubric parity, and the missing-key
onboarding paths.
2026-07-18 12:59:34 -07:00

378 lines
16 KiB
JavaScript

// Static gate for the centered-card ban (density-spec §7; user re-flagged
// 2026-07-17, second occurrence). Every in-stream element is a full-width,
// left-aligned row that expands INLINE. The forbidden CSS smells are
// `margin: * auto` and `align-self: center` on the tool-card / trigger /
// steer families; both survived one dedicated sweep before drift caught
// them, so we lock the ban at the test layer instead of relying on eyes.
//
// Exemption list (must stay allowed to use auto/center):
// .empty-welcome — first-boot empty-state page chrome
// .empty-launcher — launcher grid page chrome
// .context-page-empty-* — Context page empty state (page chrome)
// .msg.user — canonical user-bubble right-anchor
// (`margin-left: auto` is the shape, not a smell)
'use strict'
const test = require('node:test')
const assert = require('node:assert')
const fs = require('node:fs')
const path = require('node:path')
const cssPath = path.resolve(__dirname, '..', 'src', 'renderer', 'style.css')
const css = fs.readFileSync(cssPath, 'utf8')
// Forbidden selectors: the 13-card family (density-spec inventory) + the
// steer-chip in-stream chip. Match the selector token at a rule boundary
// so `.card` never matches `.card-diff-tree-row` or similar longer names.
const CARD_FAMILY = [
'.card',
'.tool-block',
'.artifact-card',
'.card-terminal',
'.card-diff',
'.card-code-dispatch',
'.card-web-search',
'.card-skill',
'.card-workflow',
'.visibility-card',
'.todo-card',
'.recall-card',
'.compact-card',
'.steer-chip',
]
const EXEMPT = new Set([
'.empty-welcome',
'.empty-launcher',
'.context-page-empty',
'.context-page-empty-title',
'.context-page-empty-sub',
'.msg.user',
])
// Parse each top-level rule (selectorList { body }). Nested at-rules
// (@media, @supports) get their inner rules parsed the same way — we
// walk brace by brace and only surface leaf rules (bodies without `{`).
function iterRules(source) {
const rules = []
let i = 0
const n = source.length
while (i < n) {
// Skip whitespace and comments
while (i < n) {
if (source[i] === '/' && source[i + 1] === '*') {
const end = source.indexOf('*/', i + 2)
i = end === -1 ? n : end + 2
} else if (/\s/.test(source[i])) {
i++
} else break
}
if (i >= n) break
// Read up to '{' or ';'
let start = i
let depth = 0
while (i < n && source[i] !== '{' && source[i] !== ';') i++
if (i >= n) break
if (source[i] === ';') { i++; continue }
const selector = source.slice(start, i).trim()
i++ // consume {
// Read matching body
const bodyStart = i
depth = 1
while (i < n && depth > 0) {
if (source[i] === '{') depth++
else if (source[i] === '}') depth--
if (depth > 0) i++
}
const body = source.slice(bodyStart, i)
i++ // consume }
// If selector starts with @, recurse into body (media/supports/etc.)
if (selector.startsWith('@')) {
for (const r of iterRules(body)) rules.push(r)
} else {
rules.push({ selector, body })
}
}
return rules
}
function selectorTouchesFamily(selector) {
// A rule's selectorList is comma-separated; if ANY selector in the list
// ends in a token from the family (i.e., the LAST simple selector on
// the compound is a family member), the rule sets that family's box.
const groups = selector.split(',').map((s) => s.trim())
const matched = []
for (const g of groups) {
// Last simple selector = trailing chunk before any pseudo (:hover etc.)
// We match "the compound ends in .foo(.bar)*" by checking the whole
// string ends with one of the family names, allowing further class
// qualifiers (e.g. `.card.trigger-card`) but NOT a descendant chain
// (e.g. `.card .steer-title` — that styles a child, not the card box).
const bareTail = g.replace(/::?[a-z-]+(\([^)]*\))?$/i, '') // drop pseudo
// Reject exemptions first — a selector whose last simple selector is an
// exempt name is off-limits for the ban even if the compound also has
// a family class (e.g. `.card.empty-welcome-frame`).
for (const ex of EXEMPT) {
if (endsWithSelector(bareTail, ex)) return null
}
for (const fam of CARD_FAMILY) {
if (endsWithSelector(bareTail, fam)) {
matched.push({ group: g, family: fam })
break
}
}
}
return matched.length ? matched : null
}
// A compound `X.Y.Z` "ends with" `.Y` if `.Y` appears as one of the
// classes in the trailing compound (after the last combinator).
function endsWithSelector(compoundish, needle) {
// Last simple selector = substring after the last combinator (space, >, +, ~)
const lastSep = Math.max(
compoundish.lastIndexOf(' '),
compoundish.lastIndexOf('>'),
compoundish.lastIndexOf('+'),
compoundish.lastIndexOf('~'),
)
const tail = lastSep >= 0 ? compoundish.slice(lastSep + 1) : compoundish
// Split the tail on `.` (except the very first char which may be `.`
// itself). Tokens are the classes; needle without leading `.` is the
// class name we're looking for.
const need = needle.startsWith('.') ? needle.slice(1) : needle
// Include ANY class in the compound tail (e.g., `.card.trigger-card` matches
// both `.card` and `.trigger-card`).
const classes = tail.split('.').filter(Boolean)
return classes.includes(need)
}
const rules = iterRules(css)
test('centered-card ban — no `margin: * auto` on card family or steer-chip', () => {
const offenders = []
for (const { selector, body } of rules) {
const hit = selectorTouchesFamily(selector)
if (!hit) continue
// Match `margin: <anything with the token `auto` as a whole word> ;`
// Handles: `margin: auto`, `margin: 0 auto`, `margin: 6px auto`,
// `margin: var(--space-3) auto`, `margin: 12px auto 8px`.
// NOT matched: `margin-left: auto` (right-anchoring, legitimate),
// `margin-top: auto` (flex spacer, legitimate),
// `overflow: auto` (different property).
if (/(^|[\s;])margin\s*:[^;]*\bauto\b/i.test(body)) {
offenders.push(`${selector.replace(/\s+/g, ' ')} — body has \`margin: … auto\``)
}
}
assert.deepStrictEqual(offenders, [],
`centered-card ban violated (margin auto):\n ${offenders.join('\n ')}`)
})
test('centered-card ban — no `align-self: center` on card family or steer-chip', () => {
const offenders = []
for (const { selector, body } of rules) {
const hit = selectorTouchesFamily(selector)
if (!hit) continue
if (/(^|[\s;])align-self\s*:\s*center\b/i.test(body)) {
offenders.push(`${selector.replace(/\s+/g, ' ')} — body has \`align-self: center\``)
}
}
assert.deepStrictEqual(offenders, [],
`centered-card ban violated (align-self center):\n ${offenders.join('\n ')}`)
})
test('selector matcher: family-detection sanity (guards the guard)', () => {
// Positive cases — must be flagged as family.
assert.ok(selectorTouchesFamily('.card'), '.card')
assert.ok(selectorTouchesFamily('.card.steer'), '.card.steer')
assert.ok(selectorTouchesFamily('.card.trigger-card'), '.card.trigger-card')
assert.ok(selectorTouchesFamily('.tool-block'), '.tool-block')
assert.ok(selectorTouchesFamily('.steer-chip'), '.steer-chip')
// Compound classes get matched.
assert.ok(selectorTouchesFamily('.tool-block.family-code'), '.tool-block.family-code')
// Negative cases — descendant styles are NOT the card box.
assert.strictEqual(selectorTouchesFamily('.card .steer-title'), null,
'descendant of .card is not the card box')
assert.strictEqual(selectorTouchesFamily('.tool-block summary'), null,
'element inside .tool-block is not the block box')
// Longer names starting with the same chars are NOT the family.
assert.strictEqual(selectorTouchesFamily('.card-diff-tree-row'), null,
'longer name is not `.card`')
// Exemptions win over the family flag.
assert.strictEqual(selectorTouchesFamily('.msg.user'), null,
'.msg.user is exempt')
assert.strictEqual(selectorTouchesFamily('.empty-welcome'), null,
'.empty-welcome is exempt')
})
// Brace-balance machine lock. Third-time occurrence pattern — union-splicing
// style.css after a merge has three times swallowed later rules by leaving a
// block unclosed (Bench collapse f1efa1c was the most recent). Manual review
// missed it every time; add the machine so the eye never has to be right.
//
// Method: comment-aware char walk (strip `/* … */` first) that increments on
// `{`, decrements on `}`, and asserts (a) final depth === 0 (every open block
// is closed) and (b) depth never goes negative (no stray `}` before its `{`).
// Braces inside strings/URLs are theoretically possible; in practice CSS uses
// no string literals with `{`/`}` and url() bodies never contain them, so the
// raw walk after comment stripping is sound.
test('brace balance — style.css blocks close cleanly (union-splice lock)', () => {
const src = css
let depth = 0
let minDepth = 0
let firstNegativeAt = -1
let i = 0
const n = src.length
while (i < n) {
// Skip block comments so `/* } */` doesn't fool the counter.
if (src[i] === '/' && src[i + 1] === '*') {
const end = src.indexOf('*/', i + 2)
i = end === -1 ? n : end + 2
continue
}
const ch = src[i]
if (ch === '{') {
depth++
} else if (ch === '}') {
depth--
if (depth < minDepth) {
minDepth = depth
if (firstNegativeAt === -1 && depth < 0) firstNegativeAt = i
}
}
i++
}
// Turn a raw char index into a "line X" hint (best-effort; only used in
// the failure message, so an off-by-one on the line is not load-bearing).
const lineOf = (idx) => idx < 0 ? '?' : (src.slice(0, idx).match(/\n/g) || []).length + 1
assert.strictEqual(minDepth, 0,
`stray closing brace before its matching open at ~line ${lineOf(firstNegativeAt)} ` +
`(minDepth=${minDepth}) — union-splice likely dropped a rule head`)
assert.strictEqual(depth, 0,
`style.css ends with ${depth} unclosed block${depth === 1 ? '' : 's'} — ` +
`a rule head opened without its `+`}`+` and swallowed everything below. ` +
`See f1efa1c (Bench collapse) for the last occurrence.`)
})
// -----------------------------------------------------------------------
// payload-controls overlap lock (2026-07-18 P0 hotfix, da779ac / merge
// 9743db1). The button cluster (pretty ⇅ raw · copy · download) used to
// sit on tool-block args/result rows via `float: right` + a negative
// `margin-top: -18px` pull-up hack; when the label text or drawer meta
// string got long (e.g. drawer's "(content · meta · isError · error ·
// durationMs)"), the buttons landed on top of the label text. The fix
// wrapped label+controls in a flex row (`.tool-block-label-row`) and
// switched the drawer callsite to a right-aligned strip
// (`.tool-json-section-controls`) — see style.css §payload-controls +
// §tool-block-label-row + §tool-json-section-controls.
//
// This gate freezes the fix so it can't silently regress: any rule
// whose selector ends in `.payload-controls`, `.tool-block-label-row`,
// or `.tool-json-section-controls` (in any state — plain, hover,
// descendant of a family class, etc.) is forbidden from re-introducing
// `float: right|left` or a negative `margin-top`. The trace-detail-pane
// mount (mount D, `.trace-detail-json-panel`) also gets covered by the
// same body-scan below since payload-controls is where the buttons
// live regardless of host.
//
// Method: match rules whose LAST simple selector is in the payload
// vocabulary (using the same endsWithSelector matcher we already trust
// for the centered-card gate) and grep the body for float/negative
// margin-top. `margin-left: auto` on `.payload-controls` is EXPLICITLY
// allowed — that's how the cluster right-anchors inside its flex host.
const PAYLOAD_VOCAB = [
'.payload-controls',
'.tool-block-label-row',
'.tool-json-section-controls',
]
function selectorTouchesPayload(selector) {
const groups = selector.split(',').map((s) => s.trim())
const matched = []
for (const g of groups) {
const bareTail = g.replace(/::?[a-z-]+(\([^)]*\))?$/i, '')
// Last simple selector = substring after the last combinator
const lastSep = Math.max(
bareTail.lastIndexOf(' '),
bareTail.lastIndexOf('>'),
bareTail.lastIndexOf('+'),
bareTail.lastIndexOf('~'),
)
const tail = lastSep >= 0 ? bareTail.slice(lastSep + 1) : bareTail
const classes = tail.split('.').filter(Boolean)
for (const v of PAYLOAD_VOCAB) {
if (classes.includes(v.slice(1))) { matched.push({ group: g, vocab: v }); break }
}
}
return matched.length ? matched : null
}
test('payload-controls overlap lock — no `float: left|right` on payload vocabulary', () => {
const offenders = []
for (const { selector, body } of rules) {
if (!selectorTouchesPayload(selector)) continue
// `float: none` is a legitimate DE-fluke (drawer strip explicitly
// sets `float: none` to nuke a legacy inherited float). We only ban
// the two directional values that make things overlap.
if (/(^|[\s;])float\s*:\s*(right|left)\b/i.test(body)) {
offenders.push(`${selector.replace(/\s+/g, ' ')} — body has \`float: right|left\``)
}
}
assert.deepStrictEqual(offenders, [],
`payload-controls overlap lock violated (float:right|left reintroduced):\n ${offenders.join('\n ')}\n` +
'Root cause of the 2026-07-18 P0: float:right + margin-top:-18px pulled the button ' +
'cluster on top of the label text when the drawer meta string ran long. Use the ' +
'flex-row shape (`.tool-block-label-row` for inline / `.tool-json-section-controls` ' +
'for the drawer) instead — see style.css around the payload-controls block.')
})
test('payload-controls overlap lock — no negative `margin-top` on payload vocabulary', () => {
const offenders = []
for (const { selector, body } of rules) {
if (!selectorTouchesPayload(selector)) continue
// Match `margin-top: -Npx` (or `-N`, or `-N.Nrem`, etc.). The
// shorthand `margin: -Npx …` on a payload rule is equally bad; catch
// both. We DO allow `margin: 0`, `margin-top: 0`, and any positive
// value; we DO allow `margin-left: auto` (that's the right-anchor).
if (/(^|[\s;])margin-top\s*:\s*-\d/i.test(body) ||
/(^|[\s;])margin\s*:\s*-\d/i.test(body)) {
offenders.push(`${selector.replace(/\s+/g, ' ')} — body has negative margin-top`)
}
}
assert.deepStrictEqual(offenders, [],
`payload-controls overlap lock violated (negative margin-top reintroduced):\n ${offenders.join('\n ')}\n` +
'The negative-margin pull-up hack was half of the overlap bug (the other half was ' +
'`float: right`). Both are banned on `.payload-controls`, `.tool-block-label-row`, ' +
'and `.tool-json-section-controls` — the merged fix (da779ac) uses `margin-left: auto` ' +
'inside a flex row instead. See style.css around the payload-controls block.')
})
test('payload-controls overlap lock — the fix\'s flex row shape stays present', () => {
// Positive assertion: the two flex hosts that carry the fix must still
// exist as rules and still declare `display: flex`. A refactor that
// deletes them (thinking they\'re dead) would silently reintroduce
// the overlap.
function findRule(cls) {
for (const { selector, body } of rules) {
const groups = selector.split(',').map((s) => s.trim())
for (const g of groups) {
// Match `<cls>` as the whole selector, or `<cls>` followed by
// a `>` child combinator (both `.foo` and `.foo > .bar` count
// as "the rule that sets .foo").
if (g === cls || g.startsWith(cls + ' ') || g.startsWith(cls + '>') || g.startsWith(cls + '.')) {
return { selector, body }
}
}
}
return null
}
const row = findRule('.tool-block-label-row')
assert.ok(row, '.tool-block-label-row rule missing — the P0 flex fix has been deleted')
assert.match(row.body, /display\s*:\s*flex/i,
'.tool-block-label-row must stay `display: flex` — that is what pushes the button cluster to the right instead of stacking it via float.')
const strip = findRule('.tool-json-section-controls')
assert.ok(strip, '.tool-json-section-controls rule missing — the drawer overlap fix has been deleted')
assert.match(strip.body, /display\s*:\s*flex/i,
'.tool-json-section-controls must stay `display: flex` — the drawer callsite relies on it to right-align the controls strip.')
})