Skip to content

[v5] chore(web): Ask upsell gating#1224

Merged
brendan-kellam merged 5 commits into
v5from
bkellam/ask-feature-gating
May 24, 2026
Merged

[v5] chore(web): Ask upsell gating#1224
brendan-kellam merged 5 commits into
v5from
bkellam/ask-feature-gating

Conversation

@brendan-kellam
Copy link
Copy Markdown
Contributor

@brendan-kellam brendan-kellam commented May 23, 2026

Summary by CodeRabbit

Release Notes

  • New Features

    • Added "Ask" feature with entitlement-based access control in sidebar navigation.
    • Introduced "Analytics" feature with entitlement gating.
    • Added upgrade badges to navigation items, clearly indicating which features require a paid plan.
  • Improvements

    • Enhanced license activation workflow with streamlined post-checkout experience and automatic license activation upon purchase.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 23, 2026

Walkthrough

This PR expands the ask entitlement, refactors identity provider fetching into a context provider, adds entitlement-gated sidebar UI with upgrade badges, replaces the checkout success modal with a return handler and activation dialog, and centralizes chat submission gating (login wall and upgrade checks) in the ChatBox component.

Changes

Entitlements and Identity Provider System

Layer / File(s) Summary
Entitlements type definition
packages/shared/src/entitlements.ts
The shared Entitlement union type now includes "ask" by adding it to ALL_ENTITLEMENTS.
Identity provider context and hooks
packages/web/src/features/auth/identityProvidersProvider.tsx, packages/web/src/features/auth/useIdentityProviders.ts
A new client-side React context stores identity provider metadata and is supplied by IdentityProvidersProvider. A useIdentityProviders hook retrieves providers from the context.
Root layout integration
packages/web/src/app/layout.tsx
The root layout fetches identity provider metadata server-side and wraps the app with IdentityProvidersProvider, making providers available to all descendant client components.
Auth pages refactoring
packages/web/src/app/login/page.tsx, packages/web/src/app/signup/page.tsx, packages/web/src/app/onboard/page.tsx, packages/web/src/app/invite/page.tsx
Login, signup, onboard, and invite pages remove their individual identity provider fetches and rely on the context-provided hook instead.
LoginForm and LoginDialog refactoring
packages/web/src/app/login/components/loginForm.tsx, packages/web/src/app/components/authMethodSelector.tsx, packages/web/src/features/chat/components/chatBox/loginDialog.tsx
LoginForm and AuthMethodSelector remove the providers prop, now relying on useIdentityProviders() internally. LoginDialog (formerly LoginModal) is simplified to accept only isOpen and onOpenChange, deriving callbackUrl from pathname.

Sidebar Entitlement Gating and Upgrade UI

Layer / File(s) Summary
UpgradeBadge and UpgradeButton components
packages/web/src/app/(app)/@sidebar/components/upgradeBadge.tsx, packages/web/src/app/(app)/@sidebar/components/upgradeButton.tsx
A new UpgradeBadge component renders a styled purple badge with "Upgrade" text. UpgradeButton (renamed from UpsellBadge) accepts open/onOpenChange props, connecting to the new Lighthouse UpsellDialog.
Sidebar navigation entitlement gating
packages/web/src/app/(app)/@sidebar/components/defaultSidebar/nav.tsx, packages/web/src/app/(app)/@sidebar/components/settingsSidebar/nav.tsx, packages/web/src/app/(app)/settings/layout.tsx
Both default and settings sidebar Nav components extend their NavItem type with optional requiredEntitlement, load current entitlements via useEntitlements(), and conditionally render UpgradeBadge when an item's entitlement is missing. The "Ask" nav item is marked with requiredEntitlement: "ask" and "Analytics" with requiredEntitlement: "analytics".
Sidebar footer license gating
packages/web/src/app/(app)/@sidebar/components/sidebarBase.tsx
The sidebar footer switches from UpsellBadge to UpgradeButton when the owner lacks a valid license.
useEntitlements hook
packages/web/src/features/entitlements/useEntitlements.ts
A new client-side hook that reads and returns the entitlements value from PlanContext.

License Checkout Return and Activation Flow

Layer / File(s) Summary
Activation code types and API
packages/web/src/ee/features/lighthouse/types.ts, packages/web/src/ee/features/lighthouse/client.ts
New Zod schemas and types for claiming activation codes. The Lighthouse client gains a claimActivationCode method that POSTs to the claim endpoint.
useClaimActivationCode polling hook
packages/web/src/ee/features/lighthouse/useClaimActivationCode.ts
A new client-side hook polls the claimActivationCode server action with exponential backoff, tracking status, attempt count, and errors. Supports cancellation and unmount cleanup.
Server actions for checkout and activation
packages/web/src/ee/features/lighthouse/actions.ts
createCheckoutSession now accepts an optional returnPath parameter (validated against env.AUTH_URL) and defaults interval to 'year'. A new claimActivationCode server action retrieves the claimed code from Lighthouse.
LicenseActivactionDialog component
packages/web/src/ee/features/lighthouse/licenseActivactionDialog.tsx
A new client-side dialog guides users through license activation after Stripe checkout. It auto-polls for activation codes, auto-activates when a code is received, shows toast errors/success, triggers confetti, and supports manual code entry.
CheckoutReturnHandler component
packages/web/src/ee/features/lighthouse/checkoutReturnHandler.tsx
A new client-side component reads the session_id query parameter and conditionally renders LicenseActivactionDialog with the user's email.
License settings page cleanup
packages/web/src/app/(app)/settings/license/page.tsx, packages/web/src/app/(app)/settings/license/checkoutSuccessModal.tsx
The license settings page removes the CheckoutSuccessModal import and the user destructuring, relying on CheckoutReturnHandler in the root layout instead.
Root layout integration
packages/web/src/app/(app)/layout.tsx
The root layout imports and renders CheckoutReturnHandler, passing userEmail from the session.
Lighthouse UpsellDialog component
packages/web/src/ee/features/lighthouse/upsellDialog.tsx
A new client-side upsell dialog fetches enterprise offers, shows dynamic pricing based on billing interval selection, and initiates checkout with the chosen interval and optional returnPath.
Sidebar upsellDialog removal
packages/web/src/app/(app)/@sidebar/components/upsellDialog.tsx
The old upsellDialog component from the sidebar is removed.

Chat Submission Login and Upgrade Gating

Layer / File(s) Summary
Chat constants and actions cleanup
packages/web/src/features/chat/constants.ts, packages/web/src/features/chat/actions.ts
A new session-storage constant PENDING_CHAT_SUBMISSION_SESSION_STORAGE_KEY is added. The getAskGhLoginWallData server action is removed.
useCreateNewChatThread simplification
packages/web/src/features/chat/useCreateNewChatThread.ts
The hook is rewritten to remove login-wall gating and OAuth pending-message restoration. It now reads search scopes from localStorage internally.
ChatBox login and upgrade gating
packages/web/src/features/chat/components/chatBox/chatBox.tsx
ChatBox now accepts isLoginWallEnabled and isAuthenticated props, computes requiresLogin and requiresUpgrade checks, stores pending submissions to sessionStorage when gating is triggered, opens LoginDialog or UpsellDialog, and auto-submits when gating conditions are cleared.
ChatThread authentication wiring
packages/web/src/features/chat/components/chatThread/chatThread.tsx
ChatThread now requires isAuthenticated and isLoginWallEnabled as non-optional inputs and removes the in-component login-wall flow.
Chat pages and landing page wiring
packages/web/src/app/(app)/chat/[id]/page.tsx, packages/web/src/app/(app)/chat/chatLandingPage.tsx, packages/web/src/app/(app)/chat/[id]/components/chatThreadPanel.tsx, packages/web/src/app/(app)/chat/components/landingPageChatBox.tsx, packages/web/src/app/(app)/askgh/[owner]/[repo]/components/landingPage.tsx
The chat page, landing page, and LandingPageChatBox now pass isLoginWallEnabled (derived from EXPERIMENT_ASK_GH_ENABLED environment flag) to child components.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • sourcebot-dev/sourcebot#875: Both PRs change the ask_gh landing page/chat login flow by adding/removing a login modal and wiring authentication state into the submission flow/components.
  • sourcebot-dev/sourcebot#939: Both PRs touch the AskGH login-wall feature and directly modify the same chat creation flow—useCreateNewChatThread is refactored in the same area.
  • sourcebot-dev/sourcebot#358: Main PR adds entitlement-gated UI that references the "analytics" entitlement, which directly depends on that PR's addition of the "analytics" entitlement to shared types.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 6.25% 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 '[v5] chore(web): Ask upsell gating' directly describes the main change: implementing upsell and entitlement gating for the Ask feature across the web package, including upgrade badges, login walls, and upsell dialogs.
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 docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch bkellam/ask-feature-gating

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.

