Skip to content
Merged
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
5 changes: 5 additions & 0 deletions internal/config/keybindings.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ type KeybindingsConfig struct {
CollapseBacklog *KeybindingConfig `yaml:"collapse_backlog,omitempty"`
CollapseDone *KeybindingConfig `yaml:"collapse_done,omitempty"`
OpenBrowser *KeybindingConfig `yaml:"open_browser,omitempty"`
OpenPR *KeybindingConfig `yaml:"open_pr,omitempty"`
ApprovePrompt *KeybindingConfig `yaml:"approve_prompt,omitempty"`
DenyPrompt *KeybindingConfig `yaml:"deny_prompt,omitempty"`
Spotlight *KeybindingConfig `yaml:"spotlight,omitempty"`
Expand Down Expand Up @@ -258,6 +259,10 @@ open_browser:
keys: ["b"]
help: "open in browser"

open_pr:
keys: ["G"]
help: "open PR"

# Spotlight mode
spotlight:
keys: ["f"]
Expand Down
26 changes: 26 additions & 0 deletions internal/ui/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@ type KeyMap struct {
CollapseDone key.Binding
// Open browser
OpenBrowser key.Binding
// Open PR
OpenPR key.Binding
// Quick approve/deny for executor prompts
ApprovePrompt key.Binding
DenyPrompt key.Binding
Expand Down Expand Up @@ -263,6 +265,10 @@ func DefaultKeyMap() KeyMap {
key.WithKeys("b"),
key.WithHelp("b", "open in browser"),
),
OpenPR: key.NewBinding(
key.WithKeys("G"),
key.WithHelp("G", "open PR"),
),
ApprovePrompt: key.NewBinding(
key.WithKeys("y"),
key.WithHelp("y", "approve"),
Expand Down Expand Up @@ -345,6 +351,7 @@ func ApplyKeybindingsConfig(km KeyMap, cfg *config.KeybindingsConfig) KeyMap {
km.CollapseBacklog = applyBinding(km.CollapseBacklog, cfg.CollapseBacklog)
km.CollapseDone = applyBinding(km.CollapseDone, cfg.CollapseDone)
km.OpenBrowser = applyBinding(km.OpenBrowser, cfg.OpenBrowser)
km.OpenPR = applyBinding(km.OpenPR, cfg.OpenPR)
km.ApprovePrompt = applyBinding(km.ApprovePrompt, cfg.ApprovePrompt)
km.DenyPrompt = applyBinding(km.DenyPrompt, cfg.DenyPrompt)
km.Spotlight = applyBinding(km.Spotlight, cfg.Spotlight)
Expand Down Expand Up @@ -2694,6 +2701,9 @@ func (m *AppModel) updateDetail(msg tea.Msg) (tea.Model, tea.Cmd) {
if key.Matches(keyMsg, m.keys.OpenBrowser) && m.selectedTask != nil {
return m, m.openBrowser(m.selectedTask)
}
if key.Matches(keyMsg, m.keys.OpenPR) && m.selectedTask != nil && m.selectedTask.PRURL != "" {
return m, m.openPR(m.selectedTask)
}
if key.Matches(keyMsg, m.keys.Spotlight) && m.selectedTask != nil && m.selectedTask.WorktreePath != "" {
return m, m.toggleSpotlight(m.selectedTask)
}
Expand Down Expand Up @@ -4203,6 +4213,22 @@ func (m *AppModel) openBrowser(task *db.Task) tea.Cmd {
}
}

// openPR opens the task's pull request URL in the default browser.
func (m *AppModel) openPR(task *db.Task) tea.Cmd {
return func() tea.Msg {
if task.PRURL == "" {
return browserOpenedMsg{err: fmt.Errorf("no PR linked for task #%d", task.ID)}
}

cmd := osExec.Command("open", task.PRURL)
if err := cmd.Start(); err != nil {
return browserOpenedMsg{err: fmt.Errorf("failed to open PR: %w", err)}
}

return browserOpenedMsg{message: fmt.Sprintf("Opened PR #%d", task.PRNumber)}
}
}

// spotlightMsg is returned after a spotlight action completes.
type spotlightMsg struct {
action string // "start", "stop", "sync"
Expand Down
5 changes: 5 additions & 0 deletions internal/ui/detail.go
Original file line number Diff line number Diff line change
Expand Up @@ -2896,6 +2896,11 @@ func (m *DetailModel) renderHelp() string {
}
}

// Open PR shortcut (only when task has a PR)
if m.task != nil && m.task.PRURL != "" {
keys = append(keys, helpKey{"G", "open PR", false})
}

keys = append(keys, []helpKey{
{"b", "browser", false},
{"c", "close", false},
Expand Down
Loading