Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]

### Added
- An Automatically Check for Updates toggle in the Settings menu on every
platform (config `auto-check-updates`, default on): silent launch and
4-hour checks honor it, the manual Check for Updates… item always runs
(#58).
- Network transparency: the macOS bundle ships an
`InternetAccessPolicy.plist` declaring the update feed as the app's
only network use — firewall apps (Little Snitch and friends) show the
Expand Down
35 changes: 35 additions & 0 deletions linux/src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1658,12 +1658,19 @@ void on_menu_check_updates(GSimpleAction*, GVariant*, gpointer) {
// Silent launch check: once per process, 3 s after the backend is up, gated
// by the 4-hour throttle. A failed throttle read counts as "never checked" —
// silent mode ignores errors by contract.
// UPDATE.md auto-check toggle (config `auto-check-updates`, default on);
// the win.auto-check-updates action state is the single source of truth.
bool g_auto_check_updates = true;

gboolean on_update_auto_check(gpointer) {
g_update.auto_check_source = 0;
if (g_state.api == nullptr ||
g_state.shutting_down.load(std::memory_order_acquire)) {
return G_SOURCE_REMOVE;
}
if (!g_auto_check_updates) {
return G_SOURCE_REMOVE; // silent checks skip; manual still runs
}
g_state.api->get_setting_async(
kUpdateCheckSetting,
[](rivet_app::Result<std::string> result) {
Expand Down Expand Up @@ -3085,6 +3092,8 @@ void build_menubar() {
g_menu_append(theme_section, tr("themeDark").c_str(), "win.theme-dark");
g_menu_append_section(settings_menu, nullptr, G_MENU_MODEL(theme_section));
g_object_unref(theme_section);
g_menu_append(settings_menu, tr("settingsAutoCheckUpdates").c_str(),
"win.auto-check-updates");
g_menu_append_submenu(menu, tr("menuSettings").c_str(),
G_MENU_MODEL(settings_menu));
g_object_unref(settings_menu);
Expand Down Expand Up @@ -3520,6 +3529,19 @@ int on_backend_finished(gpointer) {
apply_theme(settings.theme.empty() ? "system"
: settings.theme);
apply_language();
g_state.api->get_setting_async(
"auto-check-updates",
[](rivet_app::Result<std::string> toggle) {
if (!toggle.succeeded()) return;
g_auto_check_updates = toggle.get() != "0";
if (auto* action = G_SIMPLE_ACTION(
g_action_map_lookup_action(
G_ACTION_MAP(g_state.window),
"auto-check-updates"))) {
g_simple_action_set_state(
action, g_variant_new_boolean(g_auto_check_updates));
}
});
// Stamp the install shape so the backend picks the matching
// update flavor (UPDATE.md): appimage in-place, package
// manager guidance, portable tarball download.
Expand Down Expand Up @@ -4008,6 +4030,19 @@ void on_activate(GtkApplication* app, gpointer) {
on_action_focus_search, false);
add_window_action(GTK_APPLICATION_WINDOW(window), "toggle-completed",
on_action_toggle_completed, false);
auto* auto_check_action = g_simple_action_new_stateful(
"auto-check-updates", nullptr, g_variant_new_boolean(true));
g_signal_connect(
auto_check_action, "change-state",
G_CALLBACK(+[](GSimpleAction* action, GVariant* value, gpointer) {
g_auto_check_updates = g_variant_get_boolean(value);
g_simple_action_set_state(action, value);
save_setting("auto-check-updates",
g_auto_check_updates ? "1" : "0");
}),
nullptr);
g_action_map_add_action(G_ACTION_MAP(window), G_ACTION(auto_check_action));
g_object_unref(auto_check_action);

// Window-wide accelerators (PRODUCT-SPEC §8; Things-style keyboard-first).
static char const* const accels_today[] = {"<Primary>1", nullptr};
Expand Down
16 changes: 16 additions & 0 deletions macos-host/Sources/RivetHost/AppModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ final class AppModel {
var searchFocusToken = 0
var isSidebarVisible = true
var statusMessage = ""
/// UPDATE.md auto-check toggle (config `auto-check-updates`, default on).
var autoCheckUpdates = true
var languageChangedToken = 0
/// Completed section collapsed (hidden rows are skipped by ↑/↓).
var completedCollapsed = false
Expand Down Expand Up @@ -222,6 +224,9 @@ final class AppModel {
refreshStatusPersistent()
flashStatus(i18n.t("statusDatabaseConnected"))
reminder?.start()
if let stored = try? await api.get_setting(key: "auto-check-updates") {
autoCheckUpdates = stored != "0"
}
autoCheckForUpdates()
UpdateService.sweepStaleWorkDirs()
} catch {
Expand Down Expand Up @@ -284,12 +289,23 @@ final class AppModel {
/// Silent launch check: once shortly after startup, throttled to one
/// attempt per 4 h (config `last-update-check`); failures never nag.
func autoCheckForUpdates() {
guard autoCheckUpdates else { return }
_Concurrency.Task {
try? await _Concurrency.Task.sleep(nanoseconds: 3_000_000_000)
await runUpdateCheck(present: false)
}
}

/// §8: the toggle persists immediately; the manual check ignores it.
func setAutoCheckUpdates(_ on: Bool) {
autoCheckUpdates = on
guard let api else { return }
_Concurrency.Task {
_ = try? await api.set_setting(key: "auto-check-updates",
value: on ? "1" : "0")
}
}

private func runUpdateCheck(present: Bool) async {
guard updatePhase != .downloading else { return }
if !present {
Expand Down
4 changes: 4 additions & 0 deletions macos-host/Sources/RivetHost/TasklyApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,10 @@ struct TasklyCommands: Commands {
model.setTheme("dark")
}
}
Divider()
Toggle(model.t("settingsAutoCheckUpdates"), isOn: Binding(
get: { model.autoCheckUpdates },
set: { model.setAutoCheckUpdates($0) }))
}
}

Expand Down
5 changes: 4 additions & 1 deletion racket/taskly/backend.rkt
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,10 @@
;; Host-declared install shape (Windows: msi vs portable zip; Linux:
;; appimage vs deb/rpm vs tarball) — the updater picks the download
;; flavor from it (UPDATE.md).
("install-flavor" ("zip" "msi" "appimage" "package" "tarball"))))
("install-flavor" ("zip" "msi" "appimage" "package" "tarball"))
;; UPDATE.md auto-check toggle: silent launch/4h checks skip when "0";
;; the manual Check for Updates… item always runs.
("auto-check-updates" ("0" "1"))))

;; Integer-as-string per DATA-FORMAT §7: sidebar selection and the
;; update-check throttle (shared/spec/UPDATE.md) persist as plain integers.
Expand Down
2 changes: 1 addition & 1 deletion racket/taskly/config.rkt
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@
(for ([key (in-list '("last-db-path" "last_db_path" "language"
"last-selected-list-id"
"theme" "close-to-tray" "last-update-check"
"rollout-bucket"))])
"rollout-bucket" "auto-check-updates"))])
(define value (config-ref config key #f))
(when value
(fprintf out "~a = ~a\n" key value))))))
Expand Down
8 changes: 8 additions & 0 deletions racket/tests/backend-test.rkt
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,14 @@
(set_setting "last-selected-list-id" "7")
(check-equal? (get_setting "last-selected-list-id") "7")

;; UPDATE.md auto-check toggle: round-trips as a string enum, rejects
;; anything but "0"/"1", and survives the config writer's key list.
(set_setting "auto-check-updates" "0")
(check-equal? (get_setting "auto-check-updates") "0")
(check-equal? (config-ref (read-config) "auto-check-updates") "0")
(set_setting "auto-check-updates" "1")
(check-exn exn:fail? (lambda () (set_setting "auto-check-updates" "off")))

;; open_database remembers the opened file (DATA-FORMAT §7) so hosts
;; restart against it — the 0.2.1 OTA regression: manually opened DBs
;; were forgotten because nothing ever wrote last-db-path.
Expand Down
1 change: 1 addition & 0 deletions shared/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -135,5 +135,6 @@
"aboutChangelog": "Changelog",
"aboutCopyDiagnostics": "Copy Diagnostics",
"statusDiagnosticsCopied": "Diagnostics copied — paste it into your issue",
"settingsAutoCheckUpdates": "Automatically Check for Updates",
"updateCopyError": "Copy error"
}
1 change: 1 addition & 0 deletions shared/i18n/zh.json
Original file line number Diff line number Diff line change
Expand Up @@ -135,5 +135,6 @@
"aboutChangelog": "更新日志",
"aboutCopyDiagnostics": "复制诊断信息",
"statusDiagnosticsCopied": "已复制诊断信息,可直接粘贴到 issue",
"settingsAutoCheckUpdates": "自动检查更新",
"updateCopyError": "复制错误信息"
}
1 change: 1 addition & 0 deletions shared/spec/DATA-FORMAT.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ sides trimmed; keys case-insensitive. Writer emits two fixed header lines:
| `theme` | `system` / `light` / `dark`; Windows applies it before the first window paint | `system` |
| `last-selected-list-id` | int as string; unparseable → 0 | `0` |
| `last-update-check` | unix seconds of the last auto-update check (UPDATE.md throttle) | `0` |
| `auto-check-updates` | `1`/`0` — whether the silent update checks run (UPDATE.md) | `1` |

## 8. Conformance

