feat(tui): QA fixes + approval Ctrl keys, workflow focus, session UUID
- gate session-list nav on idle so in-session arrows/jk don't move the list (5a) - approval dismiss on esc + in-session 'a' reopens a pending approval (5b) - auto-focus a newly started workflow session (was running invisibly) - approval actions on Ctrl chords (^a/^r/^x) matching the modal; bare/Alt letters no longer approve; diff closes on ^x/esc - steer hint in the approval modal; session UUID in the input bar - env-gated debug logging (CORREX_TUI_LOG) with k.Alt in the key trace
This commit is contained in:
@@ -93,6 +93,9 @@ func (m *Model) applyServerPhased(msg protocol.ServerMessage) {
|
||||
// --- key handling ---
|
||||
|
||||
func (m Model) handleKey(k tea.KeyMsg) (tea.Model, tea.Cmd) {
|
||||
debugLog("KEY type=%v runes=%q alt=%v | ds=%s input=%s edit=%s overlay=%d steering=%v steerBuf=%q dismissed=%v entered=%v sel=%s",
|
||||
k.Type, string(k.Runes), k.Alt, m.displayState(), m.inputMode, m.editMode, m.overlay,
|
||||
m.steering, m.steerBuffer, m.approvalDismissed, m.sessionEntered, m.selectedID)
|
||||
// Ctrl+C is a universal hard-quit safety; everything else is bare-key modal.
|
||||
if k.Type == tea.KeyCtrlC {
|
||||
m.quitting = true
|
||||
@@ -120,8 +123,28 @@ func (m Model) handleNormalKey(k tea.KeyMsg) (tea.Model, tea.Cmd) {
|
||||
case tea.KeyEnter:
|
||||
return m.normalEnter()
|
||||
case tea.KeyEsc:
|
||||
if ds == StateApproval {
|
||||
m.approvalDismissed = true
|
||||
return m, nil
|
||||
}
|
||||
m.filter = ""
|
||||
return m, nil
|
||||
case tea.KeyCtrlA:
|
||||
if ds == StateApproval {
|
||||
return m.decide("APPROVE")
|
||||
}
|
||||
return m, nil
|
||||
case tea.KeyCtrlR:
|
||||
if ds == StateApproval {
|
||||
return m.decide("REJECT")
|
||||
}
|
||||
return m, nil
|
||||
case tea.KeyCtrlX:
|
||||
if m.currentDiff() != "" {
|
||||
m.overlay = OverlayDiff
|
||||
m.diffScrollOffset = 0
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
if k.Type != tea.KeyRunes {
|
||||
return m, nil
|
||||
@@ -167,27 +190,20 @@ func (m Model) handleNormalKey(k tea.KeyMsg) (tea.Model, tea.Cmd) {
|
||||
} else {
|
||||
m.cycleChatMode()
|
||||
}
|
||||
case "x":
|
||||
if m.currentDiff() != "" {
|
||||
m.overlay = OverlayDiff
|
||||
m.diffScrollOffset = 0
|
||||
}
|
||||
case "c":
|
||||
if m.selectedID != "" {
|
||||
m.client.Send(protocol.CancelSession(m.selectedID))
|
||||
}
|
||||
case "a":
|
||||
if ds == StateApproval {
|
||||
return m.decide("APPROVE")
|
||||
if ds == StateInSession {
|
||||
if s := m.session(m.selectedID); s != nil && s.Pending != nil {
|
||||
m.approvalDismissed = false
|
||||
}
|
||||
}
|
||||
case "A":
|
||||
if ds == StateApproval {
|
||||
return m.autoApprove()
|
||||
}
|
||||
case "r":
|
||||
if ds == StateApproval {
|
||||
return m.decide("REJECT")
|
||||
}
|
||||
case "q":
|
||||
m.quitting = true
|
||||
return m, tea.Quit
|
||||
@@ -277,6 +293,7 @@ func (m Model) normalEnter() (tea.Model, tea.Cmd) {
|
||||
m.client.Send(protocol.StartSession(wf.ID))
|
||||
m.wfVisible = false
|
||||
m.wfIndex = -1
|
||||
m.pendingWorkflowFocus = true
|
||||
return m, nil
|
||||
}
|
||||
if m.selectedID != "" {
|
||||
@@ -316,7 +333,7 @@ func (m Model) handleOverlayKey(k tea.KeyMsg) (tea.Model, tea.Cmd) {
|
||||
}
|
||||
case k.Type == tea.KeyDown || runeIs(k, "j"):
|
||||
m.diffScrollOffset++
|
||||
case runeIs(k, "x"):
|
||||
case k.Type == tea.KeyCtrlX:
|
||||
m.overlay = OverlayNone
|
||||
}
|
||||
case OverlayEventInspector:
|
||||
@@ -595,7 +612,9 @@ func (m *Model) navUp() {
|
||||
m.wfNav(-1)
|
||||
return
|
||||
}
|
||||
m.listNav(-1)
|
||||
if m.displayState() == StateIdle {
|
||||
m.listNav(-1)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Model) navDown() {
|
||||
@@ -607,7 +626,9 @@ func (m *Model) navDown() {
|
||||
m.wfNav(1)
|
||||
return
|
||||
}
|
||||
m.listNav(1)
|
||||
if m.displayState() == StateIdle {
|
||||
m.listNav(1)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Model) wfNav(dir int) {
|
||||
|
||||
Reference in New Issue
Block a user