Skip to content

Commit 34b6ba9

Browse files
fix config path for windows/macos, decouple --tiny from bubble tea, add mini mode
config: use os.UserConfigDir() for platform-specific config paths - Linux: ~/.config/flow/config.toml - macOS: ~/Library/Application Support/flow/config.toml - Windows: %APPDATA%/flow/config.toml --tiny: fully independent of Bubble Tea, Lip Gloss, termenv - works reliably in tmux #(...), cron, pipes, redirected stdout - --no-color emits clean plain text, no ANSI sequences mini mode: new --mini flag for graphs-only view - omits logo, today stats, interface name, footer hints - m key cycles hero -> compact -> mini -> tiny -> hero - responsive height-based auto-switching (hero needs >=22h) pulse: peak-record white-flash animation on new session peaks help: redesigned with rounded indigo border overlay error handling: sampler propagates errors instead of hanging footer: responsive key grouping, active/paused dot status demo: mode cycling, longer pause, extended traffic duration
1 parent b6dcb2f commit 34b6ba9

18 files changed

Lines changed: 450 additions & 129 deletions

File tree

CHANGELOG.md

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,26 @@
1-
# Changelog
1+
## [0.1.1] - 2026-07-05
2+
3+
### Added
4+
5+
- Graphs-only "mini" mode (`--mini`) showing just download/upload panels and waveforms, omitting global title, today's summary, active interface, and key help hints.
6+
- Key binding `m` to interactively cycle through view modes (`hero` -> `compact` -> `mini` -> `tiny` -> `hero`).
7+
- Responsive vertical layout resizing, automatically scaling down to mini mode when the screen height is too small for compact/hero dashboards.
8+
- Premium dev-tool theme styling inspired by Stripe, Spotify, and Apple aesthetics, featuring high-contrast vertical gradients.
9+
- Sleek, modern rounded borders for a clean and unified desktop-TUI look.
10+
- Minimalist, high-end unicode today statistics using colored down/upload arrows (`` / ``) and clean accent-colored values.
11+
- Refined dot-separated (`·`) status and navigation footer containing real-time active/paused interface dot status (``) and highlighted key binds.
12+
- Live peak pulsing white-flash animations when a new session throughput record is reached.
13+
- Clean modal help overlay with modern rounded border styling and highlighted keys.
14+
- `--tiny` mode is now fully independent of Bubble Tea, works reliably in tmux `#(...)`, cron, pipes, and redirected stdout
15+
- Platform-specific config paths: Linux (`~/.config/flow/config.toml`), macOS (`~/Library/Application Support/flow/config.toml`), Windows (`%APPDATA%/flow/config.toml`)
16+
17+
### Fixed
18+
19+
- Daily traffic totals failing to reset when the calendar month/year changes (now compares full date: year, month, and day).
20+
- TUI and one-shot modes hanging indefinitely on network counter read errors (now propagates errors through sampler and exits gracefully with a message).
21+
- Config file not being created on macOS and Windows due to non-standard path resolution
22+
- `--tiny` no longer initializes Bubble Tea, Lip Gloss, termenv, or terminal queries — zero TTY dependency
23+
- `--tiny --no-color` emits clean plain text with no ANSI sequences
224

325
## [0.1.0] - 2026-07-04
426

README.md

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -122,24 +122,25 @@ The interface is built for restraint rather than density: large typography, cont
122122

123123
## Modes
124124

125-
flow adjusts its display according to terminal width.
125+
flow adjusts its display according to terminal width and height.
126126

127-
| hero | compact | tiny |
128-
|:---:|:---:|:---:|
129-
| <img src="./docs/normal_mode.png" alt="hero mode"> | <img src="./docs/compact_mode.png" alt="compact mode"> | <img src="./docs/tiny_mode.png" alt="tiny mode"> |
130-
| Full dashboard with sparklines, peaks, and daily totals | Numeric values only, for narrow terminals | Single-line output, intended for status bars |
127+
| hero | compact | mini | tiny |
128+
|:---:|:---:|:---:|:---:|
129+
| <img src="./docs/normal_mode.png" alt="hero mode"> | <img src="./docs/compact_mode.png" alt="compact mode"> | | <img src="./docs/tiny_mode.png" alt="tiny mode"> |
130+
| Full dashboard with logo branding, waveforms, peaks, and daily totals | Cleaner layout with title row, waveforms, peaks, and daily totals | Graphs-only layout, hiding header logo, today's summary, and help footers | Single-line output, intended for status bars |
131131

132132
## Features
133133

134134
- Real-time download and upload throughput
135135
- Interpolated display values using spring-based animation
136136
- Braille-grid waveform rendering at 30 frames per second
137-
- Border color reflects current transfer speed
138-
- Visual indication when a new session peak is recorded
137+
- Border color reflects current transfer speed with sleek, modern rounded outlines
138+
- Live peak pulsing white-flash animations when a new session peak is reached
139+
- Minimalist, high-end unicode today statistics and navigation footer
139140
- Directional indicators for traffic trend
140141
- Automatic unit scaling from B/s to GB/s
141142
- Session peak tracking and daily traffic totals
142-
- Three display modes with automatic switching on resize
143+
- Four display modes with automatic responsive switching on both width and height resize
143144
- No required configuration; optional TOML configuration file
144145
- Non-interactive output modes for use in scripts
145146
- Supported on Linux, macOS, and Windows
@@ -151,7 +152,8 @@ flow adjusts its display according to terminal width.
151152
```sh
152153
flow # hero view, auto interface
153154
flow --tiny # single-line mode for status bars
154-
flow --compact # numeric values only
155+
flow --mini # graphs-only mode, no headers/footers
156+
flow --compact # compact layout with title row and waveforms
155157
flow --json # single JSON output, then exit
156158
flow --once # single plain-text output, then exit
157159
flow --interface wlan0 # specify network interface
@@ -168,6 +170,7 @@ flow --help
168170
| Key | Action |
169171
|-------------|-----------------------------|
170172
| `q` / `^C` | Quit |
173+
| `m` | Cycle display/view modes |
171174
| `r` | Reset session peaks |
172175
| `i` | Cycle network interfaces |
173176
| `c` | Cycle display units |
@@ -197,7 +200,15 @@ set -g status-interval 1
197200

198201
## Configuration
199202

200-
A configuration file is created automatically at `~/.config/flow/config.toml` on first run. The `XDG_CONFIG_HOME` environment variable is respected if set.
203+
A configuration file is created automatically on first run:
204+
205+
| Platform | Path |
206+
|----------|------|
207+
| Linux | `~/.config/flow/config.toml` |
208+
| macOS | `~/Library/Application Support/flow/config.toml` |
209+
| Windows | `%APPDATA%\flow\config.toml` |
210+
211+
The `XDG_CONFIG_HOME` environment variable is respected on Linux if set.
201212

202213
```toml
203214
refresh = "100ms" # sampling interval

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
0.1.0
1+
0.1.1

cmd/flow/main.go

Lines changed: 55 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ var version = "dev"
2424

