Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 0 additions & 28 deletions .env.example

This file was deleted.

8 changes: 8 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"cSpell.words": [
"arcjet",
"Kinde",
"openrouter",
"orpc"
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,15 +48,23 @@ const ReactionBar = ({ messageId, reactions, context }: ReactionBarProps) => {
const found = rxns.find((r) => r.emoji === emoji);

if (found) {
const newCount = found.count - 1;

return newCount <= 0
? rxns.filter((r) => r.emoji !== emoji)
: rxns.map((r) =>
r.emoji === emoji
? { ...r, count: newCount, reactedByMe: false }
: r
);
if (found.reactedByMe) {
const newCount = found.count - 1;

return newCount <= 0
? rxns.filter((r) => r.emoji !== emoji)
: rxns.map((r) =>
r.emoji === emoji
? { ...r, count: newCount, reactedByMe: false }
: r
);
}

return rxns.map((r) =>
r.emoji === emoji
? { ...r, count: r.count + 1, reactedByMe: true }
: r
);
}

return [...rxns, { emoji, count: 1, reactedByMe: true }];
Expand Down
12 changes: 6 additions & 6 deletions app/middleware/arcjet/heavy-write.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,11 @@ export const heavyWriteSecurityMiddleware = base
});

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.RATE_LIMITED({
// message: "Too many impactual changes. Please slow down",
// });
// }

if (decision.reason.isRateLimit()) {
throw errors.BAD_REQUEST({
Expand All @@ -47,4 +47,4 @@ export const heavyWriteSecurityMiddleware = base
}

return next();
});
});
2 changes: 1 addition & 1 deletion app/middleware/arcjet/write.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export const writeSecurityMiddleware = base
if (decision.isDenied()) {
if (decision.reason.isRateLimit()) {
throw errors.RATE_LIMITED({
message: "Too many impactual changes. Please slow down",
message: "Too many impactful changes. Please slow down",
});
}

Expand Down
4 changes: 2 additions & 2 deletions app/router/ai.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ const openrouter = createOpenRouter({
apiKey: process.env.OPENROUTER_LLM_KEY,
});

const MODEL_ID = "amazon/nova-2-lite-v1:free";

// const MODEL_ID = "amazon/nova-2-lite-v1:free";
const MODEL_ID = "nvidia/nemotron-nano-9b-v2:free"
const model = openrouter.chat(MODEL_ID);
Comment on lines +16 to 18
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines +16 to 18
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.

export const generateThreadSummary = base
Expand Down
3 changes: 2 additions & 1 deletion app/router/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,13 @@ import {
toggleMessageReaction,
updateMessage,
} from "./message";
import { createWorkspace, listWorkspace } from "./workspace";
import { createWorkspace, listWorkspace, debugSession } from "./workspace";

export const router = {
workspace: {
list: listWorkspace,
create: createWorkspace,
debug: debugSession,
member: {
list: listMembers,
invite: inviteMember,
Expand Down
107 changes: 103 additions & 4 deletions app/router/member.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { base } from "../middleware/base";
import { requiredWorkspaceMiddleware } from "../middleware/workspace";
import { inviteMemberSchema } from "../schemas/member";
import {
ApiError,
init,
organization_user,
Organizations,
Expand All @@ -14,6 +15,48 @@ import {
import { getAvatar } from "@/lib/get-avatar";
import { readSecurityMiddleware } from "../middleware/arcjet/read";

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(", ")}`);
}
}
Comment on lines +18 to +58
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.

export const inviteMember = base
.use(requiredAuthMiddleware)
.use(requiredWorkspaceMiddleware)
Expand All @@ -29,6 +72,7 @@ export const inviteMember = base
.output(z.void())
.handler(async ({ input, context, errors }) => {
try {
ensureKindeManagementEnv();
init();

await Users.createUser({
Expand All @@ -48,8 +92,41 @@ export const inviteMember = base
],
},
});
} catch {
throw errors.INTERNAL_SERVER_ERROR();
} catch (error: unknown) {
if (error instanceof ApiError) {
const reason = getKindeErrorMessage(error);

if (error.status === 401 || error.status === 403) {
throw errors.FORBIDDEN({
message:
`Kinde Management API denied this request (${error.status}). ` +
"Check M2M scopes (e.g. create:users) and credentials. " +
`Reason: ${reason}`,
});
}

if (error.status === 409) {
throw errors.BAD_REQUEST({
message:
"A user with this email already exists. Invite flow requires existing-user handling.",
});
}

if (error.status === 429) {
throw errors.RATE_LIMITED({
message: `Kinde rate limit hit. Reason: ${reason}`,
});
}

throw errors.INTERNAL_SERVER_ERROR({
message: `Kinde invite failed (${error.status}): ${reason}`,
});
}

const reason = getKindeErrorMessage(error);
throw errors.INTERNAL_SERVER_ERROR({
message: `Invite failed before Kinde call completed: ${reason}`,
});
}
});

Expand All @@ -68,6 +145,7 @@ export const listMembers = base
.output(z.array(z.custom<organization_user>()))
.handler(async ({ context, errors }) => {
try {
ensureKindeManagementEnv();
init();

const data = await Organizations.getOrganizationUsers({
Expand All @@ -80,7 +158,28 @@ export const listMembers = base
}

return data.organization_users;
} catch {
throw errors.INTERNAL_SERVER_ERROR();
} catch (error: unknown) {
if (error instanceof ApiError) {
const reason = getKindeErrorMessage(error);

if (error.status === 401 || error.status === 403) {
throw errors.FORBIDDEN({
message:
`Kinde Management API denied member listing (${error.status}). ` +
"Check M2M scopes (e.g. read:organization_users). " +
`Reason: ${reason}`,
});
}

if (error.status === 429) {
throw errors.RATE_LIMITED({
message: `Kinde rate limit hit. Reason: ${reason}`,
});
}
}

throw errors.INTERNAL_SERVER_ERROR({
message: `Failed to list members: ${getKindeErrorMessage(error)}`,
});
}
});
Loading