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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions apps/web/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Geist, Geist_Mono } from "next/font/google"

import "@workspace/ui/globals.css"
import { ThemeProvider } from "@/components/theme-provider"
import { Providers} from "@/components/providers"
import { cn } from "@workspace/ui/lib/utils";

const geist = Geist({subsets:['latin'],variable:'--font-sans'})
Expand All @@ -23,7 +23,7 @@ export default function RootLayout({
className={cn("antialiased", fontMono.variable, "font-sans", geist.variable)}
>
<body>
<ThemeProvider>{children}</ThemeProvider>
<Providers>{children}</Providers>
</body>
</html>
)
Expand Down
29 changes: 16 additions & 13 deletions apps/web/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
import { Button } from "@workspace/ui/components/button"
import { add } from "@workspace/math/add"
"use client"

import { useMutation, useQuery } from 'convex/react'
import { api } from '@workspace/backend/_generated/api'
import { Button } from '@workspace/ui/components/button'

export default function Page() {

const users = useQuery(api.users.getMany)
const addUser = useMutation(api.users.add)


return (
<div className="flex min-h-svh p-6">
<div className="flex max-w-md min-w-0 flex-col gap-4 text-sm leading-loose">
<div>
<h1 className="font-medium">Project ready!</h1>
<p>{add(1, 2)}</p>
<Button className="mt-2">Button</Button>
</div>
<div className="text-muted-foreground font-mono text-xs">
(Press <kbd>d</kbd> to toggle dark mode)
</div>
</div>
<div className="flex flex-col items-center justify-center min-h-svh">
<Button onClick={()=>addUser()}>Add User</Button>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

fd -e tsx -e ts "page.tsx" apps/web/app/ | head -20

Repository: harshshukla2807/echo

Length of output: 85


🏁 Script executed:

cat -n apps/web/app/page.tsx

Repository: harshshukla2807/echo

Length of output: 798


🏁 Script executed:

# Search for useMutation patterns in the codebase to understand typical usage
rg "useMutation" --type tsx --type ts -A 5 -B 2 | head -100

Repository: harshshukla2807/echo

Length of output: 91


🌐 Web query:

Convex useMutation API behavior - does it return a promise that needs to be awaited

💡 Result:

Yes, the Convex useMutation API returns a promise that should be awaited if you need to handle the result or error. The useMutation hook returns a ReactMutation object, which is callable and returns Promise<FunctionReturnType> when invoked with arguments. Official documentation shows examples using await doSomething and .then/.catch for handling the promise resolution. Example from docs: const doSomething = useMutation(api.functions.doSomething); const onClick = => { const result = await doSomething; // Awaits the promise }; The type definition explicitly states: ReactMutation(...args): Promise<FunctionReturnType>.

Citations:


Await the mutation and add error handling to prevent silent failures and duplicate writes.

Line 15 calls addUser() without awaiting. Since useMutation returns a promise, the mutation fires asynchronously without error handling or duplicate-click prevention. This means mutation failures are silently ignored, and rapid clicks trigger multiple writes.

Add await, wrap in try/catch, and implement state-based duplicate prevention:

Suggested implementation
 import { useState } from 'react'
 import { useMutation, useQuery } from 'convex/react'
 import { api } from '@workspace/backend/_generated/api'
 import { Button } from '@workspace/ui/components/button'

 export default function Page() {
   const users = useQuery(api.users.getMany)
   const addUser = useMutation(api.users.add)
+  const [isAdding, setIsAdding] = useState(false)
+  const [error, setError] = useState<string | null>(null)
+
+  const handleAddUser = async () => {
+    if (isAdding) return
+    setIsAdding(true)
+    setError(null)
+    try {
+      await addUser()
+    } catch {
+      setError('Failed to add user. Please retry.')
+    } finally {
+      setIsAdding(false)
+    }
+  }

   return (
     <div className="flex flex-col items-center justify-center min-h-svh">
-      <Button onClick={()=>addUser()}>Add User</Button>
+      <Button onClick={handleAddUser} disabled={isAdding}>
+        {isAdding ? 'Adding...' : 'Add User'}
+      </Button>
+      {error ? <p role="alert">{error}</p> : null}
       <p>apps/Web</p>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<Button onClick={()=>addUser()}>Add User</Button>
import { useState } from 'react'
import { useMutation, useQuery } from 'convex/react'
import { api } from '@workspace/backend/_generated/api'
import { Button } from '@workspace/ui/components/button'
export default function Page() {
const users = useQuery(api.users.getMany)
const addUser = useMutation(api.users.add)
const [isAdding, setIsAdding] = useState(false)
const [error, setError] = useState<string | null>(null)
const handleAddUser = async () => {
if (isAdding) return
setIsAdding(true)
setError(null)
try {
await addUser()
} catch {
setError('Failed to add user. Please retry.')
} finally {
setIsAdding(false)
}
}
return (
<div className="flex flex-col items-center justify-center min-h-svh">
<Button onClick={handleAddUser} disabled={isAdding}>
{isAdding ? 'Adding...' : 'Add User'}
</Button>
{error ? <p role="alert">{error}</p> : null}
<p>apps/Web</p>
</div>
)
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/app/page.tsx` at line 15, The Button's onClick calls addUser()
without awaiting or error handling, allowing silent failures and duplicate
writes; update the handler to await the promise returned by the mutation
(addUser) inside an async function, wrap the call in try/catch to surface/log
errors, and prevent duplicate submissions by disabling the Button while the
mutation is in-flight (use the mutation's isLoading or local submitting state)
and avoid re-invoking addUser until the previous promise resolves.

<p>apps/Web</p>
<div className='max-w-sm w-full mx-auto'>
{JSON.stringify(users, null , 2)}
</div>
</div>
)
}
22 changes: 22 additions & 0 deletions apps/web/components/providers.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
"use client"

import * as React from "react"
import { ConvexProvider, ConvexReactClient } from "convex/react";
// import { ConvexProviderWithClerk } from 'convex/react-clerk'
// import { useAuth } from '@clerk/nextjs'

// if (!process.env.NEXT_PUBLIC_CONVEX_URL) {
// throw new Error('Missing NEXT_PUBLIC_CONVEX_URL in your .env file')
// }

const convex = new ConvexReactClient(process.env.NEXT_PUBLIC_CONVEX_URL || "");
Comment on lines +8 to +12
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Enable environment variable validation or use non-null assertion.

The fallback to an empty string (|| "") will cause a confusing runtime error if NEXT_PUBLIC_CONVEX_URL is missing. Per Convex guidelines (see packages/backend/convex/_generated/ai/guidelines.md:181-197), the recommended pattern uses a non-null assertion with proper validation.

🔧 Proposed fix: enable validation
-// if (!process.env.NEXT_PUBLIC_CONVEX_URL) {
-//   throw new Error('Missing NEXT_PUBLIC_CONVEX_URL in your .env file')
-// }
+if (!process.env.NEXT_PUBLIC_CONVEX_URL) {
+  throw new Error('Missing NEXT_PUBLIC_CONVEX_URL in your .env file')
+}

-const convex = new ConvexReactClient(process.env.NEXT_PUBLIC_CONVEX_URL || "");
+const convex = new ConvexReactClient(process.env.NEXT_PUBLIC_CONVEX_URL);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// if (!process.env.NEXT_PUBLIC_CONVEX_URL) {
// throw new Error('Missing NEXT_PUBLIC_CONVEX_URL in your .env file')
// }
const convex = new ConvexReactClient(process.env.NEXT_PUBLIC_CONVEX_URL || "");
if (!process.env.NEXT_PUBLIC_CONVEX_URL) {
throw new Error('Missing NEXT_PUBLIC_CONVEX_URL in your .env file')
}
const convex = new ConvexReactClient(process.env.NEXT_PUBLIC_CONVEX_URL);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/components/providers.tsx` around lines 8 - 12, The ConvexReactClient
is being constructed with a fallback empty string which yields confusing runtime
errors; instead validate NEXT_PUBLIC_CONVEX_URL up-front and pass a non-null
value to ConvexReactClient. Remove the `|| ""` and either re-enable the existing
validation block (throw an Error if process.env.NEXT_PUBLIC_CONVEX_URL is falsy)
or assert non-null when calling new
ConvexReactClient(process.env.NEXT_PUBLIC_CONVEX_URL!), ensuring the check runs
before creating the `convex` instance so `ConvexReactClient` receives a valid
URL.


export function Providers({ children }: { children: React.ReactNode }) {
return (
<ConvexProvider client={convex}>{children}</ConvexProvider>

// <ConvexProviderWithClerk client={convex} useAuth={useAuth}>
// {children}
// </ConvexProviderWithClerk>
);
};
71 changes: 0 additions & 71 deletions apps/web/components/theme-provider.tsx

This file was deleted.

2 changes: 2 additions & 0 deletions apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@workspace/backend": "workspace:*",
"@workspace/math": "workspace:*",
"@workspace/ui": "workspace:*",
"convex": "^1.35.1",
"lucide-react": "^1.8.0",
"next": "16.1.6",
"next-themes": "^0.4.6",
Expand Down
3 changes: 2 additions & 1 deletion apps/web/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
"paths": {
"@/*": ["./*"],
"@workspace/ui/*": ["../../packages/ui/src/*"],
"@workspace/math/*": ["../../packages/math/src/*"]
"@workspace/math/*": ["../../packages/math/src/*"],
"@workspace/backend/*": ["../../packages/backend/convex/*"],
},
"plugins": [
{
Expand Down
4 changes: 2 additions & 2 deletions apps/widget/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Geist, Geist_Mono } from "next/font/google"

import "@workspace/ui/styles/globals.css"
import { ThemeProvider } from "@/components/theme-provider"
import { Providers } from "@/components/providers"
import { cn } from "@workspace/ui/lib/utils";

const geist = Geist({subsets:['latin'],variable:'--font-sans'})
Expand All @@ -23,7 +23,7 @@ export default function RootLayout({
className={cn("antialiased", fontMono.variable, "font-sans", geist.variable)}
>
<body>
<ThemeProvider>{children}</ThemeProvider>
<Providers>{children}</Providers>
</body>
</html>
)
Expand Down
32 changes: 15 additions & 17 deletions apps/widget/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,21 @@
import { Button } from "@workspace/ui/components/button"
import {add } from "@workspace/math/add"
import { Input } from "@workspace/ui/components/input"
"use client"

import { useMutation, useQuery } from 'convex/react'
import { api } from '@workspace/backend/_generated/api'
import { Button } from '@workspace/ui/components/button'

export default function Page() {

const users = useQuery(api.users.getMany)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Handle loading state for useQuery.

useQuery returns undefined while loading. Without a loading check, the UI will briefly show nothing (or "undefined") before data arrives.

🔧 Proposed fix: add loading state
   const users = useQuery(api.users.getMany)
   const addUser = useMutation(api.users.add)

   return (
     <div className="flex flex-col items-center justify-center min-h-svh">
           <Button onClick={()=>addUser()}>Add User</Button>
      <p>apps/widget</p>
      <div className='max-w-sm w-full mx-auto'>
-     {JSON.stringify(users, null , 2)}
+     {users === undefined ? "Loading..." : JSON.stringify(users, null, 2)}
      </div>
     </div>
   )

Also applies to: 17-17

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/widget/app/page.tsx` at line 9, The component calls
useQuery(api.users.getMany) and assigns it to users but doesn’t handle the
loading case where users is undefined; update the render logic around the users
variable (the useQuery call and any mapping/consumption of users) to check for a
falsy/undefined value and show a loading state (spinner, "Loading..." text, or
skeleton) until users is populated, e.g., guard usages of users before mapping
or accessing properties so the UI does not render "undefined" while the query is
in-flight.

const addUser = useMutation(api.users.add)

return (
<div className="flex min-h-svh p-6">
<div className="flex max-w-md min-w-0 flex-col gap-4 text-sm leading-loose">
<div>
<h1 className="font-medium">Project ready!</h1>
<p>You may now add components and start building.</p>
<p>We&apos;ve already added the button component for you.</p>
<Button className="mt-2">Button</Button>
</div>
<div className="text-muted-foreground font-mono text-xs">
(Press <kbd>d</kbd> to toggle dark mode)
<p>{add(1, 2)}</p>
</div>
<Input/>
</div>
<div className="flex flex-col items-center justify-center min-h-svh">
<Button onClick={()=>addUser()}>Add User</Button>
<p>apps/widget</p>
<div className='max-w-sm w-full mx-auto'>
{JSON.stringify(users, null , 2)}
</div>
</div>
)
}
22 changes: 22 additions & 0 deletions apps/widget/components/providers.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
"use client"

import * as React from "react"
import { ConvexProvider, ConvexReactClient } from "convex/react";
// import { ConvexProviderWithClerk } from 'convex/react-clerk'
// import { useAuth } from '@clerk/nextjs'

// if (!process.env.NEXT_PUBLIC_CONVEX_URL) {
// throw new Error('Missing NEXT_PUBLIC_CONVEX_URL in your .env file')
// }

const convex = new ConvexReactClient(process.env.NEXT_PUBLIC_CONVEX_URL || "");
Comment on lines +8 to +12
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Enable environment variable validation or use non-null assertion.

Same issue as apps/web/components/providers.tsx. The fallback to empty string will cause unclear runtime errors. Per Convex guidelines, use proper validation.

🔧 Proposed fix: enable validation
-// if (!process.env.NEXT_PUBLIC_CONVEX_URL) {
-//   throw new Error('Missing NEXT_PUBLIC_CONVEX_URL in your .env file')
-// }
+if (!process.env.NEXT_PUBLIC_CONVEX_URL) {
+  throw new Error('Missing NEXT_PUBLIC_CONVEX_URL in your .env file')
+}

-const convex = new ConvexReactClient(process.env.NEXT_PUBLIC_CONVEX_URL || "");
+const convex = new ConvexReactClient(process.env.NEXT_PUBLIC_CONVEX_URL);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// if (!process.env.NEXT_PUBLIC_CONVEX_URL) {
// throw new Error('Missing NEXT_PUBLIC_CONVEX_URL in your .env file')
// }
const convex = new ConvexReactClient(process.env.NEXT_PUBLIC_CONVEX_URL || "");
if (!process.env.NEXT_PUBLIC_CONVEX_URL) {
throw new Error('Missing NEXT_PUBLIC_CONVEX_URL in your .env file')
}
const convex = new ConvexReactClient(process.env.NEXT_PUBLIC_CONVEX_URL);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/widget/components/providers.tsx` around lines 8 - 12, The code currently
constructs convex with ConvexReactClient(process.env.NEXT_PUBLIC_CONVEX_URL ||
"") which hides missing-ENV errors; update provider initialization to validate
the env var first (or use a non-null assertion) by checking
process.env.NEXT_PUBLIC_CONVEX_URL and throwing a clear Error if missing, then
pass the validated value into ConvexReactClient (reference: ConvexReactClient
and the convex variable) so runtime failures show an explicit message instead of
using an empty-string fallback.


export function Providers({ children }: { children: React.ReactNode }) {
return (
<ConvexProvider client={convex}>{children}</ConvexProvider>

// <ConvexProviderWithClerk client={convex} useAuth={useAuth}>
// {children}
// </ConvexProviderWithClerk>
);
};
71 changes: 0 additions & 71 deletions apps/widget/components/theme-provider.tsx

This file was deleted.

2 changes: 2 additions & 0 deletions apps/widget/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@workspace/backend": "workspace:*",
"@workspace/math": "workspace:*",
"@workspace/ui": "workspace:*",
"convex": "^1.35.1",
"lucide-react": "^1.8.0",
"next": "16.1.6",
"next-themes": "^0.4.6",
Expand Down
3 changes: 2 additions & 1 deletion apps/widget/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
"paths": {
"@/*": ["./*"],
"@workspace/ui": ["../../packages/ui"],
"@workspace/ui/*": ["../../packages/ui/src/*"]
"@workspace/ui/*": ["../../packages/ui/src/*"],
"@workspace/backend/*": ["../../packages/backend/convex/*"],
},
"plugins": [
{
Expand Down
2 changes: 2 additions & 0 deletions packages/backend/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@

.env.local
Loading