Skip to content

Conversation

brendan-kellam
Copy link
Contributor

@brendan-kellam brendan-kellam commented Aug 22, 2025

Next 15 introduces a bunch of new features. The most relevant one for us turbopack that significantly improves the experience when running the webapp in dev mode. Anecdotally, it feels 5-10x faster than before.

https://nextjs.org/blog/next-15

Summary by CodeRabbit

  • New Features

    • Upgraded to Next.js 15 and React 19; enabled TurboPack for faster local dev.
    • Improved client-side navigation in onboarding.
  • Bug Fixes

    • Fixed async handling to prevent runtime errors with route params.
    • Corrected header and cookie access (including Stripe webhook handling) and invite/org link generation.
  • Style

    • Adjusted global CSS import order for CodeMirror vs Tailwind.
  • Tests

    • Updated testing libraries for React 19 compatibility.
  • Chores

    • Pinned Prettier and refreshed linting/dependency versions.

Copy link

coderabbitai bot commented Aug 22, 2025

Walkthrough

The PR upgrades frontend dependencies (Next.js → 15, React → 19), adjusts tooling/config (turbopack, ESLint, Prettier pin), and migrates many route/layout/page components and API handlers to accept and await Promise-based params/searchParams/headers. It also fixes cookie/header access, updates navigation link usage, tests, and minor typings/CSS ordering.

Changes

Cohort / File(s) Summary
Changelog
CHANGELOG.md
Add Unreleased entry noting NextJS 15 update.
Root package config
package.json
Add resolutions pin for Prettier 3.5.3; minor packageManager line formatting.
Backend devdeps
packages/backend/package.json
Bump json-schema-to-typescript ^15.0.2 → ^15.0.4.
Web package + tooling
packages/web/package.json, packages/web/.eslintignore, packages/web/next.config.mjs, packages/web/tsconfig.json, packages/web/tailwind.config.ts, packages/web/src/app/globals.css
Next.js → 15.5.0; React → 19.1.1; dev next dev --turbopack; lint script updated; add turbopack: {} to config; add target: ES2017 in tsconfig; eslintignore adds next-env.d.ts; move codemirror CSS import; tailwind file formatting-only.
Route/layout/pages: params/searchParams → Promise
packages/web/src/app/[domain]/**/* (agents/page.tsx, browse/[...path]/page.tsx, chat/page.tsx, chat/[id]/page.tsx, connections/, repos/, settings/*, upgrade/page.tsx, invite/page.tsx, login/page.tsx, signup/page.tsx, onboard/page.tsx, redeem/page.tsx, etc.)
Change component signatures to accept props where params/searchParams are Promise<...>; await inside components to extract values. Minor behavioral tweaks: one error-path change in connections/page.
Actions, auth, cookie/header fixes, API routes
packages/web/src/actions.ts, packages/web/src/app/invite/actions.ts, packages/web/src/app/api/(server)/stripe/route.ts, packages/web/src/app/api/[domain]/repos/[repoId]/image/route.ts, packages/web/src/app/components/organizationAccessSettings.tsx
Mark some exported actions async; await cookies() and headers() before using .set()/.get(); API handlers updated to accept Promise-wrapped params and await them.
Navigation menu links
packages/web/src/app/[domain]/components/navigationMenu.tsx
Replace wrapping Next Link around items with NavigationMenuLink usage via href prop; NavigationMenuLink API gains href.
Tests (renderHook import)
packages/web/src/features/chat/useExtractReferences.test.ts, packages/web/src/features/chat/useMessagePairs.test.ts
Replace @testing-library/react-hooks import with @testing-library/react for renderHook.
Types, small formatting
packages/web/src/features/fileTree/components/fileTreeItemComponent.tsx, packages/web/src/features/chat/components/chatThread/referencedSourcesListView.tsx
Widen parentRef type to include null; ref callback formatting change; minor formatting-only updates.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor U as User
  participant R as Next Router
  participant P as Page/Layout Component
  participant S as Server Services

  U->>R: Request /[domain]/...
  R->>P: Invoke with props { params: Promise, searchParams: Promise }
  rect rgba(200,235,255,0.25)
    note right of P: New pattern
    P->>P: const params = await props.params
    P->>P: const search = await props.searchParams
  end
  P->>S: Fetch org/membership/data
  S-->>P: Data / error
  alt success
    P-->>U: Render
  else error
    P-->>U: redirect / notFound / throw
  end
Loading
sequenceDiagram
  autonumber
  participant Req as HTTP Request
  participant W as /api/(server)/stripe Route
  participant ST as Stripe SDK

  Req->>W: POST with headers including stripe-signature
  rect rgba(255,245,200,0.25)
    note right of W: Header access fixed
    W->>W: const sig = (await headers()).get('stripe-signature')
  end
  W->>ST: constructEvent(body, sig, secret)
  ST-->>W: Event
  W-->>Req: 200 / 400 response
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~30 minutes

Possibly related PRs

Suggested labels

sourcebot-team

Suggested reviewers

  • msukkari

Poem

I hopped through routes where Params now wait,
Nibbling Promises—what a tidy state!
Turbopack breezes, React nineteen sings,
Cookies and headers fetched with proper things.
A carrot commit, compiled with delight. 🥕

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.


📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 5e55f66 and 157ca00.

📒 Files selected for processing (1)
  • packages/web/src/app/[domain]/layout.tsx (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*

📄 CodeRabbit inference engine (.cursor/rules/style.mdc)

Filenames should always be camelCase. Exception: if there are filenames in the same directory with a format other than camelCase, use that format to keep things consistent.

Files:

  • packages/web/src/app/[domain]/layout.tsx
🧬 Code graph analysis (1)
packages/web/src/app/[domain]/layout.tsx (6)
packages/web/src/app/[domain]/connections/layout.tsx (1)
  • Layout (10-36)
packages/web/src/app/[domain]/repos/layout.tsx (1)
  • Layout (8-29)
packages/web/src/data/org.ts (1)
  • getOrgFromDomain (4-19)
packages/shared/src/entitlements.ts (1)
  • hasEntitlement (117-120)
packages/web/src/actions.ts (1)
  • getAnonymousAccessStatus (2089-2114)
packages/web/src/lib/utils.ts (1)
  • isServiceError (319-325)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (2)
packages/web/src/app/[domain]/layout.tsx (2)

31-41: LGTM: Awaiting params before destructuring; props-based signature is consistent with Next 15.

Using export default async function Layout(props) and awaiting props.params before extracting domain is the recommended pattern; mirroring this across other layouts/pages will avoid sync-dynamic-API warnings.
Relevant guidance: “Dynamic APIs are Asynchronous.” (nextjs.org)


28-29: All routing components use async params; no synchronous typings remain

  • Searched under packages/web/src/app and found no instances of params: { … } in any page.tsx, layout.tsx, or route.tsx files.
  • The only occurrence of a synchronous params type is in components/searchBar/searchSuggestionsBox.tsx (a local handler parameter, not the Next.js routing API).
  • Every usage of props.params in route files is preceded by await props.params, ensuring no direct property access on the promise.

Typing params as Promise<{ domain: string }> in packages/web/src/app/[domain]/layout.tsx correctly aligns with Next.js’s async dynamic APIs and enables optimal static/dynamic rendering.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch bkellam/next_15

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

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

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

This comment has been minimized.

msukkari
msukkari previously approved these changes Aug 22, 2025
Copy link

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
packages/web/src/app/login/page.tsx (1)

37-41: Sanitize callbackUrl to prevent open redirects

callbackUrl is user-controlled. Passing it through to the form (and ultimately auth flow) without validation can enable open redirects. Restrict to internal URLs only.

Apply this diff within the changed section to compute a safe value:

-    const searchParams = await props.searchParams;
+    const searchParams = await props.searchParams;
+    const callbackUrl = toSafeInternalUrl(searchParams?.callbackUrl);

Then update the JSX to use the sanitized value:

<LoginForm
  callbackUrl={callbackUrl}
  error={searchParams.error}
  providers={providers}
  context="login"
/>

Add this helper in the same file (outside the component):

function toSafeInternalUrl(raw?: string): string | undefined {
  if (!raw) return undefined;
  // Disallow absolute and protocol-relative URLs
  if (raw.startsWith('http://') || raw.startsWith('https://') || raw.startsWith('//')) return undefined;
  // Normalize relative paths
  return raw.startsWith('/') ? raw : `/${raw}`;
}

I can also extract this helper to a shared util and sweep other auth entry points that accept callbackUrl for consistency.

packages/web/src/app/onboard/page.tsx (1)

80-83: Guard against NaN/invalid step values

If step is non-numeric (e.g., ?step=abc), parseInt returns NaN, propagating to currentStep and causing steps[currentStep] to be undefined (runtime crash). Clamp and coerce safely.

Apply:

-    const stepParam = searchParams?.step ? parseInt(searchParams.step) : 0;
-    const currentStep = session?.user ? Math.max(2, stepParam) : Math.max(0, Math.min(stepParam, 1));
+    const rawStep = searchParams?.step;
+    const stepParam = Number.isFinite(Number(rawStep)) ? Math.max(0, Math.trunc(Number(rawStep))) : 0;
+    const currentStep = session?.user
+      ? Math.max(2, stepParam)
+      : Math.max(0, Math.min(stepParam, 1));

Optionally, also clamp currentStep to steps.length - 1 if you ever change the step count dynamically.

🧹 Nitpick comments (50)
packages/web/src/features/chat/components/chatThread/referencedSourcesListView.tsx (1)

200-206: Use a stable key across all states and avoid shadowing the index prop.

  • Keys switch from repo/path (loading/error) to fileId (success), which forces unnecessary remounts. Use fileId consistently.
  • The map callback parameter index shadows the index prop used by getFileId, which is confusing. Rename the local iterator.

Apply this diff:

-                {fileSourceQueries.map((query, index) => {
-                    const fileSource = referencedFileSources[index];
-                    const fileName = fileSource.path.split('/').pop() ?? fileSource.path;
+                {fileSourceQueries.map((query, qi) => {
+                    const fileSource = referencedFileSources[qi];
+                    const fileName = fileSource.path.split('/').pop() ?? fileSource.path;
+                    const fileId = getFileId(fileSource);
...
-                        return (
-                            <div key={`${fileSource.repo}/${fileSource.path}`} className="space-y-2">
+                        return (
+                            <div key={fileId} className="space-y-2">
...
-                        return (
-                            <div key={`${fileSource.repo}/${fileSource.path}`} className="space-y-2">
+                        return (
+                            <div key={fileId} className="space-y-2">
...
-                    const fileId = getFileId(fileSource);
+                    // fileId already computed above

Also applies to: 218-218, 232-233

packages/web/src/app/invite/actions.ts (3)

12-12: Nit: either drop async or await the sew(...) call for consistency

Two consistent options (no behavior change):

- export const joinOrganization = async (orgId: number, inviteLinkId?: string) => sew(async () =>
+ // Option A: keep async and explicitly await for readability/stack traces
+ export const joinOrganization = async (orgId: number, inviteLinkId?: string) => await sew(async () =>

or

- export const joinOrganization = async (orgId: number, inviteLinkId?: string) => sew(async () =>
+ // Option B: remove redundant async since we just return the Promise from sew(...)
+ export const joinOrganization = (orgId: number, inviteLinkId?: string) => sew(async () =>

Pick one pattern and apply it across server actions to keep signatures uniform in the Next 15 migration.


24-41: Invite link checks: consider 403 (Forbidden) and normalizing inputs

Current responses use 400 for “not enabled” and “invalid invite link.” Semantically, 403 could be more appropriate when membership requires approval but access isn’t permitted. Also consider trimming/normalizing inviteLinkId before comparison to avoid subtle mismatches (extra spaces, casing if applicable).

- if (!org.inviteLinkEnabled) {
-   return {
-     statusCode: StatusCodes.BAD_REQUEST,
+ if (!org.inviteLinkEnabled) {
+   return {
+     statusCode: StatusCodes.FORBIDDEN,
      errorCode: ErrorCode.INVITE_LINK_NOT_ENABLED,
      message: "Invite link is not enabled.",
    } satisfies ServiceError;
 }

- if (org.inviteLinkId !== inviteLinkId) {
+ const normalizedInviteId = inviteLinkId?.trim();
+ if (org.inviteLinkId !== normalizedInviteId) {
   return {
-    statusCode: StatusCodes.BAD_REQUEST,
+    statusCode: StatusCodes.FORBIDDEN,
     errorCode: ErrorCode.INVALID_INVITE_LINK,
     message: "Invalid invite link.",
   } satisfies ServiceError;
 }

If casing is insignificant for your IDs, also normalize case on both sides.


12-51: Add an explicit return type for clearer API contracts

Make the action’s contract obvious to callers and tooling by declaring the result type. This helps avoid accidental widening at call sites during the Next 15 refactor.

+ type JoinOrganizationResult = { success: true } | ServiceError;
- export const joinOrganization = async (orgId: number, inviteLinkId?: string) => sew(async () =>
+ export const joinOrganization = async (
+   orgId: number,
+   inviteLinkId?: string
+ ): Promise<JoinOrganizationResult> => sew(async () =>
packages/web/src/features/fileTree/components/fileTreeItemComponent.tsx (3)

25-25: Ref type nit: prefer RefObject<HTMLDivElement> (T is already nullable on .current) or accept MutableRefObject via union

React.RefObject<T> already models .current as T | null. Making T itself HTMLDivElement | null is redundant and atypical. If the goal is to accept refs created via useRef, consider a union that also allows MutableRefObject<HTMLDivElement | null>.

Apply one of the diffs:

Option A — simplest and conventional:

-    parentRef: React.RefObject<HTMLDivElement | null>,
+    parentRef: React.RefObject<HTMLDivElement>,

Option B — accept both createRef/useRef callers explicitly:

-    parentRef: React.RefObject<HTMLDivElement | null>,
+    parentRef: React.RefObject<HTMLDivElement> | React.MutableRefObject<HTMLDivElement | null>,

54-68: A11y: add ARIA semantics and keyboard support (Space, Arrow keys) for tree items

Current handler only supports Enter. For a file tree, consider adding role/ARIA and common keys to improve accessibility.

Example adjustments:

-        <div
+        <div
             ref={ref}
             className={clsx("flex flex-row gap-1 items-center hover:bg-accent hover:text-accent-foreground rounded-sm cursor-pointer p-0.5", {
                 'bg-accent': isActive,
             })}
             style={{ paddingLeft: `${depth * 16}px` }}
-            tabIndex={0}
+            tabIndex={0}
+            role="treeitem"
+            aria-selected={isActive}
+            aria-expanded={isCollapseChevronVisible ? !isCollapsed : undefined}
             onKeyDown={(e) => {
-                if (e.key === 'Enter') {
+                if (e.key === 'Enter' || e.key === ' ') {
                     e.preventDefault();
                     onClick();
+                } else if (e.key === 'ArrowRight' && isCollapsed) {
+                    e.preventDefault();
+                    onClick();
+                } else if (e.key === 'ArrowLeft' && !isCollapsed) {
+                    e.preventDefault();
+                    onClick();
                 }
             }}
             onClick={onClick}
         >

30-51: Optional refactor: simplify boundary usage

The project ships with scroll-into-view-if-needed v^3.1.0, which supports behavior: "instant" alongside "auto" and "smooth", and treats a boundary function such that returning true marks the current parent as the scroll boundary (stopping traversal) and false continues up the tree. You can therefore drop the manual rectangle checks and scope scrolling directly to the parent container.

• packages/web/src/features/fileTree/components/fileTreeItemComponent.tsx lines 30–51
• scroll-into-view-if-needed v^3.1.0 confirms:

  1. "instant" is valid for behavior
  2. A boundary function returning true stops at that parent, false continues traversal

Suggested diff:

         if (isActive && ref.current) {
             scrollIntoView(ref.current, {
                 scrollMode: 'if-needed',
-                block: 'center',
-                behavior: 'instant',
-                // We only want to scroll if the element is hidden vertically
-                // in the parent element.
-                boundary: () => {
-                    if (!parentRef.current || !ref.current) {
-                        return false;
-                    }
-
-                    const rect = ref.current.getBoundingClientRect();
-                    const parentRect = parentRef.current.getBoundingClientRect();
-
-                    const completelyAbove = rect.bottom <= parentRect.top;
-                    const completelyBelow = rect.top >= parentRect.bottom;
-                    return completelyAbove || completelyBelow;
-                }
+                block: 'center',
+                behavior: 'instant',
+                boundary: parentRef.current
             });
         }

This change is purely optional but reduces complexity and leverages the library’s built-in checks.

packages/web/src/features/chat/useMessagePairs.test.ts (1)

4-4: Migration Verified – renderHook from @testing-library/react is Good to Go

  • @testing-library/react is pinned at ^16.3.0, which includes renderHook support (introduced in v13.1+, with v14+ improvements)
  • Legacy @testing-library/react-hooks is no longer listed in dependencies
  • Vitest is configured via packages/web/vitest.config.mts and explicitly sets environment: 'jsdom'

Optional: for clarity, consider renaming the remaining test titles in
packages/web/src/features/chat/useMessagePairs.test.ts:

• Line 30

- test('pairMessages pairs orphaned user messages with undefined', () => {
+ test('useMessagePairs pairs orphaned user messages with undefined', () => {

• Line 60

- test('pairMessages ignores orphaned assistant messages', () => {
+ test('useMessagePairs ignores orphaned assistant messages', () => {

• Line 89

- test('pairMessages pairs the last message with undefined if it is a user message', () => {
+ test('useMessagePairs pairs the last message with undefined if it is a user message', () => {
CHANGELOG.md (1)

10-12: Consider noting React 19 and Turbopack dev changes for completeness

Since this PR upgrades to Next 15, if React was also bumped to 19 and Turbopack is now the default in dev, add those to “Changed” so operators know to expect new peer/engine requirements and faster dev builds.

package.json (1)

27-30: Add Prettier as a root devDependency and expose a format script

Prettier is only pinned via resolutions in the root package.json (v3.5.3) and isn’t installed anywhere else nor referenced in any scripts or plugins. To ensure editors, CI, and anyone cloning the repo can run the CLI directly:

• In the root package.json, under devDependencies, add

"prettier": "3.5.3"

• Add a top-level format script, for example:

"scripts": {
  "format": "prettier --write ."
}

• (No Prettier plugins—e.g. prettier-plugin-*—or ESLint integrations were detected. If you introduce any, double-check they support Prettier 3.5.3 before upgrading.)

packages/web/next.config.mjs (1)

41-42: Remove redundant empty turbopack config

Next.js 15.5.0 (≥ v15.3.0) recognizes the top-level turbopack key, but an empty object applies no custom settings and can be safely omitted to reduce config noise. (nextjs.org)

• File: packages/web/next.config.mjs
Line 42: Remove the no-op turbopack block.

Apply this diff:

--- a/packages/web/next.config.mjs
+++ b/packages/web/next.config.mjs
@@ -39,7 +39,6 @@ export default {
     // other Next.js config keys...
-    turbopack: {}
 }
packages/web/src/app/globals.css (1)

1-2: CSS cascade change: importing codemirror styles before Tailwind may reduce their precedence.

Moving @import "./codemirror-styles.css"; above Tailwind means Tailwind’s base/components/utilities can override CodeMirror rules with equal specificity. If the intent is for CodeMirror theme to win, import after Tailwind or increase selector specificity in the CodeMirror stylesheet.

Quick check in dev: scan for any lost styles (e.g., gutters, line highlights) after this change. If needed, revert order or scope CodeMirror styles under a higher-specificity container (e.g., .cm-theme .cm-editor { ... }).

packages/web/tsconfig.json (1)

32-33: Consider targeting a more modern ECMAScript version (e.g., ES2022).

With Node 18+/20 and React 19, a higher target improves type accuracy for newer built-ins and avoids downlevel semantics during type-checking. Since "noEmit": true, this won’t affect runtime output.

Proposed tweak:

-    "target": "ES2017"
+    "target": "ES2022"
+    // Optional: preserves type-only imports/exports and improves tree-shaking in tools
+    "verbatimModuleSyntax": true
packages/web/src/app/[domain]/chat/page.tsx (1)

18-25: Parallelize independent data fetches to reduce TTFB

getConfiguredLanguageModelsInfo, getRepos, getSearchContexts, and auth are independent. Await them concurrently, then conditionally fetch chat history. This typically shaves 100–300ms on cold route hits.

Apply:

-    const languageModels = await getConfiguredLanguageModelsInfo();
-    const repos = await getRepos(params.domain);
-    const searchContexts = await getSearchContexts(params.domain);
-    const session = await auth();
-    const chatHistory = session ? await getUserChatHistory(params.domain) : [];
+    const [languageModels, repos, searchContexts, session] = await Promise.all([
+        getConfiguredLanguageModelsInfo(),
+        getRepos(params.domain),
+        getSearchContexts(params.domain),
+        auth(),
+    ]);
+    const chatHistory = session ? await getUserChatHistory(params.domain) : [];
packages/web/src/app/api/[domain]/repos/[repoId]/image/route.ts (2)

11-15: Explicit radix for parseInt

Be explicit with base 10 to avoid edge cases.

-    const repoIdNum = parseInt(repoId);
+    const repoIdNum = parseInt(repoId, 10);

22-27: Optional: add strong caching and validators for image responses

If these images are content-addressable or infrequently changing, consider ETag/Last-Modified and a longer cache policy (and/or s-maxage for CDN). This reduces re-downloads and improves perceived performance.

packages/web/src/app/api/(server)/stripe/route.ts (1)

1-12: Pin Node.js runtime to avoid Edge pitfalls (Stripe + Prisma)

Stripe’s HMAC verification and Prisma aren’t Edge-friendly. Explicitly set the runtime to Node.js to prevent accidental regressions if project defaults change.

 import { headers } from 'next/headers';
 import { NextRequest } from 'next/server';
 import Stripe from 'stripe';
 import { prisma } from '@/prisma';
 import { ConnectionSyncStatus, StripeSubscriptionStatus } from '@sourcebot/db';
 import { stripeClient } from '@/ee/features/billing/stripe';
 import { env } from '@/env.mjs';
 import { createLogger } from "@sourcebot/logger";

 const logger = createLogger('stripe-webhook');
 
+export const runtime = 'nodejs';
+
 export async function POST(req: NextRequest) {
packages/web/src/app/[domain]/settings/license/page.tsx (1)

61-71: Small resilience/UX tweaks (optional)

  • If seats are undefined or 0, consider a fallback to avoid numMembers / seats surprises in future changes.
  • Consider rendering a tooltip on “Expired” to show exact UTC timestamp for support debugging.

Also applies to: 115-127

packages/web/src/app/login/page.tsx (1)

12-17: Standardize searchParams Optionality Across Pages

It looks like most pages currently declare searchParams as a required prop, whereas only the Onboarding page marks it optional. This discrepancy can lead to extra conditional checks and unexpected type churn when passing or handling searchParams.

Files with required searchParams: Promise<…>

  • packages/web/src/app/login/page.tsx
  • packages/web/src/app/invite/page.tsx
  • packages/web/src/app/signup/page.tsx
  • packages/web/src/app/redeem/page.tsx
  • packages/web/src/app/[domain]/connections/[id]/page.tsx
  • packages/web/src/app/[domain]/settings/members/page.tsx

File with optional searchParams?: Promise<…>

  • packages/web/src/app/onboard/page.tsx

Consider choosing one convention—either making searchParams required everywhere (and defaulting to an empty object when none are provided) or marking it optional in all pages—and updating the remaining files to match. This will reduce repetitive nullish checks and keep your prop interfaces consistent.

packages/web/src/app/[domain]/settings/billing/page.tsx (1)

89-91: Minor: stabilize date formatting across environments

toLocaleDateString() without options can vary across server locales/timezones and cause snapshot/test flakiness. Consider specifying locale or options (e.g., UTC) to ensure consistent renders.

Example:

new Date(subscription.nextBillingDate * 1000).toLocaleDateString('en-US', { timeZone: 'UTC' })
packages/web/src/app/onboard/page.tsx (1)

23-25: Async searchParams pattern is fine; make the optional type intentional

Awaiting an optional Promise is okay (awaiting undefined yields undefined). If the intent is “searchParams may be absent,” this is correctly modeled. If it’s always present, drop ? to simplify downstream checks.

Also applies to: 42-44

packages/web/src/app/[domain]/settings/secrets/page.tsx (1)

1-6: Avoid accidental caching on a secrets page

Secrets are sensitive and frequently updated. Ensure this route is dynamic and not statically cached.

You can enforce this at the route level:

export const dynamic = 'force-dynamic';

Or, call noStore() (or unstable_noStore() depending on your Next version) at the top of the component if you perform fetches that might otherwise cache.

I can sweep the settings pages and propose where dynamic = 'force-dynamic' is warranted if you’d like.

packages/web/src/app/signup/page.tsx (3)

12-17: Rename prop interface to match the page and factor a reusable type alias.

Using LoginProps in a signup page is confusing. Also, a small alias makes reuse across login/signup trivial.

-interface LoginProps {
-    searchParams: Promise<{
-        callbackUrl?: string;
-        error?: string;
-    }>
-}
+type SignupSearchParams = {
+  callbackUrl?: string;
+  error?: string;
+};
+interface SignupPageProps {
+  searchParams: Promise<SignupSearchParams>;
+}

19-20: Minor ergonomic tweak: one-liner destructure (or React’s use) for params.

Keeps the intent crisp, and aligns with the broader migration.

-export default async function Signup(props: LoginProps) {
-    const searchParams = await props.searchParams;
+export default async function Signup(props: SignupPageProps) {
+    const { callbackUrl, error } = await props.searchParams;

And below:

-                    callbackUrl={searchParams.callbackUrl}
-                    error={searchParams.error}
+                    callbackUrl={callbackUrl}
+                    error={error}

37-38: Guard against open-redirects via callbackUrl.

If callbackUrl can be absolute, ensure it’s validated (e.g., relative-only or whitelisted) before use in downstream flows. If already handled by LoginForm/auth flow, ignore.

packages/web/src/app/[domain]/settings/members/page.tsx (4)

26-38: Inline destructuring keeps the flow tighter.

Reduces vertical space and matches the updated approach elsewhere.

-export default async function MembersSettingsPage(props: MembersSettingsPageProps) {
-    const searchParams = await props.searchParams;
-
-    const {
-        tab
-    } = searchParams;
-
-    const params = await props.params;
-
-    const {
-        domain
-    } = params;
+export default async function MembersSettingsPage(props: MembersSettingsPageProps) {
+    const { tab } = await props.searchParams;
+    const { domain } = await props.params;

58-71: Fetch members/invites/requests in parallel to cut TTFB.

These calls are independent; batching them with Promise.all will shave a round trip or two under load.

-    const members = await getOrgMembers(domain);
-    if (isServiceError(members)) {
-        throw new ServiceErrorException(members);
-    }
-
-    const invites = await getOrgInvites(domain);
-    if (isServiceError(invites)) {
-        throw new ServiceErrorException(invites);
-    }
-
-    const requests = await getOrgAccountRequests(domain);
-    if (isServiceError(requests)) {
-        throw new ServiceErrorException(requests);
-    }
+    const [members, invites, requests] = await Promise.all([
+        getOrgMembers(domain),
+        getOrgInvites(domain),
+        getOrgAccountRequests(domain),
+    ]);
+    if (isServiceError(members)) throw new ServiceErrorException(members);
+    if (isServiceError(invites)) throw new ServiceErrorException(invites);
+    if (isServiceError(requests)) throw new ServiceErrorException(requests);

73-74: Validate tab to known values to avoid unknown state.

If TabSwitcher/Tabs receives an unknown value, ensure it degrades gracefully. Otherwise normalize to "members".

Example:

const allowedTabs = new Set(["members", "requests", "invites"]);
const currentTab = allowedTabs.has(tab ?? "") ? (tab as string) : "members";

14-14: Unify OrgRole imports to @sourcebot/db

I found four locations still importing OrgRole directly from @prisma/client. To ensure a single source-of-truth and avoid any enum mismatches, please update them to import from @sourcebot/db instead:

• packages/web/src/app/[domain]/settings/layout.tsx (line 13)
• packages/web/src/app/[domain]/settings/members/page.tsx (line 14)
• packages/web/src/app/[domain]/settings/members/components/membersList.tsx (line 10)
• packages/web/src/app/[domain]/settings/members/components/inviteMemberCard.tsx (line 12)

Change each:

-import { OrgRole } from "@prisma/client";
+import { OrgRole } from "@sourcebot/db";

This aligns with the rest of the codebase where OrgRole is consumed from @sourcebot/db.

packages/web/src/app/redeem/page.tsx (2)

17-19: Inline destructure (or use) for clarity.

Keeps things succinct and idiomatic.

-export default async function RedeemPage(props: RedeemPageProps) {
-    const searchParams = await props.searchParams;
+export default async function RedeemPage(props: RedeemPageProps) {
+    const { invite_id: inviteId } = await props.searchParams;

And below:

-    const inviteId = searchParams.invite_id;

19-33: Optional: parallelize org and session lookups.

Shaves latency when both are needed. If you want to keep the early redirect for not-onboarded orgs without fetching session, skip this.

-    const org = await getOrgFromDomain(SINGLE_TENANT_ORG_DOMAIN);
+    const org = await getOrgFromDomain(SINGLE_TENANT_ORG_DOMAIN);
     if (!org || !org.isOnboarded) {
         return redirect("/onboard");
     }
-
-    const session = await auth();
+    const session = await auth();

Alternate (parallel):

const [{ isOnboarded, ...orgRest }, session] = await Promise.all([
  getOrgFromDomain(SINGLE_TENANT_ORG_DOMAIN),
  auth(),
]);
// early-return if not onboarded, else proceed
packages/web/src/app/[domain]/settings/access/page.tsx (2)

15-21: Inline destructure of params for brevity.

-export default async function AccessPage(props: AccessPageProps) {
-    const params = await props.params;
-
-    const {
-        domain
-    } = params;
+export default async function AccessPage(props: AccessPageProps) {
+    const { domain } = await props.params;

22-31: Parallelize org and user lookups.

getOrgFromDomain and getMe are independent; fetch together to reduce latency.

-    const org = await getOrgFromDomain(domain);
-    if (!org) {
-        throw new Error("Organization not found");
-    }
-
-    const me = await getMe();
+    const [org, me] = await Promise.all([
+        getOrgFromDomain(domain),
+        getMe(),
+    ]);
+    if (!org) {
+        throw new Error("Organization not found");
+    }
     if (isServiceError(me)) {
         throw new ServiceErrorException(me);
     }
packages/web/src/app/invite/page.tsx (3)

19-21: Inline destructure for consistency.

-export default async function InvitePage(props: InvitePageProps) {
-    const searchParams = await props.searchParams;
+export default async function InvitePage(props: InvitePageProps) {
+    const { id: inviteLinkId } = await props.searchParams;

And below:

-    const inviteLinkId = searchParams.id;

79-79: Encode callbackUrl query param.

If inviteLinkId ever contains reserved characters, this avoids malformed URLs. If you’re certain it’s base64/slug-safe, this is optional.

-                        callbackUrl={`/invite?id=${inviteLinkId}`}
+                        callbackUrl={`/invite?id=${encodeURIComponent(inviteLinkId)}`}

53-55: Positioning nit: make the wrapper relative if the escape hatch should anchor to it.

redeem/page.tsx uses a relative wrapper; this page doesn’t. If the intent is to pin to the page container rather than viewport, add relative.

-        <div className="min-h-screen flex items-center justify-center p-6">
+        <div className="min-h-screen relative flex items-center justify-center p-6">
packages/web/src/app/[domain]/settings/(general)/page.tsx (1)

16-22: Inline destructuring of awaited params for brevity.

Slight simplification; same behavior, less code.

-export default async function GeneralSettingsPage(props: GeneralSettingsPageProps) {
-    const params = await props.params;
-
-    const {
-        domain
-    } = params;
+export default async function GeneralSettingsPage(props: GeneralSettingsPageProps) {
+    const { domain } = await props.params;
packages/web/src/actions.ts (3)

189-213: Async signature is fine; optionally await the sew call for consistency/readability.

Functionally equivalent, but explicitly awaiting makes intent clearer and aligns with other async functions returning awaited results.

-export const createOrg = async (name: string, domain: string): Promise<{ id: number } | ServiceError> => sew(() =>
+export const createOrg = async (name: string, domain: string): Promise<{ id: number } | ServiceError> => await sew(() =>

296-314: Same optional readability tweak for getSecrets.

-export const getSecrets = async (domain: string): Promise<{ createdAt: Date; key: string; }[] | ServiceError> => sew(() =>
+export const getSecrets = async (domain: string): Promise<{ createdAt: Date; key: string; }[] | ServiceError> => await sew(() =>

1993-1995: Avoid double-await on cookies().set; mirror usage style elsewhere in this file.

cookies() is async; .set() is synchronous in server actions. The current await (await cookies()).set(...) is harmless but odd.

-export const dismissMobileUnsupportedSplashScreen = async () => sew(async () => {
-    await (await cookies()).set(MOBILE_UNSUPPORTED_SPLASH_SCREEN_DISMISSED_COOKIE_NAME, 'true');
+export const dismissMobileUnsupportedSplashScreen = async () => sew(async () => {
+    const cookieStore = await cookies();
+    cookieStore.set(MOBILE_UNSUPPORTED_SPLASH_SCREEN_DISMISSED_COOKIE_NAME, 'true');
     return true;
 });
packages/web/src/app/[domain]/page.tsx (1)

18-24: Inline destructuring of awaited params for brevity.

Matches the style used elsewhere and reduces noise.

-export default async function Home(props: { params: Promise<{ domain: string }> }) {
-    const params = await props.params;
-
-    const {
-        domain
-    } = params;
+export default async function Home(props: { params: Promise<{ domain: string }> }) {
+    const { domain } = await props.params;
packages/web/src/app/[domain]/agents/page.tsx (1)

16-22: Inline destructuring of awaited params for brevity.

Consistent with other pages and keeps the component concise.

-export default async function AgentsPage(props: { params: Promise<{ domain: string }> }) {
-  const params = await props.params;
-
-  const {
-    domain
-  } = params;
+export default async function AgentsPage(props: { params: Promise<{ domain: string }> }) {
+  const { domain } = await props.params;
packages/web/src/app/[domain]/repos/page.tsx (1)

7-12: Nit: inline destructuring of awaited params for brevity.

Slightly simpler and consistent with other pages that destructure in one line.

Apply this diff:

-export default async function ReposPage(props: { params: Promise<{ domain: string }> }) {
-    const params = await props.params;
-
-    const {
-        domain
-    } = params;
+export default async function ReposPage(props: { params: Promise<{ domain: string }> }) {
+    const { domain } = await props.params;
packages/web/src/app/[domain]/connections/page.tsx (1)

24-24: Preserve original error details from getOrgMembership.

Re-wrapping the error as notFound() discards potentially useful context (e.g., auth vs. membership issues). Prefer throwing the original membership service error.

Apply this diff:

-    if (isServiceError(membership)) {
-        throw new ServiceErrorException(notFound());
-    }
+    if (isServiceError(membership)) {
+        throw new ServiceErrorException(membership);
+    }

If you adopt this, remove the now-unused notFound import on Line 6.

packages/web/src/app/[domain]/layout.tsx (1)

96-99: Nit: unnecessary await on synchronous hasEntitlement.

hasEntitlement returns a boolean synchronously. Drop the await for clarity.

Apply this diff:

-            const ssoEntitlement = await hasEntitlement("sso");
+            const ssoEntitlement = hasEntitlement("sso");
packages/web/src/app/[domain]/chat/[id]/page.tsx (1)

22-24: Parallelize independent data fetches to reduce TTFB.

Fetching resources serially increases latency. You can compute several in parallel, then fetch chatHistory conditionally after auth() resolves.

Example refactor (outside this hunk for illustration):

const params = await props.params;

const [languageModels, repos, searchContexts, chatInfo, session] = await Promise.all([
  getConfiguredLanguageModelsInfo(),
  getRepos(params.domain),
  getSearchContexts(params.domain),
  getChatInfo({ chatId: params.id }, params.domain),
  auth(),
]);

const chatHistory = session ? await getUserChatHistory(params.domain) : [];
packages/web/src/app/[domain]/connections/new/[type]/page.tsx (1)

12-12: Valid use() import for Promise-based route params in Next 15

Using React’s use() to unwrap params is compatible with the Next.js 15 change where params/searchParams are Promises. In client components, an alternative is useParams() from next/navigation, which returns a plain object and avoids suspending on the client. Consider it if you want to simplify the prop types here. (nextjs.org)

packages/web/src/app/[domain]/connections/[id]/page.tsx (3)

37-39: Await both promises concurrently for slightly cleaner/faster code

Fetching both at once avoids an extra microtask and reads a bit cleaner.

Refs: Dynamic APIs are async in v15. (nextjs.org)

-export default async function ConnectionManagementPage(props: ConnectionManagementPageProps) {
-  const searchParams = await props.searchParams;
-  const params = await props.params;
+export default async function ConnectionManagementPage(props: ConnectionManagementPageProps) {
+  const [params, searchParams] = await Promise.all([props.params, props.searchParams]);

52-53: Normalize the tab value (handle string[] and clamp to known tabs)

searchParams.tab can be string | string[] | undefined. Unknown values currently yield an empty view since no <TabsContent> matches. Normalize and clamp to avoid blank UI.

Docs show searchParams values can be arrays. (nextjs.org)

-const currentTab = searchParams.tab || "overview";
+const allowedTabs = new Set(["overview", "settings"]);
+const requestedTab = Array.isArray(searchParams.tab) ? searchParams.tab[0] : searchParams.tab;
+const currentTab = requestedTab && allowedTabs.has(requestedTab) ? requestedTab : "overview";

40-43: Prefer next/navigation notFound() for missing resources (proper 404 semantics & caching)

Returning a custom <NotFound /> component here won’t trigger the route’s 404 behavior. Using notFound() ensures correct status and route-level not-found handling via app/not-found.tsx. (nextjs.org)

-const connection = await getConnectionByDomain(Number(params.id), params.domain);
-if (!connection) {
-  return <NotFound className="flex w/full h/full items-center justify-center" message="Connection not found" />
-}
+const idNum = Number(params.id);
+if (!Number.isFinite(idNum)) {
+  notFound();
+}
+const connection = await getConnectionByDomain(idNum, params.domain);
+if (!connection) {
+  notFound();
+}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between b36de34 and 5e55f66.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (44)
  • CHANGELOG.md (1 hunks)
  • package.json (1 hunks)
  • packages/backend/package.json (1 hunks)
  • packages/web/.eslintignore (1 hunks)
  • packages/web/next.config.mjs (1 hunks)
  • packages/web/package.json (5 hunks)
  • packages/web/src/actions.ts (3 hunks)
  • packages/web/src/app/[domain]/agents/page.tsx (1 hunks)
  • packages/web/src/app/[domain]/browse/[...path]/page.tsx (1 hunks)
  • packages/web/src/app/[domain]/chat/[id]/page.tsx (1 hunks)
  • packages/web/src/app/[domain]/chat/page.tsx (1 hunks)
  • packages/web/src/app/[domain]/components/navigationMenu.tsx (1 hunks)
  • packages/web/src/app/[domain]/connections/[id]/page.tsx (1 hunks)
  • packages/web/src/app/[domain]/connections/layout.tsx (1 hunks)
  • packages/web/src/app/[domain]/connections/new/[type]/page.tsx (2 hunks)
  • packages/web/src/app/[domain]/connections/page.tsx (1 hunks)
  • packages/web/src/app/[domain]/layout.tsx (1 hunks)
  • packages/web/src/app/[domain]/page.tsx (1 hunks)
  • packages/web/src/app/[domain]/repos/layout.tsx (1 hunks)
  • packages/web/src/app/[domain]/repos/page.tsx (1 hunks)
  • packages/web/src/app/[domain]/settings/(general)/page.tsx (1 hunks)
  • packages/web/src/app/[domain]/settings/access/page.tsx (1 hunks)
  • packages/web/src/app/[domain]/settings/billing/page.tsx (1 hunks)
  • packages/web/src/app/[domain]/settings/layout.tsx (1 hunks)
  • packages/web/src/app/[domain]/settings/license/page.tsx (1 hunks)
  • packages/web/src/app/[domain]/settings/members/page.tsx (1 hunks)
  • packages/web/src/app/[domain]/settings/secrets/page.tsx (1 hunks)
  • packages/web/src/app/[domain]/upgrade/page.tsx (1 hunks)
  • packages/web/src/app/api/(server)/stripe/route.ts (1 hunks)
  • packages/web/src/app/api/[domain]/repos/[repoId]/image/route.ts (1 hunks)
  • packages/web/src/app/components/organizationAccessSettings.tsx (1 hunks)
  • packages/web/src/app/globals.css (1 hunks)
  • packages/web/src/app/invite/actions.ts (1 hunks)
  • packages/web/src/app/invite/page.tsx (1 hunks)
  • packages/web/src/app/login/page.tsx (1 hunks)
  • packages/web/src/app/onboard/page.tsx (5 hunks)
  • packages/web/src/app/redeem/page.tsx (1 hunks)
  • packages/web/src/app/signup/page.tsx (1 hunks)
  • packages/web/src/features/chat/components/chatThread/referencedSourcesListView.tsx (1 hunks)
  • packages/web/src/features/chat/useExtractReferences.test.ts (1 hunks)
  • packages/web/src/features/chat/useMessagePairs.test.ts (1 hunks)
  • packages/web/src/features/fileTree/components/fileTreeItemComponent.tsx (1 hunks)
  • packages/web/tailwind.config.ts (1 hunks)
  • packages/web/tsconfig.json (2 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*

📄 CodeRabbit inference engine (.cursor/rules/style.mdc)

Filenames should always be camelCase. Exception: if there are filenames in the same directory with a format other than camelCase, use that format to keep things consistent.

Files:

  • packages/web/src/app/api/(server)/stripe/route.ts
  • CHANGELOG.md
  • packages/web/src/app/signup/page.tsx
  • packages/web/src/features/chat/components/chatThread/referencedSourcesListView.tsx
  • packages/web/src/app/components/organizationAccessSettings.tsx
  • packages/backend/package.json
  • packages/web/src/app/[domain]/agents/page.tsx
  • packages/web/src/features/chat/useMessagePairs.test.ts
  • packages/web/src/app/[domain]/page.tsx
  • packages/web/src/features/fileTree/components/fileTreeItemComponent.tsx
  • packages/web/src/app/[domain]/upgrade/page.tsx
  • packages/web/src/app/[domain]/settings/access/page.tsx
  • packages/web/src/app/api/[domain]/repos/[repoId]/image/route.ts
  • packages/web/next.config.mjs
  • packages/web/src/app/[domain]/connections/[id]/page.tsx
  • packages/web/src/app/redeem/page.tsx
  • packages/web/src/features/chat/useExtractReferences.test.ts
  • packages/web/src/app/globals.css
  • packages/web/tsconfig.json
  • packages/web/src/app/[domain]/settings/license/page.tsx
  • packages/web/src/app/[domain]/repos/page.tsx
  • packages/web/src/app/[domain]/chat/[id]/page.tsx
  • packages/web/src/app/[domain]/chat/page.tsx
  • packages/web/src/app/[domain]/settings/(general)/page.tsx
  • packages/web/package.json
  • packages/web/tailwind.config.ts
  • packages/web/src/app/[domain]/settings/members/page.tsx
  • package.json
  • packages/web/src/app/[domain]/connections/page.tsx
  • packages/web/src/app/[domain]/layout.tsx
  • packages/web/src/app/[domain]/settings/billing/page.tsx
  • packages/web/src/app/login/page.tsx
  • packages/web/src/actions.ts
  • packages/web/src/app/invite/actions.ts
  • packages/web/src/app/onboard/page.tsx
  • packages/web/src/app/[domain]/repos/layout.tsx
  • packages/web/src/app/[domain]/browse/[...path]/page.tsx
  • packages/web/src/app/[domain]/connections/layout.tsx
  • packages/web/src/app/[domain]/components/navigationMenu.tsx
  • packages/web/src/app/invite/page.tsx
  • packages/web/src/app/[domain]/settings/secrets/page.tsx
  • packages/web/src/app/[domain]/settings/layout.tsx
  • packages/web/src/app/[domain]/connections/new/[type]/page.tsx
🧬 Code graph analysis (9)
packages/web/src/app/[domain]/chat/[id]/page.tsx (1)
packages/web/src/app/[domain]/chat/page.tsx (1)
  • Page (18-64)
packages/web/src/app/[domain]/chat/page.tsx (1)
packages/web/src/app/[domain]/chat/[id]/page.tsx (1)
  • Page (22-91)
packages/web/src/app/[domain]/connections/page.tsx (2)
packages/web/src/actions.ts (2)
  • getConnections (575-609)
  • getOrgMembership (1632-1650)
packages/web/src/lib/serviceError.ts (2)
  • ServiceErrorException (16-20)
  • notFound (91-97)
packages/web/src/app/[domain]/layout.tsx (5)
packages/web/src/app/[domain]/connections/layout.tsx (1)
  • Layout (10-36)
packages/web/src/app/[domain]/repos/layout.tsx (1)
  • Layout (8-29)
packages/web/src/data/org.ts (1)
  • getOrgFromDomain (4-19)
packages/shared/src/entitlements.ts (1)
  • hasEntitlement (117-120)
packages/web/src/actions.ts (1)
  • getAnonymousAccessStatus (2089-2114)
packages/web/src/actions.ts (2)
packages/web/src/lib/serviceError.ts (1)
  • ServiceError (11-11)
packages/web/src/lib/constants.ts (1)
  • MOBILE_UNSUPPORTED_SPLASH_SCREEN_DISMISSED_COOKIE_NAME (25-25)
packages/web/src/app/invite/actions.ts (1)
packages/web/src/actions.ts (1)
  • sew (57-70)
packages/web/src/app/[domain]/repos/layout.tsx (2)
packages/web/src/app/[domain]/connections/layout.tsx (1)
  • Layout (10-36)
packages/web/src/app/[domain]/layout.tsx (1)
  • Layout (31-148)
packages/web/src/app/[domain]/connections/layout.tsx (2)
packages/web/src/app/[domain]/layout.tsx (1)
  • Layout (31-148)
packages/web/src/app/[domain]/repos/layout.tsx (1)
  • Layout (8-29)
packages/web/src/app/[domain]/components/navigationMenu.tsx (2)
packages/web/src/components/ui/navigation-menu.tsx (3)
  • NavigationMenuLink (125-125)
  • navigationMenuTriggerStyle (119-119)
  • NavigationMenuItem (122-122)
packages/web/src/env.mjs (2)
  • env (14-167)
  • env (14-167)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (47)
packages/web/src/features/chat/components/chatThread/referencedSourcesListView.tsx (2)

248-250: LGTM: ref callback change is a no-op.

Switching to a block-bodied arrow function doesn’t change behavior. Safe with React 19 and CodeMirror refs.


236-247: ReferencedFileSourceListItem correctly forwards ReactCodeMirrorRef

I’ve confirmed that the component is wrapped with forwardRef and its exported signature explicitly includes ref?: Ref<ReactCodeMirrorRef>, so the ref will be passed through as intended. No further changes are needed.

• File: packages/web/src/features/chat/components/chatThread/referencedFileSourceListItem.tsx, lines 357–359

export default forwardRef(ReferencedFileSourceListItem) as (
    props: ReferencedFileSourceListItemProps & { ref?: Ref<ReactCodeMirrorRef> },
) => ReturnType<typeof ReferencedFileSourceListItem>;
packages/web/src/app/invite/actions.ts (1)

12-12: All joinOrganization calls are properly awaited; async signature change is safe.

The only usage (joinOrganizationButton.tsx:21) already uses await, and no un-awaited call sites were found. No further action needed.

packages/backend/package.json (1)

18-18: json-schema-to-typescript bump – no regeneration needed

I searched the entire packages/backend folder for any imports or API calls to json-schema-to-typescript (looking for from 'json-schema-to-typescript', compileFromFile(, or compile() and found zero matches. No generated-file banners (“Generated by json-schema-to-typescript”) were present, and there’s no codegen step in the backend’s npm scripts. As there are no live codegen hooks or pre-generated artifacts, bumping to ^15.0.4 is safe and requires no additional type regeneration or committed diffs.

packages/web/.eslintignore (1)

3-3: LGTM: exclude Next’s generated next-env.d.ts from linting

This avoids noisy/auto-generated type file diffs in ESLint. No further action needed.

packages/web/tsconfig.json (1)

3-7: Formatting and path mapping changes look good.

Multi-line arrays for lib, paths, include, and exclude improve readability without changing semantics. No issues spotted.

Also applies to: 25-31, 34-43

packages/web/src/app/components/organizationAccessSettings.tsx (1)

20-23: Awaiting headers() aligns with Next 15 behavior; ensure getBaseUrl’s signature expects the resolved headers.

If headers() remains sync in some environments, await still safely returns the value. Confirm getBaseUrl accepts the ReadonlyHeaders (or equivalent) returned by Next 15 to avoid subtle type mismatches.

If getBaseUrl currently types the param as Headers, consider widening to ReadonlyHeaders | Headers to match Next’s types.

packages/web/src/features/chat/useExtractReferences.test.ts (1)

2-2: Dependencies and imports updated correctly

  • Verified @testing-library/react@^16.3.0 is declared in packages/web/package.json.
  • Confirmed @testing-library/react-hooks is no longer present in dependencies.
  • No remaining imports of @testing-library/react-hooks in the codebase.
packages/web/tailwind.config.ts (1)

4-161: Formatting-only changes — config semantics unchanged, LGTM

Indentation and spacing are standardized; no functional differences detected in darkMode, content globs, theme.extend, or plugins. Safe to merge.

packages/web/src/app/[domain]/chat/page.tsx (2)

48-53: UX check: default side panel expanded

isCollapsedInitially={false} differs from the chat-thread page (which appears to default collapsed). If intentional for “new chat” flow, ignore; otherwise consider aligning for consistency.


12-16: All page components use async Promise-based params and no synchronous header/cookie calls detected

Spot checks across the packages/web codebase confirm:

  • No page components destructure params synchronously in their function signatures.
  • All PageProps interfaces declare params as a Promise<{ domain: string }> (no plain-object variants).
  • No direct (synchronous) calls to headers().get|has|set|append or cookies().get|has|set|delete remain.

No changes are required.

packages/web/src/app/api/[domain]/repos/[repoId]/image/route.ts (1)

5-10: Double-check Route Handler params typing (Promise vs. plain object)

You’ve typed props: { params: Promise<{ domain: string; repoId: string }> } and then await it. Ensure this matches Next 15’s Route Handler context typing. Historically, route handler context delivered a plain { params }. Awaiting a non-promise is harmless at runtime, but drifting from official types can obscure future breaking changes.

If needed, I can open a follow-up to normalize the typing across all Route Handlers to the canonical Next 15 type.

packages/web/src/app/api/(server)/stripe/route.ts (2)

14-14: Correct: headers() is now awaited — LGTM

Switching to const signature = (await headers()).get('stripe-signature') aligns with the async API. Good catch.


12-18: All asynchronous header/cookie accesses verified

The repo-wide sweep found no remaining synchronous calls to headers() or cookies() in packages/web. No further migration is necessary—this change can be considered complete.

packages/web/src/app/[domain]/settings/license/page.tsx (1)

15-21: Async params migration looks good

Awaiting props.params and deferring domain extraction is consistent with the new pattern and safe. No logic changes beyond the signature refactor.

packages/web/src/app/[domain]/upgrade/page.tsx (2)

15-21: Async params migration looks correct

Awaiting props.params and destructuring domain is consistent with Next 15’s Promise-based route props. No functional changes introduced here.


15-21: Fix missing await for Promise-based params in the new Connections page

During verification, all of our route pages and layouts that declare params as a Promise<…> correctly await props.params before use—except for the new Connections “create” page. Failing to await the promise can lead to runtime errors.

Please update the following file to mirror the pattern used elsewhere:

  • packages/web/src/app/[domain]/connections/new/[type]/page.tsx

Example adjustment:

-export default async function NewConnection(props: { params: Promise<{ domain: string; type: string }> }) {
-    const { domain, type } = props.params; // ❌ missing await
+export default async function NewConnection(props: { params: Promise<{ domain: string; type: string }> }) {
+    const params = await props.params;       // ✅ await the Promise
+    const { domain, type } = params;

After making this change, double-check any other routes/layouts that use params: Promise<…> to ensure they all consistently await props.params.

Likely an incorrect or invalid review comment.

packages/web/src/app/login/page.tsx (1)

13-16: Async searchParams pattern is correct

Typing searchParams as a Promise and awaiting it inside the component aligns with Next 15 route props behavior. Looks good.

Also applies to: 19-21

packages/web/src/app/[domain]/settings/billing/page.tsx (1)

19-22: Async params migration looks good

Resolving params via await props.params and extracting domain is correct. No behavioral regressions evident.

Also applies to: 24-30

packages/web/src/app/onboard/page.tsx (2)

2-2: Good switch to next/link for internal nav

Using <Link> improves client-side navigation and avoids full reloads. Nice.


123-124: Link replacements in steps are correct

Both step transitions now use <Link> inside Button asChild, matching shadcn’s recommended pattern. Looks good.

Also applies to: 175-176

packages/web/src/app/[domain]/settings/secrets/page.tsx (1)

8-11: Async params migration looks good

Awaiting props.params and extracting domain is consistent and correct.

Also applies to: 13-19

packages/web/src/app/[domain]/settings/members/page.tsx (1)

18-24: LGTM on Promise-based props migration.

Typing params/searchParams as Promise and awaiting locally is consistent with the Next 15 pattern adopted across the PR.

packages/web/src/app/redeem/page.tsx (1)

11-15: LGTM on Promise-wrapped searchParams.

Matches the migration across the app; no behavior change.

packages/web/src/app/[domain]/settings/access/page.tsx (2)

9-13: LGTM on async params migration.

The page now awaits Promise-based params; consistent with the broader upgrade.


7-7: Standardize OrgRole import source across the codebase

We’ve identified that OrgRole is imported from both @prisma/client and @sourcebot/db, which risks enum drift. To keep a single source of truth, please update all imports to use @sourcebot/db. Specifically, change the following files:

  • packages/web/src/app/[domain]/settings/layout.tsx
  • packages/web/src/app/[domain]/settings/members/page.tsx
  • packages/web/src/app/[domain]/settings/members/components/inviteMemberCard.tsx
  • packages/web/src/app/[domain]/settings/members/components/membersList.tsx

In each, replace:

- import { OrgRole } from "@prisma/client";
+ import { OrgRole } from "@sourcebot/db";

This will ensure every reference to OrgRole comes from the same canonical package.

Likely an incorrect or invalid review comment.

packages/web/src/app/invite/page.tsx (1)

13-17: LGTM on Promise-wrapped searchParams.

Matches the PR-wide migration; no behavior change.

packages/web/src/app/[domain]/settings/(general)/page.tsx (1)

11-14: Params-as-Promise prop typing looks good for Next 15.

This matches the new async request APIs in Next 15 where params is a Promise. No issues.

packages/web/package.json (3)

6-10: Turbopack in dev and direct ESLint invocation — LGTM.

next dev --turbopack is appropriate for faster HMR, and calling ESLint directly is fine given you have eslint-config-next in devDependencies.


217-220: Yarn lockfile detected: resolutions is supported

The repository uses a yarn.lock file at the root, so Yarn is the package manager in use. Yarn (both v1 and v2+) honors the resolutions field in package.json, meaning your overrides for @types/react and @types/react-dom will be applied as intended. No changes are necessary.


149-163: Verified Next.js 15.5.0 ⇔ React 19.1.1 Compatibility

  • Ran npm view [email protected] peerDependencies and confirmed:
    "react": "^18.2.0 || ^19.0.0"
    "react-dom": "^18.2.0 || ^19.0.0"
    These semver ranges include React 19.1.1 and react-dom 19.1.1, so your versions satisfy Next.js’s requirements.
  • The official 15.5.0 release notes and docs do not call out any incompatibilities with React 19.x.

No peer-dependency warnings should surface when installing [email protected] alongside [email protected]/[email protected]. Feel free to proceed.

packages/web/src/app/[domain]/repos/page.tsx (1)

7-12: Next.js 15 params migration looks correct.

Using a Promise-wrapped params and awaiting it in the page is aligned with Next 15’s async request APIs. No functional regressions spotted in the rest of the component.

packages/web/src/app/[domain]/connections/page.tsx (1)

10-16: Next.js 15 params migration looks correct.

Awaiting props.params and destructuring domain matches the new async params API.

packages/web/src/app/[domain]/components/navigationMenu.tsx (2)

60-65: Migration to NavigationMenuLink with href prop looks good.

The updates simplify markup and are consistent with the updated UI component API.

Also applies to: 68-74, 79-85, 88-94, 96-102


60-65: Verify internal routing semantics (client-side nav & prefetch).

Since NavigationMenuLink now takes href directly, ensure it wraps next/link or otherwise preserves client-side navigation, prefetch, and accessibility for internal routes. If it renders a plain <a>, you may lose prefetch and transitions.

Would you confirm that NavigationMenuLink (packages/web/src/components/ui/navigation-menu.tsx) uses next/link under the hood for internal URLs?

Also applies to: 68-74, 79-85, 88-94, 96-102

packages/web/src/app/[domain]/layout.tsx (2)

31-41: Async Layout + Promise-based params LGTM.

The LayoutProps and await props.params pattern align with Next 15 changes. Children extraction is clear.


131-135: Good adoption of async headers()/cookies() in Next 15.

Using await headers() and await cookies() matches the updated API and keeps this route dynamic.

packages/web/src/app/[domain]/chat/[id]/page.tsx (1)

16-20: Next 15 params migration looks correct.

params as a Promise and awaiting it before use is consistent with the new request API.

Also applies to: 22-24

packages/web/src/app/[domain]/connections/new/[type]/page.tsx (2)

15-17: Signature/migration looks correct for async params

Typing params as Promise<{ type: string }> and unwrapping with use() aligns with the Next 15 guidance to treat params as async. This prevents the “accessed directly with params” warning and is future-proof. (nextjs.org)


48-48: No-op whitespace change

Nothing to do here.

packages/web/src/app/[domain]/settings/layout.tsx (2)

16-20: Local LayoutProps type is clear and appropriate

Defining a local interface with params: Promise<{ domain: string }> matches the Next 15 async-props model and keeps the contract explicit.


25-37: Async params usage consistent
I’ve verified that all props.params accesses in your app and route handlers are correctly awaited or unwrapped (via use) before destructuring. No leftover synchronous destructuring of props.params was found.

• Layouts/pages (e.g. SettingsLayout) use await props.params
• Server components (e.g. NewConnectionPage) correctly use use(props.params)

Consider a similar audit for any searchParams usages elsewhere.

packages/web/src/app/[domain]/browse/[...path]/page.tsx (1)

8-12: Good migration to Promise-based params in a Server Component

Typing params as a Promise and awaiting it in the page matches Next 15’s updated contract for dynamic segments. The rest of the logic remains unchanged and safe. (nextjs.org)

Also applies to: 14-21

packages/web/src/app/[domain]/connections/layout.tsx (2)

5-9: Type shape matches Next 15 Layout expectations

LayoutProps with params: Promise<{ domain: string }> is consistent with the framework’s new async params model.


10-22: Async params handling is correct; render path unchanged

Awaiting params up front and then rendering NavigationMenu with the resolved domain is straightforward and keeps behavior intact.

packages/web/src/app/[domain]/repos/layout.tsx (2)

3-7: Accurate LayoutProps for async params

Matches patterns elsewhere in the PR and aligns with Next 15.


8-20: Solid, minimal migration

Awaiting params and passing domain to NavigationMenu preserves behavior. No issues spotted.

@brendan-kellam brendan-kellam merged commit d9fa221 into main Aug 22, 2025
5 of 6 checks passed
@brendan-kellam brendan-kellam deleted the bkellam/next_15 branch August 22, 2025 18:48
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.

2 participants