Skip to content

Newsletter subscribe entry points + dedicated /subscribe signup - #1031

Merged
RisingOrange merged 34 commits into
mainfrom
prototype/newsletter-entry-points
Aug 20, 2026
Merged

Newsletter subscribe entry points + dedicated /subscribe signup#1031
RisingOrange merged 34 commits into
mainfrom
prototype/newsletter-entry-points

Conversation

@RisingOrange

@RisingOrange RisingOrange commented Aug 9, 2026

Copy link
Copy Markdown
Collaborator

Adds newsletter subscribe entry points and a dedicated /subscribe signup, so people who just want updates can find and complete a signup without going through the full "Join" flow.

Requested and discussed in this Discord thread.

Note

The Airtable side is done. The Members-table automations now read the local-chapter choice this form adds:

  • "Onboarder assignment + alert (new member)" only hands someone to their national chapter's leader if they agreed to be connected with a local chapter. Anyone who did not is routed to the global onboarding address instead, which keeps their verification email working, since it is CC'd to whoever is assigned.
  • "Share US signups with PAI US" only copies a US signup into the shared Google Sheet if they agreed to that too.
  • A new automation notifies the chapter when one of these subscribers later chooses to volunteer or lead, since that choice escalates the sharing anyway.

Signups through the existing Join form are unaffected: that form's privacy checkbox already covers chapter sharing, so every one of its rows carries the agreement. The broader intent/consent question in #1014 is untouched by this.

Screenshots

Entry points

Subscribe item in the header nav

A "Subscribe" item in the header

Homepage subscribe box

The existing signup box near the bottom of the homepage — it now carries the email into the form instead of subscribing straight to Substack

The signup

Subscribe form

/subscribe

Thanks + do-more prompt

After signing up

"Do more" → Get involved

Continues in place, reusing the /join flow without creating a duplicate:

Get involved intent step

Choosing how to get involved

Volunteer form under Get involved

e.g. the volunteer path

What this changes

  • Nav: a "Subscribe" entry (mail icon) in the header, before Donate. Adding a sixth item pushed the logo onto its own line on every page, so the header now gets a little more width than the text column it used to share. That part is a site-wide change, worth a look on a few pages.

  • Homepage box: the existing newsletter box near the bottom of the homepage now leads into the signup form (carrying the email) instead of subscribing straight to Substack, and its heading reads "Subscribe".

  • /subscribe: a dedicated single-page newsletter signup — name, email, country, city, with two optional opt-ins:

    • "Also send me updates from my local chapter"
    • "Also subscribe me to the PauseAI Substack"
    • Name, email, country and city are all required (as on the /join form). Privacy consent is the act of signing up (linked microcopy, no checkbox).
  • "Do more" continuation: after signing up, an optional step to get more involved (Act now / Volunteer / Lead). It continues on the same page rather than sending people to /join, and updates the signup they just made instead of creating a second one.

    This step is the /join intent step, minus its two email checkboxes ("Keep me informed" and the Substack). On /join that is where those choices get made; here they were just made on the form a moment earlier, so repeating them would only invite someone to switch off the newsletter they came for. The choices they made are kept as they are.

Consent behaviour

  • Global newsletter is the default (signing up is the opt-in).
  • Chapter sharing happens only when the local-chapter box is ticked. This decouples chapter sharing from the privacy consent on the subscribe path; the existing /join form is unchanged (it still bundles the two).
  • Choosing Volunteer or Lead does turn chapter updates on, since both mean working with a local group. If someone picks one of those and then backs out again, the choice they made on the form is restored.

Notes for reviewers

  • Live writes are gated behind ONBOARDING_LIVE; the Netlify preview runs in stub mode, so submitting the form writes nothing to Airtable or Substack — feel free to click through.
  • The subscribe form's copy is currently English-only (hardcoded), unlike the neighbouring onboarding components that route through the localization layer — worth deciding whether to localize it.

Related: signup Step 2 proposal #1014. The CRM side of the local-chapter choice is done (PauseAI/pauseai-civicrm#493) — a decline recorded here now reaches CiviCRM, keyed on this form's own signup source.

Adds a "Get updates" nav item (mail icon) between "Get involved" and
Donate, pointing at /join for now. Rendered leading-icon on desktop
(WideNavbar) and trailing-icon on mobile (NarrowNavbar) so the label
lines up with the other items. Also centers the nav row (align-items)
so the taller item does not push the others up.
…ent signup

The homepage newsletter box posted straight to Substack, which is a
different thing from the CRM movement newsletter. Adds a joinHandoff mode
to NewsletterSignup that instead sends the visitor to /join with the email
prefilled (?subscribe-email=...), wiring OnboardingFlow to consume the
previously-no-op initialEmail. Collage/learn keep the Substack behaviour.
@netlify

netlify Bot commented Aug 9, 2026

Copy link
Copy Markdown

Deploy Preview for pauseai ready!

Name Link
🔨 Latest commit db0f2d1
🔍 Latest deploy log https://app.netlify.com/projects/pauseai/deploys/6a862caedc1cab0008413c6d
😎 Deploy Preview https://deploy-preview-1031--pauseai.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
Lighthouse
Lighthouse
1 paths audited
Performance: 51 (🔴 down 20 from production)
Accessibility: 91 (no change from production)
Best Practices: 100 (no change from production)
SEO: 82 (no change from production)
PWA: -
View the detailed breakdown and full score reports
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

Now that the box leads to the signup form, "Subscribe to our newsletter"
+ "Subscribe" over-promised a one-click action. Heading -> "Get updates"
(matches the nav), button -> "Sign up" (signals it continues to a form).
Scoped to the homepage instance; Substack uses keep their copy.
Points "Get updates" (nav) and the homepage box at a new /subscribe post
instead of /join. It reuses the same OnboardingFlow but with a newsletter
title/subtitle and reads ?subscribe-email to prefill. Fixes the two
/join-inheritance issues: wrong hero copy, and the nav highlighting
"Get involved". Also gives "Get updates" its own active-state highlight.
NewsletterSignup.joinHandoff -> handoffHref (destination configurable).
…k opt-ins

Reworks /subscribe from reusing the full /join flow into a lightweight
single-step signup, per Jonathan's design review:

- SubscribeFlow: name/email/country/city, optional local-group and Substack
  opt-ins, privacy-policy microcopy (no consent checkbox — signing up is the
  consent)
- global newsletter by default; chapter sharing only on the explicit
  local-group tick. Decouples chapter sharing from the privacy consent on the
  subscribe path; the /join form still bundles the two, untouched.
- "do more" continues in place into the intent step instead of linking to
  /join, carrying the created record id so it updates rather than creating a
  duplicate
- the continuation hides the email opt-in cards and second consent, so it can
  only add involvement, never re-ask or silently undo the subscribe-time
  choices. Chapter sharing is preserved on update, escalating only for
  Volunteer/Lead.
- rename the nav entry "Get updates" -> "Subscribe"
- The continuation posted through /join's step-2 form (no subscribe_form
  marker), so its update hit the strict required-field validation and 400'd
  for any subscriber who omitted an optional field (name/city/country) — the
  common email-only case. Updates now skip the create-time required-field
  check, since they patch an existing record.
- The continuation's step-2 Back button led to step 1 and its "just browse"
  path, which had no record_id and so created a duplicate record with chapter
  sharing forced true, overriding a declined opt-in. Hide Back on the
  continuation so that path is unreachable.
- gdprConsent is no longer seeded true on the continuation (it was pre-checking
  visible consent boxes on reachable sub-forms); instead the continuation's
  step-2 submit is exempted from the consent gate, since consent was captured
  on the subscribe form.
- SubscribeFlow's email prefill now applies once into an empty field, so a
  prefilled address (?subscribe-email) can be cleared to fix a typo.
- Move the lookupChapter fetch into the stub branch, its only consumer; the
  live path never read the result.
@RisingOrange RisingOrange changed the title Newsletter entry points: "Get updates" nav item + homepage box -> /join Newsletter subscribe entry points + dedicated /subscribe signup Aug 12, 2026
The .field inputs set width:100% with their own padding, and the site's
reset doesn't apply box-sizing to form controls, so on narrow screens the
inputs rendered content-box and spilled past the card's right edge. Add
box-sizing:border-box to the card and inputs, and tighten the card padding
on mobile.
The "do more" continuation reused the /join flow but sat under the /subscribe
page's "Subscribe" heading and newsletter intro, and its stepper showed a
"✓ About you" step the subscriber never saw (the subscribe form). Both read as
off.

- Add a `showTitle` frontmatter flag (mirrors `showImage`) so a page can suppress
  the template h1 and own its heading. subscribe.md sets it and moves the intro
  into the component.
