-
Notifications
You must be signed in to change notification settings - Fork 134
chore(web): Upgrade to NextJS 15 #477
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
WalkthroughThe 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 Changes
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
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
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~30 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
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 detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 💡 Knowledge Base configuration:
You can enable these sources in your CodeRabbit configuration. 📒 Files selected for processing (1)
🧰 Additional context used📓 Path-based instructions (1)**/*📄 CodeRabbit inference engine (.cursor/rules/style.mdc)
Files:
🧬 Code graph analysis (1)packages/web/src/app/[domain]/layout.tsx (6)
⏰ 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)
🔇 Additional comments (2)
✨ Finishing Touches
🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this 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 valuesIf
step
is non-numeric (e.g.,?step=abc
),parseInt
returnsNaN
, propagating tocurrentStep
and causingsteps[currentStep]
to beundefined
(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
tosteps.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 theindex
prop.
- Keys switch from
repo/path
(loading/error) tofileId
(success), which forces unnecessary remounts. UsefileId
consistently.- The
map
callback parameterindex
shadows theindex
prop used bygetFileId
, 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 aboveAlso applies to: 218-218, 232-233
packages/web/src/app/invite/actions.ts (3)
12-12
: Nit: either dropasync
orawait
thesew(...)
call for consistencyTwo 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 inputsCurrent 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 contractsMake 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: preferRefObject<HTMLDivElement>
(T is already nullable on.current
) or acceptMutableRefObject
via union
React.RefObject<T>
already models.current
asT | null
. MakingT
itselfHTMLDivElement | null
is redundant and atypical. If the goal is to accept refs created viauseRef
, consider a union that also allowsMutableRefObject<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 itemsCurrent 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 usageThe project ships with scroll-into-view-if-needed v^3.1.0, which supports
behavior: "instant"
alongside"auto"
and"smooth"
, and treats aboundary
function such that returningtrue
marks the current parent as the scroll boundary (stopping traversal) andfalse
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:
"instant"
is valid forbehavior
- A
boundary
function returningtrue
stops at that parent,false
continues traversalSuggested 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 includesrenderHook
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 setsenvironment: '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 completenessSince 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 scriptPrettier is only pinned via
resolutions
in the rootpackage.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
, underdevDependencies
, 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 emptyturbopack
configNext.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-opturbopack
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": truepackages/web/src/app/[domain]/chat/page.tsx (1)
18-25
: Parallelize independent data fetches to reduce TTFB
getConfiguredLanguageModelsInfo
,getRepos
,getSearchContexts
, andauth
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 parseIntBe 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 responsesIf 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
: StandardizesearchParams
Optionality Across PagesIt 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 handlingsearchParams
.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 intentionalAwaiting an optional Promise is okay (awaiting
undefined
yieldsundefined
). 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 pageSecrets 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()
(orunstable_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’suse
) 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
: UnifyOrgRole
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 (oruse
) 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 proceedpackages/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 thesew
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 forgetSecrets
.-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 currentawait (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 originalmembership
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 synchronoushasEntitlement
.
hasEntitlement
returns a boolean synchronously. Drop theawait
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 afterauth()
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 15Using 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 codeFetching 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 bestring | 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. UsingnotFound()
ensures correct status and route-level not-found handling viaapp/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.
⛔ 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 ReactCodeMirrorRefI’ve confirmed that the component is wrapped with
forwardRef
and its exported signature explicitly includesref?: 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–359export default forwardRef(ReferencedFileSourceListItem) as ( props: ReferencedFileSourceListItemProps & { ref?: Ref<ReactCodeMirrorRef> }, ) => ReturnType<typeof ReferencedFileSourceListItem>;packages/web/src/app/invite/actions.ts (1)
12-12
: AlljoinOrganization
calls are properly awaited; async signature change is safe.The only usage (
joinOrganizationButton.tsx:21
) already usesawait
, 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 neededI 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(
, orcompile(
) 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 lintingThis 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
, andexclude
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
: Awaitingheaders()
aligns with Next 15 behavior; ensuregetBaseUrl
’s signature expects the resolved headers.If
headers()
remains sync in some environments,await
still safely returns the value. ConfirmgetBaseUrl
accepts theReadonlyHeaders
(or equivalent) returned by Next 15 to avoid subtle type mismatches.If
getBaseUrl
currently types the param asHeaders
, consider widening toReadonlyHeaders | 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, LGTMIndentation 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 detectedSpot checks across the
packages/web
codebase confirm:
- No page components destructure
params
synchronously in their function signatures.- All
PageProps
interfaces declareparams
as aPromise<{ domain: string }>
(no plain-object variants).- No direct (synchronous) calls to
headers().get|has|set|append
orcookies().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 thenawait
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 — LGTMSwitching to
const signature = (await headers()).get('stripe-signature')
aligns with the async API. Good catch.
12-18
: All asynchronous header/cookie accesses verifiedThe repo-wide sweep found no remaining synchronous calls to
headers()
orcookies()
inpackages/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 goodAwaiting
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 correctAwaiting
props.params
and destructuringdomain
is consistent with Next 15’s Promise-based route props. No functional changes introduced here.
15-21
: Fix missingawait
for Promise-basedparams
in the new Connections pageDuring verification, all of our route pages and layouts that declare
params
as aPromise<…>
correctlyawait 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 consistentlyawait props.params
.Likely an incorrect or invalid review comment.
packages/web/src/app/login/page.tsx (1)
13-16
: Async searchParams pattern is correctTyping
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 goodResolving
params
viaawait props.params
and extractingdomain
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 navUsing
<Link>
improves client-side navigation and avoids full reloads. Nice.
123-124
: Link replacements in steps are correctBoth step transitions now use
<Link>
insideButton 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 goodAwaiting
props.params
and extractingdomain
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 codebaseWe’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 haveeslint-config-next
in devDependencies.
217-220
: Yarn lockfile detected:resolutions
is supportedThe 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 destructuringdomain
matches the new async params API.packages/web/src/app/[domain]/components/navigationMenu.tsx (2)
60-65
: Migration toNavigationMenuLink
withhref
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 takeshref
directly, ensure it wrapsnext/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) usesnext/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
andawait props.params
pattern align with Next 15 changes. Children extraction is clear.
131-135
: Good adoption of asyncheaders()
/cookies()
in Next 15.Using
await headers()
andawait 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 paramsTyping 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 changeNothing to do here.
packages/web/src/app/[domain]/settings/layout.tsx (2)
16-20
: Local LayoutProps type is clear and appropriateDefining 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 allprops.params
accesses in your app and route handlers are correctly awaited or unwrapped (viause
) before destructuring. No leftover synchronous destructuring ofprops.params
was found.• Layouts/pages (e.g.
SettingsLayout
) useawait props.params
• Server components (e.g.NewConnectionPage
) correctly useuse(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 ComponentTyping 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 expectationsLayoutProps with params: Promise<{ domain: string }> is consistent with the framework’s new async params model.
10-22
: Async params handling is correct; render path unchangedAwaiting 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 paramsMatches patterns elsewhere in the PR and aligns with Next 15.
8-20
: Solid, minimal migrationAwaiting params and passing domain to NavigationMenu preserves behavior. No issues spotted.
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
Bug Fixes
Style
Tests
Chores