Skip to content

Commit 228b03c

Browse files
mariusvniekerkwesmclaude
authored
Recognize Ctrl-D as a TUI quit shortcut (#447)
## Summary - treat \ as a top-level EOF-style quit shortcut in the TUI - make it quit from any view, including text-entry modals and review screens - add tests covering both review and modal paths Closes #431 --------- Co-authored-by: Wes McKinney <wesmckinn+git@gmail.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 0a56700 commit 228b03c

4 files changed

Lines changed: 50 additions & 6 deletions

File tree

cmd/roborev/tui/handlers.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ func (m model) handleMouseMsg(msg tea.MouseMsg) (tea.Model, tea.Cmd) {
7575
// handleGlobalKey handles keys shared across queue, review, prompt, commit msg, and help views.
7676
func (m model) handleGlobalKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
7777
switch msg.String() {
78-
case "ctrl+c", "q":
78+
case "ctrl+c", "ctrl+d", "q":
7979
return m.handleQuitKey()
8080
case "home", "g":
8181
return m.handleHomeKey()

cmd/roborev/tui/handlers_modal.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,7 @@ func (m model) handleLogKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
253253
switch msg.String() {
254254
case "ctrl+c":
255255
return m, tea.Quit
256-
case "esc", "q":
256+
case "ctrl+d", "esc", "q":
257257
m.currentView = m.logFromView
258258
m.logStreaming = false
259259
return m, nil
@@ -329,7 +329,7 @@ func (m model) handleWorktreeConfirmKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
329329
switch msg.String() {
330330
case "ctrl+c":
331331
return m, tea.Quit
332-
case "esc", "n":
332+
case "ctrl+d", "esc", "n":
333333
m.currentView = viewTasks
334334
m.worktreeConfirmJobID = 0
335335
m.worktreeConfirmBranch = ""
@@ -347,7 +347,7 @@ func (m model) handleWorktreeConfirmKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
347347
// handleTasksKey handles key input in the tasks view.
348348
func (m model) handleTasksKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
349349
switch msg.String() {
350-
case "ctrl+c", "q":
350+
case "ctrl+c", "ctrl+d", "q":
351351
return m, tea.Quit
352352
case "esc", "T":
353353
m.currentView = viewQueue
@@ -504,7 +504,7 @@ func (m model) handlePatchKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
504504
switch msg.String() {
505505
case "ctrl+c":
506506
return m, tea.Quit
507-
case "esc", "q":
507+
case "ctrl+d", "esc", "q":
508508
m.currentView = viewTasks
509509
m.patchText = ""
510510
m.patchScroll = 0

cmd/roborev/tui/handlers_queue.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ func (m model) handleColumnOptionsInput(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
202202
}
203203

204204
switch msg.String() {
205-
case "esc":
205+
case "ctrl+d", "esc":
206206
m.currentView = m.colOptionsReturnView
207207
if m.currentView == viewTasks && !m.tasksWorkflowEnabled() {
208208
m.currentView = viewQueue

cmd/roborev/tui/review_views_test.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,50 @@ func TestTUICommitMsgViewNavigationWithQ(t *testing.T) {
120120
}
121121
}
122122

123+
func TestTUICtrlDQuitsFromQueueView(t *testing.T) {
124+
m := initTestModel(withCurrentView(viewQueue))
125+
126+
_, cmd := pressSpecial(m, tea.KeyCtrlD)
127+
128+
if cmd == nil {
129+
t.Fatal("Expected quit command")
130+
}
131+
assertMsgType[tea.QuitMsg](t, cmd())
132+
}
133+
134+
func TestTUICtrlDNavigatesBackFromReviewView(t *testing.T) {
135+
job := makeJob(1)
136+
m := initTestModel(
137+
withCurrentView(viewReview),
138+
withReview(&storage.Review{JobID: 1, Job: &job}),
139+
withReviewFromView(viewQueue),
140+
)
141+
142+
got, _ := pressSpecial(m, tea.KeyCtrlD)
143+
144+
assertView(t, got, viewQueue)
145+
}
146+
147+
func TestTUICtrlDNoOpInCommentModal(t *testing.T) {
148+
m := initTestModel(withCurrentView(viewKindComment))
149+
m.commentFromView = viewQueue
150+
m.commentText = "draft comment"
151+
m.commentJobID = 42
152+
153+
got, cmd := pressSpecial(m, tea.KeyCtrlD)
154+
155+
assertView(t, got, viewKindComment)
156+
if got.commentText != "draft comment" {
157+
t.Errorf("Expected comment text preserved, got %q", got.commentText)
158+
}
159+
if got.commentJobID != 42 {
160+
t.Errorf("Expected comment job ID preserved, got %d", got.commentJobID)
161+
}
162+
if cmd != nil {
163+
t.Fatal("Expected no command from Ctrl-D in comment modal")
164+
}
165+
}
166+
123167
func TestFetchCommitMsgJobTypeDetection(t *testing.T) {
124168
// Test that fetchCommitMsg correctly identifies job types and returns appropriate errors
125169
// This is critical: Prompt field is populated for ALL jobs (stores review prompt),

0 commit comments

Comments
 (0)