- SubscribeFlow now renders a phase-aware heading: "Subscribe" (+ intro) for the
  signup, "Get involved" once they continue. No more newsletter framing over the
  volunteer form.
- Drop the "About you" step from the stepper on the continuation, so it reads
  Intent -> (Volunteer form ->) Confirmed.
The global `form label { flex-direction: column }` rule made the .opt-in label
stack under the checkbox. Force flex-direction: row so the checkbox sits beside
its label.
- Match the /join form's wording ("your local PauseAI chapter") on the
  local-updates opt-in, instead of the nav's "local group".
- Add the required-field star to the Email label, as /join step 1 does.
Per review, the subscribe form now requires all four fields (name, email,
country, city) with the same required-field stars as /join step 1, rather than
the lighter email-only form. Server validation and the submit gate updated to
match; chapter sharing still keys off the explicit local-chapter tick.
…apping

The bespoke desktop treatment (a bare LinkWithoutIcon with inline-flex, custom
padding/negative margins and a larger font, plus align-items:center on
.nav-links) disturbed the header's flex layout enough that the logo wrapped
above the nav even with room to spare. Render it through the standard Navlink
like every other item and drop the custom styles. The mail icon stays on the
mobile panel; can revisit a desktop icon without the bespoke markup.
…t inline

The header was capped at --page-width (the 768px content column), so the sixth
top-level item ("Subscribe") pushed the nav links past the row and the logo
wrapped above them on every page, regardless of viewport width. Add a
--header-width (48rem) and apply it where the nav is width-capped: the hero
.menu-band nav, and the .wide-navbar inside the width-capped .layout grid on
other pages. The header now gets slightly more room than the content column, so
all six items sit inline beside the logo; genuinely narrow widths still center
the logo above the nav as before. Restores the desktop mail icon on Subscribe.
…n prefill

- The homepage box's native form still posted straight to Substack, so a submit
  before hydration (or with JS off) bypassed the signup flow entirely. When a
  handoff target is set, the form's native action/method/field now mirror the
  hydrated goto(), landing on /subscribe with the email prefilled.
- Apply the prefill latch to OnboardingFlow too: /join?subscribe-email=... could
  not be cleared to fix a typo, because the effect restored it as soon as the
  field went empty.
…ate hardening

- The Subscribe nav item turned brand-orange on hover, which is invisible on the
  hero's orange band (the :hover rule outranked .inverted at equal specificity).
  Go black on hover when inverted, matching .navlink.inverted:hover.
- Backing out of Volunteer/Lead in the "do more" step left "GDPR chapter share
  permission" true for someone who had declined chapter updates on the subscribe
  form. The continuation now reposts that signup-time choice, so a non-escalating
  intent restores it; /join's own volunteer updates are unaffected.
- Updates only write name/country/city when non-empty, so a partial post can't
  blank fields the create collected.
- Localize the homepage handoff target so a visitor on a localized homepage stays
  in their locale instead of landing on the English page.
The subscribe card was 560px against /join's 800px, so the card visibly jumped
width when the "do more" step swapped one for the other in place. Match the
width, and override the global 30rem form cap as OnboardingFlow does so the
fields fill the card instead of leaving the extra width empty.
…that misstate the signup

- The heading spacing was inverted relative to /join (19px under the title and
  48px before the card, where /join has 48 and 19). Mirror the page template's
  hgroup rhythm the page suppresses, so it now measures identically.
- The "do more" confirmation repeated "You're in, thanks for joining us" moments
  after the subscribe thanks screen, and its opt-in confirmations claimed "we'll
  connect you with your local PauseAI chapter" even for someone who declined the
  chapter box on the subscribe form. Both are hidden on the continuation, where
  those opt-ins were already made and confirmed.
On the continuation the person has already signed up, so that step records their
intent and moves them on rather than submitting a signup. /join is unchanged.
The act-now path stopped repeating 'You're in, thanks for joining us' on the
continuation, but the no-intent confirmation still greeted them with 'You're
in.' moments after the subscribe thanks screen. Gate it the same way, and
correct a comment that misdescribed why the inverted hover rule wins.
…age itself

- Clicking "Subscribe" in the nav while already on /subscribe did nothing: the
  finished flow stayed on screen, so the link looked broken. It now returns to
  the start, cleared — which also keeps a second signup from being one click
  away from duplicating the last one. Only same-path navigations reset, so
  arriving from the homepage box still prefills the handed-off email.
