Skip to content

[go-fan] Go Module Review: huhΒ #57647

Description

@github-actions

🐹 Go Fan Report: charm.land/huh/v2

Today's pick (deterministic alphabetical round-robin β€” see note below): charm.land/huh/v2 v2.0.3, Charm's terminal form/prompt library and part of the same Charm v2 stack as bubbletea/v2, bubbles/v2, and lipgloss/v2 already used here.

⚠️ Tooling limitation this run: no GitHub MCP tool was available for the pushed_at metadata step, gh api returned 401 Bad credentials for cross-repo reads, and WebFetch/WebSearch permissions were not granted in this automated context. Module selection fell back to the documented deterministic alphabetical order, and the research below is based on static code analysis plus existing knowledge of huh rather than a live changelog check. Recommendations should be spot-checked against the current huh release notes before acting.

Key Findings

  • Nondeterministic form field order β€” collectInputsWithMap in pkg/cli/run_interactive.go builds huh.Groups by iterating a Go map[string]*workflow.InputDefinition; Go randomizes map iteration order, so the prompted order of a workflow's inputs shuffles between runs of the same workflow.
  • One field per page β€” that same function creates a separate huh.Group (i.e. a separate wizard page) per input, even though huh.Group is designed to hold multiple fields together on one page.
  • Two different non-TTY fallback UX paths β€” pkg/console/confirm.go/list.go hand-roll text-based showTextConfirm/showTextList loops for non-TTY terminals, while pkg/cli/interactive.go's wizard instead relies on huh's own WithAccessible(true) mode. Both work, but they're two codepaths to maintain instead of one.
  • Dead loop-variable shadowing β€” inputName := name / inputDef := input copies in run_interactive.go predate Go 1.22's per-iteration range variables; gh-aw targets go 1.26.7, so these are now redundant.
Full analysis (usage, research limitations, and detailed recommendations)

Module Overview

huh builds interactive multi-step terminal forms (inputs, selects, multi-selects, confirms, free text) from composable Group/Field primitives, rendering either as a full Bubble Tea program or in an accessible plain-text mode. MIT licensed, maintained by Charm.

Current Usage in gh-aw

  • 19 files reference huh, across a thin wrapper layer and many CLI wizard call sites:
    • pkg/styles/huh_theme.go β€” maps gh-aw's Dracula-inspired palette onto huh.Styles via a huh.ThemeFunc + lipgloss v2 LightDark adaptive colors. Clean, idiomatic use of ThemeFunc.
    • pkg/console/prompt_form.go, confirm.go, input.go, list.go β€” a PromptForm wrapper around huh.Form plus NewInputForm/NewSelectForm/NewConfirmForm helpers.
    • pkg/cli/interactive.go, run_interactive.go, add_interactive_*.go, bootstrap_profile_*.go, engine_secrets.go β€” the wizards themselves (add-workflow, engine selection, auth setup, schedule picking, git merge-conflict resolution, orchestrator config, GitHub App bootstrap, secret entry).
  • Key APIs used: huh.NewForm, huh.NewGroup, huh.Input (Validate, Suggestions, EchoMode(EchoModePassword)), huh.Select/huh.NewOption, huh.MultiSelect, huh.Confirm, huh.Text, huh.ThemeBase/ThemeFunc, Form.WithTheme, Form.WithAccessible, Form.WithHeight, Form.RunWithContext, huh.ErrUserAborted.
  • Good practice already in place: every form call site goes through the console.New*Form wrapper, so theme + accessibility mode are applied consistently everywhere β€” no call site constructs a raw huh.NewForm directly.
  • Notable workaround: PromptForm.run (pkg/console/prompt_form.go:72-84) reserves promptReservedRows lines and uses manual ANSI save/restore-cursor sequences to erase completed questions after a form finishes, per an inline comment describing a scrolling bug when a form renders near the bottom of the terminal.

Improvement Opportunities

πŸƒ Quick Wins

  • Sort input names (sort.Strings) before building form groups in collectInputsWithMap so field order is stable across runs β€” small fix, real UX bug.
  • Delete the now-redundant inputName := name / inputDef := input loop-variable copies in run_interactive.go.

✨ Feature Opportunities

  • Group 2-3 related optional inputs into a shared huh.Group in collectInputsWithMap instead of one group per field, cutting the number of Enter/Tab steps for workflows with several inputs, while keeping per-field Validate.
  • Retire the bespoke showTextConfirm/showTextList loops in favor of huh's own WithAccessible(true) mode (already used in pkg/cli/interactive.go), leaving one non-interactive codepath to maintain and test.

πŸ“ Best Practice Alignment

  • Re-check the manual ANSI cursor-clear + WithHeight workaround in PromptForm.run against the current huh/Bubble Tea v2 changelog next review cycle, in case a newer release narrowed or removed the underlying scrolling issue it works around.
  • Keep the wrapper-only convention (console.New*Form) for any new interactive prompt call sites β€” it's exactly the theme-once-reuse-everywhere pattern Charm recommends.

πŸ”§ General Improvements

  • None beyond the above β€” the module is otherwise well-integrated: consistent theming, correct password masking (EchoModePassword), and proper cancellation handling via huh.ErrUserAborted.

Recommendations (priority order)

  1. Fix nondeterministic form field ordering in collectInputsWithMap (real bug, cheap fix).
  2. Remove dead loop-variable shadowing in the same function.
  3. Consider consolidating non-TTY fallback logic onto huh's built-in accessible mode to shrink the maintenance surface.
  4. Re-review the cursor-clear workaround in PromptForm.run next cycle against upstream changes.

Next Steps

  • File a small PR for the map-ordering fix (rejig docsΒ #1) since it's a concrete bug, not just a style nit.
  • On a future Go Fan run, retry live repository research once GitHub MCP / gh api access is restored, to confirm whether recent huh releases affect the PromptForm workaround.

Module summary saved to: scratchpad/mods/huh.md

References:

Generated by 🐹 Go Fan Β· claude Β· agent Β· 203.5 AIC Β· βŒ– 9.86 AIC Β· ⊞ 8.3K Β· β—·

  • expires on Sep 2, 2026, 12:10 AM UTC-08:00

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions