From 4640db5021de3f167e0077e2d16722cb5159c1c0 Mon Sep 17 00:00:00 2001
From: Vedant <48868398+vedantb2@users.noreply.github.com>
Date: Tue, 24 Mar 2026 21:17:57 +0000
Subject: [PATCH 01/22] feat(backend): move agent-login to Convex HTTP action
Removes dependency on TanStack Start server runtime. Agent auth now flows through
Convex site endpoint using @clerk/backend to create sign-in tokens.
---
apps/web-v2/src/env/client.ts | 1 +
apps/web-v2/src/routes/index.tsx | 10 ++--
packages/backend/convex/http.ts | 43 +++++++++++++++++
packages/backend/package.json | 1 +
pnpm-lock.yaml | 82 ++++++++++++++++++++++++++++++++
5 files changed, 133 insertions(+), 4 deletions(-)
diff --git a/apps/web-v2/src/env/client.ts b/apps/web-v2/src/env/client.ts
index 752aacfd3..1f56cfd68 100644
--- a/apps/web-v2/src/env/client.ts
+++ b/apps/web-v2/src/env/client.ts
@@ -6,6 +6,7 @@ export const clientEnv = createEnv({
client: {
VITE_CONVEX_URL: z.string().min(1),
VITE_CLERK_PUBLISHABLE_KEY: z.string().min(1),
+ VITE_CONVEX_SITE_URL: z.string().min(1),
VITE_ENV: z.enum(["development", "staging", "production"]),
},
runtimeEnv: import.meta.env,
diff --git a/apps/web-v2/src/routes/index.tsx b/apps/web-v2/src/routes/index.tsx
index b3d9c23db..86facaac9 100644
--- a/apps/web-v2/src/routes/index.tsx
+++ b/apps/web-v2/src/routes/index.tsx
@@ -1,4 +1,8 @@
-import { createFileRoute, useNavigate, useSearch } from "@tanstack/react-router";
+import {
+ createFileRoute,
+ useNavigate,
+ useSearch,
+} from "@tanstack/react-router";
import { useAuth, SignInButton, SignUpButton } from "@clerk/clerk-react";
import { Button } from "@conductor/ui";
import { clientEnv } from "@/env/client";
@@ -27,9 +31,7 @@ function LandingPage() {
}
if (agent) {
- console.log(
- "[agent-auth] ?agent detected but HTTP action not built yet — skipping redirect",
- );
+ window.location.href = `${clientEnv.VITE_CONVEX_SITE_URL}/api/auth/agent-login`;
}
}, [isLoaded, isSignedIn, agent, navigate]);
diff --git a/packages/backend/convex/http.ts b/packages/backend/convex/http.ts
index 0a103c4cc..e61ca5b6f 100644
--- a/packages/backend/convex/http.ts
+++ b/packages/backend/convex/http.ts
@@ -1,4 +1,5 @@
import { httpRouter } from "convex/server";
+import { createClerkClient } from "@clerk/backend";
import { httpAction } from "./_generated/server";
import { internal } from "./_generated/api";
import { SANDBOX_JWT_ISSUER } from "./sandboxAuthConfig";
@@ -287,4 +288,46 @@ http.route({
}),
});
+http.route({
+ path: "/api/auth/agent-login",
+ method: "GET",
+ handler: httpAction(async (_ctx, request) => {
+ const clerkSecretKey = process.env.CLERK_SECRET_KEY;
+ const agentUserId = process.env.AGENT_CLERK_USER_ID;
+
+ if (!clerkSecretKey || !agentUserId) {
+ return Response.json(
+ {
+ error: "CLERK_SECRET_KEY and AGENT_CLERK_USER_ID must be configured",
+ },
+ { status: 500 },
+ );
+ }
+
+ const origin =
+ request.headers.get("Origin") ||
+ request.headers.get("Referer")?.replace(/\/$/, "") ||
+ null;
+
+ if (!origin) {
+ return Response.json(
+ { error: "Could not determine callback origin" },
+ { status: 400 },
+ );
+ }
+
+ const clerk = createClerkClient({ secretKey: clerkSecretKey });
+
+ const { token } = await clerk.signInTokens.createSignInToken({
+ userId: agentUserId,
+ expiresInSeconds: 60,
+ });
+
+ const callbackUrl = new URL("/agent-callback", origin);
+ callbackUrl.searchParams.set("ticket", token);
+
+ return Response.redirect(callbackUrl.toString(), 302);
+ }),
+});
+
export default http;
diff --git a/packages/backend/package.json b/packages/backend/package.json
index 5f702d433..016b51b0e 100644
--- a/packages/backend/package.json
+++ b/packages/backend/package.json
@@ -13,6 +13,7 @@
"license": "ISC",
"description": "",
"dependencies": {
+ "@clerk/backend": "^1.25.0",
"@convex-dev/crons": "^0.2.0",
"@convex-dev/presence": "^0.3.0",
"@convex-dev/prosemirror-sync": "^0.2.1",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 68c9b8580..267c8161f 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -1093,6 +1093,9 @@ importers:
packages/backend:
dependencies:
+ '@clerk/backend':
+ specifier: ^1.25.0
+ version: 1.34.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1)
'@convex-dev/crons':
specifier: ^0.2.0
version: 0.2.0(convex@1.31.7(@clerk/clerk-react@5.60.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(react@19.2.1))
@@ -2250,6 +2253,15 @@ packages:
'@chevrotain/utils@11.0.3':
resolution: {integrity: sha512-YslZMgtJUyuMbZ+aKvfF3x1f5liK4mWNxghFRv7jqRR9C3R3fAOGTTKvxXDa2Y1s9zSbcpuO0cAxDYsc9SrXoQ==}
+ '@clerk/backend@1.34.0':
+ resolution: {integrity: sha512-9rZ8hQJVpX5KX2bEpiuVXfpjhojQCiqCWADJDdCI0PCeKxn58Ep0JPYiIcczg4VKUc3a7jve9vXylykG2XajLQ==}
+ engines: {node: '>=18.17.0'}
+ peerDependencies:
+ svix: ^1.62.0
+ peerDependenciesMeta:
+ svix:
+ optional: true
+
'@clerk/backend@2.30.1':
resolution: {integrity: sha512-GoxnJzVH0ycNPAGCDMfo3lPBFbo5nehpLSVFjgGEnzIRGGahBtAB8PQT7KM2zo58pD8apjb/+suhcB/WCiEasQ==}
engines: {node: '>=18.17.0'}
@@ -7079,6 +7091,10 @@ packages:
resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==}
engines: {node: '>= 0.6'}
+ cookie@1.0.2:
+ resolution: {integrity: sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==}
+ engines: {node: '>=18'}
+
cookie@1.1.1:
resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==}
engines: {node: '>=18'}
@@ -7549,6 +7565,9 @@ packages:
domutils@3.2.2:
resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==}
+ dot-case@3.0.4:
+ resolution: {integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==}
+
dotenv-expand@11.0.7:
resolution: {integrity: sha512-zIHwmZPRshsCdpMDyVsqGmgyP0yT8GAgXUnkdAoJisxvf33k7yO6OuoKmcTGuXPWSsm8Oh88nZicRLA9Y0rUeA==}
engines: {node: '>=12'}
@@ -9588,6 +9607,9 @@ packages:
resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
hasBin: true
+ lower-case@2.0.2:
+ resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==}
+
lowercase-keys@2.0.0:
resolution: {integrity: sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==}
engines: {node: '>=8'}
@@ -9646,6 +9668,10 @@ packages:
makeerror@1.0.12:
resolution: {integrity: sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==}
+ map-obj@4.3.0:
+ resolution: {integrity: sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==}
+ engines: {node: '>=8'}
+
markdown-it@14.1.0:
resolution: {integrity: sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==}
hasBin: true
@@ -10185,6 +10211,9 @@ packages:
nice-try@1.0.5:
resolution: {integrity: sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==}
+ no-case@3.0.4:
+ resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==}
+
node-abi@3.87.0:
resolution: {integrity: sha512-+CGM1L1CgmtheLcBuleyYOn7NWPVu0s0EJH2C4puxgEZb9h8QpR9G2dBfZJOAUhi7VQxuBPMd0hiISWcTyiYyQ==}
engines: {node: '>=10'}
@@ -11646,6 +11675,13 @@ packages:
resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==}
engines: {node: '>= 6.0.0', npm: '>= 3.0.0'}
+ snake-case@3.0.4:
+ resolution: {integrity: sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==}
+
+ snakecase-keys@8.0.1:
+ resolution: {integrity: sha512-Sj51kE1zC7zh6TDlNNz0/Jn1n5HiHdoQErxO8jLtnyrkJW/M5PrI7x05uDgY3BO7OUQYKCvmeMurW6BPUdwEOw==}
+ engines: {node: '>=18'}
+
socks-proxy-agent@7.0.0:
resolution: {integrity: sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww==}
engines: {node: '>= 10'}
@@ -12118,6 +12154,10 @@ packages:
resolution: {integrity: sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==}
engines: {node: '>=8'}
+ type-fest@4.41.0:
+ resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==}
+ engines: {node: '>=16'}
+
type-is@2.0.1:
resolution: {integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==}
engines: {node: '>= 0.6'}
@@ -14251,6 +14291,17 @@ snapshots:
'@chevrotain/utils@11.0.3': {}
+ '@clerk/backend@1.34.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1)':
+ dependencies:
+ '@clerk/shared': 3.44.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1)
+ '@clerk/types': 4.101.14(react-dom@19.2.1(react@19.2.1))(react@19.2.1)
+ cookie: 1.0.2
+ snakecase-keys: 8.0.1
+ tslib: 2.8.1
+ transitivePeerDependencies:
+ - react
+ - react-dom
+
'@clerk/backend@2.30.1(react-dom@18.3.1(react@19.2.1))(react@19.2.1)':
dependencies:
'@clerk/shared': 3.44.0(react-dom@18.3.1(react@19.2.1))(react@19.2.1)
@@ -20127,6 +20178,8 @@ snapshots:
cookie@0.7.2: {}
+ cookie@1.0.2: {}
+
cookie@1.1.1: {}
copy-to-clipboard@3.3.3:
@@ -20636,6 +20689,11 @@ snapshots:
domelementtype: 2.3.0
domhandler: 5.0.3
+ dot-case@3.0.4:
+ dependencies:
+ no-case: 3.0.4
+ tslib: 2.8.1
+
dotenv-expand@11.0.7:
dependencies:
dotenv: 16.4.7
@@ -23090,6 +23148,10 @@ snapshots:
dependencies:
js-tokens: 4.0.0
+ lower-case@2.0.2:
+ dependencies:
+ tslib: 2.8.1
+
lowercase-keys@2.0.0: {}
lowlight@1.20.0:
@@ -23175,6 +23237,8 @@ snapshots:
dependencies:
tmpl: 1.0.5
+ map-obj@4.3.0: {}
+
markdown-it@14.1.0:
dependencies:
argparse: 2.0.1
@@ -24039,6 +24103,11 @@ snapshots:
nice-try@1.0.5: {}
+ no-case@3.0.4:
+ dependencies:
+ lower-case: 2.0.2
+ tslib: 2.8.1
+
node-abi@3.87.0:
dependencies:
semver: 7.7.4
@@ -25904,6 +25973,17 @@ snapshots:
smart-buffer@4.2.0: {}
+ snake-case@3.0.4:
+ dependencies:
+ dot-case: 3.0.4
+ tslib: 2.8.1
+
+ snakecase-keys@8.0.1:
+ dependencies:
+ map-obj: 4.3.0
+ snake-case: 3.0.4
+ type-fest: 4.41.0
+
socks-proxy-agent@7.0.0:
dependencies:
agent-base: 6.0.2
@@ -26428,6 +26508,8 @@ snapshots:
type-fest@0.7.1: {}
+ type-fest@4.41.0: {}
+
type-is@2.0.1:
dependencies:
content-type: 1.0.5
From dca9aa5ecddfae9677d93376f41ea54a2534fedd Mon Sep 17 00:00:00 2001
From: Vedant <48868398+vedantb2@users.noreply.github.com>
Date: Tue, 24 Mar 2026 21:38:49 +0000
Subject: [PATCH 02/22] fix(web-v2): handle boolean search param parsing for
agent login
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
TanStack Router's JSON.parse-based parser converts ?agent=true to boolean,
not string — validateSearch now checks for both.
---
apps/web-v2/src/routes/index.tsx | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/apps/web-v2/src/routes/index.tsx b/apps/web-v2/src/routes/index.tsx
index 86facaac9..e537e731c 100644
--- a/apps/web-v2/src/routes/index.tsx
+++ b/apps/web-v2/src/routes/index.tsx
@@ -11,8 +11,11 @@ import { useEffect } from "react";
const isProduction = clientEnv.VITE_ENV === "production";
export const Route = createFileRoute("/")({
- validateSearch: (search: Record) => ({
- agent: search.agent === "" || search.agent === "true" ? true : undefined,
+ validateSearch: (search: Record) => ({
+ agent:
+ search.agent === "" || search.agent === "true" || search.agent === true
+ ? true
+ : undefined,
}),
component: LandingPage,
});
From 4fda2fb9580c3666389113a10239d2f31b4ef921 Mon Sep 17 00:00:00 2001
From: Vedant <48868398+vedantb2@users.noreply.github.com>
Date: Tue, 24 Mar 2026 23:00:43 +0000
Subject: [PATCH 03/22] refactor(web-v2): move agent-login to vite dev
middleware
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Removes Convex HTTP endpoint (security hole in open-source codebase).
Agent auth now handled server-side in Vite dev middleware only — zero
attack surface in production. Secrets stay in .env.local, never bundled.
---
apps/web-v2/package.json | 1 +
apps/web-v2/src/env/client.ts | 1 -
apps/web-v2/src/routes/index.tsx | 13 ++---
apps/web-v2/vite.config.ts | 66 +++++++++++++++++++++-
packages/backend/convex/http.ts | 43 --------------
packages/backend/package.json | 1 -
pnpm-lock.yaml | 97 +++++---------------------------
7 files changed, 87 insertions(+), 135 deletions(-)
diff --git a/apps/web-v2/package.json b/apps/web-v2/package.json
index 0bebb392b..95c85e492 100644
--- a/apps/web-v2/package.json
+++ b/apps/web-v2/package.json
@@ -47,6 +47,7 @@
"@tabler/icons-react": "^3.33.0",
"@tanstack/react-hotkeys": "^0.3.2",
"@tanstack/react-router": "^1.120.0",
+ "@tanstack/router-zod-adapter": "^1.81.5",
"@tiptap/core": "^3.20.1",
"@tiptap/extension-underline": "^3.20.1",
"@tiptap/pm": "^3.20.1",
diff --git a/apps/web-v2/src/env/client.ts b/apps/web-v2/src/env/client.ts
index 1f56cfd68..752aacfd3 100644
--- a/apps/web-v2/src/env/client.ts
+++ b/apps/web-v2/src/env/client.ts
@@ -6,7 +6,6 @@ export const clientEnv = createEnv({
client: {
VITE_CONVEX_URL: z.string().min(1),
VITE_CLERK_PUBLISHABLE_KEY: z.string().min(1),
- VITE_CONVEX_SITE_URL: z.string().min(1),
VITE_ENV: z.enum(["development", "staging", "production"]),
},
runtimeEnv: import.meta.env,
diff --git a/apps/web-v2/src/routes/index.tsx b/apps/web-v2/src/routes/index.tsx
index e537e731c..0ff93f388 100644
--- a/apps/web-v2/src/routes/index.tsx
+++ b/apps/web-v2/src/routes/index.tsx
@@ -3,20 +3,19 @@ import {
useNavigate,
useSearch,
} from "@tanstack/react-router";
+import { zodSearchValidator } from "@tanstack/router-zod-adapter";
import { useAuth, SignInButton, SignUpButton } from "@clerk/clerk-react";
import { Button } from "@conductor/ui";
import { clientEnv } from "@/env/client";
import { useEffect } from "react";
+import { z } from "zod";
const isProduction = clientEnv.VITE_ENV === "production";
export const Route = createFileRoute("/")({
- validateSearch: (search: Record) => ({
- agent:
- search.agent === "" || search.agent === "true" || search.agent === true
- ? true
- : undefined,
- }),
+ validateSearch: zodSearchValidator(
+ z.object({ agent: z.boolean().optional() }),
+ ),
component: LandingPage,
});
@@ -34,7 +33,7 @@ function LandingPage() {
}
if (agent) {
- window.location.href = `${clientEnv.VITE_CONVEX_SITE_URL}/api/auth/agent-login`;
+ window.location.href = "/api/auth/agent-login";
}
}, [isLoaded, isSignedIn, agent, navigate]);
diff --git a/apps/web-v2/vite.config.ts b/apps/web-v2/vite.config.ts
index 2e8d81e8c..99c397c54 100644
--- a/apps/web-v2/vite.config.ts
+++ b/apps/web-v2/vite.config.ts
@@ -1,8 +1,71 @@
-import { defineConfig } from "vite";
+import { defineConfig, loadEnv, type Plugin } from "vite";
import react from "@vitejs/plugin-react";
import { TanStackRouterVite } from "@tanstack/router-plugin/vite";
import path from "path";
+function agentLoginPlugin(): Plugin {
+ let env: Record;
+
+ return {
+ name: "agent-login",
+ configureServer(server) {
+ env = loadEnv("development", server.config.root, "");
+
+ server.middlewares.use("/api/auth/agent-login", async (_req, res) => {
+ const secretKey = env.CLERK_SECRET_KEY;
+ const agentUserId = env.AGENT_CLERK_USER_ID;
+
+ if (!secretKey || !agentUserId) {
+ res.writeHead(500, { "Content-Type": "application/json" });
+ res.end(
+ JSON.stringify({
+ error:
+ "CLERK_SECRET_KEY and AGENT_CLERK_USER_ID must be set in .env.local",
+ }),
+ );
+ return;
+ }
+
+ const resp = await fetch("https://api.clerk.com/v1/sign_in_tokens", {
+ method: "POST",
+ headers: {
+ Authorization: `Bearer ${secretKey}`,
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify({
+ user_id: agentUserId,
+ expires_in_seconds: 60,
+ }),
+ });
+
+ if (!resp.ok) {
+ res.writeHead(502, { "Content-Type": "application/json" });
+ res.end(
+ JSON.stringify({
+ error: "Failed to create sign-in token",
+ details: await resp.text(),
+ }),
+ );
+ return;
+ }
+
+ const data = await resp.json();
+ const token = data.token;
+ if (typeof token !== "string") {
+ res.writeHead(502, { "Content-Type": "application/json" });
+ res.end(JSON.stringify({ error: "No token in Clerk response" }));
+ return;
+ }
+
+ res.writeHead(302, {
+ Location: `/agent-callback?ticket=${encodeURIComponent(token)}`,
+ });
+ res.end();
+ });
+ },
+ };
+}
+
export default defineConfig({
plugins: [
TanStackRouterVite({
@@ -10,6 +73,7 @@ export default defineConfig({
routeFileIgnorePattern: "(_components/|_utils\\.ts|Client\\.tsx)",
}),
react(),
+ agentLoginPlugin(),
],
resolve: {
alias: {
diff --git a/packages/backend/convex/http.ts b/packages/backend/convex/http.ts
index e61ca5b6f..0a103c4cc 100644
--- a/packages/backend/convex/http.ts
+++ b/packages/backend/convex/http.ts
@@ -1,5 +1,4 @@
import { httpRouter } from "convex/server";
-import { createClerkClient } from "@clerk/backend";
import { httpAction } from "./_generated/server";
import { internal } from "./_generated/api";
import { SANDBOX_JWT_ISSUER } from "./sandboxAuthConfig";
@@ -288,46 +287,4 @@ http.route({
}),
});
-http.route({
- path: "/api/auth/agent-login",
- method: "GET",
- handler: httpAction(async (_ctx, request) => {
- const clerkSecretKey = process.env.CLERK_SECRET_KEY;
- const agentUserId = process.env.AGENT_CLERK_USER_ID;
-
- if (!clerkSecretKey || !agentUserId) {
- return Response.json(
- {
- error: "CLERK_SECRET_KEY and AGENT_CLERK_USER_ID must be configured",
- },
- { status: 500 },
- );
- }
-
- const origin =
- request.headers.get("Origin") ||
- request.headers.get("Referer")?.replace(/\/$/, "") ||
- null;
-
- if (!origin) {
- return Response.json(
- { error: "Could not determine callback origin" },
- { status: 400 },
- );
- }
-
- const clerk = createClerkClient({ secretKey: clerkSecretKey });
-
- const { token } = await clerk.signInTokens.createSignInToken({
- userId: agentUserId,
- expiresInSeconds: 60,
- });
-
- const callbackUrl = new URL("/agent-callback", origin);
- callbackUrl.searchParams.set("ticket", token);
-
- return Response.redirect(callbackUrl.toString(), 302);
- }),
-});
-
export default http;
diff --git a/packages/backend/package.json b/packages/backend/package.json
index 016b51b0e..5f702d433 100644
--- a/packages/backend/package.json
+++ b/packages/backend/package.json
@@ -13,7 +13,6 @@
"license": "ISC",
"description": "",
"dependencies": {
- "@clerk/backend": "^1.25.0",
"@convex-dev/crons": "^0.2.0",
"@convex-dev/presence": "^0.3.0",
"@convex-dev/prosemirror-sync": "^0.2.1",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 267c8161f..cd329c41d 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -921,6 +921,9 @@ importers:
'@tanstack/react-router':
specifier: ^1.120.0
version: 1.168.2(react-dom@19.2.1(react@19.2.1))(react@19.2.1)
+ '@tanstack/router-zod-adapter':
+ specifier: ^1.81.5
+ version: 1.81.5(@tanstack/react-router@1.168.2(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(zod@4.3.6)
'@tiptap/core':
specifier: ^3.20.1
version: 3.20.1(@tiptap/pm@3.20.1)
@@ -1093,9 +1096,6 @@ importers:
packages/backend:
dependencies:
- '@clerk/backend':
- specifier: ^1.25.0
- version: 1.34.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1)
'@convex-dev/crons':
specifier: ^0.2.0
version: 0.2.0(convex@1.31.7(@clerk/clerk-react@5.60.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(react@19.2.1))
@@ -2253,15 +2253,6 @@ packages:
'@chevrotain/utils@11.0.3':
resolution: {integrity: sha512-YslZMgtJUyuMbZ+aKvfF3x1f5liK4mWNxghFRv7jqRR9C3R3fAOGTTKvxXDa2Y1s9zSbcpuO0cAxDYsc9SrXoQ==}
- '@clerk/backend@1.34.0':
- resolution: {integrity: sha512-9rZ8hQJVpX5KX2bEpiuVXfpjhojQCiqCWADJDdCI0PCeKxn58Ep0JPYiIcczg4VKUc3a7jve9vXylykG2XajLQ==}
- engines: {node: '>=18.17.0'}
- peerDependencies:
- svix: ^1.62.0
- peerDependenciesMeta:
- svix:
- optional: true
-
'@clerk/backend@2.30.1':
resolution: {integrity: sha512-GoxnJzVH0ycNPAGCDMfo3lPBFbo5nehpLSVFjgGEnzIRGGahBtAB8PQT7KM2zo58pD8apjb/+suhcB/WCiEasQ==}
engines: {node: '>=18.17.0'}
@@ -5299,6 +5290,13 @@ packages:
resolution: {integrity: sha512-nRcYw+w2OEgK6VfjirYvGyPLOK+tZQz1jkYcmH5AjMamQ9PycnlxZF2aEZtPpNoUsaceX2bHptn6Ub5hGXqNvw==}
engines: {node: '>=20.19'}
+ '@tanstack/router-zod-adapter@1.81.5':
+ resolution: {integrity: sha512-oJp3QaCI5YwW7H46iuivC8pJLmYboXa1OztncRZNmfVBX69FZ7DodfxdrwNzceGpN3sXZT/f0t4sV05dKsneHg==}
+ engines: {node: '>=12'}
+ peerDependencies:
+ '@tanstack/react-router': '>=1.43.2'
+ zod: '>=3'
+
'@tanstack/store@0.9.1':
resolution: {integrity: sha512-+qcNkOy0N1qSGsP7omVCW0SDrXtaDcycPqBDE726yryiA5eTDFpjBReaYjghVJwNf1pcPMyzIwTGlYjCSQR0Fg==}
@@ -7091,10 +7089,6 @@ packages:
resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==}
engines: {node: '>= 0.6'}
- cookie@1.0.2:
- resolution: {integrity: sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==}
- engines: {node: '>=18'}
-
cookie@1.1.1:
resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==}
engines: {node: '>=18'}
@@ -7565,9 +7559,6 @@ packages:
domutils@3.2.2:
resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==}
- dot-case@3.0.4:
- resolution: {integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==}
-
dotenv-expand@11.0.7:
resolution: {integrity: sha512-zIHwmZPRshsCdpMDyVsqGmgyP0yT8GAgXUnkdAoJisxvf33k7yO6OuoKmcTGuXPWSsm8Oh88nZicRLA9Y0rUeA==}
engines: {node: '>=12'}
@@ -9607,9 +9598,6 @@ packages:
resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
hasBin: true
- lower-case@2.0.2:
- resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==}
-
lowercase-keys@2.0.0:
resolution: {integrity: sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==}
engines: {node: '>=8'}
@@ -9668,10 +9656,6 @@ packages:
makeerror@1.0.12:
resolution: {integrity: sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==}
- map-obj@4.3.0:
- resolution: {integrity: sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==}
- engines: {node: '>=8'}
-
markdown-it@14.1.0:
resolution: {integrity: sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==}
hasBin: true
@@ -10211,9 +10195,6 @@ packages:
nice-try@1.0.5:
resolution: {integrity: sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==}
- no-case@3.0.4:
- resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==}
-
node-abi@3.87.0:
resolution: {integrity: sha512-+CGM1L1CgmtheLcBuleyYOn7NWPVu0s0EJH2C4puxgEZb9h8QpR9G2dBfZJOAUhi7VQxuBPMd0hiISWcTyiYyQ==}
engines: {node: '>=10'}
@@ -11675,13 +11656,6 @@ packages:
resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==}
engines: {node: '>= 6.0.0', npm: '>= 3.0.0'}
- snake-case@3.0.4:
- resolution: {integrity: sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==}
-
- snakecase-keys@8.0.1:
- resolution: {integrity: sha512-Sj51kE1zC7zh6TDlNNz0/Jn1n5HiHdoQErxO8jLtnyrkJW/M5PrI7x05uDgY3BO7OUQYKCvmeMurW6BPUdwEOw==}
- engines: {node: '>=18'}
-
socks-proxy-agent@7.0.0:
resolution: {integrity: sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww==}
engines: {node: '>= 10'}
@@ -12154,10 +12128,6 @@ packages:
resolution: {integrity: sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==}
engines: {node: '>=8'}
- type-fest@4.41.0:
- resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==}
- engines: {node: '>=16'}
-
type-is@2.0.1:
resolution: {integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==}
engines: {node: '>= 0.6'}
@@ -14291,17 +14261,6 @@ snapshots:
'@chevrotain/utils@11.0.3': {}
- '@clerk/backend@1.34.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1)':
- dependencies:
- '@clerk/shared': 3.44.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1)
- '@clerk/types': 4.101.14(react-dom@19.2.1(react@19.2.1))(react@19.2.1)
- cookie: 1.0.2
- snakecase-keys: 8.0.1
- tslib: 2.8.1
- transitivePeerDependencies:
- - react
- - react-dom
-
'@clerk/backend@2.30.1(react-dom@18.3.1(react@19.2.1))(react@19.2.1)':
dependencies:
'@clerk/shared': 3.44.0(react-dom@18.3.1(react@19.2.1))(react@19.2.1)
@@ -18130,6 +18089,11 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ '@tanstack/router-zod-adapter@1.81.5(@tanstack/react-router@1.168.2(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(zod@4.3.6)':
+ dependencies:
+ '@tanstack/react-router': 1.168.2(react-dom@19.2.1(react@19.2.1))(react@19.2.1)
+ zod: 4.3.6
+
'@tanstack/store@0.9.1': {}
'@tanstack/store@0.9.2': {}
@@ -20178,8 +20142,6 @@ snapshots:
cookie@0.7.2: {}
- cookie@1.0.2: {}
-
cookie@1.1.1: {}
copy-to-clipboard@3.3.3:
@@ -20689,11 +20651,6 @@ snapshots:
domelementtype: 2.3.0
domhandler: 5.0.3
- dot-case@3.0.4:
- dependencies:
- no-case: 3.0.4
- tslib: 2.8.1
-
dotenv-expand@11.0.7:
dependencies:
dotenv: 16.4.7
@@ -23148,10 +23105,6 @@ snapshots:
dependencies:
js-tokens: 4.0.0
- lower-case@2.0.2:
- dependencies:
- tslib: 2.8.1
-
lowercase-keys@2.0.0: {}
lowlight@1.20.0:
@@ -23237,8 +23190,6 @@ snapshots:
dependencies:
tmpl: 1.0.5
- map-obj@4.3.0: {}
-
markdown-it@14.1.0:
dependencies:
argparse: 2.0.1
@@ -24103,11 +24054,6 @@ snapshots:
nice-try@1.0.5: {}
- no-case@3.0.4:
- dependencies:
- lower-case: 2.0.2
- tslib: 2.8.1
-
node-abi@3.87.0:
dependencies:
semver: 7.7.4
@@ -25973,17 +25919,6 @@ snapshots:
smart-buffer@4.2.0: {}
- snake-case@3.0.4:
- dependencies:
- dot-case: 3.0.4
- tslib: 2.8.1
-
- snakecase-keys@8.0.1:
- dependencies:
- map-obj: 4.3.0
- snake-case: 3.0.4
- type-fest: 4.41.0
-
socks-proxy-agent@7.0.0:
dependencies:
agent-base: 6.0.2
@@ -26508,8 +26443,6 @@ snapshots:
type-fest@0.7.1: {}
- type-fest@4.41.0: {}
-
type-is@2.0.1:
dependencies:
content-type: 1.0.5
From 03895cc02d288fb4e1a977975056211d04e9ce3d Mon Sep 17 00:00:00 2001
From: Vedant <48868398+vedantb2@users.noreply.github.com>
Date: Wed, 25 Mar 2026 09:44:28 +0000
Subject: [PATCH 04/22] remove hard coded
---
packages/backend/convex/sandboxAuthConfig.ts | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/packages/backend/convex/sandboxAuthConfig.ts b/packages/backend/convex/sandboxAuthConfig.ts
index b06e60114..fe863d4cf 100644
--- a/packages/backend/convex/sandboxAuthConfig.ts
+++ b/packages/backend/convex/sandboxAuthConfig.ts
@@ -3,5 +3,9 @@ const SANDBOX_JWT_FALLBACK_ISSUER = "https://elegant-snail-639.convex.site";
export const SANDBOX_JWT_ISSUER =
process.env.CONVEX_SITE_URL ?? SANDBOX_JWT_FALLBACK_ISSUER;
-export const SANDBOX_JWT_JWKS_DATA_URI =
- "data:application/json;base64,eyJrZXlzIjpbeyJrdHkiOiJFQyIsIngiOiIzTmxVWkF3ZXA4OFFVTXJGbFh1MEk1ZEktUWVyWjhTZ21WOG54SkFGWnFnIiwieSI6IkpPNnRpLU13VXMydWpNaGcwRlJGaDNqOF9HU0lvNU5VX28ySWJnVG1QU0UiLCJjcnYiOiJQLTI1NiIsImtpZCI6InNhbmRib3gtMSIsImFsZyI6IkVTMjU2IiwidXNlIjoic2lnIn1dfQ==";
+const jwksJson = process.env.SANDBOX_JWT_JWKS;
+if (!jwksJson) {
+ throw new Error("Missing SANDBOX_JWT_JWKS env var");
+}
+const jwksBase64 = btoa(jwksJson);
+export const SANDBOX_JWT_JWKS_DATA_URI = `data:application/json;base64,${jwksBase64}`;
From 55053c40772ef129885be0086225df6be9cc16f8 Mon Sep 17 00:00:00 2001
From: Vedant <48868398+vedantb2@users.noreply.github.com>
Date: Wed, 25 Mar 2026 10:23:32 +0000
Subject: [PATCH 05/22] fix(web): enable repo-level audit management from app
context
Allow toggling and deleting repo-level audits directly from app settings, instead of forcing users to navigate to the parent repo. Also enabled adding new repo-level audits from app context.
---
.../$owner/$repo/settings/AuditsClient.tsx | 44 +++++--------------
.../[repo]/settings/audits/AuditsClient.tsx | 44 +++++--------------
2 files changed, 24 insertions(+), 64 deletions(-)
diff --git a/apps/web-v2/src/routes/_repo/$owner/$repo/settings/AuditsClient.tsx b/apps/web-v2/src/routes/_repo/$owner/$repo/settings/AuditsClient.tsx
index a31b84dee..68d63325b 100644
--- a/apps/web-v2/src/routes/_repo/$owner/$repo/settings/AuditsClient.tsx
+++ b/apps/web-v2/src/routes/_repo/$owner/$repo/settings/AuditsClient.tsx
@@ -34,28 +34,22 @@ export function AuditsClient() {
Repo-level Audits
- {isApp
- ? "These audits are managed at the repo level and apply to all apps."
- : "These audits run for all tasks across the repo and all apps."}
+ These audits run for all tasks across the repo and all apps.
{repoCategories.length > 0 && (
- {repoCategories.map((category) =>
- isApp ? (
-
- ) : (
-
- toggleEnabled({ id: category._id, enabled })
- }
- onRemove={() => removeCategory({ id: category._id })}
- />
- ),
- )}
+ {repoCategories.map((category) => (
+
+ toggleEnabled({ id: category._id, enabled })
+ }
+ onRemove={() => removeCategory({ id: category._id })}
+ />
+ ))}
)}
@@ -65,7 +59,7 @@ export function AuditsClient() {
)}
- {!isApp && }
+
{isApp && (
@@ -100,20 +94,6 @@ export function AuditsClient() {
);
}
-function ReadOnlyCategoryRow({ category }: { category: Category }) {
- return (
-
-
-
-
{category.name}
-
- {category.description}
-
-
-
- );
-}
-
function CategoryRow({
category,
onToggle,
diff --git a/apps/web/app/(repo)/[owner]/[repo]/settings/audits/AuditsClient.tsx b/apps/web/app/(repo)/[owner]/[repo]/settings/audits/AuditsClient.tsx
index 3c5743a3f..34b6966e4 100644
--- a/apps/web/app/(repo)/[owner]/[repo]/settings/audits/AuditsClient.tsx
+++ b/apps/web/app/(repo)/[owner]/[repo]/settings/audits/AuditsClient.tsx
@@ -34,28 +34,22 @@ export function AuditsClient() {
Repo-level Audits
- {isApp
- ? "These audits are managed at the repo level and apply to all apps."
- : "These audits run for all tasks across the repo and all apps."}
+ These audits run for all tasks across the repo and all apps.
{repoCategories.length > 0 && (
- {repoCategories.map((category) =>
- isApp ? (
-
- ) : (
-
- toggleEnabled({ id: category._id, enabled })
- }
- onRemove={() => removeCategory({ id: category._id })}
- />
- ),
- )}
+ {repoCategories.map((category) => (
+
+ toggleEnabled({ id: category._id, enabled })
+ }
+ onRemove={() => removeCategory({ id: category._id })}
+ />
+ ))}
)}
@@ -65,7 +59,7 @@ export function AuditsClient() {
)}
- {!isApp && }
+
{isApp && (
@@ -100,20 +94,6 @@ export function AuditsClient() {
);
}
-function ReadOnlyCategoryRow({ category }: { category: Category }) {
- return (
-
-
-
-
{category.name}
-
- {category.description}
-
-
-
- );
-}
-
function CategoryRow({
category,
onToggle,
From c91905c9f96a565ff3f644433b72219193d97f1b Mon Sep 17 00:00:00 2001
From: Vedant <48868398+vedantb2@users.noreply.github.com>
Date: Wed, 25 Mar 2026 13:43:32 +0000
Subject: [PATCH 06/22] refactor(chrome-extension): move hardcoded URLs to env
vars
Consolidate EVA_URL configuration across dev/staging/production environments. Remove duplicate SYNC_HOST constant and use env vars (VITE_EVA_URL) for all URL references. Add build:staging script for staging builds.
---
apps/chrome-extension/.gitignore | 3 ++-
apps/chrome-extension/package.json | 1 +
apps/chrome-extension/src/shared/messaging.ts | 6 +-----
apps/chrome-extension/src/sidepanel/App.tsx | 7 ++-----
4 files changed, 6 insertions(+), 11 deletions(-)
diff --git a/apps/chrome-extension/.gitignore b/apps/chrome-extension/.gitignore
index b0e4c33b1..90d11dfbc 100644
--- a/apps/chrome-extension/.gitignore
+++ b/apps/chrome-extension/.gitignore
@@ -3,4 +3,5 @@ dist/
.env.chrome
.env.development
.env
-.env.production
\ No newline at end of file
+.env.production
+.env.staging
\ No newline at end of file
diff --git a/apps/chrome-extension/package.json b/apps/chrome-extension/package.json
index 927e6a697..dc9725a26 100644
--- a/apps/chrome-extension/package.json
+++ b/apps/chrome-extension/package.json
@@ -6,6 +6,7 @@
"scripts": {
"dev": "vite build --watch --mode development",
"build": "vite build",
+ "build:staging": "vite build --mode staging",
"preview": "vite preview"
},
"dependencies": {
diff --git a/apps/chrome-extension/src/shared/messaging.ts b/apps/chrome-extension/src/shared/messaging.ts
index 086054511..fba508bf3 100644
--- a/apps/chrome-extension/src/shared/messaging.ts
+++ b/apps/chrome-extension/src/shared/messaging.ts
@@ -224,11 +224,7 @@ export type ExtensionMessage =
| RequestAnnotationsMessage
| RequestToolbarStateMessage;
-export const EVA_URL =
- typeof chrome !== "undefined" &&
- chrome.runtime?.getManifest?.()?.version_name === "development"
- ? "http://localhost:3000"
- : "https://eva-git-staging-vedantb.vercel.app";
+export const EVA_URL = import.meta.env.VITE_EVA_URL;
export function isSessionId(value: unknown): value is Id<"sessions"> {
return typeof value === "string" && value.length > 0;
diff --git a/apps/chrome-extension/src/sidepanel/App.tsx b/apps/chrome-extension/src/sidepanel/App.tsx
index c34aaee3d..3216647d6 100644
--- a/apps/chrome-extension/src/sidepanel/App.tsx
+++ b/apps/chrome-extension/src/sidepanel/App.tsx
@@ -56,9 +56,6 @@ if (!PUBLISHABLE_KEY) {
}
const EXTENSION_URL = chrome.runtime.getURL(".");
-const SYNC_HOST = import.meta.env.DEV
- ? "http://localhost:3000"
- : "https://eva-git-staging-vedantb.vercel.app";
function getHostFromUrl(url: string): string | null {
try {
@@ -847,7 +844,7 @@ function AuthenticatedApp() {
function SignInScreen() {
const handleOpenWebApp = () => {
- chrome.tabs.create({ url: SYNC_HOST });
+ chrome.tabs.create({ url: EVA_URL });
};
return (
@@ -876,7 +873,7 @@ export default function App() {
afterSignOutUrl={`${EXTENSION_URL}/sidepanel.html`}
signInFallbackRedirectUrl={`${EXTENSION_URL}/sidepanel.html`}
signUpFallbackRedirectUrl={`${EXTENSION_URL}/sidepanel.html`}
- syncHost={SYNC_HOST}
+ syncHost={EVA_URL}
>
From 86d6ae5dc5c5b8acbd0ab9e2a7f7e16fea520945 Mon Sep 17 00:00:00 2001
From: Eva <48868398+vedantb2@users.noreply.github.com>
Date: Wed, 25 Mar 2026 15:05:51 +0000
Subject: [PATCH 07/22] feat: use compact relative time on quick task cards
Change date display from "x days ago" to compact format (3d, 1h, 5m)
on QuickTaskCard in both web-v2 and web apps.
---
.../components/quick-tasks/QuickTaskCard.tsx | 4 ++--
.../components/quick-tasks/QuickTaskCard.tsx | 4 ++--
packages/shared/src/utils/dates.ts | 18 ++++++++++++++++++
3 files changed, 22 insertions(+), 4 deletions(-)
diff --git a/apps/web-v2/src/lib/components/quick-tasks/QuickTaskCard.tsx b/apps/web-v2/src/lib/components/quick-tasks/QuickTaskCard.tsx
index 02540868f..89ad0eee3 100644
--- a/apps/web-v2/src/lib/components/quick-tasks/QuickTaskCard.tsx
+++ b/apps/web-v2/src/lib/components/quick-tasks/QuickTaskCard.tsx
@@ -40,7 +40,7 @@ import {
statusConfig,
type TaskStatus,
} from "@/lib/components/tasks/TaskStatusBadge";
-import dayjs from "@conductor/shared/dates";
+import dayjs, { compactRelativeTime } from "@conductor/shared/dates";
import { useState } from "react";
import { useRepo } from "@/lib/contexts/RepoContext";
import { DeleteTaskDialog } from "./_components/DeleteTaskDialog";
@@ -204,7 +204,7 @@ export function QuickTaskCard({
- {dayjs(createdAt).fromNow()}
+ {compactRelativeTime(createdAt)}
diff --git a/apps/web/lib/components/quick-tasks/QuickTaskCard.tsx b/apps/web/lib/components/quick-tasks/QuickTaskCard.tsx
index 02540868f..89ad0eee3 100644
--- a/apps/web/lib/components/quick-tasks/QuickTaskCard.tsx
+++ b/apps/web/lib/components/quick-tasks/QuickTaskCard.tsx
@@ -40,7 +40,7 @@ import {
statusConfig,
type TaskStatus,
} from "@/lib/components/tasks/TaskStatusBadge";
-import dayjs from "@conductor/shared/dates";
+import dayjs, { compactRelativeTime } from "@conductor/shared/dates";
import { useState } from "react";
import { useRepo } from "@/lib/contexts/RepoContext";
import { DeleteTaskDialog } from "./_components/DeleteTaskDialog";
@@ -204,7 +204,7 @@ export function QuickTaskCard({
- {dayjs(createdAt).fromNow()}
+ {compactRelativeTime(createdAt)}
diff --git a/packages/shared/src/utils/dates.ts b/packages/shared/src/utils/dates.ts
index c5edf5354..f4efd0d92 100644
--- a/packages/shared/src/utils/dates.ts
+++ b/packages/shared/src/utils/dates.ts
@@ -3,4 +3,22 @@ import relativeTime from "dayjs/plugin/relativeTime";
dayjs.extend(relativeTime);
+export function compactRelativeTime(date: number | string | Date): string {
+ const now = dayjs();
+ const then = dayjs(date);
+ const diffSeconds = now.diff(then, "second");
+
+ if (diffSeconds < 60) return `${diffSeconds}s`;
+ const diffMinutes = now.diff(then, "minute");
+ if (diffMinutes < 60) return `${diffMinutes}m`;
+ const diffHours = now.diff(then, "hour");
+ if (diffHours < 24) return `${diffHours}h`;
+ const diffDays = now.diff(then, "day");
+ if (diffDays < 30) return `${diffDays}d`;
+ const diffMonths = now.diff(then, "month");
+ if (diffMonths < 12) return `${diffMonths}mo`;
+ const diffYears = now.diff(then, "year");
+ return `${diffYears}y`;
+}
+
export default dayjs;
From 5014d7e2bb12f89a29cc73059d2e7d38a3a98666 Mon Sep 17 00:00:00 2001
From: Eva <48868398+vedantb2@users.noreply.github.com>
Date: Wed, 25 Mar 2026 15:06:56 +0000
Subject: [PATCH 08/22] fix(web-v2): add inbox to KNOWN_SUB_PAGES to fix URL
parsing
When on /{owner}/{repo}/inbox, the route parser treated "inbox" as an
appName because it wasn't in KNOWN_SUB_PAGES, causing repoBasePath to
include "/inbox" and all nav links to generate broken URLs.
---
apps/web-v2/src/lib/components/Sidebar.tsx | 1 +
1 file changed, 1 insertion(+)
diff --git a/apps/web-v2/src/lib/components/Sidebar.tsx b/apps/web-v2/src/lib/components/Sidebar.tsx
index 856b8ea8e..b60f68d15 100644
--- a/apps/web-v2/src/lib/components/Sidebar.tsx
+++ b/apps/web-v2/src/lib/components/Sidebar.tsx
@@ -59,6 +59,7 @@ const KNOWN_SUB_PAGES = new Set([
"testing-arena",
"stats",
"automations",
+ "inbox",
]);
const CONTEXT_SIDEBAR_BY_NAV_NAME = {
From 9464fb32cbded450ac3f19b3d75a8bb8500c517c Mon Sep 17 00:00:00 2001
From: Eva <48868398+vedantb2@users.noreply.github.com>
Date: Wed, 25 Mar 2026 15:10:00 +0000
Subject: [PATCH 09/22] feat: show "My Team" / "{user}'s Team" instead of
"Personal" for personal teams
Personal teams now display contextual names:
- Your own personal team shows as "My Team"
- Other users' personal teams show as "{firstName}'s Team"
Updated in both web and web-v2 apps, with the display logic computed
in the backend teams.list and teams.get queries.
---
.../src/routes/_global/home/ReposClient.tsx | 11 ++++----
.../routes/_global/teams/TeamDetailClient.tsx | 2 +-
.../src/routes/_global/teams/TeamsClient.tsx | 4 ++-
.../$repo/settings/TeamEnvVarsClient.tsx | 3 ++-
apps/web/app/(global)/home/ReposClient.tsx | 11 ++++----
apps/web/app/(global)/teams/TeamsClient.tsx | 4 ++-
.../teams/[teamId]/TeamDetailClient.tsx | 2 +-
.../env-variables/TeamEnvVarsClient.tsx | 3 ++-
packages/backend/convex/teams.ts | 25 +++++++++++++++++++
9 files changed, 49 insertions(+), 16 deletions(-)
diff --git a/apps/web-v2/src/routes/_global/home/ReposClient.tsx b/apps/web-v2/src/routes/_global/home/ReposClient.tsx
index eb55efd3d..9a6312877 100644
--- a/apps/web-v2/src/routes/_global/home/ReposClient.tsx
+++ b/apps/web-v2/src/routes/_global/home/ReposClient.tsx
@@ -50,9 +50,10 @@ export function ReposClient() {
const groupedRepos = repos
? repos.reduce>((groups, repo) => {
- const groupKey = repo.teamId
- ? (teams.find((t) => t._id === repo.teamId)?.name ?? "Unknown Team")
- : "Personal";
+ const team = repo.teamId
+ ? teams.find((t) => t._id === repo.teamId)
+ : undefined;
+ const groupKey = team ? (team.displayName ?? team.name) : "My Team";
if (!groups[groupKey]) {
groups[groupKey] = [];
}
@@ -62,8 +63,8 @@ export function ReposClient() {
: {};
const groupNames = Object.keys(groupedRepos).sort((a, b) => {
- if (a === "Personal") return -1;
- if (b === "Personal") return 1;
+ if (a === "My Team") return -1;
+ if (b === "My Team") return 1;
return a.localeCompare(b);
});
diff --git a/apps/web-v2/src/routes/_global/teams/TeamDetailClient.tsx b/apps/web-v2/src/routes/_global/teams/TeamDetailClient.tsx
index b723cba8a..54233ea3c 100644
--- a/apps/web-v2/src/routes/_global/teams/TeamDetailClient.tsx
+++ b/apps/web-v2/src/routes/_global/teams/TeamDetailClient.tsx
@@ -43,7 +43,7 @@ export function TeamDetailClient({ teamId }: TeamDetailClientProps) {
const isOwner = team.userRole === "owner";
return (
-
+
{
diff --git a/apps/web-v2/src/routes/_global/teams/TeamsClient.tsx b/apps/web-v2/src/routes/_global/teams/TeamsClient.tsx
index 0b8c90c4c..6656e55cc 100644
--- a/apps/web-v2/src/routes/_global/teams/TeamsClient.tsx
+++ b/apps/web-v2/src/routes/_global/teams/TeamsClient.tsx
@@ -144,7 +144,9 @@ export function TeamsClient() {
- {team.name}
+
+ {team.displayName ?? team.name}
+
{team.userRole}
diff --git a/apps/web-v2/src/routes/_repo/$owner/$repo/settings/TeamEnvVarsClient.tsx b/apps/web-v2/src/routes/_repo/$owner/$repo/settings/TeamEnvVarsClient.tsx
index c5fd02ee4..f11009bb2 100644
--- a/apps/web-v2/src/routes/_repo/$owner/$repo/settings/TeamEnvVarsClient.tsx
+++ b/apps/web-v2/src/routes/_repo/$owner/$repo/settings/TeamEnvVarsClient.tsx
@@ -76,7 +76,8 @@ export function TeamEnvVarsClient() {
- Team: {team.name}
+ Team:{" "}
+ {team.displayName ?? team.name}
•
>((groups, repo) => {
- const groupKey = repo.teamId
- ? (teams.find((t) => t._id === repo.teamId)?.name ?? "Unknown Team")
- : "Personal";
+ const team = repo.teamId
+ ? teams.find((t) => t._id === repo.teamId)
+ : undefined;
+ const groupKey = team ? (team.displayName ?? team.name) : "My Team";
if (!groups[groupKey]) {
groups[groupKey] = [];
}
@@ -64,8 +65,8 @@ export function ReposClient() {
: {};
const groupNames = Object.keys(groupedRepos).sort((a, b) => {
- if (a === "Personal") return -1;
- if (b === "Personal") return 1;
+ if (a === "My Team") return -1;
+ if (b === "My Team") return 1;
return a.localeCompare(b);
});
diff --git a/apps/web/app/(global)/teams/TeamsClient.tsx b/apps/web/app/(global)/teams/TeamsClient.tsx
index ac4e717e1..6e7b7a0ce 100644
--- a/apps/web/app/(global)/teams/TeamsClient.tsx
+++ b/apps/web/app/(global)/teams/TeamsClient.tsx
@@ -147,7 +147,9 @@ export function TeamsClient() {
-
{team.name}
+
+ {team.displayName ?? team.name}
+
{team.userRole}
diff --git a/apps/web/app/(global)/teams/[teamId]/TeamDetailClient.tsx b/apps/web/app/(global)/teams/[teamId]/TeamDetailClient.tsx
index d14fb663e..a594a75c4 100644
--- a/apps/web/app/(global)/teams/[teamId]/TeamDetailClient.tsx
+++ b/apps/web/app/(global)/teams/[teamId]/TeamDetailClient.tsx
@@ -41,7 +41,7 @@ export function TeamDetailClient({ teamId }: { teamId: string }) {
const isOwner = team.userRole === "owner";
return (
-
+
{
diff --git a/apps/web/app/(repo)/[owner]/[repo]/settings/env-variables/TeamEnvVarsClient.tsx b/apps/web/app/(repo)/[owner]/[repo]/settings/env-variables/TeamEnvVarsClient.tsx
index 1b05638af..8744f408e 100644
--- a/apps/web/app/(repo)/[owner]/[repo]/settings/env-variables/TeamEnvVarsClient.tsx
+++ b/apps/web/app/(repo)/[owner]/[repo]/settings/env-variables/TeamEnvVarsClient.tsx
@@ -77,7 +77,8 @@ export function TeamEnvVarsClient() {
- Team: {team.name}
+ Team:{" "}
+ {team.displayName ?? team.name}
•
Date: Thu, 26 Mar 2026 09:17:57 +0000
Subject: [PATCH 10/22] fix(users): use fullName as fallback for display when
firstName/lastName unavailable
---
packages/backend/convex/users.ts | 2 ++
.../shared/src/components/user-initials.tsx | 18 +++++++++++++++---
2 files changed, 17 insertions(+), 3 deletions(-)
diff --git a/packages/backend/convex/users.ts b/packages/backend/convex/users.ts
index 3c149e5fe..323cfd460 100644
--- a/packages/backend/convex/users.ts
+++ b/packages/backend/convex/users.ts
@@ -19,6 +19,7 @@ export const get = authQuery({
v.object({
firstName: v.optional(v.string()),
lastName: v.optional(v.string()),
+ fullName: v.optional(v.string()),
lastSeenAt: v.optional(v.number()),
}),
v.null(),
@@ -29,6 +30,7 @@ export const get = authQuery({
return {
firstName: user.firstName,
lastName: user.lastName,
+ fullName: user.fullName,
lastSeenAt: user.lastSeenAt,
};
},
diff --git a/packages/shared/src/components/user-initials.tsx b/packages/shared/src/components/user-initials.tsx
index ed30d91ea..9245fc3fc 100644
--- a/packages/shared/src/components/user-initials.tsx
+++ b/packages/shared/src/components/user-initials.tsx
@@ -16,10 +16,22 @@ export function UserInitials({
}) {
const user = useQuery(api.users.get, { id: userId });
if (!user) return null;
+ const firstLast =
+ `${user.firstName?.[0] ?? ""}${user.lastName?.[0] ?? ""}`.toUpperCase();
const initials =
- `${user.firstName?.[0] ?? ""}${user.lastName?.[0] ?? ""}`.toUpperCase() ||
- "?";
- const name = `${user.firstName ?? ""} ${user.lastName ?? ""}`.trim();
+ firstLast ||
+ (user.fullName
+ ? user.fullName
+ .split(/\s+/)
+ .map((w) => w[0])
+ .join("")
+ .toUpperCase()
+ .slice(0, 2)
+ : "?");
+ const name =
+ `${user.firstName ?? ""} ${user.lastName ?? ""}`.trim() ||
+ user.fullName ||
+ "";
const online = !!user.lastSeenAt && Date.now() - user.lastSeenAt < 120_000;
const tooltip = online
? `${name} · Online`
From 953f8157e4ff6e21202c13e2b170af1e7a5167b0 Mon Sep 17 00:00:00 2001
From: Vedant <48868398+vedantb2@users.noreply.github.com>
Date: Thu, 26 Mar 2026 10:35:25 +0000
Subject: [PATCH 11/22] chore: allow any origin in dev mode for proxy
environments
---
apps/web/next.config.js | 1 +
1 file changed, 1 insertion(+)
diff --git a/apps/web/next.config.js b/apps/web/next.config.js
index 13f5e1ab2..e0bda3ad2 100644
--- a/apps/web/next.config.js
+++ b/apps/web/next.config.js
@@ -1,5 +1,6 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
+ allowedDevOrigins: ["*"],
turbopack: {
resolveExtensions: [".ts", ".tsx", ".js", ".jsx"],
},
From 77f846f5758344b479511bb437fc9cf426ccd27d Mon Sep 17 00:00:00 2001
From: Vedant <48868398+vedantb2@users.noreply.github.com>
Date: Thu, 26 Mar 2026 10:37:56 +0000
Subject: [PATCH 12/22] rename repositories to codebases
---
apps/web-v2/src/routes/_global/home/ReposClient.tsx | 2 +-
.../src/routes/_global/home/_components/EmptyOnboarding.tsx | 4 ++--
.../routes/_global/home/_components/HiddenReposSheet.tsx | 2 +-
apps/web-v2/src/routes/_global/setup/RepoSetupClient.tsx | 6 +++---
apps/web-v2/src/routes/_global/teams/TeamDetailClient.tsx | 2 +-
apps/web-v2/src/routes/_global/teams/TeamsClient.tsx | 4 ++--
.../src/routes/_global/teams/_components/TeamEnvVarsTab.tsx | 2 +-
.../routes/_repo/$owner/$repo/settings/MonorepoClient.tsx | 2 +-
.../_repo/$owner/$repo/settings/TeamEnvVarsClient.tsx | 2 +-
apps/web/app/(global)/home/ReposClient.tsx | 2 +-
apps/web/app/(global)/home/_components/EmptyOnboarding.tsx | 4 ++--
apps/web/app/(global)/home/_components/HiddenReposSheet.tsx | 2 +-
apps/web/app/(global)/setup/[id]/RepoSetupClient.tsx | 6 +++---
apps/web/app/(global)/teams/TeamsClient.tsx | 4 ++--
apps/web/app/(global)/teams/[teamId]/TeamDetailClient.tsx | 2 +-
.../(global)/teams/[teamId]/_components/TeamEnvVarsTab.tsx | 2 +-
.../[repo]/settings/env-variables/TeamEnvVarsClient.tsx | 2 +-
.../[owner]/[repo]/settings/monorepo/MonorepoClient.tsx | 2 +-
18 files changed, 26 insertions(+), 26 deletions(-)
diff --git a/apps/web-v2/src/routes/_global/home/ReposClient.tsx b/apps/web-v2/src/routes/_global/home/ReposClient.tsx
index 9a6312877..02fa53208 100644
--- a/apps/web-v2/src/routes/_global/home/ReposClient.tsx
+++ b/apps/web-v2/src/routes/_global/home/ReposClient.tsx
@@ -70,7 +70,7 @@ export function ReposClient() {
return (
{hasRepos && }
diff --git a/apps/web-v2/src/routes/_global/home/_components/EmptyOnboarding.tsx b/apps/web-v2/src/routes/_global/home/_components/EmptyOnboarding.tsx
index cb70e9cd5..82ff16398 100644
--- a/apps/web-v2/src/routes/_global/home/_components/EmptyOnboarding.tsx
+++ b/apps/web-v2/src/routes/_global/home/_components/EmptyOnboarding.tsx
@@ -63,8 +63,8 @@ export function EmptyOnboarding({ connectUrl }: { connectUrl: string }) {
Connect your GitHub
- Link your repositories to unlock Eva's AI tools for planning, coding,
- and shipping features autonomously.
+ Link your codebases to unlock Eva's AI tools for planning, coding, and
+ shipping features autonomously.
- Hidden Repositories
+ Hidden Codebases
{hiddenRepos.map((repo) => (
diff --git a/apps/web-v2/src/routes/_global/setup/RepoSetupClient.tsx b/apps/web-v2/src/routes/_global/setup/RepoSetupClient.tsx
index e45ca77a1..80d139906 100644
--- a/apps/web-v2/src/routes/_global/setup/RepoSetupClient.tsx
+++ b/apps/web-v2/src/routes/_global/setup/RepoSetupClient.tsx
@@ -126,7 +126,7 @@ export function RepoSetupClient({
- {syncing ? "Adding repositories..." : "Loading repositories..."}
+ {syncing ? "Adding codebases..." : "Loading codebases..."}
@@ -139,7 +139,7 @@ export function RepoSetupClient({
{error}
navigate({ to: "/home" })}>
- Back to Repositories
+ Back to Codebases
@@ -153,7 +153,7 @@ export function RepoSetupClient({
GitHub App Installed
- Select which repositories you want to add to Eva.
+ Select which codebases you want to add to Eva.
diff --git a/apps/web-v2/src/routes/_global/teams/TeamDetailClient.tsx b/apps/web-v2/src/routes/_global/teams/TeamDetailClient.tsx
index 54233ea3c..e89877307 100644
--- a/apps/web-v2/src/routes/_global/teams/TeamDetailClient.tsx
+++ b/apps/web-v2/src/routes/_global/teams/TeamDetailClient.tsx
@@ -52,7 +52,7 @@ export function TeamDetailClient({ teamId }: TeamDetailClientProps) {
>
Members
- Repositories
+ Codebases
Environment Variables
diff --git a/apps/web-v2/src/routes/_global/teams/TeamsClient.tsx b/apps/web-v2/src/routes/_global/teams/TeamsClient.tsx
index 6656e55cc..f9e096115 100644
--- a/apps/web-v2/src/routes/_global/teams/TeamsClient.tsx
+++ b/apps/web-v2/src/routes/_global/teams/TeamsClient.tsx
@@ -74,7 +74,7 @@ export function TeamsClient() {
- Manage your teams and collaborate on repositories
+ Manage your teams and collaborate on codebases
@@ -164,7 +164,7 @@ export function TeamsClient() {
No teams yet
- Create a team to collaborate on repositories
+ Create a team to collaborate on codebases
{
await toggleSandboxExclude({ teamId, key, sandboxExclude });
}}
- description="Team-level variables inherited by all repositories in this team."
+ description="Team-level variables inherited by all codebases in this team."
/>
);
}
diff --git a/apps/web-v2/src/routes/_repo/$owner/$repo/settings/MonorepoClient.tsx b/apps/web-v2/src/routes/_repo/$owner/$repo/settings/MonorepoClient.tsx
index c854264ed..6f679d62e 100644
--- a/apps/web-v2/src/routes/_repo/$owner/$repo/settings/MonorepoClient.tsx
+++ b/apps/web-v2/src/routes/_repo/$owner/$repo/settings/MonorepoClient.tsx
@@ -172,7 +172,7 @@ export function MonorepoClient() {
{repo.owner}/{repo.name}
{" "}
- for workspace apps and add them as separate repositories.
+ for workspace apps and add them as separate codebases.
diff --git a/apps/web-v2/src/routes/_repo/$owner/$repo/settings/TeamEnvVarsClient.tsx b/apps/web-v2/src/routes/_repo/$owner/$repo/settings/TeamEnvVarsClient.tsx
index f11009bb2..eee0c90a3 100644
--- a/apps/web-v2/src/routes/_repo/$owner/$repo/settings/TeamEnvVarsClient.tsx
+++ b/apps/web-v2/src/routes/_repo/$owner/$repo/settings/TeamEnvVarsClient.tsx
@@ -46,7 +46,7 @@ export function TeamEnvVarsClient() {
{
if (!repo.teamId) return;
await upsertTeamVar({
diff --git a/apps/web/app/(global)/home/ReposClient.tsx b/apps/web/app/(global)/home/ReposClient.tsx
index 8b6649488..4ec45e2b1 100644
--- a/apps/web/app/(global)/home/ReposClient.tsx
+++ b/apps/web/app/(global)/home/ReposClient.tsx
@@ -72,7 +72,7 @@ export function ReposClient() {
return (
{hasRepos && }
diff --git a/apps/web/app/(global)/home/_components/EmptyOnboarding.tsx b/apps/web/app/(global)/home/_components/EmptyOnboarding.tsx
index cb70e9cd5..82ff16398 100644
--- a/apps/web/app/(global)/home/_components/EmptyOnboarding.tsx
+++ b/apps/web/app/(global)/home/_components/EmptyOnboarding.tsx
@@ -63,8 +63,8 @@ export function EmptyOnboarding({ connectUrl }: { connectUrl: string }) {
Connect your GitHub
- Link your repositories to unlock Eva's AI tools for planning, coding,
- and shipping features autonomously.
+ Link your codebases to unlock Eva's AI tools for planning, coding, and
+ shipping features autonomously.
- Hidden Repositories
+ Hidden Codebases
{hiddenRepos.map((repo) => (
diff --git a/apps/web/app/(global)/setup/[id]/RepoSetupClient.tsx b/apps/web/app/(global)/setup/[id]/RepoSetupClient.tsx
index b2787a318..45082b00d 100644
--- a/apps/web/app/(global)/setup/[id]/RepoSetupClient.tsx
+++ b/apps/web/app/(global)/setup/[id]/RepoSetupClient.tsx
@@ -126,7 +126,7 @@ export function RepoSetupClient({ installationId }: RepoSetupClientProps) {
- {syncing ? "Adding repositories..." : "Loading repositories..."}
+ {syncing ? "Adding codebases..." : "Loading codebases..."}
@@ -139,7 +139,7 @@ export function RepoSetupClient({ installationId }: RepoSetupClientProps) {
{error}
router.push("/home")}>
- Back to Repositories
+ Back to Codebases
@@ -153,7 +153,7 @@ export function RepoSetupClient({ installationId }: RepoSetupClientProps) {
GitHub App Installed
- Select which repositories you want to add to Eva.
+ Select which codebases you want to add to Eva.
diff --git a/apps/web/app/(global)/teams/TeamsClient.tsx b/apps/web/app/(global)/teams/TeamsClient.tsx
index 6e7b7a0ce..cf280a56d 100644
--- a/apps/web/app/(global)/teams/TeamsClient.tsx
+++ b/apps/web/app/(global)/teams/TeamsClient.tsx
@@ -77,7 +77,7 @@ export function TeamsClient() {
- Manage your teams and collaborate on repositories
+ Manage your teams and collaborate on codebases
@@ -167,7 +167,7 @@ export function TeamsClient() {
No teams yet
- Create a team to collaborate on repositories
+ Create a team to collaborate on codebases
Members
- Repositories
+ Codebases
Environment Variables
diff --git a/apps/web/app/(global)/teams/[teamId]/_components/TeamEnvVarsTab.tsx b/apps/web/app/(global)/teams/[teamId]/_components/TeamEnvVarsTab.tsx
index 11430524a..a8c49b967 100644
--- a/apps/web/app/(global)/teams/[teamId]/_components/TeamEnvVarsTab.tsx
+++ b/apps/web/app/(global)/teams/[teamId]/_components/TeamEnvVarsTab.tsx
@@ -34,7 +34,7 @@ export function TeamEnvVarsTab({ teamId, teamEnvVars }: TeamEnvVarsTabProps) {
onToggleSandboxExclude={async (key, sandboxExclude) => {
await toggleSandboxExclude({ teamId, key, sandboxExclude });
}}
- description="Team-level variables inherited by all repositories in this team."
+ description="Team-level variables inherited by all codebases in this team."
/>
);
}
diff --git a/apps/web/app/(repo)/[owner]/[repo]/settings/env-variables/TeamEnvVarsClient.tsx b/apps/web/app/(repo)/[owner]/[repo]/settings/env-variables/TeamEnvVarsClient.tsx
index 8744f408e..bec348939 100644
--- a/apps/web/app/(repo)/[owner]/[repo]/settings/env-variables/TeamEnvVarsClient.tsx
+++ b/apps/web/app/(repo)/[owner]/[repo]/settings/env-variables/TeamEnvVarsClient.tsx
@@ -47,7 +47,7 @@ export function TeamEnvVarsClient() {
{
if (!repo.teamId) return;
await upsertTeamVar({
diff --git a/apps/web/app/(repo)/[owner]/[repo]/settings/monorepo/MonorepoClient.tsx b/apps/web/app/(repo)/[owner]/[repo]/settings/monorepo/MonorepoClient.tsx
index c854264ed..6f679d62e 100644
--- a/apps/web/app/(repo)/[owner]/[repo]/settings/monorepo/MonorepoClient.tsx
+++ b/apps/web/app/(repo)/[owner]/[repo]/settings/monorepo/MonorepoClient.tsx
@@ -172,7 +172,7 @@ export function MonorepoClient() {
{repo.owner}/{repo.name}
{" "}
- for workspace apps and add them as separate repositories.
+ for workspace apps and add them as separate codebases.
From 117b76104071e59f8aa89b05b046e5509d32e0c1 Mon Sep 17 00:00:00 2001
From: Vedant <48868398+vedantb2@users.noreply.github.com>
Date: Thu, 26 Mar 2026 10:44:36 +0000
Subject: [PATCH 13/22] feat: hide dev-only features in sidebar for
non-development environments
Filters out Designs, Sessions, Documents, Testing Arena, and Analyse features when not in development mode. Production and staging only show Projects, Quick Tasks, Inbox, Automations, Stats, and Settings.
---
apps/web-v2/src/lib/components/Sidebar.tsx | 200 +++++++++++----------
apps/web/lib/components/Sidebar.tsx | 199 ++++++++++----------
2 files changed, 213 insertions(+), 186 deletions(-)
diff --git a/apps/web-v2/src/lib/components/Sidebar.tsx b/apps/web-v2/src/lib/components/Sidebar.tsx
index b60f68d15..5fad32e42 100644
--- a/apps/web-v2/src/lib/components/Sidebar.tsx
+++ b/apps/web-v2/src/lib/components/Sidebar.tsx
@@ -31,6 +31,7 @@ import {
IconTool,
IconX,
} from "@tabler/icons-react";
+import { clientEnv } from "@/env/client";
import { api } from "@conductor/backend";
import { Button, Spinner, cn } from "@conductor/ui";
import { ActiveTasksBadge } from "@/lib/components/sidebar/ActiveTasksPopover";
@@ -176,99 +177,112 @@ export function Sidebar() {
owner && repoName ? { owner, name: repoName, appName } : "skip",
);
- const repoNavigation = useMemo(
- () =>
- isRepoRoute && repoBasePath
- ? [
- {
- label: "BUILD",
- groupIcon: IconHammer,
- items: [
- {
- name: "Projects",
- href: `${repoBasePath}/projects`,
- icon: IconLayoutKanban,
- },
- {
- name: "Designs",
- href: `${repoBasePath}/designs`,
- icon: IconPalette,
- },
- ],
- },
- {
- label: "FIX",
- groupIcon: IconTool,
- items: [
- {
- name: "Quick Tasks",
- href: `${repoBasePath}/quick-tasks`,
- icon: IconChecklist,
- },
- {
- name: "Sessions",
- href: `${repoBasePath}/sessions`,
- icon: IconTerminal2,
- },
- ],
- },
- {
- label: "TEST",
- groupIcon: IconTestPipe,
- items: [
- {
- name: "Documents",
- href: `${repoBasePath}/docs`,
- icon: IconFileText,
- },
- {
- name: "Testing Arena",
- href: `${repoBasePath}/testing-arena`,
- icon: IconFlask,
- },
- ],
- },
- {
- label: "DATA",
- groupIcon: IconChartBar,
- items: [
- {
- name: "Analyse",
- href: `${repoBasePath}/analyse`,
- icon: IconBrain,
- },
- ],
- },
- {
- label: "SETTINGS",
- groupIcon: IconSettings,
- items: [
- {
- name: "Inbox",
- href: `${repoBasePath}/inbox`,
- icon: IconInbox,
- },
- {
- name: "Automations",
- href: `${repoBasePath}/automations`,
- icon: IconPlayerPlay,
- },
- {
- name: "Stats",
- href: `${repoBasePath}/stats`,
- icon: IconChartBar,
- },
- {
- name: "Settings",
- href: `${repoBasePath}/settings/config`,
- icon: IconSettings,
- },
- ],
- },
- ]
- : [],
- [repoBasePath, isRepoRoute],
- );
+ const isDev = clientEnv.VITE_ENV === "development";
+
+ const repoNavigation = useMemo(() => {
+ if (!isRepoRoute || !repoBasePath) return [];
+ const allGroups = [
+ {
+ label: "BUILD",
+ groupIcon: IconHammer,
+ items: [
+ {
+ name: "Projects",
+ href: `${repoBasePath}/projects`,
+ icon: IconLayoutKanban,
+ },
+ {
+ name: "Designs",
+ href: `${repoBasePath}/designs`,
+ icon: IconPalette,
+ devOnly: true,
+ },
+ ],
+ },
+ {
+ label: "FIX",
+ groupIcon: IconTool,
+ items: [
+ {
+ name: "Quick Tasks",
+ href: `${repoBasePath}/quick-tasks`,
+ icon: IconChecklist,
+ },
+ {
+ name: "Sessions",
+ href: `${repoBasePath}/sessions`,
+ icon: IconTerminal2,
+ },
+ ],
+ },
+ {
+ label: "TEST",
+ groupIcon: IconTestPipe,
+ devOnly: true,
+ items: [
+ {
+ name: "Documents",
+ href: `${repoBasePath}/docs`,
+ icon: IconFileText,
+ devOnly: true,
+ },
+ {
+ name: "Testing Arena",
+ href: `${repoBasePath}/testing-arena`,
+ icon: IconFlask,
+ devOnly: true,
+ },
+ ],
+ },
+ {
+ label: "DATA",
+ groupIcon: IconChartBar,
+ devOnly: true,
+ items: [
+ {
+ name: "Analyse",
+ href: `${repoBasePath}/analyse`,
+ icon: IconBrain,
+ devOnly: true,
+ },
+ ],
+ },
+ {
+ label: "SETTINGS",
+ groupIcon: IconSettings,
+ items: [
+ {
+ name: "Inbox",
+ href: `${repoBasePath}/inbox`,
+ icon: IconInbox,
+ },
+ {
+ name: "Automations",
+ href: `${repoBasePath}/automations`,
+ icon: IconPlayerPlay,
+ },
+ {
+ name: "Stats",
+ href: `${repoBasePath}/stats`,
+ icon: IconChartBar,
+ },
+ {
+ name: "Settings",
+ href: `${repoBasePath}/settings/config`,
+ icon: IconSettings,
+ },
+ ],
+ },
+ ];
+ if (isDev) return allGroups;
+ return allGroups
+ .filter((g) => !g.devOnly)
+ .map((g) => ({
+ ...g,
+ items: g.items.filter((i) => !i.devOnly),
+ }))
+ .filter((g) => g.items.length > 0);
+ }, [repoBasePath, isRepoRoute, isDev]);
const { theme, toggleTheme } = useThemeContext();
diff --git a/apps/web/lib/components/Sidebar.tsx b/apps/web/lib/components/Sidebar.tsx
index f1f1b2100..caf5dcf2f 100644
--- a/apps/web/lib/components/Sidebar.tsx
+++ b/apps/web/lib/components/Sidebar.tsx
@@ -175,99 +175,112 @@ export function Sidebar() {
owner && repoName ? { owner, name: repoName, appName } : "skip",
);
- const repoNavigation = useMemo(
- () =>
- isRepoRoute && repoBasePath
- ? [
- {
- label: "BUILD",
- groupIcon: IconHammer,
- items: [
- {
- name: "Projects",
- href: `${repoBasePath}/projects`,
- icon: IconLayoutKanban,
- },
- {
- name: "Designs",
- href: `${repoBasePath}/designs`,
- icon: IconPalette,
- },
- ],
- },
- {
- label: "FIX",
- groupIcon: IconTool,
- items: [
- {
- name: "Quick Tasks",
- href: `${repoBasePath}/quick-tasks`,
- icon: IconChecklist,
- },
- {
- name: "Sessions",
- href: `${repoBasePath}/sessions`,
- icon: IconTerminal2,
- },
- ],
- },
- {
- label: "TEST",
- groupIcon: IconTestPipe,
- items: [
- {
- name: "Documents",
- href: `${repoBasePath}/docs`,
- icon: IconFileText,
- },
- {
- name: "Testing Arena",
- href: `${repoBasePath}/testing-arena`,
- icon: IconFlask,
- },
- ],
- },
- {
- label: "DATA",
- groupIcon: IconChartBar,
- items: [
- {
- name: "Analyse",
- href: `${repoBasePath}/analyse`,
- icon: IconBrain,
- },
- ],
- },
- {
- label: "SETTINGS",
- groupIcon: IconSettings,
- items: [
- {
- name: "Inbox",
- href: `${repoBasePath}/inbox`,
- icon: IconInbox,
- },
- {
- name: "Automations",
- href: `${repoBasePath}/automations`,
- icon: IconPlayerPlay,
- },
- {
- name: "Stats",
- href: `${repoBasePath}/stats`,
- icon: IconChartBar,
- },
- {
- name: "Settings",
- href: `${repoBasePath}/settings/config`,
- icon: IconSettings,
- },
- ],
- },
- ]
- : [],
- [repoBasePath, isRepoRoute],
- );
+ const isDev = process.env.NODE_ENV === "development";
+
+ const repoNavigation = useMemo(() => {
+ if (!isRepoRoute || !repoBasePath) return [];
+ const allGroups = [
+ {
+ label: "BUILD",
+ groupIcon: IconHammer,
+ items: [
+ {
+ name: "Projects",
+ href: `${repoBasePath}/projects`,
+ icon: IconLayoutKanban,
+ },
+ {
+ name: "Designs",
+ href: `${repoBasePath}/designs`,
+ icon: IconPalette,
+ devOnly: true,
+ },
+ ],
+ },
+ {
+ label: "FIX",
+ groupIcon: IconTool,
+ items: [
+ {
+ name: "Quick Tasks",
+ href: `${repoBasePath}/quick-tasks`,
+ icon: IconChecklist,
+ },
+ {
+ name: "Sessions",
+ href: `${repoBasePath}/sessions`,
+ icon: IconTerminal2,
+ },
+ ],
+ },
+ {
+ label: "TEST",
+ groupIcon: IconTestPipe,
+ devOnly: true,
+ items: [
+ {
+ name: "Documents",
+ href: `${repoBasePath}/docs`,
+ icon: IconFileText,
+ devOnly: true,
+ },
+ {
+ name: "Testing Arena",
+ href: `${repoBasePath}/testing-arena`,
+ icon: IconFlask,
+ devOnly: true,
+ },
+ ],
+ },
+ {
+ label: "DATA",
+ groupIcon: IconChartBar,
+ devOnly: true,
+ items: [
+ {
+ name: "Analyse",
+ href: `${repoBasePath}/analyse`,
+ icon: IconBrain,
+ devOnly: true,
+ },
+ ],
+ },
+ {
+ label: "SETTINGS",
+ groupIcon: IconSettings,
+ items: [
+ {
+ name: "Inbox",
+ href: `${repoBasePath}/inbox`,
+ icon: IconInbox,
+ },
+ {
+ name: "Automations",
+ href: `${repoBasePath}/automations`,
+ icon: IconPlayerPlay,
+ },
+ {
+ name: "Stats",
+ href: `${repoBasePath}/stats`,
+ icon: IconChartBar,
+ },
+ {
+ name: "Settings",
+ href: `${repoBasePath}/settings/config`,
+ icon: IconSettings,
+ },
+ ],
+ },
+ ];
+ if (isDev) return allGroups;
+ return allGroups
+ .filter((g) => !g.devOnly)
+ .map((g) => ({
+ ...g,
+ items: g.items.filter((i) => !i.devOnly),
+ }))
+ .filter((g) => g.items.length > 0);
+ }, [repoBasePath, isRepoRoute, isDev]);
const { theme, toggleTheme } = useThemeContext();
From fe620726f712415d76045553fd13291e583936a9 Mon Sep 17 00:00:00 2001
From: Vedant <48868398+vedantb2@users.noreply.github.com>
Date: Thu, 26 Mar 2026 10:57:56 +0000
Subject: [PATCH 14/22] fix ext
---
apps/chrome-extension/src/sidepanel/components/ChatPanel.tsx | 1 -
1 file changed, 1 deletion(-)
diff --git a/apps/chrome-extension/src/sidepanel/components/ChatPanel.tsx b/apps/chrome-extension/src/sidepanel/components/ChatPanel.tsx
index 6adc5edf3..8b5233e1e 100644
--- a/apps/chrome-extension/src/sidepanel/components/ChatPanel.tsx
+++ b/apps/chrome-extension/src/sidepanel/components/ChatPanel.tsx
@@ -209,7 +209,6 @@ export function ChatPanel({
mode: "ask",
model,
responseLength,
- installationId: selectedRepo.installationId,
});
} catch (error) {
await appendMessage({
From 9406fd53d0a82cd79bd8c1ba76d1192bc5c20fec Mon Sep 17 00:00:00 2001
From: Vedant <48868398+vedantb2@users.noreply.github.com>
Date: Thu, 26 Mar 2026 11:09:29 +0000
Subject: [PATCH 15/22] fix: allow team members with repo access to execute
sessions
Replace strict session.userId check with hasRepoAccess validation in startExecute, cancelExecution, and sessionComplete mutations. This allows any team member with repo access to execute on sessions created by others, matching the behavior of sessions.list which already shows all repo sessions to all team members.
---
packages/backend/convex/sessionWorkflow.ts | 11 +++++++----
1 file changed, 7 insertions(+), 4 deletions(-)
diff --git a/packages/backend/convex/sessionWorkflow.ts b/packages/backend/convex/sessionWorkflow.ts
index 6310863a0..c8263ed1b 100644
--- a/packages/backend/convex/sessionWorkflow.ts
+++ b/packages/backend/convex/sessionWorkflow.ts
@@ -3,7 +3,7 @@ import { internalMutation, internalQuery } from "./_generated/server";
import { internal } from "./_generated/api";
import { defineEvent, type WorkflowId } from "@convex-dev/workflow";
import { workflow } from "./workflowManager";
-import { authMutation } from "./functions";
+import { authMutation, hasRepoAccess } from "./functions";
import { sessionModeValidator, workflowCompleteValidator } from "./validators";
import { RUN_TIMEOUT_MS } from "./workflowWatchdog";
import { clearStreamingActivity } from "./_taskWorkflow/helpers";
@@ -444,7 +444,8 @@ export const handleCompletion = authMutation({
handler: async (ctx, args) => {
const session = await ctx.db.get(args.sessionId);
if (!session || !session.activeWorkflowId) return null;
- if (session.userId !== ctx.userId) throw new Error("Not authorized");
+ if (!(await hasRepoAccess(ctx.db, session.repoId, ctx.userId)))
+ throw new Error("Not authorized");
await workflow.sendEvent(ctx, {
...sessionCompleteEvent,
@@ -482,7 +483,8 @@ export const startExecute = authMutation({
handler: async (ctx, args) => {
const session = await ctx.db.get(args.sessionId);
if (!session) throw new Error("Session not found");
- if (session.userId !== ctx.userId) throw new Error("Not authorized");
+ if (!(await hasRepoAccess(ctx.db, session.repoId, ctx.userId)))
+ throw new Error("Not authorized");
if (
args.mode !== "ask" &&
@@ -531,7 +533,8 @@ export const cancelExecution = authMutation({
handler: async (ctx, args) => {
const session = await ctx.db.get(args.sessionId);
if (!session) throw new Error("Session not found");
- if (session.userId !== ctx.userId) throw new Error("Not authorized");
+ if (!(await hasRepoAccess(ctx.db, session.repoId, ctx.userId)))
+ throw new Error("Not authorized");
if (session.activeWorkflowId) {
await workflow.cancel(ctx, session.activeWorkflowId as WorkflowId);
From 75a07563fe957d68b402ffbb7d6b94935c52900d Mon Sep 17 00:00:00 2001
From: Vedant <48868398+vedantb2@users.noreply.github.com>
Date: Thu, 26 Mar 2026 11:11:58 +0000
Subject: [PATCH 16/22] fix: use ActivitySteps fallback instead of Reasoning in
chrome extension chat
Replaces broken Reasoning component (which uses BrainIcon from lucide) with ActivitySteps fallback step pattern. Fixes chrome extension showing literal "IconBrain" text when AI starts streaming.
---
.../src/sidepanel/components/ChatPanel.tsx | 83 ++++++-------------
1 file changed, 26 insertions(+), 57 deletions(-)
diff --git a/apps/chrome-extension/src/sidepanel/components/ChatPanel.tsx b/apps/chrome-extension/src/sidepanel/components/ChatPanel.tsx
index 8b5233e1e..790ab0fc8 100644
--- a/apps/chrome-extension/src/sidepanel/components/ChatPanel.tsx
+++ b/apps/chrome-extension/src/sidepanel/components/ChatPanel.tsx
@@ -19,8 +19,6 @@ import {
Message as AIMessage,
MessageContent,
MessageResponse,
- Reasoning,
- ReasoningTrigger,
PromptInput,
PromptInputTextarea,
PromptInputFooter,
@@ -408,33 +406,21 @@ export function ChatPanel({
}
>
{message.role === "assistant" && !message.content ? (
- (() => {
- const steps = parseActivitySteps(streamingActivity);
- return steps ? (
-
- ) : (
-
-
- streaming ? "Working..." : "Processing complete"
- }
- />
-
-
- {streamingActivity ||
- message.activityLog ||
- "Starting..."}
-
-
-
- );
- })()
+
) : (
<>
{message.role === "assistant" ? (
@@ -452,18 +438,7 @@ export function ChatPanel({
startedAt={message.timestamp}
duration={duration}
/>
- ) : (
-
- "View logs"}
- />
-
-
- {message.activityLog}
-
-
-
- );
+ ) : null;
})()}
{"imageUrl" in message && message.imageUrl && (
{(() => {
- const steps = parseActivitySteps(streamingActivity);
const loadingEvaIcon = (
);
const lastMsg = messages[messages.length - 1];
- return steps ? (
+ return (
- ) : (
-
-
- streaming ? "Working..." : "Processing complete"
- }
- />
-
-
- {streamingActivity || "Starting..."}
-
-
-
);
})()}
From 13693c90c8a0254d0aba8f5c6bfa46f5ab3957ea Mon Sep 17 00:00:00 2001
From: Vedant <48868398+vedantb2@users.noreply.github.com>
Date: Fri, 27 Mar 2026 10:00:41 +0000
Subject: [PATCH 17/22] fix: remove zod validation from agent login query param
URLSearchParams directly checks for agent param presence instead of relying on
strict zod validation that rejected string/empty values from query strings.
Fixes "not found" error when navigating to /?agent or /?agent=true.
---
apps/web-v2/src/routes/index.tsx | 17 ++++-------------
.../deployment-status-tracking.md | 0
.../{todo => implemented}/figma-linear-auth.md | 0
.../{todo => implemented}/image-screenshot.md | 0
.../{todo => implemented}/sandbox-auto-auth.md | 0
.../{todo => implemented}/sandbox-mcp-auth.md | 0
.../plans/{todo => implemented}/video-clip.md | 0
7 files changed, 4 insertions(+), 13 deletions(-)
rename internal/plans/{todo => implemented}/deployment-status-tracking.md (100%)
rename internal/plans/{todo => implemented}/figma-linear-auth.md (100%)
rename internal/plans/{todo => implemented}/image-screenshot.md (100%)
rename internal/plans/{todo => implemented}/sandbox-auto-auth.md (100%)
rename internal/plans/{todo => implemented}/sandbox-mcp-auth.md (100%)
rename internal/plans/{todo => implemented}/video-clip.md (100%)
diff --git a/apps/web-v2/src/routes/index.tsx b/apps/web-v2/src/routes/index.tsx
index 0ff93f388..97e4632f8 100644
--- a/apps/web-v2/src/routes/index.tsx
+++ b/apps/web-v2/src/routes/index.tsx
@@ -1,28 +1,19 @@
-import {
- createFileRoute,
- useNavigate,
- useSearch,
-} from "@tanstack/react-router";
-import { zodSearchValidator } from "@tanstack/router-zod-adapter";
+import { createFileRoute, useNavigate } from "@tanstack/react-router";
import { useAuth, SignInButton, SignUpButton } from "@clerk/clerk-react";
import { Button } from "@conductor/ui";
import { clientEnv } from "@/env/client";
import { useEffect } from "react";
-import { z } from "zod";
const isProduction = clientEnv.VITE_ENV === "production";
export const Route = createFileRoute("/")({
- validateSearch: zodSearchValidator(
- z.object({ agent: z.boolean().optional() }),
- ),
component: LandingPage,
});
function LandingPage() {
const { isSignedIn, isLoaded } = useAuth();
const navigate = useNavigate();
- const { agent } = useSearch({ from: "/" });
+ const hasAgent = new URLSearchParams(window.location.search).has("agent");
useEffect(() => {
if (!isLoaded) return;
@@ -32,10 +23,10 @@ function LandingPage() {
return;
}
- if (agent) {
+ if (hasAgent) {
window.location.href = "/api/auth/agent-login";
}
- }, [isLoaded, isSignedIn, agent, navigate]);
+ }, [isLoaded, isSignedIn, hasAgent, navigate]);
return (
diff --git a/internal/plans/todo/deployment-status-tracking.md b/internal/plans/implemented/deployment-status-tracking.md
similarity index 100%
rename from internal/plans/todo/deployment-status-tracking.md
rename to internal/plans/implemented/deployment-status-tracking.md
diff --git a/internal/plans/todo/figma-linear-auth.md b/internal/plans/implemented/figma-linear-auth.md
similarity index 100%
rename from internal/plans/todo/figma-linear-auth.md
rename to internal/plans/implemented/figma-linear-auth.md
diff --git a/internal/plans/todo/image-screenshot.md b/internal/plans/implemented/image-screenshot.md
similarity index 100%
rename from internal/plans/todo/image-screenshot.md
rename to internal/plans/implemented/image-screenshot.md
diff --git a/internal/plans/todo/sandbox-auto-auth.md b/internal/plans/implemented/sandbox-auto-auth.md
similarity index 100%
rename from internal/plans/todo/sandbox-auto-auth.md
rename to internal/plans/implemented/sandbox-auto-auth.md
diff --git a/internal/plans/todo/sandbox-mcp-auth.md b/internal/plans/implemented/sandbox-mcp-auth.md
similarity index 100%
rename from internal/plans/todo/sandbox-mcp-auth.md
rename to internal/plans/implemented/sandbox-mcp-auth.md
diff --git a/internal/plans/todo/video-clip.md b/internal/plans/implemented/video-clip.md
similarity index 100%
rename from internal/plans/todo/video-clip.md
rename to internal/plans/implemented/video-clip.md
From 21ae978a08f344f7ff1d0e838ca630941abd0a30 Mon Sep 17 00:00:00 2001
From: Vedant <48868398+vedantb2@users.noreply.github.com>
Date: Fri, 27 Mar 2026 10:02:50 +0000
Subject: [PATCH 18/22] fix: allow Daytona proxy subdomains in
allowedDevOrigins
Wildcard pattern *.daytonaproxy01.net allows Next.js dev server to accept
cross-origin requests from Daytona proxy URLs that rotate every 30 minutes.
---
apps/web/next.config.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/apps/web/next.config.js b/apps/web/next.config.js
index e0bda3ad2..10a46ac61 100644
--- a/apps/web/next.config.js
+++ b/apps/web/next.config.js
@@ -1,6 +1,6 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
- allowedDevOrigins: ["*"],
+ allowedDevOrigins: ["*.daytonaproxy01.net"],
turbopack: {
resolveExtensions: [".ts", ".tsx", ".js", ".jsx"],
},
From 5aa9f5c2ed5f1b2ee021c229efe3f94aadeb8781 Mon Sep 17 00:00:00 2001
From: Vedant <48868398+vedantb2@users.noreply.github.com>
Date: Fri, 27 Mar 2026 10:08:37 +0000
Subject: [PATCH 19/22] fix: add CSP header for 4-segment session routes in
web-v2
URLs like /:owner/:repo/:extra/sessions/:path* were missing the
upgrade-insecure-requests header, causing mixed content errors when
iframes requested HTTP sandbox URLs from HTTPS pages.
---
apps/web-v2/vercel.json | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/apps/web-v2/vercel.json b/apps/web-v2/vercel.json
index 20a236ae3..c291b59d7 100644
--- a/apps/web-v2/vercel.json
+++ b/apps/web-v2/vercel.json
@@ -9,6 +9,15 @@
"value": "upgrade-insecure-requests"
}
]
+ },
+ {
+ "source": "/:owner/:repo/:extra/sessions/:path*",
+ "headers": [
+ {
+ "key": "Content-Security-Policy",
+ "value": "upgrade-insecure-requests"
+ }
+ ]
}
]
}
From ba274f12f9bf879738ee63b51efa5bd19062a536 Mon Sep 17 00:00:00 2001
From: Vedant <48868398+vedantb2@users.noreply.github.com>
Date: Fri, 27 Mar 2026 10:50:58 +0000
Subject: [PATCH 20/22] fix: resolve duplicate CORS headers and vite network
accessibility
vite was binding to localhost-only (::1) making it unreachable from Daytona proxy. disabled vite CORS since the proxy already handles it, preventing duplicate Access-Control-Allow-Origin headers. removed credentials flag from dismissDaytonaWarning fetch to simplify CORS handling.
---
apps/web-v2/src/lib/utils/dismissDaytonaWarning.ts | 2 +-
apps/web-v2/vite.config.ts | 4 ++++
apps/web/lib/utils/dismissDaytonaWarning.ts | 2 +-
3 files changed, 6 insertions(+), 2 deletions(-)
diff --git a/apps/web-v2/src/lib/utils/dismissDaytonaWarning.ts b/apps/web-v2/src/lib/utils/dismissDaytonaWarning.ts
index f16beef88..acd951ddb 100644
--- a/apps/web-v2/src/lib/utils/dismissDaytonaWarning.ts
+++ b/apps/web-v2/src/lib/utils/dismissDaytonaWarning.ts
@@ -6,8 +6,8 @@ export async function dismissDaytonaWarning(url: string): Promise {
try {
await fetch(url, {
method: "HEAD",
+ mode: "no-cors",
headers: { "X-Daytona-Skip-Preview-Warning": "true" },
- credentials: "include",
});
dismissed.add(origin);
} catch {
diff --git a/apps/web-v2/vite.config.ts b/apps/web-v2/vite.config.ts
index 99c397c54..f76dfc64b 100644
--- a/apps/web-v2/vite.config.ts
+++ b/apps/web-v2/vite.config.ts
@@ -75,6 +75,10 @@ export default defineConfig({
react(),
agentLoginPlugin(),
],
+ server: {
+ host: "0.0.0.0",
+ cors: false,
+ },
resolve: {
alias: {
"@": path.resolve(__dirname, "./src"),
diff --git a/apps/web/lib/utils/dismissDaytonaWarning.ts b/apps/web/lib/utils/dismissDaytonaWarning.ts
index f16beef88..acd951ddb 100644
--- a/apps/web/lib/utils/dismissDaytonaWarning.ts
+++ b/apps/web/lib/utils/dismissDaytonaWarning.ts
@@ -6,8 +6,8 @@ export async function dismissDaytonaWarning(url: string): Promise {
try {
await fetch(url, {
method: "HEAD",
+ mode: "no-cors",
headers: { "X-Daytona-Skip-Preview-Warning": "true" },
- credentials: "include",
});
dismissed.add(origin);
} catch {
From b93f4efe94a64a59be1f8f7e68c0b66077c07811 Mon Sep 17 00:00:00 2001
From: Vedant <48868398+vedantb2@users.noreply.github.com>
Date: Fri, 27 Mar 2026 11:52:56 +0000
Subject: [PATCH 21/22] fix: retry on Daytona command execution timeout and
increase branch check timeout
Add "command execution timeout" to retryable session git errors so transient Daytona
sandbox exec timeouts trigger retries (3 attempts) instead of failing immediately.
Increase remoteBranchExists timeout from 10s to 20s to give snapshot sandboxes more
headroom for initial git ls-remote calls.
---
packages/backend/convex/_daytona/sessions.ts | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/packages/backend/convex/_daytona/sessions.ts b/packages/backend/convex/_daytona/sessions.ts
index 22ac35812..c1ce76e1b 100644
--- a/packages/backend/convex/_daytona/sessions.ts
+++ b/packages/backend/convex/_daytona/sessions.ts
@@ -64,6 +64,7 @@ function isRetryableSessionGitError(message: string): boolean {
const lower = message.toLowerCase();
return (
(lower.includes("sandbox exec") && lower.includes("timed out")) ||
+ lower.includes("command execution timeout") ||
lower.includes("fetch failed") ||
lower.includes("econnreset") ||
lower.includes("econnrefused") ||
@@ -89,7 +90,7 @@ async function checkRemoteBranchExistsWithRetry(
repoOwner,
repoName,
branchName,
- 10,
+ 20,
);
if (attempt > 1) {
logSession(
From faf08d50ebb4c5ae81bdcbdbec0b614fa128429b Mon Sep 17 00:00:00 2001
From: Vedant <48868398+vedantb2@users.noreply.github.com>
Date: Fri, 27 Mar 2026 14:31:19 +0000
Subject: [PATCH 22/22] fix: resolve mixed content and URL encoding issues in
web preview iframe
- decode URL-encoded paths in router history (proxy encodes ? as %3F)
- fix buildUrlWithPath to preserve query strings and enforce HTTPS
- upgrade Daytona dismiss fetch to HTTPS to prevent mixed content blocks
---
apps/web-v2/src/lib/components/PreviewNavBar.tsx | 5 +++--
apps/web-v2/src/lib/history.ts | 2 +-
apps/web-v2/src/lib/utils/dismissDaytonaWarning.ts | 7 +++++--
3 files changed, 9 insertions(+), 5 deletions(-)
diff --git a/apps/web-v2/src/lib/components/PreviewNavBar.tsx b/apps/web-v2/src/lib/components/PreviewNavBar.tsx
index df0535fe4..4b8cbcc74 100644
--- a/apps/web-v2/src/lib/components/PreviewNavBar.tsx
+++ b/apps/web-v2/src/lib/components/PreviewNavBar.tsx
@@ -23,8 +23,9 @@ function getPathFromUrl(fullUrl: string): string {
function buildUrlWithPath(baseUrl: string, path: string): string {
try {
const parsed = new URL(baseUrl);
- parsed.pathname = path.startsWith("/") ? path : `/${path}`;
- return parsed.toString();
+ if (parsed.protocol === "http:") parsed.protocol = "https:";
+ const fullPath = path.startsWith("/") ? path : `/${path}`;
+ return `${parsed.origin}${fullPath}`;
} catch {
return baseUrl;
}
diff --git a/apps/web-v2/src/lib/history.ts b/apps/web-v2/src/lib/history.ts
index 991e5d753..199bb81c5 100644
--- a/apps/web-v2/src/lib/history.ts
+++ b/apps/web-v2/src/lib/history.ts
@@ -87,7 +87,7 @@ function toDisplayHref(href: string): string {
export function createAppHistory() {
return createBrowserHistory({
parseLocation() {
- const raw = `${window.location.pathname}${window.location.search}${window.location.hash}`;
+ const raw = `${decodeURIComponent(window.location.pathname)}${window.location.search}${window.location.hash}`;
const internal = toInternalHref(raw);
const hashIndex = internal.indexOf("#");
diff --git a/apps/web-v2/src/lib/utils/dismissDaytonaWarning.ts b/apps/web-v2/src/lib/utils/dismissDaytonaWarning.ts
index acd951ddb..24e46d98c 100644
--- a/apps/web-v2/src/lib/utils/dismissDaytonaWarning.ts
+++ b/apps/web-v2/src/lib/utils/dismissDaytonaWarning.ts
@@ -1,10 +1,13 @@
const dismissed = new Set();
export async function dismissDaytonaWarning(url: string): Promise {
- const origin = new URL(url).origin;
+ const parsed = new URL(url);
+ if (parsed.protocol === "http:") parsed.protocol = "https:";
+ const httpsUrl = parsed.toString();
+ const origin = parsed.origin;
if (dismissed.has(origin)) return;
try {
- await fetch(url, {
+ await fetch(httpsUrl, {
method: "HEAD",
mode: "no-cors",
headers: { "X-Daytona-Skip-Preview-Warning": "true" },