fix(onboarding): scope tour-launcher sessionStorage keys by user id (v1.4.15 H4)

Both `healthlog-tour-session-dismissed` and `healthlog-tour-referrer`
were un-scoped global keys, so a browser shared by two HealthLog
identities (admin impersonating a target user, family laptop, etc.)
inherited the first user's "tour dismissed for this session" flag —
the second user logged in and silently never saw the spotlight tour.

Both keys are now suffixed with `:${userId}`, so each identity's
state stays independent. The two builders (`tourSessionDismissedKey`
and `tourReferrerKey`) are exported so the wizard's `persistAndExit`
shares the same wire format and a unit test pins the scheme against
future drift.

One-off cost: every currently-open tab re-fires the tour once on the
next load because the legacy un-prefixed keys are no longer read.
Acceptable tax for multi-tenant correctness ahead of v1.5.
This commit is contained in:
Marc-André Bombeck committed 2026-05-10 00:27:20 +02:00
1 parent 7f1a4ded25
commit 2afe3c47ce
3 files changed
+109 -34

No files matched your search

+16 -4
View File
@@ -19,6 +19,8 @@ import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Logo } from "@/components/ui/logo";
import { MeasurementForm } from "@/components/measurements/measurement-form";
import { tourReferrerKey } from "@/components/onboarding/tour-launcher";
import { useAuth } from "@/hooks/use-auth";
import { useTranslations } from "@/lib/i18n/context";
import { locales, localeLabels, type Locale } from "@/lib/i18n/config";
import { cn } from "@/lib/utils";
@@ -74,6 +76,7 @@ export default function OnboardingPage() {
const router = useRouter();
const queryClient = useQueryClient();
const { t, locale, setLocale } = useTranslations();
const { user } = useAuth();
const [step, setStep] = useState<1 | 2 | 3>(1);
const [saving, setSaving] = useState(false);
@@ -129,10 +132,19 @@ export default function OnboardingPage() {
// render-phase decision can stay pure under
// `react-hooks/purity` — the launcher consumes-and-clears the
// key on first read.
try {
window.sessionStorage.setItem("healthlog-tour-referrer", "1");
} catch {
/* ignore — storage disabled or full */
//
// v1.4.15 H4: key is scoped by user id so a shared browser
// (admin impersonation, family laptop) keeps each user's
// wizard-referrer state independent. Skip the write entirely
// if the auth payload hasn't loaded — the worst-case is the
// tour pops up immediately instead of after the 1.5s grace
// window, which is harmless.
if (user?.id) {
try {
window.sessionStorage.setItem(tourReferrerKey(user.id), "1");
} catch {
/* ignore — storage disabled or full */
}
}
if (opts?.gotoSettingsHash) {