ronantakizawa/codereview-bench huggingface dataset:
Repository: cli/cli
File: pkg/iostreams/iostreams.go (Go)

Code before:
func (s *IOStreams) SetNeverPrompt(v bool) {
	s.neverPrompt = v
}

func (s *IOStreams) StartProgressIndicator() {
	s.StartProgressIndicatorWithLabel("")
}

func (s *IOStreams) StartProgressIndicatorWithLabel(label string) {
	if !s.progressIndicatorEnabled {
		return
	}

	s.progressIndicatorMu.Lock()
	defer s.progressIndicatorMu.Unlock()

	if s.progressIndicator != nil {
		if label == "" {
			s.progressIndicator.Prefix = ""
		} else {
			s.progressIndicator.Prefix = label + " "
		}
		return
	}

	spinnerDisabledValue, spinnerDisabledIsSet := os.LookupEnv("GH_SPINNER_DISABLED")
	falseyValues := []string{"false", "0", "no", ""}

	var spinnerStyle []string
	if spinnerDisabledIsSet && !slices.Contains(falseyValues, spinnerDisabledValue) {
		// This progress indicator must be readable by screen readers
		spinnerStyle = []string{"Working..."}
	} else {
		// https://github.com/briandowns/spinner#available-character-sets
		// ⣾ ⣷ ⣽ ⣻ ⡿
		spinnerStyle = spinner.CharSets[11]
	}

	sp := spinner.New(spinnerStyle, 120*time.Millisecond, spinner.WithWriter(s.ErrOut), spinner.WithColor("fgCyan"))
	if label != "" {
		sp.Prefix = label + " "
	}

	sp.Start()
	s.progressIndicator = sp
}

