Key dates
M1 - Signup migrated to the registration step machine
The old approach guessed locally at what the visitor should see next. trial_manager's /v1/trial-registration/* surface is a step machine: every call — including the ones that represent a refusal — answers 200 with a TrialStepResponse carrying status, step, and page_data. Whether an email already has an account, whether SSO is required, whether the visitor is a student, whether the campaign expired, whether the product is licensable in their region: all of it is now the server's call, passed through untranslated.
M2 - Internal management tooling removed; the app is a funnel again
Management surfaces that used to live here — the trials dashboard, per-school detail, contact add/edit, and the health-banner route — are gone. Trials is now the public funnel plus admin.vue for staff provisioning; internal trial and catalog management belongs to Catalyst (rad-trials). Marketing copy across the funnel was refreshed in the same pass.
This split is the point, not a side effect: two apps, one public and one internal, with opposite crawler postures and opposite audiences.
M3 - Demo-mode audience switch for staff
A "Demo as" control lets staff preview the funnel as a signed-in or signed-out visitor without juggling sessions — the thing that makes the funnel's branches walkable at all. Gated behind requireDeStaff, which validates the DE session against /v1/users/me and requires a campaign-manager role (401 unauthenticated, 403 without the role).
M4 - Accessibility and fallback-flow pass
Skip-to-main-content link, aria-live on the loading overlay, role="alert" on every form error across the account / trial / admin flows, prefers-reduced-motion handling, and labelled aria-expanded disclosure controls in the trial layout. Alongside it: the fallback paths were exercised — hardcoded campaign URLs still stand in when a campaign can't be resolved, and the SSR product list still renders when the live catalog fetch fails.
M5 - SEO and AIO surface
All three discovery routes generate from shared/products, so they can't drift as the catalog changes. llms.txt (llmstxt.org format) gives assistants a structured guide with the qualifying facts they need to answer correctly — free, no credit card, up to 60 days, US K–12. FAQPage schema is backed by the same three questions rendered visibly on the page, so an educator and an answer engine get the identical answer. Organization / WebSite / ItemList on the homepage; Product + Offer + BreadcrumbList per product. useSiteUrl pins one canonical origin so preview hosts don't fragment canonicals.
T1 — Step-machine contract tests
The registration dispatcher is the highest-risk code in the app and has no test. Cover the allowlist (unlisted path, wrong method, undeclared query param all rejected before upstream), the CSRF pair (minted per mutation, Set-Cookie relayed to the browser), the provisioning timeout, and pass-through fidelity — a TrialStepResponse must reach the client unmodified, including the statuses that represent refusals.
Exit criteria: every ENDPOINTS row exercised against a mocked trial_manager; a 200-with- refusal-status never surfaces as an error, and a transport fault never surfaces as a step.
T2 — Funnel E2E across visitor states
Playwright walk of the funnel in each state the step machine can put a visitor in: new email, existing account, SSO-required, student-blocked, expired campaign, out-of-region, plus trial-one and trial-everything. Demo mode drives the signed-in/signed-out split.
Exit criteria: each branch reaches a correct terminal screen; no branch dead-ends or shows a raw error.
R1 — Ship the migrated funnel to dev, then stage
The funnel now depends on trial_manager for every branch, so the deploy matters more than usual: verify against the dev stack that the anonymous CSRF pair survives the real cookie domain, that provisioning completes inside its timeout, and that the fallback paths don't mask a healthy backend.
Exit criteria: a real trial provisioned end to end in dev with no local heuristics involved.

