Repository: cli/cli (go)

Issue:
Accessible prompter: default selections not always printed, not clear, or sometimes not readable by speech synthesis
### Describe the bug

Default values in the accessible prompter are not indicated and sometimes not respected at all like they are with the other prompter.

### Steps to reproduce the behavior

As one example, the multi select form does not indicate which options are selected aside from the checkmarks, which as far as I can tell, are _not guaranteed to be audible by speech synthesis_.

```
Assignees
1.   ✓ BagToad

Select up to 1 options. 0 to continue.

```

Another example is the select form, which does not indicate the default selection at all - below if I press enter, `BagToad` is selected.

```
Repository owner
1. BagToad
<snip>
Input a number between 1 and 17: 
```

Another example is the text input, which has a couple issues. It displays empty parenthesis when there is no default, and displays no _readable text_ that indicates what is being spoken is the default value.

```
Repository name ()

Input: 
```

This list is not exhaustive - we need to look through all the prompters and harmonize how we display defaults 🤔 

### Expected vs actual behavior

Every a11y prompt needs to display its defaults in a way that is readable by speech synthesis. Brief example:

```
Would you like to add a .gitignore? (default: No)

Choose [y/N]: 
```

☝ The change being the addition of `(default: No)`

Pull request:
Accessible prompter always displays defaults in speech synthesis readable format
Fixes #10936 

> [!NOTE]
> I encourage reviewers to try out this branch with a screen reader because I did and it sounds quite good in my opinion 😄  

Proposed experience demo with `repo create`:

```
What would you like to do? 
1. Create a new repository on github.com from scratch
2. Create a new repository on github.com from a template repository
3. Push an existing local repository to github.com
Input a number between 1 and 3: 1

Repository name testing-a11y

Repository owner (default: BagToad) 
1. BagToad
<snip>
Input a number between 1 and 17: 

Description testing

Visibility (default: Public) 
1. Public
2. Private
Input a number between 1 and 2: 2

Would you like to add a README file? (default: no) [y/N] 

Would you like to add a .gitignore? (default: no) [y/N] 

Would you like to add a license? (default: no) [y/N] 

This will create "testing-a11y" as a private repository on github.com. Continue? (default: yes) [Y/n] 
```

Proposed experience demo with `gh issue edit`, showing multi select defaults:

```
Assignees (defaults: BagToad, Evil-Bagtoad) 
Select up to 3 options.
1. ✓ BagToad
2.   Fred
3. ✓ Evil-Bagtoad
0.   Confirm selection
Input a number between 0 and 3: 
```

Patch:
diff --git a/internal/prompter/prompter.go b/internal/prompter/prompter.go
index 1e4f5592a..c2233fd92 100644
--- a/internal/prompter/prompter.go
+++ b/internal/prompter/prompter.go
@@ -77,10 +77,40 @@ func (p *accessiblePrompter) newForm(groups ...*huh.Group) *huh.Form {
 		WithOutput(p.stdout)
 }
 
