Skip to content

Commit fcc7503

Browse files
committed
fix: TUI deadlock, log panel during setup, msgChan buffer
- Fix critical deadlock: all RunSetupTask callers now return listenToChannel() so the msgChan is always being consumed during async task execution - Increase msgChan buffer 100→500 to prevent goroutine blocking - Show Logs panel (not Status spinner) when right panel is visible during setup - Add progress bar + step label inside the Logs panel during setup/wipe tasks - Animated spinner when task is active but no logs yet - Add getOwdDepsLocal/getOwdDepsFromNpm helpers for post-install dep detection
1 parent 189e2ad commit fcc7503

6 files changed

Lines changed: 419 additions & 101 deletions

File tree

go/tui/handlers.go

Lines changed: 143 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -53,21 +53,129 @@ func (m *TuiModel) handlePromptKeys(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
5353
}
5454

5555
func (m *TuiModel) handleSettingsKeys(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
56-
switch msg.String() {
56+
keyStr := msg.String()
57+
isInputFocused := m.settingsSel == 2 || m.settingsSel == 3
58+
59+
if isInputFocused {
60+
switch keyStr {
61+
case "esc":
62+
m.activePrompt = PromptNone
63+
m.settingsOrgsInput.Blur()
64+
m.settingsUserInput.Blur()
65+
return m, nil
66+
case "up", "down", "enter":
67+
// Intercept and fall through to navigation/actions below
68+
default:
69+
var cmd tea.Cmd
70+
if m.settingsSel == 2 {
71+
m.settingsOrgsInput, cmd = m.settingsOrgsInput.Update(msg)
72+
} else {
73+
m.settingsUserInput, cmd = m.settingsUserInput.Update(msg)
74+
}
75+
return m, cmd
76+
}
77+
}
78+
79+
switch keyStr {
5780
case "esc", "q":
5881
m.activePrompt = PromptNone
82+
m.settingsOrgsInput.Blur()
83+
m.settingsUserInput.Blur()
5984
return m, nil
6085
case "up", "k":
6186
if m.settingsSel >= SettingsFieldCount {
6287
m.settingsSel = SettingsFieldCount - 1
6388
} else if m.settingsSel > 0 {
6489
m.settingsSel--
6590
}
66-
case "down", "j":
67-
if m.settingsSel == SettingsFieldCount-1 {
68-
m.settingsSel = SettingsFieldCount
69-
} else if m.settingsSel < SettingsFieldCount-1 {
70-
m.settingsSel++
91+
m.updateSettingsFocus()
92+
case "down", "j", "enter":
93+
if keyStr == "enter" && isInputFocused {
94+
m.settingsSel = 5 // Jump directly to SAVE button
95+
m.updateSettingsFocus()
96+
return m, nil
97+
}
98+
99+
if keyStr == "down" || keyStr == "j" {
100+
if m.settingsSel == SettingsFieldCount-1 {
101+
m.settingsSel = SettingsFieldCount
102+
} else if m.settingsSel < SettingsFieldCount-1 {
103+
m.settingsSel++
104+
}
105+
m.updateSettingsFocus()
106+
} else if keyStr == "enter" {
107+
switch m.settingsSel {
108+
case 0: // Install Mode
109+
if m.settingsInstallMode == "npm" {
110+
m.settingsInstallMode = "workspace"
111+
} else {
112+
m.settingsInstallMode = "npm"
113+
}
114+
case 1: // Catalog Sort
115+
modes := []string{"updated", "name", "stars", "installed"}
116+
idx := 0
117+
for i, mth := range modes {
118+
if mth == m.settingsCatalogSort {
119+
idx = (i + 1) % len(modes)
120+
break
121+
}
122+
}
123+
m.settingsCatalogSort = modes[idx]
124+
case 2: // Trusted Orgs
125+
// Handled by textinput
126+
case 3: // GitHub User
127+
// Handled by textinput
128+
case 4: // Reset Workspace
129+
m.activePrompt = PromptWipeWorkspaceConfirm
130+
m.promptSel = 1 // default to No
131+
case 5: // Save
132+
m.activePrompt = PromptNone
133+
m.settingsOrgsInput.Blur()
134+
m.settingsUserInput.Blur()
135+
if m.ctx != nil {
136+
settings := m.ctx.Settings
137+
settings.InstallMode = m.settingsInstallMode
138+
settings.CatalogSort = m.settingsCatalogSort
139+
140+
// Parse Trusted Orgs (GithubOrgs) from comma-separated list
141+
rawOrgs := m.settingsOrgsInput.Value()
142+
var orgs []string
143+
for _, part := range strings.Split(rawOrgs, ",") {
144+
trimmed := strings.TrimSpace(part)
145+
if trimmed != "" {
146+
orgs = append(orgs, trimmed)
147+
}
148+
}
149+
settings.GithubOrgs = orgs
150+
151+
// Parse GitHub User (GithubUser)
152+
ghUserVal := m.settingsUserInput.Value()
153+
if ghUserVal == "" {
154+
settings.GithubUser = nil
155+
} else {
156+
settings.GithubUser = &ghUserVal
157+
}
158+
159+
payload := &bridge.WritePayload{
160+
Settings: &settings,
161+
}
162+
m.statusMsg = "Saving settings…"
163+
m.addLog(">>> Saving settings configuration…")
164+
if err := bridge.WriteChanges(m.workspaceRoot, payload); err != nil {
165+
m.statusMsg = fmt.Sprintf("Save failed: %v", err)
166+
m.addLog(fmt.Sprintf(">>> Save settings failed: %v", err))
167+
} else {
168+
m.statusMsg = "Settings saved successfully."
169+
m.addLog(">>> Settings saved successfully.")
170+
}
171+
}
172+
m.loading = true
173+
return m, tea.Batch(m.loadContextCmd(), m.loadCatalogCmd(false))
174+
case 6: // Cancel
175+
m.activePrompt = PromptNone
176+
m.settingsOrgsInput.Blur()
177+
m.settingsUserInput.Blur()
178+
}
71179
}
72180
case "left", "h":
73181
if m.settingsSel == SettingsFieldCount+1 {
@@ -77,55 +185,6 @@ func (m *TuiModel) handleSettingsKeys(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
77185
if m.settingsSel == SettingsFieldCount {
78186
m.settingsSel = SettingsFieldCount + 1
79187
}
80-
case "enter":
81-
switch m.settingsSel {
82-
case 0: // Install Mode
83-
if m.settingsInstallMode == "npm" {
84-
m.settingsInstallMode = "workspace"
85-
} else {
86-
m.settingsInstallMode = "npm"
87-
}
88-
case 1: // Catalog Sort
89-
modes := []string{"updated", "name", "stars", "installed"}
90-
idx := 0
91-
for i, mth := range modes {
92-
if mth == m.settingsCatalogSort {
93-
idx = (i + 1) % len(modes)
94-
break
95-
}
96-
}
97-
m.settingsCatalogSort = modes[idx]
98-
case 2: // Trusted Orgs
99-
m.addLog(">>> To configure Trusted Orgs, modify .desktop/settings.json (githubOrgs field).")
100-
case 3: // GitHub User
101-
m.addLog(">>> To configure GitHub User, modify .desktop/settings.json or use OWD_GITHUB_USER env var.")
102-
case 4: // Reset Workspace
103-
m.activePrompt = PromptWipeWorkspaceConfirm
104-
m.promptSel = 1 // default to No
105-
case 5: // Save
106-
m.activePrompt = PromptNone
107-
if m.ctx != nil {
108-
settings := m.ctx.Settings
109-
settings.InstallMode = m.settingsInstallMode
110-
settings.CatalogSort = m.settingsCatalogSort
111-
payload := &bridge.WritePayload{
112-
Settings: &settings,
113-
}
114-
m.statusMsg = "Saving settings…"
115-
m.addLog(">>> Saving settings configuration…")
116-
if err := bridge.WriteChanges(m.workspaceRoot, payload); err != nil {
117-
m.statusMsg = fmt.Sprintf("Save failed: %v", err)
118-
m.addLog(fmt.Sprintf(">>> Save settings failed: %v", err))
119-
} else {
120-
m.statusMsg = "Settings saved successfully."
121-
m.addLog(">>> Settings saved successfully.")
122-
}
123-
}
124-
m.loading = true
125-
return m, tea.Batch(m.loadContextCmd(), m.loadCatalogCmd(false))
126-
case 6: // Cancel
127-
m.activePrompt = PromptNone
128-
}
129188
}
130189
return m, nil
131190
}
@@ -181,6 +240,7 @@ func (m *TuiModel) handleUninstallConfirmKeys(msg tea.KeyMsg) (tea.Model, tea.Cm
181240
m.promptPkg = nil
182241
m.promptQueue = nil
183242
m.promptQueueIndex = 0
243+
m.startServerAfterSetup = false
184244
return m, nil
185245
case "left", "h", "up", "k":
186246
m.promptSel = 0
@@ -220,6 +280,7 @@ func (m *TuiModel) handleInstallMethodKeys(msg tea.KeyMsg) (tea.Model, tea.Cmd)
220280
m.promptPkg = nil
221281
m.promptQueue = nil
222282
m.promptQueueIndex = 0
283+
m.startServerAfterSetup = false
223284
return m, nil
224285
case "up", "k":
225286
if m.promptSel > 0 {
@@ -333,7 +394,7 @@ func (m *TuiModel) triggerUninstall(pkg *bridge.CatalogEntry) (tea.Model, tea.Cm
333394
return m, nil
334395
}
335396
m.RunSetupTask(make(map[string]string))
336-
return m, nil
397+
return m, m.listenToChannel()
337398
}
338399

339400
func (m *TuiModel) triggerInstall(pkg *bridge.CatalogEntry, method string) (tea.Model, tea.Cmd) {
@@ -388,7 +449,7 @@ func (m *TuiModel) triggerInstall(pkg *bridge.CatalogEntry, method string) (tea.
388449
return m, nil
389450
}
390451
m.RunSetupTask(map[string]string{pkg.Name: method})
391-
return m, nil
452+
return m, m.listenToChannel()
392453
}
393454

394455
func (m *TuiModel) triggerForceReinstall(pkg *bridge.CatalogEntry) (tea.Model, tea.Cmd) {
@@ -432,7 +493,7 @@ func (m *TuiModel) triggerForceReinstall(pkg *bridge.CatalogEntry) (tea.Model, t
432493
}
433494

434495
m.RunSetupTask(map[string]string{pkg.Name: method})
435-
return m, nil
496+
return m, m.listenToChannel()
436497
}
437498

438499
func (m *TuiModel) triggerUpdate(pkg *bridge.CatalogEntry) (tea.Model, tea.Cmd) {
@@ -475,7 +536,7 @@ func (m *TuiModel) triggerUpdate(pkg *bridge.CatalogEntry) (tea.Model, tea.Cmd)
475536
err := runtime.runProcessAndStreamLogsSilent(workspaceRoot, "pnpm", []string{"run", "prepare:modules"})
476537
runtime.msgChan <- taskFinishedMsg{Success: err == nil, Err: err}
477538
}()
478-
return m, nil
539+
return m, m.listenToChannel()
479540
}
480541

481542
// ─────────────────────────────────────────────
@@ -587,7 +648,8 @@ func (m *TuiModel) startStartupCheck() tea.Cmd {
587648
func (m *TuiModel) processNextQueueDecision() tea.Cmd {
588649
if m.promptQueueIndex >= len(m.promptQueue) {
589650
m.activePrompt = PromptNone
590-
return m.applyQueueChangesCmd()
651+
// IMPORTANT: batch with listenToChannel so the setup task's messages are consumed
652+
return tea.Batch(m.applyQueueChangesCmd(), m.listenToChannel())
591653
}
592654

593655
dec := m.promptQueue[m.promptQueueIndex]
@@ -760,15 +822,16 @@ func (m *TuiModel) applyQueueChangesCmd() tea.Cmd {
760822
return taskFinishedMsg{Success: false, Err: err}
761823
}
762824

825+
m.runtime.msgChan <- logLineMsg(">>> Workspace changes written. Starting setup…")
763826
m.RunSetupTask(finalAdds)
764-
return logLineMsg(">>> Workspace changes written. Running setup task…")
827+
return nil
765828
}
766829
}
767830

768831
func (m *TuiModel) runWipeWorkspaceCmd() tea.Cmd {
769832
return func() tea.Msg {
770833
m.RunWipeWorkspaceTask()
771-
return nil
834+
return logLineMsg(">>> Wipe workspace task started…")
772835
}
773836
}
774837

@@ -912,3 +975,21 @@ func (m *TuiModel) startQueueReview() tea.Cmd {
912975

913976
return m.processNextQueueDecision()
914977
}
978+
979+
func (m *TuiModel) updateSettingsFocus() {
980+
if m.activePrompt == PromptSettings {
981+
if m.settingsSel == 2 {
982+
m.settingsOrgsInput.Focus()
983+
m.settingsUserInput.Blur()
984+
} else if m.settingsSel == 3 {
985+
m.settingsUserInput.Focus()
986+
m.settingsOrgsInput.Blur()
987+
} else {
988+
m.settingsOrgsInput.Blur()
989+
m.settingsUserInput.Blur()
990+
}
991+
} else {
992+
m.settingsOrgsInput.Blur()
993+
m.settingsUserInput.Blur()
994+
}
995+
}

0 commit comments

Comments
 (0)