Skip to content

feat(onboarding): run the tour against the project named in the URL - #8229

Closed
talissoncosta wants to merge 2 commits into
fix/onboarding-skip-flag-creation-existing-projectfrom
feat/onboarding-use-current-project
Closed

feat(onboarding): run the tour against the project named in the URL#8229
talissoncosta wants to merge 2 commits into
fix/onboarding-skip-flag-creation-existing-projectfrom
feat/onboarding-use-current-project

Conversation

@talissoncosta

@talissoncosta talissoncosta commented Aug 5, 2026

Copy link
Copy Markdown
Contributor
  • I have read the Contributing Guide.
  • I have added information to docs/ if required so people know about the feature.
  • I have filled in the "Changes" section below.
  • I have filled in the "How did you test this code" section below.

Changes

Stacked on #8217. Suggested by @Zaimwa9.

/getting-started carries no project, so the tour ran against the org's first one. You could be inside project B, click Getting Started, and see project A.

  • The nav link now names the project: the one you're in (TopNavbar already has projectId), else the one you were last in.
  • The page runs against that project, but only if it's in this organisation's list. Otherwise it falls back, so a link carried across orgs or an edited URL can't point the tour somewhere it shouldn't.
  • A bare /getting-started, which is what a fresh signup gets, behaves as before.

A query param rather than a nested route: /getting-started has to work before any project exists. /project/:id/environment/:key/getting-started is the better shape and would delete this selection code, but it needs an entry that provisions and redirects, plus a decision on whether the tour keeps its chromeless layout. Worth doing with the drawer.

Environment choice is unchanged, still Development or the first one. lastEnv holds an environment too, but using it would mean telling someone to wire up production during a tour.

How did you test this code?

  • 12 unit tests: URL project wins, id matched as a string, id outside the org ignored, no param, empty org, plus parseLastEnv against absent and malformed storage

  • test:unit (406), typecheck and lint clean

  • Inside project B: link carries ?project=<B> and the tour shows B

  • Outside a project: falls back to the one you were last in

  • After switching org: no stale project from the previous one

  • URL edited to another org's project: falls back

  • New signup: unchanged

Visiting /getting-started created show_demo_button in whichever project came
back first, whether or not the customer had ever onboarded. Features are
project-level, so it appeared in every environment of that project, including
production, alongside a new Onboarding tag. The Getting Started nav link is
ungated, so any customer could trigger this by clicking it.

ensureFlag already computed isFirstFeature for analytics; it now also decides
whether to create anything. An empty project still gets the demo flag, an
established one gets nothing.

That leaves the tour with no flag to teach with, so the page says so and points
at the project's own flags instead of walking someone through connecting a
project that is already connected.

Copy and treatment of that state are provisional.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@vercel

vercel Bot commented Aug 5, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
flagsmith-frontend-preview Ready Ready Preview Aug 5, 2026 6:29pm
flagsmith-frontend-staging Ready Ready Preview Aug 5, 2026 6:29pm
1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Ignored Ignored Preview Aug 5, 2026 6:29pm

Request Review

@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 6df6d0a9-98da-4055-bb50-7ba3043e25d5

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • ✅ Review completed - (🔄 Check again to review again)

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

/getting-started carries no project, so the tour took the org's first project.
For an established customer that is arbitrary: you can be inside project B, click
Getting Started, and see project A.

The entry URL now names it. The nav link carries the project you are in, or the
one you were last in, and the page runs against that. An id that is not in this
organisation's list is ignored rather than trusted, and a bare /getting-started
(a fresh signup) still falls back to the first project or creates one.

A query param rather than a nested route because /getting-started is an entry
point, not a project-scoped page: it has to work before any project exists. A
route like /project/:id/environment/:key/getting-started is the better long-term
shape, but it needs an entry that provisions and redirects, and a decision about
whether the tour keeps its chromeless layout.

Environment choice is unchanged, still Development or the first one. Sending
someone to wire up production during a tour would be worse than the problem.

Suggested by @Zaimwa9.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@talissoncosta talissoncosta changed the title feat(onboarding): run the tour against the project you were last in feat(onboarding): run the tour against the project named in the URL Aug 5, 2026
@talissoncosta
talissoncosta force-pushed the feat/onboarding-use-current-project branch from 0495abd to eb26894 Compare August 5, 2026 18:28
@github-actions github-actions Bot added feature New feature or request and removed feature New feature or request labels Aug 5, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: a77f25a7-9761-4396-8872-5cf2ee7f58e0

📥 Commits

Reviewing files that changed from the base of the PR and between 3f8c70c and eb26894.

