@@ -225,10 +225,19 @@ struct MountSummary {
225225 status: String,
226226 root_exists: bool,
227227 entity_count: usize,
228+ hydration_progress: Option<MountHydrationProgress>,
228229 pending_change_count: usize,
229230 provider: Option<ProviderRuntimeSummary>,
230231}
231232
233+ #[derive(Clone, Serialize)]
234+ #[serde(rename_all = "camelCase")]
235+ struct MountHydrationProgress {
236+ indexed_files: usize,
237+ remaining_files: usize,
238+ total_files: usize,
239+ }
240+
232241#[derive(Clone, Serialize)]
233242#[serde(rename_all = "camelCase")]
234243struct ProviderRuntimeSummary {
@@ -3937,6 +3946,7 @@ fn degraded_snapshot(message: String) -> DesktopSnapshot {
39373946 status: "error".to_string(),
39383947 root_exists: false,
39393948 entity_count: 0,
3949+ hydration_progress: None,
39403950 pending_change_count: 0,
39413951 provider: None,
39423952 },
@@ -4122,6 +4132,7 @@ fn mount_summary_with_pending_change_count(
41224132 status: "not_mounted".to_string(),
41234133 root_exists: false,
41244134 entity_count: 0,
4135+ hydration_progress: None,
41254136 pending_change_count: 0,
41264137 provider: None,
41274138 };
@@ -4133,6 +4144,7 @@ fn mount_summary_with_pending_change_count(
41334144 .unwrap_or("ready");
41344145 let access_root = mount_access_root(mount);
41354146 let root_exists = mount_root_exists_for_desktop_summary(mount, &access_root);
4147+ let entities = store.and_then(|store| store.list_entities(&mount.mount_id).ok());
41364148
41374149 MountSummary {
41384150 mount_id: mount.mount_id.0.clone(),
@@ -4160,10 +4172,13 @@ fn mount_summary_with_pending_change_count(
41604172 read_only: mount.read_only,
41614173 status: mount_status.to_string(),
41624174 root_exists,
4163- entity_count: store
4164- .and_then(|store| store.list_entities(&mount.mount_id).ok() )
4175+ entity_count: entities
4176+ .as_ref( )
41654177 .map(|entities| entities.len())
41664178 .unwrap_or(0),
4179+ hydration_progress: entities
4180+ .as_ref()
4181+ .and_then(|entities| mount_hydration_progress(entities)),
41674182 pending_change_count: pending_change_count.unwrap_or_else(|| {
41684183 store
41694184 .map(|store| pending_changes_for_mount(store, state_root, &mount.mount_id))
@@ -4175,6 +4190,33 @@ fn mount_summary_with_pending_change_count(
41754190 }
41764191}
41774192
4193+ fn mount_hydration_progress(entities: &[EntityRecord]) -> Option<MountHydrationProgress> {
4194+ let mut indexed_files = 0usize;
4195+ let mut remaining_files = 0usize;
4196+
4197+ for entity in entities {
4198+ if entity.kind != EntityKind::Page {
4199+ continue;
4200+ }
4201+ match entity.hydration {
4202+ HydrationState::Hydrated | HydrationState::Dirty | HydrationState::Conflicted => {
4203+ indexed_files += 1;
4204+ }
4205+ HydrationState::Stub => {
4206+ remaining_files += 1;
4207+ }
4208+ HydrationState::Virtual => {}
4209+ }
4210+ }
4211+
4212+ let total_files = indexed_files + remaining_files;
4213+ (total_files > 0).then_some(MountHydrationProgress {
4214+ indexed_files,
4215+ remaining_files,
4216+ total_files,
4217+ })
4218+ }
4219+
41784220fn mount_live_mode_summary(
41794221 store: &SqliteStateStore,
41804222 mount: Option<&MountConfig>,
@@ -11603,6 +11645,7 @@ mod tests {
1160311645 let summary = super::mount_summary(None, Path::new("."), None, None, None);
1160411646
1160511647 assert!(Path::new(&summary.local_path).is_absolute());
11648+ assert!(summary.hydration_progress.is_none());
1160611649 }
1160711650
1160811651 #[test]
@@ -11793,6 +11836,95 @@ mod tests {
1179311836 assert!(snapshot.recent_files.is_empty());
1179411837 }
1179511838
11839+ #[test]
11840+ fn desktop_snapshot_reports_hydratable_file_progress() {
11841+ let temp = TestTempDir::new("desktop-file-progress");
11842+ let mut store = SqliteStateStore::open(temp.path().to_path_buf()).expect("open store");
11843+ let connection = test_connection("workspace-1", "CodeFlash");
11844+ let mount = MountConfig::new(
11845+ MountId::new("notion-main"),
11846+ "notion",
11847+ temp.path().join("codeflash-wiki"),
11848+ )
11849+ .with_connection_id(connection.connection_id.clone())
11850+ .projection(ProjectionMode::LinuxFuse);
11851+
11852+ store.save_connection(connection).expect("save connection");
11853+ store.save_mount(mount).expect("save mount");
11854+
11855+ for (remote_id, title, path, hydration) in [
11856+ (
11857+ "hydrated-page",
11858+ "Hydrated",
11859+ "Hydrated/page.md",
11860+ HydrationState::Hydrated,
11861+ ),
11862+ (
11863+ "dirty-page",
11864+ "Dirty",
11865+ "Dirty/page.md",
11866+ HydrationState::Dirty,
11867+ ),
11868+ (
11869+ "conflicted-page",
11870+ "Conflicted",
11871+ "Conflicted/page.md",
11872+ HydrationState::Conflicted,
11873+ ),
11874+ ("stub-page", "Stub", "Stub/page.md", HydrationState::Stub),
11875+ (
11876+ "virtual-page",
11877+ "Virtual",
11878+ "Virtual/page.md",
11879+ HydrationState::Virtual,
11880+ ),
11881+ ] {
11882+ store
11883+ .save_entity(
11884+ EntityRecord::new(
11885+ MountId::new("notion-main"),
11886+ RemoteId::new(remote_id),
11887+ EntityKind::Page,
11888+ title,
11889+ path,
11890+ )
11891+ .with_hydration(hydration),
11892+ )
11893+ .expect("save page entity");
11894+ }
11895+
11896+ for (remote_id, kind, path) in [
11897+ ("directory-1", EntityKind::Directory, "Directory"),
11898+ ("database-1", EntityKind::Database, "Database"),
11899+ ("asset-1", EntityKind::Asset, "asset.png"),
11900+ ] {
11901+ store
11902+ .save_entity(
11903+ EntityRecord::new(
11904+ MountId::new("notion-main"),
11905+ RemoteId::new(remote_id),
11906+ kind,
11907+ remote_id,
11908+ path,
11909+ )
11910+ .with_hydration(HydrationState::Stub),
11911+ )
11912+ .expect("save non-page entity");
11913+ }
11914+
11915+ let snapshot = super::load_desktop_snapshot_from_store(&store, temp.path())
11916+ .expect("load snapshot from test store");
11917+ let progress = snapshot
11918+ .mount
11919+ .hydration_progress
11920+ .expect("file progress is reported");
11921+
11922+ assert_eq!(snapshot.mount.entity_count, 8);
11923+ assert_eq!(progress.indexed_files, 3);
11924+ assert_eq!(progress.remaining_files, 1);
11925+ assert_eq!(progress.total_files, 4);
11926+ }
11927+
1179611928 #[test]
1179711929 fn desktop_snapshot_prefers_active_granola_over_stale_notion_mount() {
1179811930 let temp = TestTempDir::new("desktop-granola-first");
@@ -16152,6 +16284,11 @@ fn sample_snapshot() -> DesktopSnapshot {
1615216284 status: "ready".to_string(),
1615316285 root_exists: true,
1615416286 entity_count: 42,
16287+ hydration_progress: Some(MountHydrationProgress {
16288+ indexed_files: 16,
16289+ remaining_files: 4,
16290+ total_files: 20,
16291+ }),
1615516292 pending_change_count: 3,
1615616293 provider: None,
1615716294 };
0 commit comments