feat(bonfire): SSU widgets on charm v2 behind the Output interface

- Spinner, gradient progress bar and the multi-row process view ported
  from SSU to bubbletea v2 (input off, no signal handler, command
  context); boxed tables through lipgloss/table
- Prompts use huh fields and a single-select port of SSU's selector with
  its confirmation screen on an interactive terminal; line prompts off a
  terminal and under --no-ansi are unchanged (piped passwords still work)
- -q hides decoration but keeps data and errors; -n returns defaults and
  Secret fails with a hint on a terminal stdin; --no-ansi and non-TTY
  runs print today's plain lines
- Helpers: GlobalsOf, VerbosityOf, IsQuiet, Interactive, ConfirmProceed,
  Process, Current, Failed, PrintAbove, ItemProgress, ErrAborted
- diary's styled console lines print above a running live view
This commit is contained in:
Jakub Zych
2026-10-21 12:30:53 +02:00
parent cb38c493ce
commit 3f38862b22
15 changed files with 1266 additions and 198 deletions

View File

@@ -29,7 +29,7 @@ Use cases:
- Component levels: `log.components` sets a minimum level per component (`river: warn`, `blog: debug`); components without one use `log.level`.
- Environment overrides: `SUMMER_LOG__` variables overlay the defaults key by key, and string values parse where YAML gives numbers or lists (`SUMMER_LOG__CHANNELS__DAILY__DAYS=30`, `SUMMER_LOG__CHANNELS__STACK__CHANNELS=console,daily`).
- Validation: an unknown driver, format or level, a stack naming a missing channel or containing itself, and a relative daily path that leaves the application root fail `diary.Setup` with an error naming the key.
- Console lines in bonfire's palette: time, level, the component as a fixed-width prefix, the message and `key=value` attributes. Control characters other than tab are removed from messages and values, so logged data cannot inject terminal escape sequences.
- Console lines in bonfire's palette: time, level, the component as a fixed-width prefix, the message and `key=value` attributes. Control characters other than tab are removed from messages and values, so logged data cannot inject terminal escape sequences. While a bonfire live view (spinner, progress bar, process view) runs, styled lines print above it through `bonfire.PrintAbove`.
- Daily files: the path `storage/logs/summer.log` gives files `storage/logs/summer-2026-10-21.log`, created mode 0640 in directories created 0750, opened on the first record, rotated at local midnight, and deleted after the retention in days (only files of that name pattern). A relative path resolves against the application root. A directory that cannot be written logs one warning from component `diary` on the console and disables the channel; it never fails the start-up.
- Redaction: every channel leaf is wrapped by `sunscreen.Wrap`.
- Runtime verbosity: `diary.SetVerbosity` lets info (1) or debug (2) through for every component and adds source locations on styled lines (3); quiet raises only the console channels to warn.

View File

@@ -39,7 +39,8 @@ type ConsoleOptions struct {
// as dotted key prefixes. Control characters other than tab are removed from
// the message and attribute values, so logged data cannot inject terminal
// escape sequences. Each line is written with one Write call, serialized
// across the handler and its WithAttrs and WithGroup clones.
// across the handler and its WithAttrs and WithGroup clones; while a bonfire
// live view runs, lines print above it through bonfire.PrintAbove.
func NewConsoleHandler(w io.Writer, opts ConsoleOptions) slog.Handler {
source := new(atomic.Bool)
source.Store(opts.AddSource)
@@ -154,6 +155,11 @@ func (h *consoleHandler) Handle(_ context.Context, r slog.Record) error {
b.WriteString(p.Muted.Render(filepath.Base(frame.File) + ":" + strconv.Itoa(frame.Line)))
}
}
// While a bonfire live view (spinner, progress, process view) runs,
// the line prints above it instead of tearing it.
if bonfire.PrintAbove(b.String()) {
return nil
}
b.WriteByte('\n')
h.mu.Lock()