From f1f866050eeeea6c4a855a8c4a8bca5dccb3372c Mon Sep 17 00:00:00 2001 From: jochen Date: Tue, 13 Oct 2026 16:54:51 +0200 Subject: [PATCH] Revert the showmissed lock of #277: the operator wants no special handling in betterbird's config (hq issue 640) The fix is the approved settings layer (proposal s09bf8c99da7ac8b9: calendar.alarms.showmissed false), pushed to g14 and shanks. --- modules/betterbird/README.md | 22 +----- .../cmd/betterbird-tools/betterbird.go | 3 - .../cmd/betterbird-tools/config_test.go | 8 +- .../cmd/betterbird-tools/install.go | 69 +---------------- .../cmd/betterbird-tools/install_test.go | 76 ------------------- .../install/distribution/policies.json | 4 - 6 files changed, 10 insertions(+), 172 deletions(-) diff --git a/modules/betterbird/README.md b/modules/betterbird/README.md index 3b814646..4e2a2acd 100644 --- a/modules/betterbird/README.md +++ b/modules/betterbird/README.md @@ -97,7 +97,7 @@ start; `prefs.js` wins over a default. value true, false, a whole number or a string. The node-engine merges the layers key by key, so a node's layer overrides one preference, or takes one out with `null`. The operator's setup approved on 2026-10-12 (compact density, 14-point text, the vertical view with multi-line cards and threads, no new-mail pop-up, sound or tray -icon, the calendar's day from 09:00 to 20:00, reminders missed while asleep shown (since locked off: issue 640), chat off, remote content +icon, the calendar's day from 09:00 to 20:00, reminders missed while asleep shown, chat off, remote content blocked, Do Not Track; 21 preferences) moved from the catalogue's defaults file into the mesh-wide layer of `prefs`, set on the operator's Approve. @@ -109,24 +109,10 @@ desktop node writes the map's text into the old build's user.js, and `betterbird refused until the push. **Locked** (the policy): Betterbird's updater off (`DisableAppUpdate`); telemetry off (`DisableTelemetry`); -add-ons change only through the mesh's pins (`ExtensionUpdate` false); and four preferences locked off: +add-ons change only through the mesh's pins (`ExtensionUpdate` false); and three preferences locked off: `mail.shell.checkDefaultClient` (the ask to be the default client), `mail.inappnotifications.enabled` (the -vendor's in-app notices), `mailnews.start_page.enabled` (the start page, which otherwise loads from the -vendor at every start), and `calendar.alarms.showmissed` (the reminders of meetings that have passed, below). - -**A passed meeting's reminder is not shown again** (novox/hq issue 640). With `calendar.alarms.showmissed` on, -Betterbird's alarm service fires, at every load of a calendar (each start, each refresh, and each time Owl -registers its calendar after signing in), every reminder of the past month whose item carries no later -acknowledgement (`X-MOZ-LASTACK`). Off, Betterbird's own default, a past reminder fires only while its meeting -has not ended. A dismissal is that acknowledgement, and Owl never sends it to Exchange (its changes carry the -reminder's offset alone): it lives only in Betterbird's local calendar cache, which Owl empties when it -resynchronises a calendar from scratch. With the pass on, Exchange meetings of the past month came back about -every ten minutes on g14 from the start that placed the setting's `true`, Dismiss or not. So the policy locks the -pass off, whatever the setting `prefs` or a profile says; a setting's value for it has no effect (`betterbird_config` -shows the lock). What it costs: a reminder that fell due while the laptop slept is not shown once the meeting -is over; one for a meeting still running is. `betterbird_check` says when the running Betterbird still has the -pass on beside an Owl calendar, with the remedy (the module's push, or `betterbird_restart` once the policy is -placed). +vendor's in-app notices), and `mailnews.start_page.enabled` (the start page, which otherwise loads from the +vendor at every start). **Kept at Betterbird's default** (`// keeps Betterbird's default: ` in `mesh-defaults.js`): the theme, system colours, always-underlined links, manual spam marking, and the composer's attachment pane. A value the diff --git a/modules/betterbird/cmd/betterbird-tools/betterbird.go b/modules/betterbird/cmd/betterbird-tools/betterbird.go index 900d3c5d..a76148e3 100644 --- a/modules/betterbird/cmd/betterbird-tools/betterbird.go +++ b/modules/betterbird/cmd/betterbird-tools/betterbird.go @@ -981,9 +981,6 @@ func (m *Machine) Check() (CheckAnswer, error) { "take them out of install/defaults-pref/mesh-defaults.js in a reviewed catalogue change: an account is declared in the setting accounts") } } - if what, do := m.missedReminders(profilePath); what != "" { - add(what, do) - } if why := m.defaultClientAsk(profilePath); why != "" && in.From != "not installed" { add("Betterbird asks at its start to be the default mail client: "+why, "push the module to this node: its policy locks "+defaultClientPref+" off (novox/hq issue 345)") diff --git a/modules/betterbird/cmd/betterbird-tools/config_test.go b/modules/betterbird/cmd/betterbird-tools/config_test.go index da612459..c92a7288 100644 --- a/modules/betterbird/cmd/betterbird-tools/config_test.go +++ b/modules/betterbird/cmd/betterbird-tools/config_test.go @@ -249,8 +249,8 @@ func TestTheConfigShowsTheLayersAndTheDrift(t *testing.T) { f.withDefaults() f.betterbirdsOwn() links(t, nil) - f.declare(`user_pref("mail.uifontsize", 16); user_pref("mail.biff.show_alert", false); user_pref("calendar.alarms.playsound", false);`) - f.write(profileDir+"/user.js", mark+")\n"+`user_pref("mail.uifontsize", 16);`+"\n"+`user_pref("calendar.alarms.playsound", false);`+"\n") + f.declare(`user_pref("mail.uifontsize", 16); user_pref("mail.biff.show_alert", false); user_pref("calendar.alarms.showmissed", true);`) + f.write(profileDir+"/user.js", mark+")\n"+`user_pref("mail.uifontsize", 16);`+"\n"+`user_pref("calendar.alarms.showmissed", true);`+"\n") f.write(profileDir+"/prefs.js", "// Mozilla User Preferences\n"+ `user_pref("extensions.activeThemeID", "thunderbird-compact-dark@mozilla.org");`+"\n"+ `user_pref("mail.uifontsize", 12);`+"\n"+ @@ -266,7 +266,7 @@ func TestTheConfigShowsTheLayersAndTheDrift(t *testing.T) { for _, p := range c.Prefs { by[p.Name] = p } - if len(c.Prefs) != len(lockedOff)+3+len(shippedKeepsWant) || !strings.HasPrefix(c.Defaults, "placed") { + if len(c.Prefs) != 3+3+len(shippedKeepsWant) || !strings.HasPrefix(c.Defaults, "placed") { t.Fatalf("every lock, declared preference and kept default: %d, %s", len(c.Prefs), c.Defaults) } if !reflect.DeepEqual(c.Drifting, []string{"extensions.activeThemeID", "mail.spam.manualMark", "mail.biff.show_alert"}) { @@ -293,7 +293,7 @@ func TestTheConfigShowsTheLayersAndTheDrift(t *testing.T) { if p := by["mailnews.start_page.enabled"]; p.Layer != "locked" || p.Effective != "false" || p.Drift != "" { t.Errorf("a lock wins over the profile: %+v", p) } - if p := by["calendar.alarms.playsound"]; p.Effective != "false" || p.Layer != "declared" { + if p := by["calendar.alarms.showmissed"]; p.Effective != "true" || p.Layer != "declared" { t.Errorf("%+v", p) } // T5: the check says the drift, with both remedies. diff --git a/modules/betterbird/cmd/betterbird-tools/install.go b/modules/betterbird/cmd/betterbird-tools/install.go index ba71bc0a..e9826e07 100644 --- a/modules/betterbird/cmd/betterbird-tools/install.go +++ b/modules/betterbird/cmd/betterbird-tools/install.go @@ -18,7 +18,6 @@ import ( "fmt" "io" "net/http" - "os" "path/filepath" "strings" "time" @@ -173,8 +172,8 @@ func (m *Machine) policyState() string { return "placed, and " + why } return "placed: Betterbird's updater, its telemetry and add-on updates off, Owl installed into every profile from " + - "the mesh's xpi with its updates off, and the ask to be the default mail client, the vendor's in-app notices, " + - "the start page and the reminders of meetings that have passed locked off" + "the mesh's xpi with its updates off, and the ask to be the default mail client, the vendor's in-app notices " + + "and the start page locked off" } // lockedOff are the preferences the module's policy locks to false (novox/hq issue 345), each with what it is. @@ -182,70 +181,6 @@ var lockedOff = []struct{ Name, What string }{ {defaultClientPref, "the ask to be the default mail client"}, {"mail.inappnotifications.enabled", "the vendor's in-app notices"}, {"mailnews.start_page.enabled", "the start page, loaded from the vendor at every start"}, - {missedPref, "the reminders of meetings that have passed (novox/hq issue 640)"}, -} - -// missedPref is Betterbird's pass over missed reminders. With it on, the calendar's alarm service fires, at -// every load of a calendar (each start, each refresh, each time Owl registers its calendar after signing in), -// every reminder of the past month whose item carries no later acknowledgement (X-MOZ-LASTACK); with it off -// (Betterbird's own default), a past reminder fires only while its meeting has not ended. Owl never sends a -// dismissal to Exchange (its changes carry the reminder's offset alone) and keeps it only in Betterbird's local -// cache, which a resynchronisation from scratch empties: a passed meeting's reminder then comes back at every -// reload, Dismiss or not (novox/hq issue 640). The policy locks it off, so no setting and no profile can turn the -// pass back on; betterbird_check says when the running Betterbird still has it on beside an Owl calendar. -const missedPref = "calendar.alarms.showmissed" - -// missedReminders says why the running Betterbird shows a passed meeting's reminder again at each reload of an -// Owl calendar, with the remedy, or two empty strings: Betterbird's pass over missed reminders on (the profile's -// user.js, which wins at the start, else its prefs.js; Betterbird's default is off) while the profile holds an -// Owl calendar and the placed policy did not lock it off before Betterbird started. -func (m *Machine) missedReminders(profile string) (string, string) { - if profile == "" { - return "", "" - } - prefs := mergedPrefs(profile) - owl := 0 - for _, c := range profileCalendars(prefs) { - if c.Kind == "owl" { - owl++ - } - } - if owl == 0 { - return "", "" - } - value, from := "false", "Betterbird's default" - for _, f := range []string{"user.js", "prefs.js"} { - b, err := readBounded(filepath.Join(profile, f)) - if err != nil { - continue - } - if v, ok := statementsOf(b)[missedPref]; ok { - value, from = v, "the profile's "+f - break - } - } - locked := m.locks()[missedPref] == "false" - ps := m.mainProcs() - if locked { - if len(ps) == 0 { - return "", "" - } - st, err := os.Stat(m.path(policyFile)) - t, terr := time.Parse(time.RFC3339, ps[0].Since) - if err != nil || terr != nil || !st.ModTime().After(t) { - return "", "" - } - } - if value != "true" || len(ps) == 0 { - return "", "" - } - what := fmt.Sprintf("Betterbird shows the reminders of meetings that have passed again at every reload of its %d Owl "+ - "calendar(s): %s is true (%s), and Owl keeps a dismissal only in Betterbird's local cache, never on the Exchange "+ - "server (novox/hq issue 640)", owl, missedPref, from) - if locked { - return what, "betterbird_restart: the module's policy locks " + missedPref + " off, and the running Betterbird started before it was placed" - } - return what, "push the module to this node: its policy locks " + missedPref + " off; then betterbird_restart" } // policyProblem is everything the policy fails to say of what ADR 0316 and issue 345 ask, or "". diff --git a/modules/betterbird/cmd/betterbird-tools/install_test.go b/modules/betterbird/cmd/betterbird-tools/install_test.go index 2a80c2ab..ffbb2884 100644 --- a/modules/betterbird/cmd/betterbird-tools/install_test.go +++ b/modules/betterbird/cmd/betterbird-tools/install_test.go @@ -229,79 +229,3 @@ func mustRead(t *testing.T, f *fake, p string) []byte { } return raw } - -// owlCalendar is an Owl calendar in the profile's prefs.js, as Owl registers its account's calendar. -func (f *fake) owlCalendar(extra string) { - f.write(profileDir+"/prefs.js", "// Mozilla User Preferences\n"+ - `user_pref("calendar.registry.45b5.type", "owl");`+"\n"+ - `user_pref("calendar.registry.45b5.uri", "owl://someone%40example%2Eorg@outlook.example.org/");`+"\n"+ - `user_pref("calendar.registry.45b5.name", "someone@example.org");`+"\n"+extra) -} - -// A passed meeting's reminder shows at most once: the policy locks Betterbird's pass over missed reminders off, -// whatever the setting prefs or the profile say, because Owl keeps a dismissal only in Betterbird's local cache -// and the pass fires every unacknowledged reminder of the past month again at each reload (novox/hq issue 640). -func TestAPassedMeetingsReminderIsNotShownAgainAtEachReloadOfAnOwlCalendar(t *testing.T) { - raw, err := os.ReadFile(filepath.Join("..", "..", "install", "distribution", "policies.json")) - if err != nil { - t.Fatal(err) - } - if p := policyPrefsOf(raw)[missedPref]; p.Value != "false" || !p.Locked { - t.Fatalf("the module's policy locks %s off: %+v", missedPref, p) - } - before := strings.Replace(string(raw), `"calendar.alarms.showmissed"`, `"calendar.alarms.unrelated"`, 1) - if why := policyProblem([]byte(before)); !strings.Contains(why, missedPref) { - t.Fatalf("a policy that leaves the missed reminders on is said: %q", why) - } - - f := newFake(t) - f.betterbird() - f.placedByTheMesh() - links(t, nil) - nag := func() Finding { - t.Helper() - c, err := f.Check() - if err != nil { - t.Fatal(err) - } - for _, x := range c.Findings { - if strings.Contains(x.What, "Owl calendar") { - return x - } - } - return Finding{} - } - // g14 on 2026-10-13: the module's user.js turns the pass on, an Owl calendar is in the profile, and the - // placed policy does not lock it. - f.write(policyFile, before) - f.owlCalendar("") - f.write(profileDir+"/user.js", mark+")\n"+`user_pref("calendar.alarms.showmissed", true);`+"\n") - if x := nag(); !strings.Contains(x.What, "1 Owl calendar") || !strings.Contains(x.What, "user.js") || - !strings.Contains(x.Do, "push the module") { - t.Fatalf("the nag is a finding with its remedy: %+v", x) - } - // The policy placed after the running Betterbird started: in effect from its next start. - f.write(policyFile, string(raw)) - later := time.Unix(1_900_000_000, 0) - os.Chtimes(filepath.Join(f.Root, policyFile), later, later) - if x := nag(); !strings.Contains(x.Do, "betterbird_restart") || strings.Contains(x.Do, "push") { - t.Fatalf("a lock placed after the start waits for a restart: %+v", x) - } - // The lock in effect: nothing the profile says turns the pass back on. - f.write(policyFile, string(raw)) - f.owlCalendar(`user_pref("calendar.alarms.showmissed", true);` + "\n") - if x := nag(); x.What != "" { - t.Fatalf("the lock holds whatever the profile says: %+v", x) - } - // Without the lock, the pass off is no nag, and neither is the pass on without an Owl calendar. - f.write(policyFile, before) - f.write(profileDir+"/user.js", mark+")\n"+`user_pref("calendar.alarms.showmissed", false);`+"\n") - if x := nag(); x.What != "" { - t.Fatalf("the pass off: %+v", x) - } - f.write(profileDir+"/user.js", mark+")\n"+`user_pref("calendar.alarms.showmissed", true);`+"\n") - f.write(profileDir+"/prefs.js", `user_pref("calendar.registry.c1.type", "caldav");`+"\n"+`user_pref("calendar.registry.c1.uri", "https://dav.example.org/c1/");`+"\n") - if x := nag(); x.What != "" { - t.Fatalf("a CalDAV calendar keeps its dismissals on its server: %+v", x) - } -} diff --git a/modules/betterbird/install/distribution/policies.json b/modules/betterbird/install/distribution/policies.json index 600b9c7a..4edb6abe 100644 --- a/modules/betterbird/install/distribution/policies.json +++ b/modules/betterbird/install/distribution/policies.json @@ -22,10 +22,6 @@ "mailnews.start_page.enabled": { "Value": false, "Status": "locked" - }, - "calendar.alarms.showmissed": { - "Value": false, - "Status": "locked" } } }