Apply the AGENTS.md comments guideline to every .ts under tests/: remove what the code already expresses, keep only what it cannot. Removed: spec-header coverage inventories the describe/test tree already names; comments restating the next line's assertion or its message; helper JSDoc whose body already says it; within-file duplicates; five stale claims (trendChart's "six aggregated turns" against agg.length === 8, agentGraph's "six categories" against CATS' seven, fold-timing's "cheapest call LAST" against a loop that feeds it first, fold-surface's unreachable plain-JSON note, contextView's "two turns" for a one-turn fixture). Kept and condensed: the projection-freeze class, strict wire/state-schema hazards, retention and pricing windows, pairing rules, hostile-input rationale, fixture and expectation-array ground truth, jsdom mechanics, and issue-numbered regressions. Comment lines 2604 -> 2014 (-22%); 85 module headers kept, 30 coverage inventories dropped; every surviving comment line is within the 140-column limit, with 36 wrapped lines joined onto fuller ones. No logic, string, identifier, import or directive changed: a comment-strip gate proves each file's comment-free code is byte-identical to HEAD, and a line-level proof pairs all 845 changed lines with an unchanged code part. pnpm run lint && pnpm run test (106 files / 2061 tests, per-file 100% coverage) && pnpm run build all pass.
523 lines
22 KiB
TypeScript
523 lines
22 KiB
TypeScript
import { act, createElement as h, useSyncExternalStore } from 'react'
|
|
import assert from 'node:assert/strict'
|
|
import { afterEach, describe, test, vi } from 'vitest'
|
|
import { makeContextModal } from '../../../src/client/components/contextModal'
|
|
import { resetTimelineDetailStores } from '../../../src/client/timelineSource'
|
|
import { modalStoreOf, setPendingConsume, takePendingConsume } from '../../../src/client/modalStore'
|
|
import { watchHistoryFaces } from '../../../src/client/historyPage'
|
|
import { createContextSettings } from '../../../src/client/settings'
|
|
import type { ContextTimeline } from '../../../src/shared/types'
|
|
import { DICT_EN } from '../../../src/client/i18n'
|
|
import { TestClientCtx, TestSessions, asClientCtx } from '../helpers/harness'
|
|
import { click, flush, hover, keydown, makeKit, mount, query, queryAll, text, unhover, until } from '../helpers/kit'
|
|
|
|
const kit = makeKit()
|
|
const settings = createContextSettings()
|
|
|
|
|
|
function timeline(over: Record<string, unknown> = {}): ContextTimeline {
|
|
return {
|
|
ok: true,
|
|
current: { system: 1, tools: 2, user: 3, inject: 4, skill: 0, assistant: 5, tool: 6, total: 21 },
|
|
requests: [],
|
|
events: [],
|
|
nodes: [],
|
|
droppedNodes: 0,
|
|
archive: [],
|
|
...over,
|
|
} as ContextTimeline
|
|
}
|
|
|
|
function boundModalHook(sessionId: string) {
|
|
const store = modalStoreOf(sessionId)
|
|
return (sel: (open: boolean) => boolean): boolean =>
|
|
useSyncExternalStore(store.subscribe, () => sel(store.getSnapshot()))
|
|
}
|
|
|
|
const OPEN = (): boolean => true
|
|
|
|
afterEach(() => {
|
|
vi.restoreAllMocks()
|
|
vi.unstubAllGlobals()
|
|
// The cold-start share (timelineSource.ts) is page-lifetime per session:
|
|
// drop it so one test's cold read never leaks its state into a later test that reuses the same session id.
|
|
resetTimelineDetailStores()
|
|
})
|
|
|
|
describe('ContextModal', () => {
|
|
test('renders null without a useContextModal hook or while closed', async () => {
|
|
const ctx = new TestClientCtx({ services: { sessions: new TestSessions() } })
|
|
const ContextModal = makeContextModal(asClientCtx(ctx), kit, settings)
|
|
|
|
const m1 = await mount(h(ContextModal, { sessionId: 'sm-none', useProjection: () => timeline() }))
|
|
assert.equal(m1.container.childElementCount, 0)
|
|
await m1.unmount()
|
|
|
|
const m2 = await mount(h(ContextModal, {
|
|
sessionId: 'sm-closed',
|
|
useContextModal: () => false,
|
|
useProjection: () => timeline(),
|
|
}))
|
|
assert.equal(m2.container.childElementCount, 0)
|
|
await m2.unmount()
|
|
})
|
|
|
|
test('open with no projection hook (or an empty one) shows the loading view', async () => {
|
|
const ctx = new TestClientCtx({ services: { sessions: new TestSessions() } })
|
|
const ContextModal = makeContextModal(asClientCtx(ctx), kit, settings)
|
|
|
|
const m1 = await mount(h(ContextModal, { sessionId: 'sm-load1', useContextModal: OPEN }))
|
|
assert.ok(text(m1.container).includes(DICT_EN.loading))
|
|
assert.ok(m1.container.querySelector('.lc-modal-card') !== null)
|
|
await m1.unmount()
|
|
|
|
const m2 = await mount(h(ContextModal, {
|
|
sessionId: 'sm-load2',
|
|
useContextModal: OPEN,
|
|
useProjection: () => undefined,
|
|
}))
|
|
assert.ok(text(m2.container).includes(DICT_EN.loading))
|
|
await m2.unmount()
|
|
})
|
|
|
|
test('a cold read that settles without data surfaces the retryable failure, not a stall', async () => {
|
|
vi.stubGlobal('fetch', () => Promise.resolve({ ok: false, status: 500, json: async () => null } as Response))
|
|
const ctx = new TestClientCtx({ services: { sessions: new TestSessions() } })
|
|
const ContextModal = makeContextModal(asClientCtx(ctx), kit, settings)
|
|
const m = await mount(h(ContextModal, {
|
|
sessionId: 'sm-cold-fail',
|
|
useContextModal: OPEN,
|
|
useProjection: () => undefined,
|
|
}))
|
|
await until(() => text(m.container).includes(DICT_EN['detail.loadFailed']), 'the failure note never surfaced')
|
|
assert.ok(!text(m.container).includes(DICT_EN.loading), 'no spinner remains once failed')
|
|
await act(async () => {
|
|
const hit = queryAll(m.container, 'button').find(b => text(b) === DICT_EN['detail.loadFailed'])
|
|
if (hit === undefined) throw new Error('retry button not found')
|
|
hit.click()
|
|
})
|
|
await m.unmount()
|
|
})
|
|
|
|
test('a corrupt timeline projection is sanitized and still renders the composition', async () => {
|
|
const ctx = new TestClientCtx({ services: { sessions: new TestSessions() } })
|
|
const ContextModal = makeContextModal(asClientCtx(ctx), kit, settings)
|
|
const m = await mount(h(ContextModal, {
|
|
sessionId: 'sm-garbage',
|
|
useContextModal: OPEN,
|
|
useProjection: (key: string) =>
|
|
key === 'contextTimeline' ? { current: 'junk', requests: 'nope', nodes: 7, archive: null } : undefined,
|
|
}))
|
|
assert.ok(!text(m.container).includes(DICT_EN.loading))
|
|
assert.ok(text(m.container).includes(DICT_EN['overview.title']))
|
|
assert.ok(m.container.querySelector('.lc-br-cats') !== null)
|
|
await m.unmount()
|
|
})
|
|
|
|
test('full render: subtitle variants and hover link through the composed browser', async () => {
|
|
const sessions = new TestSessions()
|
|
const ctx = new TestClientCtx({ services: { sessions } })
|
|
const ContextModal = makeContextModal(asClientCtx(ctx), kit, settings)
|
|
const projections: Record<string, unknown> = {
|
|
contextTimeline: timeline({
|
|
model: 'deepseek-v4-flash',
|
|
provider: 'deepseek',
|
|
}),
|
|
contextPressure: { projectedTokens: 100, contextWindow: 128000 },
|
|
contextBreakdown: { systemTokens: 1, toolsTokens: 2, messageTokens: 18 },
|
|
contextHeaders: {
|
|
headers: [{ seq: 1, time: 0, system: 'SYS', tools: [{ name: 'bash', tokens: 12, description: 'run' }] }],
|
|
},
|
|
}
|
|
const m = await mount(h(ContextModal, {
|
|
sessionId: 'sm-full',
|
|
useContextModal: OPEN,
|
|
useProjection: (key: string) => projections[key],
|
|
}))
|
|
assert.ok(text(m.container).includes('deepseek-v4-flash · deepseek'))
|
|
|
|
// Hovering a composition segment flows into hoverCat (dim + legend chip).
|
|
const compCard = query(m.container, '.lc-modal-card .lc-card')
|
|
const seg = query(compCard, '.lc-stacked-seg')
|
|
await hover(seg)
|
|
assert.ok(query(compCard, '.lc-stacked').className.includes('lc-stacked-dim'))
|
|
assert.ok(queryAll(compCard, '.lc-chip').some(c => c.className.includes('lc-chip-on')))
|
|
await unhover(query(compCard, '.lc-stacked'))
|
|
assert.ok(!query(compCard, '.lc-stacked').className.includes('lc-stacked-dim'))
|
|
await m.unmount()
|
|
})
|
|
|
|
test('subtitle degrades to the model alone, then to nothing', async () => {
|
|
const ctx = new TestClientCtx({ services: { sessions: new TestSessions() } })
|
|
const ContextModal = makeContextModal(asClientCtx(ctx), kit, settings)
|
|
|
|
const m1 = await mount(h(ContextModal, {
|
|
sessionId: 'sm-sub1',
|
|
useContextModal: OPEN,
|
|
useProjection: (key: string) => key === 'contextTimeline' ? timeline({ model: 'only-model' }) : undefined,
|
|
}))
|
|
assert.ok(text(m1.container).includes('only-model'))
|
|
assert.ok(!text(m1.container).includes('only-model ·'))
|
|
await m1.unmount()
|
|
|
|
const m2 = await mount(h(ContextModal, {
|
|
sessionId: 'sm-sub2',
|
|
useContextModal: OPEN,
|
|
useProjection: (key: string) => key === 'contextTimeline' ? timeline() : undefined,
|
|
}))
|
|
const card = query(m2.container, '.lc-modal-card .lc-card')
|
|
assert.equal(card.querySelector('.lc-card-sub'), null)
|
|
await m2.unmount()
|
|
})
|
|
|
|
test('backdrop click without a sessionId is a no-op', async () => {
|
|
const sessions = new TestSessions()
|
|
const ctx = new TestClientCtx({ services: { sessions } })
|
|
const ContextModal = makeContextModal(asClientCtx(ctx), kit, settings)
|
|
const m = await mount(h(ContextModal, { useContextModal: OPEN, useProjection: () => undefined }))
|
|
await click(query(m.container, '.lc-modal-backdrop'))
|
|
assert.ok(m.container.querySelector('.lc-modal-backdrop') !== null)
|
|
assert.equal(sessions.bails.length, 0)
|
|
await m.unmount()
|
|
})
|
|
|
|
test('clicking the card itself does not close (stopPropagation)', async () => {
|
|
const sessions = new TestSessions()
|
|
const ctx = new TestClientCtx({ services: { sessions } })
|
|
const ContextModal = makeContextModal(asClientCtx(ctx), kit, settings)
|
|
const m = await mount(h(ContextModal, { sessionId: 'sm-card', useContextModal: OPEN, useProjection: () => undefined }))
|
|
await click(query(m.container, '.lc-modal-card'))
|
|
assert.ok(m.container.querySelector('.lc-modal-backdrop') !== null)
|
|
assert.equal(sessions.bails.length, 0)
|
|
await m.unmount()
|
|
})
|
|
|
|
test('close flips the store and bails a pending consume through the session scope', async () => {
|
|
const sessions = new TestSessions()
|
|
const ctx = new TestClientCtx({ services: { sessions } })
|
|
const ContextModal = makeContextModal(asClientCtx(ctx), kit, settings)
|
|
const sid = 'sm-close'
|
|
modalStoreOf(sid).set(true)
|
|
setPendingConsume(sid, { kind: 'bare-token', token: '/context' })
|
|
const m = await mount(h(ContextModal, {
|
|
sessionId: sid,
|
|
useContextModal: boundModalHook(sid),
|
|
useProjection: () => undefined,
|
|
}))
|
|
assert.ok(m.container.querySelector('.lc-modal-backdrop') !== null)
|
|
|
|
await click(query(m.container, '.lc-modal-close'))
|
|
assert.equal(modalStoreOf(sid).getSnapshot(), false)
|
|
assert.equal(m.container.querySelector('.lc-modal-backdrop'), null)
|
|
assert.equal(sessions.bails.length, 1)
|
|
assert.equal(sessions.bails[0].event, 'slash/input-consume-token')
|
|
assert.deepEqual(sessions.bails[0].payload, { guard: { kind: 'bare-token', token: '/context' } })
|
|
assert.equal(takePendingConsume(sid), undefined)
|
|
await m.unmount()
|
|
})
|
|
|
|
test('close without a pending consume sets the store but bails nothing', async () => {
|
|
const sessions = new TestSessions()
|
|
const ctx = new TestClientCtx({ services: { sessions } })
|
|
const ContextModal = makeContextModal(asClientCtx(ctx), kit, settings)
|
|
const sid = 'sm-noguard'
|
|
modalStoreOf(sid).set(true)
|
|
const m = await mount(h(ContextModal, {
|
|
sessionId: sid,
|
|
useContextModal: boundModalHook(sid),
|
|
useProjection: () => undefined,
|
|
}))
|
|
await click(query(m.container, '.lc-modal-backdrop'))
|
|
assert.equal(modalStoreOf(sid).getSnapshot(), false)
|
|
assert.equal(sessions.bails.length, 0)
|
|
await m.unmount()
|
|
})
|
|
|
|
test('a pending consume without the sessions service closes quietly', async () => {
|
|
const ctx = new TestClientCtx()
|
|
const ContextModal = makeContextModal(asClientCtx(ctx), kit, settings)
|
|
const sid = 'sm-nosvc'
|
|
modalStoreOf(sid).set(true)
|
|
setPendingConsume(sid, { kind: 'bare-token', token: '/context' })
|
|
const m = await mount(h(ContextModal, {
|
|
sessionId: sid,
|
|
useContextModal: boundModalHook(sid),
|
|
useProjection: () => undefined,
|
|
}))
|
|
await click(query(m.container, '.lc-modal-backdrop'))
|
|
assert.equal(modalStoreOf(sid).getSnapshot(), false)
|
|
assert.equal(m.container.querySelector('.lc-modal-backdrop'), null)
|
|
await m.unmount()
|
|
})
|
|
|
|
test('a pending consume whose scope is gone closes quietly', async () => {
|
|
const scoped: string[] = []
|
|
const sessions = {
|
|
scope: (id: string) => {
|
|
scoped.push(id)
|
|
return undefined
|
|
},
|
|
}
|
|
const ctx = new TestClientCtx({ services: { sessions } })
|
|
const ContextModal = makeContextModal(asClientCtx(ctx), kit, settings)
|
|
const sid = 'sm-noscope'
|
|
modalStoreOf(sid).set(true)
|
|
setPendingConsume(sid, { kind: 'span', span: { start: 0, end: 8, draftRev: 3 } })
|
|
const m = await mount(h(ContextModal, {
|
|
sessionId: sid,
|
|
useContextModal: boundModalHook(sid),
|
|
useProjection: () => undefined,
|
|
}))
|
|
await click(query(m.container, '.lc-modal-backdrop'))
|
|
assert.equal(modalStoreOf(sid).getSnapshot(), false)
|
|
assert.deepEqual(scoped, [sid])
|
|
await m.unmount()
|
|
})
|
|
|
|
test('the mask docks between the frame sidebars, follows template rewrites, and disconnects on close', async () => {
|
|
vi.stubGlobal('innerWidth', 1280)
|
|
const sessions = new TestSessions()
|
|
const ctx = new TestClientCtx({ services: { sessions } })
|
|
const ContextModal = makeContextModal(asClientCtx(ctx), kit, settings)
|
|
const sid = 'sm-dock'
|
|
// The shell frame: the plugin matches its INLINE grid template, the one
|
|
// host anchor carried by every supported baseline (dockMeasure.ts).
|
|
const frame = document.createElement('div')
|
|
frame.style.gridTemplateColumns = '280px minmax(400px, 1fr) minmax(0px, 360px)'
|
|
const seat = document.createElement('div')
|
|
frame.appendChild(seat)
|
|
document.body.appendChild(frame)
|
|
|
|
const disconnectSpy = vi.spyOn(MutationObserver.prototype, 'disconnect')
|
|
const m = await mount(h(ContextModal, {
|
|
sessionId: sid,
|
|
useContextModal: boundModalHook(sid),
|
|
useProjection: () => undefined,
|
|
}))
|
|
// Relocate the mount container under the frame before opening, so the measure walks the real ancestor chain.
|
|
seat.appendChild(m.container)
|
|
await act(async () => {
|
|
modalStoreOf(sid).set(true)
|
|
})
|
|
const backdrop = query(m.container, '.lc-modal-backdrop')
|
|
assert.equal(backdrop.style.left, '280px')
|
|
assert.equal(backdrop.style.right, '360px')
|
|
|
|
// A sidebar rewrite while open (the right panel closed here) is followed through the observer on both edges.
|
|
await act(async () => {
|
|
frame.style.gridTemplateColumns = '56px minmax(0px, 1fr) minmax(0px, 0px)'
|
|
})
|
|
await flush()
|
|
const rewritten = query(m.container, '.lc-modal-backdrop')
|
|
assert.equal(rewritten.style.left, '56px')
|
|
assert.equal(rewritten.style.right, '0px')
|
|
|
|
disconnectSpy.mockClear()
|
|
await click(query(m.container, '.lc-modal-backdrop'))
|
|
assert.equal(m.container.querySelector('.lc-modal-backdrop'), null)
|
|
assert.equal(disconnectSpy.mock.calls.length, 1)
|
|
frame.remove()
|
|
await m.unmount()
|
|
})
|
|
|
|
test('without a frame ancestor the mask stays full-viewport', async () => {
|
|
const ctx = new TestClientCtx({ services: { sessions: new TestSessions() } })
|
|
const ContextModal = makeContextModal(asClientCtx(ctx), kit, settings)
|
|
const m = await mount(h(ContextModal, {
|
|
sessionId: 'sm-noframe',
|
|
useContextModal: OPEN,
|
|
useProjection: () => undefined,
|
|
}))
|
|
const backdrop = query(m.container, '.lc-modal-backdrop')
|
|
assert.equal(backdrop.style.left, '0px')
|
|
assert.equal(backdrop.style.right, '0px')
|
|
await m.unmount()
|
|
})
|
|
|
|
test('Escape closes; other keys do not; the listener is removed on close', async () => {
|
|
const sessions = new TestSessions()
|
|
const ctx = new TestClientCtx({ services: { sessions } })
|
|
const ContextModal = makeContextModal(asClientCtx(ctx), kit, settings)
|
|
const sid = 'sm-esc'
|
|
modalStoreOf(sid).set(true)
|
|
const m = await mount(h(ContextModal, {
|
|
sessionId: sid,
|
|
useContextModal: boundModalHook(sid),
|
|
useProjection: () => undefined,
|
|
}))
|
|
await keydown('a')
|
|
assert.ok(m.container.querySelector('.lc-modal-backdrop') !== null)
|
|
|
|
await keydown('Escape')
|
|
assert.equal(modalStoreOf(sid).getSnapshot(), false)
|
|
assert.equal(m.container.querySelector('.lc-modal-backdrop'), null)
|
|
|
|
// Listener removed on close: another Escape must not run close() again —
|
|
// with a pending consume armed, a live listener would bail.
|
|
setPendingConsume(sid, { kind: 'bare-token', token: '/context' })
|
|
await keydown('Escape')
|
|
assert.equal(sessions.bails.length, 0)
|
|
assert.notEqual(takePendingConsume(sid), undefined)
|
|
await m.unmount()
|
|
})
|
|
|
|
test('closing restores focus to the previously focused element', async () => {
|
|
const ctx = new TestClientCtx({ services: { sessions: new TestSessions() } })
|
|
const ContextModal = makeContextModal(asClientCtx(ctx), kit, settings)
|
|
const sid = 'sm-focus'
|
|
const btn = document.createElement('button')
|
|
document.body.appendChild(btn)
|
|
btn.focus()
|
|
assert.equal(document.activeElement, btn)
|
|
|
|
const m = await mount(h(ContextModal, {
|
|
sessionId: sid,
|
|
useContextModal: boundModalHook(sid),
|
|
useProjection: () => undefined,
|
|
}))
|
|
await act(async () => {
|
|
modalStoreOf(sid).set(true)
|
|
})
|
|
const closeBtn = query(m.container, '.lc-modal-close')
|
|
closeBtn.focus()
|
|
assert.equal(document.activeElement, closeBtn)
|
|
|
|
await click(closeBtn)
|
|
assert.equal(document.activeElement, btn)
|
|
btn.remove()
|
|
await m.unmount()
|
|
})
|
|
|
|
test('closing skips the focus restore when the previous element left the document', async () => {
|
|
const ctx = new TestClientCtx({ services: { sessions: new TestSessions() } })
|
|
const ContextModal = makeContextModal(asClientCtx(ctx), kit, settings)
|
|
const sid = 'sm-focus-gone'
|
|
const btn = document.createElement('button')
|
|
document.body.appendChild(btn)
|
|
btn.focus()
|
|
|
|
const m = await mount(h(ContextModal, {
|
|
sessionId: sid,
|
|
useContextModal: boundModalHook(sid),
|
|
useProjection: () => undefined,
|
|
}))
|
|
await act(async () => {
|
|
modalStoreOf(sid).set(true)
|
|
})
|
|
btn.remove()
|
|
await click(query(m.container, '.lc-modal-close'))
|
|
assert.equal(modalStoreOf(sid).getSnapshot(), false)
|
|
assert.ok(!document.contains(btn))
|
|
await m.unmount()
|
|
})
|
|
|
|
test('no focus bookkeeping when nothing was focused (activeElement not an element)', async () => {
|
|
const ctx = new TestClientCtx({ services: { sessions: new TestSessions() } })
|
|
const ContextModal = makeContextModal(asClientCtx(ctx), kit, settings)
|
|
const sid = 'sm-focus-null'
|
|
modalStoreOf(sid).set(true)
|
|
// Environment control (not a plugin fake): jsdom always reports body;
|
|
// the harness browser can report null before the body exists.
|
|
vi.spyOn(document, 'activeElement', 'get').mockReturnValue(null)
|
|
const m = await mount(h(ContextModal, {
|
|
sessionId: sid,
|
|
useContextModal: boundModalHook(sid),
|
|
useProjection: () => undefined,
|
|
}))
|
|
vi.restoreAllMocks()
|
|
await click(query(m.container, '.lc-modal-close'))
|
|
assert.equal(modalStoreOf(sid).getSnapshot(), false)
|
|
assert.equal(document.activeElement, document.body)
|
|
await m.unmount()
|
|
})
|
|
})
|
|
|
|
describe('ContextModal — the split generation', () => {
|
|
test('the modal shares the detail route: the browser lands the collections on open', async () => {
|
|
vi.stubGlobal('fetch', async () => ({
|
|
ok: true,
|
|
status: 200,
|
|
json: async () => ({
|
|
ok: true,
|
|
value: {
|
|
rev: 1,
|
|
requests: [{ seq: 1, turn: 1, step: 1, time: 1, system: 1, tools: 2, user: 3, inject: 4, assistant: 5, tool: 6, total: 21 }],
|
|
events: [],
|
|
nodes: [{ seq: 1, cat: 'assistant', tokens: 5, text: 'modal reply' }],
|
|
droppedNodes: 0,
|
|
archive: [],
|
|
},
|
|
}),
|
|
}))
|
|
const ctx = new TestClientCtx({ services: { sessions: new TestSessions() } })
|
|
const ContextModal = makeContextModal(asClientCtx(ctx), kit, settings)
|
|
const head = timeline({
|
|
counts: { turns: 1, steps: 1, injects: 0, compactions: 0, prunes: 0 },
|
|
last: { seq: 1, total: 21, prompt: 20 },
|
|
detailRev: 1,
|
|
})
|
|
const m = await mount(h(ContextModal, {
|
|
sessionId: 'sm-slim',
|
|
useContextModal: OPEN,
|
|
useProjection: (key: string) => (key === 'contextTimeline' ? head : undefined),
|
|
}))
|
|
// The composition card paints off the head while the browser's detail note shows.
|
|
assert.ok(text(m.container).includes(DICT_EN['overview.title']))
|
|
assert.ok(text(m.container).includes(DICT_EN['detail.loading']))
|
|
await until(() => !text(m.container).includes(DICT_EN['detail.loading']), 'the modal detail never landed')
|
|
assert.equal(queryAll(m.container, '.lc-br-pick option').length, 2, 'live + the one served step')
|
|
assert.ok(text(m.container).includes('1 Item'), 'the assistant section counts the served node')
|
|
await m.unmount()
|
|
})
|
|
|
|
test('a mount that raced the declared inject heals when the face lands mid-open', async () => {
|
|
// The race: the modal mounts (and its fetchers snapshot the face) BEFORE
|
|
// `remote` composes — the degraded note shows — then the inject fires and
|
|
// the OPEN system section must auto-fetch without any user action.
|
|
const calls: { sessionId: string; throughSeq: number; beforeSeq: number }[] = []
|
|
const ctx = new TestClientCtx({ services: { sessions: new TestSessions() } })
|
|
// Armed before either service exists: the inject stays pending, the race.
|
|
watchHistoryFaces(asClientCtx(ctx))
|
|
const ContextModal = makeContextModal(asClientCtx(ctx), kit, settings)
|
|
const projections: Record<string, unknown> = {
|
|
contextTimeline: timeline(),
|
|
contextHeaders: { headers: [{ seq: 1, time: 0, systemTokens: 5, tools: [] }] },
|
|
}
|
|
const m = await mount(h(ContextModal, {
|
|
sessionId: 'sm-race',
|
|
useContextModal: OPEN,
|
|
useProjection: (key: string) => projections[key],
|
|
}))
|
|
const sysRow = queryAll(m.container, '.lc-br-cat-row').find(r => text(r).includes(DICT_EN['cat.system']))
|
|
assert.ok(sysRow !== undefined)
|
|
await click(sysRow)
|
|
assert.ok(text(m.container).includes(DICT_EN['browser.headerMetaOnly']), 'the faceless mount shows the static note')
|
|
|
|
// The face lands mid-open (cordis replays the pending inject): the modal
|
|
// re-renders, the fetcher builds, and the open epoch fetches by itself.
|
|
// The face rides INSIDE the `remote` facade; the `remote.session` service key stays hostile, as on the real host.
|
|
await act(async () => {
|
|
ctx.setService('remote.session', { get page() { throw new Error('cannot get property "remote.session" without inject') } })
|
|
ctx.setService('remote', {
|
|
session: {
|
|
page: (request: { address: { sessionId: string }; throughSeq: number; beforeSeq: number }) => {
|
|
calls.push({ sessionId: request.address.sessionId, throughSeq: request.throughSeq, beforeSeq: request.beforeSeq })
|
|
return Promise.resolve({
|
|
ok: true,
|
|
value: {
|
|
records: [{ type: 'event', event: { type: 'system/message', seq: 1, time: 1, data: { message: { content: [{ type: 'text', text: 'HEALED SYSTEM PROMPT' }] } } } }],
|
|
},
|
|
})
|
|
},
|
|
},
|
|
})
|
|
})
|
|
await until(() => text(m.container).includes('HEALED SYSTEM PROMPT'), 'the open epoch never healed after the face landed')
|
|
assert.ok(!text(m.container).includes(DICT_EN['browser.headerMetaOnly']))
|
|
assert.deepEqual(calls, [{ sessionId: 'sm-race', throughSeq: 1, beforeSeq: 2 }])
|
|
await m.unmount()
|
|
// Unload the declared slot so no face stales into the next test.
|
|
ctx.dispose()
|
|
})
|
|
})
|