User Onboarding: Teach by Doing
GMI's first onboarding (April 2026) explained the product: a Getting Started page, an eight-slide mobile carousel and a 22-step guided tour. Two beginners in live demos showed that reading gets skipped. The September 2026 redesign (Wave 20, the Guided First Report) replaces explanation with one real report and four short callouts, and it needed no schema change.
BUILT · SEPTEMBER 2026 ClientWeb · MAUI · AdminWeb · public walkthroughWhat the Demos Showed
In September 2026 two beginners were walked through GMI live, and both stalled at the same first step: type a ticker. Neither knew one offhand, and neither knew what a grade would tell them once they had one. The existing onboarding answered with reading material, and reading is what people skip.
The fix is deliberately small: a new account's home page doesn't say "add a symbol". It says "Pick a company you know" and shows eight names a beginner recognizes. One tap builds a real report. The first time that report opens, four short callouts explain the grade on the page where it appears. Then the user is invited to add something they actually own. There's no "beginner or pro?" branching, no onboarding engine and no new tables. It only grows if the funnel data says it should.
flowchart TB
subgraph PUB[Public site - no account]
W[Walkthrough<br/>4 beats over a dated,<br/>real Apple grade] --> PK[Pick a company you know<br/>stored in browser storage]
end
PK --> SI[Sign in, create account,<br/>verify email]
SI --> PICK
subgraph APP[In the app - onboarding flag false]
PICK[Home card: PICK<br/>8 recognizable names] -->|one tap| START[Track symbol, price check,<br/>queue the real report]
START -->|price above 0| ASK[Ask first:<br/>this costs X, build it?]
ASK --> BUILD
START --> BUILD[Home card: BUILDING<br/>live phase, 5 s poll]
BUILD --> READY[Home card: READY<br/>open your report]
READY --> CO[First view only:<br/>4 callouts on the report]
end
CO -->|Done, close, or Add my holdings| DONE[Flag set true<br/>normal dashboard forever]A Home Card With No State of Its Own
One pure function, GuidedFirstReport.DetermineStage, shared by ClientWeb and MAUI,
decides what the card shows from two facts that already exist: the account's jobs and the
platform's onboarding flag. Nothing extra is stored, so the card can't disagree with what actually
happened. A failed job, or a report built from the regular Generate page instead of the card,
shows up correctly.
| Onboarding flag | Jobs on the account | Stage | Card says |
|---|---|---|---|
true | anything | None | (no card: normal dashboard) |
false | none | Pick | "Pick a company you know" + 8 buttons |
false | only failed or scheduled | Pick | same, plus "your last report didn't finish, try again" |
false | one queued or running | Building | live phase line + progress, polled every 5 s |
false | at least one finished | Ready | "Your report is ready: Open it" |
The eight picks (Apple, Amazon, Costco, Microsoft, Nike, Coca-Cola, Disney,
Starbucks) are chosen in code, not queried. "Recognizable" is an editorial call, not a filter on
2,400 public tickers, and all eight are S&P 500 members that already have public
/grade/{TICKER} pages. Every stage has a quiet "Skip, I know my way around" link.
One Tap, and No Surprise Charges
GuidedFirstReportService lives in the shared REST client library, so web and mobile run
the identical sequence. It calls the same list and report endpoints the Generate Report page
uses, so every server-side rule still applies: email verification, the free tier's 8-symbol cap and
one-report-a-month allowance, billing, and ad attribution.
- Track the symbol in a one-symbol list named
First pick: Apple, reused if an earlier tap created it, so a cancelled confirmation doesn't leave duplicate lists behind. - Notify one address: the sign-up email gets "your report is ready", which brings back anyone who closed the tab.
- Check the price. For free and staff accounts it's $0. If an account with a card on file would be charged, nothing is queued until the card asks, "This report costs $0.30, billed to your card on file. Build it?" A one-tap flow shouldn't be able to charge someone by surprise, and unit tests cover that.
- Queue the report and flip the card to Building. The polling uses a
PeriodicTimerwith aCancellationToken, because Blazor WebAssembly ignoresHttpClient.Timeout(a lesson from an earlier spinner that never stopped).
Each refusal gets its own message rather than a generic error: the symbol cap ("remove a symbol or add a card"), the monthly allowance (the existing upgrade wording, with the symbol still tracked so its live grade appears anyway), or the server's own message, such as an unverified email.
Four Callouts, Using the Report's Own Numbers
| # | Title | Anchored to (web) | Says |
|---|---|---|---|
| 1 | This is the grade | the AI Grade badge | one letter, A+ to F, describing the company's financials today: not a prediction, not a recommendation |
| 2 | Here's what it's based on | the 0–100 score | start at 50, six factors from the company's own statements add or subtract points, "AAPL scored 93" |
| 3 | What a low mark on debt means | the grade badge | the company's actual debt reading, how that factor runs from +15 to −10, and its weakest factor if one scores ≤ 0 |
| 4 | Now grade what you own | Symbol Lists in the nav | everything tracked gets a live grade, 8 free, with an Add my holdings button |
- One source for the words.
FirstReportCallouts.Build(symbol, isMobile)generates the text for both platforms. It uses the report's real numbers when available and generic wording when not (an older report without a breakdown, a symbol with no grade). A unit test fails the build if the text contains buy/sell language. - Web reuses Shepherd.js, which was already loaded for the nav tour, as a third named tour (
first-report) fed the steps from C#. If an anchor is missing or hidden (the row was sorted away, or the nav is collapsed on a phone), the step falls back to a centered callout instead of pointing at nothing. - MAUI shows the same steps as a card at the top of the report (Next · Done · ✕). Mobile has no hover and no side nav to anchor to, so the callouts adapt to the platform instead of copying the web layout.
- No "remind me later". The callouts are four sentences, and showing them twice would be nagging. Done, ✕, Escape and "Add my holdings" all mark onboarding complete.
Decision: Change What the Flag Means, Not the Schema
A new HasCompletedGuidedFirstReport column would have been cleaner on paper and risky
in practice. Migrations here are applied by hand, and a column the code expects but prod lacks
breaks every account read. That's a real incident from August 2026. So the existing
per-platform flags (HasCompletedWebOnboarding / HasCompletedMobileOnboarding)
carry the whole feature, and what changed is their meaning:
| Before (April 2026) | After (Wave 20) | |
|---|---|---|
false means | hasn't clicked through Getting Started / the carousel | hasn't finished the guided first report |
Set true by | Getting Started, the carousel, or finishing the tour | finishing or dismissing the first-report callouts; "Skip" on the card |
Dashboard when false | redirects to /getting-started (web) / the carousel (mobile) | shows the guided card, no redirect |
| Getting Started / carousel | forced on first visit | one tap away in the nav / More tab, never forced, doesn't touch the flag |
| 22-step nav tour (web) | auto-started; completion set the flag | only via "Replay Guided Tour"; doesn't touch the flag |
Existing accounts need no migration either. Anyone who already clicked through has true
and sees nothing new. An older account that never did sees the Ready stage once and the callouts
once, which is intended. Per-platform independence is kept on purpose: finishing on web still shows
the callouts once on the first mobile report, where the layout is different. AdminWeb's Manage
Accounts page shows "Guided first report: done / not yet" per platform with a reset button, so the
flow can be re-tested without SQL.
The Public Walkthrough: A Real Company, With a Date
/walkthrough on the public site has four short beats over a real graded company: the
Apple worked example from Anatomy of a Grade (FY2025 10-K,
Aug 21, 2026 close, 50 + 10 + 10 + 5 + 15 + 0 + 3 = 93 → A+), shown with its date and
linked to Apple's live page for today's number. A real, dated, checkable example beats a mock-up
that drifts from the product. It even shows a weak spot honestly: Apple's current ratio of 0.89
earns zero points.
It ends with the same eight buttons. A tap stores the pick in browser storage and goes to
sign-in, and after sign-up the dashboard highlights "You picked Apple". Only the curated eight are
accepted (FirstReportPicks.Find), so a hand-edited value can't queue anything, and
nothing is generated until the user taps inside the app. The page exists in both the static-SSR
project (for crawlers and first visits) and the WASM app (for in-app navigation), following the
platform's static-SSR rule.
How Success Is Measured
Success means a beginner gets from sign-up to understanding one grade with nobody on a call, and when they don't, the data shows where they dropped off. New GA4 events fill the gap between "signed up" and "understood a grade", building an open funnel:
walkthrough_pick → sign_up → email_verified →
guided_pick → first_report_generated → first_report_ready →
first_report_viewed → first_report_callouts_done
Side events (guided_cost_prompt, guided_blocked with a reason,
guided_skipped, guided_next_step) explain the drop-offs. MAUI has no GA4
SDK, so it logs the same moments to the existing activity log that AdminWeb already reports on.
Known next steps, only if the data calls for them: a priority queue for an
account's first report if first_report_ready lags; a follow-up "add a fund you own"
stage if guided_next_step is low; and making the picks admin-editable if the names turn
out to matter.
The Reference Material Is Still There, Just Not Forced
The April 2026 onboarding wasn't deleted. It became optional reference material, one tap away:
| Web (Blazor WASM) | Mobile (.NET MAUI) | |
|---|---|---|
| Guided first report | GuidedFirstReportCard on the Dashboard; first-report Shepherd tour on the Report Dashboard | Guided card on DashboardPage; callout card on ReportDashboardPage |
| Getting Started | Page with video, 4-step workflow, pricing and billing explained; linked in the nav | CarouselView of swipeable slides; linked from More |
| Guided tour | 22-step Shepherd.js nav tour, "Replay Guided Tour" only | Syncfusion SfPopup tour, replay only |
| Completion flag | HasCompletedWebOnboarding | HasCompletedMobileOnboarding |
| Funnel telemetry | GA4 events (ga4-events.js) | Activity log (app source "MAUI") |
Account guards are unchanged on both platforms: no account → Create Account; unverified email →
Verify Email; the verification link activates the free account. The onboarding endpoints are the
existing IDOR-protected PATCH /accounts/{id}/complete-{web|mobile}-onboarding and
reset-{web|mobile}-onboarding. The guided flow added no endpoints and no migration.
Tests: 33 unit tests on the stage function, the callout builder and the one-tap starter (billing
safety, caps, entitlement walls, voice).