@brendan-kellam brendan-kellam changed the title [wip][v5] Ask upsell gating [v5] chore(web): Ask upsell gating May 24, 2026
@brendan-kellam brendan-kellam marked this pull request as ready for review May 24, 2026 02:03
@github-actions
Copy link
Copy Markdown
Contributor

@brendan-kellam your pull request is missing a changelog!

@brendan-kellam
Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 24, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown
Contributor

@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: 7

🧹 Nitpick comments (1)
packages/web/src/features/chat/useCreateNewChatThread.ts (1)

27-30: ⚡ Quick win

Add braces around the stored guard.

Line 29 violates the repo rule requiring braces on every if, so this will keep failing style expectations in follow-up changes.

Proposed fix
         try {
             const stored = window.localStorage.getItem(SELECTED_SEARCH_SCOPES_LOCAL_STORAGE_KEY);
-            if (stored) storedScopes = JSON.parse(stored) as SearchScope[];
+            if (stored) {
+                storedScopes = JSON.parse(stored) as SearchScope[];
+            }
         } catch { /* fall through to [] */ }
As per coding guidelines, "Always use curly braces for `if` statements, with the body on a new line — even for single-line bodies."
🤖 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 `@packages/web/src/features/chat/useCreateNewChatThread.ts` around lines 27 -
30, The guard "if (stored)" in useCreateNewChatThread.ts lacks braces; update
the block around retrieving parsed scopes
(SELECTED_SEARCH_SCOPES_LOCAL_STORAGE_KEY, stored, storedScopes) to use explicit
curly braces with the body on a new line per style rules — i.e., wrap the
JSON.parse assignment inside a braced if block so the code becomes an if
(stored) { storedScopes = JSON.parse(stored) as SearchScope[]; } within the try
block.
🤖 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 `@packages/web/src/app/`(app)/askgh/[owner]/[repo]/components/landingPage.tsx:
- Around line 79-80: The component currently hardcodes isLoginWallEnabled={true}
in landingPage.tsx which forces the login wall; change the call site to receive
and pass a runtime/config value instead: add or use an existing boolean (e.g.,
isLoginWallEnabled or featureFlags.isLoginWallEnabled) from the route props,
page loader, or config and pass that variable into the component instead of the
literal true so rollout/feature-flag control works (locate the prop usage of
isLoginWallEnabled in landingPage.tsx and the parent that renders it and thread
the boolean through).

In `@packages/web/src/app/`(app)/chat/[id]/page.tsx:
- Line 163: The isLoginWallEnabled prop is being set with a string comparison
that always fails because env.EXPERIMENT_ASK_GH_ENABLED is already a boolean;
update the assignment in the ChatThreadPanel instantiation so it passes the
boolean directly (use env.EXPERIMENT_ASK_GH_ENABLED or
Boolean(env.EXPERIMENT_ASK_GH_ENABLED)) instead of comparing to the string
'true' to ensure ChatThreadPanel receives true when the flag is enabled.

