Skip to content

Commit c7cdba2

Browse files
committed
fix: scope macOS file provider mount activation
1 parent 5858eda commit c7cdba2

5 files changed

Lines changed: 181 additions & 104 deletions

File tree

apps/desktop/src-tauri/src/main.rs

Lines changed: 135 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ const VIRTUAL_PROJECTION_SOURCE_READY_TIMEOUT: Duration = Duration::from_secs(30
176176
const VIRTUAL_PROJECTION_SOURCE_READY_POLL: Duration = Duration::from_millis(250);
177177
const VIRTUAL_PROJECTION_SOURCE_READY_LOG_EVERY: Duration = Duration::from_secs(2);
178178
#[cfg(target_os = "macos")]
179-
const MACOS_FILE_PROVIDER_MOUNT_ROOT_APPEAR_TIMEOUT: Duration = Duration::from_secs(5);
179+
const MACOS_FILE_PROVIDER_MOUNT_ROOT_APPEAR_TIMEOUT: Duration = Duration::from_secs(30);
180180
#[cfg(target_os = "macos")]
181181
const MACOS_FILE_PROVIDER_MOUNT_ROOT_RECOVERY_TIMEOUT: Duration = Duration::from_secs(30);
182182
const LIVE_MODE_RUNNER_ACTIVE_INTERVAL: Duration = Duration::from_millis(500);
@@ -8808,7 +8808,7 @@ fn activate_virtual_projection_mount(
88088808
wait_for_mount_entities(state_root, &mount.mount_id)?;
88098809
}
88108810
ensure_virtual_projection_runtime(state_root, mount)?;
8811-
signal_virtual_projection_refresh(mount);
8811+
refresh_virtual_projection_mount_activation(mount);
88128812
recover_virtual_projection_mount_root_if_needed(state_root, mount)?;
88138813
desktop_log(
88148814
"info",
@@ -8877,25 +8877,15 @@ fn recover_macos_file_provider_mount_root_if_needed(
88778877

88788878
desktop_log(
88798879
"warn",
8880-
"file_provider.recover_started",
8880+
"file_provider.mount_repair_started",
88818881
format!(
8882-
"recovering macOS File Provider domain for mount `{}` after activation: {reason}",
8882+
"refreshing macOS File Provider mount `{}` after activation without resetting the shared domain: {reason}",
88838883
mount.mount_id.0
88848884
),
88858885
);
8886-
reset_macos_file_provider_domain(&reason)?;
8887-
register_macos_virtual_projection(&mount.mount_id.0, &mount.root.display().to_string())?;
8888-
macos_file_provider_domain_url(localityd::file_provider::MACOS_FILE_PROVIDER_DOMAIN_ID)
8889-
.map_err(|error| {
8890-
format!(
8891-
"Could not open macOS File Provider domain after recovery for `{}`: {}",
8892-
mount.mount_id.0,
8893-
error.message()
8894-
)
8895-
})?;
88968886
prefetch_virtual_projection_root(state_root, mount)?;
88978887
ensure_virtual_projection_runtime(state_root, mount)?;
8898-
signal_virtual_projection_refresh(mount);
8888+
refresh_virtual_projection_mount_activation(mount);
88998889

89008890
wait_for_macos_file_provider_mount_root_recovery(
89018891
&root,
@@ -8905,9 +8895,9 @@ fn recover_macos_file_provider_mount_root_if_needed(
89058895

89068896
desktop_log(
89078897
"info",
8908-
"file_provider.recover_finished",
8898+
"file_provider.mount_repair_finished",
89098899
format!(
8910-
"recovered macOS File Provider domain for mount `{}`",
8900+
"refreshed macOS File Provider mount `{}` without resetting the shared domain",
89118901
mount.mount_id.0
89128902
),
89138903
);
@@ -8937,7 +8927,7 @@ fn wait_for_macos_file_provider_mount_root_recovery(
89378927

89388928
if Instant::now() >= deadline {
89398929
return Err(format!(
8940-
"macOS File Provider recovery did not produce a healthy mount root `{}`: {reason}",
8930+
"macOS File Provider did not finish preparing mount root `{}`: {reason}",
89418931
root.display()
89428932
));
89438933
}
@@ -9000,22 +8990,10 @@ fn evaluate_macos_file_provider_mount_root(root: &Path) -> Result<String, String
90008990
Ok(details)
90018991
}
90028992

9003-
#[cfg(target_os = "macos")]
9004-
fn reset_macos_file_provider_domain(reason: &str) -> Result<(), String> {
9005-
run_macos_file_provider_helper("reset", Vec::new())
9006-
.map(|_| ())
9007-
.map_err(|error| {
9008-
format!(
9009-
"Could not reset macOS File Provider domain while recovering from `{reason}`: {}",
9010-
error.message()
9011-
)
9012-
})
9013-
}
9014-
90158993
fn macos_file_provider_mount_root_health_error(root: &Path, details: &str) -> Option<String> {
90168994
if details.contains("uploadingError") || details.contains("NSCocoaErrorDomain Code=3328") {
90178995
return Some(format!(
9018-
"The macOS File Provider mount root `{}` is in a local-upload error state. Run clean-start or reset the Locality File Provider domain, then reconnect Notion.",
8996+
"The macOS File Provider mount root `{}` is in a local-upload error state. Locality left the shared File Provider domain and other source folders unchanged; review File Provider diagnostics before repairing this mount.",
90198997
root.display()
90208998
));
90218999
}
@@ -9802,42 +9780,82 @@ fn running_daemon_build(state_root: &Path) -> Option<DaemonBuildInfo> {
98029780
.map(|report| report.build)
98039781
}
98049782

9805-
fn signal_virtual_projection_refresh(mount: &MountConfig) {
9806-
for identifier in virtual_projection_refresh_signal_identifiers(mount) {
9807-
if let Err(error) = signal_virtual_projection_container(mount, &identifier) {
9808-
desktop_log(
9809-
"warn",
9810-
"file_provider.signal_failed",
9811-
format!(
9812-
"could not signal {}:{} refresh: {error}",
9813-
mount.mount_id.0, identifier
9814-
),
9815-
);
9783+
#[derive(Clone, Debug, PartialEq, Eq)]
9784+
enum VirtualProjectionRefreshAction {
9785+
Signal(String),
9786+
ReimportThenSignal(String),
9787+
}
9788+
9789+
impl VirtualProjectionRefreshAction {
9790+
fn identifier(&self) -> &str {
9791+
match self {
9792+
Self::Signal(identifier) | Self::ReimportThenSignal(identifier) => identifier,
98169793
}
98179794
}
98189795
}
98199796

9820-
fn virtual_projection_refresh_signal_identifiers(mount: &MountConfig) -> Vec<String> {
9797+
fn signal_virtual_projection_refresh(mount: &MountConfig) {
9798+
for action in virtual_projection_refresh_actions(mount) {
9799+
run_virtual_projection_refresh_action(mount, &action);
9800+
}
9801+
}
9802+
9803+
fn refresh_virtual_projection_mount_activation(mount: &MountConfig) {
9804+
for action in virtual_projection_mount_activation_refresh_actions(mount) {
9805+
run_virtual_projection_refresh_action(mount, &action);
9806+
}
9807+
}
9808+
9809+
fn run_virtual_projection_refresh_action(
9810+
mount: &MountConfig,
9811+
action: &VirtualProjectionRefreshAction,
9812+
) {
9813+
if let Err(error) = refresh_virtual_projection_container(mount, &action) {
9814+
desktop_log(
9815+
"warn",
9816+
"file_provider.signal_failed",
9817+
format!(
9818+
"could not signal {}:{} refresh: {error}",
9819+
mount.mount_id.0,
9820+
action.identifier()
9821+
),
9822+
);
9823+
}
9824+
}
9825+
9826+
fn virtual_projection_refresh_actions(mount: &MountConfig) -> Vec<VirtualProjectionRefreshAction> {
98219827
if mount.projection == ProjectionMode::MacosFileProvider {
98229828
return vec![
9823-
ROOT_CONTAINER_IDENTIFIER.to_string(),
9824-
mount_point_identifier(mount),
9825-
"working-set".to_string(),
9829+
VirtualProjectionRefreshAction::Signal(ROOT_CONTAINER_IDENTIFIER.to_string()),
9830+
VirtualProjectionRefreshAction::ReimportThenSignal(mount_point_identifier(mount)),
9831+
VirtualProjectionRefreshAction::Signal("working-set".to_string()),
98269832
];
98279833
}
98289834
vec![
9829-
ROOT_CONTAINER_IDENTIFIER.to_string(),
9830-
mount_point_identifier(mount),
9835+
VirtualProjectionRefreshAction::Signal(ROOT_CONTAINER_IDENTIFIER.to_string()),
9836+
VirtualProjectionRefreshAction::Signal(mount_point_identifier(mount)),
98319837
]
98329838
}
98339839

9834-
fn signal_virtual_projection_container(
9840+
fn virtual_projection_mount_activation_refresh_actions(
98359841
mount: &MountConfig,
9836-
container_identifier: &str,
9842+
) -> Vec<VirtualProjectionRefreshAction> {
9843+
if mount.projection == ProjectionMode::MacosFileProvider {
9844+
return vec![
9845+
VirtualProjectionRefreshAction::Signal(ROOT_CONTAINER_IDENTIFIER.to_string()),
9846+
VirtualProjectionRefreshAction::ReimportThenSignal(mount_point_identifier(mount)),
9847+
];
9848+
}
9849+
virtual_projection_refresh_actions(mount)
9850+
}
9851+
9852+
fn refresh_virtual_projection_container(
9853+
mount: &MountConfig,
9854+
action: &VirtualProjectionRefreshAction,
98379855
) -> Result<(), String> {
98389856
match mount.projection {
98399857
ProjectionMode::MacosFileProvider => {
9840-
refresh_macos_virtual_projection(&mount.mount_id.0, container_identifier)
9858+
refresh_macos_virtual_projection(&mount.mount_id.0, action)
98419859
}
98429860
ProjectionMode::LinuxFuse
98439861
| ProjectionMode::PlainFiles
@@ -9846,13 +9864,19 @@ fn signal_virtual_projection_container(
98469864
}
98479865

98489866
#[cfg(target_os = "macos")]
9849-
fn refresh_macos_virtual_projection(mount_id: &str, identifier: &str) -> Result<(), String> {
9850-
if identifier == "working-set" {
9851-
return signal_macos_virtual_projection(mount_id, identifier);
9867+
fn refresh_macos_virtual_projection(
9868+
mount_id: &str,
9869+
action: &VirtualProjectionRefreshAction,
9870+
) -> Result<(), String> {
9871+
match action {
9872+
VirtualProjectionRefreshAction::Signal(identifier) => {
9873+
signal_macos_virtual_projection(mount_id, identifier)
9874+
}
9875+
VirtualProjectionRefreshAction::ReimportThenSignal(identifier) => {
9876+
reimport_macos_virtual_projection(mount_id, identifier)
9877+
.or_else(|_| signal_macos_virtual_projection(mount_id, identifier))
9878+
}
98529879
}
9853-
9854-
reimport_macos_virtual_projection(mount_id, identifier)
9855-
.or_else(|_| signal_macos_virtual_projection(mount_id, identifier))
98569880
}
98579881

98589882
#[cfg(target_os = "macos")]
@@ -9903,7 +9927,10 @@ fn run_macos_file_provider_refresh_action(
99039927
}
99049928

99059929
#[cfg(not(target_os = "macos"))]
9906-
fn refresh_macos_virtual_projection(_mount_id: &str, _identifier: &str) -> Result<(), String> {
9930+
fn refresh_macos_virtual_projection(
9931+
_mount_id: &str,
9932+
_action: &VirtualProjectionRefreshAction,
9933+
) -> Result<(), String> {
99079934
Ok(())
99089935
}
99099936

@@ -10512,7 +10539,7 @@ fn recoverable_macos_file_provider_activation_error(message: &str) -> bool {
1051210539
|| message.contains("registered but not enabled")
1051310540
|| message.contains("did not return a CloudStorage URL")
1051410541
|| message.contains("macOS has not created")
10515-
|| (message.contains("did not produce a healthy mount root")
10542+
|| (message.contains("did not finish preparing mount root")
1051610543
&& macos_file_provider_mount_root_is_missing(message))
1051710544
}
1051810545

@@ -12001,12 +12028,13 @@ mod tests {
1200112028
ActionReport, DESKTOP_ACTIVITY_LIMIT, DESKTOP_INSTALL_MARKER_VERSION,
1200212029
LIVE_MODE_REMOTE_FAST_FORWARD_LEASE, LIVE_MODE_RUNNER_ACTIVE_INTERVAL,
1200312030
LIVE_MODE_RUNNER_PERIODIC_RECHECK, MonitorScreenBounds, PendingChange, ScreenBounds,
12004-
TerminalCliLinkState, TrayVisualState, acknowledge_install_state_at, activity_timestamp,
12005-
clear_mount_cached_projection, clear_state_root_contents, clear_visible_projection_paths,
12006-
conflict_preview, connection_metadata_changed, current_daemon_build_id,
12007-
current_desktop_build_id, diff_report_message, exact_located_entity_record,
12008-
exact_notion_entry_matches, failed_push_summary, has_unresolved_conflict_markers,
12009-
hydration_after_editor_write, inspect_install_state, install_terminal_cli_link_at,
12031+
TerminalCliLinkState, TrayVisualState, VirtualProjectionRefreshAction,
12032+
acknowledge_install_state_at, activity_timestamp, clear_mount_cached_projection,
12033+
clear_state_root_contents, clear_visible_projection_paths, conflict_preview,
12034+
connection_metadata_changed, current_daemon_build_id, current_desktop_build_id,
12035+
diff_report_message, exact_located_entity_record, exact_notion_entry_matches,
12036+
failed_push_summary, has_unresolved_conflict_markers, hydration_after_editor_write,
12037+
inspect_install_state, install_terminal_cli_link_at,
1201012038
install_terminal_cli_link_in_path_dirs, is_notion_access_lost_message,
1201112039
is_unsupported_schema_version_message, live_mode_claim_remote_fast_forward_key,
1201212040
live_mode_enabled_mount, live_mode_local_reconcile_targets_for_mount_at,
@@ -12032,8 +12060,8 @@ mod tests {
1203212060
summarize_virtual_projection_children, terminal_cli_link_state, tray_icon_image,
1203312061
tray_icon_should_use_template, tray_popover_anchor, tray_popover_position,
1203412062
unsupported_notion_locator_url_message, validate_mount_root,
12035-
validate_source_action_confirmation, virtual_projection_prefetch_container_identifiers,
12036-
virtual_projection_refresh_signal_identifiers,
12063+
validate_source_action_confirmation, virtual_projection_mount_activation_refresh_actions,
12064+
virtual_projection_prefetch_container_identifiers, virtual_projection_refresh_actions,
1203712065
virtual_projection_source_ready_timeout_message,
1203812066
virtual_projection_waits_for_mount_point_children_before_registration,
1203912067
wait_for_live_mode_state_change, wake_live_mode_runner, write_terminal_cli_path_section,
@@ -14445,9 +14473,9 @@ mod tests {
1444514473
}
1444614474

1444714475
#[test]
14448-
fn file_provider_missing_child_mount_after_recovery_is_recoverable() {
14476+
fn file_provider_missing_child_mount_while_preparing_is_recoverable() {
1444914477
assert!(super::recoverable_macos_file_provider_activation_error(
14450-
"macOS File Provider recovery did not produce a healthy mount root `/Users/codeflash/Library/CloudStorage/Locality/linear`: Could not inspect macOS File Provider mount root `/Users/codeflash/Library/CloudStorage/Locality/linear`: Error: Error Domain=NSPOSIXErrorDomain Code=2 \"Couldn't find a file for /Users/codeflash/Library/CloudStorage/Locality/linear\" UserInfo={NSDescription=Couldn't find a file for /Users/codeflash/Library/CloudStorage/Locality/linear}"
14478+
"macOS File Provider did not finish preparing mount root `/Users/codeflash/Library/CloudStorage/Locality/linear`: Could not inspect macOS File Provider mount root `/Users/codeflash/Library/CloudStorage/Locality/linear`: Error: Error Domain=NSPOSIXErrorDomain Code=2 \"Couldn't find a file for /Users/codeflash/Library/CloudStorage/Locality/linear\" UserInfo={NSDescription=Couldn't find a file for /Users/codeflash/Library/CloudStorage/Locality/linear}"
1445114479
));
1445214480
}
1445314481

@@ -14970,7 +14998,27 @@ mod tests {
1497014998
}
1497114999

1497215000
#[test]
14973-
fn virtual_projection_refresh_signals_macos_file_provider_visible_containers() {
15001+
fn macos_file_provider_refresh_is_scoped_to_the_new_mount() {
15002+
let mount = MountConfig::new(
15003+
MountId::new("google-calendar-main"),
15004+
"google-calendar",
15005+
"/tmp/Locality/google-calendar-main",
15006+
)
15007+
.projection(ProjectionMode::MacosFileProvider);
15008+
15009+
assert_eq!(
15010+
virtual_projection_mount_activation_refresh_actions(&mount),
15011+
vec![
15012+
VirtualProjectionRefreshAction::Signal("root".to_string()),
15013+
VirtualProjectionRefreshAction::ReimportThenSignal(
15014+
"mount:google-calendar-main".to_string(),
15015+
),
15016+
]
15017+
);
15018+
}
15019+
15020+
#[test]
15021+
fn macos_file_provider_visible_refresh_never_reimports_the_shared_root() {
1497415022
let mount = MountConfig::new(
1497515023
MountId::new("notion-main"),
1497615024
"notion",
@@ -14979,15 +15027,28 @@ mod tests {
1497915027
.projection(ProjectionMode::MacosFileProvider);
1498015028

1498115029
assert_eq!(
14982-
virtual_projection_refresh_signal_identifiers(&mount),
15030+
virtual_projection_refresh_actions(&mount),
1498315031
vec![
14984-
"root".to_string(),
14985-
"mount:notion-main".to_string(),
14986-
"working-set".to_string(),
15032+
VirtualProjectionRefreshAction::Signal("root".to_string()),
15033+
VirtualProjectionRefreshAction::ReimportThenSignal("mount:notion-main".to_string(),),
15034+
VirtualProjectionRefreshAction::Signal("working-set".to_string()),
1498715035
]
1498815036
);
1498915037
}
1499015038

15039+
#[cfg(target_os = "macos")]
15040+
#[test]
15041+
fn macos_file_provider_mount_preparation_uses_non_trivial_waits() {
15042+
assert_eq!(
15043+
super::MACOS_FILE_PROVIDER_MOUNT_ROOT_APPEAR_TIMEOUT,
15044+
Duration::from_secs(30)
15045+
);
15046+
assert_eq!(
15047+
super::MACOS_FILE_PROVIDER_MOUNT_ROOT_RECOVERY_TIMEOUT,
15048+
Duration::from_secs(30)
15049+
);
15050+
}
15051+
1499115052
#[test]
1499215053
fn virtual_projection_prefetch_container_identifiers_use_mount_point_root() {
1499315054
let mount = MountConfig::new(

apps/desktop/src/mounts.test.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ describe("mount display helpers", () => {
7575
title: "Notion",
7676
subtitle: "CodeFlash / notion",
7777
localPath: "/home/ada/Locality/notion",
78-
displayPath: "/home/ada/Locality/notion",
78+
displayPath: "~/Locality/notion",
7979
projection: "Linux FUSE",
8080
access: "Edit enabled",
8181
content: "24 items, 3 pending",
@@ -192,13 +192,25 @@ describe("mount display helpers", () => {
192192

193193
it("compacts long paths from the middle so filenames remain visible", () => {
194194
expect(compactPath("/home/ada/Locality/notion/Engineering/Roadmap 2026/page.md", 42)).toBe(
195-
"/.../Engineering/Roadmap 2026/page.md",
195+
"~/.../Engineering/Roadmap 2026/page.md",
196196
);
197197
expect(compactPath("~/Library/CloudStorage/Locality/notion", 64)).toBe(
198198
"~/Library/CloudStorage/Locality/notion",
199199
);
200200
});
201201

202+
it("shows macOS source mounts relative to the home directory", () => {
203+
expect(
204+
compactPath(
205+
"/Users/saurabh/Library/CloudStorage/Locality/google-calendar-main",
206+
64,
207+
),
208+
).toBe("~/Library/CloudStorage/Locality/google-calendar-main");
209+
expect(compactPath("/Users/saurabh/Library/CloudStorage/Locality/notion", 64)).toBe(
210+
"~/Library/CloudStorage/Locality/notion",
211+
);
212+
});
213+
202214
it("selects the clicked mount row by mount id", () => {
203215
const notion = mount({});
204216
const google = mount({

0 commit comments

Comments
 (0)