Files
dsh-context/tests/client/overviewStore.spec.ts
T
Bowen Liang 2f265f3f18 feat(client): add the Context Dashboard panel above Settings
A cross-session insight surface: the sidebar-foot entry (stacked above
Settings by the harness's own foot layout, with a running-sessions badge)
opens a frame-wide overlay through the shared module store. The panel reads
the root-scope useSessions/useWorkspaces standard kit — every list row's
host-cached projection values — so no session log needs opening:

- KPI band over the picked range (7d / 30d / all): active sessions, billed
  tokens, estimated cost (models.dev book), cache-hit rate.
- Activity heatmap (GitHub-style, 12 weeks, square cells filling the card):
  a day click pins the session grid to that day.
- Aggregate composition donut over the range's sessions.
- Session cards with a mini composition ring, billed/turns/cost figures and
  a workspace-group / project breadcrumb (deduped when both names match);
  grid filters by range, pinned day, group chips (incl. Ungrouped), and
  search; sorts by recency, tokens, or context size; a card click opens the
  session through the harness's own sessions.open and closes the panel.

Subagent-origin rows are excluded, mirroring the workspace browser's own
predicate (plain open() cannot address them). Every row is sanitized in
isolation — a hostile row degrades to a metadata-only card, never an error.
2026-09-16 08:56:30 +08:00

56 lines
1.6 KiB
TypeScript

// The Context Dashboard's open-state store (src/client/overviewStore.ts):
// subscribe/notify semantics, idempotent sets, unsubscribe.
import assert from 'node:assert/strict'
import { afterEach, describe, test } from 'vitest'
import { overviewStore } from '../../src/client/overviewStore'
afterEach(() => {
overviewStore.set(false)
})
describe('overviewStore', () => {
test('closed by default; set flips and notifies', () => {
assert.equal(overviewStore.getSnapshot(), false)
const seen: boolean[] = []
const unsubscribe = overviewStore.subscribe(() => { seen.push(overviewStore.getSnapshot()) })
overviewStore.set(true)
assert.equal(overviewStore.getSnapshot(), true)
assert.deepEqual(seen, [true])
overviewStore.set(false)
assert.deepEqual(seen, [true, false])
unsubscribe()
})
test('an idempotent set notifies nobody', () => {
overviewStore.set(true)
let calls = 0
const unsubscribe = overviewStore.subscribe(() => { calls++ })
overviewStore.set(true)
assert.equal(calls, 0)
overviewStore.set(false)
assert.equal(calls, 1)
unsubscribe()
})
test('unsubscribe detaches the listener', () => {
let calls = 0
const unsubscribe = overviewStore.subscribe(() => { calls++ })
unsubscribe()
overviewStore.set(true)
assert.equal(calls, 0)
})
test('several listeners all hear a flip', () => {
let a = 0
let b = 0
const unA = overviewStore.subscribe(() => { a++ })
const unB = overviewStore.subscribe(() => { b++ })
overviewStore.set(true)
assert.equal(a, 1)
assert.equal(b, 1)
unA()
unB()
})
})