-func (p *accessiblePrompter) Select(prompt, _ string, options []string) (int, error) {
+// addDefaultsToPrompt adds default values to the prompt string.
+func (p *accessiblePrompter) addDefaultsToPrompt(prompt string, defaultValues []string) string {
+	// Removing empty defaults from the slice.
+	defaultValues = slices.DeleteFunc(defaultValues, func(s string) bool {
+		return s == ""
+	})
+
+	// Pluralizing the prompt if there are multiple default values.
+	if len(defaultValues) == 1 {
+		prompt = fmt.Sprintf("%s (default: %s)", prompt, defaultValues[0])
+	} else if len(defaultValues) > 1 {
+		prompt = fmt.Sprintf("%s (defaults: %s)", prompt, strings.Join(defaultValues, ", "))
+	}
+
+	// Zero-length defaultValues means return prompt unchanged.
+	return prompt
+}
+
+func (p *accessiblePrompter) Select(prompt, defaultValue string, options []string) (int, error) {
 	var result int
+
+	// Remove invalid default values from the defaults slice.
+	if !slices.Contains(options, defaultValue) {
+		defaultValue = ""
+	}
+
+	prompt = p.addDefaultsToPrompt(prompt, []string{defaultValue})
 	formOptions := []huh.Option[int]{}
 	for i, o := range options {
+		// If this option is the default value, assign its index
+		// to the result variable. huh will treat it as a default selection.
+		if defaultValue == o {
+			result = i
+		}
 		formOptions = append(formOptions, huh.NewOption(o, i))
 	}
 
@@ -99,12 +129,18 @@ func (p *accessiblePrompter) Select(prompt, _ string, options []string) (int, er
 
 func (p *accessiblePrompter) MultiSelect(prompt string, defaults []string, options []string) ([]int, error) {
 	var result []int
+
+	// Remove invalid default values from the defaults slice.
+	defaults = slices.DeleteFunc(defaults, func(s string) bool {
+		return !slices.Contains(options, s)
+	})
+
+	prompt = p.addDefaultsToPrompt(prompt, defaults)
 	formOptions := make([]huh.Option[int], len(options))
 	for i, o := range options {
 		// If this option is in the defaults slice,
 		// let's add its index to the result slice and huh
 		// will treat it as a default selection.
-		// TODO: does an invalid default value constitute a panic?
 		if slices.Contains(defaults, o) {
 			result = append(result, i)
 		}
@@ -131,7 +167,7 @@ func (p *accessiblePrompter) MultiSelect(prompt string, defaults []string, optio
 
 func (p *accessiblePrompter) Input(prompt, defaultValue string) (string, error) {
 	result := defaultValue
-	prompt = fmt.Sprintf("%s (%s)", prompt, defaultValue)
+	prompt = p.addDefaultsToPrompt(prompt, []string{defaultValue})
 	form := p.newForm(
 		huh.NewGroup(
 			huh.NewInput().
@@ -167,6 +203,13 @@ func (p *accessiblePrompter) Password(prompt string) (string, error) {
 
 func (p *accessiblePrompter) Confirm(prompt string, defaultValue bool) (bool, error) {
 	result := defaultValue
+
+	if defaultValue {
+		prompt = p.addDefaultsToPrompt(prompt, []string{"yes"})
+	} else {
+		prompt = p.addDefaultsToPrompt(prompt, []string{"no"})
+	}
+
 	form := p.newForm(
 		huh.NewGroup(
 			huh.NewConfirm().
@@ -174,6 +217,7 @@ func (p *accessiblePrompter) Confirm(prompt string, defaultValue bool) (bool, er
 				Value(&result),
 		),
 	)
+
 	if err := form.Run(); err != nil {
 		return false, err
 	}


Test patch:
diff --git a/internal/prompter/accessible_prompter_test.go b/internal/prompter/accessible_prompter_test.go
index ed4da8de8..b2a66141a 100644
--- a/internal/prompter/accessible_prompter_test.go
+++ b/internal/prompter/accessible_prompter_test.go
@@ -5,6 +5,7 @@ package prompter_test
 import (
 	"fmt"
 	"io"
+	"slices"
 	"strings"
 	"testing"
 	"time"
@@ -54,6 +55,73 @@ func TestAccessiblePrompter(t *testing.T) {
 		assert.Equal(t, 0, selectValue)
 	})
 
+	t.Run("Select - blank input returns default value", func(t *testing.T) {
+		console := newTestVirtualTerminal(t)
+		p := newTestAccessiblePrompter(t, console)
+		dummyDefaultValue := "12345abcdefg"
+		options := []string{"1", "2", dummyDefaultValue}
+
+		go func() {
+			// Wait for prompt to appear
+			_, err := console.ExpectString("Input a number between 1 and 3:")
+			require.NoError(t, err)
+
+			// Just press enter to accept the default
+			_, err = console.SendLine("")
+			require.NoError(t, err)
+		}()
+
+		selectValue, err := p.Select("Select a number", dummyDefaultValue, options)
+		require.NoError(t, err)
+
+		expectedIndex := slices.Index(options, dummyDefaultValue)
+		assert.Equal(t, expectedIndex, selectValue)
+	})
+
+	t.Run("Select - default value is in prompt and in readable format", func(t *testing.T) {
+		console := newTestVirtualTerminal(t)
+		p := newTestAccessiblePrompter(t, console)
+		dummyDefaultValue := "12345abcdefg"
+		options := []string{"1", "2", dummyDefaultValue}
+
+		go func() {
+			// Wait for prompt to appear
+			_, err := console.ExpectString("Select a number (default: 12345abcdefg)")
+			require.NoError(t, err)
+
+			// Just press enter to accept the default
+			_, err = console.SendLine("")
+			require.NoError(t, err)
+		}()
+
+		selectValue, err := p.Select("Select a number", dummyDefaultValue, options)
+		require.NoError(t, err)
+
+		expectedIndex := slices.Index(options, dummyDefaultValue)
+		assert.Equal(t, expectedIndex, selectValue)
+	})
+
+	t.Run("Select - invalid defaults are excluded from prompt", func(t *testing.T) {
+		console := newTestVirtualTerminal(t)
+		p := newTestAccessiblePrompter(t, console)
+		dummyDefaultValue := "foo"
+		options := []string{"1", "2"}
+
+		go func() {
+			// Wait for prompt to appear without the invalid default value
+			_, err := console.ExpectString("Select a number \r\n")
+			require.NoError(t, err)
+
+			// Select option 2
+			_, err = console.SendLine("2")
+			require.NoError(t, err)
+		}()
+
+		selectValue, err := p.Select("Select a number", dummyDefaultValue, options)
+		require.NoError(t, err)
+		assert.Equal(t, 1, selectValue)
+	})
+
 	t.Run("MultiSelect", func(t *testing.T) {
 		console := newTestVirtualTerminal(t)
 		p := newTestAccessiblePrompter(t, console)
@@ -100,6 +168,62 @@ func TestAccessiblePrompter(t *testing.T) {
 		assert.Equal(t, []int{1}, multiSelectValue)
 	})
 
+	t.Run("MultiSelect - default value is in prompt and in readable format", func(t *testing.T) {
+		console := newTestVirtualTerminal(t)
+		p := newTestAccessiblePrompter(t, console)
+		dummyDefaultValues := []string{"foo", "bar"}
+		options := []string{"1", "2"}
+		options = append(options, dummyDefaultValues...)
+
+		go func() {
+			// Wait for prompt to appear
+			_, err := console.ExpectString("Select a number (defaults: foo, bar)")
+			require.NoError(t, err)
+
+			// Don't select anything because the defaults should be selected.
+
+			// This confirms selections
+			_, err = console.SendLine("0")
+			require.NoError(t, err)
+		}()
+
+		multiSelectValues, err := p.MultiSelect("Select a number", dummyDefaultValues, options)
+		require.NoError(t, err)
+		var expectedIndices []int
+
+		// Get the indices of the default values within the options slice
+		// as that's what we expect the prompter to return when no selections are made.
+		for _, defaultValue := range dummyDefaultValues {
+			expectedIndices = append(expectedIndices, slices.Index(options, defaultValue))
+		}
+		assert.Equal(t, expectedIndices, multiSelectValues)
+	})
+
+	t.Run("MultiSelect - invalid defaults are excluded from prompt", func(t *testing.T) {
+		console := newTestVirtualTerminal(t)
+		p := newTestAccessiblePrompter(t, console)
+		dummyDefaultValues := []string{"foo", "bar"}
+		options := []string{"1", "2"}
+
+		go func() {
+			// Wait for prompt to appear without the invalid default values
+			_, err := console.ExpectString("Select a number \r\n")
+			require.NoError(t, err)
+
+			// Not selecting anything will fail because there are no defaults.
+			_, err = console.SendLine("2")
+			require.NoError(t, err)
+
+			// This confirms selections
+			_, err = console.SendLine("0")
+			require.NoError(t, err)
+		}()
+
+		multiSelectValues, err := p.MultiSelect("Select a number", dummyDefaultValues, options)
+		require.NoError(t, err)
+		assert.Equal(t, []int{1}, multiSelectValues)
+	})
+
 	t.Run("Input", func(t *testing.T) {
 		console := newTestVirtualTerminal(t)
 		p := newTestAccessiblePrompter(t, console)
@@ -140,6 +264,26 @@ func TestAccessiblePrompter(t *testing.T) {
 		assert.Equal(t, dummyDefaultValue, inputValue)
 	})
 
+	t.Run("Input - default value is in prompt and in readable format", func(t *testing.T) {
+		console := newTestVirtualTerminal(t)
+		p := newTestAccessiblePrompter(t, console)
+		dummyDefaultValue := "12345abcdefg"
+
+		go func() {
+			// Wait for prompt to appear
+			_, err := console.ExpectString("Enter some characters (default: 12345abcdefg)")
+			require.NoError(t, err)
+
+			// Enter nothing
+			_, err = console.SendLine("")
+			require.NoError(t, err)
+		}()
+
+		inputValue, err := p.Input("Enter some characters", dummyDefaultValue)
+		require.NoError(t, err)
+		assert.Equal(t, dummyDefaultValue, inputValue)
+	})
+
 	t.Run("Password", func(t *testing.T) {
 		console := newTestVirtualTerminal(t)
 		p := newTestAccessiblePrompter(t, console)
@@ -206,6 +350,26 @@ func TestAccessiblePrompter(t *testing.T) {
 		require.Equal(t, false, confirmValue)
 	})
 
+	t.Run("Confirm - default value is in prompt and in readable format", func(t *testing.T) {
+		console := newTestVirtualTerminal(t)
+		p := newTestAccessiblePrompter(t, console)
+		defaultValue := true
+
+		go func() {
+			// Wait for prompt to appear
+			_, err := console.ExpectString("Are you sure (default: yes)")
+			require.NoError(t, err)
+
+			// Enter nothing
+			_, err = console.SendLine("")
+			require.NoError(t, err)
+		}()
+
+		confirmValue, err := p.Confirm("Are you sure", defaultValue)
+		require.NoError(t, err)
+		require.Equal(t, defaultValue, confirmValue)
+	})
+
 	t.Run("AuthToken", func(t *testing.T) {
 		console := newTestVirtualTerminal(t)
 		p := newTestAccessiblePrompter(t, console)