- Require an intent to continue on the "do more" step. It was clickable with
  nothing selected (the newsletter opt-in is seeded on, which satisfied the
  /join rule), submitting a no-op update and landing on a confirmation for a
  choice never made.
main added Turnstile bot protection to the shared submit action, which now 403s
a submission without a valid token. The subscribe form posts to that same
action, so it needed the widget too — without it the form would have been
rejected outright wherever a site key is configured.

Conflict resolution in the intent step keeps both sides: main's Turnstile widget
and its canSubmit gate, and this branch's continuation rules (no second consent
field, and an intent required to continue).
The reset that runs when Subscribe is clicked from the page itself had to
re-list every field, so a new field would silently keep its value across a
restart — and that reset is what stops a stale signup being resubmitted. One
object with a blank factory means the two can't drift apart. Also drops an
unused honeypot binding: the field posts natively and nothing reads it.
/join and /subscribe ask for chapter consent differently, and once a row is
written nothing else says which form produced it — so the distinction has to be
recorded at create time or not at all. Give /subscribe its own Signup source.

Also stop writing Signup source on updates: it is provenance, and the volunteer
step carries no subscribe marker, so it would have rewritten a /subscribe row as
a /join one.
The Turnstile site key only allows the production hostname, so on a deploy
preview the widget refuses to render (Cloudflare error 110200) and no token can
ever exist. The submit button then stays disabled and the server 403s, which
makes the signup form untestable on every preview — the one place reviewers
click through it.

Gate the check on whether the submission actually writes. In stub mode there is
no record, no subscription and no mail to protect, so there is nothing for the
check to defend; live deploys verify exactly as before. The client mirrors the
same rule, and assumes live if it cannot reach the mode endpoint, so an
unanswered request keeps the check required rather than dropping it.

Deliberately not done: pointing previews at Cloudflare's test keys. The verifier
refuses those outside dev on purpose, because a test secret accepts every token
and would silently reopen the form to bots.
- Read isOnboardingLive() once and use it for both the check and the writes, so
  'they cannot disagree' holds by construction rather than by relying on the
  platform keeping env vars stable within a request.
- Only an explicit false relaxes the client-side check, so a malformed mode
  response can't drop it by being falsy.
- join-form-flow.md said Turnstile runs regardless of mode, which is no longer
  true, and now records that ONBOARDING_LIVE=true has to be scoped to the
  Production context: set site-wide it makes previews live, which both brings
  the untestable-preview problem back and lets a preview run write real data.
Pulls in the fix so this branch's preview is clickable before #1046 lands — the
Turnstile widget can't render on a preview hostname, which left the signup form
untestable exactly where it gets reviewed. Once #1046 is in main the later merge
is a no-op.

Applies the same rule to SubscribeFlow, which gates its own submit on a token
and so was blocked the same way. Conflict resolution keeps this branch's
lookupChapter placement (stub-only) with the fix's single liveness read.
In stub mode the check is skipped, but the widget still mounted — and on a
deploy preview it can't render, so reviewers saw 'The anti-spam check could not
load' next to a working, enabled button. Render it only when a token is actually
required.
…scribeFlow

Conflict resolution keeps this branch's continuation rule (no second consent
field) alongside the fix's gating of the widget itself.
Conflicts are this branch's copy of that fix meeting the trimmed version that
went into main. Takes main's wording and its widget-waits-for-the-mode change,
keeps this branch's continuation rule and its stub-only lookupChapter, and
applies the same widget gating to SubscribeFlow.
…omepage box

The homepage box carried the generic newsletter description, which described the
Substack it no longer signs people up for; it now has its own text.
@RisingOrange
RisingOrange marked this pull request as ready for review August 17, 2026 23:23
@hturnbull93

Copy link
Copy Markdown
Collaborator

We now require name email country and city on Substack signup, so a bit confused why we need to change the airtable automations? Surely we capture everything needed for them?

@hturnbull93

Copy link
Copy Markdown
Collaborator

Unless we mean that if someone subscribes in a country without a national leader, then they get assigned to a global handler?

@RisingOrange

Copy link
Copy Markdown
Collaborator Author

@hturnbull93 Right now the Onboarder assignment + alert automation notifies an onboarder of someone's country about every new signup, and it doesn't know about the new "Also send me updates from my local chapter" checkbox. Same for Share US signups with PAI US. And picking Volunteer or Lead in the get-involved step only updates the row, so nothing fires and the onboarder never hears about it.

I think I have the updates we should do to the automations nearly worked out.

@hturnbull93

Copy link
Copy Markdown
Collaborator

@hturnbull93 Right now the Onboarder assignment + alert automation notifies an onboarder of someone's country about every new signup, and it doesn't know about the new "Also send me updates from my local chapter" checkbox. Same for Share US signups with PAI US. And picking Volunteer or Lead in the get-involved step only updates the row, so nothing fires and the onboarder never hears about it.

I think I have the updates we should do to the automations nearly worked out.

Ok cool, I think those templates got adjusted to include the action/intent they chose, right?
On the "lead" intent they are prompted to email Irina so less critical the national lead gets told about it. Also I think we have checks for if there is already an appropriate existing leader? I can't recall if I put that in here or for catalyse.

@hturnbull93 hturnbull93 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lgtm, happy with the discussion and can see the new flow in the screenshots

@RisingOrange

Copy link
Copy Markdown
Collaborator Author

I think those templates got adjusted to include the action/intent they chose, right?

Yeah, the intent is shown in the mail.

On the "lead" intent they are prompted to email Irina so less critical the national lead gets told about it.

Good point, this means that the gap only really matters for volunteers.

Also I think we have checks for if there is already an appropriate existing leader?

Yes, the existing-leader check is in there: it looks up a chapter for their country and falls back to Irina + global onboarding when there isn't one. We can reuse this branch for people who leave the local-chapter box unchecked, so they get the global onboarder instead of a national one

"We'll connect you with your local PauseAI chapter" promises contact that may
not happen: whether a chapter reaches out to a newsletter signup is the open
question in #1014. The label already says what the box does.
@RisingOrange
RisingOrange merged commit 5d00323 into main Aug 20, 2026
9 checks passed
@RisingOrange
RisingOrange deleted the prototype/newsletter-entry-points branch August 20, 2026 08:22
@Wituareard

Copy link
Copy Markdown
Collaborator

@RisingOrange Could you maybe update docs/join-form-flow.md?

