Add user funnel analytics tracking#838
Conversation
|
Warning Review limit reached
More reviews will be available in 26 minutes and 53 seconds. Learn how PR review limits work. To continue reviewing without waiting, enable usage-based billing in the billing tab. ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits. 🚦 How do rate limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
📝 WalkthroughWalkthroughThe PR adds a shared analytics helper and instruments route guards, navigation/auth entry points, onboarding and submission views, and builder/community/validator journeys with page, click, intent, timing, and outcome tracking. It also adds tests for the analytics module. ChangesFrontend analytics instrumentation
Sequence Diagram(s)sequenceDiagram
participant RouteGuard as App.svelte
participant EntryPoint as Navbar/Sidebar/SocialLink/SubmitContribution
participant Analytics as frontend/src/lib/analytics.js
participant AuthButton as AuthButton.svelte
participant Journey as Builder/Community/Validator routes
RouteGuard->>Analytics: trackEvent('protected_route_redirect'/'role_locked_redirect', ...)
RouteGuard->>Analytics: trackPageView($location, getAnalyticsContext())
EntryPoint->>Analytics: setConnectWalletIntent(...)
EntryPoint->>Analytics: trackEvent(...)
AuthButton->>Analytics: consumeConnectWalletIntent()
AuthButton->>Analytics: trackEvent('wallet_provider_selected'/'wallet_auth_started'/'wallet_auth_success'/'wallet_auth_failed', ...)
Journey->>Analytics: trackEvent('journey_view'/'journey_step_visible'/'journey_step_action_click', ...)
Journey->>Analytics: markFunnelTime()/markLifecycleTime()
Journey->>Analytics: trackEvent('journey_started'/'journey_start_error'/'journey_exit'/'journey_completed', ...)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 7
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@frontend/src/components/ProfileCompletionGuard.svelte`:
- Line 70: The `ProfileCompletionGuard` analytics payload is sending `$location`
directly as `source_route`, which can leak wallet-address segments into
analytics. Update the `ProfileCompletionGuard.svelte` flow to pass
`source_route` through `templateRoute` from `lib/analytics.js` before emission,
and make sure the import is present where the analytics event is built.
In `@frontend/src/components/social-tasks/SocialTaskCard.svelte`:
- Around line 116-119: The Verify flow in SocialTaskCard.svelte is
double-counting journey_step_action_click because callComplete defaults
trackAction to true and the Verify button path is not suppressing it. Update the
Verify button handler to call callComplete with trackAction set to false,
matching the click-through path and preventing the duplicate event emission from
handleOpened/callComplete.
In `@frontend/src/lib/analytics.js`:
- Around line 44-46: URL_RE in analytics sanitization is too narrow because it
only matches strings that start with http(s), so embedded links inside free-text
values can still be sent. Update the value-checking logic in analytics.js to use
a separate unanchored URL detector for string values, and apply it in the same
sanitization flow that already uses ADDRESS_RE and EMAIL_RE so embedded URLs are
blocked before truncation.
- Around line 167-173: The shouldDropKey helper currently only blocks keys
ending in _id, so bare sensitive identifiers like id, slug, and token can still
be sent to analytics. Update shouldDropKey in analytics.js to explicitly reject
these bare identifier keys, either by extending FORBIDDEN_KEYS or by switching
the helper to a strict allowlist, and keep the existing checks around
SAFE_ID_KEYS, error_message, and raw intact.
- Line 96: The route label mapping in analytics is still emitting “badge” for
legacy matches, so update the template alias in analytics.js to use the current
product term instead. In the route-label mapping near the badge-style regex
entry, keep matching the legacy path if needed but change the emitted analytics
label to the contribution term (for example, use the contribution equivalent of
the current template) so reports no longer contain “badge”.
In `@frontend/src/routes/BuilderJourney.svelte`:
- Around line 479-483: The `error_code` branch in `BuilderJourney.svelte` is
unreachable because the `if (error?.code !== 4001)` guard excludes user
rejections, so `journey_step_error` never records `user_rejected`. Update the
`wallet_add_chain` error handling around `trackBuilderStepEvent` so rejection
analytics are emitted outside or before the toast guard, and keep the
conditional only for the toast/display logic. Use the `error?.code` check and
`trackBuilderStepEvent('journey_step_error', 'networks', ...)` block as the
anchor when refactoring.
In `@frontend/src/tests/analytics.test.js`:
- Around line 198-205: The analytics test for getAnalyticsContext currently only
checks a partial shape with toMatchObject, so it would miss accidental exposure
of identifier fields. Update the assertion around getAnalyticsContext() to keep
the existing expected context checks and add negative assertions that user,
email, and address are not present in the returned object.
🪄 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: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: c34c85a8-8cbb-4194-9209-def6fad9ea96
📒 Files selected for processing (17)
frontend/src/App.sveltefrontend/src/components/AuthButton.sveltefrontend/src/components/Navbar.sveltefrontend/src/components/ProfileCompletionGuard.sveltefrontend/src/components/Sidebar.sveltefrontend/src/components/SocialLink.sveltefrontend/src/components/funnel/RoleFunnel.sveltefrontend/src/components/funnel/RoleLanding.sveltefrontend/src/components/portal/submit-contribution/SubmitContribution.sveltefrontend/src/components/social-tasks/SocialTaskCard.sveltefrontend/src/lib/analytics.jsfrontend/src/routes/BuilderJourney.sveltefrontend/src/routes/CommunityJourney.sveltefrontend/src/routes/MySubmissions.sveltefrontend/src/routes/SubmitContribution.sveltefrontend/src/routes/ValidatorWaitlist.sveltefrontend/src/tests/analytics.test.js
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
frontend/src/routes/BuilderJourney.svelte (1)
238-265: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winWait for user hydration before auto-starting the builder journey.
When
useris stillundefinedon first mount,!user?.has_builder_welcome && !user?.builderevaluates to true, sostartBuilderJourney()can fire too early. That can create falsejourney_started/journey_start_erroranalytics and an unnecessary backend call. Move this into a one-shot effect that waits for$userStore.loading === falseand a realuserobject.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frontend/src/routes/BuilderJourney.svelte` around lines 238 - 265, The auto-start logic in BuilderJourney should not run until user hydration is complete, because the current `!user?.has_builder_welcome && !user?.builder` check can fire while `user` is still undefined. Move the `startBuilderJourney()` call into a one-time effect tied to `userStore.loading === false` and a resolved `user` object, and guard it so it only runs once after hydration; keep the existing analytics (`journey_started`, `journey_start_error`) and warning handling inside that hydrated flow.frontend/src/components/ProfileCompletionGuard.svelte (2)
162-188: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winTrim the profile fields before validating and submitting.
if (!email || !name)treats whitespace-only input as valid, but this guard already considers trimmed-empty values incomplete. A" "display name can get submitted and drop the user straight back into the completion gate.Suggested fix
async function handleProfileSubmit() { + const trimmedEmail = email.trim(); + const trimmedName = name.trim(); + trackEvent('profile_completion_submit', getAnalyticsContext({ selected_role: selectedRole, preselected_role: preselectedRole, })); // Validate inputs - if (!email || !name) { + if (!trimmedEmail || !trimmedName) { trackEvent('profile_completion_error', getAnalyticsContext({ selected_role: selectedRole, error_stage: 'validation', })); profileError = 'Please provide both email and display name'; return; } - if (!isValidEmail(email)) { + if (!isValidEmail(trimmedEmail)) { trackEvent('profile_completion_error', getAnalyticsContext({ selected_role: selectedRole, error_stage: 'validation', })); profileError = 'Please enter a valid email address'; return; } @@ - if (email) updateData.email = email; - if (name) updateData.name = name; + updateData.email = trimmedEmail; + updateData.name = trimmedName;🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frontend/src/components/ProfileCompletionGuard.svelte` around lines 162 - 188, Trim the profile inputs in ProfileCompletionGuard.svelte before running the validation and building the update payload so whitespace-only values are treated as empty. Update the logic around the validation block that checks email and name to use trimmed email/name values for the !email || !name and isValidEmail checks, and use those trimmed values when populating updateData before submit. Keep the existing analytics tracking and error messages, but make sure the submit flow cannot send a display name consisting only of spaces.
163-177: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick winRoute submit failures through the toast system too.
These validation/backend branches only set
profileError, so this flow bypasses the shared notification channel required for components and routes. Keep the inline message if you want, but also emit a toast here. As per coding guidelines,frontend/**/{components,routes}/**/*.{ts,tsx,js,svelte}: Always handle API errors with try-catch blocks and provide user-facing error messages via toast notifications.Also applies to: 215-233
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frontend/src/components/ProfileCompletionGuard.svelte` around lines 163 - 177, The validation and backend failure paths in ProfileCompletionGuard currently only set profileError, so they bypass the shared toast notification flow. Update the submit/error handling in the profile completion logic to also emit a user-facing toast alongside the inline message, including the existing validation branches and the backend-related handling later in the same flow. Use the existing trackEvent/profileError flow as the anchor, and make sure the route/component error handling is wrapped with try-catch where applicable so failures are surfaced through the toast system.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@frontend/src/components/ProfileCompletionGuard.svelte`:
- Around line 162-188: Trim the profile inputs in ProfileCompletionGuard.svelte
before running the validation and building the update payload so whitespace-only
values are treated as empty. Update the logic around the validation block that
checks email and name to use trimmed email/name values for the !email || !name
and isValidEmail checks, and use those trimmed values when populating updateData
before submit. Keep the existing analytics tracking and error messages, but make
sure the submit flow cannot send a display name consisting only of spaces.
- Around line 163-177: The validation and backend failure paths in
ProfileCompletionGuard currently only set profileError, so they bypass the
shared toast notification flow. Update the submit/error handling in the profile
completion logic to also emit a user-facing toast alongside the inline message,
including the existing validation branches and the backend-related handling
later in the same flow. Use the existing trackEvent/profileError flow as the
anchor, and make sure the route/component error handling is wrapped with
try-catch where applicable so failures are surfaced through the toast system.
In `@frontend/src/routes/BuilderJourney.svelte`:
- Around line 238-265: The auto-start logic in BuilderJourney should not run
until user hydration is complete, because the current
`!user?.has_builder_welcome && !user?.builder` check can fire while `user` is
still undefined. Move the `startBuilderJourney()` call into a one-time effect
tied to `userStore.loading === false` and a resolved `user` object, and guard it
so it only runs once after hydration; keep the existing analytics
(`journey_started`, `journey_start_error`) and warning handling inside that
hydrated flow.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: af524d57-88ad-4324-adf8-cb0433dcce98
📒 Files selected for processing (5)
frontend/src/components/ProfileCompletionGuard.sveltefrontend/src/components/social-tasks/SocialTaskCard.sveltefrontend/src/lib/analytics.jsfrontend/src/routes/BuilderJourney.sveltefrontend/src/tests/analytics.test.js
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (5)
frontend/src/components/Sidebar.svelte (1)
269-269: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winKeep these as plain in-app anchors.
These link updates call
preventDefault()and route throughnavigate(...); for normal in-app navigation, leave the<a href="...">behavior intact and track clicks without taking over navigation.As per coding guidelines, “Use plain
<a href="/path">anchors for in-app navigation (global interceptor handles SPA routing) instead of callingpush()in click handlers.”Also applies to: 493-493, 734-734, 923-927
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frontend/src/components/Sidebar.svelte` at line 269, The Sidebar.svelte link handlers are overriding normal anchor behavior by calling preventDefault() and navigate(...), which should be removed for in-app navigation. Update the affected anchor elements in the Sidebar component to use plain <a href="..."> links and keep any click tracking separate from routing, so the global interceptor can handle SPA navigation; apply this to the leaderboard and the other matching links referenced in the Sidebar markup.Source: Coding guidelines
frontend/src/routes/ValidatorWaitlist.svelte (1)
187-193: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winPreserve the caught error when classifying start failures.
This always reports
journey_start_error.error_stageasbackend; network failures lose their classification.🐛 Proposed fix
- } catch (_) { + } catch (err) { trackEvent('journey_start_error', getAnalyticsContext({ role_context: 'validator', selected_role: 'validator', surface: 'journey', - error_stage: 'backend', + error_stage: err.response?.status ? 'backend' : 'network', }));🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frontend/src/routes/ValidatorWaitlist.svelte` around lines 187 - 193, The start-failure tracking in ValidatorWaitlist.svelte is discarding the caught exception, so every journey_start_error is classified as backend and network failures are misreported. Update the catch block around the journey start flow to retain the caught error and derive error_stage from it before calling trackEvent/getAnalyticsContext, using the existing journey_start_error reporting path and the validator role_context/selected_role fields so the classification reflects the actual failure type.frontend/src/routes/CommunityJourney.svelte (1)
425-456: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winRecompute lifecycle durations after marking role unlock.
claimParamsis created beforemarkLifecycleTime('role_unlocked:community'), so the success/completed/unlocked analytics can miss the just-recorded unlock timing.🐛 Proposed fix
if (res.data?.user) userStore.updateUser(res.data.user); await userStore.loadUser?.(); markLifecycleTime('role_unlocked:community'); - trackEvent('community_role_claim_success', getAnalyticsContext(claimParams)); + const successParams = { + ...claimParams, + ...getLifecycleDurations('community'), + }; + trackEvent('community_role_claim_success', getAnalyticsContext(successParams)); trackEvent('journey_completed', getAnalyticsContext({ - ...claimParams, + ...successParams, journey_state: 'completed', })); trackEvent('role_unlocked', getAnalyticsContext({ - ...claimParams, + ...successParams, role_context: 'community', selected_role: 'community',🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frontend/src/routes/CommunityJourney.svelte` around lines 425 - 456, The analytics payload in completeJourney() is built too early, so the success/completed/unlocked events can omit the newly recorded role unlock timing. Move the lifecycle timing update and then recompute the claim analytics context after markLifecycleTime('role_unlocked:community') in CommunityJourney.svelte, or build a fresh payload for the post-unlock trackEvent calls so getLifecycleDurations('community') reflects the latest state.frontend/src/routes/BuilderJourney.svelte (2)
456-484: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winEmit
journey_step_verifiedfor individual network adds.Bulk add reports
networksas verified, but adding Bradbury/Asimov/Studio one-by-one never emits the verified event when the final missing network is added.🐛 Proposed fix
async function addNetwork(network, kind, { silent = false } = {}) { if (typeof window === 'undefined') return; const walletProvider = provider || window.ethereum; if (!walletProvider) { showWarning('Please connect your wallet first.'); return false; } + const networksCompleteBefore = NETWORKS.every((item) => networkDone(item.kind)); if (kind === 'bradbury') isAddingBradbury = true; if (kind === 'asimov') isAddingAsimov = true; if (kind === 'studio') isAddingStudio = true; @@ }); markNetworkAdded(kind); + if (!silent && !networksCompleteBefore && NETWORKS.every((item) => networkDone(item.kind))) { + trackBuilderStepEvent('journey_step_verified', 'networks'); + } if (!silent) showSuccess(`${network.chainName} added.`); return true; } catch (error) { if (isAlreadyAddedNetworkError(error)) { markNetworkAdded(kind); + if (!silent && !networksCompleteBefore && NETWORKS.every((item) => networkDone(item.kind))) { + trackBuilderStepEvent('journey_step_verified', 'networks'); + } if (!silent) showSuccess(`${network.chainName} already exists in your wallet.`); return true; }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frontend/src/routes/BuilderJourney.svelte` around lines 456 - 484, The individual network add flow in addNetwork currently updates local state and success messages, but never emits journey_step_verified when the last missing network is added. Update addNetwork to detect when Bradbury, Asimov, and Studio are all marked added after a successful add or already-added path, and emit journey_step_verified with networks from that function (using markNetworkAdded and the existing tracking logic as the hook). Keep the bulk-add behavior unchanged and ensure the event fires only once when the final required network becomes verified.
626-657: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winRecompute lifecycle durations after marking role unlock.
claimParamsis built beforemarkLifecycleTime('role_unlocked:builder'), so success/completion/unlock events reuse stale lifecycle timing data.🐛 Proposed fix
await journeyAPI.completeBuilderJourney(); await userStore.loadUser(); markLifecycleTime('role_unlocked:builder'); - trackEvent('builder_role_claim_success', getAnalyticsContext(claimParams)); + const successParams = { + ...claimParams, + ...getLifecycleDurations('builder'), + }; + trackEvent('builder_role_claim_success', getAnalyticsContext(successParams)); trackEvent('journey_completed', getAnalyticsContext({ - ...claimParams, + ...successParams, journey_state: 'completed', })); trackEvent('role_unlocked', getAnalyticsContext({ - ...claimParams, + ...successParams, role_context: 'builder', selected_role: 'builder', surface: 'journey',🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frontend/src/routes/BuilderJourney.svelte` around lines 626 - 657, The lifecycle timing in completeJourney is captured too early, so the success/completion/unlock analytics use stale values. Update BuilderJourney’s completeJourney flow so markLifecycleTime('role_unlocked:builder') happens before collecting lifecycle durations, then rebuild the analytics payload used by trackEvent calls (including builder_role_claim_success, journey_completed, and role_unlocked) with fresh getLifecycleDurations('builder') data.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@frontend/src/components/ProfileCompletionGuard.svelte`:
- Around line 242-246: The post-success reload in ProfileCompletionGuard.svelte
is still inside the failure-sensitive flow, so a loadUser() error can block the
redirect even after updateUserProfile and journey start succeed. Make the
userStore.loadUser() refresh best-effort by catching its failure locally, or
move the push(journeyPath(targetRole)) navigation so it cannot be skipped by a
reload error; keep the redirect logic anchored around updateUserProfile,
userStore.loadUser, and push.
- Around line 232-240: The caught API failure paths in
ProfileCompletionGuard.svelte only track analytics or update state, but they do
not notify the user. Update the relevant try-catch blocks around the journey/API
actions, including the catch for journey start and the other affected API error
handlers in this component, to call showWarning or showError from the toast
store with a user-facing message. Keep the existing analytics/state handling,
and make sure each API failure path surfaces a toast using the existing toast
helpers.
---
Outside diff comments:
In `@frontend/src/components/Sidebar.svelte`:
- Line 269: The Sidebar.svelte link handlers are overriding normal anchor
behavior by calling preventDefault() and navigate(...), which should be removed
for in-app navigation. Update the affected anchor elements in the Sidebar
component to use plain <a href="..."> links and keep any click tracking separate
from routing, so the global interceptor can handle SPA navigation; apply this to
the leaderboard and the other matching links referenced in the Sidebar markup.
In `@frontend/src/routes/BuilderJourney.svelte`:
- Around line 456-484: The individual network add flow in addNetwork currently
updates local state and success messages, but never emits journey_step_verified
when the last missing network is added. Update addNetwork to detect when
Bradbury, Asimov, and Studio are all marked added after a successful add or
already-added path, and emit journey_step_verified with networks from that
function (using markNetworkAdded and the existing tracking logic as the hook).
Keep the bulk-add behavior unchanged and ensure the event fires only once when
the final required network becomes verified.
- Around line 626-657: The lifecycle timing in completeJourney is captured too
early, so the success/completion/unlock analytics use stale values. Update
BuilderJourney’s completeJourney flow so
markLifecycleTime('role_unlocked:builder') happens before collecting lifecycle
durations, then rebuild the analytics payload used by trackEvent calls
(including builder_role_claim_success, journey_completed, and role_unlocked)
with fresh getLifecycleDurations('builder') data.
In `@frontend/src/routes/CommunityJourney.svelte`:
- Around line 425-456: The analytics payload in completeJourney() is built too
early, so the success/completed/unlocked events can omit the newly recorded role
unlock timing. Move the lifecycle timing update and then recompute the claim
analytics context after markLifecycleTime('role_unlocked:community') in
CommunityJourney.svelte, or build a fresh payload for the post-unlock trackEvent
calls so getLifecycleDurations('community') reflects the latest state.
In `@frontend/src/routes/ValidatorWaitlist.svelte`:
- Around line 187-193: The start-failure tracking in ValidatorWaitlist.svelte is
discarding the caught exception, so every journey_start_error is classified as
backend and network failures are misreported. Update the catch block around the
journey start flow to retain the caught error and derive error_stage from it
before calling trackEvent/getAnalyticsContext, using the existing
journey_start_error reporting path and the validator role_context/selected_role
fields so the classification reflects the actual failure type.
🪄 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: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: f77a4418-a6e0-4d36-a1d1-7dfc1882a36e
📒 Files selected for processing (5)
frontend/src/components/ProfileCompletionGuard.sveltefrontend/src/components/Sidebar.sveltefrontend/src/routes/BuilderJourney.sveltefrontend/src/routes/CommunityJourney.sveltefrontend/src/routes/ValidatorWaitlist.svelte
There was a problem hiding this comment.
Caution
Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (5)
frontend/src/components/Sidebar.svelte (1)
269-269: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winKeep these as plain in-app anchors.
These link updates call
preventDefault()and route throughnavigate(...); for normal in-app navigation, leave the<a href="...">behavior intact and track clicks without taking over navigation.As per coding guidelines, “Use plain
<a href="/path">anchors for in-app navigation (global interceptor handles SPA routing) instead of callingpush()in click handlers.”Also applies to: 493-493, 734-734, 923-927
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frontend/src/components/Sidebar.svelte` at line 269, The Sidebar.svelte link handlers are overriding normal anchor behavior by calling preventDefault() and navigate(...), which should be removed for in-app navigation. Update the affected anchor elements in the Sidebar component to use plain <a href="..."> links and keep any click tracking separate from routing, so the global interceptor can handle SPA navigation; apply this to the leaderboard and the other matching links referenced in the Sidebar markup.Source: Coding guidelines
frontend/src/routes/ValidatorWaitlist.svelte (1)
187-193: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winPreserve the caught error when classifying start failures.
This always reports
journey_start_error.error_stageasbackend; network failures lose their classification.🐛 Proposed fix
- } catch (_) { + } catch (err) { trackEvent('journey_start_error', getAnalyticsContext({ role_context: 'validator', selected_role: 'validator', surface: 'journey', - error_stage: 'backend', + error_stage: err.response?.status ? 'backend' : 'network', }));🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frontend/src/routes/ValidatorWaitlist.svelte` around lines 187 - 193, The start-failure tracking in ValidatorWaitlist.svelte is discarding the caught exception, so every journey_start_error is classified as backend and network failures are misreported. Update the catch block around the journey start flow to retain the caught error and derive error_stage from it before calling trackEvent/getAnalyticsContext, using the existing journey_start_error reporting path and the validator role_context/selected_role fields so the classification reflects the actual failure type.frontend/src/routes/CommunityJourney.svelte (1)
425-456: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winRecompute lifecycle durations after marking role unlock.
claimParamsis created beforemarkLifecycleTime('role_unlocked:community'), so the success/completed/unlocked analytics can miss the just-recorded unlock timing.🐛 Proposed fix
if (res.data?.user) userStore.updateUser(res.data.user); await userStore.loadUser?.(); markLifecycleTime('role_unlocked:community'); - trackEvent('community_role_claim_success', getAnalyticsContext(claimParams)); + const successParams = { + ...claimParams, + ...getLifecycleDurations('community'), + }; + trackEvent('community_role_claim_success', getAnalyticsContext(successParams)); trackEvent('journey_completed', getAnalyticsContext({ - ...claimParams, + ...successParams, journey_state: 'completed', })); trackEvent('role_unlocked', getAnalyticsContext({ - ...claimParams, + ...successParams, role_context: 'community', selected_role: 'community',🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frontend/src/routes/CommunityJourney.svelte` around lines 425 - 456, The analytics payload in completeJourney() is built too early, so the success/completed/unlocked events can omit the newly recorded role unlock timing. Move the lifecycle timing update and then recompute the claim analytics context after markLifecycleTime('role_unlocked:community') in CommunityJourney.svelte, or build a fresh payload for the post-unlock trackEvent calls so getLifecycleDurations('community') reflects the latest state.frontend/src/routes/BuilderJourney.svelte (2)
456-484: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winEmit
journey_step_verifiedfor individual network adds.Bulk add reports
networksas verified, but adding Bradbury/Asimov/Studio one-by-one never emits the verified event when the final missing network is added.🐛 Proposed fix
async function addNetwork(network, kind, { silent = false } = {}) { if (typeof window === 'undefined') return; const walletProvider = provider || window.ethereum; if (!walletProvider) { showWarning('Please connect your wallet first.'); return false; } + const networksCompleteBefore = NETWORKS.every((item) => networkDone(item.kind)); if (kind === 'bradbury') isAddingBradbury = true; if (kind === 'asimov') isAddingAsimov = true; if (kind === 'studio') isAddingStudio = true; @@ }); markNetworkAdded(kind); + if (!silent && !networksCompleteBefore && NETWORKS.every((item) => networkDone(item.kind))) { + trackBuilderStepEvent('journey_step_verified', 'networks'); + } if (!silent) showSuccess(`${network.chainName} added.`); return true; } catch (error) { if (isAlreadyAddedNetworkError(error)) { markNetworkAdded(kind); + if (!silent && !networksCompleteBefore && NETWORKS.every((item) => networkDone(item.kind))) { + trackBuilderStepEvent('journey_step_verified', 'networks'); + } if (!silent) showSuccess(`${network.chainName} already exists in your wallet.`); return true; }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frontend/src/routes/BuilderJourney.svelte` around lines 456 - 484, The individual network add flow in addNetwork currently updates local state and success messages, but never emits journey_step_verified when the last missing network is added. Update addNetwork to detect when Bradbury, Asimov, and Studio are all marked added after a successful add or already-added path, and emit journey_step_verified with networks from that function (using markNetworkAdded and the existing tracking logic as the hook). Keep the bulk-add behavior unchanged and ensure the event fires only once when the final required network becomes verified.
626-657: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winRecompute lifecycle durations after marking role unlock.
claimParamsis built beforemarkLifecycleTime('role_unlocked:builder'), so success/completion/unlock events reuse stale lifecycle timing data.🐛 Proposed fix
await journeyAPI.completeBuilderJourney(); await userStore.loadUser(); markLifecycleTime('role_unlocked:builder'); - trackEvent('builder_role_claim_success', getAnalyticsContext(claimParams)); + const successParams = { + ...claimParams, + ...getLifecycleDurations('builder'), + }; + trackEvent('builder_role_claim_success', getAnalyticsContext(successParams)); trackEvent('journey_completed', getAnalyticsContext({ - ...claimParams, + ...successParams, journey_state: 'completed', })); trackEvent('role_unlocked', getAnalyticsContext({ - ...claimParams, + ...successParams, role_context: 'builder', selected_role: 'builder', surface: 'journey',🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frontend/src/routes/BuilderJourney.svelte` around lines 626 - 657, The lifecycle timing in completeJourney is captured too early, so the success/completion/unlock analytics use stale values. Update BuilderJourney’s completeJourney flow so markLifecycleTime('role_unlocked:builder') happens before collecting lifecycle durations, then rebuild the analytics payload used by trackEvent calls (including builder_role_claim_success, journey_completed, and role_unlocked) with fresh getLifecycleDurations('builder') data.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@frontend/src/components/ProfileCompletionGuard.svelte`:
- Around line 242-246: The post-success reload in ProfileCompletionGuard.svelte
is still inside the failure-sensitive flow, so a loadUser() error can block the
redirect even after updateUserProfile and journey start succeed. Make the
userStore.loadUser() refresh best-effort by catching its failure locally, or
move the push(journeyPath(targetRole)) navigation so it cannot be skipped by a
reload error; keep the redirect logic anchored around updateUserProfile,
userStore.loadUser, and push.
- Around line 232-240: The caught API failure paths in
ProfileCompletionGuard.svelte only track analytics or update state, but they do
not notify the user. Update the relevant try-catch blocks around the journey/API
actions, including the catch for journey start and the other affected API error
handlers in this component, to call showWarning or showError from the toast
store with a user-facing message. Keep the existing analytics/state handling,
and make sure each API failure path surfaces a toast using the existing toast
helpers.
---
Outside diff comments:
In `@frontend/src/components/Sidebar.svelte`:
- Line 269: The Sidebar.svelte link handlers are overriding normal anchor
behavior by calling preventDefault() and navigate(...), which should be removed
for in-app navigation. Update the affected anchor elements in the Sidebar
component to use plain <a href="..."> links and keep any click tracking separate
from routing, so the global interceptor can handle SPA navigation; apply this to
the leaderboard and the other matching links referenced in the Sidebar markup.
In `@frontend/src/routes/BuilderJourney.svelte`:
- Around line 456-484: The individual network add flow in addNetwork currently
updates local state and success messages, but never emits journey_step_verified
when the last missing network is added. Update addNetwork to detect when
Bradbury, Asimov, and Studio are all marked added after a successful add or
already-added path, and emit journey_step_verified with networks from that
function (using markNetworkAdded and the existing tracking logic as the hook).
Keep the bulk-add behavior unchanged and ensure the event fires only once when
the final required network becomes verified.
- Around line 626-657: The lifecycle timing in completeJourney is captured too
early, so the success/completion/unlock analytics use stale values. Update
BuilderJourney’s completeJourney flow so
markLifecycleTime('role_unlocked:builder') happens before collecting lifecycle
durations, then rebuild the analytics payload used by trackEvent calls
(including builder_role_claim_success, journey_completed, and role_unlocked)
with fresh getLifecycleDurations('builder') data.
In `@frontend/src/routes/CommunityJourney.svelte`:
- Around line 425-456: The analytics payload in completeJourney() is built too
early, so the success/completed/unlocked events can omit the newly recorded role
unlock timing. Move the lifecycle timing update and then recompute the claim
analytics context after markLifecycleTime('role_unlocked:community') in
CommunityJourney.svelte, or build a fresh payload for the post-unlock trackEvent
calls so getLifecycleDurations('community') reflects the latest state.
In `@frontend/src/routes/ValidatorWaitlist.svelte`:
- Around line 187-193: The start-failure tracking in ValidatorWaitlist.svelte is
discarding the caught exception, so every journey_start_error is classified as
backend and network failures are misreported. Update the catch block around the
journey start flow to retain the caught error and derive error_stage from it
before calling trackEvent/getAnalyticsContext, using the existing
journey_start_error reporting path and the validator role_context/selected_role
fields so the classification reflects the actual failure type.
🪄 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: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: f77a4418-a6e0-4d36-a1d1-7dfc1882a36e
📒 Files selected for processing (5)
frontend/src/components/ProfileCompletionGuard.sveltefrontend/src/components/Sidebar.sveltefrontend/src/routes/BuilderJourney.sveltefrontend/src/routes/CommunityJourney.sveltefrontend/src/routes/ValidatorWaitlist.svelte
🛑 Comments failed to post (2)
frontend/src/components/ProfileCompletionGuard.svelte (2)
232-240: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Show toast feedback for API failures.
The new/updated API failure paths only emit analytics or set inline state. Add
showWarning/showErrorfrom the toast store for these caught API errors.As per coding guidelines, “Always handle API errors with try-catch blocks and provide user-facing error messages via toast notifications.”
Also applies to: 247-265
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frontend/src/components/ProfileCompletionGuard.svelte` around lines 232 - 240, The caught API failure paths in ProfileCompletionGuard.svelte only track analytics or update state, but they do not notify the user. Update the relevant try-catch blocks around the journey/API actions, including the catch for journey start and the other affected API error handlers in this component, to call showWarning or showError from the toast store with a user-facing message. Keep the existing analytics/state handling, and make sure each API failure path surfaces a toast using the existing toast helpers.Source: Coding guidelines
242-246: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
Don’t let the post-success reload strand the user.
After
updateUserProfileand journey start succeed,await userStore.loadUser()can still fail and fall into the outer “profile completion failed” path, preventing the redirect even though the profile was already saved. Make the reload best-effort or move navigation out of this failure path.Also applies to: 247-251
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frontend/src/components/ProfileCompletionGuard.svelte` around lines 242 - 246, The post-success reload in ProfileCompletionGuard.svelte is still inside the failure-sensitive flow, so a loadUser() error can block the redirect even after updateUserProfile and journey start succeed. Make the userStore.loadUser() refresh best-effort by catching its failure locally, or move the push(journeyPath(targetRole)) navigation so it cannot be skipped by a reload error; keep the redirect logic anchored around updateUserProfile, userStore.loadUser, and push.
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
frontend/src/components/SocialLink.svelte (1)
364-376: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick winTrack the journey CTA before redirecting into auth.
In the unauthenticated branch, this only stores wallet intent and opens the auth flow.
trackOAuthAction()never runs there, so logged-out users do not emit the newjourney_step_action_clickevent withstep_id,step_index, andverification_mode.Proposed fix
async function linkAccount({ refreshUsername = false } = {}) { + if (!refreshUsername) trackOAuthAction(); + if (!$authState.isAuthenticated) { const meta = oauthStepMeta(); setConnectWalletIntent({ surface: meta ? 'journey' : 'form', cta_id: meta ? `${meta.step}_oauth_auth` : `${platform}_oauth_auth`, @@ document.querySelector('.auth-button')?.click(); return; } - - if (!refreshUsername) trackOAuthAction(); handledOAuthResult = false;🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frontend/src/components/SocialLink.svelte` around lines 364 - 376, In linkAccount in SocialLink.svelte, the unauthenticated path only sets connect-wallet intent and triggers the auth button, so the journey CTA event is never tracked there. Move or add the tracking call so logged-out users emit the same journey_step_action_click event before the auth redirect, using the oauth step metadata from oauthStepMeta() to populate step_id, step_index, and verification_mode. Keep the authenticated path behavior unchanged and make sure the tracking happens before document.querySelector('.auth-button')?.click().frontend/src/components/AuthButton.svelte (1)
112-146: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick winCarry the original auth-entry context into the wallet outcome events.
handleAuth()saves the entry metadata inwalletSelectorContext, buthandleWalletSelected()rebuilds a fresh context forwallet_provider_selected,wallet_auth_started,wallet_auth_success, andwallet_auth_failed. That drops the originalcta_id,target_route, andselected_role, so route-guard and deep-link attribution disappears at the most important step.Proposed fix
async function handleWalletSelected(provider, walletName) { // Start the connection process with selected wallet loading = true; const provider_type = providerType(walletName); const context = getAnalyticsContext({ + ...walletSelectorContext, surface: 'modal', provider_type, }); trackEvent('wallet_provider_selected', context); trackEvent('wallet_auth_started', context); @@ markFunnelTime('wallet_auth_success'); markLifecycleTime('first_wallet_auth_success'); trackEvent('wallet_auth_success', getAnalyticsContext({ + ...walletSelectorContext, surface: 'modal', provider_type, time_from_wallet_click_ms: getFunnelDurationMs('wallet_click'), })); @@ } catch (err) { trackEvent('wallet_auth_failed', getAnalyticsContext({ + ...walletSelectorContext, surface: 'modal', provider_type, error_stage: walletErrorStage(err), }));🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frontend/src/components/AuthButton.svelte` around lines 112 - 146, Carry the original auth-entry metadata through the wallet flow instead of rebuilding a fresh analytics context in handleWalletSelected. Reuse the context saved by handleAuth() in walletSelectorContext for wallet_provider_selected, wallet_auth_started, wallet_auth_success, and wallet_auth_failed so cta_id, target_route, and selected_role are preserved. Update the analytics calls in AuthButton.svelte to extend that stored context with the wallet-specific fields (like surface, provider_type, time_from_wallet_click_ms, and error_stage) rather than replacing it.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@frontend/src/components/AnalyticsConsentBanner.svelte`:
- Around line 19-122: The AnalyticsConsentBanner component is using a custom
<style> block for layout, spacing, colors, and responsiveness, but the repo
convention for Svelte components is Tailwind utilities only. Move the styling in
AnalyticsConsentBanner.svelte onto the existing markup for the visible section,
consent-copy, consent-actions, consent-button-primary, and
consent-button-secondary elements using Tailwind classes, and remove the custom
CSS entirely while preserving the same responsive behavior and button states.
---
Outside diff comments:
In `@frontend/src/components/AuthButton.svelte`:
- Around line 112-146: Carry the original auth-entry metadata through the wallet
flow instead of rebuilding a fresh analytics context in handleWalletSelected.
Reuse the context saved by handleAuth() in walletSelectorContext for
wallet_provider_selected, wallet_auth_started, wallet_auth_success, and
wallet_auth_failed so cta_id, target_route, and selected_role are preserved.
Update the analytics calls in AuthButton.svelte to extend that stored context
with the wallet-specific fields (like surface, provider_type,
time_from_wallet_click_ms, and error_stage) rather than replacing it.
In `@frontend/src/components/SocialLink.svelte`:
- Around line 364-376: In linkAccount in SocialLink.svelte, the unauthenticated
path only sets connect-wallet intent and triggers the auth button, so the
journey CTA event is never tracked there. Move or add the tracking call so
logged-out users emit the same journey_step_action_click event before the auth
redirect, using the oauth step metadata from oauthStepMeta() to populate
step_id, step_index, and verification_mode. Keep the authenticated path behavior
unchanged and make sure the tracking happens before
document.querySelector('.auth-button')?.click().
🪄 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: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 30319b29-900f-4f1b-96cc-70471435edb3
📒 Files selected for processing (8)
frontend/src/App.sveltefrontend/src/components/AnalyticsConsentBanner.sveltefrontend/src/components/AuthButton.sveltefrontend/src/components/SocialLink.sveltefrontend/src/components/WalletSelector.sveltefrontend/src/lib/analytics.jsfrontend/src/main.jsfrontend/src/tests/analytics.test.js
* Rework portal onboarding into role funnels and journeys
Builders, Validators, and Community each get a role landing that adapts to
three states: signed-out, signed-in without the role, and earned. Dedicated
journey routes guide users step by step to unlock their role, and journeys
now grant the ROLE itself rather than points: farmable point grants are
removed, and points come only from verifiable tasks (starring the boilerplate
repo, posting on X). Journey progress is durable via zero-point marker
contributions that also drive a "started" state.
The Builder journey is connect GitHub plus star the boilerplate repo. The
Community journey is five verified steps (link X, link Discord, follow on X,
join Discord, and a verified X post) and grants the Creator role on
completion, with the post checked through Sorsa against the linked handle.
The Validator journey stays a single waitlist form and keeps the graduation
reward. Linking GitHub now records a contribution like X and Discord.
Community membership is only created by completing the journey: the previous
automatic creation from community contributions, POAP claims, and Discord XP
is removed. Sidebar subsections stay locked until the relevant journey is
complete, the profile shows an in-progress role with an owner-only grey
badge, and the What's New dialog is shown only to returning users.
## Implementation Notes
- frontend/src/components/funnel/: role funnel dispatcher, per-role landings,
and shared journey building blocks
- frontend/src/routes/{BuilderJourney,CommunityJourney}.svelte, ValidatorWaitlist.svelte:
dedicated journey views
- frontend/src/lib/roleState.js: funnel state machine (none/started/earned),
role/category mapping, journey paths
- frontend/src/{App,components/Sidebar,components/profile/*,routes/Profile}.svelte:
routing, locked subsections, in-progress role card and header badge
- frontend/src/components/WhatsNewDialog.svelte: gate to returning users only
- backend/users/views.py: start/complete journey endpoints, link_github_account,
community journey status and X-post verification
- backend/creators/community_journey.py, models.py, views.py: five-step status,
CommunityPostProof, journey-gated membership
- backend/{leaderboard/models,poaps/signals,community_xp/services}.py: remove
automatic Creator creation
- backend/social_tasks/sorsa_client.py: tweet lookup for X-post verification
- backend/.../migrations: community-link-github contribution type, builder star
task, CommunityPostProof
* Update changelog
* Dedup journey/link endpoints and trim funnel landing dead code
Clean up the over-engineering surfaced by the funnel review with no
behavior change. Two role landing components dropped a duplicate CTA
label that mirrored the primary one, and the validator landing's
repeated waitlist badge is now a single snippet. On the backend, the
three social-link award endpoints and the two journey-start endpoints
now delegate to shared helpers; every public endpoint and response
stays identical.
## Claude Implementation Notes
- backend/users/views.py: Add `_award_social_link_points` (link_x/discord/github become thin wrappers) and `_mark_journey_started` (start_builder/start_role become thin wrappers). Endpoints, messages and points unchanged. start_builder now also ensures a 0-point `builder-welcome` multiplier exists, matching the generic path (harmless on a 0-point type).
- frontend/src/components/funnel/CommunityLanding.svelte: Remove redundant `finalLabel`, reuse `primaryLabel`.
- frontend/src/components/funnel/ValidatorLanding.svelte: Remove redundant `finalLabel`; extract the duplicated "on the waitlist" badge markup into a `{#snippet}` rendered in both spots.
* Address PR-837 review: harden funnel idempotency, privacy, and copy
Applies the safe CodeRabbit findings across the builder, community, and
validator funnels with behavior preserved on the happy paths.
Backend: the builder/community completion and legacy creator-join endpoints
are now idempotent under concurrent requests (row lock + get_or_create instead
of check-then-create), the welcome-marker write runs in a single locked
transaction, public profiles no longer expose in-progress funnel state
(has_*_welcome / link booleans), the community X-post gate requires an exact
@mention (boundary regex, not substring), CommunityPostProof.verified_at now
tracks re-verification, the GitHub-link seed migration fails closed when the
builder category is missing, and the Sorsa tweet-info parser guards a
malformed user payload instead of 500-ing.
Frontend: journey init/copy/storage errors that were silently swallowed now
surface as toasts, the builder resources route is gated like the locked
sidebar, stored onboarding roles are validated before redirect, the community
landing copy no longer overpromises points (the journey grants the role; tasks
earn points), the hero progress bar collapses to zero at 0%, the inert notice
"dismiss" control is removed, profiles stop showing earned Community members as
"in progress" while status loads, and the shared journey components drop the
banned 'badge' prop name for 'contribution'.
## Claude Implementation Notes
- backend/users/views.py: idempotent + row-locked complete_builder_journey, complete_community_journey, and _mark_journey_started.
- backend/users/serializers.py: strip has_*_welcome / has_community_link_* from public_profile responses.
- backend/creators/views.py: join_creator_view uses get_or_create in an atomic block.
- backend/creators/community_journey.py: post_matches requires an exact @handle via boundary regex.
- backend/creators/models.py + migrations/0003_communitypostproof.py: verified_at auto_now instead of auto_now_add.
- backend/social_tasks/sorsa_client.py: guard non-dict user payload, raise SorsaError.
- backend/contributions/migrations/0073_create_community_link_github.py: fail closed if builder category absent.
- backend/users/tests/test_social_link_rewards.py, test_community_journey.py: fixtures match prod (builder category; handle from genlayer_handle()).
- frontend journeys (JourneyHeroCard/JourneyStepRow): badge prop -> contribution*; progress fill 0-width at 0%.
- frontend funnel/RoleLanding + routes/BuilderJourney/CommunityJourney/ValidatorWaitlist: navigate-on-success, init/storage/copy error toasts, execCommand return check.
- frontend JourneyNotice: remove inert dismiss control.
- frontend App.svelte: gate /builders/resources with roleGatedRoute.
- frontend ProfileCompletionGuard: validate stored onboarding role.
- frontend Sidebar: request-key guard on community lock-state fetch.
- frontend SocialLink: surface GitHub link-reward failure.
- frontend Profile.svelte: only show Community "in progress" for a loaded started-incomplete journey.
- frontend funnel/CommunityLanding: corrected points copy.
- frontend profile/RoleJourneyCard: anchor CTA instead of push().
* Address PR-837 review round 2: route gates, multiplier races, best-effort refresh
Second batch of CodeRabbit findings on the funnel work.
Backend: the default-multiplier bootstrap is now serialized by contribution
type (lock the type row before exists()/create()), so concurrent requests from
different users can no longer duplicate the default multiplier; the Sorsa
tweet-info parser rejects a non-string username instead of letting lstrip raise
an AttributeError that escapes SorsaError handling.
Frontend: the remaining locked validator/builder subsections
(all-contributions, participants, wall-of-shame) are gated at the route level
to match the locked sidebar, so direct navigation can't skip the funnel; and a
transient userStore.loadUser() failure no longer blocks entering a journey or
the waitlist redirect once the server-side mutation has already succeeded
(refresh is now best-effort).
## Claude Implementation Notes
- backend/users/views.py: lock ContributionType row before the multiplier exists()/create() in both _mark_journey_started and _award_social_link_points.
- backend/social_tasks/sorsa_client.py: raise SorsaError when user.username is not a string.
- frontend/src/App.svelte: roleGatedRoute for /builders/all-contributions, /validators/all-contributions, /validators/participants, /validators/wall-of-shame (sidebar already locks these).
- frontend/src/components/funnel/RoleLanding.svelte: navigate right after startRoleJourney succeeds; loadUser best-effort (fire-and-forget).
- frontend/src/routes/ValidatorWaitlist.svelte: startValidatorJourney is the only required step; loadUser + sessionStorage best-effort; redirect always runs on join success.
* Apply role journeys to newcomers, grandfather existing members
The role funnels are for onboarding newcomers only: anyone who already holds
a role (community member, builder, or waitlisted/graduated validator) keeps
full access without being pushed back through a journey. "Earned" is now read
straight from role membership for all three categories, so existing community
members reach their dashboard and unlocked subsections immediately. This drops
the live community journey-status round-trip that previously gated the earned
state, which also removes the spinner on every community entry, the sidebar
lock flash, and the misclassification of an earned member as in-progress on a
transient status-call failure.
Builder status auto-grant on steward-accept and leaderboard recalc no longer
re-creates the removed builder-welcome (+20) and builder (+50) point
contributions (the +20 had also become out-of-range against its now point-free
type); it still grants the role, which is human-gated, not farmable. The
in-progress funnel state stays owner-only but is no longer stripped from the
owner's own public profile, so their private grey "only you can see this"
badge renders again.
## Implementation Notes
- creators/community_journey.py: journey_status treats a Creator as
started/complete/is_member regardless of the newer marker + step records
- users/views.py: complete_community_journey short-circuits an existing Creator
with a 200 before the step gate (removes the now-dead later guard)
- leaderboard/models.py: ensure_builder_status only ensures the Builder profile,
no longer writes the builder-welcome/builder point contributions
- users/serializers.py: in-progress funnel flags are stripped only from other
viewers on a public profile, not from the owner
- components/funnel/RoleFunnel.svelte, App.svelte, components/Sidebar.svelte:
community earned/lock state reads role membership synchronously like the
other roles; removed the async journey-status gating
- users/tests/test_community_journey.py: assert existing Creators are
grandfathered instead of re-funneled
* Gate builder claim on GitHub+star; protect personal routes
The Builder journey now enables the Claim button as soon as the role-gating
steps are done, connecting GitHub and starring the boilerplate repo, which
matches the backend that grants the role on the star task alone. Adding
networks, getting testnet GEN, and deploying are relabeled as recommended next
steps rather than blockers, so an eligible builder can no longer be stalled by
client-side-only progress that does not survive across devices. The displayed
journey and the actual requirement now agree.
Separately, the personal authenticated-only surfaces (submit contribution, my
submissions, edit submission, edit profile, and notifications) are wrapped so a
signed-out visitor is sent to sign in instead of seeing the page shell.
## Implementation Notes
- routes/BuilderJourney.svelte: claim gated on the required steps
(wallet/github/star) instead of all six; networks, top-up, and deploy marked
"Recommended"; hero, notice, and completion copy updated; removed the now
unused step counters
- App.svelte: wrap /submit-contribution, /my-submissions, /contributions/:id,
/profile, and /notifications in protectedRoute
* Consolidate funnel role landings and polish journey UI
Introduces a shared AuthenticatedRoleLanding component and trims the per-role
landings (Community and Validator) down onto it, removing duplicated signed-in
markup. The journey hero and step-row components and the builder, community,
and validator journey views get UI polish, and the profile header and rankings
widget are adjusted to match. Playwright artifacts are added to .gitignore.
## Implementation Notes
- components/funnel/AuthenticatedRoleLanding.svelte: new shared signed-in role
landing
- components/funnel/{RoleLanding,BuilderLanding,CommunityLanding,ValidatorLanding}.svelte:
route the signed-in state through the shared landing, drop duplicated markup
- components/funnel/journeys/{JourneyHeroCard,JourneyStepRow}.svelte: presentation polish
- routes/{BuilderJourney,CommunityJourney,ValidatorWaitlist}.svelte: journey view polish
- components/profile/{ProfileHeader,RankingsWidget}.svelte: profile adjustments
- users/tests/test_builder_journey.py: extend builder journey coverage
- .gitignore: ignore Playwright report/test-results artifacts
* Fix funnel signed-out flash and validator start atomicity
RoleFunnel now shows a neutral spinner while the session is still being
verified, instead of deriving the signed-in state too early: a logged-in user
with an existing session no longer briefly sees the signed-out landing flash
before their dashboard loads.
The validator journey start now preflights the waitlist contribution type and
wraps the started marker and the waitlist contribution in a single
transaction, so a misconfigured type or a failed write can no longer leave a
user marked "started" without the single waitlist step.
Also reworded an owner-only profile comment to drop forbidden "badge" wording.
## Implementation Notes
- components/funnel/RoleFunnel.svelte: gate rendering on authState.hasVerified
so the spinner covers the pending-verification window
- users/views.py: start_validator_journey resolves validator-waitlist before
marking started and runs both writes inside one transaction.atomic()
- users/serializers.py: in-progress funnel state comment uses "indicator"
* Update funnel creator journeys and modals
* Address funnel PR review feedback
* Update route OG previews (#840)
* Update unauthenticated landing pages (#841)
* Fix community leaderboard search test fixture
* Update journey onboarding views (#842)
* Show community journey task points
* Add user funnel analytics tracking (#838)
* Add user funnel analytics tracking
* Address analytics review feedback
* Address analytics consent and funnel gaps
* Use Tailwind for analytics consent banner
* Add network activity heatmap tab
* Add grouped evidence URL requirements for contribution types (#844)
* Add grouped evidence URL requirements for contribution types
Contribution types gain an optional evidence URL group rule: each group
must be satisfied by at least one submitted evidence URL, with AND across
groups and OR within a group, so a type can require a contract code link
and a deployed-contract explorer link together. Grouped types are
implicitly accepted when an accepted-types whitelist is set, matching the
existing required-types behavior. A new GenLayer Explorer Contract evidence
type recognizes Asimov, Bradbury, Studio, and Testnet explorer contract
addresses, and the Studio import contract pattern is tightened to avoid
false matches.
## Implementation Notes
- backend/contributions/models.py: Add required_evidence_url_type_groups JSONField to ContributionType.
- backend/contributions/serializers.py: Enforce the group rule during evidence validation and union grouped types into the accepted whitelist.
- backend/contributions/migrations/0073_contributiontype_required_evidence_url_type_groups.py: Field migration.
- backend/contributions/migrations/0074_intelligent_contracts_evidence_types.py: Seed the GenLayer Explorer Contract type and tighten the studio-contract regex.
- backend/contributions/tests/test_required_evidence_url_type_groups.py: Cover the group rule and whitelist interaction.
- backend/contributions/tests/test_required_evidence_url_types.py: Make setUp robust to migration-seeded URL types.
* Update changelog
* Combine Intelligent Contracts migrations into one
Merge the required_evidence_url_type_groups field add and the evidence URL
type seed into a single migration; the two operations are independent so
they need no separate steps.
## Implementation Notes
- backend/contributions/migrations/0075_intelligent_contracts_evidence_types.py: Single migration with the AddField plus the RunPython seed (explorer type + tightened studio-contract regex).
- backend/contributions/migrations/0076_intelligent_contracts_evidence_types.py: Removed; folded into 0075.
- backend/contributions/tests/test_required_evidence_url_type*.py: Update seed-migration reference in setUp comment.
* Validate grouped evidence URL config and tighten related tests
ContributionType save now rejects a malformed required_evidence_url_type_groups
value (non-list, empty group, or unknown EvidenceURLType slug) instead of
letting it become an impossible submission rule. The whitelist mismatch error
now lists grouped types among the accepted ones, matching the acceptance logic.
The studio-contract test fixture uses the tightened production pattern with a
real import-contract URL so the suite exercises the shipped detection.
## Implementation Notes
- backend/contributions/models.py: Validate required_evidence_url_type_groups shape and slugs in ContributionType.clean().
- backend/contributions/serializers.py: Include grouped type names in the whitelist "Expected" error text.
- backend/contributions/tests/test_required_evidence_url_type_groups.py: Add clean() validation tests for malformed and unknown-slug groups.
- backend/contributions/tests/test_required_evidence_url_types.py: Align the studio-contract fixture and test URL with the tightened regex.
* Address review feedback on network activity heatmap
The heatmap now derives its calendar bounds from the backend's declared
activity window instead of the first/last populated rows, so the grid keeps
its full six-month span even if zero-activity edge days are ever omitted. The
view toggle exposes its selected state to assistive tech via aria-pressed, and
the legacy snapshot upconvert path now normalizes the full v5 schema so older
snapshots and fresh payloads share an identical shape.
## Claude Implementation Notes
- backend/api/overview_metrics.py: default latest_week_by_source to {} when upconverting v3/v4 snapshots to v5
- backend/api/tests.py: assert latest_week_by_source is present in the v3-to-v5 normalization test
- frontend/src/components/portal/ActivityHeatmap.svelte: accept activityWindow prop and prefer it for grid start/end, falling back to row bounds
- frontend/src/components/portal/NetworkActivity.svelte: pass activity_window to the heatmap and add aria-pressed to the Graph/Activity tabs
* Make declared activity window authoritative for heatmap cells
The heatmap grid bounds already followed the backend's declared window, but cell
inclusion still keyed on row presence, so a date missing from inside the window
would blank out and shift month labels. Cell inclusion now derives from the
window range, and absent days render as zero-count cells instead of gaps.
## Claude Implementation Notes
- frontend/src/components/portal/ActivityHeatmap.svelte: compute inRange from the window-derived first/last date range (and past-or-today) rather than Boolean(row); missing rows already default to zero counts
* Add social task eligibility gates (#846)
Adds privacy-safe Google Analytics tracking across onboarding, role journeys, validator waitlist, contribution submission, and submission lifecycle surfaces.
Centralizes sanitization, route templating, lifecycle timers, and one-shot events so funnel metrics can be measured without sending raw user identifiers or free-text content.
Tightens event attribution for dynamic page views, OAuth journey links, click-through tasks, and deep-linked contribution forms.
Summary by CodeRabbit