📒 Files selected for processing (7)
  • frontend/common/hooks/__tests__/useLastEnv.test.ts
  • frontend/common/hooks/useLastEnv.ts
  • frontend/web/components/navigation/navbars/TopNavbar.tsx
  • frontend/web/components/pages/onboarding/hooks/__tests__/onboardingProject.test.ts
  • frontend/web/components/pages/onboarding/hooks/bootstrapOnboarding.ts
  • frontend/web/components/pages/onboarding/hooks/onboardingProject.ts
  • frontend/web/components/pages/onboarding/hooks/useEnsureOnboardingResources.ts

Comment on lines +20 to +28
export const useLastEnv = (): LastEnv | null => {
const [lastEnv, setLastEnv] = useState<LastEnv | null>(null)

useEffect(() => {
if (typeof AsyncStorage === 'undefined') return
Promise.resolve(AsyncStorage.getItem('lastEnv')).then((raw) =>
setLastEnv(parseLastEnv(raw)),
)
}, [])

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

fd -t f '(usePageTracking|TopNavbar|useLastEnv)\.(ts|tsx)$' frontend |
  while IFS= read -r file; do
    ast-grep outline "$file" --items all
  done

rg -n -C 5 -g '*.ts' -g '*.tsx' \
  'AsyncStorage\.setItem\(|lastEnv|useLastEnv\s*\(|usePageTracking\s*\(' frontend

Repository: Flagsmith/flagsmith

Length of output: 15096


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== useLastEnv =="
cat -n frontend/common/hooks/useLastEnv.ts

echo
echo "== usePageTracking relevant section =="
sed -n '1,90p' frontend/common/hooks/usePageTracking.ts | cat -n

echo
echo "== TopNavbar full =="
cat -n frontend/web/components/navigation/navbars/TopNavbar.tsx

echo
echo "== App references =="
rg -n -C 4 -g '*.ts' -g '*.tsx' 'useLastEnv|lastEnv|getting-started|onboardingProjectId' frontend/web frontend/common

echo
echo "== Route context environment/project assignment areas =="
rg -n -C 3 -g '*.ts' -g '*.tsx' 'organisationId|environmentId|projectId|routeContext|setRouteContext' frontend/web frontend/common | head -n 220

Repository: Flagsmith/flagsmith

Length of output: 37713


Keep lastEnv changes in a single source of truth.

useLastEnv only reads storage when it mounts, while usePageTracking, EnvironmentAside, and Nav can write new lastEnv values. TopNavbar can then render the Getting Started link with a project that is no longer the last selected one. Move shared lastEnv state into common/store.ts and make writers update that state when they persist to storage.

Sources: Coding guidelines, Learnings

Comment on lines +23 to +27
useEffect(() => {
if (typeof AsyncStorage === 'undefined') return
Promise.resolve(AsyncStorage.getItem('lastEnv')).then((raw) =>
setLastEnv(parseLastEnv(raw)),
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Handle failed storage reads.

If AsyncStorage.getItem('lastEnv') rejects or throws, this effect creates an unhandled error. Set the fallback state to null when the read fails.

Proposed fix
-    Promise.resolve(AsyncStorage.getItem('lastEnv')).then((raw) =>
-      setLastEnv(parseLastEnv(raw)),
-    )
+    void Promise.resolve()
+      .then(() => AsyncStorage.getItem('lastEnv'))
+      .then((raw) => setLastEnv(parseLastEnv(raw)))
+      .catch(() => setLastEnv(null))
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
useEffect(() => {
if (typeof AsyncStorage === 'undefined') return
Promise.resolve(AsyncStorage.getItem('lastEnv')).then((raw) =>
setLastEnv(parseLastEnv(raw)),
)
useEffect(() => {
if (typeof AsyncStorage === 'undefined') return
void Promise.resolve()
.then(() => AsyncStorage.getItem('lastEnv'))
.then((raw) => setLastEnv(parseLastEnv(raw)))
.catch(() => setLastEnv(null))

@talissoncosta
talissoncosta force-pushed the fix/onboarding-skip-flag-creation-existing-project branch from 3f8c70c to e66002b Compare August 5, 2026 20:00
@talissoncosta

Copy link
Copy Markdown
Contributor Author

Closing this one. Walking the cases, it buys less than it looked like:

  • A fresh signup has no ?project= on the URL and one project, so projects[0] is already the right answer.
  • An established customer clicking the nav link lands on a project that almost certainly has flags, so fix(onboarding): don't seed a demo flag into a project that has flags #8217 shows them the already-set-up state either way. More predictable, same destination.
  • It only genuinely helps with two or more projects while you're inside a new empty one and projects[0] is an established one.

The change that actually fixes "established customers shouldn't be in the tour" is gating the nav link on whether the account has already evaluated a flag. Once that lands, everyone who can reach /getting-started is a new user with a single project, and ?project= equals projects[0] for all of them. Worth revisiting if the onboarding drawer re-opens the entry point to established accounts.

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

Labels

feature New feature or request front-end Issue related to the React Front End Dashboard

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant