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:
alt-glitch 2026-06-12 08:40:53 +05:30
parent c5806b9ad9
commit fc8d5f203a
4 changed files with 49 additions and 16 deletions

View file

@ -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,

View file

@ -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()

View file

@ -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')
})

View file

@ -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`)