RisingOrange added a commit that referenced this pull request Aug 20, 2026
…le retry (#1049)

* docs(join-form-flow): cover the /subscribe flow and its consent model

The submit action names this document as the flow contract to keep in sync
when its inputs or behaviour change, and #1031 changed both without touching
it.

Adds /subscribe as a third entry point, the create-versus-update axis the
action now turns on, and a section on chapter sharing, which the two forms
capture differently and which decides how the Airtable automations route a
signup. Also corrects two statements that #1031 made false: that the basics
are unconditionally required, and that OnboardingFlow has no initialEmail
prop.

* docs(join-form-flow): correct three claims and document continuation mode

Review found the previous commit asserted things the code does not do.

- Cut a sentence claiming the unconditional required-field check had broken
  the hand-off. Every create enforces all four fields, so no short row exists,
  and the hand-off reposts them anyway. The rule is robustness, not a fix.
- Only OnboardingFlow logs the onboarding mode; SubscribeFlow fetches it to
  gate Turnstile and logs nothing.
- The signup-time chapter choice is reposted by OnboardingFlow's step-2 form
  under isContinuation, not by every update, which the Signup source section
  already implied by noting the volunteer step carries no subscribe marker.

Adds the constraint most likely to be broken by a future editor: Email,
Intent, Email subscription and Data privacy policy agreed are written from the
post on every call, so dropping the keep_informed hidden input would clear
subscription flags silently. Also documents continuation mode, which changes
the step machine the diagram describes, and drops the initialEmail note that
now only narrates history.

* docs(join-form-flow): tighten claims a second review found overstated

- The subscription-preservation warning named one hidden input; there are two,
  one on the step-2 intent form and one on the step-3 volunteer form, and
  dropping either clears the flag. Naming one left the other looking safe to
  remove.
- Data privacy policy agreed is hard-coded true on every call, not taken from
  the post like the fields beside it.
- The submit action is not the only route to a Substack subscription: a
  NewsletterSignup without handoffHref, as the Collagen banner uses, posts
  straight to Substack.
- The result shape splits on live versus stub, not create versus update.
- Turnstile hostname checking is weaker than stated: an absent hostname is
  accepted by design, and it compares hostname rather than full origin.
- Adds Paying Interest to the volunteer update fields, drops an implementation
  detail that carried no constraint, and stops calling the join flow four-step
  when a newsletter-only signup finishes at step 3.

Also points at where the Airtable automation behaviour is documented, since
nothing in this repository can establish it.

* docs(join-form-flow): cut duplicated claims, and put the flagship hazard in the code

A review of the document's direction, rather than of its accuracy, argued the
unit to watch is claim surface: how many independently falsifiable statements
need re-verifying whenever the action changes. This branch had been growing it.

- Collects create-versus-update into one section. It was spread across the
  endpoint section, the Signup source paragraph and two validation bullets, so
  the next change to the action had three places to rot and a reviewer fixing
  one copy would miss the others.
- Merges the two bot-protection sections, which had drifted into stating the
  Turnstile hostname check at two different strengths, and defers the mechanism
  to turnstile-verify.ts, which is commented in full.
- Replaces the volunteer field inventory with a pointer to the block that
  writes it. That list is where a field went missing, and a second copy of a
  list is a sync liability rather than a tripwire. The create list stays: every
  field in it has a rule stated elsewhere, so it works as an index.
- Drops the snippet inventory, the continuation-mode cosmetics, and the
  homepage plumbing narration, keeping the coupling that handoffHref carries.
- Removes a pointer to a pauseai-civicrm file that is untracked and exists only
  on one machine, so no other reader could have followed it.

Adds the comments the document cannot substitute for: the hazard about
Email subscription being cleared is now stated at both hidden inputs in
OnboardingFlow and at the field in the action, which is where someone deleting
an apparently redundant input will actually be looking.

* docs(onboarding): correct two stale comments in the submit action

The chapter-routing comment said notifying the chapter stays a manual Airtable
process. That has not been true for as long as the onboarder-alert automation
has existed, and it is what led a reviewer to read a correct paragraph in
join-form-flow.md as contradicting the code. It now says why the live branch
has no use for the lookup: the automations run their own, and route on the
chapter-share field this action writes.

The header listed only the /join entry points, and claimed the document is the
full flow contract while the body states most of the same rules inline. That
instruction is what generates the duplication: told to keep a full contract in
sync, the next person copies their rule into the document too. It now names
both routes and says which half belongs where.

* docs(onboarding): allow deliberate duplication between doc and comments

The previous wording said a single-site rule belongs in a comment rather than
copied into the document. The document then does exactly that for three rules
the action already comments at their site, so the header contradicted the diff
it shipped in, and the ban was too strong anyway: a rule important enough to
document is usually worth stating at the line that governs it as well.

The rule is now about where relying on one place fails, not about avoiding
overlap. The document alone is what does not work, because whoever breaks the
rule is editing the code and does not have it open.

* docs(onboarding): restore a dropped contract and fix comments the merge left stale

From a review of the commits nothing had looked at yet.

- The /subscribe hidden-input trim dropped intent=Keep informed as noise. It is
  not: the route pins that value, so every row it creates starts there whatever
  the person picks later, and an Airtable automation keys on exactly that pair
  of signup source and intent. Restored.
- The rewritten header named /join and /subscribe but not the embed route,
  which is itself an entry point and which the document lists as Route 2. A
  header that presents an inventory has to be complete.
- Three comments in the action still described the pre-/subscribe topology:
  consent bundling as universal when it is a /join arrangement, and record
  creation and Substack subscription as step-2 events when /subscribe does both
  during its own signup. Those are exactly the site-local explanations the new
  convention says to trust, so leaving them stale undercuts it.
- The Turnstile hostname check is also skipped in dev, and dev is independent of
  ONBOARDING_LIVE.
- The widget remount is not universal: SubscribeFlow remounts on every result,
  OnboardingFlow skips its unexpected-error branch, so a retry after one of
  those reposts a spent token.

* fix(onboarding): remount Turnstile after an unexpected submit error

The submit handler reset the widget on success and on a validation failure but
not on the unexpected-error branch. Verification spends the token whether or
not the write then succeeded, so after a 500 the next attempt reposted a spent
token and failed the spam check until the user reloaded the page.

Hoisted the reset to run on every result, which is what SubscribeFlow already
does. Found while checking a claim the flow document makes about this contract,
which was true of one form and not the other.
@RisingOrange

Copy link
Copy Markdown
Collaborator Author

@RisingOrange Could you maybe update docs/join-form-flow.md?

Good point, updated it in #1049

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants