Skip to content

feat(onboarding): smart step filtering and stability fixes#84

Open
mtlouzada wants to merge 6 commits intoSkateHive:mainfrom
mtlouzada:feat/onboarding
Open

feat(onboarding): smart step filtering and stability fixes#84
mtlouzada wants to merge 6 commits intoSkateHive:mainfrom
mtlouzada:feat/onboarding

Conversation

@mtlouzada
Copy link
Copy Markdown

@mtlouzada mtlouzada commented Apr 23, 2026

Summary

  • Step filtering: photo step is skipped when user already has a non-dicebear avatar; bio step is skipped when user.bio is already filled — covers users signing up via Hive, Farcaster, MetaMask or email who already have profile data
  • Existing users protected: ONBOARDING_LAUNCH_DATE = 2026-04-22 gates the entire flow — accounts created before this date never see onboarding, no database migration required
  • Modal stability: removed isLoading from OnboardingDetector guard to prevent modal unmount during background session refreshes (window focus/visibility events were triggering maybeRefresh, resetting modal state mid-flow)
  • Window remount fix: passes windowId="onboarding-modal" to SkateModal so the title change between welcome screen and step view no longer causes a full unmount
  • Bitmask sync: silently PATCHes onboarding_step_flag when pre-existing avatar or bio data is detected, keeping the server state consistent
  • Dynamic CTA: floating card button shows the specific pending step label when only one step remains (e.g. "Add a photo") instead of always showing "finish setup"
  • Context updates: added bio and created_at to UserbaseUser type and session API queries; updated areUsersEqual to include both fields

Test plan

  • New account (post 2026-04-22) — onboarding auto-opens after 1.2s
  • Account with Hive/Farcaster avatar — photo step is skipped
  • Account with bio already filled — bio step is skipped
  • Account created before 2026-04-22 — onboarding never appears
  • Upload photo in modal — modal stays open after file picker closes (no remount)
  • Complete last pending step — floating card disappears
  • Floating card CTA shows step label when only one step is pending

Summary by CodeRabbit

  • New Features
    • Introduced guided onboarding experience for new users with steps for setting profile photo, adding bio, and creating an initial post
    • Added bio field to user profiles for storing personal information
    • Enhanced session data to include user biography and account creation date

- Add OnboardingModal with welcome screen + 3 steps (photo, bio, intro snap)
- Add OnboardingDetector with persistent floating card for incomplete steps
- Track progress via bitmask (onboarding_step_flag) in profile PATCH API
- Post intro as a snap (comment on peak.snaps container) not a blog post
- Hot-swap photo preview: instant blob URL replaced by IPFS URL on upload
- Add HIVE_DRY_RUN env flag to skip broadcasts in local dev
- Add dev-only TLS bypass for nodemailer self-signed cert issue
- Mount OnboardingDetector globally in RootLayoutClient
- Use bitmask equality check instead of >= for onboarding completion
- Guard HIVE_DRY_RUN behind NODE_ENV=development to prevent use in staging/CI
- Remove soft post status update in dry-run (no broadcast = no attribution)
- Narrow TLS bypass from !== production to === development
- Filter photo step from pendingSteps when user.avatar_url is set
- Sync ONBOARDING_FLAG_PHOTO to server silently on mount if needed
- Use useCallback for saveToServer to stabilize deps in useEffect
- Welcome screen now iterates pendingSteps instead of hardcoded list
- Skip photo step when user already has a non-dicebear avatar
- Skip bio step when user.bio is already filled
- Sync bitmask flags silently to server for pre-existing data
- Extract DICEBEAR_URL_PATTERN constant shared across both components
- Filter existing users via ONBOARDING_LAUNCH_DATE (2026-04-22)
- Add bio and created_at fields to UserbaseUser context type
- Fix areUsersEqual to include bio and created_at comparisons
- Remove isLoading from OnboardingDetector guard to prevent modal
  unmount during background session refreshes (focus/visibility events)
- Pass windowId="onboarding-modal" to prevent remount on title change
- Dynamic ctaLabel in floating card: shows pending step name when only one remains
@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 23, 2026

@mtlouzada is attempting to deploy a commit to the sktbrd's projects Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 23, 2026

Warning

Rate limit exceeded

@mtlouzada has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 22 minutes and 45 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 22 minutes and 45 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 399fa418-ee20-4b78-9f79-ca508aca1a59

📥 Commits

Reviewing files that changed from the base of the PR and between 32b90e0 and 8ba734f.

📒 Files selected for processing (2)
  • components/onboarding/OnboardingDetector.tsx
  • components/onboarding/OnboardingModal.tsx
📝 Walkthrough

Walkthrough

A comprehensive onboarding system for new users has been added, featuring client-side detector and modal components that guide users through photo upload, bio creation, and post submission. Supporting API routes, context fields, and development-mode utilities have been updated to enable this flow.

Changes

Cohort / File(s) Summary
Onboarding Components
components/onboarding/OnboardingDetector.tsx, components/onboarding/OnboardingModal.tsx
New client components implementing a multi-step onboarding flow with session-based modal triggering, bitmask-based progress tracking, IPFS photo upload, bio entry, Hive comment posting, and floating checklist card UI.
Root Layout Integration
app/RootLayoutClient.tsx
Dynamically loads and mounts OnboardingDetector as a global client-side detector alongside existing modals.
User Data API Routes
app/api/userbase/auth/session/route.ts, app/api/userbase/profile/route.ts
Session endpoint now returns bio and created_at fields; profile endpoint accepts onboarding_step_flag parameter to set bitmask flags without clearing existing bits.
Development Utilities & Blockchain
app/api/userbase/auth/magic-link/route.ts, app/api/userbase/hive/comment/route.ts
Magic-link Nodemailer transport disables TLS certificate verification in development; Hive comment endpoint supports dry-run mode logging in development environment.
User Type Context
contexts/UserbaseAuthContext.tsx
Updated UserbaseUser type to include bio and created_at fields; areUsersEqual comparison now accounts for these new fields.

Sequence Diagram(s)

sequenceDiagram
    participant Browser
    participant OnboardingDetector
    participant API
    participant IPFS
    participant Hive as Hive RPC
    participant User DB

    Browser->>OnboardingDetector: Render (check sessionStorage)
    alt Session seen before
        OnboardingDetector->>Browser: Return null
    else First visit
        OnboardingDetector->>API: Fetch user (bio, created_at, onboarding_step)
        API->>User DB: Query user fields
        User DB-->>API: Return user data
        API-->>OnboardingDetector: User with progress status
        OnboardingDetector->>OnboardingDetector: Compute pending steps (bitmask)
        OnboardingDetector->>Browser: Auto-open OnboardingModal (1.2s delay)
    end
    
    rect rgba(100, 150, 255, 0.5)
    Note over Browser,User DB: User completes photo step
    Browser->>OnboardingDetector: Upload photo
    OnboardingDetector->>IPFS: Upload blob
    IPFS-->>OnboardingDetector: IPFS hash (avatar_url)
    OnboardingDetector->>API: PATCH profile (avatar_url + photo flag)
    API->>User DB: Update user
    User DB-->>API: Confirmed
    API-->>OnboardingDetector: Updated user
    end
    
    rect rgba(150, 255, 100, 0.5)
    Note over Browser,User DB: User completes bio step
    Browser->>OnboardingDetector: Submit bio text
    OnboardingDetector->>API: PATCH profile (bio + bio flag)
    API->>User DB: Update user
    User DB-->>API: Confirmed
    API-->>OnboardingDetector: Updated user
    end
    
    rect rgba(255, 200, 100, 0.5)
    Note over Browser,Hive: User completes post step
    Browser->>OnboardingDetector: Submit post
    OnboardingDetector->>Hive: Query post parent permlink
    Hive-->>OnboardingDetector: Permlink data
    OnboardingDetector->>API: POST comment (Hive operation)
    API-->>OnboardingDetector: Success
    OnboardingDetector->>API: PATCH profile (post flag)
    API->>User DB: Update user (onboarding_step = 7)
    User DB-->>API: Confirmed
    API-->>OnboardingDetector: Updated user
    OnboardingDetector->>Browser: Close modal, set sessionStorage done
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~65 minutes

Poem

🐰 Hop along, new friends, through steps we've designed,
Photos, words, and posts intertwined,
Our checklist bounces in corners so bright,
Welcoming you to the hive's delight! 🌟✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately captures the main changes: smart step filtering (photo/bio skipping) and stability fixes (modal remount, isLoading guard removal, bitmask sync).
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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 and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/api/userbase/profile/route.ts`:
- Around line 265-275: The current read-then-write on onboarding_step (the block
using onboarding_step_flag, fetching userbase_users.onboarding_step into
currentStep and setting updateData.onboarding_step = currentStep |
onboarding_step_flag) can lose bits under concurrency; replace that logic with
an atomic DB-side bitwise OR via a Postgres RPC: add a migration that creates a
function like set_onboarding_flag(p_user_id uuid, p_flag int) returning the
updated onboarding_step, then call it from route.ts with
supabase.rpc('set_onboarding_flag', { p_user_id: userId, p_flag:
onboarding_step_flag }) and use the returned onboarding_step (or rely on the RPC
result) instead of doing the local read/OR/write so the OR happens inside the DB
in one transaction.

In `@components/onboarding/OnboardingDetector.tsx`:
- Line 105: The current check in OnboardingDetector uses new
Date(user.created_at) < ONBOARDING_LAUNCH_DATE but doesn't guard against
missing/invalid created_at; parse user.created_at into a Date (e.g., createdAt =
new Date(user.created_at)) and explicitly validate it
(isNaN(createdAt.getTime()) or !user?.created_at) and if it is missing/invalid
treat it as pre-launch by returning null (fail closed); update the conditional
around user.created_at / createdAt so only a valid date that is >=
ONBOARDING_LAUNCH_DATE allows onboarding to be shown.

In `@components/onboarding/OnboardingModal.tsx`:
- Line 265: Replace all hardcoded user-facing strings in the OnboardingModal
component with translations from useTranslations(namespace) (import from
`@/lib/i18n/hooks`); create corresponding keys in the translation files for
modalTitle, toastTitle, stepLabel, stepDescription, placeholders, CTA labels,
etc., and call const t = useTranslations('onboarding') at the top of the
component, then replace usages like the toast call (toast({ title: "Posted to
the feed! Welcome 🛹", ... })) with toast({ title: t('toastTitle'), ... });
replace the template-literal modal title that uses stepIndex + 1 and totalSteps
with an interpolated translation t('modalTitle', { step: stepIndex + 1, total:
totalSteps }) (do not concatenate strings), and similarly swap all inline
strings referenced in the review (lines noted) to t('...') keys so all
user-facing text is driven by the translation files.
- Around line 237-272: submitPost currently always calls advance() in the
finally block, causing the UI to move past the post step even when
getSnapContainer() or the fetch to /api/userbase/hive/comment fails; remove the
unconditional advance() from the finally block and instead call advance() only
on the successful-post path (after toast success and updating completedFlagsRef
and saveToServer), keep the early-return advance() for the empty post case, and
leave setIsPosting(false) in finally so the spinner resets but the modal stays
on the post step on error to allow retry; reference submitPost,
getSnapContainer, advance, completedFlagsRef, ONBOARDING_FLAG_POST.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7a59c2e9-cdd6-46a0-ba05-a052d12fa3ed

📥 Commits

Reviewing files that changed from the base of the PR and between ba7280a and 32b90e0.

📒 Files selected for processing (8)
  • app/RootLayoutClient.tsx
  • app/api/userbase/auth/magic-link/route.ts
  • app/api/userbase/auth/session/route.ts
  • app/api/userbase/hive/comment/route.ts
  • app/api/userbase/profile/route.ts
  • components/onboarding/OnboardingDetector.tsx
  • components/onboarding/OnboardingModal.tsx
  • contexts/UserbaseAuthContext.tsx

Comment on lines +265 to +275
// onboarding_step_flag: bitmask OR — only adds bits, never removes them
// bit 0 (1) = photo, bit 1 (2) = bio, bit 2 (4) = intro post
if (typeof onboarding_step_flag === "number" && onboarding_step_flag > 0) {
const { data: currentUser } = await supabase
.from("userbase_users")
.select("onboarding_step")
.eq("id", userId)
.single();
const currentStep = currentUser?.onboarding_step ?? 0;
updateData.onboarding_step = currentStep | onboarding_step_flag;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Race condition: read-then-write on onboarding_step can drop flag bits under concurrency.

This does a non-atomic read → OR → write. Concurrent PATCHes (which the client can easily produce — e.g., OnboardingModal.savePhoto and the silent-sync effect at OnboardingModal.tsx lines 117‑131 firing at nearly the same time) both read the same currentStep, each OR in their own flag, and the second write clobbers the first. Net result: one of the bits is lost and the user stays stuck on onboarding.

Perform the OR atomically at the database level via a Postgres RPC so the read/OR/write happens in a single transaction:

🛡️ Suggested fix — atomic bitwise OR via RPC
-- migration
create or replace function set_onboarding_flag(p_user_id uuid, p_flag int)
returns int
language sql
as $$
  update userbase_users
     set onboarding_step = coalesce(onboarding_step, 0) | p_flag
   where id = p_user_id
  returning onboarding_step;
$$;
-    if (typeof onboarding_step_flag === "number" && onboarding_step_flag > 0) {
-      const { data: currentUser } = await supabase
-        .from("userbase_users")
-        .select("onboarding_step")
-        .eq("id", userId)
-        .single();
-      const currentStep = currentUser?.onboarding_step ?? 0;
-      updateData.onboarding_step = currentStep | onboarding_step_flag;
-    }
+    if (typeof onboarding_step_flag === "number" && onboarding_step_flag > 0) {
+      const { data: newStep, error: flagErr } = await supabase.rpc(
+        "set_onboarding_flag",
+        { p_user_id: userId, p_flag: onboarding_step_flag }
+      );
+      if (flagErr) {
+        console.error("Failed to OR onboarding_step_flag:", flagErr);
+        return NextResponse.json(
+          { error: "Failed to update onboarding flag" },
+          { status: 500 }
+        );
+      }
+      // Skip adding onboarding_step to updateData — the RPC already persisted it.
+      // (Keep other fields in updateData; below block will still return updatedUser.)
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/api/userbase/profile/route.ts` around lines 265 - 275, The current
read-then-write on onboarding_step (the block using onboarding_step_flag,
fetching userbase_users.onboarding_step into currentStep and setting
updateData.onboarding_step = currentStep | onboarding_step_flag) can lose bits
under concurrency; replace that logic with an atomic DB-side bitwise OR via a
Postgres RPC: add a migration that creates a function like
set_onboarding_flag(p_user_id uuid, p_flag int) returning the updated
onboarding_step, then call it from route.ts with
supabase.rpc('set_onboarding_flag', { p_user_id: userId, p_flag:
onboarding_step_flag }) and use the returned onboarding_step (or rely on the RPC
result) instead of doing the local read/OR/write so the OR happens inside the DB
in one transaction.

Comment thread components/onboarding/OnboardingDetector.tsx Outdated
Comment thread components/onboarding/OnboardingDetector.tsx
Comment thread components/onboarding/OnboardingModal.tsx
Comment thread components/onboarding/OnboardingModal.tsx
- Move advance() from finally to success path in submitPost so a failed
  post lets the user retry instead of advancing past the step
- Guard created_at with isNaN check in OnboardingDetector so invalid or
  null dates fail closed (hide onboarding) instead of falling through
@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 23, 2026

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

Project Deployment Actions Updated (UTC)
skatehive3-0 Ready Ready Preview, Comment Apr 23, 2026 6:53pm

Request Review

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant