From bc0ab8e0d885240aba66990600e60dff7b8a989c Mon Sep 17 00:00:00 2001 From: RisingOrange Date: Wed, 29 Jul 2026 17:55:58 +0200 Subject: [PATCH 1/7] feat(onboarding): show the critical-alert notice on the email field The notice has been in the form but commented out since it was written, so no signup has ever seen it. Maxime asked for it to be shown. It matters for one specific group: someone who ticks only the required privacy-policy box and neither optional box. They are still added to the critical alerts list in CiviCRM, so they can be reached if something urgent happens, but nothing in the form tells them any email may follow. The required checkbox covers the privacy policy and chapter sharing, and the privacy policy itself only describes the opt-in Substack newsletter and contact via Airtable for coordination. That population is 64 people today and grows by roughly 60 a month. Reworded from "We may contact you about critical mobilizations": we do not use the word "mobilization" anywhere a signup would see, and "critical alert" matches the name on the unsubscribe page (PauseAI Critical Alerts), so the wording at signup and at unsubscribe agree. "critical" and "occasionally" are load-bearing, keeping this a bounded tier rather than "we may email you anything", which would undercut the opt-in it bypasses. Moved into messages.ts as onboarding_email_critical_notice rather than left as inline English, with de and fr, following onboarding_gdpr_consent which likewise carries the privacy link as inline markup rendered through {@html}. Both form variants use the same key. --- src/lib/components/onboarding/OnboardingFlow.svelte | 12 ++++-------- src/lib/components/onboarding/messages.ts | 7 +++++++ 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/lib/components/onboarding/OnboardingFlow.svelte b/src/lib/components/onboarding/OnboardingFlow.svelte index 1473e6c23..35fd127d4 100644 --- a/src/lib/components/onboarding/OnboardingFlow.svelte +++ b/src/lib/components/onboarding/OnboardingFlow.svelte @@ -422,10 +422,8 @@ autocomplete="email" bind:value={basics.email} /> - + +

{@html msgs.onboarding_email_critical_notice}

@@ -621,10 +619,8 @@ autocomplete="email" bind:value={basics.email} /> - + +

{@html msgs.onboarding_email_critical_notice}

privacy policy.', onboarding_field_country: 'Country of residence *', onboarding_placeholder_country: 'Select your country', onboarding_field_city: 'City / town of residence *', @@ -398,6 +401,8 @@ const de: OnboardingMessages = { onboarding_placeholder_full_name: 'Vollständiger Name', onboarding_field_email: 'E-Mail *', onboarding_placeholder_email: 'E-Mail', + onboarding_email_critical_notice: + 'Wir senden dir gelegentlich eine kritische Warnung, auch wenn du dich für nichts anderes anmeldest. Siehe unsere Datenschutzrichtlinie.', onboarding_field_country: 'Wohnsitzland *', onboarding_placeholder_country: 'Land auswählen', onboarding_field_city: 'Stadt / Gemeinde *', @@ -618,6 +623,8 @@ const fr: OnboardingMessages = { onboarding_placeholder_full_name: 'Nom complet', onboarding_field_email: 'E-mail *', onboarding_placeholder_email: 'E-mail', + onboarding_email_critical_notice: + 'Nous pouvons occasionnellement vous envoyer une alerte critique, même si vous ne vous inscrivez à rien d\'autre. Voir notre politique de confidentialité.', onboarding_field_country: 'Pays de résidence *', onboarding_placeholder_country: 'Sélectionne ton pays', onboarding_field_city: 'Ville / commune de résidence *', From 1af2826886b5ccbfa455959d1ac9c63e2d58762c Mon Sep 17 00:00:00 2001 From: RisingOrange Date: Thu, 30 Jul 2026 15:07:42 +0200 Subject: [PATCH 2/7] fix(onboarding): restore the modal "may" in the German notice MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The German read "Wir senden dir gelegentlich eine kritische Warnung", which is declarative: we DO send these. The English and French both reserve the right rather than promise the act ("We may", "Nous pouvons"). In a notice whose whole purpose is telling someone what we might do with their address, that is the wrong claim. Now "Wir können ... senden". --- src/lib/components/onboarding/messages.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/components/onboarding/messages.ts b/src/lib/components/onboarding/messages.ts index 8d5bfcf04..462d41ec6 100644 --- a/src/lib/components/onboarding/messages.ts +++ b/src/lib/components/onboarding/messages.ts @@ -402,7 +402,7 @@ const de: OnboardingMessages = { onboarding_field_email: 'E-Mail *', onboarding_placeholder_email: 'E-Mail', onboarding_email_critical_notice: - 'Wir senden dir gelegentlich eine kritische Warnung, auch wenn du dich für nichts anderes anmeldest. Siehe unsere Datenschutzrichtlinie.', + 'Wir können dir gelegentlich eine kritische Warnung senden, auch wenn du dich für nichts anderes anmeldest. Siehe unsere Datenschutzrichtlinie.', onboarding_field_country: 'Wohnsitzland *', onboarding_placeholder_country: 'Land auswählen', onboarding_field_city: 'Stadt / Gemeinde *', From c47dd93aa86188a7dd92ed3231d7b6300d27dc56 Mon Sep 17 00:00:00 2001 From: RisingOrange Date: Thu, 30 Jul 2026 15:41:17 +0200 Subject: [PATCH 3/7] fix(onboarding): move the notice to the intent step and drop "else" Two problems with putting it under the email field on step 1. The sentence says "even if you don't opt into anything else", but step 1 shows no opt-ins at all: the two checkboxes are on step 2. The clause pointed at something the reader had not seen yet. Moving it under those checkboxes gives it a referent, and costs nothing in notice-at-collection terms, because step 1 submits nothing: its form is onsubmit=continueToIntent, which only advances the step. Data first leaves the browser when step 2 posts. "else" also presupposed a first thing the reader had opted into, which quietly framed critical alerts as one more option on the form, when the point is that they are the one thing you do not choose. Now "any of these", which points at the checkboxes above it. Reworded in all three locales. Also drops the notice from the browse mini-form. That form hardcodes keep_informed=on, so submitting it IS the opt-in, and telling those users we may write "even if you don't opt into any of these" is simply false. The required privacy-policy checkbox still carries the general disclosure there. --- src/lib/components/onboarding/OnboardingFlow.svelte | 6 ++---- src/lib/components/onboarding/messages.ts | 6 +++--- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/lib/components/onboarding/OnboardingFlow.svelte b/src/lib/components/onboarding/OnboardingFlow.svelte index 35fd127d4..9008c09df 100644 --- a/src/lib/components/onboarding/OnboardingFlow.svelte +++ b/src/lib/components/onboarding/OnboardingFlow.svelte @@ -422,8 +422,6 @@ autocomplete="email" bind:value={basics.email} /> - -

{@html msgs.onboarding_email_critical_notice}

@@ -515,6 +513,8 @@
+ +

{@html msgs.onboarding_email_critical_notice}

{msgs.onboarding_intent_more_optional}

{#each intentOptions as option (option.key)} @@ -619,8 +619,6 @@ autocomplete="email" bind:value={basics.email} /> - -

{@html msgs.onboarding_email_critical_notice}

privacy policy.', + 'We may occasionally send you a critical alert, even if you don\'t opt into any of these. See our privacy policy.', onboarding_field_country: 'Country of residence *', onboarding_placeholder_country: 'Select your country', onboarding_field_city: 'City / town of residence *', @@ -402,7 +402,7 @@ const de: OnboardingMessages = { onboarding_field_email: 'E-Mail *', onboarding_placeholder_email: 'E-Mail', onboarding_email_critical_notice: - 'Wir können dir gelegentlich eine kritische Warnung senden, auch wenn du dich für nichts anderes anmeldest. Siehe unsere Datenschutzrichtlinie.', + 'Wir können dir gelegentlich eine kritische Warnung senden, auch wenn du nichts davon auswählst. Siehe unsere Datenschutzrichtlinie.', onboarding_field_country: 'Wohnsitzland *', onboarding_placeholder_country: 'Land auswählen', onboarding_field_city: 'Stadt / Gemeinde *', @@ -624,7 +624,7 @@ const fr: OnboardingMessages = { onboarding_field_email: 'E-mail *', onboarding_placeholder_email: 'E-mail', onboarding_email_critical_notice: - 'Nous pouvons occasionnellement vous envoyer une alerte critique, même si vous ne vous inscrivez à rien d\'autre. Voir notre politique de confidentialité.', + 'Nous pouvons occasionnellement vous envoyer une alerte critique, même si vous n\'en sélectionnez aucune. Voir notre politique de confidentialité.', onboarding_field_country: 'Pays de résidence *', onboarding_placeholder_country: 'Sélectionne ton pays', onboarding_field_city: 'Ville / commune de résidence *', From 67c6048cbfe4bd4c82503592e2c43efdd8c680ba Mon Sep 17 00:00:00 2001 From: RisingOrange Date: Thu, 30 Jul 2026 16:28:44 +0200 Subject: [PATCH 4/7] fix(onboarding): idiomatic German for the critical-alert notice MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit "kritische Warnung" read as a translation: "Warnung" already is an alert, so the qualifier is close to redundant, and "kritisch" in German leans toward "critical of" or "in a critical condition" rather than "important". Now "In dringenden Fällen könnten wir dir eine Nachricht senden". Nothing is lost by dropping "kritisch": the reason English says "critical alert" is to match the unsubscribe page (PauseAI Critical Alerts), and that page is English-only, so the German is free to be idiomatic. Note this bounds the promise differently from the English. English bounds by frequency ("occasionally"), the German by circumstance ("in dringenden Fällen"), which is the harder bound to stretch. Singular "eine Nachricht" carries the rarity that "occasionally" carries in English; the plural read like an ongoing stream, which is what this tier is trying not to be. Stays informal: the German locale is 56 du/dich/dir forms and no Sie, so a formal sentence here would be the only one on the form. --- src/lib/components/onboarding/messages.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/components/onboarding/messages.ts b/src/lib/components/onboarding/messages.ts index 6746138bb..ff4f93abe 100644 --- a/src/lib/components/onboarding/messages.ts +++ b/src/lib/components/onboarding/messages.ts @@ -402,7 +402,7 @@ const de: OnboardingMessages = { onboarding_field_email: 'E-Mail *', onboarding_placeholder_email: 'E-Mail', onboarding_email_critical_notice: - 'Wir können dir gelegentlich eine kritische Warnung senden, auch wenn du nichts davon auswählst. Siehe unsere Datenschutzrichtlinie.', + 'In dringenden Fällen könnten wir dir eine Nachricht senden, auch wenn du nichts davon auswählst. Siehe unsere Datenschutzrichtlinie.', onboarding_field_country: 'Wohnsitzland *', onboarding_placeholder_country: 'Land auswählen', onboarding_field_city: 'Stadt / Gemeinde *', From 78414a70f0e7b1e98fbd9598746d53e8e083e492 Mon Sep 17 00:00:00 2001 From: RisingOrange Date: Thu, 30 Jul 2026 16:50:34 +0200 Subject: [PATCH 5/7] fix(onboarding): tutoiement and idiomatic French for the notice MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The string was the only vouvoiement in the French locale: 39 tu forms against 2 vous, both of them mine. It now uses tu like everything else. Three other things went with it. "Voir notre..." was an infinitive label, where the locale uses tu-imperatives for links ("Consulte notre liste d'actions"), so it is now "Consulte notre". "n'en sélectionnes aucune" left the pronoun without a named antecedent, which English gets away with because "these" points at the checkboxes on screen; French now says "aucune de ces options". And "alerte critique" carried the same redundancy "kritische Warnung" did in German, since "alerte" already conveys urgency, so it is now "alerte urgente". Unlike the German, this keeps the English's two bounds: occasion ("urgente") and frequency ("occasionnellement"). --- src/lib/components/onboarding/messages.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/components/onboarding/messages.ts b/src/lib/components/onboarding/messages.ts index ff4f93abe..d4bc865b4 100644 --- a/src/lib/components/onboarding/messages.ts +++ b/src/lib/components/onboarding/messages.ts @@ -624,7 +624,7 @@ const fr: OnboardingMessages = { onboarding_field_email: 'E-mail *', onboarding_placeholder_email: 'E-mail', onboarding_email_critical_notice: - 'Nous pouvons occasionnellement vous envoyer une alerte critique, même si vous n\'en sélectionnez aucune. Voir notre politique de confidentialité.', + 'Nous pouvons occasionnellement t\'envoyer une alerte urgente, même si tu ne sélectionnes aucune de ces options. Consulte notre politique de confidentialité.', onboarding_field_country: 'Pays de résidence *', onboarding_placeholder_country: 'Sélectionne ton pays', onboarding_field_city: 'Ville / commune de résidence *', From bbb880f00a8cb62655722bf38d9c64fdc1241e41 Mon Sep 17 00:00:00 2001 From: RisingOrange Date: Thu, 30 Jul 2026 17:49:13 +0200 Subject: [PATCH 6/7] fix(onboarding): name what the German clause refers to MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit "nichts davon auswählst" left the reference dangling: "davon" points back at something the sentence never names. Same weakness the French had with "n'en sélectionnes aucune", fixed there in the previous commit. Now "keine dieser Optionen wählst". The demonstrative does the work "these" does in English and "ces options" in French, pointing at the two checkboxes the notice sits directly beneath. --- src/lib/components/onboarding/messages.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/components/onboarding/messages.ts b/src/lib/components/onboarding/messages.ts index d4bc865b4..51ea5d894 100644 --- a/src/lib/components/onboarding/messages.ts +++ b/src/lib/components/onboarding/messages.ts @@ -402,7 +402,7 @@ const de: OnboardingMessages = { onboarding_field_email: 'E-Mail *', onboarding_placeholder_email: 'E-Mail', onboarding_email_critical_notice: - 'In dringenden Fällen könnten wir dir eine Nachricht senden, auch wenn du nichts davon auswählst. Siehe unsere Datenschutzrichtlinie.', + 'In dringenden Fällen könnten wir dir eine Nachricht senden, auch wenn du keine dieser Optionen wählst. Siehe unsere Datenschutzrichtlinie.', onboarding_field_country: 'Wohnsitzland *', onboarding_placeholder_country: 'Land auswählen', onboarding_field_city: 'Stadt / Gemeinde *', From a11b56f6888ffe60ac044eec3caa14c2fd77a2dc Mon Sep 17 00:00:00 2001 From: RisingOrange Date: Thu, 30 Jul 2026 18:36:52 +0200 Subject: [PATCH 7/7] a11y(onboarding): associate the notice with the opt-in controls A review pointed out the notice was a standalone paragraph after the two checkbox-style buttons, with nothing tying them together. A screen-reader user tabbing through the controls hears both buttons and then the first radio option, and can pass the notice without ever meeting it. For a paragraph whose whole purpose is to be read before you decide not to tick anything, that defeats the point of showing it at all. Both controls now carry aria-describedby pointing at the paragraph's id. Nothing else in this file uses aria-describedby, so this is the first instance rather than a convention being followed; the other helper texts are hints, whereas this one is a disclosure. The eslint-disable moved inside the

: Prettier splits the element across lines once it has two attributes, which left the disable applying to the opening tag rather than the {@html} it was written for, and the rule fired. --- src/lib/components/onboarding/OnboardingFlow.svelte | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/lib/components/onboarding/OnboardingFlow.svelte b/src/lib/components/onboarding/OnboardingFlow.svelte index 9008c09df..fa65693b6 100644 --- a/src/lib/components/onboarding/OnboardingFlow.svelte +++ b/src/lib/components/onboarding/OnboardingFlow.svelte @@ -479,6 +479,7 @@ class="intent-option" class:selected={keepInformed} role="checkbox" + aria-describedby="critical-alert-notice" aria-checked={keepInformed} onclick={() => (keepInformed = !keepInformed)} > @@ -498,6 +499,7 @@ class="intent-option" class:selected={basics.newsletter} role="checkbox" + aria-describedby="critical-alert-notice" aria-checked={basics.newsletter} onclick={() => (basics.newsletter = !basics.newsletter)} > @@ -513,8 +515,10 @@

- -

{@html msgs.onboarding_email_critical_notice}

+

+ + {@html msgs.onboarding_email_critical_notice} +

{msgs.onboarding_intent_more_optional}

{#each intentOptions as option (option.key)}