tui: switch busy spinner to cli-spinners 'dots3' preset
a single-cell 10-frame braille rotation at 80ms per step. matches the well-known dots3 preset from sindresorhus/cli-spinners (MIT). small, recognisable, occupies one terminal cell so the busyPrefix in the status bar stays compact regardless of message length.
- commit
- d6ac856ee7f25fc45583e486848a7fb7e1ac7557
- author
- patriceckhart · · 2026-04-19 09:52 UTC
- parent
- fdef8ac
- signature
- unsigned
- refs
- tag: v0.0.12
1 file · +20 -4
M internal/agent/modes/spinner.go +20 -4
| @@ -46,8 +46,23 @@ var funnyWorkingLines = []string{ | |||
| 46 | 46 | "rewriting history", | |
| 47 | 47 | } | |
| 48 | 48 | ||
| 49 | - | // spinnerFrames is a smooth braille spinner. | |
| 50 | - | var spinnerFrames = []string{"\u280b", "\u2819", "\u2839", "\u2838", "\u283c", "\u2834", "\u2826", "\u2827", "\u2807", "\u280f"} | |
| 49 | + | // spinnerFrames is the cli-spinners "dots3" preset — a 10-frame | |
| 50 | + | // single-cell braille spinner that reads as a small box of dots | |
| 51 | + | // rotating around its perimeter. Source: | |
| 52 | + | // https://github.com/sindresorhus/cli-spinners (MIT). Used at the | |
| 53 | + | // preset's recommended 80ms per step. | |
| 54 | + | var spinnerFrames = []string{ | |
| 55 | + | "⠋", | |
| 56 | + | "⠙", | |
| 57 | + | "⠚", | |
| 58 | + | "⠞", | |
| 59 | + | "⠖", | |
| 60 | + | "⠦", | |
| 61 | + | "⠴", | |
| 62 | + | "⠲", | |
| 63 | + | "⠳", | |
| 64 | + | "⠓", | |
| 65 | + | } | |
| 51 | 66 | ||
| 52 | 67 | // newSpinner constructs a fresh spinner. | |
| 53 | 68 | func newSpinner() *spinner { | |
| @@ -76,13 +91,14 @@ func (s *spinner) StartFixed(msg string) { | |||
| 76 | 91 | } | |
| 77 | 92 | ||
| 78 | 93 | // Frame returns the current spinner glyph for the running animation. | |
| 94 | + | // | |
| 95 | + | // 80ms per step matches the dots3 preset's recommended interval. | |
| 79 | 96 | func (s *spinner) Frame() string { | |
| 80 | 97 | if s.startedAt.IsZero() { | |
| 81 | 98 | return s.frames[0] | |
| 82 | 99 | } | |
| 83 | 100 | elapsed := time.Since(s.startedAt) | |
| 84 | - | // 120ms per frame matches the interactive tick rate. | |
| 85 | - | idx := int(elapsed/(120*time.Millisecond)) % len(s.frames) | |
| 101 | + | idx := int(elapsed/(80*time.Millisecond)) % len(s.frames) | |
| 86 | 102 | return s.frames[idx] | |
| 87 | 103 | } | |
| 88 | 104 | ||
| @@ -46,8 +46,23 @@ var funnyWorkingLines = []string{ | |||||
| 46 | "rewriting history", | 46 | "rewriting history", | ||
| 47 | } | 47 | } | ||
| 48 | 48 | ||||
| 49 | - | // spinnerFrames is a smooth braille spinner. | 49 | + | // spinnerFrames is the cli-spinners "dots3" preset — a 10-frame |
| 50 | - | var spinnerFrames = []string{"\u280b", "\u2819", "\u2839", "\u2838", "\u283c", "\u2834", "\u2826", "\u2827", "\u2807", "\u280f"} | 50 | + | // single-cell braille spinner that reads as a small box of dots |
| 51 | + | // rotating around its perimeter. Source: | |||
| 52 | + | // https://github.com/sindresorhus/cli-spinners (MIT). Used at the | |||
| 53 | + | // preset's recommended 80ms per step. | |||
| 54 | + | var spinnerFrames = []string{ | |||
| 55 | + | "⠋", | |||
| 56 | + | "⠙", | |||
| 57 | + | "⠚", | |||
| 58 | + | "⠞", | |||
| 59 | + | "⠖", | |||
| 60 | + | "⠦", | |||
| 61 | + | "⠴", | |||
| 62 | + | "⠲", | |||
| 63 | + | "⠳", | |||
| 64 | + | "⠓", | |||
| 65 | + | } | |||
| 51 | 66 | ||||
| 52 | // newSpinner constructs a fresh spinner. | 67 | // newSpinner constructs a fresh spinner. | ||
| 53 | func newSpinner() *spinner { | 68 | func newSpinner() *spinner { | ||
| @@ -76,13 +91,14 @@ func (s *spinner) StartFixed(msg string) { | |||||
| 76 | } | 91 | } | ||
| 77 | 92 | ||||
| 78 | // Frame returns the current spinner glyph for the running animation. | 93 | // Frame returns the current spinner glyph for the running animation. | ||
| 94 | + | // | |||
| 95 | + | // 80ms per step matches the dots3 preset's recommended interval. | |||
| 79 | func (s *spinner) Frame() string { | 96 | func (s *spinner) Frame() string { | ||
| 80 | if s.startedAt.IsZero() { | 97 | if s.startedAt.IsZero() { | ||
| 81 | return s.frames[0] | 98 | return s.frames[0] | ||
| 82 | } | 99 | } | ||
| 83 | elapsed := time.Since(s.startedAt) | 100 | elapsed := time.Since(s.startedAt) | ||
| 84 | - | // 120ms per frame matches the interactive tick rate. | 101 | + | idx := int(elapsed/(80*time.Millisecond)) % len(s.frames) |
| 85 | - | idx := int(elapsed/(120*time.Millisecond)) % len(s.frames) | |||
| 86 | return s.frames[idx] | 102 | return s.frames[idx] | ||
| 87 | } | 103 | } | ||
| 88 | 104 | ||||