Allow a notification keyword to start with a dot (#34574)

Both notification settings tabs stored a keyword under a push rule whose id
was the keyword itself. Homeservers reserve the rule ids beginning with a
dot for the rules they define, and Synapse refuses to create any other, so
saving a keyword such as "...push complete" failed outright and the whole
save was reported as an error.

The rule id is an internal name that the user never sees — both tabs list
keywords by the rule's pattern — so it is the id that gives way. It now
drops the leading dots while the pattern keeps the keyword exactly as it
was typed, which is what is matched against messages. Two keywords can
want the same id that way, so a number is appended when one is taken,
which also keeps "banana" from overwriting the rule for ".banana".

Tests: the shared reconciler and the older tab both store a dotted keyword
under an accepted id, and two keywords differing only by a leading dot get
an id each.
This commit is contained in:
hayyaksi
2026-08-10 11:24:15 +00:00
committed by GitHub
parent 127ae519c5
commit 4807ff1568
5 changed files with 81 additions and 3 deletions
@@ -215,4 +215,29 @@ describe("NotificationSettings", () => {
expect(pendingChanges.deleted).toHaveLength(0);
expect(pendingChanges.updated).toHaveLength(0);
});
it("stores a keyword that starts with a dot under an id the server will accept", async () => {
const pushRules = (await import("./pushrules_default.json")) as IPushRules;
const model = { ...DefaultNotificationSettings, keywords: ["...push complete"] };
const pendingChanges = reconcileNotificationSettings(pushRules, model, false);
expect(pendingChanges.added).toEqual([
expect.objectContaining({
kind: PushRuleKind.ContentSpecific,
rule_id: "push complete",
pattern: "...push complete",
}),
]);
});
it("keeps the ids of two keywords that differ only by a leading dot apart", async () => {
const pushRules = (await import("./pushrules_default.json")) as IPushRules;
const model = { ...DefaultNotificationSettings, keywords: ["banana", ".banana"] };
const pendingChanges = reconcileNotificationSettings(pushRules, model, false);
expect(pendingChanges.added.map((rule) => rule.rule_id)).toEqual(["banana", "banana-2"]);
expect(pendingChanges.added.map((rule) => rule.pattern)).toEqual(["banana", ".banana"]);
});
});