mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-09 13:21:42 +00:00
opentui(v6): post-rebase fixups — dedup probe mouse, .demo lint ignore, cap tests to windowing-aware contract
Rebasing onto fcf49f313 (multi-click selection) collided in the test
probe (both sides added 'mouse' — deduped) and surfaced two test debts
the cap-restore commit (3cc56517a) had shipped masked by a piped exit
code: store cap tests still asserted the 1000 default (now: 3000
windowed, 1000 with HERMES_TUI_WINDOWING=0, both covered), and the
burst-interplay test relied on the old cap trimming a 1500-row burst
(now pins HERMES_TUI_MAX_MESSAGES=1000 explicitly for both stores).
Also: .demo/ build artifacts excluded from typed linting. 669 tests
exit 0 (verified unpiped). Multi-click selections flow through the
same renderer selection seam, so windowing's drag-freeze + row-pinning
covers them with no changes.
This commit is contained in:
parent
c5806b9ad9
commit
fc8d5f203a
4 changed files with 49 additions and 16 deletions
|
|
@ -4,9 +4,9 @@ import unusedImports from "eslint-plugin-unused-imports"
|
|||
|
||||
export default tseslint.config(
|
||||
{
|
||||
// .bench/ is a build artifact of the bench suite's `nodes` cell
|
||||
// (bench/run.mjs builds scripts/mem-bench.tsx into it) — never lint it.
|
||||
ignores: ["node_modules/**", "dist/**", ".bench/**", ".repos/**", "*.frame.txt", "*.ansi"],
|
||||
// .bench/ and .demo/ are build artifacts (bench `nodes` cell and the
|
||||
// smoke demo: `node scripts/build.mjs scripts/demo.tsx .demo`) — never lint.
|
||||
ignores: ["node_modules/**", "dist/**", ".bench/**", ".demo/**", ".repos/**", "*.frame.txt", "*.ansi"],
|
||||
},
|
||||
js.configs.recommended,
|
||||
...tseslint.configs.recommendedTypeChecked,
|
||||
|
|
|
|||
|
|
@ -65,8 +65,6 @@ export interface RenderProbe {
|
|||
readonly scroll: (x: number, y: number, direction: 'up' | 'down') => Promise<void>
|
||||
/** The mock keyboard (typeText / pressArrow / pressEnter / …) — pair with `settle()`. */
|
||||
readonly keys: TestRendererSetup['mockInput']
|
||||
/** The full mock mouse (drag / pressDown / release / …) — selection tests. */
|
||||
readonly mouse: TestRendererSetup['mockMouse']
|
||||
/** The live test renderer (e.g. `getSelection()` assertions). */
|
||||
readonly renderer: TestRendererSetup['renderer']
|
||||
/** Run a render pass + flush so simulated input lands in the next `frame()`. */
|
||||
|
|
@ -125,7 +123,6 @@ export async function renderProbe(
|
|||
await setup.flush()
|
||||
},
|
||||
keys: setup.mockInput,
|
||||
mouse: setup.mockMouse,
|
||||
renderer: setup.renderer,
|
||||
settle: async () => {
|
||||
await setup.renderOnce()
|
||||
|
|
|
|||
|
|
@ -506,10 +506,14 @@ describe('session store — resume hydrate (Phase 4b)', () => {
|
|||
|
||||
describe('session store — rolling message cap (bounds the Yoga node high-water mark)', () => {
|
||||
const ENV_KEY = 'HERMES_TUI_MAX_MESSAGES'
|
||||
const WINDOWING_KEY = 'HERMES_TUI_WINDOWING'
|
||||
const prev = process.env[ENV_KEY]
|
||||
const prevWindowing = process.env[WINDOWING_KEY]
|
||||
afterEach(() => {
|
||||
if (prev === undefined) delete process.env[ENV_KEY]
|
||||
else process.env[ENV_KEY] = prev
|
||||
if (prevWindowing === undefined) delete process.env[WINDOWING_KEY]
|
||||
else process.env[WINDOWING_KEY] = prevWindowing
|
||||
})
|
||||
|
||||
test('caps the message array at the env-tuned MESSAGE_CAP, dropping the oldest (head)', () => {
|
||||
|
|
@ -578,23 +582,37 @@ describe('session store — rolling message cap (bounds the Yoga node high-water
|
|||
expect(store.state.messages.at(-1)!.text).toBe('h7')
|
||||
})
|
||||
|
||||
test('defaults to 1000 (the handle-safe ceiling) when the env var is unset/invalid', () => {
|
||||
test('defaults to 3000 (windowed ceiling) when the env var is unset/invalid and windowing is on', () => {
|
||||
// With transcript windowing (the default) the mounted set is ~3 viewports
|
||||
// regardless of store size, so the scrollback ceiling is 3000 (#27 payoff).
|
||||
delete process.env[ENV_KEY]
|
||||
delete process.env[WINDOWING_KEY]
|
||||
const store = createSessionStore()
|
||||
for (let i = 0; i < 1050; i++) store.pushUser(`m${i}`)
|
||||
expect(store.state.messages).toHaveLength(1000)
|
||||
for (let i = 0; i < 3050; i++) store.pushUser(`m${i}`)
|
||||
expect(store.state.messages).toHaveLength(3000)
|
||||
expect(store.state.messages[0]!.text).toBe('m50') // oldest 50 dropped
|
||||
})
|
||||
|
||||
test('env values ABOVE the handle-safe ceiling are clamped to it (the native handle table binds, not memory)', () => {
|
||||
test('HERMES_TUI_WINDOWING=0 keeps the handle-safe 1000 ceiling (every row mounts again)', () => {
|
||||
delete process.env[ENV_KEY]
|
||||
process.env[WINDOWING_KEY] = '0'
|
||||
const store = createSessionStore()
|
||||
for (let i = 0; i < 1050; i++) store.pushUser(`m${i}`)
|
||||
expect(store.state.messages).toHaveLength(1000)
|
||||
expect(store.state.messages[0]!.text).toBe('m50')
|
||||
})
|
||||
|
||||
test('env values ABOVE the ceiling are clamped to it (the native handle table binds, not memory)', () => {
|
||||
// @opentui/core's global handle registry holds 65,534 live objects and a
|
||||
// text renderable costs 3; ~47 handles/row on the realistic fixture means
|
||||
// ≳1,400 live rows crashes mid-mount ("Failed to create SyntaxStyle").
|
||||
// A 100000 "cap" is therefore a crash sentence, not a cap — clamp it.
|
||||
// ≳1,400 live MOUNTED rows crashes mid-mount ("Failed to create
|
||||
// SyntaxStyle"). Windowing bounds the mounted set (peak 31 measured), so
|
||||
// the windowed ceiling is 3000 stored rows; a 100000 "cap" still clamps.
|
||||
process.env[ENV_KEY] = '100000'
|
||||
delete process.env[WINDOWING_KEY]
|
||||
const store = createSessionStore()
|
||||
for (let i = 0; i < 1100; i++) store.pushUser(`m${i}`)
|
||||
expect(store.state.messages).toHaveLength(1000)
|
||||
for (let i = 0; i < 3100; i++) store.pushUser(`m${i}`)
|
||||
expect(store.state.messages).toHaveLength(3000)
|
||||
expect(store.state.dropped).toBe(100)
|
||||
expect(store.state.messages[0]!.text).toBe('m100')
|
||||
})
|
||||
|
|
|
|||
|
|
@ -153,7 +153,18 @@ function clipScrollbar(frame: string): string {
|
|||
|
||||
describe('transcript windowing — S2 append-time adjudication', () => {
|
||||
test('bursting 1500 appends keeps the peak mounted-row count bounded (< 120)', async () => {
|
||||
const onStore = createSessionStore()
|
||||
// Pin the cap below the burst size: this test ALSO exercises the
|
||||
// cap-trim × windowing interplay (trimmed rows' spacers must be pruned,
|
||||
// scroll-to-top must land on the oldest SURVIVOR, row-500). The default
|
||||
// ceiling is 3000 under windowing (#27) — a 1500-row burst never trims it.
|
||||
process.env.HERMES_TUI_MAX_MESSAGES = '1000'
|
||||
const onStore = (() => {
|
||||
try {
|
||||
return createSessionStore()
|
||||
} finally {
|
||||
delete process.env.HERMES_TUI_MAX_MESSAGES
|
||||
}
|
||||
})()
|
||||
onStore.apply({ type: 'gateway.ready' })
|
||||
const on = await mountTranscript(onStore, '1')
|
||||
try {
|
||||
|
|
@ -172,7 +183,14 @@ describe('transcript windowing — S2 append-time adjudication', () => {
|
|||
// ZERO-JANK INVARIANT survives the burst: spacers (measured or
|
||||
// estimated — these rows never soft-wrap) occupy EXACTLY the height the
|
||||
// full tree would. (The store cap trims both to the same 1000 rows.)
|
||||
const offStore = createSessionStore()
|
||||
process.env.HERMES_TUI_MAX_MESSAGES = '1000'
|
||||
const offStore = (() => {
|
||||
try {
|
||||
return createSessionStore()
|
||||
} finally {
|
||||
delete process.env.HERMES_TUI_MAX_MESSAGES
|
||||
}
|
||||
})()
|
||||
offStore.apply({ type: 'gateway.ready' })
|
||||
for (let i = 0; i < 1500; i++) {
|
||||
offStore.pushSystem(i % 5 === 4 ? `row-${i} marker\nsecond line\nthird line` : `row-${i} marker`)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue