Skip to content

Commit dd6fef0

Browse files
authored
refactor(experiment): separate huh and lipgloss changes of charm (#411)
1 parent bc159a6 commit dd6fef0

File tree

15 files changed

+168
-128
lines changed

15 files changed

+168
-128
lines changed

cmd/help/help.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ func HelpFunc(
3535
if help, _ := clients.Config.Flags.GetBool("help"); help {
3636
clients.Config.LoadExperiments(ctx, clients.IO.PrintDebug)
3737
}
38-
style.ToggleCharm(clients.Config.WithExperimentOn(experiment.Charm))
38+
style.ToggleLipgloss(clients.Config.WithExperimentOn(experiment.Lipgloss))
3939
experiments := []string{}
4040
for _, exp := range clients.Config.GetExperiments() {
4141
if experiment.Includes(exp) {
@@ -68,7 +68,7 @@ func PrintHelpTemplate(cmd *cobra.Command, data style.TemplateData) {
6868
}
6969
cmd.Long = cmdLongF.String()
7070
tmpl := legacyHelpTemplate
71-
if style.IsCharmEnabled() {
71+
if style.IsLipglossEnabled() {
7272
tmpl = charmHelpTemplate
7373
}
7474
err = style.PrintTemplate(cmd.OutOrStdout(), tmpl, templateInfo{cmd, data})
@@ -121,7 +121,7 @@ const charmHelpTemplate string = `{{.Long | ToDescription}}
121121
// ════════════════════════════════════════════════════════════════════════════════
122122
// DEPRECATED: Legacy help template — aurora styling
123123
//
124-
// Delete this entire block when the charm experiment is permanently enabled.
124+
// Delete this entire block when the lipgloss experiment is permanently enabled.
125125
// ════════════════════════════════════════════════════════════════════════════════
126126

127127
const legacyHelpTemplate string = `{{.Long}}

cmd/project/create_samples.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ func promptSampleSelection(ctx context.Context, clients *shared.ClientFactory, s
6767
sortedRepos := sortRepos(filteredRepos)
6868
selectOptions := make([]string, len(sortedRepos))
6969
for i, r := range sortedRepos {
70-
if !clients.Config.WithExperimentOn(experiment.Charm) {
70+
if !clients.Config.WithExperimentOn(experiment.Huh) {
7171
selectOptions[i] = fmt.Sprint(i+1, ". ", r.Name)
7272
} else {
7373
selectOptions[i] = r.Name
@@ -78,7 +78,7 @@ func promptSampleSelection(ctx context.Context, clients *shared.ClientFactory, s
7878
selection, err = clients.IO.SelectPrompt(ctx, "Select a sample to build upon:", selectOptions, iostreams.SelectPromptConfig{
7979
Description: func(value string, index int) string {
8080
desc := sortedRepos[index].Description
81-
if !clients.Config.WithExperimentOn(experiment.Charm) {
81+
if !clients.Config.WithExperimentOn(experiment.Huh) {
8282
desc += "\n https://github.com/" + sortedRepos[index].FullName
8383
}
8484
return desc

cmd/root.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -295,7 +295,7 @@ func InitConfig(ctx context.Context, clients *shared.ClientFactory, rootCmd *cob
295295

296296
// Init configurations
297297
clients.Config.LoadExperiments(ctx, clients.IO.PrintDebug)
298-
style.ToggleCharm(clients.Config.WithExperimentOn(experiment.Charm))
298+
style.ToggleLipgloss(clients.Config.WithExperimentOn(experiment.Lipgloss))
299299
// TODO(slackcontext) Consolidate storing CLI version to slackcontext
300300
clients.Config.Version = clients.CLIVersion
301301

docs/reference/experiments.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,15 @@ The Slack CLI has an experiment (`-e`) flag behind which we put features current
66

77
The following is a list of currently available experiments. We'll remove experiments from this page if we decide they are no longer needed or once they are released, in which case we'll make an announcement about the feature's general availability in the [developer changelog](https://docs.slack.dev/changelog).
88

9-
- `charm`: shows beautiful prompts ([PR#348](https://github.com/slackapi/slack-cli/pull/348)).
9+
- `huh`: shows beautiful prompts.
10+
- `lipgloss`: shows pretty styles.
1011
- `sandboxes`: enables users who have joined the Slack Developer Program to manage their sandboxes ([PR#379](https://github.com/slackapi/slack-cli/pull/379)).
1112

1213
## Experiments changelog
1314

1415
Below is a list of updates related to experiments.
1516

17+
- **March 2026**: Split the `charm` experiment into more beautiful `huh` prompts and prettier `lipgloss` styles for ongoing change.
1618
- **March 2026**: Concluded the `bolt` and `bolt-install` experiments with full Bolt framework support now enabled by default in the Slack CLI. All Bolt project features including remote manifest management are now standard functionality. See the announcement [here](https://slack.dev/slackcli-supports-bolt-apps/).
1719
- **February 2026**: Added the `charm` experiment.
1820
- **December 2025**: Concluded the `read-only-collaborators` experiment with full support introduced to the Slack CLI. See the changelog announcement [here](https://docs.slack.dev/changelog/2025/12/04/slack-cli).

internal/experiment/experiment.go

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,20 +30,24 @@ type Experiment string
3030
// e.g. --experiment=first-toggle,second-toggle
3131

3232
const (
33-
// Charm experiment enables beautiful prompts.
34-
Charm Experiment = "charm"
33+
// Huh experiment shows beautiful prompts.
34+
Huh Experiment = "huh"
3535

36-
// Sandboxes experiment lets users who have joined the Slack Developer Program use the CLI to manage their sandboxes.
37-
Sandboxes Experiment = "sandboxes"
36+
// Lipgloss experiment shows pretty styles.
37+
Lipgloss Experiment = "lipgloss"
3838

3939
// Placeholder experiment is a placeholder for testing and does nothing... or does it?
4040
Placeholder Experiment = "placeholder"
41+
42+
// Sandboxes experiment lets users who have joined the Slack Developer Program use the CLI to manage their sandboxes.
43+
Sandboxes Experiment = "sandboxes"
4144
)
4245

4346
// AllExperiments is a list of all available experiments that can be enabled
4447
// Please also add here 👇
4548
var AllExperiments = []Experiment{
46-
Charm,
49+
Huh,
50+
Lipgloss,
4751
Placeholder,
4852
Sandboxes,
4953
}

internal/experiment/experiment_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ func Test_Includes(t *testing.T) {
2525
require.Equal(t, true, Includes(Experiment(Placeholder)))
2626

2727
// Test expected experiments
28-
require.Equal(t, true, Includes(Experiment("charm")))
28+
require.Equal(t, true, Includes(Experiment("huh")))
29+
require.Equal(t, true, Includes(Experiment("lipgloss")))
2930

3031
// Test invalid experiment
3132
require.Equal(t, false, Includes(Experiment("should-fail")))

internal/iostreams/charm.go

Lines changed: 32 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -14,21 +14,31 @@
1414

1515
package iostreams
1616

17-
// Charm-based prompt implementations using the huh library
18-
// These are used when the "charm" experiment is enabled
17+
// Charm-based prompt implementations using the huh library.
18+
// These are used when the "huh" experiment is enabled.
1919

2020
import (
2121
"context"
2222
"errors"
2323
"slices"
2424

2525
huh "charm.land/huh/v2"
26+
"github.com/slackapi/slack-cli/internal/experiment"
2627
"github.com/slackapi/slack-cli/internal/slackerror"
2728
"github.com/slackapi/slack-cli/internal/style"
2829
)
2930

31+
// newForm wraps a field in a huh form, applying the Slack theme when the lipgloss experiment is enabled.
32+
func newForm(io *IOStreams, field huh.Field) *huh.Form {
33+
form := huh.NewForm(huh.NewGroup(field))
34+
if io != nil && io.config.WithExperimentOn(experiment.Lipgloss) {
35+
form = form.WithTheme(style.ThemeSlack())
36+
}
37+
return form
38+
}
39+
3040
// buildInputForm constructs a huh form for text input prompts.
31-
func buildInputForm(message string, cfg InputPromptConfig, input *string) *huh.Form {
41+
func buildInputForm(io *IOStreams, message string, cfg InputPromptConfig, input *string) *huh.Form {
3242
field := huh.NewInput().
3343
Title(message).
3444
Prompt(style.Chevron() + " ").
@@ -37,13 +47,13 @@ func buildInputForm(message string, cfg InputPromptConfig, input *string) *huh.F
3747
if cfg.Required {
3848
field.Validate(huh.ValidateMinLength(1))
3949
}
40-
return huh.NewForm(huh.NewGroup(field)).WithTheme(style.ThemeSlack())
50+
return newForm(io, field)
4151
}
4252

4353
// charmInputPrompt prompts for text input using a charm huh form
44-
func charmInputPrompt(_ *IOStreams, _ context.Context, message string, cfg InputPromptConfig) (string, error) {
54+
func charmInputPrompt(io *IOStreams, _ context.Context, message string, cfg InputPromptConfig) (string, error) {
4555
var input string
46-
err := buildInputForm(message, cfg, &input).Run()
56+
err := buildInputForm(io, message, cfg, &input).Run()
4757
if errors.Is(err, huh.ErrUserAborted) {
4858
return "", slackerror.New(slackerror.ErrProcessInterrupted)
4959
} else if err != nil {
@@ -53,17 +63,17 @@ func charmInputPrompt(_ *IOStreams, _ context.Context, message string, cfg Input
5363
}
5464

5565
// buildConfirmForm constructs a huh form for yes/no confirmation prompts.
56-
func buildConfirmForm(message string, choice *bool) *huh.Form {
66+
func buildConfirmForm(io *IOStreams, message string, choice *bool) *huh.Form {
5767
field := huh.NewConfirm().
5868
Title(message).
5969
Value(choice)
60-
return huh.NewForm(huh.NewGroup(field)).WithTheme(style.ThemeSlack())
70+
return newForm(io, field)
6171
}
6272

6373
// charmConfirmPrompt prompts for a yes/no confirmation using a charm huh form
64-
func charmConfirmPrompt(_ *IOStreams, _ context.Context, message string, defaultValue bool) (bool, error) {
74+
func charmConfirmPrompt(io *IOStreams, _ context.Context, message string, defaultValue bool) (bool, error) {
6575
var choice = defaultValue
66-
err := buildConfirmForm(message, &choice).Run()
76+
err := buildConfirmForm(io, message, &choice).Run()
6777
if errors.Is(err, huh.ErrUserAborted) {
6878
return false, slackerror.New(slackerror.ErrProcessInterrupted)
6979
} else if err != nil {
@@ -73,7 +83,7 @@ func charmConfirmPrompt(_ *IOStreams, _ context.Context, message string, default
7383
}
7484

7585
// buildSelectForm constructs a huh form for single-selection prompts.
76-
func buildSelectForm(msg string, options []string, cfg SelectPromptConfig, selected *string) *huh.Form {
86+
func buildSelectForm(io *IOStreams, msg string, options []string, cfg SelectPromptConfig, selected *string) *huh.Form {
7787
var opts []huh.Option[string]
7888
for _, opt := range options {
7989
key := opt
@@ -91,13 +101,13 @@ func buildSelectForm(msg string, options []string, cfg SelectPromptConfig, selec
91101
Options(opts...).
92102
Value(selected)
93103

94-
return huh.NewForm(huh.NewGroup(field)).WithTheme(style.ThemeSlack())
104+
return newForm(io, field)
95105
}
96106

97107
// charmSelectPrompt prompts the user to select one option using a charm huh form
98-
func charmSelectPrompt(_ *IOStreams, _ context.Context, msg string, options []string, cfg SelectPromptConfig) (SelectPromptResponse, error) {
108+
func charmSelectPrompt(io *IOStreams, _ context.Context, msg string, options []string, cfg SelectPromptConfig) (SelectPromptResponse, error) {
99109
var selected string
100-
err := buildSelectForm(msg, options, cfg, &selected).Run()
110+
err := buildSelectForm(io, msg, options, cfg, &selected).Run()
101111
if errors.Is(err, huh.ErrUserAborted) {
102112
return SelectPromptResponse{}, slackerror.New(slackerror.ErrProcessInterrupted)
103113
} else if err != nil {
@@ -109,7 +119,7 @@ func charmSelectPrompt(_ *IOStreams, _ context.Context, msg string, options []st
109119
}
110120

111121
// buildPasswordForm constructs a huh form for password (hidden input) prompts.
112-
func buildPasswordForm(message string, cfg PasswordPromptConfig, input *string) *huh.Form {
122+
func buildPasswordForm(io *IOStreams, message string, cfg PasswordPromptConfig, input *string) *huh.Form {
113123
field := huh.NewInput().
114124
Title(message).
115125
Prompt(style.Chevron() + " ").
@@ -118,13 +128,13 @@ func buildPasswordForm(message string, cfg PasswordPromptConfig, input *string)
118128
if cfg.Required {
119129
field.Validate(huh.ValidateMinLength(1))
120130
}
121-
return huh.NewForm(huh.NewGroup(field)).WithTheme(style.ThemeSlack())
131+
return newForm(io, field)
122132
}
123133

124134
// charmPasswordPrompt prompts for a password (hidden input) using a charm huh form
125-
func charmPasswordPrompt(_ *IOStreams, _ context.Context, message string, cfg PasswordPromptConfig) (PasswordPromptResponse, error) {
135+
func charmPasswordPrompt(io *IOStreams, _ context.Context, message string, cfg PasswordPromptConfig) (PasswordPromptResponse, error) {
126136
var input string
127-
err := buildPasswordForm(message, cfg, &input).Run()
137+
err := buildPasswordForm(io, message, cfg, &input).Run()
128138
if errors.Is(err, huh.ErrUserAborted) {
129139
return PasswordPromptResponse{}, slackerror.New(slackerror.ErrProcessInterrupted)
130140
} else if err != nil {
@@ -134,7 +144,7 @@ func charmPasswordPrompt(_ *IOStreams, _ context.Context, message string, cfg Pa
134144
}
135145

136146
// buildMultiSelectForm constructs a huh form for multiple-selection prompts.
137-
func buildMultiSelectForm(message string, options []string, selected *[]string) *huh.Form {
147+
func buildMultiSelectForm(io *IOStreams, message string, options []string, selected *[]string) *huh.Form {
138148
var opts []huh.Option[string]
139149
for _, opt := range options {
140150
opts = append(opts, huh.NewOption(opt, opt))
@@ -145,13 +155,13 @@ func buildMultiSelectForm(message string, options []string, selected *[]string)
145155
Options(opts...).
146156
Value(selected)
147157

148-
return huh.NewForm(huh.NewGroup(field)).WithTheme(style.ThemeSlack())
158+
return newForm(io, field)
149159
}
150160

151161
// charmMultiSelectPrompt prompts the user to select multiple options using a charm huh form
152-
func charmMultiSelectPrompt(_ *IOStreams, _ context.Context, message string, options []string) ([]string, error) {
162+
func charmMultiSelectPrompt(io *IOStreams, _ context.Context, message string, options []string) ([]string, error) {
153163
var selected []string
154-
err := buildMultiSelectForm(message, options, &selected).Run()
164+
err := buildMultiSelectForm(io, message, options, &selected).Run()
155165
if errors.Is(err, huh.ErrUserAborted) {
156166
return []string{}, slackerror.New(slackerror.ErrProcessInterrupted)
157167
} else if err != nil {

0 commit comments

Comments
 (0)