fix(tui): make /clear confirm window humane (3s → 30s, reset on other slash)

The 3s gate was too tight — users reading the prompt and retyping
consistently blow past it and get stuck in a loop ("press /clear
again within 3s" forever). Fixes:

- bump CONFIRM_WINDOW_MS 3_000 → 30_000
- drop the time number from the confirmation message to remove the
  pressure vibe: "press /clear again to confirm — starts a new session"
- reset the gate from createSlashHandler whenever any non-destructive
  slash command runs, so stale arming from 20s ago can't silently
  turn the next /clear into an unintended confirm
- export the gate + isDestructiveCommand helper for that wiring
- add armed() introspection method

Follow-up to #4069 / 3366714b.
This commit is contained in:
Brooklyn Nicholson 2026-04-18 17:55:53 -05:00
parent 20eab355e7
commit 75377feb07
4 changed files with 33 additions and 5 deletions

View file

@ -3,6 +3,7 @@ import type { SlashExecResponse } from '../gatewayTypes.js'
import { asCommandDispatch, rpcErrorMessage } from '../lib/rpc.js'
import type { SlashHandlerContext } from './interfaces.js'
import { destructiveGate, isDestructiveCommand } from './slash/commands/core.js'
import { findSlashCommand } from './slash/registry.js'
import type { SlashRunCtx } from './slash/types.js'
import { getUiState } from './uiStore.js'
@ -40,11 +41,17 @@ export function createSlashHandler(ctx: SlashHandlerContext): (cmd: string) => b
const found = findSlashCommand(parsed.name)
if (found) {
if (!isDestructiveCommand(found.name)) {
destructiveGate.reset()
}
found.run(parsed.arg, runCtx, cmd)
return true
}
destructiveGate.reset()
if (catalog?.canon) {
const needle = `/${parsed.name}`.toLowerCase()

View file

@ -15,7 +15,11 @@ import { patchOverlayState } from '../../overlayStore.js'
import { patchUiState } from '../../uiStore.js'
import type { SlashCommand } from '../types.js'
const destructiveGate = createDestructiveGate()
export const destructiveGate = createDestructiveGate()
const DESTRUCTIVE_COMMANDS = new Set(['clear', 'new'])
export const isDestructiveCommand = (name: string) => DESTRUCTIVE_COMMANDS.has(name)
const flagFromArg = (arg: string, current: boolean): boolean | null => {
if (!arg) {
@ -89,7 +93,7 @@ export const coreCommands: SlashCommand[] = [
const label = cmd.startsWith('/new') ? '/new' : '/clear'
if (!NO_CONFIRM_DESTRUCTIVE && !destructiveGate.request('clear')) {
return ctx.transcript.sys(`press ${label} again within 3s to confirm (starts a new session)`)
return ctx.transcript.sys(`press ${label} again to confirm — starts a new session`)
}
patchUiState({ status: 'forging session…' })