refactor: remove RegisterOnly and redesign sync setup UX

Replace confusing RegisterOnly action with clearer ShareLocalLibrary and JoinRemoteLibrary actions. Add placeholder for future MergeLibraries.

- Remove RegisterOnly from LibrarySyncAction enum
- Add ShareLocalLibrary (share your library to remote device)
- Add JoinRemoteLibrary (join existing remote library)
- Add MergeLibraries stub for future implementation
- Update CLI with three-option menu instead of two
- Simplify CLI arg parsing for new actions

The new UX is clearer: users choose to either share their library or join a remote one, instead of the ambiguous "register only" concept.
This commit is contained in:
Jamie Pine
2025-10-23 18:36:17 -07:00
parent 7917819f48
commit 559324ceb1
3 changed files with 79 additions and 49 deletions

View File

@@ -161,21 +161,45 @@ impl SetupArgs {
};
// Parse action
let action_str = self.action.as_deref().unwrap_or("register-only");
let action_str = self.action.as_deref().unwrap_or("share-local");
let action = match action_str {
"register-only" => LibrarySyncAction::RegisterOnly,
"create-shared" => {
"share-local" => {
let name = self
.name
.clone()
.ok_or_else(|| anyhow::anyhow!("--name is required for create-shared action"))?;
LibrarySyncAction::CreateShared {
leader_device_id,
name,
.ok_or_else(|| anyhow::anyhow!("--name is required for share-local action"))?;
LibrarySyncAction::ShareLocalLibrary { library_name: name }
}
"join-remote" => {
let remote_library_id = self
.remote_library
.ok_or_else(|| anyhow::anyhow!("--remote-library is required for join-remote action"))?;
let name = self
.name
.clone()
.ok_or_else(|| anyhow::anyhow!("--name is required for join-remote action"))?;
LibrarySyncAction::JoinRemoteLibrary {
remote_library_id,
remote_library_name: name,
}
}
"merge" => {
let local_library_id = local_library;
let remote_library_id = self
.remote_library
.ok_or_else(|| anyhow::anyhow!("--remote-library is required for merge action"))?;
let name = self
.name
.clone()
.ok_or_else(|| anyhow::anyhow!("--name is required for merge action"))?;
LibrarySyncAction::MergeLibraries {
local_library_id,
remote_library_id,
merged_name: name,
}
}
_ => anyhow::bail!(
"Invalid action '{}'. Supported: register-only, create-shared",
"Invalid action '{}'. Supported: share-local, join-remote, merge",
action_str
),
};

View File

@@ -326,16 +326,31 @@ async fn run_interactive_sync_setup(ctx: &Context) -> Result<LibrarySyncSetupInp
let action_idx = select(
"Select sync action",
&[
"Register existing library (sync with existing library on remote device)".to_string(),
"Create shared library (create new shared library on both devices)".to_string(),
"Share my library to remote device (create shared library from local)".to_string(),
"Join remote library (use their existing library)".to_string(),
"Merge libraries (combine two libraries) [NOT YET IMPLEMENTED]".to_string(),
],
)?;
let action = match action_idx {
0 => {
// Register existing library
// Share local library
let name = libraries[library_idx].name.clone();
println!(
"\n✓ Will share library '{}' to remote device '{}'\n",
name, remote_device.name
);
(
LibrarySyncAction::ShareLocalLibrary { library_name: name },
None,
)
}
1 => {
// Join remote library
if discovery_out.libraries.is_empty() {
anyhow::bail!("No libraries found on remote device. Use 'Create shared library' instead.");
anyhow::bail!("No libraries found on remote device. Use 'Share my library' instead.");
}
let remote_lib_choices: Vec<String> = discovery_out
@@ -349,42 +364,30 @@ async fn run_interactive_sync_setup(ctx: &Context) -> Result<LibrarySyncSetupInp
})
.collect();
let remote_lib_idx = select("Select remote library to sync with", &remote_lib_choices)?;
let remote_library_id = discovery_out.libraries[remote_lib_idx].id;
let remote_lib_idx = select("Select remote library to join", &remote_lib_choices)?;
let remote_library = &discovery_out.libraries[remote_lib_idx];
println!(
"\n✓ Will sync with remote library: {}\n",
discovery_out.libraries[remote_lib_idx].name
);
(LibrarySyncAction::RegisterOnly, Some(remote_library_id))
}
1 => {
// Create shared library - the local library selected earlier will be shared to the remote device
let name = libraries[library_idx].name.clone();
println!(
"\n✓ Will share library '{}' to remote device '{}'\n",
name, remote_device.name
"\n✓ Will join remote library: {}\n",
remote_library.name
);
(
LibrarySyncAction::CreateShared {
leader_device_id: local_device_id,
name,
LibrarySyncAction::JoinRemoteLibrary {
remote_library_id: remote_library.id,
remote_library_name: remote_library.name.clone(),
},
None,
Some(remote_library.id),
)
}
2 => {
anyhow::bail!("Library merging is not yet implemented");
}
_ => unreachable!(),
};
let leader_device_id = match &action.0 {
LibrarySyncAction::CreateShared {
leader_device_id, ..
} => *leader_device_id,
_ => local_device_id,
};
// Leader device is always local for now (deprecated concept but still in input struct)
let leader_device_id = local_device_id;
Ok(LibrarySyncSetupInput {
local_device_id,

View File

@@ -8,23 +8,26 @@ use uuid::Uuid;
#[derive(Debug, Clone, Serialize, Deserialize, Type)]
#[serde(tag = "type", rename_all = "camelCase")]
pub enum LibrarySyncAction {
/// Keep libraries separate, only register devices in each other's libraries
/// This allows Spacedrop and future selective sync without full library merge
RegisterOnly,
/// Future: Merge remote library into local (local becomes leader)
/// Share local library to remote device (creates same library with same UUID on remote)
/// This is the primary way to create a shared library
#[serde(rename_all = "camelCase")]
MergeIntoLocal { remote_library_id: Uuid },
ShareLocalLibrary { library_name: String },
/// Future: Merge local library into remote (remote becomes leader)
/// Join an existing remote library (creates same library with same UUID locally)
/// Use this when the other device has already shared their library
#[serde(rename_all = "camelCase")]
MergeIntoRemote { local_library_id: Uuid },
JoinRemoteLibrary {
remote_library_id: Uuid,
remote_library_name: String,
},
/// Future: Create new shared library (choose leader)
/// Future: Merge two different libraries into one (combines data from both)
/// Not yet implemented - requires full sync system
#[serde(rename_all = "camelCase")]
CreateShared {
leader_device_id: Uuid,
name: String,
MergeLibraries {
local_library_id: Uuid,
remote_library_id: Uuid,
merged_name: String,
},
}