Skip to content

Commit 1334315

Browse files
authored
Merge pull request #91 from codeflash-ai/feat/finder-enable-onboarding
Guide users through Finder File Provider enablement
2 parents fa0a377 + 20a97fb commit 1334315

18 files changed

Lines changed: 2516 additions & 37 deletions

Makefile

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,10 @@ prepare-macos-file-provider: ## Stage the macOS File Provider extension for Taur
148148
install-macos-file-provider: ## Install/register the local macOS File Provider development bundle.
149149
platform/macos/LocalityFileProvider/scripts/install-dev-bundle.sh
150150

151+
.PHONY: install-macos-prompt-test-app
152+
install-macos-prompt-test-app: build-tauri ## Build, reset, install, register, and launch a fresh macOS File Provider prompt test app.
153+
scripts/install-macos-prompt-test-app.sh $(PROMPT_TEST_APP_ARGS)
154+
151155
.PHONY: prepare-desktop-dev-sidecars
152156
prepare-desktop-dev-sidecars: ## Build debug desktop sidecars used by Tauri dev.
153157
$(DESKTOP_NPM) run dev:prepare
@@ -209,6 +213,10 @@ test-linux-publish-config: ## Validate Linux package publish configuration.
209213
test-macos-publish-config: ## Validate macOS package publish configuration.
210214
tests/macos_publish_config.sh
211215

216+
.PHONY: test-macos-prompt-test-app-installer
217+
test-macos-prompt-test-app-installer: ## Validate the macOS prompt test app installer dry-run plan.
218+
bash scripts/install-macos-prompt-test-app.test.sh
219+
212220
.PHONY: test-windows-publish-config
213221
test-windows-publish-config: ## Validate Windows package publish configuration.
214222
tests/windows_publish_config.sh

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

Lines changed: 273 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,14 @@ struct WorkspaceMountOnboardingReport {
357357
launch_strategy: String,
358358
}
359359

360+
#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
361+
#[serde(rename_all = "camelCase")]
362+
struct FileProviderEnablementReport {
363+
state: String,
364+
message: String,
365+
path: Option<String>,
366+
}
367+
360368
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
361369
enum MacosWorkspaceMountOnboardingState {
362370
Created,
@@ -369,7 +377,7 @@ impl MacosWorkspaceMountOnboardingState {
369377
fn as_str(self) -> &'static str {
370378
match self {
371379
Self::Created => "created",
372-
Self::ApprovalRequired => "approval_required",
380+
Self::ApprovalRequired => "needs_finder_enable",
373381
Self::WaitingForCloudStorageRoot => "waiting_for_cloudstorage_root",
374382
Self::Failed => "failed",
375383
}
@@ -1929,6 +1937,33 @@ async fn run_workspace_mount_onboarding(
19291937
report
19301938
}
19311939

1940+
#[tauri::command]
1941+
async fn file_provider_enablement_status() -> FileProviderEnablementReport {
1942+
tauri::async_runtime::spawn_blocking(macos_file_provider_enablement_status_blocking)
1943+
.await
1944+
.unwrap_or_else(|error| FileProviderEnablementReport {
1945+
state: "unavailable".to_string(),
1946+
message: format!("File Provider status worker failed: {error}"),
1947+
path: None,
1948+
})
1949+
}
1950+
1951+
#[tauri::command]
1952+
async fn reveal_file_provider_enablement() -> ActionReport {
1953+
tauri::async_runtime::spawn_blocking(reveal_file_provider_enablement_blocking)
1954+
.await
1955+
.map_or_else(
1956+
|error| ActionReport {
1957+
ok: false,
1958+
message: format!("Finder worker failed: {error}"),
1959+
},
1960+
|result| match result {
1961+
Ok(message) => ActionReport { ok: true, message },
1962+
Err(message) => ActionReport { ok: false, message },
1963+
},
1964+
)
1965+
}
1966+
19321967
async fn create_desktop_mount_command(
19331968
app: AppHandle,
19341969
request: CreateDesktopMountRequest,
@@ -10101,7 +10136,7 @@ fn workspace_mount_onboarding_curated_message(
1010110136
) -> Option<&'static str> {
1010210137
match state {
1010310138
MacosWorkspaceMountOnboardingState::ApprovalRequired => {
10104-
Some("Enable Locality in Finder, then return here and click Check again.")
10139+
Some("In Finder, click Enable for Locality. Locality will continue automatically.")
1010510140
}
1010610141
MacosWorkspaceMountOnboardingState::WaitingForCloudStorageRoot => {
1010710142
Some("Locality is still waiting for the CloudStorage folder to appear.")
@@ -10193,6 +10228,142 @@ fn macos_workspace_mount_domain_user_enabled() -> Result<bool, String> {
1019310228
.unwrap_or(false))
1019410229
}
1019510230

10231+
fn classify_macos_file_provider_enablement(
10232+
user_enabled: Option<bool>,
10233+
fallback_path: Option<PathBuf>,
10234+
resolved_root: Result<Option<PathBuf>, String>,
10235+
) -> FileProviderEnablementReport {
10236+
let path = |value: Option<PathBuf>| value.map(|path| path.display().to_string());
10237+
match user_enabled {
10238+
None => FileProviderEnablementReport {
10239+
state: "not_registered".to_string(),
10240+
message: "Locality is preparing the Finder location.".to_string(),
10241+
path: path(fallback_path),
10242+
},
10243+
Some(false) => FileProviderEnablementReport {
10244+
state: "needs_finder_enable".to_string(),
10245+
message: "In Finder, click Enable for Locality.".to_string(),
10246+
path: path(fallback_path),
10247+
},
10248+
Some(true) => match resolved_root {
10249+
Ok(Some(root)) => FileProviderEnablementReport {
10250+
state: "ready".to_string(),
10251+
message: "Locality is enabled in Finder.".to_string(),
10252+
path: path(Some(root)),
10253+
},
10254+
Ok(None) => FileProviderEnablementReport {
10255+
state: "waiting_for_root".to_string(),
10256+
message: "Finishing the Locality folder setup.".to_string(),
10257+
path: path(fallback_path),
10258+
},
10259+
Err(message) => FileProviderEnablementReport {
10260+
state: "unavailable".to_string(),
10261+
message,
10262+
path: path(fallback_path),
10263+
},
10264+
},
10265+
}
10266+
}
10267+
10268+
fn macos_file_provider_domain_status(
10269+
report: &serde_json::Value,
10270+
) -> (Option<bool>, Option<PathBuf>) {
10271+
let domain = report
10272+
.get("domains")
10273+
.and_then(serde_json::Value::as_array)
10274+
.and_then(|domains| {
10275+
domains.iter().find(|domain| {
10276+
domain.get("identifier").and_then(serde_json::Value::as_str)
10277+
== Some(localityd::file_provider::MACOS_FILE_PROVIDER_DOMAIN_ID)
10278+
})
10279+
});
10280+
let user_enabled = domain
10281+
.and_then(|domain| domain.get("userEnabled"))
10282+
.and_then(serde_json::Value::as_bool);
10283+
let path = domain
10284+
.and_then(|domain| domain.get("url"))
10285+
.and_then(serde_json::Value::as_str)
10286+
.filter(|url| !url.is_empty())
10287+
.map(PathBuf::from);
10288+
(user_enabled, path)
10289+
}
10290+
10291+
#[cfg(target_os = "macos")]
10292+
fn macos_file_provider_enablement_status_blocking() -> FileProviderEnablementReport {
10293+
let provider_roots = macos_file_provider_cloud_storage_roots();
10294+
let report = match run_macos_file_provider_helper("list", Vec::new()) {
10295+
Ok(report) => report,
10296+
Err(error) => {
10297+
return FileProviderEnablementReport {
10298+
state: "unavailable".to_string(),
10299+
message: error.message(),
10300+
path: provider_roots
10301+
.first()
10302+
.map(|path| path.display().to_string()),
10303+
};
10304+
}
10305+
};
10306+
let (user_enabled, reported_path) = macos_file_provider_domain_status(&report.helper_report);
10307+
let fallback_path = reported_path.or_else(|| provider_roots.first().cloned());
10308+
10309+
let resolved_root = if user_enabled == Some(true) {
10310+
match macos_file_provider_domain_url(
10311+
localityd::file_provider::MACOS_FILE_PROVIDER_DOMAIN_ID,
10312+
) {
10313+
Ok(root) => Ok(Some(root)),
10314+
Err(error) if recoverable_macos_file_provider_activation_error(&error.message()) => {
10315+
Ok(None)
10316+
}
10317+
Err(error) => Err(error.message()),
10318+
}
10319+
} else {
10320+
Ok(None)
10321+
};
10322+
classify_macos_file_provider_enablement(user_enabled, fallback_path, resolved_root)
10323+
}
10324+
10325+
#[cfg(not(target_os = "macos"))]
10326+
fn macos_file_provider_enablement_status_blocking() -> FileProviderEnablementReport {
10327+
FileProviderEnablementReport {
10328+
state: "unavailable".to_string(),
10329+
message: "File Provider enablement is only available on macOS.".to_string(),
10330+
path: None,
10331+
}
10332+
}
10333+
10334+
fn macos_file_provider_reveal_path(
10335+
domain_path: Option<&Path>,
10336+
candidates: &[PathBuf],
10337+
) -> Option<PathBuf> {
10338+
domain_path
10339+
.into_iter()
10340+
.chain(candidates.iter().map(PathBuf::as_path))
10341+
.find_map(|candidate| {
10342+
candidate
10343+
.ancestors()
10344+
.find(|ancestor| ancestor.is_dir())
10345+
.map(Path::to_path_buf)
10346+
})
10347+
}
10348+
10349+
#[cfg(target_os = "macos")]
10350+
fn reveal_file_provider_enablement_blocking() -> Result<String, String> {
10351+
let report = macos_file_provider_enablement_status_blocking();
10352+
let domain_path = report.path.as_deref().map(Path::new);
10353+
let path =
10354+
macos_file_provider_reveal_path(domain_path, &macos_file_provider_cloud_storage_roots())
10355+
.ok_or_else(|| {
10356+
"Could not find the Locality location or its CloudStorage parent.".to_string()
10357+
})?;
10358+
open_in_file_manager(&path)?;
10359+
Ok(format!("Opened {} in Finder.", path.display()))
10360+
}
10361+
10362+
#[cfg(not(target_os = "macos"))]
10363+
fn reveal_file_provider_enablement_blocking() -> Result<String, String> {
10364+
Err("File Provider enablement is only available on macOS.".to_string())
10365+
}
10366+
1019610367
#[cfg(not(target_os = "macos"))]
1019710368
fn macos_workspace_mount_domain_user_enabled() -> Result<bool, String> {
1019810369
Ok(false)
@@ -13642,6 +13813,100 @@ mod tests {
1364213813
));
1364313814
}
1364413815

13816+
#[test]
13817+
fn file_provider_enablement_report_distinguishes_registration_and_approval() {
13818+
let missing = super::classify_macos_file_provider_enablement(None, None, Ok(None));
13819+
assert_eq!(missing.state, "not_registered");
13820+
13821+
let disabled = super::classify_macos_file_provider_enablement(
13822+
Some(false),
13823+
Some(PathBuf::from("/Users/test/Library/CloudStorage/Locality")),
13824+
Ok(None),
13825+
);
13826+
assert_eq!(disabled.state, "needs_finder_enable");
13827+
assert_eq!(
13828+
disabled.path.as_deref(),
13829+
Some("/Users/test/Library/CloudStorage/Locality")
13830+
);
13831+
}
13832+
13833+
#[test]
13834+
fn file_provider_domain_status_reads_helper_url_while_disabled() {
13835+
let helper_report = serde_json::json!({
13836+
"domains": [{
13837+
"identifier": "loc",
13838+
"userEnabled": false,
13839+
"url": "/Users/test/Library/CloudStorage/LocalityPromptTest"
13840+
}]
13841+
});
13842+
13843+
let (user_enabled, path) = super::macos_file_provider_domain_status(&helper_report);
13844+
13845+
assert_eq!(user_enabled, Some(false));
13846+
assert_eq!(
13847+
path,
13848+
Some(PathBuf::from(
13849+
"/Users/test/Library/CloudStorage/LocalityPromptTest"
13850+
))
13851+
);
13852+
}
13853+
13854+
#[test]
13855+
fn file_provider_enablement_report_waits_for_enabled_root() {
13856+
let report = super::classify_macos_file_provider_enablement(
13857+
Some(true),
13858+
Some(PathBuf::from("/Users/test/Library/CloudStorage/Locality")),
13859+
Ok(None),
13860+
);
13861+
13862+
assert_eq!(report.state, "waiting_for_root");
13863+
assert!(report.message.contains("Finishing"));
13864+
}
13865+
13866+
#[test]
13867+
fn file_provider_enablement_report_is_ready_only_with_resolved_root() {
13868+
let root = PathBuf::from("/Users/test/Library/CloudStorage/Locality");
13869+
let report = super::classify_macos_file_provider_enablement(
13870+
Some(true),
13871+
Some(root.clone()),
13872+
Ok(Some(root.clone())),
13873+
);
13874+
13875+
assert_eq!(report.state, "ready");
13876+
assert_eq!(report.path.as_deref(), root.to_str());
13877+
}
13878+
13879+
#[test]
13880+
fn file_provider_enablement_report_exposes_unavailable_errors() {
13881+
let report = super::classify_macos_file_provider_enablement(
13882+
Some(true),
13883+
None,
13884+
Err("File Provider helper unavailable".to_string()),
13885+
);
13886+
13887+
assert_eq!(report.state, "unavailable");
13888+
assert_eq!(report.message, "File Provider helper unavailable");
13889+
}
13890+
13891+
#[test]
13892+
fn file_provider_reveal_path_prefers_domain_then_existing_parent() {
13893+
let temp = TestTempDir::new("file-provider-reveal");
13894+
let cloud_storage = temp.path().join("Library/CloudStorage");
13895+
fs::create_dir_all(&cloud_storage).expect("create CloudStorage parent");
13896+
let domain = cloud_storage.join("Locality");
13897+
13898+
assert_eq!(
13899+
super::macos_file_provider_reveal_path(Some(&domain), &[domain.clone()]),
13900+
Some(cloud_storage)
13901+
);
13902+
13903+
fs::create_dir_all(&domain).expect("create domain root");
13904+
assert_eq!(
13905+
super::macos_file_provider_reveal_path(Some(&domain), &[domain.clone()]),
13906+
Some(domain)
13907+
);
13908+
}
13909+
1364513910
#[test]
1364613911
fn macos_file_provider_approval_surface_path_uses_first_existing_candidate() {
1364713912
let temp = TestTempDir::new("approval-surface");
@@ -13676,6 +13941,9 @@ mod tests {
1367613941
),
1367713942
expected
1367813943
);
13944+
if let Some(state) = expected {
13945+
assert_eq!(state.as_str(), "needs_finder_enable");
13946+
}
1367913947
}
1368013948

1368113949
#[test]
@@ -13716,7 +13984,7 @@ mod tests {
1371613984
super::workspace_mount_onboarding_curated_message(
1371713985
super::MacosWorkspaceMountOnboardingState::ApprovalRequired
1371813986
),
13719-
Some("Enable Locality in Finder, then return here and click Check again.")
13987+
Some("In Finder, click Enable for Locality. Locality will continue automatically.")
1372013988
);
1372113989
assert_eq!(
1372213990
super::workspace_mount_onboarding_curated_message(
@@ -17068,6 +17336,8 @@ fn main() {
1706817336
disconnect_source,
1706917337
export_source_backup,
1707017338
run_workspace_mount_onboarding,
17339+
file_provider_enablement_status,
17340+
reveal_file_provider_enablement,
1707117341
install_agent_guidance,
1707217342
locate_notion_page,
1707317343
search_notion_pages,

0 commit comments

Comments
 (0)