diff --git a/docs/manual-checks.md b/docs/manual-checks.md
index 6d97601..46926ea 100644
--- a/docs/manual-checks.md
+++ b/docs/manual-checks.md
@@ -984,10 +984,12 @@ across that line would leave everyone else in the shared vault seeing a machine
**Failure means:** the set's write is `ChangingTheGroupOfTheChosenHosts_FilesThemAllAtOnce` again — one
command reads the whole set, so "filed one of three" has no half-gesture to hide in the way the old drag's
-payload did. The refusal, though, is asserted by **nothing automated at all**: `VaultViewModel.RegroupChosenHosts`
-counts the distinct vaults and no test raises it, so this check is the only thing between that sentence and
-silence. A panel that does open over a mixed set is the worse half — it would offer one keychain's groups
-for another keychain's machines, which is the half-filed set the refusal exists to prevent.
+payload did. The refusal is covered headlessly too:
+`RegroupingHostsChosenAcrossTwoKeychains_IsRefusedBeforeThePickerOpens` ticks a host in each of two
+keychains and asserts no picker opens and the sentence is on the status line. What is left for the eye is
+7.9's wiring, as in 7.6 — and a panel that does open over a mixed set is the worse half: it would offer one
+keychain's groups for another keychain's machines, which is the half-filed set the refusal exists to
+prevent.
### 7.7 A click still selects, and a double click still connects
diff --git a/tests/DodoSSH.Client.App.Tests/ShellFlowTests.cs b/tests/DodoSSH.Client.App.Tests/ShellFlowTests.cs
index 88887b7..898525f 100644
--- a/tests/DodoSSH.Client.App.Tests/ShellFlowTests.cs
+++ b/tests/DodoSSH.Client.App.Tests/ShellFlowTests.cs
@@ -5845,6 +5845,60 @@ public sealed class ShellFlowTests : IAsyncLifetime
Host(vault, "staging").Host.GroupId.ShouldBeNull("it was never ticked");
}
+ ///
+ /// The other refusal at the same door, and the one that needs two keychains to raise: a group is an
+ /// item of one vault, so filing a mixed set under it would leave everyone else in the shared vault
+ /// seeing a machine filed under nothing. Checked when the picker is asked for and over the whole set —
+ /// see — rather than once per host mid-write, which is
+ /// why no panel opens at all and the status line's sentence has to carry the whole explanation.
+ ///
+ [Fact]
+ public async Task RegroupingHostsChosenAcrossTwoKeychains_IsRefusedBeforeThePickerOpens()
+ {
+ await UnlockedAsync();
+
+ var vaults = shell.Vaults;
+
+ await vaults.LoadAsync(Token);
+
+ vaults.NewVaultCommand.Execute(null);
+ vaults.NewVaultName = "Platform secrets";
+
+ await vaults.CreateVaultCommand.ExecuteAsync(null);
+
+ var vault = shell.Vault!;
+ var sharedVaultId = vaults.SelectedVault!.VaultId;
+
+ await vault.LoadAsync(Token);
+
+ await AddHostAsync(vault, "prod-db");
+
+ vault.NewHostCommand.Execute(null);
+ vault.EditorSelectedVault =
+ vault.EditorVaultChoices.Single(choice => choice.VaultId == sharedVaultId);
+ vault.EditorLabel = "prod-web";
+ vault.EditorHostname = "web.internal";
+
+ await vault.SaveHostCommand.ExecuteAsync(null);
+
+ vault.ChooseHostCommand.Execute(Host(vault, "prod-db"));
+ vault.ToggleHostChoiceCommand.Execute(Host(vault, "prod-web"));
+
+ vault.RegroupChosenHostsCommand.Execute(null);
+
+ vault.IsRegroupingChosenHosts.ShouldBeFalse("a group belongs to one keychain");
+ vault.Status.ShouldStartWith("These hosts are in more than one keychain");
+ vault.IsChoosingHosts.ShouldBeTrue("the set was refused, not dissolved");
+
+ // Unticking the visitor is all it takes: the refusal is about the set, not a latch the screen has
+ // to be talked out of.
+ vault.ToggleHostChoiceCommand.Execute(Host(vault, "prod-web"));
+
+ vault.RegroupChosenHostsCommand.Execute(null);
+
+ vault.IsRegroupingChosenHosts.ShouldBeTrue(vault.Status);
+ }
+
///
/// Duplicating keeps the group and the tags, which is the whole difference between it and a copy into
/// another vault: the copy stays in the same keychain, so everything it points at is still there.