From 37b95035e5ad6a92d108048a9d654c6e3695a769 Mon Sep 17 00:00:00 2001 From: Don Brown Date: Thu, 26 Mar 2026 18:38:31 -0600 Subject: [PATCH] #10: Add inline text support to /comment - Parse slash command args from input - Post comment directly when text provided - Guard nil pendingItem in comment handler --- internal/tui/conversation_keys.go | 9 +++++--- internal/tui/handlers_chat.go | 21 ++++++++++++++---- internal/tui/slashcmds.go | 36 ++++++++++++++++++------------- 3 files changed, 44 insertions(+), 22 deletions(-) diff --git a/internal/tui/conversation_keys.go b/internal/tui/conversation_keys.go index ae56ed9..655bbad 100644 --- a/internal/tui/conversation_keys.go +++ b/internal/tui/conversation_keys.go @@ -144,12 +144,15 @@ func (s *ConversationScene) handleSlashCommand(m *Model) (Scene, tea.Cmd, bool) if !strings.HasPrefix(input, "/") { return s, nil, false } - name := strings.ToLower(strings.TrimPrefix(input, "/")) + raw := strings.TrimPrefix(input, "/") + name, args, _ := strings.Cut(raw, " ") + name = strings.ToLower(name) + args = strings.TrimSpace(args) slashCmds, _ := commandMap() if cmd, ok := slashCmds[name]; ok { logger.Info("[user] slash command: /%s", name) s.input.Reset() - return cmd.Run(s, m) + return cmd.Run(s, m, args) } // Check if it matches a skill name — send as a chat message asking to activate the skill. @@ -168,7 +171,7 @@ func (s *ConversationScene) handleCommandKey(key string, m *Model) (Scene, tea.C _, keyCmds := commandMap() if cmd, ok := keyCmds[key]; ok { logger.Info("[user] key command: %s → %s", key, cmd.Name) - return cmd.Run(s, m) + return cmd.Run(s, m, "") } return s, nil, false } diff --git a/internal/tui/handlers_chat.go b/internal/tui/handlers_chat.go index 36316e2..369ee7a 100644 --- a/internal/tui/handlers_chat.go +++ b/internal/tui/handlers_chat.go @@ -2,6 +2,7 @@ package tui import ( "context" + "fmt" tea "github.com/charmbracelet/bubbletea" @@ -104,10 +105,22 @@ func (m *Model) handleCommentSubmitted(msg commentSubmittedMsg) (Model, tea.Cmd) } } if card := m.currentCard(); card != nil && card.Ctx.Repo == msg.repo && card.PR.Number == msg.prNumber { - if msg.err == nil { - m.diffView.ConfirmComment(msg.pendingItem) - } else { - m.diffView.RemoveComment(msg.pendingItem) + if msg.pendingItem != nil { + if msg.err == nil { + m.diffView.ConfirmComment(msg.pendingItem) + } else { + m.diffView.RemoveComment(msg.pendingItem) + } + } + // Update action status for slash-command comments (no pending item). + if cs, ok := m.scene.(*ConversationScene); ok && msg.pendingItem == nil { + cs.actionDone = true + if msg.err != nil { + cs.actionStatus = fmt.Sprintf("Comment failed: %s", msg.err) + } else { + cs.actionStatus = fmt.Sprintf("Comment posted on PR #%d", msg.prNumber) + } + cs.BuildScrollback(m) } } return *m, nil diff --git a/internal/tui/slashcmds.go b/internal/tui/slashcmds.go index 8739c65..e34d15b 100644 --- a/internal/tui/slashcmds.go +++ b/internal/tui/slashcmds.go @@ -21,7 +21,7 @@ type Command struct { Description string // shown in help/autocomplete KeyBinding string // optional ctrl combo (e.g. "ctrl+d"), empty = no binding Scope CommandScope // controls autocomplete visibility - Run func(s *ConversationScene, m *Model) (Scene, tea.Cmd, bool) + Run func(s *ConversationScene, m *Model, args string) (Scene, tea.Cmd, bool) } // commands returns all registered commands. @@ -32,7 +32,7 @@ func commands() []Command { Description: "Go to next PR", KeyBinding: "ctrl+n", Scope: ScopeGlobal, - Run: func(s *ConversationScene, m *Model) (Scene, tea.Cmd, bool) { + Run: func(s *ConversationScene, m *Model, args string) (Scene, tea.Cmd, bool) { m.navigatePR(1, s) return s, nil, true }, @@ -42,7 +42,7 @@ func commands() []Command { Description: "Go to previous PR", KeyBinding: "ctrl+p", Scope: ScopeGlobal, - Run: func(s *ConversationScene, m *Model) (Scene, tea.Cmd, bool) { + Run: func(s *ConversationScene, m *Model, args string) (Scene, tea.Cmd, bool) { m.navigatePR(-1, s) return s, nil, true }, @@ -52,7 +52,7 @@ func commands() []Command { Description: "Toggle diff overlay", KeyBinding: "ctrl+d", Scope: ScopePR, - Run: func(s *ConversationScene, m *Model) (Scene, tea.Cmd, bool) { + Run: func(s *ConversationScene, m *Model, args string) (Scene, tea.Cmd, bool) { s.input.Blur() m.diffView.Focused = true ds := newDiffOverlayScene(s, m.width, m.height) @@ -70,7 +70,7 @@ func commands() []Command { Name: "approve", Description: "Approve current PR", Scope: ScopePR, - Run: func(s *ConversationScene, m *Model) (Scene, tea.Cmd, bool) { + Run: func(s *ConversationScene, m *Model, args string) (Scene, tea.Cmd, bool) { card := m.currentCard() if card == nil || card.Scoring || m.isOwnPR(card) { return s, nil, true @@ -96,7 +96,7 @@ func commands() []Command { Name: "flag", Description: "Flag a merged PR (thumbs down)", Scope: ScopePR, - Run: func(s *ConversationScene, m *Model) (Scene, tea.Cmd, bool) { + Run: func(s *ConversationScene, m *Model, args string) (Scene, tea.Cmd, bool) { card := m.currentCard() if card == nil || !card.PostMerge { return s, nil, true @@ -115,7 +115,7 @@ func commands() []Command { Description: "Toggle showing all merged PRs", KeyBinding: "ctrl+a", Scope: ScopeGlobal, - Run: func(s *ConversationScene, m *Model) (Scene, tea.Cmd, bool) { + Run: func(s *ConversationScene, m *Model, args string) (Scene, tea.Cmd, bool) { m.showAllMerged = !m.showAllMerged if m.showAllMerged { s.actionStatus = "Showing all merged PRs" @@ -136,7 +136,7 @@ func commands() []Command { Name: "merge", Description: "Merge current PR", Scope: ScopePR, - Run: func(s *ConversationScene, m *Model) (Scene, tea.Cmd, bool) { + Run: func(s *ConversationScene, m *Model, args string) (Scene, tea.Cmd, bool) { card := m.currentCard() if card == nil || card.Scoring || !m.isOwnPR(card) { return s, nil, true @@ -164,7 +164,7 @@ func commands() []Command { Name: "reject", Description: "Request changes on current PR", Scope: ScopePR, - Run: func(s *ConversationScene, m *Model) (Scene, tea.Cmd, bool) { + Run: func(s *ConversationScene, m *Model, args string) (Scene, tea.Cmd, bool) { card := m.currentCard() if card == nil || card.Scoring || card.Assessment == nil || m.isOwnPR(card) { return s, nil, true @@ -182,12 +182,18 @@ func commands() []Command { Name: "comment", Description: "Post a comment on current PR", Scope: ScopePR, - Run: func(s *ConversationScene, m *Model) (Scene, tea.Cmd, bool) { + Run: func(s *ConversationScene, m *Model, args string) (Scene, tea.Cmd, bool) { card := m.currentCard() if card == nil { return s, nil, true } - // Enter diff overlay with comment modal open + // If args provided, post the comment directly + if args != "" { + repo, num := card.Ctx.Repo, card.PR.Number + s.actionStatus = "Posting comment…" + return s, postGlobalCommentCmd(repo, num, args, nil), true + } + // No args — enter diff overlay with comment modal open s.input.Blur() m.diffView.Focused = true ds := newDiffOverlayScene(s, m.width, m.height) @@ -200,7 +206,7 @@ func commands() []Command { Description: "Open bulk approve view", KeyBinding: "ctrl+b", Scope: ScopeGlobal, - Run: func(s *ConversationScene, m *Model) (Scene, tea.Cmd, bool) { + Run: func(s *ConversationScene, m *Model, args string) (Scene, tea.Cmd, bool) { m.tryEnterBulkApprove() return m.scene, nil, true }, @@ -210,7 +216,7 @@ func commands() []Command { Description: "Refresh current PR and check for new PRs", KeyBinding: "ctrl+r", Scope: ScopeGlobal, - Run: func(s *ConversationScene, m *Model) (Scene, tea.Cmd, bool) { + Run: func(s *ConversationScene, m *Model, args string) (Scene, tea.Cmd, bool) { card := m.currentCard() if card == nil { return s, nil, true @@ -229,7 +235,7 @@ func commands() []Command { Description: "Quit prx", KeyBinding: "ctrl+q", Scope: ScopeGlobal, - Run: func(s *ConversationScene, m *Model) (Scene, tea.Cmd, bool) { + Run: func(s *ConversationScene, m *Model, args string) (Scene, tea.Cmd, bool) { return s, m.cleanupWorktrees(), true }, }, @@ -237,7 +243,7 @@ func commands() []Command { Name: "exit", Description: "Quit prx", Scope: ScopeGlobal, - Run: func(s *ConversationScene, m *Model) (Scene, tea.Cmd, bool) { + Run: func(s *ConversationScene, m *Model, args string) (Scene, tea.Cmd, bool) { return s, m.cleanupWorktrees(), true }, },