Files
summercms/.planning/notes/2026-10-21-console-charm-stack.md
Jakub Zych e6039b445f docs(19.8): decide the charm v2 console stack
- Decision note: lipgloss, bubbletea, bubbles, huh, fang v2 and colorprofile, fang used narrowly, own diary console handler, stdlib daily writer
- STACK.md Supporting Libraries rows for the six modules
2026-10-21 11:28:48 +02:00

6.6 KiB

Decision: the console runs on charm's v2 stack, logging stays on stdlib slog

Date: 2026-10-21 Status: Accepted Context: Phase 19.8 (console commands overhaul: D-01, D-02, D-05, D-08 and the CONTEXT discretion item on the console handler), 19.8-RESEARCH.md Standard Stack, Alternatives Considered, Package Legitimacy Audit and the probes quoted there

Decision

The console layer (modules/bonfire) and the new logging module (modules/diary) adopt charm's v2 libraries on the charm.land vanity paths. Log routing itself stays on the standard library's log/slog.

  • Modules and versions. All six were checked with go list -m -versions on 2026-10-21; each is the newest tag, so the research pins stand:
    • charm.land/lipgloss/v2 v2.0.6: styles, layout and lipgloss/v2/table for list, help, errors, tables and log lines.
    • charm.land/bubbletea/v2 v2.1.0: program runtime for the live spinner, process and progress views.
    • charm.land/bubbles/v2 v2.2.1: spinner (MiniDot, the frames bonfire already draws), progress (the cyan-to-green bar), key and help.
    • charm.land/huh/v2 v2.0.3: Ask, Confirm, Choice and Secret on an interactive terminal, with accessible mode.
    • charm.land/fang/v2 v2.0.1: cobra error rendering hook and Windows VT enabling.
    • github.com/charmbracelet/colorprofile v0.4.3: imported directly for colorprofile.Writer and colorprofile.Detect (already a transitive dependency of all of the above).
  • v2, not SSU's v1 pins. SSU (/media/nvme/dev/pxpx/ssu) pins github.com/charmbracelet/lipgloss v1.0.0, bubbletea v1.3.0 and bubbles v0.20.0. fang v1 already depends on a charm.land/lipgloss/v2 beta, so v1 widgets plus any fang would compile two lipgloss majors into every binary, and the v1 line is maintenance-only. SSU's widgets are ported with the v2 renames: View() returns tea.View, key events are tea.KeyPressMsg, the progress gradient is progress.WithColors / WithDefaultBlend, and widths are set with SetWidth.
  • Who imports them. Only modules/bonfire (all five charm libraries plus colorprofile) and modules/diary (lipgloss and colorprofile) import charm modules. Plugins and other framework modules reach them through bonfire.Output and diary.For, never directly.
  • fang is used narrowly. fang.Execute runs with fang.WithoutVersion() (its -v/--version would collide with the verbosity flags), fang.WithoutManpage() (it would add a hidden man command to the list and to duplicate checks), fang.WithoutCompletions(), and fang.WithErrorHandler set to bonfire's own error renderer. The help and list layouts are bonfire's own Artisan layouts (D-14); fang's help func never reaches subcommands because bonfire sets a help func on every command.
  • Color is applied at the writer. lipgloss v2 Render always emits ANSI, so --ansi, --no-ansi, NO_COLOR, TERM=dumb and FORCE_COLOR are applied through colorprofile.Writer with a profile that bonfire detects.
  • The diary console handler is our own lipgloss-styled slog.Handler. charm's slog logger module (charm.land/log/v2 v2.0.1) is rejected: it fails testing/slogtest (WithGroup treated as a prefix, empty attributes printed, attributes of empty-key groups not inlined), and it brings its own level and prefix model that duplicates diary's component levels. This settles the CONTEXT discretion item.
  • Daily files use a stdlib writer of about 120 lines (one file per day, name-YYYY-MM-DD.log, retention by days, mode 0640). gopkg.in/natefinch/lumberjack.v2, which SSU uses, is rejected: it rotates by size, has no per-day naming, and would need its own note.
  • Fan-out and GORM. The stack driver is the standard library's slog.NewMultiHandler (Go 1.26+), and GORM logs through gorm's own logger.NewSlogLogger (already in gorm v1.31.2). No logging library is added.
  • Cost. The probe measured about +2.1 MB on a stripped binary (-trimpath -s -w): 2,527,495 bytes for cobra and x/term alone, 4,649,223 bytes with lipgloss/table, bubbletea, the bubbles spinner and progress, huh and fang.
  • Toolchain fit. The module go directives are at most 1.26 (lipgloss 1.25.0, bubbletea 1.26.0, bubbles 1.25.0, huh 1.25.8, fang 1.25.0) and fit this module's go 1.27.0. Minimal version selection keeps github.com/spf13/cobra v1.10.2, github.com/spf13/pflag v1.0.9 and golang.org/x/text v0.42.0 (fang asks for older cobra and pflag).
  • YAML rule. gopkg.in/yaml.v3 enters the module graph only as testify's indirect test dependency of fang and huh. Nothing in this repository imports it; YAML stays on goccy/go-yaml.
  • Package legitimacy. The MSD package-legitimacy seam rejects the Go ecosystem, so the six modules were checked by hand against proxy.golang.org (@latest, .mod, @v/list) and the charm.land go-import meta tags, whose origins are github.com/charmbracelet/*. Verdict: approved. fang is young (two v2 tags, repository first tagged 2025-06-18), which is acceptable because it is used only for the error hook.
  • Install order. Plan 19.8-01 installs charm.land/lipgloss/v2 and github.com/charmbracelet/colorprofile with the code that imports them (the palette and the diary console handler); plan 19.8-02 installs bubbletea, bubbles, huh and fang with the widgets. go mod tidy drops requirements that no package imports, so each plan adds only what it uses.

Rejected alternatives. SSU's v1 pins were rejected for the two-majors problem above. charm.land/log/v2 was rejected for the slogtest failures and the duplicated level model. lumberjack was rejected because it rotates by size and names files differently. A hand-written fan-out handler (SSU's MultiHandler) was rejected in favour of slog.NewMultiHandler, which clones records, honours Enabled and joins errors.

  • Standard library only for logging: log/slog (NewMultiHandler, JSONHandler, TextHandler, DiscardHandler, LevelVar), testing/slogtest for handler conformance tests, os and path/filepath for the daily writer.
  • gorm.io/gorm v1.31.2 (already required) for logger.NewSlogLogger.
  • github.com/spf13/cobra v1.10.2 (already required) stays the command parser under bonfire.

Consequences

  • Every application binary grows by about 2.1 MB once plan 19.8-02 lands the widgets.
  • Removing the stack later means rewriting bonfire's widgets and diary's console handler; D-01 and D-02 are rated costly for that reason.
  • Plugins never import charm modules directly, so a later change of styling library touches only modules/bonfire and modules/diary.
  • A check in the plan's acceptance criteria (go list over every package's imports) keeps the import boundary honest.