In `@packages/web/src/app/`(app)/chat/chatLandingPage.tsx:
- Line 72: The landing page currently sets isLoginWallEnabled using a string
comparison which prevents the boolean feature flag from working; update the prop
assignment in chatLandingPage.tsx to pass the boolean experiment flag directly
(use env.EXPERIMENT_ASK_GH_ENABLED) instead of comparing it to the string 'true'
so that isLoginWallEnabled receives the actual boolean value.

In `@packages/web/src/ee/features/lighthouse/checkoutReturnHandler.tsx`:
- Around line 15-23: The handler currently shows LicenseActivactionDialog
whenever a session_id query param exists; restrict it to only run for Stripe
success redirects by checking both searchParams.get("session_id") and
searchParams.get("checkout") === "success" before returning the dialog. Update
the logic in the component that calls useSearchParams (referencing searchParams
and sessionId) to require the checkout="success" value, and only then render
<LicenseActivactionDialog userEmail={userEmail} />; otherwise return null.

In `@packages/web/src/ee/features/lighthouse/upsellDialog.tsx`:
- Around line 219-226: The Switch control rendering in upsellDialog.tsx (the
Switch using checked={billingInterval === "year"} and onCheckedChange={(checked)
=> setBillingInterval(checked ? "year" : "month")}) lacks an accessible label;
update the Switch to include an accessible name by either adding an aria-label
(e.g., aria-label="Billing interval") or linking it to the visible text via
aria-labelledby (create an id for the <span> "Annual billing" and set
aria-labelledby on the Switch) so screen readers announce the control; ensure
the chosen approach is applied to the Switch instance and preserves the existing
checked/onCheckedChange behavior.

In `@packages/web/src/ee/features/lighthouse/useClaimActivationCode.ts`:
- Around line 35-39: The shared cancelledRef allows older polling loops to
continue after a new start() is invoked; change the cancellation to be
run-scoped by introducing a per-run token (e.g., a currentRunId or an
AbortController instance) stored in a ref (like currentRunRef) and have start()
create a new token and assign it before launching the poll loop; inside the loop
check the token specific to that run (compare runId or token.signal.aborted) so
only the matching run can be cancelled, and update cancel() to abort/mark only
the currentRunRef token rather than toggling a global cancelledRef; update any
references in useClaimActivationCode.ts (cancelledRef, cancel, start) to use the
new run-scoped token.

In `@packages/web/src/features/chat/components/chatBox/chatBox.tsx`:
- Around line 188-203: The pending chat payload saved under
PENDING_CHAT_SUBMISSION_SESSION_STORAGE_KEY currently only stores pathname and
editor.children, so restored drafts lose the user's selectedSearchScopes and run
against the wrong context after login/upgrade; update the save calls (the two
blocks that set sessionStorage) to include the draft scopes (e.g.,
selectedSearchScopes or however scopes are represented) alongside pathname and
children, then update the submit flow so _onSubmit (and callers in ChatThread
and LandingPageChatBox) accepts and honors restored scopes instead of falling
back to current state—ensure ChatThreadPanel rehydrates scopes from the pending
payload and that the restored scopes are threaded through the submit contract to
replay the original intent exactly.

---

Nitpick comments:
In `@packages/web/src/features/chat/useCreateNewChatThread.ts`:
- Around line 27-30: The guard "if (stored)" in useCreateNewChatThread.ts lacks
braces; update the block around retrieving parsed scopes
(SELECTED_SEARCH_SCOPES_LOCAL_STORAGE_KEY, stored, storedScopes) to use explicit
curly braces with the body on a new line per style rules — i.e., wrap the
JSON.parse assignment inside a braced if block so the code becomes an if
(stored) { storedScopes = JSON.parse(stored) as SearchScope[]; } within the try
block.
🪄 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: CHILL

Plan: Pro

Run ID: 36781576-5fab-4be5-944b-bade818ec8dc

📥 Commits

Reviewing files that changed from the base of the PR and between fb42cb8 and 3e96456.

📒 Files selected for processing (39)
  • packages/shared/src/entitlements.ts
  • packages/web/src/app/(app)/@sidebar/components/defaultSidebar/nav.tsx
  • packages/web/src/app/(app)/@sidebar/components/settingsSidebar/nav.tsx
  • packages/web/src/app/(app)/@sidebar/components/sidebarBase.tsx
  • packages/web/src/app/(app)/@sidebar/components/upgradeBadge.tsx
  • packages/web/src/app/(app)/@sidebar/components/upgradeButton.tsx
  • packages/web/src/app/(app)/@sidebar/components/upsellDialog.tsx
  • packages/web/src/app/(app)/askgh/[owner]/[repo]/components/landingPage.tsx
  • packages/web/src/app/(app)/chat/[id]/components/chatThreadPanel.tsx
  • packages/web/src/app/(app)/chat/[id]/page.tsx
  • packages/web/src/app/(app)/chat/chatLandingPage.tsx
  • packages/web/src/app/(app)/chat/components/landingPageChatBox.tsx
  • packages/web/src/app/(app)/layout.tsx
  • packages/web/src/app/(app)/settings/layout.tsx
  • packages/web/src/app/(app)/settings/license/checkoutSuccessModal.tsx
  • packages/web/src/app/(app)/settings/license/page.tsx
  • packages/web/src/app/components/authMethodSelector.tsx
  • packages/web/src/app/invite/page.tsx
  • packages/web/src/app/layout.tsx
  • packages/web/src/app/login/components/loginForm.tsx
  • packages/web/src/app/login/page.tsx
  • packages/web/src/app/onboard/page.tsx
  • packages/web/src/app/signup/page.tsx
  • packages/web/src/ee/features/lighthouse/actions.ts
  • packages/web/src/ee/features/lighthouse/checkoutReturnHandler.tsx
  • packages/web/src/ee/features/lighthouse/client.ts
  • packages/web/src/ee/features/lighthouse/licenseActivactionDialog.tsx
  • packages/web/src/ee/features/lighthouse/types.ts
  • packages/web/src/ee/features/lighthouse/upsellDialog.tsx
  • packages/web/src/ee/features/lighthouse/useClaimActivationCode.ts
  • packages/web/src/features/auth/identityProvidersProvider.tsx
  • packages/web/src/features/auth/useIdentityProviders.ts
  • packages/web/src/features/chat/actions.ts
  • packages/web/src/features/chat/components/chatBox/chatBox.tsx
  • packages/web/src/features/chat/components/chatBox/loginDialog.tsx
  • packages/web/src/features/chat/components/chatThread/chatThread.tsx
  • packages/web/src/features/chat/constants.ts
  • packages/web/src/features/chat/useCreateNewChatThread.ts
  • packages/web/src/features/entitlements/useEntitlements.ts
💤 Files with no reviewable changes (6)
  • packages/web/src/app/(app)/settings/license/checkoutSuccessModal.tsx
  • packages/web/src/app/login/page.tsx
  • packages/web/src/app/(app)/@sidebar/components/upsellDialog.tsx
  • packages/web/src/app/signup/page.tsx
  • packages/web/src/features/chat/actions.ts
  • packages/web/src/app/onboard/page.tsx

Comment thread packages/web/src/app/(app)/chat/[id]/page.tsx
Comment thread packages/web/src/app/(app)/chat/chatLandingPage.tsx
Comment thread packages/web/src/ee/features/lighthouse/checkoutReturnHandler.tsx
Comment thread packages/web/src/ee/features/lighthouse/upsellDialog.tsx
Comment thread packages/web/src/ee/features/lighthouse/useClaimActivationCode.ts
Comment thread packages/web/src/features/chat/components/chatBox/chatBox.tsx
@brendan-kellam brendan-kellam merged commit 501b1f1 into v5 May 24, 2026
11 checks passed
@brendan-kellam brendan-kellam deleted the bkellam/ask-feature-gating branch May 24, 2026 02:27
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