Skip to content

Commit 1530572

Browse files
fix: Release v1.2.3 with auto-update fixes
GitHub update checks bypass Conduit system proxy; scheduler retries on failure; install prompt after auto-download; throttle tray refresh during download. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 03aec8c commit 1530572

10 files changed

Lines changed: 167 additions & 16 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,15 @@ All notable changes to this project are documented in this file.
44

55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
66

7+
## [Unreleased]
8+
9+
## [1.2.3] - 2026-06-09
10+
11+
### Fixed
12+
13+
- Auto-update: GitHub requests bypass system proxy; retry failed checks at 1 and 5 minutes; prompt to install when download completes; tray tooltip reflects update state.
14+
- Auto-update: throttle tray menu refresh during download progress (every 10%).
15+
716
## [1.2.2] - 2026-06-09
817

918
### Added

‎internal/app/tray.go‎

Lines changed: 54 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,10 @@ type trayUI struct {
4848
installItem *application.MenuItem
4949
healthOK bool
5050
healthKnown bool
51-
strategyItems map[dpi.Strategy]*application.MenuItem
52-
dnsItems map[dns.Provider]*application.MenuItem
53-
domainItems map[proxy.AllowlistPreset]*application.MenuItem
51+
strategyItems map[dpi.Strategy]*application.MenuItem
52+
dnsItems map[dns.Provider]*application.MenuItem
53+
domainItems map[proxy.AllowlistPreset]*application.MenuItem
54+
promptedUpdateVer string
5455
}
5556

5657
func setupTray(deps trayDeps) *trayUI {
@@ -379,9 +380,9 @@ func (ui *trayUI) runHealthCheck() {
379380
}
380381
}
381382

382-
func (ui *trayUI) checkUpdates(manual bool) {
383+
func (ui *trayUI) checkUpdates(manual bool) bool {
383384
if !ui.checkMu.TryLock() {
384-
return
385+
return false
385386
}
386387
defer ui.checkMu.Unlock()
387388

@@ -391,15 +392,17 @@ func (ui *trayUI) checkUpdates(manual bool) {
391392
available, err := ui.deps.updater.Check(ctx, skipped)
392393
if err != nil {
393394
ui.refreshMenu()
394-
return
395+
ui.syncUpdateTooltip()
396+
return false
395397
}
396398
ui.deps.prefs.Update(func(p *Preferences) { p.LastUpdateCheck = time.Now() })
397399
if available && ui.deps.prefs.Get().AutoUpdate {
398400
_ = ui.deps.updater.Download(ctx)
399401
}
400-
if manual {
401-
ui.refreshMenu()
402-
}
402+
ui.refreshMenu()
403+
ui.syncUpdateTooltip()
404+
ui.maybePromptInstall(manual)
405+
return true
403406
}
404407

405408
func (ui *trayUI) installUpdate() {
@@ -421,6 +424,28 @@ func (ui *trayUI) installUpdate() {
421424

422425
func (ui *trayUI) onUpdateChange() {
423426
ui.refreshMenu()
427+
ui.syncUpdateTooltip()
428+
}
429+
430+
func (ui *trayUI) syncUpdateTooltip() {
431+
if ui.tray == nil {
432+
return
433+
}
434+
ui.tray.SetTooltip(ui.updateStatusLabel())
435+
}
436+
437+
func (ui *trayUI) maybePromptInstall(manual bool) {
438+
if manual || ui.deps.updater.State() != update.StateReady {
439+
return
440+
}
441+
ver := ui.deps.updater.Latest().TagName
442+
if ver == "" || ver == ui.promptedUpdateVer {
443+
return
444+
}
445+
ui.promptedUpdateVer = ver
446+
if showUpdateReadyPrompt(ui.lang(), ver) {
447+
ui.installUpdate()
448+
}
424449
}
425450

426451
func (ui *trayUI) updateStatusLabel() string {
@@ -475,10 +500,26 @@ func menuFromItems(items []*application.MenuItem) *application.Menu {
475500

476501
func startUpdateScheduler(prefs *preferenceStore, svc *update.Service, ui *trayUI) {
477502
go func() {
478-
select {
479-
case <-time.After(config.UpdateCheckDelay):
480-
ui.checkUpdates(false)
481-
case <-ui.stopCh:
503+
retryWaits := []time.Duration{
504+
config.UpdateCheckDelay,
505+
time.Minute,
506+
5 * time.Minute,
507+
}
508+
var elapsed time.Duration
509+
for _, wait := range retryWaits {
510+
wait -= elapsed
511+
if wait < 0 {
512+
wait = 0
513+
}
514+
elapsed += wait
515+
select {
516+
case <-time.After(wait):
517+
if ui.checkUpdates(false) {
518+
return
519+
}
520+
case <-ui.stopCh:
521+
return
522+
}
482523
}
483524
}()
484525
go func() {

‎internal/app/update_prompt_stub.go‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
//go:build !windows
2+
3+
package app
4+
5+
import "github.com/foursecondfivefour/conduit/internal/i18n"
6+
7+
func showUpdateReadyPrompt(_ i18n.Lang, _ string) bool {
8+
return false
9+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
//go:build windows
2+
3+
package app
4+
5+
import (
6+
"syscall"
7+
"unsafe"
8+
9+
"github.com/foursecondfivefour/conduit/internal/i18n"
10+
)
11+
12+
const (
13+
mbYesNo = 0x00000004
14+
mbIconInformation = 0x00000040
15+
idYes = 6
16+
)
17+
18+
func showUpdateReadyPrompt(lang i18n.Lang, version string) bool {
19+
text := i18n.Tf(lang, "tray.updates.ready.prompt", version)
20+
title := i18n.T(lang, "tray.updates.ready.title")
21+
textPtr, _ := syscall.UTF16PtrFromString(text)
22+
titlePtr, _ := syscall.UTF16PtrFromString(title)
23+
ret, _, _ := procMessageBoxW.Call(
24+
0,
25+
uintptr(unsafe.Pointer(textPtr)),
26+
uintptr(unsafe.Pointer(titlePtr)),
27+
uintptr(mbYesNo|mbIconInformation|mbTopmost),
28+
)
29+
return ret == idYes
30+
}

‎internal/config/version.go‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
package config
22

33
// Version is set at build time via -ldflags "-X ...Version=...".
4-
var Version = "1.2.2"
4+
var Version = "1.2.3"

‎internal/i18n/messages.go‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ var messages = map[Lang]map[string]string{
5858
"tray.updates.error": "Ошибка обновления",
5959
"tray.updates.check": "Проверить сейчас",
6060
"tray.updates.install": "Установить обновление",
61+
"tray.updates.ready.title": "Обновление готово",
62+
"tray.updates.ready.prompt": "Доступна версия %s. Установить сейчас?",
6163
"tray.updates.later": "Напомнить позже",
6264
"tray.updates.auto": "Автообновление",
6365
"tray.updates.browser": "Открыть в браузере",
@@ -113,6 +115,8 @@ var messages = map[Lang]map[string]string{
113115
"tray.updates.error": "Update error",
114116
"tray.updates.check": "Check now",
115117
"tray.updates.install": "Install update",
118+
"tray.updates.ready.title": "Update ready",
119+
"tray.updates.ready.prompt": "Version %s is ready. Install now?",
116120
"tray.updates.later": "Remind later",
117121
"tray.updates.auto": "Auto-update",
118122
"tray.updates.browser": "Open in browser",

‎internal/update/github.go‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ type Client struct {
3333

3434
func NewClient() *Client {
3535
return &Client{
36-
HTTP: &http.Client{Timeout: 30 * time.Second},
36+
HTTP: directHTTPClient(30 * time.Second),
3737
RepoURL: repoLatest,
3838
}
3939
}

‎internal/update/httpclient.go‎

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package update
2+
3+
import (
4+
"net"
5+
"net/http"
6+
"net/url"
7+
"time"
8+
)
9+
10+
// directHTTPClient returns a client that bypasses the OS/system proxy.
11+
// Update checks must reach api.github.com even when Conduit routes other apps
12+
// through its local CONNECT proxy with a YouTube-only allowlist.
13+
func directHTTPClient(timeout time.Duration) *http.Client {
14+
return &http.Client{
15+
Timeout: timeout,
16+
Transport: &http.Transport{
17+
Proxy: func(*http.Request) (*url.URL, error) { return nil, nil },
18+
DialContext: (&net.Dialer{
19+
Timeout: timeout,
20+
KeepAlive: 30 * time.Second,
21+
}).DialContext,
22+
ForceAttemptHTTP2: true,
23+
MaxIdleConns: 4,
24+
IdleConnTimeout: 90 * time.Second,
25+
TLSHandshakeTimeout: 10 * time.Second,
26+
ExpectContinueTimeout: 1 * time.Second,
27+
},
28+
}
29+
}

‎internal/update/httpclient_test.go‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package update
2+
3+
import (
4+
"net/http"
5+
"testing"
6+
)
7+
8+
func TestDirectHTTPClientBypassesProxy(t *testing.T) {
9+
c := directHTTPClient(0)
10+
tr, ok := c.Transport.(*http.Transport)
11+
if !ok {
12+
t.Fatalf("transport type %T", c.Transport)
13+
}
14+
url, err := tr.Proxy(&http.Request{})
15+
if err != nil {
16+
t.Fatal(err)
17+
}
18+
if url != nil {
19+
t.Fatalf("proxy = %v, want nil", url)
20+
}
21+
}

‎internal/update/service.go‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,13 +211,21 @@ func (s *Service) Download(ctx context.Context) error {
211211

212212
func (s *Service) set(state State, percent int, err error) {
213213
s.mu.Lock()
214+
prevState := s.state
215+
prevPercent := s.percent
214216
s.state = state
215217
if percent >= 0 {
216218
s.percent = percent
217219
}
218220
s.err = err
221+
notify := prevState != state
222+
if !notify && state == StateDownloading && percent >= 0 {
223+
notify = percent/10 != prevPercent/10
224+
}
219225
s.mu.Unlock()
220-
s.notify()
226+
if notify {
227+
s.notify()
228+
}
221229
}
222230

223231
func downloadFile(ctx context.Context, client *http.Client, url, dest string, progress func(int)) error {

0 commit comments

Comments
 (0)