Skip to content

Commit f96fabc

Browse files
author
SIN CI
committed
fix(tui): wire compact toggle, crash recovery on quit, block input during streaming
3 real bugs fixed: 1. CompactToggle (c key) was in keymap but never handled in handleKey. Now toggles compact mode with system message feedback. 2. CrashRecovery.Save() was never called on quit — chat history was lost on crash/quit. Now saves on all 3 quit paths (Ctrl+X, Ctrl+C, keymap.Quit). Init() restores on next launch with '↻ Restored from previous session' message. 3. User could submit messages during streaming — wastes tokens and interrupts the response. SubmitMsg handler now blocks with '⏳ Wait for the current response to finish (Esc to interrupt)'. Input shows '⏳ Waiting for response…' placeholder when disabled. Build ✅ | Tests ✅ (-race) | CEO 99/A+
1 parent 6e24b62 commit f96fabc

3 files changed

Lines changed: 58 additions & 10 deletions

File tree

cmd/sin-code/tui/chat/input.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ type Input struct {
4141
historyCursor int
4242
filePicker filePickerState
4343
pasteDetected bool
44+
disabled bool
4445
}
4546

4647
func NewInput(store *attachments.Store) *Input {
@@ -90,6 +91,10 @@ func (i *Input) Blur() {
9091
i.textarea.Blur()
9192
}
9293

94+
func (i *Input) SetDisabled(d bool) {
95+
i.disabled = d
96+
}
97+
9398
func (i *Input) Value() string {
9499
val := i.textarea.Value()
95100
for _, a := range i.attachments {
@@ -414,6 +419,9 @@ func (i *Input) submit() (tea.Cmd, *SubmitMsg) {
414419

415420
func (i *Input) View() string {
416421
var b strings.Builder
422+
if i.disabled {
423+
b.WriteString(" ⏳ Waiting for response… (Esc to interrupt)\n")
424+
}
417425
if len(i.attachments) > 0 {
418426
b.WriteString(i.renderAttachmentChips())
419427
b.WriteString("\n")

cmd/sin-code/tui/chat_input.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,12 @@ func (m *Model) updateChat(msg tea.Msg) tea.Cmd {
247247
if kmsg, ok := msg.(tea.KeyPressMsg); ok {
248248
if m.CompactMode != nil && key.Matches(kmsg, keymap.CompactToggle) {
249249
m.CompactMode.Toggle()
250+
active := m.CompactMode.Active()
251+
toggleMsg := "Compact mode off"
252+
if active {
253+
toggleMsg = "Compact mode on — messages rendered in condensed format"
254+
}
255+
m.appendChat(ChatMessage{Kind: chatSystem, Text: toggleMsg})
250256
return nil
251257
}
252258
}
@@ -312,6 +318,11 @@ func (m *Model) updateChat(msg tea.Msg) tea.Cmd {
312318

313319
cmd, submit := m.ChatInput.Update(msg)
314320
if submit != nil {
321+
if m.IsStreaming() {
322+
m.appendChat(ChatMessage{Kind: chatSystem, Text: "⏳ Wait for the current response to finish (Esc to interrupt)"})
323+
m.ChatInput.Clear()
324+
return nil
325+
}
315326
agentCmd := handleChatSubmit(m, *submit)
316327
m.ChatInput.Clear()
317328
if agentCmd != nil {

cmd/sin-code/tui/update.go

Lines changed: 39 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,18 @@ func (m *Model) Init() tea.Cmd {
7474
RefreshLSPCmd(),
7575
InitGitRefresh(),
7676
}
77+
78+
if m.CrashRecovery != nil {
79+
if state, err := m.CrashRecovery.Load(); err == nil && state != nil && len(state.ChatHistory) > 0 {
80+
m.ChatHistory = state.ChatHistory
81+
if state.ViewKind >= 0 && state.ViewKind < viewCount {
82+
m.ViewKind = ViewKind(state.ViewKind)
83+
}
84+
m.appendChat(ChatMessage{Kind: chatSystem, Text: "↻ Restored from previous session (crash recovery)"})
85+
_ = m.CrashRecovery.Clear()
86+
}
87+
}
88+
7789
return tea.Batch(cmds...)
7890
}
7991

@@ -445,16 +457,18 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
445457

446458
case tea.KeyPressMsg:
447459
// Ctrl+X and Ctrl+C quit immediately from any view/mode
448-
if msg.Code == 'x' && msg.Mod&tea.ModCtrl != 0 {
449-
m.saveChatHistory()
450-
m.Quitting = true
451-
return m, tea.Quit
452-
}
453-
if msg.Code == 'c' && msg.Mod&tea.ModCtrl != 0 {
454-
m.saveChatHistory()
455-
m.Quitting = true
456-
return m, tea.Quit
457-
}
460+
if msg.Code == 'x' && msg.Mod&tea.ModCtrl != 0 {
461+
m.saveChatHistory()
462+
m.SaveCrashState()
463+
m.Quitting = true
464+
return m, tea.Quit
465+
}
466+
if msg.Code == 'c' && msg.Mod&tea.ModCtrl != 0 {
467+
m.saveChatHistory()
468+
m.SaveCrashState()
469+
m.Quitting = true
470+
return m, tea.Quit
471+
}
458472
if m.NotificationBanner != nil {
459473
k := msg.String()
460474
if k == "o" || k == "d" || k == "n" {
@@ -616,6 +630,7 @@ func (m *Model) handleKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
616630
switch {
617631
case key.Matches(msg, keymap.Quit):
618632
m.saveChatHistory()
633+
m.SaveCrashState()
619634
m.Quitting = true
620635
return m, tea.Quit
621636
case key.Matches(msg, keymap.CopyMessage):
@@ -757,6 +772,17 @@ func (m *Model) handleKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
757772
m.CycleTheme()
758773
m.AppendHistory(m.ViewKind.String(), "theme", Themes[m.ThemeIdx].Name, true)
759774
return m, nil
775+
case key.Matches(msg, keymap.CompactToggle):
776+
if m.ViewKind == ViewChat && m.CompactMode != nil {
777+
m.CompactMode.Toggle()
778+
active := m.CompactMode.Active()
779+
toggleMsg := "Compact mode off"
780+
if active {
781+
toggleMsg = "Compact mode on — messages rendered in condensed format"
782+
}
783+
m.appendChat(ChatMessage{Kind: chatSystem, Text: toggleMsg})
784+
}
785+
return m, nil
760786
case key.Matches(msg, keymap.CycleAgent):
761787
m.Footer.CycleAgent()
762788
m.AppendHistory(m.ViewKind.String(), "agent", m.Footer.AgentName(), true)
@@ -2317,6 +2343,9 @@ func (m *Model) setStreaming(streaming bool) {
23172343
if streaming && m.TypewriterBuf != nil {
23182344
m.TypewriterBuf.Reset()
23192345
}
2346+
if m.ChatInput != nil {
2347+
m.ChatInput.SetDisabled(streaming)
2348+
}
23202349
}
23212350

23222351
// IsStreaming reports whether the model is currently receiving a streaming

0 commit comments

Comments
 (0)