feat: onboarding prototype#7637
Conversation
Folder/identifier rename from "onboarding-aha" to "onboarding-quickstart" matches the design intent (fast path to first eval) rather than the internal product term. Includes the SCSS class prefix, the feature flag name (`onboarding_quickstart_flow`), the page component name, and the track-event namespace. New shape adds a role-selection step at the start (Engineer / PM / Other) and branches the flow: - Engineer: SDK install snippet + first-eval polling (existing) - PM: integrations grid (visual capability check) + invite-an-engineer - Other: skips the evaluation step entirely, drops to features page Other changes folded in: - CodeSnippet rebuilt with correct package names (`@Flagsmith/flagsmith`), minimal per-language snippets, and interpolation of the user's chosen feature name (not placeholder identifiers) - Custom toggle replaced with the existing `Switch` component - Skip button consolidated to the page header (was duplicated in each step footer) - "Choose for me" buttons removed from Org + Project steps — the pre-fill via `useSmartDefaults` does the same job - Per-block layout constraints (form steps narrow, evaluation step wide, page itself full-width) — drops the legacy 1280px page cap - text-secondary → text-muted across the flow to avoid the Bootstrap `$secondary` (#fae392) collision that fails contrast on light surfaces - "Aha" terminology dropped from step names — internal key `'evaluation'`, user-facing title "See it works" The activation signal (real first-SDK-eval detection) is still the polling stub from earlier — replacement signal needs to pull from Influx, tracked separately. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Keyboard accessibility:
- Enter on org / project / custom-feature inputs advances the step
- Step transition focuses the first interactive element of the new step
- RoleStep and FeatureStep now follow WAI-ARIA radiogroup pattern with
arrow-key navigation (ArrowUp/Down/Left/Right + Home/End), roving
tabindex, and `role='radio'` / `aria-checked` semantics
- First arrow press with no selection picks the focused option
instead of skipping past it
- Enter on a focused option (with a valid selection) advances —
saves a Tab + Enter to reach the Next/Finish button
- Visible `:focus-visible` outline restored within `.onboarding-quickstart`,
scoped to override the project-wide `.btn:focus-visible { box-shadow: none }`
rule that hides focus for sighted keyboard users
PM path content:
- "See it works" stepper label is role-aware: engineer keeps it,
PM gets "Connect your tools"
- PM evaluation step now shows a read-only integrations grid (visual
capability check) using the same data merge as `IntegrationSelect`
(Flagsmith remote-config `integration_data` + `Constants.integrationSummaries`,
deduped by title). Top 12 entries rendered as cards
- Reuse-the-whole `IntegrationSelect` was considered but discarded —
its select-tools interaction has no downstream effect today, which
would set wrong expectations at the AHA moment
- PM CTA copy: "Invite a teammate" (was "Invite an engineer")
New primitive:
- `BareButton` — a `<button>` reset for keyboard-accessible custom
surfaces (card rows, custom radios, icon-only triggers). Defaults
`type='button'`, provides a focus-visible outline. SCSS co-located.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
Code Review
This pull request introduces a new multi-step onboarding quickstart flow (AHA-first flow) gated by a feature flag, featuring custom wizard steps tailored to user roles, a slide-out progress drawer, and minimal SDK code snippets for various languages. While the implementation is clean and well-structured, several critical issues need to be addressed: unsafe JSON parsing of remote-config integration data could crash the flow for PM users; syntax errors in the generated C# code snippet will cause compiler errors; the FORCE_ON flag remains hardcoded to true; and the final redirection URL incorrectly uses display names and API keys instead of project and environment IDs.
| const pmIntegrations = useMemo(() => { | ||
| if (role !== 'pm') return [] | ||
| const remote = Utils.getFlagsmithValue('integration_data') | ||
| if (typeof remote !== 'string' || !remote) { | ||
| return Constants.integrationSummaries.slice(0, 12) | ||
| } | ||
| const merged = uniqBy( | ||
| Object.values(JSON.parse(remote)).concat( | ||
| Constants.integrationSummaries, | ||
| ) as IntegrationSummary[], | ||
| (v) => v.title.toLowerCase(), | ||
| ) | ||
| return merged.slice(0, 12) | ||
| }, [role]) |
There was a problem hiding this comment.
The parsing of the remote-config value integration_data is done using JSON.parse and Object.values without any safety guards or error handling. If integration_data is malformed, null, or not a dictionary, this will throw a runtime exception and completely crash the onboarding flow for PM users.
Wrap the parsing logic in a try...catch block and validate that the parsed result is an object/array before calling Object.values or concatenating.
const pmIntegrations = useMemo(() => {
if (role !== 'pm') return []
const remote = Utils.getFlagsmithValue('integration_data')
if (typeof remote !== 'string' || !remote) {
return Constants.integrationSummaries.slice(0, 12)
}
try {
const parsed = JSON.parse(remote)
if (!parsed || typeof parsed !== 'object') {
return Constants.integrationSummaries.slice(0, 12)
}
const remoteValues = Array.isArray(parsed) ? parsed : Object.values(parsed)
const merged = uniqBy(
remoteValues.concat(
Constants.integrationSummaries,
) as IntegrationSummary[],
(v) => v.title?.toLowerCase(),
)
return merged.slice(0, 12)
} catch (e) {
// eslint-disable-next-line no-console
console.error('Failed to parse integration_data', e)
return Constants.integrationSummaries.slice(0, 12)
}
}, [role])
| case 'dotnet': | ||
| return `var flagsmith = new FlagsmithClient(new FlagsmithConfiguration { | ||
| EnvironmentKey = "${environmentKey}" | ||
| }); | ||
| var flags = await flagsmith.GetEnvironmentFlags(); | ||
| Console.WriteLine($"${featureName}: {await flags.IsFeatureEnabled(\\"${featureName}\\")}");` |
There was a problem hiding this comment.
There are two issues in the generated C# (dotnet) code snippet that will cause compiler errors:
- The first
$is escaped as\$, which outputs literal${featureName}in the C# code. SincefeatureNameis not a defined variable in the C# scope, this will fail to compile. It should be unescaped so that the actual feature name is interpolated at generation time. - The double quotes inside the interpolation hole
{}are escaped as\". In C# interpolated strings, escaping quotes inside the expression hole is a syntax error. They should be unescaped double quotes".
Let's correct the snippet to generate valid, compilable C# code.
| case 'dotnet': | |
| return `var flagsmith = new FlagsmithClient(new FlagsmithConfiguration { | |
| EnvironmentKey = "${environmentKey}" | |
| }); | |
| var flags = await flagsmith.GetEnvironmentFlags(); | |
| Console.WriteLine($"${featureName}: {await flags.IsFeatureEnabled(\\"${featureName}\\")}");` | |
| case 'dotnet': | |
| return `var flagsmith = new FlagsmithClient(new FlagsmithConfiguration { | |
| EnvironmentKey = "${environmentKey}" | |
| }); | |
| var flags = await flagsmith.GetEnvironmentFlags(); | |
| Console.WriteLine($"${featureName}: {await flags.IsFeatureEnabled("${featureName}")}");` |
|
|
||
| // TEMP: forced on for local validation. Revert to `Utils.getFlagsmithHasFeature(...)` | ||
| // before merge so the flag actually gates the flow. | ||
| const FORCE_ON = true |
There was a problem hiding this comment.
The FORCE_ON flag is hardcoded to true for local validation. As noted in the comments and PR description, this needs to be reverted to use the actual feature flag check before merging to production to avoid exposing the unfinished flow prematurely.
| const FORCE_ON = true | |
| const FORCE_ON = false |
| const finishedDestination = | ||
| environmentKey && projectName | ||
| ? `/project/${projectName}/environment/${environmentKey}/features` | ||
| : '/organisations' |
There was a problem hiding this comment.
The finishedDestination URL is constructed using projectName and environmentKey (which is the API key) as path parameters:
/project/${projectName}/environment/${environmentKey}/features
However, the application routes (defined in routes.js) expect numeric/alphanumeric IDs for :projectId and :environmentId (e.g., /project/:projectId/environment/:environmentId/features). Using display names or API keys in the URL path will cause routing or API fetch failures.
Please ensure that the real implementation uses the actual projectId and environmentId instead of the display name and API key.
- Rename role title from "Something else" to "Other" - Drop the evaluation step from the stepper when Other is picked — they never reach it, so it shouldn't appear on the timeline - Make finishing as Other actually land on the features page; previously the stale environmentKey closure routed them back to /organisations Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…feature steps - Show 'My first project' as placeholder text instead of pre-filled content, falling back to it when the field is left blank. - Disallow spaces in the custom feature name, mirroring the main app's feature-ID constraint (spaces -> underscores). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…nment creation Adds createOrganisation, createProject and createEnvironment RTK Query mutations — previously these only existed in the legacy Flux stores (account-store / organisation-store). These are the foundation for wiring the onboarding create chain to real APIs without leaning on the stores the project is migrating off. Also documents the backend integration plan for the flow. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replaces the DEMO_ENVIRONMENT_KEY stub with the real create chain: reuse-or-create organisation -> create project -> create Development + Production environments -> create the first feature flag, then land on the project's features page. - Organisation is hybrid: reuse the already-selected org (common after signup) and skip the org step; create + select one only when none exists. Org selection state is Flux/Redux-owned, so a pure-RTK create would leave the shell unaware of the new org. - Fixes the features URL to use the numeric project id and the environment api_key (was wrongly using the project name as a slug). - Step navigation is now array-driven so it stays correct as steps are added/removed (org skip, role branching). - Per-step error handling surfaces failures via ErrorMessage. - Refreshes the legacy organisation store so the shell picks up the new project without a reload. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… org-create coherence When onboarding_quickstart_flow is enabled, post-signup users with no organisation are routed to /getting-started (the new flow creates the org as its first step) instead of the legacy /create page. Flag off = unchanged /create behaviour. Also fixes the create-org path to go through the account store (AppActions.createOrganisation + select) rather than the RTK mutation. Much of the shell still reads the current organisation from AccountStore, which a pure-RTK create leaves empty — verified end-to-end that this caused org-scoped calls to fire with undefined/garbage ids. Routing via the account store populates it so the new org id flows correctly. Verified on staging: fresh signup -> /getting-started -> create org -> project -> Development+Production envs -> first flag -> features page, with the new org/project coherent in the shell. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Removes the FORCE_ON local-validation override so GettingStartedSwitch
gates on Utils.getFlagsmithHasFeature('onboarding_quickstart_flow').
Until the flag exists/is enabled on Flagsmith-on-Flagsmith, the check
returns false and the existing GettingStartedPage renders (safe default).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Tracks entities already created (org, project, Dev/Prod envs, feature) in a ref so that retrying after a partial failure — e.g. project created but an environment call failed — reuses them instead of creating duplicates. On a fresh run the ref is empty and every step runs as before; only retries skip already-completed steps. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The project step let Next stay enabled while the field was empty (it fell back to the placeholder), yet still rendered the empty field with the red invalid border — a contradiction. Require a name instead: Next is disabled until one is entered, so the red border correctly signals "needs input". The placeholder remains a hint, not a fallback. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…loyment Per prototype feedback: only surface the invite CTA where it makes sense — self-hosted (any plan) or paid SaaS. On the free SaaS plan it's hidden rather than shown with an upgrade nudge, which would be friction at the success moment. When hidden, "Explore the dashboard" becomes the primary CTA and the supporting copy drops its invite reference (success panel and the PM intro paragraph). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ng the flow The quickstart onboarding is a focused surface; the global Announcement / AnnouncementPerPage promo banners (e.g. event/workshop CTAs) are a distraction there. Suppress them while on /getting-started when the quickstart flow is enabled. Other pages and the legacy getting-started page are unaffected. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The quickstart onboarding is a focused, isolated surface — render only the flow, bypassing the Nav shell (top bar, sidebar, project/account/docs links) so the customer can't navigate away mid-onboarding. The flow keeps its own explicit "Skip — set up manually" escape. Only applies on /getting-started when the quickstart flag is enabled; every other route renders the full shell as before. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The onboarding flow is now reached via the feature-flagged route + post-signup routing, so the temporary "Test onboarding" quick link is no longer needed. Also drops the dead commented-out "Getting Started" NavLink that was left for easy revert; the OnboardingChipWithDrawer stays as the nav's getting-started element. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Addresses prototype feedback on step 1:
- Replace the named 5-step stepper with a slim progress bar ("Step X of Y"
+ thin fill). The labelled stepper made the flow look like more work
than it is and conflicted with the "2 minutes" promise; the bar also
reflects the real, variable step count (org/eval steps are skipped in
some flows).
- Drop the vague "We'll tailor what you see next…" subtitle and instead
put a concrete 3-5 word focus on each role button (Engineer — "Get the
SDK working", Product manager — "Manage flags, no code", Other —
"Explore the dashboard"), so the choice is meaningful rather than
marketing-flavoured.
StepShell now renders its subtitle only when provided.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…oggle) Introduces FlagDemo — the "see your flag work" AHA as a self-contained, context-agnostic component: the SDK snippet (reusing CodeSnippet), a live SampleAppPreview whose "New feature" element the flag controls, and a toggle, all in one view. It takes the flag inputs and emits a toggle event; the host owns the framing/CTAs, so the same component can be the stepped flow's final step and the centrepiece of the single-page flow. Slots it into the engineer path of step 5, replacing the mocked status panel + first-eval poll (the fabricated "3 evaluations" Matt flagged) and the separate snippet. v1 is simulated — the toggle drives local state; the interface is unchanged when upgraded to a real SDK eval. Removes the now-orphaned StatusPanel and useFirstEvaluationPoll. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…honest verify) Implements the combined single-page design: header, pre-created resources line (org/project/flag), a Connect card with "Connect with AI" / "Connect manually" tabs, an honest waiting→connected verify signal, the flag toggle, and a dashboard link. - AI tab: a copyable prompt that carries the pre-created env key + flag (the no-MCP path — the user's coding agent wires it in). - Manual tab: reuses CodeSnippet. - The verify signal stays in the waiting state and only goes green via the `connected` prop — wired to the real "first request" signal when it exists, never faked. Presentational/props-driven for now; data wiring (auto-create chain) and the multivariate variant gating are the next steps. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…iant gating - useEnsureOnboardingResources: ensures the flow has real, pre-created resources (reuse-or-create org → project → Dev/Prod envs → first flag), reusing the same create chain as the stepped flow. Waits for the orgs query to settle before reuse-vs-create to avoid a duplicate-org race. - Extracts createOrganisationViaAccountStore into a shared module. - OnboardingSinglePageContainer: runs the hook, shows a loading/error state, and feeds the resources + dashboard destination to the page. - GettingStartedSwitch now reads the multivariate onboarding_experience flag (control / stepped / single_page), with the legacy boolean onboarding_quickstart_flow as a fallback. Missing/control → unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ent setup Single-page onboarding improvements: - Make the flag toggle real: it reflects the flag's actual enabled state in the Development environment and persists toggles via the shared feature-state path (incl. v2 feature versioning), instead of a cosmetic local switch that showed "On" while the flag was off. - Inline-edit organisation and project names via the shared GhostInput, persisting renames with a single-field PATCH. - Make resource setup idempotent: reuse an existing org/project/environment/ flag on a return visit instead of recreating, fixing a 403 raised when the plan's org cap rejected a second org create. - Inject the API base URL into the generated AI prompt when not on SaaS (staging/self-hosted), and add an honest "verify, don't fabricate" instruction so agents don't claim success without observing an evaluation. - Fix GhostInput clipping the last character(s) (offsetWidth * 0.95 -> + 2). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…-adaptive code
Single-page onboarding, manual ("Connect manually") path:
- Replace the generic snippet with a focused JS/React example that shows the
real payoff: install, then wire the SDK and gate a demo button on the user's
actual flag (useFlags + conditional render in React; hasFeature + reveal in
JS) — referencing the real flag, not placeholder names.
- Render via Highlight with escaping on, so JSX (e.g. <FlagsmithProvider>,
<button>) displays as highlighted text and Copy yields the raw code. CodeHelp
couldn't be used here: it renders via innerHTML with escaping off, which
strips JSX tags and would copy < entities.
- Single React/JavaScript toggle drives both steps.
- Inject the SDK api option only when off the default SaaS endpoint
(staging / self-hosted), matching the AI prompt's behaviour.
AI path:
- Shorten the generated prompt to the essentials (key, flag, API URL when
custom) plus the one honesty instruction, so it reads less like a wall.
Styling:
- Theme-adaptive code blocks scoped to onboarding: light syntax palette +
light scrollbar in light mode, the app's dark palette restored in dark mode.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…prototype Hardcodes the onboarding_experience variant to single_page so the flow can be demoed without the multivariate flag being set. Revert before merge — the real gate is the onboarding_experience flag. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Thanks for submitting a PR! Please check the boxes below:
docs/if required so people know about the feature.Changes
Contributes to #7544
Working prototype of the role-branched onboarding quickstart flow, gated behind the
onboarding_quickstart_flowfeature flag (FORCE_ON = truefor local validation — flip toUtils.getFlagsmithHasFeature('onboarding_quickstart_flow')before merge).Flow shape
Keyboard navigation (per #7544 hard requirement)
role='radio'/aria-checkedsemantics:focus-visibleoutline restored (scoped) — the project-wide.btn:focus-visible { box-shadow: none }in_buttons.scsshides focus for sighted keyboard users; this PR scopes a restoration to.onboarding-quickstartwithout touching the global ruleOther notable changes
CodeSnippetrewritten with minimal per-language snippets, correct package names (@flagsmith/flagsmith), and the user's chosen feature name interpolatedBareButtonprimitive atweb/components/base/BareButton.tsxfor keyboard-accessible custom-styled surfaces (consumed in follow-up)text-mutedused in place oftext-secondarythroughout to avoid the Bootstrap\$secondary: #fae392collisionOut of scope (per issue)
handleFinishstill stubs the environment keyuseFirstEvaluationPollis placeholder polling; the real signal needs to pull from Influx (since flag evals go through the edge API now) and is tracked separatelyHow did you test this code?
GettingStartedSwitchworks (currentlyFORCE_ON = true)