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.

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

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.

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