Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 14 additions & 5 deletions src/screen-parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.+$/;
Comment on lines +185 to +186

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Handle glyph-selected queued options

The option matcher just above accepts glyph-only numbered choices such as ◉ 1. ... or ☑ 1. ..., but this new selected-option regex only reports a selection when an arrow also precedes the glyph. For a queued-message Claude picker rendered with a header plus glyph-selected choices and fewer than two auxiliary choices, hasStructuredClaudePicker stays false, so the delivery guard treats the open picker as safe and may type into it.

Useful? React with 👍 / 👎.

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.+$/;
Expand Down Expand Up @@ -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;
}
Expand Down
28 changes: 28 additions & 0 deletions tests/screen-parser.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand Down
Loading