Files
sxchnge/docs/ux/auth-journeys.md
T
Stephan D f1cbf64282 Fix onboarding UX and add replaceable journey analytics
Make recipient changes explicit, prioritize verification retry, and label simulated outcomes prominently. Add shared typed analytics with bounded dummy capture, correlated service events and documented PostHog adapter contract. Validate privacy, retries, abandonment, keyboard recovery and accessibility. Related to #12.
2026-09-12 08:36:45 +02:00

8.9 KiB

Authentication UX and analytics contract

Task: #12. Applies to login, signup through email verification, and standalone confirmation links.

User outcomes and interface

Signup leads to email delivery, then confirmation. Sent status shows a fixed recipient; Change email explicitly leaves that state and focuses the editor. A temporary confirmation failure prioritizes Retry verification, with requesting a replacement email secondary. Expired/invalid links offer replacement delivery. Demo labels sit beside the heading, and demo outcomes explicitly state that no email was delivered or account verified. The demo confirmation link is the primary next step on the simulated sent screen. EN/RU use the installed theme.

Measurement scope and attempts

Owner/consumer: the sxchnge product team, diagnosing onboarding completion and choosing between fixes to registration, delivery and confirmation recovery.

A journey starts at the first locally valid service request. This intentionally measures submitted attempts, not page views or field editing; pre-submit form abandonment and validation friction are outside this denominator. No generic click/field events are collected.

  • login: one requested login through its service result.
  • signup: signup acceptance, email delivery requests, then email confirmation.
  • verification: confirmation/replacement delivery when there is no active signup.

Retries reuse the journey ID and get a new operation number. Signup success and email delivery are intermediate states; confirmation success is the onboarding terminal outcome. Login success is terminal. Failed operations remain recoverable. Every operation has one completion, and every journey at most one terminal event. Changing auth mode, returning from verification to login, changing a confirmation token, or disposing the app ends the open attempt explicitly. A new operation after a terminal outcome starts a new journey.

The inactivity window is 30 minutes after the last settled nonterminal service result, reset by a new request. It is suspended while a service operation is pending: missing completion is not assumed to be abandonment. An explicit exit can end an attempt while work is pending; it does not cancel backend work. Late results cannot reopen that attempt or emit success after its abandonment.

IDs are random, scoped to this app instance and never derived from identity or link tokens. Same-instance signup → demo link is correlated. A real email opened in another tab/device or after reload starts a standalone verification journey. Cross-device signup conversion needs a future backend-issued, non-sensitive correlation contract; client code must never use email or the token as that ID. Tab termination is not reliably observable. Missing exits remain unknown, not inferred abandonment. Background timer throttling can delay idle detection.

Event schema (version 1)

Event Emission condition Product decision supported
auth_journey_started First valid request for a new attempt Submitted-attempt denominator
auth_{step}_requested A domain service operation starts Locate the last meaningful step and retries
auth_{step}_succeeded Service confirms a non-simulated result Step conversion and outcome duration
auth_{step}_simulated Dummy result, or terminal outcome of a mixed simulated journey Validate the preview funnel without reporting business conversion
auth_{step}_failed Service operation fails Failure rates by safe error category and recovery
auth_journey_abandoned Explicit exit or settled inactivity timeout Diagnose known abandonment/drop-off

step is login, signup, email_delivery, or email_confirmation. Properties: schema_version, event_id, journey_id, journey_kind, sequence, operation (per-journey counter when applicable), occurred_at (UTC), elapsed_ms (monotonic journey duration), terminal, simulated, and optional typed failure/exit. Every event in a known dummy journey is marked simulated. No email, password, name, user ID, confirmation token, URL, raw exception, or form contents is captured.

Metrics

Group by journey_kind and journey_id; deduplicate delivery by event_id, ordering by sequence. Use closed attempts (terminal outcome or explicit abandonment) for settled cohort rates; report open/unknown counts separately. For a fixed reporting cohort, include starts in that interval and allow their defined inactivity window to settle. Report pending operations separately even after it.

  • Completion: distinct successful terminal journey IDs / closed journey IDs.
  • Simulated completion: same calculation using simulated terminals, reported separately. Never mix these into actual account/session conversion.
  • Failure rate: distinct failed operations / requested operations; also report journeys with any failure / started journeys.
  • Recovery: closed journeys completing after a failed operation / closed journeys with a failed operation.
  • Known abandonment: abandoned journey IDs / closed journey IDs, by exit reason.
  • Step drop-off: closed journeys reaching a step but not the next / closed journeys reaching that step (signup acceptance → delivery → confirmation).
  • Duration: terminal elapsed_ms; step latency from matching requested/result operation timestamps. Report distributions separately for retries and failures.

Current service results are client evidence only. Future production adapters must return success only after server confirmation; delivery API acceptance still isn't proof that the email reached an inbox. That remains a documented proxy.

Dummy service and PostHog replacement

sxchnge_analytics is a platform-independent shared package. AnalyticsService accepts typed AnalyticsEvent; DummyAnalyticsService retains the latest 500 unique events in memory for tests/debugging. It exposes an immutable event list and clear(), writes no logs/files, installs no SDK, and makes no network requests. It is diagnostic storage, not a durable metrics backend; older events are evicted.

Inject the service through SxchngeApp(analyticsService: service). Auth services are decorated by InstrumentedAuthServices, so rebuilds, locale changes and layout changes cannot generate events. Analytics exceptions are swallowed at the recorder boundary and analytics futures are never awaited by auth operations. Adapters must do lightweight capture/enqueue work; synchronous CPU blocking in an adapter cannot be isolated by a Dart Future.

A future PostHog adapter should implement capture using event.name and the allowlisted event.properties. Official API reference: https://pub.dev/documentation/posthog_flutter/latest/posthog_flutter/Posthog-class.html

Preserve event_id and sequence for deduplication/reconstruction. Configure consent, retention, region and anonymous identity before enabling collection. Do not enable autocapture, session replay, automatic URL capture or user identification as a side effect of this adapter: confirmation URLs and authentication forms contain sensitive data. No PostHog key or network integration is included in this task. Retries/buffering in a future adapter must preserve the original event_id and occurred_at, remain bounded, and never delay authentication.

UX review and verification

  • Purpose/primary action: explicit demo outcomes and primary confirmation retry.
  • Context/recovery: confirmed recipient separate from editor, focus restored by Change email, no sent claim while editing; service failure retains input.
  • States: initial/loading/sent/verified/expired/invalid/internal error remain available. Restricted/offline backend behavior is outside this dummy service.
  • Responsiveness: EN/RU and 200% text coverage retained; focused tests cover keyboard submission, semantic heading/live region and mobile target labels/sizes.
  • Measurement: tests cover correlated success, simulated outcomes, failures, retries, duplicate completion/delivery, explicit exit, pending operations, inactivity, and throwing/failing/non-completing analytics adapters.
  • Privacy: payload tests exclude supplied form values and tokens; only typed allowlisted properties are exposed. Dummy buffer is bounded and read-only.
  • Remaining limits: no real PostHog delivery or cross-device correlation is claimed. Automated semantics checks do not replace assistive-technology testing on supported devices; a native VoiceOver/TalkBack audit remains a release check.

Recorded validation

The automated suite passes 68 tests: 7 analytics, 20 auth, 3 theme, 3 theme consumer, and 35 app tests. Formatting and analysis pass for the three affected packages; the web build passes. Browser walkthrough at 390px covers internal-error hierarchy, secondary email recovery, actual Tab/Enter submission, and the demo sent screen. The heading is exposed as a live accessibility heading. Recipient text uses a normal text node to remain exposed to assistive technology.