diff --git a/README.md b/README.md
index 5a5753b..192c881 100644
--- a/README.md
+++ b/README.md
@@ -148,7 +148,9 @@ with the matching package references in the tool, not in the library:
```
-Only toggles given a `persistAs` key are remembered, and only the tray reads them: a command line run
+Only toggles given a `persistAs` key are remembered, and each needs a key of its own: `Toggle` throws
+`ArgumentException` for a blank key or one another toggle already uses, since two toggles sharing a key
+would both be restored to whichever was clicked last. Only the tray reads them: a command line run
says what it wants, so letting a remembered setting override an argument would be surprising. Within
the tray, what was typed this time beats what the tray was left set to last time.
diff --git a/TrayApp.Test/TrayAppBuilderTests.cs b/TrayApp.Test/TrayAppBuilderTests.cs
index 44607aa..e020eda 100644
--- a/TrayApp.Test/TrayAppBuilderTests.cs
+++ b/TrayApp.Test/TrayAppBuilderTests.cs
@@ -72,6 +72,46 @@ public void Flag_WithAnAliasAnotherOptionTook_Throws()
Assert.AreEqual(1, builder.Build().Options.Count);
}
+ [TestMethod]
+ public void Toggle_WithAPersistAsKeyAnotherToggleTook_Throws()
+ {
+ // Both toggles would read and write one remembered value, so the next launch restores both to it.
+ TrayAppBuilder builder = TrayAppBuilder.Create("demo")
+ .Toggle("Keep awake", () => false, _ => { }, persistAs: "awake");
+
+ ArgumentException ex = Assert.ThrowsExactly(
+ () => builder.Toggle("Keep display on", () => false, _ => { }, persistAs: "awake"));
+
+ Assert.Contains("'awake'", ex.Message, StringComparison.Ordinal);
+ Assert.AreEqual("persistAs", ex.ParamName);
+ Assert.HasCount(1, builder.Build().Toggles);
+ }
+
+ [TestMethod]
+ public void Toggle_WithPersistAsKeysThatDifferOnlyInCase_KeepsBoth()
+ {
+ // The store compares keys ordinally, so these are two remembered values.
+ TrayAppDefinition app = TrayAppBuilder.Create("demo")
+ .Toggle("Keep awake", () => false, _ => { }, persistAs: "awake")
+ .Toggle("Keep display on", () => false, _ => { }, persistAs: "Awake")
+ .Toggle("Not remembered", () => false, _ => { })
+ .Toggle("Not remembered either", () => false, _ => { })
+ .Build();
+
+ Assert.HasCount(4, app.Toggles);
+ }
+
+ [TestMethod]
+ [DataRow("")]
+ [DataRow(" ")]
+ public void Toggle_WithABlankPersistAsKey_Throws(string key)
+ {
+ ArgumentException ex = Assert.ThrowsExactly(
+ () => TrayAppBuilder.Create("demo").Toggle("Keep awake", () => false, _ => { }, persistAs: key));
+
+ Assert.AreEqual("persistAs", ex.ParamName);
+ }
+
[TestMethod]
public void Build_CollectsItemsTogglesAndOptions()
{
diff --git a/TrayApp/TrayAppBuilder.cs b/TrayApp/TrayAppBuilder.cs
index 73cf5ca..fdbc14c 100644
--- a/TrayApp/TrayAppBuilder.cs
+++ b/TrayApp/TrayAppBuilder.cs
@@ -205,9 +205,14 @@ public TrayAppBuilder ActiveWhen(Func predicate)
///
/// The name to remember this toggle under between runs, or not to remember it.
/// Only meaningful alongside .
+ /// Each remembered toggle needs a key of its own, compared ordinally: two toggles sharing one would read and
+ /// write a single remembered value, and the next launch would restore both to it.
///
/// The builder, for chaining.
/// , , or is .
+ ///
+ /// is empty or white space, or another toggle already uses it.
+ ///
public TrayAppBuilder Toggle(
string label,
Func getState,
@@ -215,6 +220,19 @@ public TrayAppBuilder Toggle(
Func? isEnabled = null,
string? persistAs = null)
{
+ if (persistAs is not null)
+ {
+ if (string.IsNullOrWhiteSpace(persistAs))
+ {
+ throw new ArgumentException("A preference key cannot be empty or white space.", nameof(persistAs));
+ }
+
+ if (toggles.Any(existing => string.Equals(existing.PreferenceKey, persistAs, StringComparison.Ordinal)))
+ {
+ throw new ArgumentException($"'{persistAs}' is already remembered by another toggle.", nameof(persistAs));
+ }
+ }
+
TrayToggleItem toggle = new(label, getState, setState, isEnabled, persistAs);
items.Add(toggle);
toggles.Add(toggle);