Expand Down
5 changes: 5 additions & 0 deletions shared/spec/UPDATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@
manual `检查更新… / Check for Updates…` menu item (family menu
convention: Help menu on Windows/Linux, App menu below About on macOS)
that bypasses the throttle.
- **Auto-check toggle**: config key `auto-check-updates`, `"1"` (default)
or `"0"`, exposed as a checkable 自动检查更新 / Automatically Check for
Updates item in the Settings menu. When `"0"`, the silent launch and
periodic checks skip; the manual item always checks and is unaffected
by the toggle or the throttle.
- **Silent mode**: network failure / rate limit → silently ignore (never
nag). Manual mode reports every outcome.
- **Newer version found** (semver compare, downgrades ignored) → dialog
Expand Down
41 changes: 41 additions & 0 deletions windows/MainWindow.Update.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,9 @@ void MainWindow::RunSilentUpdateCheck() {
if (IsDevCopy()) {
return; // dev copies never phone home (UPDATE.md silent mode)
}
if (!auto_check_updates_) {
return; // UPDATE.md auto-check toggle: silent checks skip
}
// Host-side throttle: at most one silent check per 4 h, persisted as the
// `last-update-check` setting (unix epoch seconds, shared with the other
// platforms' hosts). Manual checks bypass this entirely.
Expand Down Expand Up @@ -762,4 +765,42 @@ winrt::fire_and_forget MainWindow::ShowUpdateDialog(
}
}


void MainWindow::LoadAutoCheckUpdatesSetting() {
auto const weak = get_weak();
auto const dispatcher = DispatcherQueue();
auto backend = backend_;
if (backend == nullptr || !backend->running()) {
return;
}
try {
rivet_app::API api(*backend);
(void)api.get_setting_async(
"auto-check-updates",
[dispatcher, weak](rivet_app::Result<std::string> result) {
bool on = true;
try {
on = result.get() != "0"; // missing or unreadable → default on
} catch (...) {
}
dispatcher.TryEnqueue([weak, on] {
if (auto window = weak.get()) {
window->auto_check_updates_ = on;
window->MenuAutoCheckUpdates().IsChecked(on);
}
});
});
} catch (...) {
// Silent: the toggle defaults to on until the backend answers.
}
}

void MainWindow::OnAutoCheckUpdatesToggled(
winrt::Windows::Foundation::IInspectable const&,
Microsoft::UI::Xaml::RoutedEventArgs const&) {
auto_check_updates_ = MenuAutoCheckUpdates().IsChecked();
SaveSettingAsync("auto-check-updates",
auto_check_updates_ ? "1" : "0");
}

} // namespace winrt::RivetHost::implementation
2 changes: 2 additions & 0 deletions windows/MainWindow.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@
<MenuFlyoutItem x:Name="MenuThemeLight" Click="OnThemeLight" />
<MenuFlyoutItem x:Name="MenuThemeDark" Click="OnThemeDark" />
</MenuFlyoutSubItem>
<MenuFlyoutSeparator />
<ToggleMenuFlyoutItem x:Name="MenuAutoCheckUpdates" Click="OnAutoCheckUpdatesToggled" />
</MenuBarItem>
<MenuBarItem Title="Help" x:Name="MenuHelp">
<!-- Family menu convention: Check for Updates… and About are
Expand Down
3 changes: 3 additions & 0 deletions windows/MainWindow.xaml.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -692,6 +692,7 @@ winrt::fire_and_forget MainWindow::InitializeBackendAsync() {
// the throttled silent check.
current->HandleInstallMarkers();
current->OpenDefaultDatabaseAsync();
current->LoadAutoCheckUpdatesSetting();
current->AutoCheckUpdatesAsync();
}
});
Expand Down Expand Up @@ -749,6 +750,8 @@ void MainWindow::ApplyLanguage() {
MenuThemeSystem().Text(t("themeFollowSystem"));
MenuThemeLight().Text(t("themeLight"));
MenuThemeDark().Text(t("themeDark"));
MenuAutoCheckUpdates().Text(t("settingsAutoCheckUpdates"));
MenuAutoCheckUpdates().IsChecked(auto_check_updates_);
MenuCheckUpdates().Text(t("updateCheckNow"));
MenuAbout().Text(t("menuAbout"));
SearchBox().PlaceholderText(t("searchHint"));
Expand Down
6 changes: 6 additions & 0 deletions windows/MainWindow.xaml.h
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,10 @@ struct MainWindow : MainWindowT<MainWindow> {
void OnCancelUpdateDownload(
winrt::Windows::Foundation::IInspectable const&,
Microsoft::UI::Xaml::RoutedEventArgs const&);
void OnAutoCheckUpdatesToggled(
winrt::Windows::Foundation::IInspectable const&,
Microsoft::UI::Xaml::RoutedEventArgs const&);
void LoadAutoCheckUpdatesSetting();

private:
enum class ViewKind { Today, Planned, All, Completed, List };
Expand Down Expand Up @@ -221,6 +225,8 @@ struct MainWindow : MainWindowT<MainWindow> {

std::shared_ptr<rivet::windows::Backend> backend_;
rivet_app::Settings settings_{};
// Host-owned (like last-update-check): UPDATE.md auto-check toggle.
bool auto_check_updates_{true};
rivet_app::SmartCounts current_counts_{};
std::vector<rivet_app::TodoList> lists_;
std::vector<rivet_app::Task> tasks_;
Expand Down
Loading