Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
525c063
Custom implementation: Google OAuth authentication and AI Standard br…
Mar 1, 2026
6fa0abc
Fix pnpm lockfile for Vercel deployment
Mar 1, 2026
2f68c5e
Add files via upload
fairenow Mar 1, 2026
ec0fd54
Fix Vercel build: replace @vercel/remix imports with @remix-run/node
claude Mar 1, 2026
79d1698
Fix Google OAuth authentication in Convex backend
Mar 1, 2026
5899106
Fix: Use globalThis.process.env for GOOGLE_CLIENT_SECRET
Mar 1, 2026
aadac34
Add Convex OAuth for dashboard/team access
Mar 1, 2026
a30e8c7
Fix: Replace lucide-react icons with @radix-ui/react-icons
Mar 1, 2026
8d14cea
Fix: Correct Button import path
Mar 1, 2026
7bad491
Trigger redeploy for VITE_CONVEX_OAUTH_CLIENT_ID env var
Mar 1, 2026
9b6ca6d
Fix dashboard API calls to use Convex OAuth tokens
Mar 1, 2026
4a94464
Add phase-wise generation, review cycles, and fix Convex OAuth integr…
Mar 4, 2026
94d7e83
Fix Convex OAuth dashboard callback endpoint
Mar 4, 2026
5861713
Fix OAuth token endpoint - use api.convex.dev instead of auth.convex.dev
Mar 4, 2026
7ceafe9
Disable dashboard API calls for third-party OAuth apps
Mar 4, 2026
8862393
Fix Google profile images and auto-select personal team
Mar 4, 2026
070cdba
Disable dashboard API calls and add Vercel production setup
Mar 4, 2026
2b5eb5f
Fix: Route projects to correct Convex team
Mar 4, 2026
7097ec7
Fix: Critical chatIdStore initialization error and LaunchDarkly errors
Mar 4, 2026
4667ebb
Add comprehensive console errors documentation
Mar 4, 2026
d94a61c
Fix Convex provisioning auth flow and team slug selection
Mar 12, 2026
47b7b69
Retry Convex project provisioning with fallback auth token
Mar 12, 2026
8216f3b
Merge branch 'get-convex:main' into codex/fix-provisioning-401-token-…
fairenow Mar 12, 2026
cf8f3fd
Fix homepage chatId race and enforce dashboard token provisioning
Mar 12, 2026
99daf18
Add files via upload
fairenow Mar 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 52 additions & 63 deletions .cursor/rules/convex_rules.mdc
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import { query } from "./_generated/server";
import { v } from "convex/values";
export const f = query({
args: {},
returns: v.null(),
handler: async (ctx, args) => {
// Function body
},
Expand Down Expand Up @@ -71,20 +70,6 @@ export default defineSchema({
)
});
```
- Always use the `v.null()` validator when returning a null value. Below is an example query that returns a null value:
```typescript
import { query } from "./_generated/server";
import { v } from "convex/values";

export const exampleQuery = query({
args: {},
returns: v.null(),
handler: async (ctx, args) => {
console.log("This query returns a null value");
return null;
},
});
```
- Here are the valid Convex types along with their respective validators:
Convex Type | TS/JS type | Example Usage | Validator for argument validation and schemas | Notes |
| ----------- | ------------| -----------------------| -----------------------------------------------| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
Expand All @@ -103,7 +88,7 @@ Convex Type | TS/JS type | Example Usage | Validator for argument val
- Use `internalQuery`, `internalMutation`, and `internalAction` to register internal functions. These functions are private and aren't part of an app's API. They can only be called by other Convex functions. These functions are always imported from `./_generated/server`.
- Use `query`, `mutation`, and `action` to register public functions. These functions are part of the public API and are exposed to the public Internet. Do NOT use `query`, `mutation`, or `action` to register sensitive internal functions that should be kept private.
- You CANNOT register a function through the `api` or `internal` objects.
- ALWAYS include argument and return validators for all Convex functions. This includes all of `query`, `internalQuery`, `mutation`, `internalMutation`, `action`, and `internalAction`. If a function doesn't return anything, include `returns: v.null()` as its output validator.
- ALWAYS include argument validators for all Convex functions. This includes all of `query`, `internalQuery`, `mutation`, `internalMutation`, `action`, and `internalAction`.
- If the JavaScript implementation of a Convex function doesn't have a return value, it implicitly returns `null`.

### Function calling
Expand All @@ -117,15 +102,13 @@ Convex Type | TS/JS type | Example Usage | Validator for argument val
```
export const f = query({
args: { name: v.string() },
returns: v.string(),
handler: async (ctx, args) => {
return "Hello " + args.name;
},
});

export const g = query({
args: {},
returns: v.null(),
handler: async (ctx, args) => {
const result: string = await ctx.runQuery(api.example.f, { name: "Bob" });
return null;
Expand Down Expand Up @@ -159,7 +142,7 @@ export const listWithExtraArg = query({
handler: async (ctx, args) => {
return await ctx.db
.query("messages")
.filter((q) => q.eq(q.field("author"), args.author))
.withIndex("by_author", (q) => q.eq("author", args.author))
.order("desc")
.paginate(args.paginationOpts);
},
Expand All @@ -169,9 +152,9 @@ Note: `paginationOpts` is an object with the following properties:
- `numItems`: the maximum number of documents to return (the validator is `v.number()`)
- `cursor`: the cursor to use to fetch the next page of documents (the validator is `v.union(v.string(), v.null())`)
- A query that ends in `.paginate()` returns an object that has the following properties:
- page (contains an array of documents that you fetches)
- isDone (a boolean that represents whether or not this is the last page of documents)
- continueCursor (a string that represents the cursor to use to fetch the next page of documents)
- page (contains an array of documents that you fetches)
- isDone (a boolean that represents whether or not this is the last page of documents)
- continueCursor (a string that represents the cursor to use to fetch the next page of documents)


## Validator guidelines
Expand All @@ -194,11 +177,10 @@ import { Doc, Id } from "./_generated/dataModel";

export const exampleQuery = query({
args: { userIds: v.array(v.id("users")) },
returns: v.record(v.id("users"), v.string()),
handler: async (ctx, args) => {
const idToUsername: Record<Id<"users">, string> = {};
for (const userId of args.userIds) {
const user = await ctx.db.get(userId);
const user = await ctx.db.get("users", userId);
if (user) {
idToUsername[user._id] = user.username;
}
Expand All @@ -212,7 +194,6 @@ export const exampleQuery = query({
- Always use `as const` for string literals in discriminated union types.
- When using the `Array` type, make sure to always define your arrays as `const array: Array<T> = [...];`
- When using the `Record` type, make sure to always define your records as `const record: Record<KeyType, ValueType> = {...};`
- Always add `@types/node` to your `package.json` when using any Node.js built-in modules.

## Full text search guidelines
- A query for "10 messages in channel '#general' that best match the query 'hello hi' in their body" would look like:
Expand All @@ -236,19 +217,20 @@ const messages = await ctx.db


## Mutation guidelines
- Use `ctx.db.replace` to fully replace an existing document. This method will throw an error if the document does not exist.
- Use `ctx.db.patch` to shallow merge updates into an existing document. This method will throw an error if the document does not exist.
- Use `ctx.db.replace` to fully replace an existing document. This method will throw an error if the document does not exist. Syntax: `await ctx.db.replace('tasks', taskId, { name: 'Buy milk', completed: false })`
- Use `ctx.db.patch` to shallow merge updates into an existing document. This method will throw an error if the document does not exist. Syntax: `await ctx.db.patch('tasks', taskId, { completed: true })`

## Action guidelines
- Always add `"use node";` to the top of files containing actions that use Node.js built-in modules.
- Never add `"use node";` to a file that also exports queries or mutations. Only actions can run in the Node.js runtime; queries and mutations must stay in the default Convex runtime. If you need Node.js built-ins alongside queries or mutations, put the action in a separate file.
- `fetch()` is available in the default Convex runtime. You do NOT need `"use node";` just to use `fetch()`.
- Never use `ctx.db` inside of an action. Actions don't have access to the database.
- Below is an example of the syntax for an action:
```ts
import { action } from "./_generated/server";

export const exampleAction = action({
args: {},
returns: v.null(),
handler: async (ctx, args) => {
console.log("This action does not return anything");
return null;
Expand All @@ -268,7 +250,6 @@ import { internalAction } from "./_generated/server";

const empty = internalAction({
args: {},
returns: v.null(),
handler: async (ctx, args) => {
console.log("empty");
},
Expand All @@ -290,7 +271,7 @@ export default crons;
- The `ctx.storage.getUrl()` method returns a signed URL for a given file. It returns `null` if the file doesn't exist.
- Do NOT use the deprecated `ctx.storage.getMetadata` call for loading a file's metadata.

Instead, query the `_storage` system table. For example, you can use `ctx.db.system.get` to get an `Id<"_storage">`.
Instead, query the `_storage` system table. For example, you can use `ctx.db.system.get` to get an `Id<"_storage">`.
```
import { query } from "./_generated/server";
import { Id } from "./_generated/dataModel";
Expand All @@ -305,9 +286,8 @@ type FileMetadata = {

export const exampleQuery = query({
args: { fileId: v.id("_storage") },
returns: v.null(),
handler: async (ctx, args) => {
const metadata: FileMetadata | null = await ctx.db.system.get(args.fileId);
const metadata: FileMetadata | null = await ctx.db.system.get("_storage", args.fileId);
console.log(metadata);
return null;
},
Expand Down Expand Up @@ -434,8 +414,8 @@ Internal Functions:
"description": "This example shows how to build a chat app without authentication.",
"version": "1.0.0",
"dependencies": {
"convex": "^1.17.4",
"openai": "^4.79.0"
"convex": "^1.31.2",
"openai": "^6.0.0"
},
"devDependencies": {
"typescript": "^5.7.3"
Expand Down Expand Up @@ -480,47 +460,36 @@ import OpenAI from "openai";
import { internal } from "./_generated/api";

/**
* Create a user with a given name.
*/
* Create a user with a given name.
*/
export const createUser = mutation({
args: {
name: v.string(),
},
returns: v.id("users"),
handler: async (ctx, args) => {
return await ctx.db.insert("users", { name: args.name });
},
});

/**
* Create a channel with a given name.
*/
* Create a channel with a given name.
*/
export const createChannel = mutation({
args: {
name: v.string(),
},
returns: v.id("channels"),
handler: async (ctx, args) => {
return await ctx.db.insert("channels", { name: args.name });
},
});

/**
* List the 10 most recent messages from a channel in descending creation order.
*/
* List the 10 most recent messages from a channel in descending creation order.
*/
export const listMessages = query({
args: {
channelId: v.id("channels"),
},
returns: v.array(
v.object({
_id: v.id("messages"),
_creationTime: v.number(),
channelId: v.id("channels"),
authorId: v.optional(v.id("users")),
content: v.string(),
}),
),
handler: async (ctx, args) => {
const messages = await ctx.db
.query("messages")
Expand All @@ -532,15 +501,14 @@ export const listMessages = query({
});

/**
* Send a message to a channel and schedule a response from the AI.
*/
* Send a message to a channel and schedule a response from the AI.
*/
export const sendMessage = mutation({
args: {
channelId: v.id("channels"),
authorId: v.id("users"),
content: v.string(),
},
returns: v.null(),
handler: async (ctx, args) => {
const channel = await ctx.db.get(args.channelId);
if (!channel) {
Expand Down Expand Up @@ -568,7 +536,6 @@ export const generateResponse = internalAction({
args: {
channelId: v.id("channels"),
},
returns: v.null(),
handler: async (ctx, args) => {
const context = await ctx.runQuery(internal.index.loadContext, {
channelId: args.channelId,
Expand All @@ -593,12 +560,6 @@ export const loadContext = internalQuery({
args: {
channelId: v.id("channels"),
},
returns: v.array(
v.object({
role: v.union(v.literal("user"), v.literal("assistant")),
content: v.string(),
}),
),
handler: async (ctx, args) => {
const channel = await ctx.db.get(args.channelId);
if (!channel) {
Expand Down Expand Up @@ -634,7 +595,6 @@ export const writeAgentResponse = internalMutation({
channelId: v.id("channels"),
content: v.string(),
},
returns: v.null(),
handler: async (ctx, args) => {
await ctx.db.insert("messages", {
channelId: args.channelId,
Expand Down Expand Up @@ -667,9 +627,38 @@ export default defineSchema({
});
```

#### convex/tsconfig.json
```typescript
{
/* This TypeScript project config describes the environment that
* Convex functions run in and is used to typecheck them.
* You can modify it, but some settings required to use Convex.
*/
"compilerOptions": {
/* These settings are not required by Convex and can be modified. */
"allowJs": true,
"strict": true,
"moduleResolution": "Bundler",
"jsx": "react-jsx",
"skipLibCheck": true,
"allowSyntheticDefaultImports": true,

/* These compiler options are required by Convex */
"target": "ESNext",
"lib": ["ES2021", "dom"],
"forceConsistentCasingInFileNames": true,
"module": "ESNext",
"isolatedModules": true,
"noEmit": true
},
"include": ["./**/*"],
"exclude": ["./_generated"]
}
```

#### src/App.tsx
```typescript
export default function App() {
return <div>Hello World</div>;
}
```
```
7 changes: 7 additions & 0 deletions .env.development
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,10 @@ VITE_WORKOS_REDIRECT_URI=http://127.0.0.1:5173
VITE_WORKOS_API_HOSTNAME=apiauth.convex.dev
DISABLE_USAGE_REPORTING=1
DISABLE_BEDROCK=1

# Model Provider API Keys (set at least one)
# XAI_API_KEY=your_xai_api_key_here
# GOOGLE_API_KEY=your_google_api_key_here
# ANTHROPIC_API_KEY=your_anthropic_api_key_here
# OPENAI_API_KEY=your_openai_api_key_here
# GOOGLE_VERTEX_CREDENTIALS_JSON={"your":"json","credentials":"here"}
16 changes: 16 additions & 0 deletions .env.local.additions
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Add these lines to your .env.local file to fix console errors:

# REQUIRED: Specify your Convex team slug so projects are created under the correct team
VITE_DEFAULT_TEAM_SLUG=ramon-williams

# LaunchDarkly (optional - disable by not setting this)
# VITE_LD_CLIENT_SIDE_ID=your_launchdarkly_client_id_here

# Or to disable LaunchDarkly entirely, leave it commented out
# LaunchDarkly is used for feature flags but not required for core functionality

# Sentry (optional - for error tracking)
# SENTRY_AUTH_TOKEN=your_sentry_auth_token_here

# The PostHog key you have should work, but if you see 401 errors, verify it's correct:
# VITE_POSTHOG_KEY=phc_NWQlkY67cr90RUzVIpgz67chtiz719ApjWj3HCoJ4nD
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,4 @@ template/pnpm-lock.yaml
.env*.local

temp_envars
.env.production
13 changes: 13 additions & 0 deletions .mcp.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"mcpServers": {
"convex": {
"command": "npx",
"args": [
"-y",
"convex@latest",
"mcp",
"start"
]
}
}
}
Binary file added 9f682cb9-3cc7-434a-8d2b-6828d427574b.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading