Skip to content

Pressing Enter confirms prompts with an uppercase default#93

Merged
carole-lavillonniere merged 3 commits intomainfrom
press-enter
Mar 10, 2026
Merged

Pressing Enter confirms prompts with an uppercase default#93
carole-lavillonniere merged 3 commits intomainfrom
press-enter

Conversation

@carole-lavillonniere
Copy link
Collaborator

@carole-lavillonniere carole-lavillonniere commented Mar 10, 2026

  • Case-insensitive key matching for input prompts (e.g. pressing Y now works, not just y)
  • Pressing Enter selects the option with an uppercase label as the default — no need to explicitly add {Key: "enter"} to every prompt definition; just capitalize the label (e.g. {Key: "y", Label: "Y"})

Example: Pressing Enter or y here will confirm as well
image

@carole-lavillonniere carole-lavillonniere changed the title fix(ui): pressing Enter confirms prompts with an uppercase default Pressing Enter confirms prompts with an uppercase default Mar 10, 2026
@carole-lavillonniere carole-lavillonniere marked this pull request as ready for review March 10, 2026 10:53
@coderabbitai
Copy link

coderabbitai bot commented Mar 10, 2026

📝 Walkthrough

Walkthrough

Input handling was changed to resolve pending selections via a new prioritized match: an "any" option, explicit "enter" key, an uppercase-label default, then case-insensitive key match. A new resolveOption helper and hasLetters (unicode-based) were added; tests expanded to cover these behaviors.

Changes

Cohort / File(s) Summary
Input resolution & helpers
internal/ui/app.go
Added resolveOption to pick an option by priority: "any" first, then "enter", then first option whose Label is letters-only and all-uppercase, then case-insensitive Key match. Added hasLetters using unicode.IsLetter. Reorganized Update flow to use this resolution and emit selection, clear pending input, hide prompt, and return the generated command on match. Imported unicode.
Tests (behavior & resolution)
internal/ui/app_test.go
Renamed TestAppEnterDoesNothingWithoutExplicitEnterOptionTestAppEnterSelectsUppercaseLabelDefault. Added TestAppEnterDoesNothingWithoutDefault, TestAppEnterDoesNothingWithNonLetterLabel, and TestResolveOption to validate priority order (any, enter, uppercase default, case-insensitive), edge cases with no matches, and non-letter labels. Updated expectations accordingly.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main feature added: pressing Enter now confirms prompts when an option has an uppercase label as the default.
Description check ✅ Passed The PR description clearly relates to the changeset, detailing case-insensitive key matching and Enter key confirmation using uppercase label defaults.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch press-enter

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
internal/ui/app_test.go (1)

242-310: Add a test for uppercase shortcut input too.

The production change on Line 130 of internal/ui/app.go also accepts Y for an option keyed as y, but the new coverage only exercises the Enter/default path. A small tea.KeyRunes{'Y'} case would lock in the other advertised behavior from this PR.

🧪 Suggested test
+func TestAppAcceptsUppercaseShortcutKey(t *testing.T) {
+	t.Parallel()
+
+	app := NewApp("dev", "", "", nil)
+	responseCh := make(chan output.InputResponse, 1)
+
+	model, _ := app.Update(output.UserInputRequestEvent{
+		Prompt: "Open browser now?",
+		Options: []output.InputOption{
+			{Key: "y", Label: "Y"},
+			{Key: "n", Label: "n"},
+		},
+		ResponseCh: responseCh,
+	})
+	app = model.(App)
+
+	model, cmd := app.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'Y'}})
+	app = model.(App)
+	if cmd == nil {
+		t.Fatal("expected response command for uppercase shortcut")
+	}
+	cmd()
+
+	select {
+	case resp := <-responseCh:
+		if resp.SelectedKey != "y" {
+			t.Fatalf("expected y key, got %q", resp.SelectedKey)
+		}
+	case <-time.After(time.Second):
+		t.Fatal("timed out waiting for response on channel")
+	}
+}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/ui/app_test.go` around lines 242 - 310, Add a test exercising the
uppercase shortcut input (pressing 'Y') for an option keyed "y": after creating
the App and sending the UserInputRequestEvent (same pattern as
TestAppEnterSelectsUppercaseLabelDefault), send a tea.KeyMsg with Type:
tea.KeyRunes and Runes: []rune{'Y'} to app.Update, execute the returned command
if non-nil, then assert the responseCh receives SelectedKey "y" and that
app.inputPrompt.Visible() is false; you can add this as a new test function
(e.g., TestAppUppercaseShortcutSelectsLowercaseKey) or extend the existing
TestAppEnterSelectsUppercaseLabelDefault to include the tea.KeyRunes{'Y'} case.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@internal/ui/app.go`:
- Around line 117-124: The current default-selection check in the loop over
a.pendingInput.Options (where it uses opt.Label == strings.ToUpper(opt.Label))
mistakenly treats non-letter labels like "1" or "?" as uppercase; update that
condition to require the label actually contains at least one letter before
treating it as the conventional uppercase default — e.g., add a guard that the
label has at least one letter (use unicode.IsLetter on runes or an ASCII A-Z
check) in the same if that checks opt.Label == strings.ToUpper(opt.Label), then
proceed to appendLine/formatResolvedInput and sendInputResponseCmd only when
both conditions pass.

---

Nitpick comments:
In `@internal/ui/app_test.go`:
- Around line 242-310: Add a test exercising the uppercase shortcut input
(pressing 'Y') for an option keyed "y": after creating the App and sending the
UserInputRequestEvent (same pattern as
TestAppEnterSelectsUppercaseLabelDefault), send a tea.KeyMsg with Type:
tea.KeyRunes and Runes: []rune{'Y'} to app.Update, execute the returned command
if non-nil, then assert the responseCh receives SelectedKey "y" and that
app.inputPrompt.Visible() is false; you can add this as a new test function
(e.g., TestAppUppercaseShortcutSelectsLowercaseKey) or extend the existing
TestAppEnterSelectsUppercaseLabelDefault to include the tea.KeyRunes{'Y'} case.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro

Run ID: 0ff23b91-89db-46de-8963-766f6dd32495

📥 Commits

Reviewing files that changed from the base of the PR and between cd3f6b3 and 88dcc1a.

📒 Files selected for processing (2)
  • internal/ui/app.go
  • internal/ui/app_test.go

}
}
// fall back to the option with an uppercase label (conventional default)
for _, opt := range a.pendingInput.Options {
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we need to iterate twice on the pendingInput.Options (line 107), or could we move the new if block up there?
Seems like most of the things done in these two blocks are similar.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Good catch, there were 4 identical loops in there 🙈
Extracted to a helper func with unit tests: 6d36242

Copy link
Contributor

@anisaoshafi anisaoshafi left a comment

Choose a reason for hiding this comment

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

LGTM 🙌🏼 just added one question

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
internal/ui/app_test.go (1)

242-343: ⚠️ Potential issue | 🟡 Minor

Add a regression test for uppercase rune matching.

These new tests cover the Enter-default branches well, but they never exercise the other behavior introduced by this PR: case-insensitive key matching. A regression where tea.KeyRunes{'Y'} no longer resolves {Key: "y"} would still pass this suite.

Proposed test
+func TestAppMatchesInputOptionsCaseInsensitively(t *testing.T) {
+	t.Parallel()
+
+	app := NewApp("dev", "", "", nil)
+	responseCh := make(chan output.InputResponse, 1)
+
+	model, _ := app.Update(output.UserInputRequestEvent{
+		Prompt: "Open browser now?",
+		Options: []output.InputOption{
+			{Key: "y", Label: "Y"},
+			{Key: "n", Label: "n"},
+		},
+		ResponseCh: responseCh,
+	})
+	app = model.(App)
+
+	model, cmd := app.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'Y'}})
+	app = model.(App)
+	if cmd == nil {
+		t.Fatal("expected response command for uppercase keypress")
+	}
+	cmd()
+
+	select {
+	case resp := <-responseCh:
+		if resp.SelectedKey != "y" {
+			t.Fatalf("expected y key, got %q", resp.SelectedKey)
+		}
+	case <-time.After(time.Second):
+		t.Fatal("timed out waiting for response on channel")
+	}
+
+	if app.inputPrompt.Visible() {
+		t.Fatal("expected input prompt to be hidden after response")
+	}
+}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/ui/app_test.go` around lines 242 - 343, Add a regression test to
exercise case-insensitive rune matching: when an input prompt contains an option
with Key "y" and Label "y", sending a tea.KeyMsg whose Runes contain 'Y' (e.g.,
tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'Y'}}) should produce the same
response command and select "y"; update or add a test (e.g.,
TestAppEnterSelectsCaseInsensitiveRune) that constructs the App, sends
output.UserInputRequestEvent with Options including {Key: "y", Label: "y"},
captures the responseCh, calls app.Update with the KeyRunes message, executes
the returned cmd if non-nil, and asserts the responseCh yields SelectedKey "y"
and that app.inputPrompt is hidden.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@internal/ui/app_test.go`:
- Around line 242-343: Add a regression test to exercise case-insensitive rune
matching: when an input prompt contains an option with Key "y" and Label "y",
sending a tea.KeyMsg whose Runes contain 'Y' (e.g., tea.KeyMsg{Type:
tea.KeyRunes, Runes: []rune{'Y'}}) should produce the same response command and
select "y"; update or add a test (e.g., TestAppEnterSelectsCaseInsensitiveRune)
that constructs the App, sends output.UserInputRequestEvent with Options
including {Key: "y", Label: "y"}, captures the responseCh, calls app.Update with
the KeyRunes message, executes the returned cmd if non-nil, and asserts the
responseCh yields SelectedKey "y" and that app.inputPrompt is hidden.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro

Run ID: cc386c94-b482-4979-8e6f-696561a43952

📥 Commits

Reviewing files that changed from the base of the PR and between 88dcc1a and 1856171.

📒 Files selected for processing (2)
  • internal/ui/app.go
  • internal/ui/app_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • internal/ui/app.go

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@internal/ui/app.go`:
- Around line 264-279: In resolveOption, the current early returns make
precedence depend on option order; instead do a single scan to capture pointers
for anyOption (opt.Key == "any"), enterOption (opt.Key == "enter"),
uppercaseDefault (opt.Label uppercase when msg.Type == tea.KeyEnter), and a
caseInsensitiveMatch (strings.EqualFold(msg.String(), opt.Key) when msg.Type !=
tea.KeyEnter), then apply the fixed precedence: if anyOption != nil return it;
else if msg.Type == tea.KeyEnter and enterOption != nil return enterOption; else
if msg.Type == tea.KeyEnter and uppercaseDefault != nil return uppercaseDefault;
else return caseInsensitiveMatch; update resolveOption to use those local
variables (referencing resolveOption, opt.Key, opt.Label, hasLetters,
strings.EqualFold, tea.KeyMsg) rather than returning mid-loop.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro

Run ID: e199a86b-1ba4-4b4e-a116-60c75fa5f944

📥 Commits

Reviewing files that changed from the base of the PR and between 1856171 and 6d36242.

📒 Files selected for processing (2)
  • internal/ui/app.go
  • internal/ui/app_test.go

Comment on lines +264 to +279
func resolveOption(options []output.InputOption, msg tea.KeyMsg) *output.InputOption {
var uppercaseDefault *output.InputOption
for i, opt := range options {
switch {
case opt.Key == "any":
return &options[i]
case msg.Type == tea.KeyEnter && opt.Key == "enter":
return &options[i]
case msg.Type == tea.KeyEnter && uppercaseDefault == nil &&
opt.Label != "" && hasLetters(opt.Label) && opt.Label == strings.ToUpper(opt.Label):
uppercaseDefault = &options[i]
case msg.Type != tea.KeyEnter && strings.EqualFold(msg.String(), opt.Key):
return &options[i]
}
}
return uppercaseDefault
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Honor the documented option precedence regardless of slice order.

Line 275 can return a case-insensitive key match before the loop has seen a later "any" option, and Line 270 does the same for a later "any" when Enter is pressed. That makes the result depend on option ordering even though this helper advertises a fixed priority. For example, [{Key: "y"}, {Key: "any"}] + y currently resolves to "y", not "any".

💡 One way to make the precedence order-independent
 func resolveOption(options []output.InputOption, msg tea.KeyMsg) *output.InputOption {
+	var anyOption *output.InputOption
+	var enterOption *output.InputOption
 	var uppercaseDefault *output.InputOption
+	var keyMatch *output.InputOption
 	for i, opt := range options {
 		switch {
-		case opt.Key == "any":
-			return &options[i]
-		case msg.Type == tea.KeyEnter && opt.Key == "enter":
-			return &options[i]
+		case opt.Key == "any" && anyOption == nil:
+			anyOption = &options[i]
+		case msg.Type == tea.KeyEnter && opt.Key == "enter" && enterOption == nil:
+			enterOption = &options[i]
 		case msg.Type == tea.KeyEnter && uppercaseDefault == nil &&
 			opt.Label != "" && hasLetters(opt.Label) && opt.Label == strings.ToUpper(opt.Label):
 			uppercaseDefault = &options[i]
-		case msg.Type != tea.KeyEnter && strings.EqualFold(msg.String(), opt.Key):
-			return &options[i]
+		case msg.Type != tea.KeyEnter && keyMatch == nil && strings.EqualFold(msg.String(), opt.Key):
+			keyMatch = &options[i]
 		}
 	}
-	return uppercaseDefault
+	if anyOption != nil {
+		return anyOption
+	}
+	if enterOption != nil {
+		return enterOption
+	}
+	if uppercaseDefault != nil {
+		return uppercaseDefault
+	}
+	return keyMatch
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func resolveOption(options []output.InputOption, msg tea.KeyMsg) *output.InputOption {
var uppercaseDefault *output.InputOption
for i, opt := range options {
switch {
case opt.Key == "any":
return &options[i]
case msg.Type == tea.KeyEnter && opt.Key == "enter":
return &options[i]
case msg.Type == tea.KeyEnter && uppercaseDefault == nil &&
opt.Label != "" && hasLetters(opt.Label) && opt.Label == strings.ToUpper(opt.Label):
uppercaseDefault = &options[i]
case msg.Type != tea.KeyEnter && strings.EqualFold(msg.String(), opt.Key):
return &options[i]
}
}
return uppercaseDefault
func resolveOption(options []output.InputOption, msg tea.KeyMsg) *output.InputOption {
var anyOption *output.InputOption
var enterOption *output.InputOption
var uppercaseDefault *output.InputOption
var keyMatch *output.InputOption
for i, opt := range options {
switch {
case opt.Key == "any" && anyOption == nil:
anyOption = &options[i]
case msg.Type == tea.KeyEnter && opt.Key == "enter" && enterOption == nil:
enterOption = &options[i]
case msg.Type == tea.KeyEnter && uppercaseDefault == nil &&
opt.Label != "" && hasLetters(opt.Label) && opt.Label == strings.ToUpper(opt.Label):
uppercaseDefault = &options[i]
case msg.Type != tea.KeyEnter && keyMatch == nil && strings.EqualFold(msg.String(), opt.Key):
keyMatch = &options[i]
}
}
if anyOption != nil {
return anyOption
}
if enterOption != nil {
return enterOption
}
if uppercaseDefault != nil {
return uppercaseDefault
}
return keyMatch
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/ui/app.go` around lines 264 - 279, In resolveOption, the current
early returns make precedence depend on option order; instead do a single scan
to capture pointers for anyOption (opt.Key == "any"), enterOption (opt.Key ==
"enter"), uppercaseDefault (opt.Label uppercase when msg.Type == tea.KeyEnter),
and a caseInsensitiveMatch (strings.EqualFold(msg.String(), opt.Key) when
msg.Type != tea.KeyEnter), then apply the fixed precedence: if anyOption != nil
return it; else if msg.Type == tea.KeyEnter and enterOption != nil return
enterOption; else if msg.Type == tea.KeyEnter and uppercaseDefault != nil return
uppercaseDefault; else return caseInsensitiveMatch; update resolveOption to use
those local variables (referencing resolveOption, opt.Key, opt.Label,
hasLetters, strings.EqualFold, tea.KeyMsg) rather than returning mid-loop.

@carole-lavillonniere carole-lavillonniere merged commit af74352 into main Mar 10, 2026
8 checks passed
@carole-lavillonniere carole-lavillonniere deleted the press-enter branch March 10, 2026 13:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants