fix(changelog): strip commit/date suffix from version strings
Version strings like '0.1.12 (25b2bd4, ...)' were used as-is for GitHub API lookups and comparisons, causing changelog to never show. Now strips to semver only.
- commit
- d53fbf4f2d0eeba8db3a130e73630adfcc00c75d
- author
- patriceckhart · · 2026-04-25 15:50 UTC
- parent
- 25b2bd4
- signature
- unsigned
- refs
- tag: v0.1.13
1 file · +16 -6
M internal/agent/changelog.go +16 -6
| @@ -26,7 +26,17 @@ type ChangelogInfo struct { | |||
| 26 | 26 | // | |
| 27 | 27 | // Honours $GITHUB_TOKEN for private-repo access. Times out at 4s so | |
| 28 | 28 | // startup never blocks on a flaky network. | |
| 29 | + | // semverOnly strips commit hash and date suffixes from version strings | |
| 30 | + | // like "0.1.12 (25b2bd4, 2026-04-25T09:25:45Z)" to get just "0.1.12". | |
| 31 | + | func semverOnly(v string) string { | |
| 32 | + | if i := strings.IndexByte(v, ' '); i > 0 { | |
| 33 | + | return v[:i] | |
| 34 | + | } | |
| 35 | + | return v | |
| 36 | + | } | |
| 37 | + | ||
| 29 | 38 | func FetchChangelog(ctx context.Context, version string) (ChangelogInfo, error) { | |
| 39 | + | version = semverOnly(version) | |
| 30 | 40 | if version == "" || version == "dev" { | |
| 31 | 41 | return ChangelogInfo{}, nil | |
| 32 | 42 | } | |
| @@ -153,30 +163,29 @@ func FetchChangelogAsync(version string) <-chan ChangelogInfo { | |||
| 153 | 163 | // the first-ever launch (no LastChangelogShown stored — we don't | |
| 154 | 164 | // dump release notes at someone who just installed). | |
| 155 | 165 | func ShouldShowChangelog(currentVersion string, cfg Config) bool { | |
| 166 | + | currentVersion = semverOnly(currentVersion) | |
| 156 | 167 | if currentVersion == "" || currentVersion == "dev" { | |
| 157 | 168 | return false | |
| 158 | 169 | } | |
| 159 | 170 | if cfg.LastChangelogShown == "" { | |
| 160 | 171 | return false | |
| 161 | 172 | } | |
| 162 | - | // For local builds (0.0.0), always proceed to the fetch step. | |
| 163 | - | // The caller compares the fetched release version against | |
| 164 | - | // LastChangelogShown to decide whether to actually show. | |
| 165 | 173 | if currentVersion == "0.0.0" { | |
| 166 | 174 | return true | |
| 167 | 175 | } | |
| 168 | - | return cfg.LastChangelogShown != currentVersion | |
| 176 | + | return semverOnly(cfg.LastChangelogShown) != currentVersion | |
| 169 | 177 | } | |
| 170 | 178 | ||
| 171 | 179 | // MarkChangelogShown persists the version whose changelog the user | |
| 172 | 180 | // just dismissed. Idempotent; safe to call when the dialog wasn't | |
| 173 | 181 | // actually shown (e.g. fetch failed) so we don't keep retrying. | |
| 174 | 182 | func MarkChangelogShown(version string) error { | |
| 183 | + | v := semverOnly(version) | |
| 175 | 184 | cfg, _ := LoadConfig() | |
| 176 | - | if cfg.LastChangelogShown == version { | |
| 185 | + | if semverOnly(cfg.LastChangelogShown) == v { | |
| 177 | 186 | return nil | |
| 178 | 187 | } | |
| 179 | - | cfg.LastChangelogShown = version | |
| 188 | + | cfg.LastChangelogShown = v | |
| 180 | 189 | return SaveConfig(cfg) | |
| 181 | 190 | } | |
| 182 | 191 | ||
| @@ -185,6 +194,7 @@ func MarkChangelogShown(version string) error { | |||
| 185 | 194 | // correctly trigger the dialog while THIS launch (which is also | |
| 186 | 195 | // "first-ever") doesn't. | |
| 187 | 196 | func SeedChangelogVersion(version string) { | |
| 197 | + | version = semverOnly(version) | |
| 188 | 198 | if version == "" || version == "dev" { | |
| 189 | 199 | return | |
| 190 | 200 | } | |
| @@ -26,7 +26,17 @@ type ChangelogInfo struct { | |||||
| 26 | // | 26 | // | ||
| 27 | // Honours $GITHUB_TOKEN for private-repo access. Times out at 4s so | 27 | // Honours $GITHUB_TOKEN for private-repo access. Times out at 4s so | ||
| 28 | // startup never blocks on a flaky network. | 28 | // startup never blocks on a flaky network. | ||
| 29 | + | // semverOnly strips commit hash and date suffixes from version strings | |||
| 30 | + | // like "0.1.12 (25b2bd4, 2026-04-25T09:25:45Z)" to get just "0.1.12". | |||
| 31 | + | func semverOnly(v string) string { | |||
| 32 | + | if i := strings.IndexByte(v, ' '); i > 0 { | |||
| 33 | + | return v[:i] | |||
| 34 | + | } | |||
| 35 | + | return v | |||
| 36 | + | } | |||
| 37 | + | ||||
| 29 | func FetchChangelog(ctx context.Context, version string) (ChangelogInfo, error) { | 38 | func FetchChangelog(ctx context.Context, version string) (ChangelogInfo, error) { | ||
| 39 | + | version = semverOnly(version) | |||
| 30 | if version == "" || version == "dev" { | 40 | if version == "" || version == "dev" { | ||
| 31 | return ChangelogInfo{}, nil | 41 | return ChangelogInfo{}, nil | ||
| 32 | } | 42 | } | ||
| @@ -153,30 +163,29 @@ func FetchChangelogAsync(version string) <-chan ChangelogInfo { | |||||
| 153 | // the first-ever launch (no LastChangelogShown stored — we don't | 163 | // the first-ever launch (no LastChangelogShown stored — we don't | ||
| 154 | // dump release notes at someone who just installed). | 164 | // dump release notes at someone who just installed). | ||
| 155 | func ShouldShowChangelog(currentVersion string, cfg Config) bool { | 165 | func ShouldShowChangelog(currentVersion string, cfg Config) bool { | ||
| 166 | + | currentVersion = semverOnly(currentVersion) | |||
| 156 | if currentVersion == "" || currentVersion == "dev" { | 167 | if currentVersion == "" || currentVersion == "dev" { | ||
| 157 | return false | 168 | return false | ||
| 158 | } | 169 | } | ||
| 159 | if cfg.LastChangelogShown == "" { | 170 | if cfg.LastChangelogShown == "" { | ||
| 160 | return false | 171 | return false | ||
| 161 | } | 172 | } | ||
| 162 | - | // For local builds (0.0.0), always proceed to the fetch step. | |||
| 163 | - | // The caller compares the fetched release version against | |||
| 164 | - | // LastChangelogShown to decide whether to actually show. | |||
| 165 | if currentVersion == "0.0.0" { | 173 | if currentVersion == "0.0.0" { | ||
| 166 | return true | 174 | return true | ||
| 167 | } | 175 | } | ||
| 168 | - | return cfg.LastChangelogShown != currentVersion | 176 | + | return semverOnly(cfg.LastChangelogShown) != currentVersion |
| 169 | } | 177 | } | ||
| 170 | 178 | ||||
| 171 | // MarkChangelogShown persists the version whose changelog the user | 179 | // MarkChangelogShown persists the version whose changelog the user | ||
| 172 | // just dismissed. Idempotent; safe to call when the dialog wasn't | 180 | // just dismissed. Idempotent; safe to call when the dialog wasn't | ||
| 173 | // actually shown (e.g. fetch failed) so we don't keep retrying. | 181 | // actually shown (e.g. fetch failed) so we don't keep retrying. | ||
| 174 | func MarkChangelogShown(version string) error { | 182 | func MarkChangelogShown(version string) error { | ||
| 183 | + | v := semverOnly(version) | |||
| 175 | cfg, _ := LoadConfig() | 184 | cfg, _ := LoadConfig() | ||
| 176 | - | if cfg.LastChangelogShown == version { | 185 | + | if semverOnly(cfg.LastChangelogShown) == v { |
| 177 | return nil | 186 | return nil | ||
| 178 | } | 187 | } | ||
| 179 | - | cfg.LastChangelogShown = version | 188 | + | cfg.LastChangelogShown = v |
| 180 | return SaveConfig(cfg) | 189 | return SaveConfig(cfg) | ||
| 181 | } | 190 | } | ||
| 182 | 191 | ||||
| @@ -185,6 +194,7 @@ func MarkChangelogShown(version string) error { | |||||
| 185 | // correctly trigger the dialog while THIS launch (which is also | 194 | // correctly trigger the dialog while THIS launch (which is also | ||
| 186 | // "first-ever") doesn't. | 195 | // "first-ever") doesn't. | ||
| 187 | func SeedChangelogVersion(version string) { | 196 | func SeedChangelogVersion(version string) { | ||
| 197 | + | version = semverOnly(version) | |||
| 188 | if version == "" || version == "dev" { | 198 | if version == "" || version == "dev" { | ||
| 189 | return | 199 | return | ||
| 190 | } | 200 | } | ||