Public Access
Merge branch 'claude/edit-screen-refresh-items-63a808'
This commit is contained in:
@@ -2629,10 +2629,19 @@ internal sealed partial class VaultViewModel(
|
||||
/// What the authentication picker offers: a typed password, then every key, then every credential.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Rebuilt when the editor opens rather than kept in step with the two lists. A background sync could pull
|
||||
/// a new key while a host is being edited, and having the picker's contents change under the user mid-edit
|
||||
/// is worse than the list being a minute stale — only one editor may be open at a time, so the only way to
|
||||
/// add a key or a credential is to close this one anyway.
|
||||
/// <para>
|
||||
/// Filled when the editor opens and kept in step with the two lists from then on, by
|
||||
/// <see cref="RefreshOpenEditors"/>. It used to be the snapshot alone, on the grounds that only one editor
|
||||
/// could be open at a time and so the only way to add a key was to close this one — which stopped being
|
||||
/// true when <see cref="AHostEditorIsInTheWay"/> was split from <see cref="AVaultEditorIsInTheWay"/>. A
|
||||
/// host editor now sits open on the Hosts screen while a key is added on the Keychain screen, and a
|
||||
/// picker that did not notice left the user cancelling an editor to see the key they had just made.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// What the old note was protecting against is real, and is why the refill restores the selection by id
|
||||
/// rather than rebuilding from the stored host: entries appear and disappear under the user, but what
|
||||
/// they have chosen does not move.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
internal ObservableCollection<AuthenticationChoice> EditorAuthenticationChoices { get; } = [];
|
||||
|
||||
@@ -3529,9 +3538,86 @@ internal sealed partial class VaultViewModel(
|
||||
// After all four lists, because the table is a projection of three of them.
|
||||
RebuildVaultItems();
|
||||
|
||||
// Last of the rebuilds, because every picker an open editor holds is drawn from one of the lists
|
||||
// above and would otherwise be showing the vault as it was when that editor opened.
|
||||
RefreshOpenEditors();
|
||||
|
||||
await LoadConflictsAsync(cancellationToken).ConfigureAwait(true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Refills the pickers of whichever editor is open, so a key or a vault that has just arrived shows in it.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// The pickers are snapshots — see <see cref="EditorAuthenticationChoices"/> — and taking one when the
|
||||
/// editor opens was correct while nothing could change a list without closing it first. Nothing about
|
||||
/// that holds any more: the host editor lives on the Hosts screen and the keychain's editors live on the
|
||||
/// Vault screen, so a key, a credential, a tag, a group or a whole vault can be added with a host editor
|
||||
/// standing open behind it. Every one of them then failed to appear in the picker that exists to offer
|
||||
/// it, and the only way to see it was to abandon the edit and start again.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Called from the one reload rather than from each of the twenty-odd places that write to the vault,
|
||||
/// which is what makes a sync count as well as a save: a key pulled from another machine reaches the
|
||||
/// open editor by the same path a key typed here does.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// <b>Selections are carried across by id, and every typed field is left alone.</b> The refill has to be
|
||||
/// invisible to somebody halfway through a form — an editor that reset its own bindings because a
|
||||
/// background sync landed would be a worse bug than the stale list it fixes. So each picker is rebuilt
|
||||
/// and then put back onto what it was already showing, including the placeholder entries that stand for
|
||||
/// a binding whose target has gone: it is the same restore-by-id the open path does, from the editor's
|
||||
/// current selection rather than from the stored item, because for as long as the editor is open the two
|
||||
/// deliberately differ.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
private void RefreshOpenEditors()
|
||||
{
|
||||
if (IsEditing)
|
||||
{
|
||||
// Read before anything is cleared. Rebuilding a bound collection makes the control null its own
|
||||
// selection and write that back, so by the time the last picker is refilled these properties no
|
||||
// longer say what the user chose.
|
||||
var authentication = EditorSelectedAuthentication;
|
||||
var groupId = EditorSelectedGroup?.EntityId;
|
||||
|
||||
BuildTagChoices();
|
||||
|
||||
// The same order the two commands that open this editor use: the vault picker first because a
|
||||
// group belongs to one vault, then the groups, then the bindings — which offer "inherit from
|
||||
// group" only where the group picker has landed on something.
|
||||
BuildEditorVaultChoices(editingHostVaultId);
|
||||
|
||||
// The selection as it stands, not filtered through GroupInEditingVault: a group that has gone
|
||||
// keeps its placeholder here for the reason it does on the open path, so that a sync arriving
|
||||
// mid-edit cannot unfile the host when the form is saved.
|
||||
BuildGroupChoices(groupId);
|
||||
|
||||
BuildAuthenticationChoices(
|
||||
authentication?.Kind == AuthenticationKind.SshKey ? authentication.EntityId : null,
|
||||
authentication?.Kind == AuthenticationKind.Credential ? authentication.EntityId : null,
|
||||
asksForPassword: authentication?.Kind == AuthenticationKind.Typed,
|
||||
grouped: EditorSelectedGroup?.EntityId is not null);
|
||||
}
|
||||
|
||||
if (IsEditingGroup)
|
||||
{
|
||||
var authentication = GroupEditorSelectedAuthentication;
|
||||
var parentId = GroupEditorSelectedParent?.EntityId;
|
||||
|
||||
BuildGroupEditorVaultChoices(GroupEditorVaultId);
|
||||
|
||||
// Guid.Empty while creating, which is what EditingGroupId being null means and is the same
|
||||
// stand-in ClearGroupEditor uses: a group that does not exist yet cannot be its own parent.
|
||||
BuildGroupParentChoices(EditingGroupId ?? Guid.Empty, parentId);
|
||||
|
||||
BuildGroupAuthenticationChoices(
|
||||
authentication?.Kind == AuthenticationKind.SshKey ? authentication.EntityId : null,
|
||||
authentication?.Kind == AuthenticationKind.Credential ? authentication.EntityId : null);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Redraws every list from the vault, without saying anything about it.</summary>
|
||||
/// <remarks>
|
||||
/// For the two things that change which vaults exist or which are drawn without going through this type
|
||||
|
||||
Reference in New Issue
Block a user