func (s *IOStreams) StopProgressIndicator() {
	s.progressIndicatorMu.Lock()
	defer s.progressIndicatorMu.Unlock()
	if s.progressIndicator == nil {

Review comment (suggestion):
**suggest:** Though there are some places where the environment variables are evaluated JIT, I would consider whether this should be moved to the IOStream initialization logic in the factory:

https://github.com/cli/cli/blob/408e21ebdddf9cd14289e49135389a6e5125eff4/pkg/cmd/factory/default.go#L273-L310

Code after:
func (s *IOStreams) SetNeverPrompt(v bool) {
	s.neverPrompt = v
}

func (s *IOStreams) GetSpinnerDisabled() bool {
	return s.spinnerDisabled
}

func (s *IOStreams) SetSpinnerDisabled(v bool) {
	s.spinnerDisabled = v
}

func (s *IOStreams) StartProgressIndicator() {
	s.StartProgressIndicatorWithLabel("")
}

func (s *IOStreams) StartProgressIndicatorWithLabel(label string) {
	if !s.progressIndicatorEnabled {
		return
	}

	if s.spinnerDisabled {
		// If the spinner is disabled, simply print a
		// textual progress indicator and return.
		// This means that s.ProgressIndicator will be nil.
		// See also: the comment on StopProgressIndicator()
		s.startTextualProgressIndicator(label)
		return
	}

	s.progressIndicatorMu.Lock()
	defer s.progressIndicatorMu.Unlock()

	if s.progressIndicator != nil {
		if label == "" {
			s.progressIndicator.Prefix = ""
		} else {
			s.progressIndicator.Prefix = label + " "
		}
		return
	}

	// https://github.com/briandowns/spinner#available-character-sets
	// ⣾ ⣷ ⣽ ⣻ ⡿
	spinnerStyle := spinner.CharSets[11]

	sp := spinner.New(spinnerStyle, 120*time.Millisecond, spinner.WithWriter(s.ErrOut), spinner.WithColor("fgCyan"))
	if label != "" {
		sp.Prefix = label + " "
	}
Repository: cli/cli
File: pkg/iostreams/iostreams.go (Go)

Code before:

	sp.Start()
	s.progressIndicator = sp
}

func (s *IOStreams) startTextualProgressIndicator(label string) {
	s.progressIndicatorMu.Lock()
	defer s.progressIndicatorMu.Unlock()

	// Default label when spinner disabled is "Working..."
	if label == "" {
		label = "Working..."
	}

	// Add an ellipsis to the label if it doesn't already have one.
	ellipsis := "..."
	if !strings.HasSuffix(label, ellipsis) {
		label = label + ellipsis
	}

	fmt.Fprintf(s.ErrOut, "%s%s", s.ColorScheme().Cyan(label), "\n")
}

func (s *IOStreams) StopProgressIndicator() {
	s.progressIndicatorMu.Lock()
	defer s.progressIndicatorMu.Unlock()
	if s.progressIndicator == nil {
		return
	}
	s.progressIndicator.Stop()
	s.progressIndicator = nil
}

func (s *IOStreams) RunWithProgress(label string, run func() error) error {
	s.StartProgressIndicatorWithLabel(label)
	defer s.StopProgressIndicator()

	return run()
}

func (s *IOStreams) StartAlternateScreenBuffer() {
	if s.alternateScreenBufferEnabled {
		s.alternateScreenBufferMu.Lock()
		defer s.alternateScreenBufferMu.Unlock()

		if _, err := fmt.Fprint(s.Out, "\x1b[?1049h"); err == nil {
			s.alternateScreenBufferActive = true

			ch := make(chan os.Signal, 1)
			signal.Notify(ch, os.Interrupt)


Review comment (suggestion):
**thought:** I was going to ask about changes to `StopProgressIndicator()`, but I realized its unnecessary as no `IOStreams.progressIndicator` needs to be stopped.

Code after:
	sp := spinner.New(spinnerStyle, 120*time.Millisecond, spinner.WithWriter(s.ErrOut), spinner.WithColor("fgCyan"))
	if label != "" {
		sp.Prefix = label + " "
	}

	sp.Start()
	s.progressIndicator = sp
}

func (s *IOStreams) startTextualProgressIndicator(label string) {
	s.progressIndicatorMu.Lock()
	defer s.progressIndicatorMu.Unlock()

	// Default label when spinner disabled is "Working..."
	if label == "" {
		label = "Working..."
	}

	// Add an ellipsis to the label if it doesn't already have one.
	ellipsis := "..."
	if !strings.HasSuffix(label, ellipsis) {
		label = label + ellipsis
	}

	fmt.Fprintf(s.ErrOut, "%s%s", s.ColorScheme().Cyan(label), "\n")
}

// StopProgressIndicator stops the progress indicator if it is running.
// Note that a textual progess indicator does not create a progress indicator,
// so this method is a no-op in that case.
func (s *IOStreams) StopProgressIndicator() {
	s.progressIndicatorMu.Lock()
	defer s.progressIndicatorMu.Unlock()
	if s.progressIndicator == nil {
		return
	}
	s.progressIndicator.Stop()
	s.progressIndicator = nil
}

func (s *IOStreams) RunWithProgress(label string, run func() error) error {
	s.StartProgressIndicatorWithLabel(label)
	defer s.StopProgressIndicator()

	return run()
}

func (s *IOStreams) StartAlternateScreenBuffer() {
	if s.alternateScreenBufferEnabled {
		s.alternateScreenBufferMu.Lock()
		defer s.alternateScreenBufferMu.Unlock()
Repository: cli/cli
File: pkg/cmd/factory/default_test.go (Go)

Code before:
			env:             map[string]string{"GH_SPINNER_DISABLED": "false"},
			spinnerDisabled: false,
		},
		{
			name:            "spinner disabled via GH_SPINNER_DISABLED env var = no",
			env:             map[string]string{"GH_SPINNER_DISABLED": "no"},
			spinnerDisabled: false,
		},
		{
			name:            "spinner enabled via GH_SPINNER_DISABLED env var = 1",
			env:             map[string]string{"GH_SPINNER_DISABLED": "1"},
			spinnerDisabled: true,
		},
		{
			name:            "spinner enabled via GH_SPINNER_DISABLED env var = true",
			env:             map[string]string{"GH_SPINNER_DISABLED": "true"},
			spinnerDisabled: true,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if tt.env != nil {
				for k, v := range tt.env {
					t.Setenv(k, v)
				}
			}
			f := New("1")
			io := ioStreams(f)
			assert.Equal(t, tt.spinnerDisabled, io.GetSpinnerDisabled())
		})
	}
}

func Test_ioStreams_colorLabels(t *testing.T) {
	tests := []struct {
		name               string
		config             gh.Config
		colorLabelsEnabled bool
		env                map[string]string
	}{
		{
			name:               "default config",
			colorLabelsEnabled: false,
		},
		{
			name:               "config with colorLabels enabled",
			config:             enableColorLabelsConfig(),
			colorLabelsEnabled: true,
		},
		{
			name:               "config with colorLabels disabled",

Review comment (suggestion):
**question:** does the zero value of `map` mean the `nil` logic isn't needed?

```suggestion
			for k, v := range tt.env {
				t.Setenv(k, v)
			}
```

Code after:
			env:             map[string]string{"GH_SPINNER_DISABLED": "false"},
			spinnerDisabled: false,
		},
		{
			name:            "spinner disabled via GH_SPINNER_DISABLED env var = no",
			env:             map[string]string{"GH_SPINNER_DISABLED": "no"},
			spinnerDisabled: false,
		},
		{
			name:            "spinner enabled via GH_SPINNER_DISABLED env var = 1",
			env:             map[string]string{"GH_SPINNER_DISABLED": "1"},
			spinnerDisabled: true,
		},
		{
			name:            "spinner enabled via GH_SPINNER_DISABLED env var = true",
			env:             map[string]string{"GH_SPINNER_DISABLED": "true"},
			spinnerDisabled: true,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			for k, v := range tt.env {
				t.Setenv(k, v)
			}
			f := New("1")
			io := ioStreams(f)
			assert.Equal(t, tt.spinnerDisabled, io.GetSpinnerDisabled())
		})
	}
}

func Test_ioStreams_colorLabels(t *testing.T) {
	tests := []struct {
		name               string
		config             gh.Config
		colorLabelsEnabled bool
		env                map[string]string
	}{
		{
			name:               "default config",
			colorLabelsEnabled: false,
		},
		{
			name:               "config with colorLabels enabled",
			config:             enableColorLabelsConfig(),
			colorLabelsEnabled: true,
		},
		{
			name:               "config with colorLabels disabled",
			config:             disableColorLabelsConfig(),
			colorLabelsEnabled: false,
Repository: cli/cli
File: pkg/cmd/gist/delete/delete_test.go (Go)

Code before:
			httpStubs: func(reg *httpmock.Registry) {
				reg.Register(
					httpmock.REST("DELETE", "gists/1234"),
					httpmock.StatusJSONResponse(500, ghAPI.HTTPError{
						StatusCode: 500,
						Message:    "arbitrary error",
					}),
				)
			},
			hostname:      "github.com",
			gistID:        "1234",
			wantErrString: "HTTP 500: arbitrary error (https://api.github.com/gists/1234)",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			reg := &httpmock.Registry{}
			tt.httpStubs(reg)
			client := api.NewClientFromHTTP(&http.Client{Transport: reg})

			err := deleteGist(client, tt.hostname, tt.gistID)
			if tt.wantErrString != "" {
				assert.EqualError(t, err, tt.wantErrString)
			} else if tt.wantErr != nil {
				assert.ErrorIs(t, err, tt.wantErr)
			} else {
				assert.NoError(t, err)
			}

		})
	}
}


Review comment (suggestion):
Question: can we want an err string and err at the same time? Should this be separate like:

```Go
			if tt.wantErrString != "" {
				assert.EqualError(t, err, tt.wantErrString)
			} 
			
			if tt.wantErr != nil {
				assert.ErrorIs(t, err, tt.wantErr)
                        }
                       // ...
```

Code after:
		{
			name: "when there is a non-404 error deleting the gist, that error is returned",
			httpStubs: func(reg *httpmock.Registry) {
				reg.Register(
					httpmock.REST("DELETE", "gists/1234"),
					httpmock.JSONErrorResponse(500, ghAPI.HTTPError{
						StatusCode: 500,
						Message:    "arbitrary error",
					}),
				)
			},
			hostname:      "github.com",
			gistID:        "1234",
			wantErrString: "HTTP 500: arbitrary error (https://api.github.com/gists/1234)",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			reg := &httpmock.Registry{}
			tt.httpStubs(reg)
			client := api.NewClientFromHTTP(&http.Client{Transport: reg})

			err := deleteGist(client, tt.hostname, tt.gistID)
			if tt.wantErrString == "" && tt.wantErr == nil {
				require.NoError(t, err)
			} else {
				if tt.wantErrString != "" {
					require.EqualError(t, err, tt.wantErrString)
				}
				if tt.wantErr != nil {
					require.ErrorIs(t, err, tt.wantErr)
				}
			}
		})
	}
}
Repository: cli/cli
File: pkg/cmd/gist/delete/delete_test.go (Go)

Code before:
					httpmock.StatusJSONResponse(500, ghAPI.HTTPError{
						StatusCode: 500,
						Message:    "arbitrary error",
					}),
				)
			},
			hostname:      "github.com",
			gistID:        "1234",
			wantErrString: "HTTP 500: arbitrary error (https://api.github.com/gists/1234)",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			reg := &httpmock.Registry{}
			tt.httpStubs(reg)
			client := api.NewClientFromHTTP(&http.Client{Transport: reg})

			err := deleteGist(client, tt.hostname, tt.gistID)
			if tt.wantErrString != "" {
				assert.EqualError(t, err, tt.wantErrString)
			} else if tt.wantErr != nil {
				assert.ErrorIs(t, err, tt.wantErr)
			} else {
				assert.NoError(t, err)
			}

		})
	}
}


Review comment (bug):
Question: can these asserts be `require` to fail the test immediately if the condition is not satisfied? 🙏 

Code after:
				reg.Register(
					httpmock.REST("DELETE", "gists/1234"),
					httpmock.JSONErrorResponse(500, ghAPI.HTTPError{
						StatusCode: 500,
						Message:    "arbitrary error",
					}),
				)
			},
			hostname:      "github.com",
			gistID:        "1234",
			wantErrString: "HTTP 500: arbitrary error (https://api.github.com/gists/1234)",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			reg := &httpmock.Registry{}
			tt.httpStubs(reg)
			client := api.NewClientFromHTTP(&http.Client{Transport: reg})

			err := deleteGist(client, tt.hostname, tt.gistID)
			if tt.wantErrString == "" && tt.wantErr == nil {
				require.NoError(t, err)
			} else {
				if tt.wantErrString != "" {
					require.EqualError(t, err, tt.wantErrString)
				}
				if tt.wantErr != nil {
					require.ErrorIs(t, err, tt.wantErr)
				}
			}
		})
	}
}
Repository: cli/cli
File: pkg/httpmock/registry.go (Go)

Code before:
	"sync"
	"testing"

	"github.com/stretchr/testify/assert"
)

// Replace http.Client transport layer with registry so all requests get
// recorded.
func ReplaceTripper(client *http.Client, reg *Registry) {
	client.Transport = reg
}

type Registry struct {
	mu       sync.Mutex
	stubs    []*Stub
	Requests []*http.Request
}

func (r *Registry) Register(m Matcher, resp Responder) {
	r.stubs = append(r.stubs, &Stub{
		Stack:     string(debug.Stack()),
		Matcher:   m,
		Responder: resp,
	})
}

func (r *Registry) Exclude(t *testing.T, m Matcher) {
	excludedStub := &Stub{
		Matcher: m,
		Responder: func(req *http.Request) (*http.Response, error) {
			assert.FailNowf(t, "Exclude error", "API called when excluded: %v", req.URL)
			return nil, nil
		},
		exclude: true,
	}
	r.stubs = append(r.stubs, excludedStub)
}

type Testing interface {
	Errorf(string, ...interface{})
	Helper()
}

func (r *Registry) Verify(t Testing) {
	var unmatchedStubStacks []string
	for _, s := range r.stubs {
		if !s.matched && !s.exclude {
			unmatchedStubStacks = append(unmatchedStubStacks, s.Stack)
		}
	}
	if len(unmatchedStubStacks) > 0 {

Review comment (bug):
This just improves failures on `Exclude` to include the stack trace pointing back to the line where the Exclusion was made.

Code after:
	"sync"
	"testing"
)

// Replace http.Client transport layer with registry so all requests get
// recorded.
func ReplaceTripper(client *http.Client, reg *Registry) {
	client.Transport = reg
}

type Registry struct {
	mu       sync.Mutex
	stubs    []*Stub
	Requests []*http.Request
}

func (r *Registry) Register(m Matcher, resp Responder) {
	r.stubs = append(r.stubs, &Stub{
		Stack:     string(debug.Stack()),
		Matcher:   m,
		Responder: resp,
	})
}

func (r *Registry) Exclude(t *testing.T, m Matcher) {
	registrationStack := string(debug.Stack())

	excludedStub := &Stub{
		Matcher: m,
		Responder: func(req *http.Request) (*http.Response, error) {
			callStack := string(debug.Stack())

			var errMsg strings.Builder
			errMsg.WriteString("HTTP call was made when it should have been excluded:\n")
			errMsg.WriteString(fmt.Sprintf("Request URL: %s\n", req.URL))
			errMsg.WriteString(fmt.Sprintf("Was excluded by: %s\n", registrationStack))
			errMsg.WriteString(fmt.Sprintf("Was called from: %s\n", callStack))

			t.Error(errMsg.String())
			t.FailNow()
			return nil, nil
		},
		exclude: true,
	}
	r.stubs = append(r.stubs, excludedStub)
}

type Testing interface {
	Errorf(string, ...interface{})
	Helper()
}
Repository: cli/cli
File: pkg/cmd/issue/view/view.go (Go)

Code before:
	}

	lookupFields := set.NewStringSet()
	if opts.Exporter != nil {
		lookupFields.AddValues(opts.Exporter.Fields())
	} else if opts.WebMode {
		lookupFields.Add("url")
	} else {
		lookupFields.AddValues(defaultFields)
		if opts.Comments {
			lookupFields.Add("comments")
			lookupFields.Remove("lastComment")
		}
	}

	opts.IO.DetectTerminalTheme()

	opts.IO.StartProgressIndicator()
	lookupFields.Add("id")

	issue, err := issueShared.FindIssueOrPR(httpClient, baseRepo, opts.IssueNumber, lookupFields.ToSlice())
	if err != nil {
		return err
	}

	if lookupFields.Contains("comments") {
		// FIXME: this re-fetches the comments connection even though the initial set of 100 were
		// fetched in the previous request.
		err = preloadIssueComments(httpClient, baseRepo, issue)
	}
	opts.IO.StopProgressIndicator()
	if err != nil {
		var loadErr *issueShared.PartialLoadError
		if opts.Exporter == nil && errors.As(err, &loadErr) {
			fmt.Fprintf(opts.IO.ErrOut, "warning: %s\n", loadErr.Error())
		} else {
			return err
		}
	}

	if opts.WebMode {
		openURL := issue.URL
		if opts.IO.IsStdoutTTY() {
			fmt.Fprintf(opts.IO.ErrOut, "Opening %s in your browser.\n", text.DisplayURL(openURL))
		}
		return opts.Browser.Browse(openURL)
	}

	if err := opts.IO.StartPager(); err != nil {
		fmt.Fprintf(opts.IO.ErrOut, "error starting pager: %v\n", err)
	}

Review comment (suggestion):
This type might seem a little overkill right now, but in following PRs there are functions that are going to include it in the signature, and the boolean reads very poorly in tests since anonymous bools coerce even if you do `type ProjectV1Support bool`

