Reflect real sync state on the Sync settings page

`SyncConnectionState` was a one-variant enum (`SignedOut`), so the
Sync page rendered "Local-only · sign-in coming soon" even after the
bearer token landed on disk and the upload thread completed. The
state machine now mirrors the actual lifecycle.

What lands:
- `SyncConnectionState` gains `SignedIn`, `AwaitingDeviceApproval`,
  `SyncReady { last_synced_at_secs }`, `SyncError { message }`.
  `SyncObjectState::Synced` joins the per-object enum so individual
  rows can advertise "Synced" once a successful upload lands.
- `BrowserCore` stores the current `SyncConnectionState` and exposes
  `set_sync_connection_state`. `sync_status` now propagates the live
  state into the snapshot the UI reads.
- `ElyShell::probe_initial_sync_state` inspects
  `<profile_data>/sync/bearer.token` synchronously at construction
  so the first render of the sync page is honest about whether the
  user is signed in.
- A `std::sync::mpsc` channel ferries upload outcomes from the
  off-thread worker back to the shell; the existing 8 ms tick
  drains it and stamps `core.set_sync_connection_state` with the
  freshest result. The UI now shows "Signed in · awaiting first
  sync", "Synced · last upload Xm ago", "Sync error · …", and the
  worker-special "Signed in · waiting for device approval" when the
  server returns `device_not_approved`.
This commit is contained in:
2026-05-15 19:58:42 -04:00
parent 6b2578c3a8
commit 467dcb1f87
7 changed files with 260 additions and 79 deletions
+2
View File
@@ -162,6 +162,7 @@ pub struct BrowserCore {
update_policy: UpdatePolicy,
appearance: AppearanceSettings,
sync_object_policies: SyncObjectPolicies,
sync_connection_state: ely_domain::SyncConnectionState,
command_query: String,
}
@@ -210,6 +211,7 @@ impl BrowserCore {
update_policy: UpdatePolicy::default(),
appearance: AppearanceSettings::default(),
sync_object_policies: SyncObjectPolicies::default(),
sync_connection_state: ely_domain::SyncConnectionState::SignedOut,
spaces: vec![space],
profiles: vec![profile],
tabs: vec![tab],
+54 -48
View File
@@ -1,4 +1,7 @@
use ely_domain::{SyncObjectKind, SyncObjectPolicy, SyncObjectState, SyncObjectStatus, SyncStatus};
use ely_domain::{
SyncConnectionState, SyncObjectKind, SyncObjectPolicy, SyncObjectState, SyncObjectStatus,
SyncStatus,
};
use super::BrowserCore;
@@ -75,54 +78,57 @@ impl BrowserCore {
self.sync_object_policies.get(kind)
}
pub fn set_sync_connection_state(&mut self, state: SyncConnectionState) {
self.sync_connection_state = state;
}
pub(super) fn sync_status(&self) -> SyncStatus {
SyncStatus::signed_out(vec![
self.sync_object_status(
SyncObjectKind::Spaces,
self.spaces.len(),
SyncObjectState::LocalOnly,
),
self.sync_object_status(
SyncObjectKind::Tabs,
self.sync_enabled_tab_count(),
SyncObjectState::LocalOnly,
),
self.sync_object_status(
SyncObjectKind::Bookmarks,
self.bookmarks.len(),
SyncObjectState::LocalOnly,
),
self.sync_object_status(
SyncObjectKind::Notes,
self.notes.len(),
SyncObjectState::LocalOnly,
),
self.sync_object_status(
SyncObjectKind::ReadingList,
self.reading_list.len(),
SyncObjectState::LocalOnly,
),
self.sync_object_status(
SyncObjectKind::Profiles,
self.profiles.len(),
SyncObjectState::LocalOnly,
),
self.sync_object_status(
SyncObjectKind::SitePermissions,
self.site_permissions.len(),
SyncObjectState::LocalOnly,
),
self.sync_object_status(
SyncObjectKind::History,
self.history_entries.len(),
SyncObjectState::PrivacyControlled,
),
self.sync_object_status(
SyncObjectKind::PluginSettings,
self.installed_plugins.len(),
SyncObjectState::LocalOnly,
),
])
let enabled_state = match &self.sync_connection_state {
SyncConnectionState::SyncReady { .. } => SyncObjectState::Synced,
_ => SyncObjectState::LocalOnly,
};
SyncStatus::new(
self.sync_connection_state.clone(),
vec![
self.sync_object_status(SyncObjectKind::Spaces, self.spaces.len(), enabled_state),
self.sync_object_status(
SyncObjectKind::Tabs,
self.sync_enabled_tab_count(),
enabled_state,
),
self.sync_object_status(
SyncObjectKind::Bookmarks,
self.bookmarks.len(),
enabled_state,
),
self.sync_object_status(SyncObjectKind::Notes, self.notes.len(), enabled_state),
self.sync_object_status(
SyncObjectKind::ReadingList,
self.reading_list.len(),
enabled_state,
),
self.sync_object_status(
SyncObjectKind::Profiles,
self.profiles.len(),
enabled_state,
),
self.sync_object_status(
SyncObjectKind::SitePermissions,
self.site_permissions.len(),
enabled_state,
),
self.sync_object_status(
SyncObjectKind::History,
self.history_entries.len(),
SyncObjectState::PrivacyControlled,
),
self.sync_object_status(
SyncObjectKind::PluginSettings,
self.installed_plugins.len(),
enabled_state,
),
],
)
}
fn sync_object_status(