Conversation
… MessageReaction tables - Created Channel table with necessary fields and constraints. - Created Message table with fields for content, author details, and relationships. - Created MessageReaction table to track reactions to messages. - Added unique indexes for workspace and message reactions. - Established foreign key relationships between Message and Channel, and Message and MessageReaction. - Added migration lock file for version control.
|
@PAVANT009 is attempting to deploy a commit to the devsethi3's projects Team on Vercel. A member of the Team first needs to authorize it. |
There was a problem hiding this comment.
Pull request overview
This PR aims to fix emoji/reaction UI state during realtime updates by preserving reactedByMe, while also introducing several broader backend and infra changes (Kinde Management API usage/error handling, Arcjet middleware behavior edits, Prisma initial migration, and an AI model switch).
Changes:
- Preserve local
reactedByMewhen applying realtimereaction:updated/thread:reaction:updatedpayloads. - Add Kinde Management API env validation + richer error handling for workspace/member routes; add a new
/workspace/debugendpoint and expose it via the router. - Add Prisma migrations/lockfile, tweak Arcjet middleware messages/logic, and change the OpenRouter model ID.
Reviewed changes
Copilot reviewed 13 out of 14 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
| providers/ThreadRealtimeProvider.tsx | Merges reaction updates while preserving reactedByMe from cached state. |
| providers/ChannelRealtimeProvider.tsx | Same reaction-merge behavior for channel message lists. |
| app/(dashboard)/.../ReactionBar.tsx | Fixes optimistic toggle logic to increment/decrement based on reactedByMe. |
| app/router/workspace.ts | Switches workspace listing to Kinde Management API, adds env checks/error handling, and adds a debug endpoint. |
| app/router/member.ts | Adds env checks and improved Kinde Management API error handling for invite/list members. |
| app/router/index.ts | Exposes workspace.debug route. |
| app/router/ai.ts | Switches OpenRouter model constant. |
| app/middleware/arcjet/write.ts | Tweaks rate-limit message (but contains problematic denial-reason branching). |
| app/middleware/arcjet/heavy-write.ts | Edits denial branching (currently misclassifies rate-limit vs PII). |
| prisma/migrations/migration_lock.toml | Adds Prisma migration lock metadata. |
| prisma/migrations/20260309171649_init/migration.sql | Adds initial DB schema migration. |
| .vscode/settings.json | Adds workspace spellchecker words. |
| .env.example | Deleted environment template file. |
Comments suppressed due to low confidence (2)
app/middleware/arcjet/write.ts:44
decision.reason.isRateLimit()is checked twice in a row; the second branch is unreachable because the first throws. Also the second branch’s message refers to sensitive-info/PII but is gated byisRateLimit(), so it will never run for PII denials. Use the correct reason predicate for the sensitive-info rule (and keep the rate-limit branch separate).
if (decision.isDenied()) {
if (decision.reason.isRateLimit()) {
throw errors.RATE_LIMITED({
message: "Too many impactful changes. Please slow down",
});
}
if (decision.reason.isRateLimit()) {
throw errors.BAD_REQUEST({
message:
"Sensitive Information Detected! Please Remove PII (e.g., credit cards, phone numbers).",
});
}
app/middleware/arcjet/heavy-write.ts:44
- This middleware throws
BAD_REQUESTwith a PII/sensitive-info message whendecision.reason.isRateLimit()is true. That mixes up denial reasons and will misclassify rate-limit denials (and likely never emit the PII-specific error for sensitive-info denials). Split the handling by using the correct reason predicate for sensitive-info and returnRATE_LIMITEDfor actual rate limits.
if (decision.isDenied()) {
// if (decision.reason.isRateLimit()) {
// throw errors.RATE_LIMITED({
// message: "Too many impactual changes. Please slow down",
// });
// }
if (decision.reason.isRateLimit()) {
throw errors.BAD_REQUEST({
message:
"Sensitive Information Detected! Please Remove PII (e.g., credit cards, phone numbers).",
});
}
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // const MODEL_ID = "amazon/nova-2-lite-v1:free"; | ||
| const MODEL_ID = "nvidia/nemotron-nano-9b-v2:free" | ||
| const model = openrouter.chat(MODEL_ID); |
There was a problem hiding this comment.
This change switches the OpenRouter model and leaves a commented-out MODEL_ID, but the new MODEL_ID line has inconsistent spacing and is missing a trailing semicolon, which may fail formatting/lint checks used elsewhere in the repo. Please align with the surrounding style (single space around = and consistent semicolons) and remove dead commented code if it’s not needed.
| const mergeReactions = ( | ||
| prev: RealtimeMessageType["reactions"] | undefined, | ||
| next: RealtimeMessageType["reactions"] | ||
| ) => { | ||
| const prevMap = new Map<string, boolean>(); | ||
| (prev ?? []).forEach((r) => { | ||
| prevMap.set(r.emoji, r.reactedByMe); | ||
| }); | ||
|
|
||
| return next.map((r) => ({ | ||
| ...r, | ||
| reactedByMe: prevMap.get(r.emoji) ?? false, | ||
| })); | ||
| }; |
There was a problem hiding this comment.
mergeReactions is duplicated in both ChannelRealtimeProvider and ThreadRealtimeProvider. Consider extracting it into a shared helper to prevent the implementations from drifting over time.
| function ensureKindeManagementEnv() { | ||
| const missing: string[] = []; | ||
|
|
||
| if (!process.env.KINDE_MANAGEMENT_CLIENT_ID) { | ||
| missing.push("KINDE_MANAGEMENT_CLIENT_ID"); | ||
| } | ||
| if (!process.env.KINDE_MANAGEMENT_CLIENT_SECRET) { | ||
| missing.push("KINDE_MANAGEMENT_CLIENT_SECRET"); | ||
| } | ||
| if (!process.env.KINDE_DOMAIN) { | ||
| missing.push("KINDE_DOMAIN"); | ||
| } | ||
|
|
||
| if (missing.length > 0) { | ||
| throw new Error(`Missing required Kinde env vars: ${missing.join(", ")}`); | ||
| } | ||
| } |
There was a problem hiding this comment.
This PR removes .env.example, but the README still instructs cp .env.example .env.local and documents env vars there. Either keep an .env.example checked in (recommended) or update the README/setup instructions accordingly, otherwise new dev setup will break.
| const userData = await Users.getUserData({ | ||
| id: context.user.id, | ||
| expand: "organizations", | ||
| }); | ||
|
|
||
| return { | ||
| workspaces: organizations?.orgs.map((org) => ({ | ||
| id: org.code, | ||
| name: org.name ?? "My Workspace", | ||
| avatar: org.name?.charAt(0) ?? "M", | ||
| })), | ||
| user: context.user, | ||
| currentWorkspace: context.workspace, | ||
| }; | ||
| const orgCodes = userData.organizations ?? []; | ||
| const orgs = await Promise.all( | ||
| orgCodes.map(async (code) => { | ||
| try { | ||
| const org = await Organizations.getOrganization({ code }); | ||
| return { | ||
| id: org.code ?? code, | ||
| name: org.name ?? "My Workspace", | ||
| avatar: (org.name ?? "M").charAt(0), | ||
| }; | ||
| } catch { | ||
| return { | ||
| id: code, | ||
| name: "My Workspace", | ||
| avatar: "M", | ||
| }; | ||
| } | ||
| }) | ||
| ); |
There was a problem hiding this comment.
listWorkspace now does one Management API call to get org codes and then N additional calls (Organizations.getOrganization) in parallel. This can easily hit rate limits and adds latency for users with many orgs. Prefer an API that returns org details in a single call, reuse getUserOrganizations() from the session if it’s sufficient, or add caching/throttling to avoid a fan-out per request.
| function getKindeErrorMessage(error: unknown): string { | ||
| if (!error) return "Unknown Kinde error"; | ||
|
|
||
| const maybeError = error as { | ||
| message?: string; | ||
| body?: unknown; | ||
| }; | ||
|
|
||
| const body = maybeError.body; | ||
| if (body && typeof body === "object") { | ||
| const record = body as Record<string, unknown>; | ||
| const message = | ||
| (typeof record.message === "string" && record.message) || | ||
| (typeof record.error === "string" && record.error) || | ||
| (typeof record.error_description === "string" && | ||
| record.error_description); | ||
|
|
||
| if (message) return message; | ||
| } | ||
|
|
||
| if (maybeError.message) return maybeError.message; | ||
| return "Unknown Kinde error"; | ||
| } | ||
|
|
||
| function ensureKindeManagementEnv() { | ||
| const missing: string[] = []; | ||
|
|
||
| if (!process.env.KINDE_MANAGEMENT_CLIENT_ID) { | ||
| missing.push("KINDE_MANAGEMENT_CLIENT_ID"); | ||
| } | ||
| if (!process.env.KINDE_MANAGEMENT_CLIENT_SECRET) { | ||
| missing.push("KINDE_MANAGEMENT_CLIENT_SECRET"); | ||
| } | ||
| if (!process.env.KINDE_DOMAIN) { | ||
| missing.push("KINDE_DOMAIN"); | ||
| } | ||
|
|
||
| if (missing.length > 0) { | ||
| throw new Error(`Missing required Kinde env vars: ${missing.join(", ")}`); | ||
| } | ||
| } |
There was a problem hiding this comment.
getKindeErrorMessage and ensureKindeManagementEnv are duplicated here and in app/router/member.ts, which increases the chance they drift. Consider moving them to a shared server-side utility (e.g. app/lib/kinde.ts) and importing from both routers.
| export const debugSession = base | ||
| .use(requiredAuthMiddleware) | ||
| .route({ | ||
| method: "GET", | ||
| path: "/workspace/debug", | ||
| summary: "Debug Kinde session claims", | ||
| tags: ["workspace"], | ||
| }) |
There was a problem hiding this comment.
The new debugSession endpoint exposes session/user/org details and is wired into the public router. Even with requiredAuthMiddleware, this expands the API surface in production for a debug-only purpose. Consider removing it before merge or gating it behind a development-only check / admin-only authorization.
| function getKindeErrorMessage(error: unknown): string { | ||
| if (!error) return "Unknown Kinde error"; | ||
|
|
||
| const maybeError = error as { | ||
| message?: string; | ||
| body?: unknown; | ||
| }; | ||
|
|
||
| const body = maybeError.body; | ||
| if (body && typeof body === "object") { | ||
| const record = body as Record<string, unknown>; | ||
| const message = | ||
| (typeof record.message === "string" && record.message) || | ||
| (typeof record.error === "string" && record.error) || | ||
| (typeof record.error_description === "string" && | ||
| record.error_description); | ||
|
|
||
| if (message) return message; | ||
| } | ||
|
|
||
| if (maybeError.message) return maybeError.message; | ||
| return "Unknown Kinde error"; | ||
| } | ||
|
|
||
| function ensureKindeManagementEnv() { | ||
| const missing: string[] = []; | ||
|
|
||
| if (!process.env.KINDE_MANAGEMENT_CLIENT_ID) { | ||
| missing.push("KINDE_MANAGEMENT_CLIENT_ID"); | ||
| } | ||
| if (!process.env.KINDE_MANAGEMENT_CLIENT_SECRET) { | ||
| missing.push("KINDE_MANAGEMENT_CLIENT_SECRET"); | ||
| } | ||
| if (!process.env.KINDE_DOMAIN) { | ||
| missing.push("KINDE_DOMAIN"); | ||
| } | ||
|
|
||
| if (missing.length > 0) { | ||
| throw new Error(`Missing required Kinde env vars: ${missing.join(", ")}`); | ||
| } | ||
| } |
There was a problem hiding this comment.
getKindeErrorMessage and ensureKindeManagementEnv are duplicated here and in app/router/workspace.ts, which increases maintenance cost and risk of inconsistent behavior. Consider extracting these helpers into a shared module and reusing them across routers.
| // const MODEL_ID = "amazon/nova-2-lite-v1:free"; | ||
| const MODEL_ID = "nvidia/nemotron-nano-9b-v2:free" | ||
| const model = openrouter.chat(MODEL_ID); |
There was a problem hiding this comment.
PR title indicates an emoji/reaction fix, but this PR also introduces new Prisma migrations, changes Arcjet middleware behavior, switches the AI model, and adds a debug workspace endpoint. This makes the change set hard to review and riskier to ship; please split unrelated changes into separate PRs or update the PR title/description to reflect the full scope.
| const mergeReactions = ( | ||
| prev: RealtimeMessageType["reactions"] | undefined, | ||
| next: RealtimeMessageType["reactions"] | ||
| ) => { | ||
| const prevMap = new Map<string, boolean>(); | ||
| (prev ?? []).forEach((r) => { | ||
| prevMap.set(r.emoji, r.reactedByMe); | ||
| }); | ||
|
|
||
| return next.map((r) => ({ | ||
| ...r, | ||
| reactedByMe: prevMap.get(r.emoji) ?? false, | ||
| })); | ||
| }; |
There was a problem hiding this comment.
mergeReactions is duplicated in both ThreadRealtimeProvider and ChannelRealtimeProvider. To avoid divergence, consider moving it to a shared helper (e.g. lib/reactions.ts) and importing it from both providers.
|
Thanks for the contribution! However this PR includes multiple unrelated changes:
This makes it difficult to review safely. Please split this into smaller PRs: 1️⃣ emoji reaction fix Once separated it will be much easier to review and merge. Also please add a proper PR description explaining the changes. |
…l and Thread providers
No description provided.