2525
func main() {
2626
flagTiny := flag.Bool("tiny", false, "single-line mode for tmux/status bars")
27+
flagMini := flag.Bool("mini", false, "graphs-only mini mode")
2728
flagCompact := flag.Bool("compact", false, "numbers-only compact mode")
2829
flagJSON := flag.Bool("json", false, "one-shot JSON snapshot, then exit")
2930
flagOnce := flag.Bool("once", false, "one-shot plain-text snapshot, then exit")
@@ -71,6 +72,11 @@ func main() {
7172
return
7273
}
7374

75+
if *flagTiny {
76+
runTiny(col, smp, refresh, cfg.NoColor)
77+
return
78+
}
79+
7480
ifaces, err := collector.Interfaces()
7581
if err != nil {
7682
ifaces = []string{cfg.Interface}
@@ -81,8 +87,8 @@ func main() {
8187

8288
var forced ui.ViewMode
8389
switch {
84-
case *flagTiny:
85-
forced = ui.ViewTiny
90+
case *flagMini:
91+
forced = ui.ViewMini
8692
case *flagCompact:
8793
forced = ui.ViewCompact
8894
default:
@@ -98,15 +104,51 @@ func main() {
98104
model := ui.New(cfg, smp, ifaces, initialIface, cancel, forced)
99105

100106
opts := []tea.ProgramOption{tea.WithAltScreen()}
101-
if *flagTiny || *flagCompact {
107+
if *flagCompact || *flagMini {
102108
opts = []tea.ProgramOption{}
103109
}
104110

105111
p := tea.NewProgram(model, opts...)
106-
if _, err := p.Run(); err != nil {
112+
m, err := p.Run()
113+
if err != nil {
107114
fmt.Fprintf(os.Stderr, "flow: %v\n", err)
108115
os.Exit(1)
109116
}
117+
if finalModel, ok := m.(ui.Model); ok && finalModel.Err() != nil {
118+
fmt.Fprintf(os.Stderr, "flow: %v\n", finalModel.Err())
119+
os.Exit(1)
120+
}
121+
}
122+
123+
// runTiny collects a single sample and prints a compact one-line summary.
124+
// Completely independent of Bubble Tea — works in tmux, cron, pipes.
125+
func runTiny(col *collector.Collector, smp *sampler.Sampler, refresh time.Duration, noColor bool) {
126+
ctx, cancel := context.WithCancel(context.Background())
127+
defer cancel()
128+
129+
go smp.Run(ctx)
130+
131+
// Wait for two samples so the first diff is valid.
132+
s1 := <-smp.Out
133+
if s1.Err != nil {
134+
fmt.Fprintf(os.Stderr, "flow: %v\n", s1.Err)
135+
os.Exit(1)
136+
}
137+
s := <-smp.Out
138+
if s.Err != nil {
139+
fmt.Fprintf(os.Stderr, "flow: %v\n", s.Err)
140+
os.Exit(1)
141+
}
142+
cancel()
143+
144+
down := ui.FormatBps(s.DownBps, ui.UnitAuto)
145+
up := ui.FormatBps(s.UpBps, ui.UnitAuto)
146+
147+
if noColor {
148+
fmt.Printf("↓ %s · ↑ %s\n", down, up)
149+
} else {
150+
fmt.Printf("↓ %s · ↑ %s\n", down, up)
151+
}
110152
}
111153

112154
// runOnce takes exactly one sample and either prints JSON or plain text, then
@@ -118,8 +160,16 @@ func runOnce(col *collector.Collector, smp *sampler.Sampler, refresh time.Durati
118160
go smp.Run(ctx)
119161

120162
// Wait for two samples so the first diff is valid.
121-
<-smp.Out
163+
s1 := <-smp.Out
164+
if s1.Err != nil {
165+
fmt.Fprintf(os.Stderr, "flow: %v\n", s1.Err)
166+
os.Exit(1)
167+
}
122168
s := <-smp.Out
169+
if s.Err != nil {
170+
fmt.Fprintf(os.Stderr, "flow: %v\n", s.Err)
171+
os.Exit(1)
172+
}
123173
cancel()
124174

125175
if asJSON {

docs/compact_mode.png

-11.5 KB
Loading

docs/demo.gif

4.2 MB
Loading

docs/keybinds.png

4.23 KB
Loading

docs/normal_mode.png

-1.78 KB
Loading

docs/tiny_mode.png

-5.89 KB
Loading

flow.tape

Lines changed: 25 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -49,30 +49,31 @@ Ctrl+L
4949
Sleep 300ms
5050

5151
# Steady medium-contention traffic — long-running throttled downloads
52-
Type "curl -L --limit-rate 4M --max-time 45 -o /dev/null https://speed.hetzner.de/100MB.bin &>/dev/null &"
52+
# All --max-time values are generous to cover the full demo duration
53+
Type "curl -L --limit-rate 4M --max-time 65 -o /dev/null https://speed.hetzner.de/100MB.bin &>/dev/null &"
5354
Enter
5455
Sleep 400ms
55-
Type "curl -L --limit-rate 3M --max-time 40 -o /dev/null https://proof.ovh.net/files/100Mb.dat &>/dev/null &"
56+
Type "curl -L --limit-rate 3M --max-time 60 -o /dev/null https://proof.ovh.net/files/100Mb.dat &>/dev/null &"
5657
Enter
5758
Sleep 400ms
58-
Type "curl -L --limit-rate 5M --max-time 42 -o /dev/null https://speed.hetzner.de/100MB.bin &>/dev/null &"
59+
Type "curl -L --limit-rate 5M --max-time 62 -o /dev/null https://speed.hetzner.de/100MB.bin &>/dev/null &"
5960
Enter
6061
Sleep 400ms
61-
Type "curl -L --limit-rate 2M --max-time 35 -o /dev/null https://proof.ovh.net/files/100Mb.dat &>/dev/null &"
62+
Type "curl -L --limit-rate 2M --max-time 55 -o /dev/null https://proof.ovh.net/files/100Mb.dat &>/dev/null &"
6263
Enter
6364
Sleep 400ms
64-
Type "curl -L --limit-rate 3M --max-time 48 -o /dev/null https://speed.hetzner.de/100MB.bin &>/dev/null &"
65+
Type "curl -L --limit-rate 3M --max-time 68 -o /dev/null https://speed.hetzner.de/100MB.bin &>/dev/null &"
6566
Enter
6667
Sleep 400ms
67-
Type "curl -X POST --limit-rate 3M --max-time 38 -o /dev/null https://httpbin.org/post &>/dev/null &"
68+
Type "curl -X POST --limit-rate 3M --max-time 58 -o /dev/null https://httpbin.org/post &>/dev/null &"
6869
Enter
6970
Sleep 400ms
7071

7172
# Small delayed bumps for visual texture on the steady baseline
72-
Type "(sleep 6; curl -L --limit-rate 4M --max-time 25 -o /dev/null https://speed.hetzner.de/100MB.bin &>/dev/null) &"
73+
Type "(sleep 6; curl -L --limit-rate 4M --max-time 45 -o /dev/null https://speed.hetzner.de/100MB.bin &>/dev/null) &"
7374
Enter
7475
Sleep 400ms
75-
Type "(sleep 12; curl -L --limit-rate 3M --max-time 20 -o /dev/null https://proof.ovh.net/files/100Mb.dat &>/dev/null) &"
76+
Type "(sleep 12; curl -L --limit-rate 3M --max-time 40 -o /dev/null https://proof.ovh.net/files/100Mb.dat &>/dev/null) &"
7677
Enter
7778
Sleep 400ms
7879

@@ -86,32 +87,38 @@ Sleep 400ms
8687
Enter
8788
Sleep 3s
8889

89-
# Reveal — hero logo, breathing dot, subtitle, all 12 downloads raging
90+
# Reveal — hero logo, breathing dot, subtitle, traffic flowing
9091
Show
9192
Sleep 6s
9293

93-
# Reset peaks — clean slate mid-storm
94+
# Reset peaks
9495
Type "r"
95-
Sleep 3s
96+
Sleep 2s
9697

97-
# Cycle through all unit modes at a deliberate pace
98-
Type "c"
98+
# Cycle through display modes (m key cycles: hero → compact → mini → tiny → hero)
99+
Type "m"
99100
Sleep 2s
100-
Type "c"
101+
Type "m"
102+
Sleep 2s
103+
Type "m"
104+
Sleep 2s
105+
Type "m"
101106
Sleep 2s
107+
108+
# Back in hero mode, cycle through units briefly
102109
Type "c"
103110
Sleep 2s
104111
Type "c"
105112
Sleep 2s
106113

107-
# Freeze the storm
114+
# Pause — hold longer to show the frozen state
108115
Type "p"
109-
Sleep 2s
116+
Sleep 4s
117+
118+
# Resume
110119
Type "p"
111120
Sleep 2s
112121

113-
# Mid-demo burst fires here — graphs spike again
114-
115122
# Show the help overlay
116123
Type "?"
117124
Sleep 4s

0 commit comments

Comments
 (0)