refactor(term): document and test mode 2031 unsubscribe replay safety

Explain why the `CSI ?2031l` handler is intentionally not replay-guarded
(we only push CSI 997 on subscribe, so clearing stale bookkeeping during
replay is a no-op a later real `?2031h` restores) and extend the test to
actually exercise the replay path, not just the non-replay path.
This commit is contained in:
brennanb2025
2026-04-24 17:36:44 -07:00
parent eae06d9280
commit c8a08de961
2 changed files with 17 additions and 0 deletions
@@ -245,6 +245,7 @@ describe('installMode2031Handlers', () => {
// on subscribe, never on unsubscribe.
const h = setup()
try {
// Non-replay path: subscribe then unsubscribe clears state.
await writeSync(h.term, '\x1b[?2031h')
h.paneLastThemeMode.set(1, 'dark')
expect(h.paneMode2031.get(1)).toBe(true)
@@ -252,6 +253,18 @@ describe('installMode2031Handlers', () => {
await writeSync(h.term, '\x1b[?2031l')
expect(h.paneMode2031.has(1)).toBe(false)
expect(h.paneLastThemeMode.has(1)).toBe(false)
// Replay path: resubscribe, then receive `?2031l` during a replay
// window. The `l` handler must still clear — this is the invariant
// promised by the test name.
await writeSync(h.term, '\x1b[?2031h')
h.paneLastThemeMode.set(1, 'dark')
expect(h.paneMode2031.get(1)).toBe(true)
replayIntoTerminal(h.pane, h.replayingPanesRef, '\x1b[?2031l')
await new Promise<void>((resolve) => h.term.write('', resolve))
expect(h.paneMode2031.has(1)).toBe(false)
expect(h.paneLastThemeMode.has(1)).toBe(false)
} finally {
h.dispose()
}
@@ -86,6 +86,10 @@ export function installMode2031Handlers(deps: Mode2031HandlerDeps): IDisposable[
}
return false
}),
// Why no replay guard on the unsubscribe branch: clearing stale bookkeeping
// is harmless. We only push CSI 997 on subscribe, never on unsubscribe, so
// even if a cold-restore replay carries `?2031l`, this handler just deletes
// map entries that a later real `?2031h` will re-populate normally.
deps.parser.registerCsiHandler({ prefix: '?', final: 'l' }, (params) => {
if (hasMode2031(params)) {
deps.paneMode2031.delete(deps.paneId)