Code after:
	baseRepo, err := opts.BaseRepo()
	if err != nil {
		return err
	}

	lookupFields := set.NewStringSet()
	if opts.Exporter != nil {
		lookupFields.AddValues(opts.Exporter.Fields())
	} else if opts.WebMode {
		lookupFields.Add("url")
	} else {
		lookupFields.AddValues(defaultFields)
		if opts.Comments {
			lookupFields.Add("comments")
			lookupFields.Remove("lastComment")
		}

		// TODO projectsV1Deprecation
		// Remove this section as we should no longer add projectCards
		if opts.Detector == nil {
			cachedClient := api.NewCachedHTTPClient(httpClient, time.Hour*24)
			opts.Detector = fd.NewDetector(cachedClient, baseRepo.RepoHost())
		}

		projectsV1Support := opts.Detector.ProjectsV1()
		if projectsV1Support == gh.ProjectsV1Supported {
			lookupFields.Add("projectCards")
		}
	}

	opts.IO.DetectTerminalTheme()

	opts.IO.StartProgressIndicator()
	lookupFields.Add("id")

	issue, err := issueShared.FindIssueOrPR(httpClient, baseRepo, opts.IssueNumber, lookupFields.ToSlice())
	if err != nil {
		return err
	}

	if lookupFields.Contains("comments") {
		// FIXME: this re-fetches the comments connection even though the initial set of 100 were
		// fetched in the previous request.
		err = preloadIssueComments(httpClient, baseRepo, issue)
	}
	opts.IO.StopProgressIndicator()
	if err != nil {
		var loadErr *issueShared.PartialLoadError
		if opts.Exporter == nil && errors.As(err, &loadErr) {
			fmt.Fprintf(opts.IO.ErrOut, "warning: %s\n", loadErr.Error())
		} else {
Repository: cli/cli
File: pkg/cmd/pr/create/create.go (Go)

Code before:
			ctx.PRRefs.QualifiedHeadRef(), ctx.PRRefs.BaseRef(), existingPR.URL)
	}

	message := "\nCreating pull request for %s into %s in %s\n\n"
	if state.Draft {
		message = "\nCreating draft pull request for %s into %s in %s\n\n"
	}
	if opts.DryRun {
		message = "\nDry Running pull request for %s into %s in %s\n\n"
	}

	cs := opts.IO.ColorScheme()

	if opts.IO.CanPrompt() {
		fmt.Fprintf(opts.IO.ErrOut, message,
			cs.Cyan(ctx.PRRefs.QualifiedHeadRef()),
			cs.Cyan(ctx.PRRefs.BaseRef()),
			ghrepo.FullName(ctx.PRRefs.BaseRepo()))
	}

	if !opts.EditorMode && (opts.FillVerbose || opts.Autofill || opts.FillFirst || (opts.TitleProvided && opts.BodyProvided)) {
		err = handlePush(*opts, *ctx)
		if err != nil {
			return err
		}
		return submitPR(*opts, *ctx, *state)
	}

	if opts.RecoverFile != "" {
		err = shared.FillFromJSON(opts.IO, opts.RecoverFile, state)
		if err != nil {
			return fmt.Errorf("failed to recover input: %w", err)
		}
	}

	action := shared.SubmitAction
	if opts.IsDraft {
		action = shared.SubmitDraftAction
	}

	tpl := shared.NewTemplateManager(client.HTTP(), ctx.PRRefs.BaseRepo(), opts.Prompter, opts.RootDirOverride, opts.RepoOverride == "", true)

	if opts.EditorMode {
		if opts.Template != "" {
			var template shared.Template
			template, err = tpl.Select(opts.Template)
			if err != nil {
				return err
			}
			if state.Title == "" {
				state.Title = template.Title()

Review comment (suggestion):
Anywhere you see this comment is because I'm assuming that projects v1 is supported for backwards compat reasons as I pass through `gh.ProjectsV1Supported` to functions used by both `issue create` (changed here) and `pr create`).

Code after:
			ctx.PRRefs.QualifiedHeadRef(), ctx.PRRefs.BaseRef(), existingPR.URL)
	}

	message := "\nCreating pull request for %s into %s in %s\n\n"
	if state.Draft {
		message = "\nCreating draft pull request for %s into %s in %s\n\n"
	}
	if opts.DryRun {
		message = "\nDry Running pull request for %s into %s in %s\n\n"
	}

	cs := opts.IO.ColorScheme()

	if opts.IO.CanPrompt() {
		fmt.Fprintf(opts.IO.ErrOut, message,
			cs.Cyan(ctx.PRRefs.QualifiedHeadRef()),
			cs.Cyan(ctx.PRRefs.BaseRef()),
			ghrepo.FullName(ctx.PRRefs.BaseRepo()))
	}

	if !opts.EditorMode && (opts.FillVerbose || opts.Autofill || opts.FillFirst || (opts.TitleProvided && opts.BodyProvided)) {
		err = handlePush(*opts, *ctx)
		if err != nil {
			return err
		}
		// TODO wm: revisit project support
		return submitPR(*opts, *ctx, *state, gh.ProjectsV1Supported)
	}

	if opts.RecoverFile != "" {
		err = shared.FillFromJSON(opts.IO, opts.RecoverFile, state)
		if err != nil {
			return fmt.Errorf("failed to recover input: %w", err)
		}
	}

	action := shared.SubmitAction
	if opts.IsDraft {
		action = shared.SubmitDraftAction
	}

	tpl := shared.NewTemplateManager(client.HTTP(), ctx.PRRefs.BaseRepo(), opts.Prompter, opts.RootDirOverride, opts.RepoOverride == "", true)

	if opts.EditorMode {
		if opts.Template != "" {
			var template shared.Template
			template, err = tpl.Select(opts.Template)
			if err != nil {
				return err
			}
			if state.Title == "" {