diff --git a/CHANGELOG.md b/CHANGELOG.md index e6fc125..ecb456d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,29 @@ # Changelog +## [0.5.0] + +### Added + +- `ncli decode` reads cash tokens (any HRP, e.g. `lokicash1...`) and + `circlehub1...` connections. Pairing secrets are never shown. + `cashhub1...` is recognized and rejected. +- Every command's `--help` has an `Example:`. + +### Changed + +- A local flow's `ensure` now defaults to `create` (was `exists`), so a + missing store path is created instead of failing. +- The Age column shows days and weeks (`2d4h`, `1w3d`) instead of + stopping at hours. +- Shorter, less repetitive `--help` text. +- Updated `nmilat` to v0.3.1. + +### Fixed + +- Wallet transfers reused a stale client on their second call. +- NWC responses dropped the `circle_hub`/`circle_wallet` fee fields. +- Two `Example:` commands failed when run as written. + ## [0.5.0-rc.1] ### Added diff --git a/client/tui/model.go b/client/tui/model.go index 1f41a82..ef8f084 100644 --- a/client/tui/model.go +++ b/client/tui/model.go @@ -388,11 +388,33 @@ func (fm *FlowMetrics) Row() []string { output = append(output, strconv.Itoa(fm.retries)) } - output = append(output, time.Since(fm.createdAt).Truncate(time.Second).String()) + output = append(output, formatAge(time.Since(fm.createdAt))) return output } +var ageUnits = [...]struct { + suffix string + secs int64 +}{{"w", 7 * 24 * 3600}, {"d", 24 * 3600}, {"h", 3600}, {"m", 60}, {"s", 1}} + +// formatAge renders d as its two largest units, weeks at most: "45s", "5m12s", +// "3h27m", "2d4h", "1w3d". A zero second unit is dropped ("1h", "2w"). +func formatAge(d time.Duration) string { + secs := int64(max(d, 0) / time.Second) + for i, u := range ageUnits[:len(ageUnits)-1] { + if secs < u.secs { + continue + } + out := fmt.Sprintf("%d%s", secs/u.secs, u.suffix) + if next := ageUnits[i+1]; secs%u.secs/next.secs > 0 { + out += fmt.Sprintf("%d%s", secs%u.secs/next.secs, next.suffix) + } + return out + } + return fmt.Sprintf("%ds", secs) +} + func (fm *FlowMetrics) FlatRow() []int { fm.mu.RLock() defer fm.mu.RUnlock() diff --git a/client/tui/model_test.go b/client/tui/model_test.go index 717e67c..0e85912 100644 --- a/client/tui/model_test.go +++ b/client/tui/model_test.go @@ -2,6 +2,7 @@ package tui import ( "testing" + "time" "github.com/ohstr/nmilat/utils" "github.com/rs/zerolog/log" @@ -31,6 +32,55 @@ func TestModelUp(t *testing.T) { log.Debug().Msgf("check: %d==%d", len(headers), len(row)) } +func TestFormatAge(t *testing.T) { + const ( + m = time.Minute + h = time.Hour + d = 24 * time.Hour + w = 7 * d + ) + tests := []struct { + in time.Duration + want string + }{ + {-5 * time.Second, "0s"}, + {0, "0s"}, + {999 * time.Millisecond, "0s"}, + {45 * time.Second, "45s"}, + {m, "1m"}, + {5*m + 12*time.Second, "5m12s"}, + {59*m + 59*time.Second, "59m59s"}, + {h, "1h"}, + {h + 5*time.Second, "1h"}, + {3*h + 27*m + 9*time.Second, "3h27m"}, + {23*h + 59*m, "23h59m"}, + {d, "1d"}, + {2*d + 4*h + 30*m, "2d4h"}, + {6*d + 23*h, "6d23h"}, + {w, "1w"}, + {w + 12*h, "1w"}, + {10*d + m, "1w3d"}, + {2 * w, "2w"}, + {15 * d, "2w1d"}, + {52 * w, "52w"}, + } + for _, tt := range tests { + if got := formatAge(tt.in); got != tt.want { + t.Errorf("formatAge(%v) = %q, want %q", tt.in, got, tt.want) + } + } +} + +func TestRowAgeCellUsesDaysAndWeeks(t *testing.T) { + fm := NewInboundMetrics(1, "/data/db/notes.db", func() {}) + fm.createdAt = time.Now().Add(-(10*24*time.Hour + time.Minute)) + + row := fm.Row() + if got, want := row[len(row)-1], "1w3d"; got != want { + t.Errorf("Age cell = %q, want %q", got, want) + } +} + func TestModelDown(t *testing.T) { sdm := NewOutboundMetrics(1, "/data/db/notes.db", func() {}) diff --git a/go.mod b/go.mod index 098cf65..945b937 100644 --- a/go.mod +++ b/go.mod @@ -69,7 +69,7 @@ require ( github.com/charmbracelet/bubbles v1.0.0 github.com/charmbracelet/bubbletea v1.3.10 github.com/charmbracelet/lipgloss v1.1.0 - github.com/ohstr/nmilat v0.3.0 + github.com/ohstr/nmilat v0.3.1 golang.org/x/sync v0.20.0 golang.org/x/sys v0.45.0 golang.org/x/term v0.43.0 diff --git a/go.sum b/go.sum index 4567050..8656f6a 100644 --- a/go.sum +++ b/go.sum @@ -89,8 +89,8 @@ github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELU github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc= github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= -github.com/ohstr/nmilat v0.3.0 h1:/UW2Fur9uTWqoFO9ta9RIK6+3vJZOyRuiv7JDty0OHY= -github.com/ohstr/nmilat v0.3.0/go.mod h1:+6B0CT40RAJmnZESl1sBukc07RkdyPn5/EQ8ROVof0Y= +github.com/ohstr/nmilat v0.3.1 h1:XCxcXyrmC9wlAehhwBzhwqGwDXmt6vV7/B5CstdoRfw= +github.com/ohstr/nmilat v0.3.1/go.mod h1:+6B0CT40RAJmnZESl1sBukc07RkdyPn5/EQ8ROVof0Y= github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=