diff --git a/main.go b/main.go index 3822e27..c634376 100644 --- a/main.go +++ b/main.go @@ -2358,6 +2358,9 @@ func (u *ui) prefillAdvancedSyncRemoteFromSavedBinding() { func (u *ui) syncDialogTitle() string { if u.syncDialogPurpose == syncDialogPurposeRemoteSetup { + if _, ok := u.selectedVaultRemoteBinding(); ok { + return "Remote Sync Settings" + } return "Set Up Remote Sync" } return "Advanced Sync" @@ -2365,6 +2368,9 @@ func (u *ui) syncDialogTitle() string { func (u *ui) syncDialogDescription() string { if u.syncDialogPurpose == syncDialogPurposeRemoteSetup { + if _, ok := u.selectedVaultRemoteBinding(); ok { + return "Review or change this vault's saved WebDAV target, credentials, and sync mode." + } return "Send this local vault to a WebDAV target, then use that target for future sync." } return "Pick direction, choose the other vault, and then run the merge. Saved source and direction defaults now live in Settings." @@ -2372,6 +2378,9 @@ func (u *ui) syncDialogDescription() string { func (u *ui) syncDialogConfirmButtonLabel() string { if u.syncDialogPurpose == syncDialogPurposeRemoteSetup { + if _, ok := u.selectedVaultRemoteBinding(); ok { + return "Save Remote Sync Settings" + } return "Set Up Remote Sync" } return "Synchronize" @@ -2625,6 +2634,18 @@ func (u *ui) directRemoteSyncShortcutLabel() string { return "Use Remote Sync" } +func (u *ui) shouldShowRemoteSyncSettingsShortcut() bool { + if !u.hasOpenVault() || u.isVaultLocked() || u.state.Section != appstate.SectionEntries { + return false + } + _, ok := u.selectedVaultRemoteBinding() + return ok +} + +func (u *ui) remoteSyncSettingsShortcutLabel() string { + return "Remote Sync Settings" +} + func (u *ui) shouldShowRemoteSyncSetupShortcut() bool { if !u.hasOpenVault() || u.isVaultLocked() || u.state.Section != appstate.SectionEntries { return false @@ -6836,7 +6857,7 @@ func (u *ui) pathBar(gtx layout.Context) layout.Dimensions { return children }()...) } - if !u.shouldShowDirectRemoteSyncShortcut() && !u.shouldShowRemoteSyncSetupShortcut() { + if !u.shouldShowDirectRemoteSyncShortcut() && !u.shouldShowRemoteSyncSetupShortcut() && !u.shouldShowRemoteSyncSettingsShortcut() { return crumbBar(gtx) } children := []layout.FlexChild{ @@ -6856,6 +6877,14 @@ func (u *ui) pathBar(gtx layout.Context) layout.Dimensions { return tonedButton(gtx, u.theme, &u.useSavedAdvancedSyncRemote, u.remoteSyncSetupShortcutLabel()) })) } + if u.shouldShowRemoteSyncSettingsShortcut() { + if u.shouldShowDirectRemoteSyncShortcut() || u.shouldShowRemoteSyncSetupShortcut() { + children = append(children, layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout)) + } + children = append(children, layout.Rigid(func(gtx layout.Context) layout.Dimensions { + return tonedButton(gtx, u.theme, &u.useSavedAdvancedSyncRemote, u.remoteSyncSettingsShortcutLabel()) + })) + } return layout.Flex{Axis: layout.Vertical}.Layout(gtx, children...) } diff --git a/main_test.go b/main_test.go index 5705c22..b95bbed 100644 --- a/main_test.go +++ b/main_test.go @@ -5869,6 +5869,41 @@ func TestUIRemoteSyncSetupShortcutLabelUsesClearLanguage(t *testing.T) { } } +func TestUIShouldShowRemoteSyncSettingsShortcutForSavedBinding(t *testing.T) { + t.Parallel() + + u := newUIWithModel("desktop", vault.Model{ + Entries: []vault.Entry{{ + ID: "remote-creds-1", + Title: "WebDAV Sign-In", + Username: "tjulian", + Path: []string{"Crew", "Internet"}, + }}, + RemoteProfiles: []vault.RemoteProfile{{ + ID: "family-webdav", + Name: "Family Vault", + Backend: vault.RemoteBackendWebDAV, + BaseURL: "https://dav.example.invalid/remote.php/dav", + Path: "files/family/keepass.kdbx", + }}, + }) + u.state.Section = appstate.SectionEntries + + if !u.shouldShowRemoteSyncSettingsShortcut() { + t.Fatal("shouldShowRemoteSyncSettingsShortcut() = false, want true when a saved binding exists") + } +} + +func TestUIRemoteSyncSettingsShortcutLabelUsesClearLanguage(t *testing.T) { + t.Parallel() + + u := newUIWithSession("desktop", &session.Manager{}) + + if got := u.remoteSyncSettingsShortcutLabel(); got != "Remote Sync Settings" { + t.Fatalf("remoteSyncSettingsShortcutLabel() = %q, want Remote Sync Settings", got) + } +} + func TestUIOpenRemoteSyncSetupDialogPrefillsCurrentVaultSetupFlow(t *testing.T) { t.Parallel() @@ -5937,6 +5972,54 @@ func TestUISelectedLocalVaultRemoteSyncSummaryMentionsSetup(t *testing.T) { } } +func TestUISelectedLocalVaultRemoteSyncSummaryMentionsAutomaticSync(t *testing.T) { + t.Parallel() + + dir := t.TempDir() + u := newUIWithSession("desktop", &session.Manager{}, statePaths{ + DefaultSaveAsPath: filepath.Join(dir, "vault.kdbx"), + RecentVaultsPath: filepath.Join(dir, "recent-vaults.json"), + RecentRemotesPath: filepath.Join(dir, "recent-remotes.json"), + UIPreferencesPath: filepath.Join(dir, "ui-prefs.json"), + }) + u.recentRemotes = []recentRemoteRecord{{ + BaseURL: "https://dav.example.invalid/remote.php/dav", + Path: "files/family/keepass.kdbx", + LocalVaultPath: "/vaults/family.kdbx", + RemoteProfileID: "family-webdav", + CredentialEntryID: "remote-creds-1", + SyncMode: string(appstate.SyncModeAutomaticOnOpenSave), + }} + + if got := u.selectedLocalVaultRemoteSyncSummary("/vaults/family.kdbx"); got != "Saved remote sync target: keepass.kdbx · dav.example.invalid · Syncs automatically on open and save." { + t.Fatalf("selectedLocalVaultRemoteSyncSummary() = %q, want automatic sync guidance", got) + } +} + +func TestUISelectedLocalVaultRemoteSyncSummaryMentionsManualSync(t *testing.T) { + t.Parallel() + + dir := t.TempDir() + u := newUIWithSession("desktop", &session.Manager{}, statePaths{ + DefaultSaveAsPath: filepath.Join(dir, "vault.kdbx"), + RecentVaultsPath: filepath.Join(dir, "recent-vaults.json"), + RecentRemotesPath: filepath.Join(dir, "recent-remotes.json"), + UIPreferencesPath: filepath.Join(dir, "ui-prefs.json"), + }) + u.recentRemotes = []recentRemoteRecord{{ + BaseURL: "https://dav.example.invalid/remote.php/dav", + Path: "files/family/keepass.kdbx", + LocalVaultPath: "/vaults/family.kdbx", + RemoteProfileID: "family-webdav", + CredentialEntryID: "remote-creds-1", + SyncMode: string(appstate.SyncModeManual), + }} + + if got := u.selectedLocalVaultRemoteSyncSummary("/vaults/family.kdbx"); got != "Saved remote sync target: keepass.kdbx · dav.example.invalid · Sync manually when you choose Use Remote Sync." { + t.Fatalf("selectedLocalVaultRemoteSyncSummary() = %q, want manual sync guidance", got) + } +} + func TestUISyncDialogUsesRemoteSetupCopy(t *testing.T) { t.Parallel() @@ -5967,6 +6050,52 @@ func TestUISyncDialogUsesRemoteSetupCopy(t *testing.T) { } } +func TestUISyncDialogUsesRemoteSettingsCopyForExistingBinding(t *testing.T) { + t.Parallel() + + u := newUIWithModel("desktop", vault.Model{ + Entries: []vault.Entry{{ + ID: "remote-creds-1", + Title: "WebDAV Sign-In", + Username: "tjulian", + Password: "token-1", + Path: []string{"Crew", "Internet"}, + }}, + RemoteProfiles: []vault.RemoteProfile{{ + ID: "family-webdav", + Name: "Family Vault", + Backend: vault.RemoteBackendWebDAV, + BaseURL: "https://dav.example.invalid/remote.php/dav", + Path: "files/family/keepass.kdbx", + }}, + }) + u.state.Section = appstate.SectionEntries + u.vaultPath.SetText("/vaults/family.kdbx") + u.recentRemotes = []recentRemoteRecord{{ + BaseURL: "https://dav.example.invalid/remote.php/dav", + Path: "files/family/keepass.kdbx", + LocalVaultPath: "/vaults/family.kdbx", + RemoteProfileID: "family-webdav", + CredentialEntryID: "remote-creds-1", + SyncMode: string(appstate.SyncModeManual), + }} + + u.openRemoteSyncSetupDialog() + + if got := u.syncDialogTitle(); got != "Remote Sync Settings" { + t.Fatalf("syncDialogTitle() = %q, want Remote Sync Settings", got) + } + if got := u.syncDialogDescription(); got != "Review or change this vault's saved WebDAV target, credentials, and sync mode." { + t.Fatalf("syncDialogDescription() = %q, want settings guidance", got) + } + if got := u.syncDialogConfirmButtonLabel(); got != "Save Remote Sync Settings" { + t.Fatalf("syncDialogConfirmButtonLabel() = %q, want Save Remote Sync Settings", got) + } + if u.syncSetupAutomatic.Value { + t.Fatal("syncSetupAutomatic.Value = true, want false for an existing manual binding") + } +} + func TestUISyncDialogUsesAdvancedCopy(t *testing.T) { t.Parallel() diff --git a/ui_forms.go b/ui_forms.go index 5149dc6..20f1264 100644 --- a/ui_forms.go +++ b/ui_forms.go @@ -332,7 +332,11 @@ func (u *ui) selectedLocalVaultCard(gtx layout.Context, path string) layout.Dime func (u *ui) selectedLocalVaultRemoteSyncSummary(path string) string { if record, ok := u.boundRecentRemoteForLocalVault(path); ok { - return "Saved remote sync target: " + friendlyRecentRemoteLabel(record) + summary := "Saved remote sync target: " + friendlyRecentRemoteLabel(record) + if normalizeUISyncMode(appstate.SyncMode(record.SyncMode)) == appstate.SyncModeAutomaticOnOpenSave { + return summary + " · Syncs automatically on open and save." + } + return summary + " · Sync manually when you choose Use Remote Sync." } return "Open this vault to set up a WebDAV sync target for it." }