Skip to content

Commit 2483670

Browse files
halfaipgclaude
andcommitted
fix: arrow key navigation in setup wizard
The handleArrowSelect method used a pointer to modify the caller's index field, but returned its own stale copy of the model struct (Go value receiver copy semantics). Arrow presses appeared to do nothing because the index update was lost on return. Replace with a free function arrowSelect() that returns the new index value, which the caller assigns directly to its own struct. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 1273d85 commit 2483670

1 file changed

Lines changed: 43 additions & 37 deletions

File tree

setup.go

Lines changed: 43 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -152,10 +152,11 @@ func (m setupModel) handleKey(msg tea.KeyMsg) (setupModel, tea.Cmd) {
152152

153153
switch m.step {
154154
case stepProvider:
155-
return m.handleArrowSelect(key, len(providers), &m.providerIdx, func() (setupModel, tea.Cmd) {
155+
newIdx, entered := arrowSelect(key, len(providers), m.providerIdx)
156+
m.providerIdx = newIdx
157+
if entered {
156158
p := providers[m.providerIdx]
157159
if p.baseURL == "" {
158-
// Custom URL
159160
m.step = stepCustomURL
160161
m.input.SetValue("")
161162
m.input.Placeholder = "https://your-api.example.com/v1"
@@ -169,7 +170,8 @@ func (m setupModel) handleKey(msg tea.KeyMsg) (setupModel, tea.Cmd) {
169170
m.input.EchoMode = textinput.EchoPassword
170171
m.input.Focus()
171172
return m, textinput.Blink
172-
})
173+
}
174+
return m, nil
173175

174176
case stepCustomURL:
175177
if key == "enter" {
@@ -259,13 +261,17 @@ func (m setupModel) handleKey(msg tea.KeyMsg) (setupModel, tea.Cmd) {
259261
m.input, cmd = m.input.Update(msg)
260262
return m, cmd
261263
}
262-
return m.handleArrowSelect(key, len(m.models), &m.modelIdx, func() (setupModel, tea.Cmd) {
264+
newIdx, entered := arrowSelect(key, len(m.models), m.modelIdx)
265+
m.modelIdx = newIdx
266+
if entered {
263267
m.step = stepGlueChoice
264-
return m, nil
265-
})
268+
}
269+
return m, nil
266270

267271
case stepGlueChoice:
268-
return m.handleArrowSelect(key, len(glueChoices), &m.glueChoice, func() (setupModel, tea.Cmd) {
272+
newIdx, entered := arrowSelect(key, len(glueChoices), m.glueChoice)
273+
m.glueChoice = newIdx
274+
if entered {
269275
switch m.glueChoice {
270276
case 0: // Same provider
271277
m.glueBaseURL = m.baseURL
@@ -280,11 +286,13 @@ func (m setupModel) handleKey(msg tea.KeyMsg) (setupModel, tea.Cmd) {
280286
case 2: // Skip
281287
return m, m.saveAndFinish()
282288
}
283-
return m, nil
284-
})
289+
}
290+
return m, nil
285291

286292
case stepGlueProvider:
287-
return m.handleArrowSelect(key, len(providers), &m.glueProviderIdx, func() (setupModel, tea.Cmd) {
293+
newIdx, entered := arrowSelect(key, len(providers), m.glueProviderIdx)
294+
m.glueProviderIdx = newIdx
295+
if entered {
288296
p := providers[m.glueProviderIdx]
289297
if p.baseURL == "" {
290298
m.step = stepGlueCustomURL
@@ -300,7 +308,8 @@ func (m setupModel) handleKey(msg tea.KeyMsg) (setupModel, tea.Cmd) {
300308
m.input.EchoMode = textinput.EchoPassword
301309
m.input.Focus()
302310
return m, textinput.Blink
303-
})
311+
}
312+
return m, nil
304313

305314
case stepGlueCustomURL:
306315
if key == "enter" {
@@ -375,10 +384,11 @@ func (m setupModel) handleKey(msg tea.KeyMsg) (setupModel, tea.Cmd) {
375384
m.input, cmd = m.input.Update(msg)
376385
return m, cmd
377386
}
378-
return m.handleArrowSelect(key, len(m.glueModels), &m.glueFastIdx, func() (setupModel, tea.Cmd) {
387+
newIdx, entered := arrowSelect(key, len(m.glueModels), m.glueFastIdx)
388+
m.glueFastIdx = newIdx
389+
if entered {
379390
m.step = stepGlueSmartModel
380391
m.glueSmartIdx = 0
381-
// Pre-select a smarter model if possible
382392
for i, model := range m.glueModels {
383393
lower := strings.ToLower(model)
384394
if strings.Contains(lower, "70b") || strings.Contains(lower, "4o") ||
@@ -387,8 +397,8 @@ func (m setupModel) handleKey(msg tea.KeyMsg) (setupModel, tea.Cmd) {
387397
break
388398
}
389399
}
390-
return m, nil
391-
})
400+
}
401+
return m, nil
392402

393403
case stepGlueSmartModel:
394404
if m.manualEntry {
@@ -397,12 +407,9 @@ func (m setupModel) handleKey(msg tea.KeyMsg) (setupModel, tea.Cmd) {
397407
if val == "" {
398408
return m, nil
399409
}
400-
// Save: glueFast was already set, now set glueSmart
401410
m.glueSmartIdx = 0
402-
// We need to store the manual smart model separately
403411
m.input.Blur()
404412
m.manualEntry = false
405-
// Build saved config with manual entries
406413
sc := m.buildSavedConfig()
407414
sc.GlueSmartModel = val
408415
return m, func() tea.Msg { return setupDoneMsg{config: sc} }
@@ -411,36 +418,35 @@ func (m setupModel) handleKey(msg tea.KeyMsg) (setupModel, tea.Cmd) {
411418
m.input, cmd = m.input.Update(msg)
412419
return m, cmd
413420
}
414-
return m.handleArrowSelect(key, len(m.glueModels), &m.glueSmartIdx, func() (setupModel, tea.Cmd) {
421+
newIdx, entered := arrowSelect(key, len(m.glueModels), m.glueSmartIdx)
422+
m.glueSmartIdx = newIdx
423+
if entered {
415424
return m, m.saveAndFinish()
416-
})
425+
}
426+
return m, nil
417427
}
418428

419429
return m, nil
420430
}
421431

422-
func (m setupModel) handleArrowSelect(key string, count int, idx *int, onEnter func() (setupModel, tea.Cmd)) (setupModel, tea.Cmd) {
432+
// arrowSelect handles arrow/vim key navigation. Returns the new index and
433+
// whether Enter was pressed. This is a free function (not a method) to avoid
434+
// the Go value-receiver copy bug where pointer modifications to a caller's
435+
// struct field are lost when the method returns its own stale copy of m.
436+
func arrowSelect(key string, count int, idx int) (int, bool) {
423437
switch key {
424-
case "up", "k":
425-
if *idx > 0 {
426-
*idx--
427-
}
428-
case "down", "j":
429-
if *idx < count-1 {
430-
*idx++
438+
case "up", "k", "left", "h":
439+
if idx > 0 {
440+
idx--
431441
}
432-
case "left", "h":
433-
if *idx > 0 {
434-
*idx--
435-
}
436-
case "right", "l":
437-
if *idx < count-1 {
438-
*idx++
442+
case "down", "j", "right", "l":
443+
if idx < count-1 {
444+
idx++
439445
}
440446
case "enter":
441-
return onEnter()
447+
return idx, true
442448
}
443-
return m, nil
449+
return idx, false
444450
}
445451

446452
func (m setupModel) handleModelsFetched(msg modelsFetchedMsg) (setupModel, tea.Cmd) {

0 commit comments

Comments
 (0)