diff --git a/src/screen-parser.ts b/src/screen-parser.ts index 7ebec2a..9226f44 100644 --- a/src/screen-parser.ts +++ b/src/screen-parser.ts @@ -182,6 +182,8 @@ const BARE_READY_PROMPT_RE = /^\s*(?:[>❯›]|codex\s*>)\s*$/i; const PICKER_BLOCK_WINDOW_LINES = 32; const PICKER_NUMBERED_OPTION_RE = /^\s*(?:[>❯›]\s*)?(?:[☐☑◉○●◯✓✔]\s*)?\d+\.\s+\S.+$/; +const PICKER_SELECTED_NUMBERED_OPTION_RE = + /^\s*[>❯›]\s*(?:[☐☑◉○●◯✓✔]\s*)?\d+\.\s+\S.+$/; const PICKER_NAVIGATION_FOOTER_RE = /(?:Enter to (?:select|confirm).{0,60}(?:↑\/↓|↑↓).{0,30}navigate|(?:↑\/↓|↑↓)\s+to navigate|Press up to edit queued messages)/i; const CLAUDE_PICKER_HEADER_RE = /^\s*[☐☑]\s+\S.+$/; @@ -704,16 +706,23 @@ function hasPickerNavigationBlock(text: string): boolean { const isQueuedMessageFooter = /Press up to edit queued messages/i.test( footer, ); - const hasClaudePickerChrome = block.some( - (line) => - CLAUDE_PICKER_HEADER_RE.test(line) || - CLAUDE_PICKER_AUX_OPTION_RE.test(line), + const hasClaudePickerHeader = block.some((line) => + CLAUDE_PICKER_HEADER_RE.test(line), ); + const claudePickerAuxOptions = block.filter((line) => + CLAUDE_PICKER_AUX_OPTION_RE.test(line), + ).length; + const hasSelectedNumberedOption = block.some((line) => + PICKER_SELECTED_NUMBERED_OPTION_RE.test(line), + ); + const hasStructuredClaudePicker = + (hasClaudePickerHeader && hasSelectedNumberedOption) || + claudePickerAuxOptions >= 2; if ( (numberedOptions >= 2 || (!isQueuedMessageFooter && hasSelector)) && - (!isQueuedMessageFooter || hasClaudePickerChrome) + (!isQueuedMessageFooter || hasStructuredClaudePicker) ) { return true; } diff --git a/tests/screen-parser.test.ts b/tests/screen-parser.test.ts index 0881086..72fb5f9 100644 --- a/tests/screen-parser.test.ts +++ b/tests/screen-parser.test.ts @@ -146,6 +146,34 @@ Claude Code expect(isPickerOrMenuScreen(queuedComposer, "claude")).toBe(false); }); + it("does not combine ordinary checklist transcript with Claude's queued-message footer", () => { + const queuedComposer = ` +Claude Code + +☐ Delivery follow-up + +1. Added the shared preflight. +2. Verified the regression suite. + +❯ Send the final status after CI completes. +──────────────────────────────────────────────────────────────────────────────── +❯ Press up to edit queued messages +`; + + expect(isPickerOrMenuScreen(queuedComposer, "claude")).toBe(false); + }); + + it("recognizes a structured Claude picker with a queued-message footer", () => { + const picker = readFixture( + "painpoints/claude-ask-user-question-picker-2026-07-13.txt", + ).replace( + "Enter to select · ↑/↓ to navigate · Esc to cancel", + "❯ Press up to edit queued messages", + ); + + expect(isPickerOrMenuScreen(picker, "claude")).toBe(true); + }); + it.each(["codex", "cursor", "gemini"] as const)( "recognizes a footer-driven %s select list without numbered options", (cli) => {