Skip to content

Commit 0b42a8b

Browse files
committed
Address review comments
1 parent a6cfc17 commit 0b42a8b

File tree

10 files changed

+54
-111
lines changed

10 files changed

+54
-111
lines changed

cmd/root.go

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -122,10 +122,8 @@ func startEmulator(ctx context.Context, rt runtime.Runtime, cfg *env.Env, tel *t
122122
}
123123

124124
notifyOpts := update.NotifyOptions{
125-
GitHubToken: cfg.GitHubToken,
126-
UpdatePrompt: appConfig.CLI.UpdatePrompt,
127-
SkippedVersion: appConfig.CLI.UpdateSkippedVersion,
128-
PersistSkipVersion: config.SetUpdateSkippedVersion,
125+
GitHubToken: cfg.GitHubToken,
126+
UpdatePrompt: appConfig.CLI.UpdatePrompt,
129127
}
130128

131129
if isInteractiveMode(cfg) {

internal/config/config.go

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,7 @@ import (
1414
var defaultConfigTemplate string
1515

1616
type CLIConfig struct {
17-
UpdatePrompt bool `mapstructure:"update_prompt"`
18-
UpdateSkippedVersion string `mapstructure:"update_skipped_version"`
17+
UpdatePrompt bool `mapstructure:"update_prompt"`
1918
}
2019

2120
type Config struct {
@@ -33,7 +32,6 @@ func setDefaults() {
3332
},
3433
})
3534
viper.SetDefault("cli.update_prompt", true)
36-
viper.SetDefault("cli.update_skipped_version", "")
3735
}
3836

3937
func loadConfig(path string) error {
@@ -119,14 +117,6 @@ func DisableUpdatePrompt() error {
119117
return Set("cli.update_prompt", false)
120118
}
121119

122-
func SetUpdateSkippedVersion(version string) error {
123-
return Set("cli.update_skipped_version", version)
124-
}
125-
126-
func GetUpdateSkippedVersion() string {
127-
return viper.GetString("cli.update_skipped_version")
128-
}
129-
130120
func Get() (*Config, error) {
131121
var cfg Config
132122
if err := viper.Unmarshal(&cfg); err != nil {
@@ -135,9 +125,6 @@ func Get() (*Config, error) {
135125
if !viper.InConfig("cli.update_prompt") && viper.InConfig("update_prompt") {
136126
cfg.CLI.UpdatePrompt = viper.GetBool("update_prompt")
137127
}
138-
if !viper.InConfig("cli.update_skipped_version") && viper.InConfig("update_skipped_version") {
139-
cfg.CLI.UpdateSkippedVersion = viper.GetString("update_skipped_version")
140-
}
141128
for i := range cfg.Containers {
142129
if err := cfg.Containers[i].Validate(); err != nil {
143130
return nil, fmt.Errorf("invalid container config: %w", err)

internal/config/default_config.toml

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,6 @@
11
# lstk configuration file
22
# Run 'lstk config path' to see where this file lives.
33

4-
# CLI settings
5-
[cli]
6-
update_prompt = true
7-
84
# Each [[containers]] block defines an emulator instance.
95
# You can define multiple to run them side by side.
106
[[containers]]

internal/output/events.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ type UserInputRequestEvent struct {
122122
Prompt string
123123
Options []InputOption
124124
ResponseCh chan<- InputResponse
125+
Vertical bool
125126
}
126127

127128
const (

internal/ui/app.go

Lines changed: 30 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -95,25 +95,8 @@ func (a App) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
9595
return a, tea.Quit
9696
}
9797
if a.pendingInput != nil {
98-
if componentsUsesVerticalPrompt(a.inputPrompt, a.pendingInput.Options) {
99-
switch msg.Type {
100-
case tea.KeyUp:
101-
a.inputPrompt = a.inputPrompt.SetSelectedIndex(a.inputPrompt.SelectedIndex() - 1)
102-
return a, nil
103-
case tea.KeyDown:
104-
a.inputPrompt = a.inputPrompt.SetSelectedIndex(a.inputPrompt.SelectedIndex() + 1)
105-
return a, nil
106-
case tea.KeyEnter:
107-
idx := a.inputPrompt.SelectedIndex()
108-
if idx >= 0 && idx < len(a.pendingInput.Options) {
109-
opt := a.pendingInput.Options[idx]
110-
a.lines = appendLine(a.lines, styledLine{text: formatResolvedInput(*a.pendingInput, opt.Key)})
111-
responseCmd := sendInputResponseCmd(a.pendingInput.ResponseCh, output.InputResponse{SelectedKey: opt.Key})
112-
a.pendingInput = nil
113-
a.inputPrompt = a.inputPrompt.Hide()
114-
return a, responseCmd
115-
}
116-
}
98+
if a.pendingInput.Vertical {
99+
return a.handleVerticalPromptKey(msg)
117100
}
118101
if opt := resolveOption(a.pendingInput.Options, msg); opt != nil {
119102
a.lines = appendLine(a.lines, styledLine{text: formatResolvedInput(*a.pendingInput, opt.Key)})
@@ -130,7 +113,7 @@ func (a App) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
130113
if a.spinner.Visible() {
131114
a.spinner = a.spinner.SetText(output.FormatPrompt(msg.Prompt, msg.Options))
132115
} else {
133-
a.inputPrompt = a.inputPrompt.Show(msg.Prompt, msg.Options)
116+
a.inputPrompt = a.inputPrompt.Show(msg.Prompt, msg.Options, msg.Vertical)
134117
}
135118
case spinner.TickMsg:
136119
var cmd tea.Cmd
@@ -315,20 +298,36 @@ func (a *App) flushBufferedLines() {
315298
a.bufferedLines = nil
316299
}
317300

318-
func formatResolvedInput(req output.UserInputRequestEvent, selectedKey string) string {
319-
// Special handling for lstk update prompt
320-
if len(req.Options) > 0 && strings.Contains(req.Options[0].Label, "Update now") {
321-
checkmark := styles.Success.Render(output.SuccessMarker())
322-
switch selectedKey {
323-
case "u":
324-
return checkmark + " Updating lstk..."
325-
case "s":
326-
return checkmark + " Skipped this version"
327-
case "n":
328-
return checkmark + " Won't ask again"
301+
func (a App) handleVerticalPromptKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
302+
switch msg.Type {
303+
case tea.KeyUp:
304+
a.inputPrompt = a.inputPrompt.SetSelectedIndex(a.inputPrompt.SelectedIndex() - 1)
305+
return a, nil
306+
case tea.KeyDown:
307+
a.inputPrompt = a.inputPrompt.SetSelectedIndex(a.inputPrompt.SelectedIndex() + 1)
308+
return a, nil
309+
case tea.KeyEnter:
310+
idx := a.inputPrompt.SelectedIndex()
311+
if idx >= 0 && idx < len(a.pendingInput.Options) {
312+
opt := a.pendingInput.Options[idx]
313+
a.lines = appendLine(a.lines, styledLine{text: formatResolvedInput(*a.pendingInput, opt.Key)})
314+
responseCmd := sendInputResponseCmd(a.pendingInput.ResponseCh, output.InputResponse{SelectedKey: opt.Key})
315+
a.pendingInput = nil
316+
a.inputPrompt = a.inputPrompt.Hide()
317+
return a, responseCmd
329318
}
330319
}
320+
if opt := resolveOption(a.pendingInput.Options, msg); opt != nil {
321+
a.lines = appendLine(a.lines, styledLine{text: formatResolvedInput(*a.pendingInput, opt.Key)})
322+
responseCmd := sendInputResponseCmd(a.pendingInput.ResponseCh, output.InputResponse{SelectedKey: opt.Key})
323+
a.pendingInput = nil
324+
a.inputPrompt = a.inputPrompt.Hide()
325+
return a, responseCmd
326+
}
327+
return a, nil
328+
}
331329

330+
func formatResolvedInput(req output.UserInputRequestEvent, selectedKey string) string {
332331
formatted := output.FormatPrompt(req.Prompt, req.Options)
333332
firstLine := strings.Split(formatted, "\n")[0]
334333

@@ -349,10 +348,6 @@ func formatResolvedInput(req output.UserInputRequestEvent, selectedKey string) s
349348
return fmt.Sprintf("%s %s", firstLine, selected)
350349
}
351350

352-
func componentsUsesVerticalPrompt(prompt components.InputPrompt, options []output.InputOption) bool {
353-
return prompt.Visible() && len(options) > 1 && strings.Contains(options[0].Label, "[")
354-
}
355-
356351
// resolveOption finds the best matching option for a key event, in priority order:
357352
// 1. "any" — matches any keypress
358353
// 2. "enter" — matches the Enter key explicitly

internal/ui/app_test.go

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -417,13 +417,10 @@ func TestAppEnterSelectsHighlightedVerticalOption(t *testing.T) {
417417
responseCh := make(chan output.InputResponse, 1)
418418

419419
model, _ := app.Update(output.UserInputRequestEvent{
420-
Prompt: "Update lstk to latest version?",
421-
Options: []output.InputOption{
422-
{Key: "u", Label: "Update now [U]"},
423-
{Key: "s", Label: "Skip this version [S]"},
424-
{Key: "n", Label: "Never ask again [N]"},
425-
},
420+
Prompt: "Update lstk to latest version?",
421+
Options: []output.InputOption{{Key: "u", Label: "Update now [U]"}, {Key: "s", Label: "Skip this version [S]"}, {Key: "n", Label: "Never ask again [N]"}},
426422
ResponseCh: responseCh,
423+
Vertical: true,
427424
})
428425
app = model.(App)
429426

internal/ui/components/input_prompt.go

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,19 @@ type InputPrompt struct {
1212
options []output.InputOption
1313
visible bool
1414
selectedIndex int
15+
vertical bool
1516
}
1617

1718
func NewInputPrompt() InputPrompt {
1819
return InputPrompt{}
1920
}
2021

21-
func (p InputPrompt) Show(prompt string, options []output.InputOption) InputPrompt {
22+
func (p InputPrompt) Show(prompt string, options []output.InputOption, vertical bool) InputPrompt {
2223
p.prompt = prompt
2324
p.options = options
2425
p.visible = true
2526
p.selectedIndex = 0
27+
p.vertical = vertical
2628
return p
2729
}
2830

@@ -51,7 +53,7 @@ func (p InputPrompt) View() string {
5153
return ""
5254
}
5355

54-
if usesVerticalOptions(p.options) {
56+
if p.vertical {
5557
return p.viewVertical()
5658
}
5759

@@ -95,14 +97,3 @@ func (p InputPrompt) viewVertical() string {
9597
return sb.String()
9698
}
9799

98-
func usesVerticalOptions(options []output.InputOption) bool {
99-
if len(options) < 2 {
100-
return false
101-
}
102-
for _, opt := range options {
103-
if strings.Contains(opt.Label, "[") && strings.Contains(opt.Label, "]") {
104-
return true
105-
}
106-
}
107-
return false
108-
}

internal/ui/components/input_prompt_test.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,26 +14,30 @@ func TestInputPromptView(t *testing.T) {
1414
name string
1515
prompt string
1616
options []output.InputOption
17+
vertical bool
1718
contains []string
1819
excludes []string
1920
}{
2021
{
2122
name: "hidden returns empty",
2223
prompt: "",
2324
options: nil,
25+
vertical: false,
2426
contains: nil,
2527
},
2628
{
2729
name: "no options",
2830
prompt: "Continue?",
2931
options: nil,
32+
vertical: false,
3033
contains: []string{"?", "Continue?"},
3134
excludes: []string{"(", "["},
3235
},
3336
{
3437
name: "single option shows parentheses",
3538
prompt: "Continue?",
3639
options: []output.InputOption{{Key: "enter", Label: "Press ENTER"}},
40+
vertical: false,
3741
contains: []string{"?", "Continue?", "(Press ENTER)"},
3842
},
3943
{
@@ -43,12 +47,14 @@ func TestInputPromptView(t *testing.T) {
4347
{Key: "y", Label: "Y"},
4448
{Key: "n", Label: "n"},
4549
},
50+
vertical: false,
4651
contains: []string{"?", "Configure AWS profile?", "[Y/n]"},
4752
},
4853
{
4954
name: "multi-line prompt renders trailing lines",
5055
prompt: "First line\nSecond line\nThird line",
5156
options: []output.InputOption{{Key: "y", Label: "Y"}},
57+
vertical: false,
5258
contains: []string{"?", "First line", "Second line", "Third line", "(Y)"},
5359
},
5460
}
@@ -67,7 +73,7 @@ func TestInputPromptView(t *testing.T) {
6773
return
6874
}
6975

70-
p = p.Show(tc.prompt, tc.options)
76+
p = p.Show(tc.prompt, tc.options, tc.vertical)
7177
view := p.View()
7278

7379
for _, s := range tc.contains {

internal/update/notify.go

Lines changed: 6 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,8 @@ import (
1212
type versionFetcher func(ctx context.Context, token string) (string, error)
1313

1414
type NotifyOptions struct {
15-
GitHubToken string
16-
UpdatePrompt bool
17-
SkippedVersion string
18-
PersistSkipVersion func(version string) error
15+
GitHubToken string
16+
UpdatePrompt bool
1917
}
2018

2119
const checkTimeout = 2 * time.Second
@@ -56,10 +54,6 @@ func notifyUpdateWithVersion(ctx context.Context, sink output.Sink, opts NotifyO
5654
return false
5755
}
5856

59-
if opts.SkippedVersion != "" && normalizeVersion(opts.SkippedVersion) == normalizeVersion(latest) {
60-
return false
61-
}
62-
6357
if !opts.UpdatePrompt {
6458
output.EmitNote(sink, fmt.Sprintf("Update available: %s → %s (run lstk update)", current, latest))
6559
return false
@@ -76,13 +70,10 @@ func promptAndUpdate(ctx context.Context, sink output.Sink, opts NotifyOptions,
7670

7771
responseCh := make(chan output.InputResponse, 1)
7872
output.EmitUserInputRequest(sink, output.UserInputRequestEvent{
79-
Prompt: "Update lstk to latest version?",
80-
Options: []output.InputOption{
81-
{Key: "u", Label: "Update now [U]"},
82-
{Key: "r", Label: "Remind me next time [R]"},
83-
{Key: "s", Label: "Skip this version [S]"},
84-
},
73+
Prompt: "Update lstk to latest version?",
74+
Options: []output.InputOption{{Key: "u", Label: "Update now [U]"}, {Key: "r", Label: "Remind me next time [R]"}, {Key: "s", Label: "Skip this version [S]"}},
8575
ResponseCh: responseCh,
76+
Vertical: true,
8677
})
8778

8879
var resp output.InputResponse
@@ -107,11 +98,7 @@ func promptAndUpdate(ctx context.Context, sink output.Sink, opts NotifyOptions,
10798
case "r":
10899
return false
109100
case "s":
110-
if opts.PersistSkipVersion != nil {
111-
if err := opts.PersistSkipVersion(latest); err != nil {
112-
output.EmitWarning(sink, fmt.Sprintf("Failed to save preference: %v", err))
113-
}
114-
}
101+
output.EmitNote(sink, "Skipping version " + latest)
115102
return false
116103
}
117104

internal/update/notify_test.go

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -161,18 +161,3 @@ func TestNotifyUpdatePromptCancelled(t *testing.T) {
161161
assert.False(t, exit)
162162
}
163163

164-
func TestNotifyUpdateSkippedVersion(t *testing.T) {
165-
server := newTestGitHubServer(t, "v2.0.0")
166-
defer server.Close()
167-
168-
var events []any
169-
sink := output.SinkFunc(func(event any) { events = append(events, event) })
170-
171-
// Should return early without prompting since v2.0.0 was skipped
172-
exit := notifyUpdateWithVersion(context.Background(), sink, NotifyOptions{
173-
UpdatePrompt: true,
174-
SkippedVersion: "v2.0.0",
175-
}, "1.0.0", testFetcher(server.URL))
176-
assert.False(t, exit)
177-
assert.Empty(t, events) // No events should be emitted
178-
}

0 commit comments

Comments
 (0)