Skip to content

Conversation

sosweetham
Copy link
Member

@sosweetham sosweetham commented Sep 2, 2025

Description of change

adds the dream sync metastate platform to the codebase

Issue Number

n/a

Type of change

  • New (a change which implements a new feature)

How the change has been tested

n/a

Change checklist

  • I have ensured that the CI Checks pass locally
  • I have removed any unnecessary logic
  • My code is well documented
  • I have signed my commits
  • My code follows the pattern of the application
  • I have self reviewed my code

Summary by CodeRabbit

  • New Features
    • Introduces the DreamSync web app with authentication (sign in/up/out).
    • Dashboard showcasing latest matches, AI suggestions, and quick actions.
    • Full profile management (basic info, skills, interests) and progress indicator.
    • Wishlist: create/edit/delete items, detail pages with AI-recommended people and groups.
    • Matches and Suggestions pages with pagination, actions (connect/join/delete), and detail modals.
    • Groups directory with search, filters, tabs (Suggested/All/My), and join flow.
    • Responsive navigation, rich theming, toasts, and mobile-friendly UI.
    • “Find Matches” modal for tailored discovery.

@sosweetham sosweetham requested a review from coodos as a code owner September 2, 2025 06:14
Copy link
Contributor

coderabbitai bot commented Sep 2, 2025

Walkthrough

Introduces DreamSync as a full-stack app. Adds client (React + Vite + Tailwind), UI kit (Radix/shadcn), auth-aware routing, pages (dashboard, matches, suggestions, groups, wishlist), modals, hooks, query client, and utilities. Implements Express server with routes, storage (Drizzle), Postgres schema, seeds, OpenAI-powered suggestions/matching, and Vite integration.

Changes

Cohort / File(s) Summary
Project setup & config
platforms/dreamSync/package.json, .../tsconfig.json, .../tailwind.config.ts, .../postcss.config.js, .../vite.config.ts, .../drizzle.config.ts, .../components.json, platforms/dreamSync/.gitignore
Adds build/dev tooling, Tailwind setup, Vite config, Drizzle config, component registry, TS paths, and ignore rules.
Client entry & assets
.../client/index.html, .../client/src/main.tsx, .../client/src/index.css
Adds HTML entrypoint, React root bootstrap, and theme/styles with Tailwind and Radix overrides.
App shell, hooks, utilities
.../client/src/App.tsx, .../client/src/components/navigation.tsx, .../client/src/components/ui/toaster.tsx, .../client/src/hooks/useAuth.ts, .../client/src/hooks/use-toast.ts, .../client/src/hooks/use-mobile.tsx, .../client/src/lib/queryClient.ts, .../client/src/lib/utils.ts, .../client/src/lib/authUtils.ts
Introduces auth-aware routing, navigation, toast system, mobile detection, query client, class utility, API helper, and unauthorized checker.
UI component library (shadcn/Radix)
.../client/src/components/ui/*
Adds comprehensive UI primitives: accordion, alert, alert-dialog, aspect-ratio, avatar, badge, breadcrumb, button, calendar, card, carousel, chart, checkbox, collapsible, command, context-menu, dialog, drawer, dropdown-menu, form, hover-card, input, input-otp, label, menubar, navigation-menu, pagination, popover, progress, radio-group, resizable, scroll-area, select, separator, sheet, sidebar, skeleton, slider, switch, table, tabs, textarea, toast, toaster, toggle, toggle-group, tooltip.
Domain components
.../client/src/components/add-wishlist-modal.tsx, .../components/auth-modal.tsx, .../components/find-matches-modal.tsx, .../components/group-card.tsx, .../components/profile-completion.tsx, .../components/user-card.tsx, .../components/wish-user-card.tsx
Adds modals and cards for wishlist, auth, finding matches, groups, profile progress, user match cards, and wish-user cards with mutations and toasts.
Pages
.../client/src/pages/dashboard.tsx, .../pages/login.tsx, .../pages/matches-new.tsx, .../pages/suggestions.tsx, .../pages/groups.tsx, .../pages/wishlist.tsx, .../pages/wishlist-item.tsx, .../pages/not-found.tsx
Implements feature pages: dashboard, auth, matches, suggestions, groups, wishlist list/detail, and 404; data fetching/mutations via React Query.
Server core & routing
.../server/index.ts, .../server/routes.ts, .../server/vite.ts
Boots Express, registers routes, error/logging middleware, Vite dev/static serving.
Storage & DB
.../server/db.ts, .../server/storage.ts, .../shared/schema.ts
Configures Neon/Drizzle, defines schema, relations, insert schemas, and storage interface/implementation.
Authentication
.../server/replitAuth.ts, .../server/auth.ts
Adds Replit OIDC and local auth flows with sessions, Passport strategies, and middleware.
AI services
.../server/openai.ts, .../server/aiSuggestionsService.ts
Adds OpenAI-powered match search and wish analysis with file exports, uploads, JSON responses, and persistence.
Seeding & maintenance
.../server/seedData.ts, .../server/seedComprehensiveData.ts, .../server/seedEnhancedData.ts, .../server/seedExtensiveData.ts, .../server/ensureFullyPopulatedData.ts, .../server/fixPasswords.ts, .../server/generateTestSuggestions.ts
Adds scripts to seed and enrich users, profiles, skills, interests, groups, wishes, matches, suggestions, and fix passwords.
Sample data
platforms/dreamSync/user_wishes.json
Adds example wish records JSON.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor U as User
  participant C as Client (App)
  participant Q as QueryClient
  participant S as Server (Express)
  participant A as Auth Provider

  U->>C: Visit /
  C->>Q: useAuth() fetch /api/auth/user
  Q->>S: GET /api/auth/user
  alt Authenticated
    S-->>Q: 200 User
    Q-->>C: user
    C->>C: Render Dashboard
  else Unauthenticated
    S-->>Q: 401
    Q-->>C: error
    C->>U: Toast + Redirect /api/login
    U->>A: Login (OIDC/Local)
    A-->>S: Callback -> session
    S-->>U: Redirect to /
  end
Loading
sequenceDiagram
  autonumber
  actor U as User
  participant C as FindMatchesModal
  participant Q as React Query
  participant S as /api/matches/find
  participant DB as Storage

  U->>C: Open modal, set filters
  U->>C: Click "Find Matches"
  C->>Q: mutate POST /api/matches/find
  Q->>S: POST criteria
  S->>DB: Compute & persist matches
  S-->>Q: Summary (counts)
  Q-->>C: onSuccess
  C->>U: Toast (# matches)
  C->>C: Close modal, navigate /matches
Loading
sequenceDiagram
  autonumber
  actor U as User
  participant W as AddWishlistModal
  participant Q as React Query
  participant S as /api/wishlist
  participant AI as AI Suggestion Service

  U->>W: Enter item and submit
  alt Create
    W->>Q: mutate POST /api/wishlist
    Q->>S: Create item
    S->>AI: analyzeNewWish (async)
    S-->>Q: 201 item
  else Edit
    W->>Q: mutate PUT /api/wishlist/:id
    Q->>S: Update item
    S-->>Q: 200 item
  end
  Q-->>W: onSuccess (invalidate /api/profile)
  W->>U: Toast + Close + Optional redirect
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120+ minutes

Suggested labels

enhancement

Poem

A rabbit taps keys in a neon-lit night,
Spinning routes, schemas, and gradients bright.
Modals pop softly, toasts chime with cheer,
Matches and wishes now shimmer near.
From seed to OpenAI’s clever glance—
DreamSync hops forward. Onward, advance! 🐇✨

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/dream-sync

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore or @coderabbit ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 135

Comment on lines +1 to +6
node_modules
dist
.DS_Store
server/public
vite.config.ts.*
*.tar.gz No newline at end of file
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add .env ignores to prevent secrets leakage.
These often slip into commits; keep an example file tracked.

Apply:

 node_modules
 dist
 .DS_Store
 server/public
 vite.config.ts.*
 *.tar.gz
+# env files (keep examples)
+.env
+.env.local
+.env.*.local
+!.env.example
📝 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
node_modules
dist
.DS_Store
server/public
vite.config.ts.*
*.tar.gz
node_modules
dist
.DS_Store
server/public
vite.config.ts.*
*.tar.gz
# env files (keep examples)
.env
.env.local
.env.*.local
!.env.example
🤖 Prompt for AI Agents
In platforms/dreamSync/.gitignore lines 1-6, add rules to ignore environment
files (e.g., .env, .env.local, .env.*.local and other env variants) to prevent
secrets from being committed, while ensuring an example file like .env.example
remains tracked; update the file by appending the appropriate .env ignore
patterns and verify .env.example is not matched by those patterns.

<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1" />
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Don’t restrict zoom; it hurts accessibility.

Removing maximum-scale=1 improves compliance (users can zoom).

-    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1" />
+    <meta name="viewport" content="width=device-width, initial-scale=1" />
📝 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
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
🤖 Prompt for AI Agents
In platforms/dreamSync/client/index.html around line 5, the meta viewport tag
sets maximum-scale=1 which prevents user zoom and harms accessibility; remove
the maximum-scale=1 (and any related user-scalable=no) from the content
attribute so users can zoom, leaving something like width=device-width,
initial-scale=1.0 to preserve responsive behavior while restoring accessibility.

Comment on lines +73 to +76
<p className="font-medium">{user?.firstName} {user?.lastName}</p>
<p className="w-[200px] truncate text-sm text-muted-foreground">
TestUser
</p>
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Remove hard-coded “TestUser”; show real user data.
User-facing placeholder leaked into UI.

-                    <p className="w-[200px] truncate text-sm text-muted-foreground">
-                      TestUser
-                    </p>
+                    <p className="w-[200px] truncate text-sm text-muted-foreground">
+                      {user?.email ?? ""}
+                    </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
<p className="font-medium">{user?.firstName} {user?.lastName}</p>
<p className="w-[200px] truncate text-sm text-muted-foreground">
TestUser
</p>
<p className="font-medium">{user?.firstName} {user?.lastName}</p>
<p className="w-[200px] truncate text-sm text-muted-foreground">
{user?.email ?? ""}
</p>
🤖 Prompt for AI Agents
In platforms/dreamSync/client/src/components/navigation.tsx around lines 73 to
76, the second <p> currently contains a hard-coded "TestUser"; replace that with
the actual user property (e.g., user?.email or user?.username) and keep the
w-[200px] truncate text-sm text-muted-foreground classes for styling and
truncation; also provide a sensible fallback (e.g., an empty string or "Unknown
user") when the user property is undefined so the UI never shows the hard-coded
placeholder.

Comment on lines +45 to +47
label: "Skills & Hobbies",
completed: !!(userProfile?.skills?.length || userProfile?.hobbies?.length),
},
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Type mismatch: userProfile.hobbies is not in UserWithProfile; use interests (per schema).

Switch to interests to align with the shared type.

-      label: "Skills & Hobbies",
-      completed: !!(userProfile?.skills?.length || userProfile?.hobbies?.length),
+      label: "Skills & Interests",
+      completed: !!(userProfile?.skills?.length || userProfile?.interests?.length),
📝 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
label: "Skills & Hobbies",
completed: !!(userProfile?.skills?.length || userProfile?.hobbies?.length),
},
label: "Skills & Interests",
completed: !!(userProfile?.skills?.length || userProfile?.interests?.length),
},
🤖 Prompt for AI Agents
In platforms/dreamSync/client/src/components/profile-completion.tsx around lines
45 to 47, the code checks userProfile.hobbies which is not defined on
UserWithProfile per the schema; replace the hobbies reference with interests and
keep the same null-safe length check (e.g., userProfile?.interests?.length) so
completed uses skills or interests instead of the nonexistent hobbies field.


const AspectRatio = AspectRatioPrimitive.Root

export { AspectRatio }
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Verify export style consistency and barrel wiring.

Ensure other UI primitives follow the same named-export pattern and that a barrel (if any) re-exports AspectRatio.


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 1) Is AspectRatio re-exported from any barrel under components/ui?
fd -t f -a '^index\.(ts|tsx)$' platforms/dreamSync/client/src/components/ui \
  -x rg -n 'export\s+{\s*AspectRatio\b' {} \; || true

# 2) Quick scan of export styles for other UI components to ensure consistency.
rg -nP -C1 --glob '!**/node_modules/**' \
  'export\s+(default|{)\s*' platforms/dreamSync/client/src/components/ui | head -n 100

Length of output: 5714


Add AspectRatio to UI barrel exports
No barrel file under platforms/dreamSync/client/src/components/ui currently re-exports AspectRatio; update or create index.ts/index.tsx there and include

export { AspectRatio }

alongside the other named exports.

🤖 Prompt for AI Agents
In platforms/dreamSync/client/src/components/ui around line 5, the AspectRatio
component is not exported from the UI barrel; update or create the barrel file
platforms/dreamSync/client/src/components/ui/index.ts or index.tsx and add a
named re-export for AspectRatio (using the correct relative path to the
AspectRatio module) alongside the other UI exports so consumers can import {
AspectRatio } from '.../components/ui'.

Comment on lines +262 to +265
userId: user.id,
targetType: Math.random() > 0.5 ? "user" : "group",
targetId: currentUsers[Math.floor(Math.random() * currentUsers.length)].id,
reason: suggestion,
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Bug: suggestions.targetId points to a user even when targetType === "group".

This violates the schema contract and can break consumers.

- await db.insert(suggestions).values({
-   userId: user.id,
-   targetType: Math.random() > 0.5 ? "user" : "group",
-   targetId: currentUsers[Math.floor(Math.random() * currentUsers.length)].id,
+ const targetType = Math.random() > 0.5 ? "user" : "group";
+ const targetId =
+   targetType === "user"
+     ? getRandomItem(currentUsers.filter(u => u.id !== user.id)).id
+     : getRandomItem(currentGroups).id;
+ await db.insert(suggestions).values({
+   userId: user.id,
+   targetType,
+   targetId,
    reason: suggestion,
    score: Math.floor(Math.random() * 30) + 70,
    status: "pending",
 });
📝 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
userId: user.id,
targetType: Math.random() > 0.5 ? "user" : "group",
targetId: currentUsers[Math.floor(Math.random() * currentUsers.length)].id,
reason: suggestion,
const targetType = Math.random() > 0.5 ? "user" : "group";
const targetId =
targetType === "user"
? getRandomItem(currentUsers.filter(u => u.id !== user.id)).id
: getRandomItem(currentGroups).id;
await db.insert(suggestions).values({
userId: user.id,
targetType,
targetId,
reason: suggestion,
score: Math.floor(Math.random() * 30) + 70,
status: "pending",
});
🤖 Prompt for AI Agents
In platforms/dreamSync/server/seedEnhancedData.ts around lines 262 to 265,
suggestions are assigning targetId from currentUsers regardless of targetType
which can yield a user id when targetType === "group"; update the assignment so
that if targetType === "group" you select targetId from the groups array (random
group id), and if targetType === "user" you select from currentUsers (random
user id); also guard against empty arrays by providing a sensible fallback or
skipping creation when no valid targets exist so the targetId always matches the
declared targetType.

@@ -0,0 +1,85 @@
import express, { type Express } from "express";
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

import.meta.dirname is not standard in Node ESM; switch to fileURLToPath.

Prevents runtime failures resolving paths on Node.

 import express, { type Express } from "express";
+import { fileURLToPath } from "url";
 import fs from "fs";
 import path from "path";
@@
-      const clientTemplate = path.resolve(
-        import.meta.dirname,
+      const __dirname = path.dirname(fileURLToPath(import.meta.url));
+      const clientTemplate = path.resolve(
+        __dirname,
         "..",
         "client",
         "index.html",
       );
@@
-export function serveStatic(app: Express) {
-  const distPath = path.resolve(import.meta.dirname, "public");
+export function serveStatic(app: Express) {
+  const __dirname = path.dirname(fileURLToPath(import.meta.url));
+  const distPath = path.resolve(__dirname, "public");

Also applies to: 49-53, 71-71

🤖 Prompt for AI Agents
In platforms/dreamSync/server/vite.ts around lines 1 (and also at 49-53 and 71),
the code uses non-standard import.meta.dirname; replace it by importing
fileURLToPath from "url" and dirname from "path", derive __filename =
fileURLToPath(import.meta.url) and __dirname = dirname(__filename), then update
all usages that relied on import.meta.dirname to use the new __dirname value;
ensure the new imports are added at the top of the file and remove/replace any
import.meta.dirname references at the specified lines.

Comment on lines +5 to +7
"incremental": true,
"tsBuildInfoFile": "./node_modules/typescript/tsbuildinfo",
"noEmit": true,
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

tsBuildInfo path points inside node_modules (write-protected in many CI).
This can fail incremental builds. Store it at repo root.

Apply:

   "compilerOptions": {
     "incremental": true,
-    "tsBuildInfoFile": "./node_modules/typescript/tsbuildinfo",
+    "tsBuildInfoFile": "./.tsbuildinfo",
📝 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
"incremental": true,
"tsBuildInfoFile": "./node_modules/typescript/tsbuildinfo",
"noEmit": true,
"compilerOptions": {
"incremental": true,
"tsBuildInfoFile": "./.tsbuildinfo",
"noEmit": true,
// …other options…
}
🤖 Prompt for AI Agents
In platforms/dreamSync/tsconfig.json around lines 5 to 7, the tsBuildInfoFile is
currently set inside node_modules which is often write-protected in CI and
breaks incremental builds; change tsBuildInfoFile to point outside node_modules
(for example "./.tsbuildinfo" or "./build/tsbuildinfo") so the incremental build
info is stored at the repo root (and create the build directory if you choose
one), keeping "incremental": true and "noEmit": true as-is.

Comment on lines +10 to +17
...(process.env.NODE_ENV !== "production" &&
process.env.REPL_ID !== undefined
? [
await import("@replit/vite-plugin-cartographer").then((m) =>
m.cartographer(),
),
]
: []),
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Avoid top-level await in the plugins array; prefer async defineConfig with mode.

Safer across Node/tooling and clearer intent.

Refactor:

-export default defineConfig({
-  plugins: [
-    react(),
-    runtimeErrorOverlay(),
-    ...(process.env.NODE_ENV !== "production" &&
-    process.env.REPL_ID !== undefined
-      ? [
-          await import("@replit/vite-plugin-cartographer").then((m) =>
-            m.cartographer(),
-          ),
-        ]
-      : []),
-  ],
+export default defineConfig(async ({ mode }) => {
+  const isProd = mode === "production";
+  const plugins = [react(), runtimeErrorOverlay()];
+  if (!isProd && process.env.REPL_ID) {
+    const { cartographer } = await import("@replit/vite-plugin-cartographer");
+    plugins.push(cartographer());
+  }
+  return {
+    plugins,
+    resolve: { alias: {
+      "@": path.resolve(__dirname, "client", "src"),
+      "@shared": path.resolve(__dirname, "shared"),
+      "@assets": path.resolve(__dirname, "attached_assets"),
+    }},
+    root: path.resolve(__dirname, "client"),
+    build: { outDir: path.resolve(__dirname, "dist/public"), emptyOutDir: true },
+    server: { fs: { strict: true, deny: ["**/.*"] } },
+  };
+});

Verify Node/tooling compatibility:


🏁 Script executed:

#!/bin/bash
# Check declared Node engines and Vite version
rg -n '"engines"\s*:' -n -C2 -g 'package.json'
rg -nP '"vite"\s*:\s*".+?"' -g '**/package.json' -C0

Length of output: 829


Use async defineConfig to replace top-level await in the plugins array

Refactor only the plugins section to avoid top-level await:

-export default defineConfig({
-  plugins: [
-    react(),
-    runtimeErrorOverlay(),
-    ...(process.env.NODE_ENV !== 'production' && process.env.REPL_ID
-      ? [
-          await import('@replit/vite-plugin-cartographer').then(m =>
-            m.cartographer(),
-          ),
-        ]
-      : []),
-  ],
-});
+export default defineConfig(async ({ mode }) => {
+  const plugins = [react(), runtimeErrorOverlay()];
+  if (mode !== 'production' && process.env.REPL_ID) {
+    const { cartographer } = await import('@replit/vite-plugin-cartographer');
+    plugins.push(cartographer());
+  }
+  return { plugins };
+});

Ensure import { defineConfig } from 'vite' is present at the top. This change clarifies intent and is fully supported on Node >=18 with Vite ^5.4.19. (vite.dev, stackoverflow.com)

📝 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
...(process.env.NODE_ENV !== "production" &&
process.env.REPL_ID !== undefined
? [
await import("@replit/vite-plugin-cartographer").then((m) =>
m.cartographer(),
),
]
: []),
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import runtimeErrorOverlay from 'vite-plugin-runtime-error-overlay'
// …other imports…
export default defineConfig(async ({ mode }) => {
const plugins = [
react(),
runtimeErrorOverlay(),
]
if (mode !== 'production' && process.env.REPL_ID) {
const { cartographer } = await import('@replit/vite-plugin-cartographer')
plugins.push(cartographer())
}
return {
plugins,
// …other config entries if needed…
}
})
🤖 Prompt for AI Agents
In platforms/dreamSync/vite.config.ts around lines 10 to 17, the plugins array
uses a top-level await which must be replaced by using an async defineConfig;
ensure you import { defineConfig } from 'vite' at the top, convert the exported
config to export default defineConfig(async () => ({ plugins: [
...(process.env.NODE_ENV !== "production" && process.env.REPL_ID !== undefined ?
[ (await import("@replit/vite-plugin-cartographer")).cartographer() ] : []), /*
other plugins */ ] , /* rest of config */ })); this removes top-level await
while keeping the conditional dynamic import and plugin instantiation inside the
async factory function.

Comment on lines +21 to +27
"@": path.resolve(import.meta.dirname, "client", "src"),
"@shared": path.resolve(import.meta.dirname, "shared"),
"@assets": path.resolve(import.meta.dirname, "attached_assets"),
},
},
root: path.resolve(import.meta.dirname, "client"),
build: {
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

import.meta.dirname is not standard in Node for config files — compute __dirname via fileURLToPath.

Vite loads the config in Node; import.meta.dirname will be undefined in many environments.

Apply:

-import path from "path";
+import path from "node:path";
+import { fileURLToPath } from "node:url";
+
+const __dirname = path.dirname(fileURLToPath(import.meta.url));
@@
-      "@": path.resolve(import.meta.dirname, "client", "src"),
-      "@shared": path.resolve(import.meta.dirname, "shared"),
-      "@assets": path.resolve(import.meta.dirname, "attached_assets"),
+      "@": path.resolve(__dirname, "client", "src"),
+      "@shared": path.resolve(__dirname, "shared"),
+      "@assets": path.resolve(__dirname, "attached_assets"),
@@
-  root: path.resolve(import.meta.dirname, "client"),
+  root: path.resolve(__dirname, "client"),
@@
-    outDir: path.resolve(import.meta.dirname, "dist/public"),
+    outDir: path.resolve(__dirname, "dist/public"),

Also applies to: 28-30

🤖 Prompt for AI Agents
In platforms/dreamSync/vite.config.ts around lines 21-27 (and also lines 28-30),
import.meta.dirname is nonstandard and may be undefined when Node loads the Vite
config; replace its usage by computing a proper __dirname at the top of the file
using fileURLToPath(import.meta.url) with URL from 'url' (e.g., const __filename
= fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename);)
and then use that __dirname for all path.resolve calls (including the alias and
root/build paths) so the config resolves correctly in Node environments.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Review continued from previous batch...

Comment on lines +108 to +109
<Dialog open={open} onOpenChange={onClose}>
<DialogContent className="sm:max-w-md">
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Handle Dialog onOpenChange correctly to avoid accidental re-open/close loops.

Only close when nextOpen is false; don’t call onClose on open events.

-    <Dialog open={open} onOpenChange={onClose}>
+    <Dialog open={open} onOpenChange={(next) => { if (!next) onClose(); }}>
📝 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
<Dialog open={open} onOpenChange={onClose}>
<DialogContent className="sm:max-w-md">
<Dialog
open={open}
onOpenChange={(next) => {
if (!next) onClose();
}}
>
<DialogContent className="sm:max-w-md">
🤖 Prompt for AI Agents
In platforms/dreamSync/client/src/components/auth-modal.tsx around lines 108 to
109, the Dialog currently uses onOpenChange={onClose} which will run the close
handler for both open and close events; change the handler to check the incoming
open state and only call onClose when nextOpen is false (e.g., replace with an
inline handler like (nextOpen) => { if (!nextOpen) onClose(); }) so opening the
dialog does not trigger the close callback.

Comment on lines +35 to +43
const AlertTitle = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLHeadingElement>
>(({ className, ...props }, ref) => (
<h5
ref={ref}
className={cn("mb-1 font-medium leading-none tracking-tight", className)}
{...props}
/>
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix forwardRef element types (mismatched ref/props vs rendered tags).

  • AlertTitle renders an h5 but uses HTMLParagraphElement in generics.
  • AlertDescription renders a div but uses HTMLParagraphElement.
    These cause incorrect typings and potential ref issues.
-const AlertTitle = React.forwardRef<
-  HTMLParagraphElement,
-  React.HTMLAttributes<HTMLHeadingElement>
->(({ className, ...props }, ref) => (
+const AlertTitle = React.forwardRef<
+  HTMLHeadingElement,
+  React.HTMLAttributes<HTMLHeadingElement>
+>(({ className, ...props }, ref) => (
   <h5
     ref={ref}
     className={cn("mb-1 font-medium leading-none tracking-tight", className)}
     {...props}
   />
 ))
@@
-const AlertDescription = React.forwardRef<
-  HTMLParagraphElement,
-  React.HTMLAttributes<HTMLParagraphElement>
->(({ className, ...props }, ref) => (
+const AlertDescription = React.forwardRef<
+  HTMLDivElement,
+  React.HTMLAttributes<HTMLDivElement>
+>(({ className, ...props }, ref) => (
   <div
     ref={ref}
     className={cn("text-sm [&_p]:leading-relaxed", className)}
     {...props}
   />
 ))

Also applies to: 47-55

🤖 Prompt for AI Agents
In platforms/dreamSync/client/src/components/ui/alert.tsx around lines 35-43
(and similarly 47-55), the forwardRef generics use HTMLParagraphElement while
the components render an h5 and a div respectively; update the generic element
types and prop types to match the actual rendered tags (e.g., use
HTMLHeadingElement or HTMLHeadingElement for the h5 ref and
React.HTMLAttributes<HTMLHeadingElement> for props, and use HTMLDivElement with
React.HTMLAttributes<HTMLDivElement> for the div) so the ref and props types
align with the rendered elements.

Comment on lines +42 to +53
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button"
return (
<Comp
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props}
/>
)
}
)
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Default native buttons to type="button" to prevent accidental form submits.

Without an explicit type, a button inside a form submits by default. Set type="button" when rendering a native button unless a type is provided.

-const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
-  ({ className, variant, size, asChild = false, ...props }, ref) => {
+const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
+  ({ className, variant, size, asChild = false, type, ...props }, ref) => {
     const Comp = asChild ? Slot : "button"
     return (
       <Comp
         className={cn(buttonVariants({ variant, size, className }))}
         ref={ref}
+        {...(!asChild ? { type: (type as React.ButtonHTMLAttributes<HTMLButtonElement>["type"]) ?? "button" } : {})}
         {...props}
       />
     )
   }
 )
📝 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
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button"
return (
<Comp
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props}
/>
)
}
)
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, type, ...props }, ref) => {
const Comp = asChild ? Slot : "button"
return (
<Comp
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...(!asChild
? { type: (type as React.ButtonHTMLAttributes<HTMLButtonElement>["type"]) ?? "button" }
: {})}
{...props}
/>
)
}
)
🤖 Prompt for AI Agents
In platforms/dreamSync/client/src/components/ui/button.tsx around lines 42 to
53, the native button lacks a default type which causes accidental form
submissions; when rendering the native "button" (i.e., asChild is false) ensure
you set type="button" unless a type is already provided via props—do this by
deriving a finalType = props.type ?? "button" and pass it only to the native
button element (do not inject into Slot when asChild is true), then spread the
rest of props as before.

Comment on lines +32 to +45
const CardTitle = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn(
"text-2xl font-semibold leading-none tracking-tight",
className
)}
{...props}
/>
))
CardTitle.displayName = "CardTitle"
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Use semantic elements for title/description for better a11y.

Render CardTitle as h3 and CardDescription as p. This improves semantics and screen-reader output.

-const CardTitle = React.forwardRef<
-  HTMLDivElement,
-  React.HTMLAttributes<HTMLDivElement>
->(({ className, ...props }, ref) => (
-  <div
+const CardTitle = React.forwardRef<
+  React.ElementRef<"h3">,
+  React.ComponentPropsWithoutRef<"h3">
+>(({ className, ...props }, ref) => (
+  <h3
     ref={ref}
     className={cn(
       "text-2xl font-semibold leading-none tracking-tight",
       className
     )}
     {...props}
-  />
+  />
 ))
 
-const CardDescription = React.forwardRef<
-  HTMLDivElement,
-  React.HTMLAttributes<HTMLDivElement>
->(({ className, ...props }, ref) => (
-  <div
+const CardDescription = React.forwardRef<
+  React.ElementRef<"p">,
+  React.ComponentPropsWithoutRef<"p">
+>(({ className, ...props }, ref) => (
+  <p
     ref={ref}
     className={cn("text-sm text-muted-foreground", className)}
     {...props}
-  />
+  />
 ))

Also applies to: 47-56

Comment on lines +86 to +97
const handleKeyDown = React.useCallback(
(event: React.KeyboardEvent<HTMLDivElement>) => {
if (event.key === "ArrowLeft") {
event.preventDefault()
scrollPrev()
} else if (event.key === "ArrowRight") {
event.preventDefault()
scrollNext()
}
},
[scrollPrev, scrollNext]
)
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Keyboard nav should respect orientation and not hijack inputs.

  • Support ArrowUp/ArrowDown for vertical carousels.
  • Don’t prevent default when focus is on input/textarea/select or contentEditable.
-    const handleKeyDown = React.useCallback(
-      (event: React.KeyboardEvent<HTMLDivElement>) => {
-        if (event.key === "ArrowLeft") {
-          event.preventDefault()
-          scrollPrev()
-        } else if (event.key === "ArrowRight") {
-          event.preventDefault()
-          scrollNext()
-        }
-      },
-      [scrollPrev, scrollNext]
-    )
+    const handleKeyDown = React.useCallback(
+      (event: React.KeyboardEvent<HTMLDivElement>) => {
+        const target = event.target as HTMLElement
+        const tag = target.tagName?.toLowerCase()
+        if (
+          target.isContentEditable ||
+          tag === "input" ||
+          tag === "textarea" ||
+          tag === "select"
+        ) {
+          return
+        }
+        if (orientation === "horizontal") {
+          if (event.key === "ArrowLeft") {
+            event.preventDefault()
+            scrollPrev()
+          } else if (event.key === "ArrowRight") {
+            event.preventDefault()
+            scrollNext()
+          }
+        } else {
+          if (event.key === "ArrowUp") {
+            event.preventDefault()
+            scrollPrev()
+          } else if (event.key === "ArrowDown") {
+            event.preventDefault()
+            scrollNext()
+          }
+        }
+      },
+      [scrollPrev, scrollNext, orientation]
+    )
📝 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
const handleKeyDown = React.useCallback(
(event: React.KeyboardEvent<HTMLDivElement>) => {
if (event.key === "ArrowLeft") {
event.preventDefault()
scrollPrev()
} else if (event.key === "ArrowRight") {
event.preventDefault()
scrollNext()
}
},
[scrollPrev, scrollNext]
)
const handleKeyDown = React.useCallback(
(event: React.KeyboardEvent<HTMLDivElement>) => {
const target = event.target as HTMLElement
const tag = target.tagName?.toLowerCase()
if (
target.isContentEditable ||
tag === "input" ||
tag === "textarea" ||
tag === "select"
) {
return
}
if (orientation === "horizontal") {
if (event.key === "ArrowLeft") {
event.preventDefault()
scrollPrev()
} else if (event.key === "ArrowRight") {
event.preventDefault()
scrollNext()
}
} else {
if (event.key === "ArrowUp") {
event.preventDefault()
scrollPrev()
} else if (event.key === "ArrowDown") {
event.preventDefault()
scrollNext()
}
}
},
[scrollPrev, scrollNext, orientation]
)
🤖 Prompt for AI Agents
In platforms/dreamSync/client/src/components/ui/carousel.tsx around lines 86 to
97, the keyboard handler currently only handles ArrowLeft/ArrowRight and always
calls event.preventDefault(), which incorrectly hijacks typing and doesn't
support vertical carousels; update the handler to check the carousel orientation
(use the component's orientation prop/state) and handle ArrowUp/ArrowDown when
orientation === "vertical" while keeping ArrowLeft/ArrowRight for horizontal,
and before calling event.preventDefault() bail out if the event target is an
input, textarea, select or has contentEditable=true (or is inside a
contentEditable), and add orientation to the useCallback dependency array so the
handler updates correctly.

Comment on lines +132 to +160
const suggestionType = getRandomItem(suggestionTypes);

try {
if (suggestionType.type === "user") {
const suggestedUser = getRandomItem(allUsers.filter(u => u.id !== user.id));
await db.insert(suggestions).values({
userId: user.id,
targetType: "user",
targetId: suggestedUser.id,
reason: suggestionType.reason,
score: Math.floor(Math.random() * 30) + 70, // 70-100%
status: "pending",
});
} else {
const suggestedGroup = getRandomItem(allGroups);
await db.insert(suggestions).values({
userId: user.id,
targetType: "group",
targetId: suggestedGroup.id,
reason: suggestionType.reason,
score: Math.floor(Math.random() * 30) + 70, // 70-100%
status: "pending",
});
}
generalSuggestionsCreated++;
} catch (error) {
// Ignore duplicates
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Guard against empty pools and dedupe general suggestions

Avoid runtime errors when user/group pools are empty and ensure idempotency.

-          if (suggestionType.type === "user") {
-            const suggestedUser = getRandomItem(allUsers.filter(u => u.id !== user.id));
-            await db.insert(suggestions).values({
-              userId: user.id,
-              targetType: "user",
-              targetId: suggestedUser.id,
-              reason: suggestionType.reason,
-              score: Math.floor(Math.random() * 30) + 70, // 70-100%
-              status: "pending",
-            });
-          } else {
-            const suggestedGroup = getRandomItem(allGroups);
-            await db.insert(suggestions).values({
-              userId: user.id,
-              targetType: "group",
-              targetId: suggestedGroup.id,
-              reason: suggestionType.reason,
-              score: Math.floor(Math.random() * 30) + 70, // 70-100%
-              status: "pending",
-            });
-          }
+          if (suggestionType.type === "user") {
+            const pool = allUsers.filter(u => u.id !== user.id);
+            if (pool.length === 0) continue;
+            const suggestedUser = getRandomItem(pool);
+            await db
+              .insert(suggestions)
+              .values({
+                userId: user.id,
+                targetType: "user",
+                targetId: suggestedUser.id,
+                reason: suggestionType.reason,
+                score: Math.floor(Math.random() * 30) + 70, // 70-100%
+                status: "pending",
+              })
+              .onConflictDoNothing({
+                target: [suggestions.userId, suggestions.targetType, suggestions.targetId],
+              });
+          } else {
+            if (allGroups.length === 0) continue;
+            const suggestedGroup = getRandomItem(allGroups);
+            await db
+              .insert(suggestions)
+              .values({
+                userId: user.id,
+                targetType: "group",
+                targetId: suggestedGroup.id,
+                reason: suggestionType.reason,
+                score: Math.floor(Math.random() * 30) + 70, // 70-100%
+                status: "pending",
+              })
+              .onConflictDoNothing({
+                target: [suggestions.userId, suggestions.targetType, suggestions.targetId],
+              });
+          }
📝 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
const suggestionType = getRandomItem(suggestionTypes);
try {
if (suggestionType.type === "user") {
const suggestedUser = getRandomItem(allUsers.filter(u => u.id !== user.id));
await db.insert(suggestions).values({
userId: user.id,
targetType: "user",
targetId: suggestedUser.id,
reason: suggestionType.reason,
score: Math.floor(Math.random() * 30) + 70, // 70-100%
status: "pending",
});
} else {
const suggestedGroup = getRandomItem(allGroups);
await db.insert(suggestions).values({
userId: user.id,
targetType: "group",
targetId: suggestedGroup.id,
reason: suggestionType.reason,
score: Math.floor(Math.random() * 30) + 70, // 70-100%
status: "pending",
});
}
generalSuggestionsCreated++;
} catch (error) {
// Ignore duplicates
}
}
const suggestionType = getRandomItem(suggestionTypes);
try {
if (suggestionType.type === "user") {
const pool = allUsers.filter(u => u.id !== user.id);
if (pool.length === 0) continue;
const suggestedUser = getRandomItem(pool);
await db
.insert(suggestions)
.values({
userId: user.id,
targetType: "user",
targetId: suggestedUser.id,
reason: suggestionType.reason,
score: Math.floor(Math.random() * 30) + 70, // 70-100%
status: "pending",
})
.onConflictDoNothing({
target: [suggestions.userId, suggestions.targetType, suggestions.targetId],
});
} else {
if (allGroups.length === 0) continue;
const suggestedGroup = getRandomItem(allGroups);
await db
.insert(suggestions)
.values({
userId: user.id,
targetType: "group",
targetId: suggestedGroup.id,
reason: suggestionType.reason,
score: Math.floor(Math.random() * 30) + 70, // 70-100%
status: "pending",
})
.onConflictDoNothing({
target: [suggestions.userId, suggestions.targetType, suggestions.targetId],
});
}
generalSuggestionsCreated++;
} catch (error) {
// Ignore duplicates
}

Comment on lines +11 to +13
if (!process.env.REPLIT_DOMAINS) {
throw new Error("Environment variable REPLIT_DOMAINS not provided");
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Validate all required environment variables upfront.

Fail fast with a clear list of missing vars.

-if (!process.env.REPLIT_DOMAINS) {
-  throw new Error("Environment variable REPLIT_DOMAINS not provided");
-}
+{
+  const required = ["REPLIT_DOMAINS", "SESSION_SECRET", "DATABASE_URL", "REPL_ID"];
+  const missing = required.filter((k) => !process.env[k]);
+  if (missing.length) {
+    throw new Error(`Missing required environment variables: ${missing.join(", ")}`);
+  }
+}
📝 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.REPLIT_DOMAINS) {
throw new Error("Environment variable REPLIT_DOMAINS not provided");
}
{
const required = ["REPLIT_DOMAINS", "SESSION_SECRET", "DATABASE_URL", "REPL_ID"];
const missing = required.filter((k) => !process.env[k]);
if (missing.length) {
throw new Error(`Missing required environment variables: ${missing.join(", ")}`);
}
}
🤖 Prompt for AI Agents
In platforms/dreamSync/server/replitAuth.ts around lines 11 to 13, the current
check only verifies REPLIT_DOMAINS and throws a generic error; replace this with
a upfront validation that collects all required environment variable names
(e.g., REPLIT_DOMAINS and any others used by this module), iterate over them to
build a list of missing vars, and if the list is non-empty throw a single Error
that includes a clear, comma-separated list of the missing environment variables
so the process fails fast with actionable output.

Comment on lines +25 to +45
export function getSession() {
const sessionTtl = 7 * 24 * 60 * 60 * 1000; // 1 week
const pgStore = connectPg(session);
const sessionStore = new pgStore({
conString: process.env.DATABASE_URL,
createTableIfMissing: true,
ttl: sessionTtl,
tableName: "sessions",
});
return session({
secret: process.env.SESSION_SECRET!,
store: sessionStore,
resave: false,
saveUninitialized: false,
cookie: {
httpOnly: true,
secure: process.env.NODE_ENV === "production",
maxAge: sessionTtl,
},
});
}
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Session security and TTL units.

  • sameSite should be set (Lax is a safe default).
  • Verify connect-pg-simple expects ttl in seconds; express cookie maxAge is ms.
-export function getSession() {
-  const sessionTtl = 7 * 24 * 60 * 60 * 1000; // 1 week
+export function getSession() {
+  const sessionTtlMs = 7 * 24 * 60 * 60 * 1000; // 1 week
+  const sessionTtlSeconds = Math.floor(sessionTtlMs / 1000);
   const pgStore = connectPg(session);
   const sessionStore = new pgStore({
     conString: process.env.DATABASE_URL,
     createTableIfMissing: true,
-    ttl: sessionTtl,
+    // NOTE: connect-pg-simple ttl is typically in seconds; verify this for your version.
+    ttl: sessionTtlSeconds,
     tableName: "sessions",
   });
   return session({
     secret: process.env.SESSION_SECRET!,
     store: sessionStore,
     resave: false,
     saveUninitialized: false,
     cookie: {
       httpOnly: true,
       secure: process.env.NODE_ENV === "production",
-      maxAge: sessionTtl,
+      sameSite: "lax",
+      maxAge: sessionTtlMs,
     },
   });
 }

To confirm ttl units, run:


🏁 Script executed:

#!/bin/bash
# Check for connect-pg-simple version and docs reference
fd -t f package.json | xargs -I{} jq -r '(.name // "") + " " + ((.dependencies["connect-pg-simple"] // .devDependencies["connect-pg-simple"]) // "")' {} | rg connect-pg-simple -n
rg -n "connect-pg-simple" -C2

Length of output: 3677


Adjust TTL units and enable sameSite for session cookies

  • Convert sessionTtl from milliseconds to seconds for connect-pg-simple (it expects seconds) and continue using milliseconds for cookie.maxAge (github.com, npmjs.com)
  • Add sameSite: "lax" to cookie options for CSRF mitigation
 export function getSession() {
-  const sessionTtl = 7 * 24 * 60 * 60 * 1000; // 1 week
+  const sessionTtlMs = 7 * 24 * 60 * 60 * 1000; // 1 week
+  const sessionTtlSeconds = Math.floor(sessionTtlMs / 1000);
   const pgStore = connectPg(session);
   const sessionStore = new pgStore({
     conString: process.env.DATABASE_URL,
     createTableIfMissing: true,
-    ttl: sessionTtl,
+    ttl: sessionTtlSeconds,
     tableName: "sessions",
   });
   return session({
     secret: process.env.SESSION_SECRET!,
     store: sessionStore,
     resave: false,
     saveUninitialized: false,
     cookie: {
       httpOnly: true,
       secure: process.env.NODE_ENV === "production",
+      sameSite: "lax",
       maxAge: sessionTtlMs,
     },
   });
 }
📝 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
export function getSession() {
const sessionTtl = 7 * 24 * 60 * 60 * 1000; // 1 week
const pgStore = connectPg(session);
const sessionStore = new pgStore({
conString: process.env.DATABASE_URL,
createTableIfMissing: true,
ttl: sessionTtl,
tableName: "sessions",
});
return session({
secret: process.env.SESSION_SECRET!,
store: sessionStore,
resave: false,
saveUninitialized: false,
cookie: {
httpOnly: true,
secure: process.env.NODE_ENV === "production",
maxAge: sessionTtl,
},
});
}
export function getSession() {
// 1 week in milliseconds and seconds
const sessionTtlMs = 7 * 24 * 60 * 60 * 1000; // 1 week
const sessionTtlSeconds = Math.floor(sessionTtlMs / 1000);
const pgStore = connectPg(session);
const sessionStore = new pgStore({
conString: process.env.DATABASE_URL,
createTableIfMissing: true,
ttl: sessionTtlSeconds,
tableName: "sessions",
});
return session({
secret: process.env.SESSION_SECRET!,
store: sessionStore,
resave: false,
saveUninitialized: false,
cookie: {
httpOnly: true,
secure: process.env.NODE_ENV === "production",
sameSite: "lax",
maxAge: sessionTtlMs,
},
});
}
🤖 Prompt for AI Agents
In platforms/dreamSync/server/replitAuth.ts around lines 25 to 45, the session
TTL is currently defined in milliseconds but connect-pg-simple expects seconds,
and the session cookie should include sameSite for CSRF mitigation; change the
code to use two values (e.g., sessionTtlMs for cookie.maxAge in milliseconds and
sessionTtlSec for the pgStore ttl in seconds calculated by
Math.floor(sessionTtlMs / 1000)), pass sessionTtlSec to the pgStore ttl option
and sessionTtlMs to cookie.maxAge, and add sameSite: "lax" to the cookie
options.

Comment on lines +122 to +131
app.get("/api/logout", (req, res) => {
req.logout(() => {
res.redirect(
client.buildEndSessionUrl(config, {
client_id: process.env.REPL_ID!,
post_logout_redirect_uri: `${req.protocol}://${req.hostname}`,
}).href
);
});
});
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Destroy server session on logout and clear cookie.

Ensure server-side session and cookie are removed before redirecting to the OP’s end-session endpoint.

-  app.get("/api/logout", (req, res) => {
-    req.logout(() => {
-      res.redirect(
-        client.buildEndSessionUrl(config, {
-          client_id: process.env.REPL_ID!,
-          post_logout_redirect_uri: `${req.protocol}://${req.hostname}`,
-        }).href
-      );
-    });
-  });
+  app.get("/api/logout", (req, res) => {
+    const redirectUrl = client.buildEndSessionUrl(config, {
+      client_id: process.env.REPL_ID!,
+      post_logout_redirect_uri: `${req.protocol}://${req.hostname}`,
+    }).href;
+    req.logout(() => {
+      req.session?.destroy(() => {
+        res.clearCookie("connect.sid", { httpOnly: true, sameSite: "lax", secure: process.env.NODE_ENV === "production" });
+        res.redirect(redirectUrl);
+      });
+    });
+  });
📝 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
app.get("/api/logout", (req, res) => {
req.logout(() => {
res.redirect(
client.buildEndSessionUrl(config, {
client_id: process.env.REPL_ID!,
post_logout_redirect_uri: `${req.protocol}://${req.hostname}`,
}).href
);
});
});
app.get("/api/logout", (req, res) => {
const redirectUrl = client.buildEndSessionUrl(config, {
client_id: process.env.REPL_ID!,
post_logout_redirect_uri: `${req.protocol}://${req.hostname}`,
}).href;
req.logout(() => {
req.session?.destroy(() => {
res.clearCookie("connect.sid", {
httpOnly: true,
sameSite: "lax",
secure: process.env.NODE_ENV === "production",
});
res.redirect(redirectUrl);
});
});
});
🤖 Prompt for AI Agents
In platforms/dreamSync/server/replitAuth.ts around lines 122-131, ensure the
server-side session is destroyed and the session cookie cleared before
redirecting to the OP end-session URL: after calling req.logout, call
req.session.destroy with a callback that handles errors, call res.clearCookie
using your session cookie name (e.g. process.env.SESSION_COOKIE_NAME or
'connect.sid' as default), then perform the res.redirect to
client.buildEndSessionUrl(...).href inside that callback so cleanup completes
before redirecting.

Comment on lines +134 to +161
export const isAuthenticated: RequestHandler = async (req, res, next) => {
const user = req.user as any;

if (!req.isAuthenticated() || !user.expires_at) {
return res.status(401).json({ message: "Unauthorized" });
}

const now = Math.floor(Date.now() / 1000);
if (now <= user.expires_at) {
return next();
}

const refreshToken = user.refresh_token;
if (!refreshToken) {
res.status(401).json({ message: "Unauthorized" });
return;
}

try {
const config = await getOidcConfig();
const tokenResponse = await client.refreshTokenGrant(config, refreshToken);
updateUserSession(user, tokenResponse);
return next();
} catch (error) {
res.status(401).json({ message: "Unauthorized" });
return;
}
};
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Refresh even when expires_at is missing; avoid premature 401s.

Attempt a refresh if the session is authenticated but has no expiry or is expired.

 export const isAuthenticated: RequestHandler = async (req, res, next) => {
   const user = req.user as any;
 
-  if (!req.isAuthenticated() || !user.expires_at) {
+  if (!req.isAuthenticated()) {
     return res.status(401).json({ message: "Unauthorized" });
   }
 
-  const now = Math.floor(Date.now() / 1000);
-  if (now <= user.expires_at) {
-    return next();
-  }
-
-  const refreshToken = user.refresh_token;
-  if (!refreshToken) {
-    res.status(401).json({ message: "Unauthorized" });
-    return;
-  }
-
-  try {
-    const config = await getOidcConfig();
-    const tokenResponse = await client.refreshTokenGrant(config, refreshToken);
-    updateUserSession(user, tokenResponse);
-    return next();
-  } catch (error) {
-    res.status(401).json({ message: "Unauthorized" });
-    return;
-  }
+  const now = Math.floor(Date.now() / 1000);
+  const needsRefresh = !user.expires_at || now > user.expires_at;
+  if (!needsRefresh) return next();
+
+  const refreshToken = user.refresh_token;
+  if (!refreshToken) return res.status(401).json({ message: "Unauthorized" });
+
+  try {
+    const config = await getOidcConfig();
+    const tokenResponse = await client.refreshTokenGrant(config, refreshToken);
+    updateUserSession(user, tokenResponse);
+    return next();
+  } catch {
+    return res.status(401).json({ message: "Unauthorized" });
+  }
 };
📝 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
export const isAuthenticated: RequestHandler = async (req, res, next) => {
const user = req.user as any;
if (!req.isAuthenticated() || !user.expires_at) {
return res.status(401).json({ message: "Unauthorized" });
}
const now = Math.floor(Date.now() / 1000);
if (now <= user.expires_at) {
return next();
}
const refreshToken = user.refresh_token;
if (!refreshToken) {
res.status(401).json({ message: "Unauthorized" });
return;
}
try {
const config = await getOidcConfig();
const tokenResponse = await client.refreshTokenGrant(config, refreshToken);
updateUserSession(user, tokenResponse);
return next();
} catch (error) {
res.status(401).json({ message: "Unauthorized" });
return;
}
};
export const isAuthenticated: RequestHandler = async (req, res, next) => {
const user = req.user as any;
if (!req.isAuthenticated()) {
return res.status(401).json({ message: "Unauthorized" });
}
const now = Math.floor(Date.now() / 1000);
const needsRefresh = !user.expires_at || now > user.expires_at;
if (!needsRefresh) return next();
const refreshToken = user.refresh_token;
if (!refreshToken) return res.status(401).json({ message: "Unauthorized" });
try {
const config = await getOidcConfig();
const tokenResponse = await client.refreshTokenGrant(config, refreshToken);
updateUserSession(user, tokenResponse);
return next();
} catch {
return res.status(401).json({ message: "Unauthorized" });
}
};
🤖 Prompt for AI Agents
In platforms/dreamSync/server/replitAuth.ts around lines 134 to 161, the
middleware currently returns 401 when a session is authenticated but
user.expires_at is missing; instead, if req.isAuthenticated() is true and
expires_at is missing or expired, attempt to refresh using user.refresh_token
before rejecting. Change the flow so: if not authenticated return 401
immediately; otherwise compute now and if expires_at exists and now <=
expires_at call next(); if expires_at missing or expired, check for
refresh_token and, if present, call getOidcConfig() and
client.refreshTokenGrant(config, refreshToken), then call
updateUserSession(user, tokenResponse) and next(); if refresh_token is missing
or refresh fails return 401. Ensure no premature 401 when authenticated without
expires_at.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Review continued from previous batch...

Comment on lines +68 to +79
mutationFn: async () => {
const searchParams = {
radius: useRadius ? searchRadius[0] : undefined,
selectedSkills: selectedSkills.length > 0 ? selectedSkills : undefined,
selectedInterests: selectedInterests.length > 0 ? selectedInterests : undefined,
minCompatibility: minCompatibility[0],
};

await apiRequest("POST", "/api/matches/find", searchParams);
},
onSuccess: (data: any) => {
const matchCount = data?.matchCount || 0;
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

onSuccess receives a Response, not parsed JSON.

apiRequest returns a Response. You’re reading data.matchCount from an unparsed Response, yielding 0.

-    mutationFn: async () => {
+    mutationFn: async (): Promise<{ matchCount: number }> => {
       const searchParams = {
         radius: useRadius ? searchRadius[0] : undefined,
         selectedSkills: selectedSkills.length > 0 ? selectedSkills : undefined,
         selectedInterests: selectedInterests.length > 0 ? selectedInterests : undefined,
         minCompatibility: minCompatibility[0],
       };
-      await apiRequest("POST", "/api/matches/find", searchParams);
+      const res = await apiRequest("POST", "/api/matches/find", searchParams);
+      return res.json();
     },
-    onSuccess: (data: any) => {
-      const matchCount = data?.matchCount || 0;
+    onSuccess: (data) => {
+      const matchCount = data.matchCount ?? 0;
📝 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
mutationFn: async () => {
const searchParams = {
radius: useRadius ? searchRadius[0] : undefined,
selectedSkills: selectedSkills.length > 0 ? selectedSkills : undefined,
selectedInterests: selectedInterests.length > 0 ? selectedInterests : undefined,
minCompatibility: minCompatibility[0],
};
await apiRequest("POST", "/api/matches/find", searchParams);
},
onSuccess: (data: any) => {
const matchCount = data?.matchCount || 0;
mutationFn: async (): Promise<{ matchCount: number }> => {
const searchParams = {
radius: useRadius ? searchRadius[0] : undefined,
selectedSkills: selectedSkills.length > 0 ? selectedSkills : undefined,
selectedInterests: selectedInterests.length > 0 ? selectedInterests : undefined,
minCompatibility: minCompatibility[0],
};
const res = await apiRequest("POST", "/api/matches/find", searchParams);
return res.json();
},
onSuccess: (data) => {
const matchCount = data.matchCount ?? 0;
🤖 Prompt for AI Agents
In platforms/dreamSync/client/src/components/find-matches-modal.tsx around lines
68 to 79, the mutation onSuccess handler expects parsed JSON but mutationFn
awaits apiRequest which returns a Response, so data is an unparsed Response and
data.matchCount is undefined; fix by parsing the response body to JSON before
returning it from mutationFn (await response.json()) or alternatively keep
mutationFn returning the Response and parse response.json() inside onSuccess,
and ensure you await the json parse and handle errors (check response.ok and
throw or log on non-OK) so matchCount is read from the actual parsed payload.

Comment on lines +27 to +29
const FormFieldContext = React.createContext<FormFieldContextValue>(
{} as FormFieldContextValue
)
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Fix broken guards in useFormField and context defaults.

The current guards never trigger (contexts default to {}), and getFieldState runs before validation. This can yield undefined IDs (e.g., “undefined-form-item”) and silent a11y breakage. Default contexts to undefined and validate before use.

-const FormFieldContext = React.createContext<FormFieldContextValue>(
-  {} as FormFieldContextValue
-)
+const FormFieldContext = React.createContext<FormFieldContextValue | undefined>(
+  undefined
+)

 const useFormField = () => {
-  const fieldContext = React.useContext(FormFieldContext)
-  const itemContext = React.useContext(FormItemContext)
-  const { getFieldState, formState } = useFormContext()
-
-  const fieldState = getFieldState(fieldContext.name, formState)
-
-  if (!fieldContext) {
-    throw new Error("useFormField should be used within <FormField>")
-  }
-
-  const { id } = itemContext
+  const fieldContext = React.useContext(FormFieldContext)
+  const itemContext = React.useContext(FormItemContext)
+  const { getFieldState, formState } = useFormContext()
+
+  if (!fieldContext) {
+    throw new Error("useFormField should be used within <FormField>")
+  }
+  if (!itemContext) {
+    throw new Error("useFormField should be used within <FormItem>")
+  }
+
+  const { id } = itemContext
+  const fieldState = getFieldState(fieldContext.name, formState)

   return {
     id,
     name: fieldContext.name,
     formItemId: `${id}-form-item`,
     formDescriptionId: `${id}-form-item-description`,
     formMessageId: `${id}-form-item-message`,
     ...fieldState,
   }
 }

-const FormItemContext = React.createContext<FormItemContextValue>(
-  {} as FormItemContextValue
-)
+const FormItemContext = React.createContext<FormItemContextValue | undefined>(
+  undefined
+)

Also applies to: 44-65, 71-73

🤖 Prompt for AI Agents
In platforms/dreamSync/client/src/components/ui/form.tsx around lines 27-29 (and
also address lines 44-65, 71-73): the context is initialized to an empty object
({} as FormFieldContextValue) so guards in useFormField never trigger and
getFieldState runs before validation, causing undefined IDs and accessibility
breakage. Change the FormFieldContext default to undefined, update the
hook/consumers to check for undefined and throw or return early with a clear
error if the context is missing, and ensure you validate the presence of
required props/state before calling getFieldState (i.e., run validation/guard
checks first, then call getFieldState) so IDs are always defined and guards
actually run.

@@ -0,0 +1,128 @@
import * as React from "react"
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Mark as a Client Component.
This file wraps interactive Radix primitives and is imported by client code. Without "use client", Next.js may error when a Client Component imports it.

+ "use client"
+
 import * as React from "react"
📝 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
import * as React from "react"
"use client"
import * as React from "react"
🤖 Prompt for AI Agents
In platforms/dreamSync/client/src/components/ui/navigation-menu.tsx around line
1, this file uses interactive Radix primitives and must be a Next.js Client
Component; add the "use client" directive as the very first line of the file
(before any imports) to mark it as a client component so Next.js treats imports
and hooks correctly.

Comment on lines +98 to +105
<span
aria-hidden
className={cn("flex h-9 w-9 items-center justify-center", className)}
{...props}
>
<MoreHorizontal className="h-4 w-4" />
<span className="sr-only">More pages</span>
</span>
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Fix a11y: sr-only text is hidden by parent’s aria-hidden.

aria-hidden on the wrapper hides “More pages” from AT. Move aria-hidden to the icon instead.

-  <span
-    aria-hidden
-    className={cn("flex h-9 w-9 items-center justify-center", className)}
-    {...props}
-  >
-    <MoreHorizontal className="h-4 w-4" />
-    <span className="sr-only">More pages</span>
-  </span>
+  <span
+    className={cn("flex h-9 w-9 items-center justify-center", className)}
+    {...props}
+  >
+    <MoreHorizontal aria-hidden className="h-4 w-4" />
+    <span className="sr-only">More pages</span>
+  </span>
📝 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
<span
aria-hidden
className={cn("flex h-9 w-9 items-center justify-center", className)}
{...props}
>
<MoreHorizontal className="h-4 w-4" />
<span className="sr-only">More pages</span>
</span>
<span
className={cn("flex h-9 w-9 items-center justify-center", className)}
{...props}
>
<MoreHorizontal aria-hidden className="h-4 w-4" />
<span className="sr-only">More pages</span>
</span>
🤖 Prompt for AI Agents
In platforms/dreamSync/client/src/components/ui/pagination.tsx around lines 98
to 105, the wrapper span has aria-hidden which hides the "More pages" sr-only
text from assistive tech; remove aria-hidden from the wrapper and apply
aria-hidden={true} to the icon element (MoreHorizontal) instead so the
decorative icon is ignored while the sr-only text remains exposed; keep the
wrapper as a focusable/accessible element (no aria-hidden) and ensure
props/className remain unchanged.

Comment on lines +3 to +7
import { GripVertical } from "lucide-react"
import * as ResizablePrimitive from "react-resizable-panels"

import { cn } from "@/lib/utils"

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

React types used without importing React.

You reference React.ComponentProps but don’t import React; this will type-error.

Apply this diff:

-"use client"
+"use client"
 
-import { GripVertical } from "lucide-react"
+import * as React from "react"
+import { GripVertical } from "lucide-react"
 import * as ResizablePrimitive from "react-resizable-panels"
 
 import { cn } from "@/lib/utils"
📝 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
import { GripVertical } from "lucide-react"
import * as ResizablePrimitive from "react-resizable-panels"
import { cn } from "@/lib/utils"
"use client"
import * as React from "react"
import { GripVertical } from "lucide-react"
import * as ResizablePrimitive from "react-resizable-panels"
import { cn } from "@/lib/utils"
🤖 Prompt for AI Agents
platforms/dreamSync/client/src/components/ui/resizable.tsx around lines 3 to 7:
Type references to React (e.g., React.ComponentProps) are used but React is not
imported, causing type errors; add an appropriate import at the top such as
"import type { ComponentProps } from 'react'" (or "import * as React from
'react'" if runtime React is needed) and update type usages to use
ComponentProps directly if you imported it as a type to resolve the type error.

Comment on lines +146 to +151
await db.insert(interests).values({
userId,
name: interest,
category: interestCategories[Math.floor(Math.random() * interestCategories.length)]
}).onConflictDoNothing();
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix interests insert to match schema.

You're inserting "category", which doesn't exist in the interests table. Use "frequency".

-        await db.insert(interests).values({
-          userId,
-          name: interest,
-          category: interestCategories[Math.floor(Math.random() * interestCategories.length)]
-        }).onConflictDoNothing();
+        await db.insert(interests).values({
+          userId,
+          name: interest,
+          frequency: interestFrequencies[Math.floor(Math.random() * interestFrequencies.length)]
+        }).onConflictDoNothing();
📝 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
await db.insert(interests).values({
userId,
name: interest,
category: interestCategories[Math.floor(Math.random() * interestCategories.length)]
}).onConflictDoNothing();
}
await db.insert(interests).values({
userId,
name: interest,
frequency: interestFrequencies[Math.floor(Math.random() * interestFrequencies.length)]
}).onConflictDoNothing();
}
🤖 Prompt for AI Agents
In platforms/dreamSync/server/seedExtensiveData.ts around lines 146 to 151, the
insert currently sets a non-existent "category" column; change the object key to
"frequency" and supply a valid value (e.g., pick from an existing
interestFrequencies array or a sensible default) so the inserted fields match
the interests table schema and types; remove any usage of interestCategories for
this column and ensure the value conforms to the DB enum/type for frequency.

Comment on lines +249 to +260
const userInterests = [
{ name: "Technology", category: "career" },
{ name: "Startups", category: "career" },
{ name: "Photography", category: "hobby" },
{ name: "Hiking", category: "lifestyle" },
{ name: "Cooking", category: "lifestyle" },
{ name: "Travel", category: "lifestyle" },
{ name: "Reading", category: "hobby" },
{ name: "Fitness", category: "lifestyle" },
{ name: "Music", category: "culture" },
{ name: "AI", category: "technology" }
];
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Align hardcoded userInterests with schema.

Replace "category" with "frequency".

-  const userInterests = [
-    { name: "Technology", category: "career" },
-    { name: "Startups", category: "career" },
-    { name: "Photography", category: "hobby" },
-    { name: "Hiking", category: "lifestyle" },
-    { name: "Cooking", category: "lifestyle" },
-    { name: "Travel", category: "lifestyle" },
-    { name: "Reading", category: "hobby" },
-    { name: "Fitness", category: "lifestyle" },
-    { name: "Music", category: "culture" },
-    { name: "AI", category: "technology" }
-  ];
+  const userInterests = [
+    { name: "Technology", frequency: "daily" },
+    { name: "Startups", frequency: "weekly" },
+    { name: "Photography", frequency: "weekly" },
+    { name: "Hiking", frequency: "monthly" },
+    { name: "Cooking", frequency: "weekly" },
+    { name: "Travel", frequency: "occasionally" },
+    { name: "Reading", frequency: "weekly" },
+    { name: "Fitness", frequency: "weekly" },
+    { name: "Music", frequency: "weekly" },
+    { name: "AI", frequency: "daily" }
+  ];
📝 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
const userInterests = [
{ name: "Technology", category: "career" },
{ name: "Startups", category: "career" },
{ name: "Photography", category: "hobby" },
{ name: "Hiking", category: "lifestyle" },
{ name: "Cooking", category: "lifestyle" },
{ name: "Travel", category: "lifestyle" },
{ name: "Reading", category: "hobby" },
{ name: "Fitness", category: "lifestyle" },
{ name: "Music", category: "culture" },
{ name: "AI", category: "technology" }
];
const userInterests = [
{ name: "Technology", frequency: "daily" },
{ name: "Startups", frequency: "weekly" },
{ name: "Photography", frequency: "weekly" },
{ name: "Hiking", frequency: "monthly" },
{ name: "Cooking", frequency: "weekly" },
{ name: "Travel", frequency: "occasionally" },
{ name: "Reading", frequency: "weekly" },
{ name: "Fitness", frequency: "weekly" },
{ name: "Music", frequency: "weekly" },
{ name: "AI", frequency: "daily" }
];
🤖 Prompt for AI Agents
In platforms/dreamSync/server/seedExtensiveData.ts around lines 249 to 260 the
objects in userInterests use the key "category" but the schema expects
"frequency"; rename the "category" property to "frequency" for each entry (and
replace or normalize values to the allowed frequency values used by the schema),
and update any downstream usage in this file if it references .category to
instead reference .frequency.

Comment on lines +262 to +268
for (const interest of userInterests) {
await db.insert(interests).values({
userId: currentUserId,
name: interest.name,
category: interest.category
}).onConflictDoNothing();
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix interests insert in profile enhancement.

Insert "frequency" instead of "category".

-    await db.insert(interests).values({
-      userId: currentUserId,
-      name: interest.name,
-      category: interest.category
-    }).onConflictDoNothing();
+    await db.insert(interests).values({
+      userId: currentUserId,
+      name: interest.name,
+      frequency: interest.frequency
+    }).onConflictDoNothing();
📝 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
for (const interest of userInterests) {
await db.insert(interests).values({
userId: currentUserId,
name: interest.name,
category: interest.category
}).onConflictDoNothing();
}
for (const interest of userInterests) {
await db.insert(interests).values({
userId: currentUserId,
name: interest.name,
frequency: interest.frequency
}).onConflictDoNothing();
}
🤖 Prompt for AI Agents
In platforms/dreamSync/server/seedExtensiveData.ts around lines 262 to 268, the
interests insert is using the wrong column: it sets category when the intent is
to set frequency; update the values object to use frequency: interest.frequency
(or the correct source field) instead of category, and keep onConflictDoNothing
unchanged; verify the interests table/schema expects a frequency column and
adjust the property name if the source object uses a different key.

Comment on lines +283 to +290
Promise.all([
seedExtensiveUsers(),
seedExtensiveGroups()
]).then(() => {
console.log("Background seeding completed!");
}).catch(error => {
console.error("Background seeding error:", error);
});
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Foreign key risk: groups may reference users before they exist.

Seeding users and groups concurrently can violate groups.createdBy → users.id FK. Seed sequentially (still in background).

-      Promise.all([
-        seedExtensiveUsers(),
-        seedExtensiveGroups()
-      ]).then(() => {
-        console.log("Background seeding completed!");
-      }).catch(error => {
-        console.error("Background seeding error:", error);
-      });
+      (async () => {
+        try {
+          await seedExtensiveUsers();
+          await seedExtensiveGroups();
+          console.log("Background seeding completed!");
+        } catch (error) {
+          console.error("Background seeding error:", error);
+        }
+      })();
📝 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
Promise.all([
seedExtensiveUsers(),
seedExtensiveGroups()
]).then(() => {
console.log("Background seeding completed!");
}).catch(error => {
console.error("Background seeding error:", error);
});
(async () => {
try {
await seedExtensiveUsers();
await seedExtensiveGroups();
console.log("Background seeding completed!");
} catch (error) {
console.error("Background seeding error:", error);
}
})();
🤖 Prompt for AI Agents
In platforms/dreamSync/server/seedExtensiveData.ts around lines 283 to 290,
running seedExtensiveUsers() and seedExtensiveGroups() concurrently risks
violating groups.createdBy → users.id FK; change the background seeding to run
users first then groups sequentially (but still off the main flow) by awaiting
seedExtensiveUsers() before calling seedExtensiveGroups(), and keep the same
then/catch logging behavior for completion and errors.

Comment on lines +245 to +251
async getWishlistItem(id: string, userId: string): Promise<Wish | undefined> {
const [item] = await db
.select()
.from(wishes)
.where(and(eq(wishes.id, id), eq(wishes.userId, userId)));
return item;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Duplicate method definition: getWishlistItem is declared twice.

Two class methods with the same name will collide; the first is silently overridden and TypeScript will flag a duplicate identifier. Keep a single implementation consistent with the IStorage signature.

Apply this diff to remove the duplicate and keep the typed, interface-aligned version:

-  async getWishlistItem(id: string, userId: string): Promise<Wish | undefined> {
-    const [item] = await db
-      .select()
-      .from(wishes)
-      .where(and(eq(wishes.id, id), eq(wishes.userId, userId)));
-    return item;
-  }
...
-  async getWishlistItem(id: string, userId: string): Promise<WishlistItem | undefined> {
+  async getWishlistItem(id: string, userId: string): Promise<WishlistItem | undefined> {
     const [item] = await db
       .select()
       .from(wishes)
       .where(and(eq(wishes.id, id), eq(wishes.userId, userId)));
     return item;
   }

Also applies to: 262-268

🤖 Prompt for AI Agents
In platforms/dreamSync/server/storage.ts around lines 245-251 (and the duplicate
at 262-268), there are two getWishlistItem method definitions which collide;
remove the duplicate so only the implementation that matches the IStorage
interface and its typing remains. Delete the redundant method block and ensure
the remaining getWishlistItem has the correct signature Promise<Wish |
undefined>, uses the db query as intended, and is the sole definition in the
class.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Review continued from previous batch...

Comment on lines +66 to +73
// Wishlist operations
createWishlistItem(item: InsertWishlistItem): Promise<WishlistItem>;
getUserWishlistItems(userId: string): Promise<WishlistItem[]>;
getWishlistItem(id: string, userId: string): Promise<WishlistItem | undefined>;
updateWishlistItem(id: string, item: Partial<InsertWishlistItem>): Promise<WishlistItem | undefined>;
deleteWishlistItem(id: string): Promise<void>;
getWishSuggestions(wishId: string): Promise<{ users: any[], groups: any[] }>;

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Wishlist deletion lacks ownership check.

deleteWishlistItem(id) deletes by id without verifying the caller owns the record. Require userId in the API and scope the delete.

Interface change:

-  deleteWishlistItem(id: string): Promise<void>;
+  deleteWishlistItem(id: string, userId: string): Promise<void>;

Implementation change:

-  async deleteWishlistItem(id: string): Promise<void> {
-    const [item] = await db.select().from(wishes).where(eq(wishes.id, id));
-    await db.delete(wishes).where(eq(wishes.id, id));
+  async deleteWishlistItem(id: string, userId: string): Promise<void> {
+    const [item] = await db
+      .select()
+      .from(wishes)
+      .where(and(eq(wishes.id, id), eq(wishes.userId, userId)));
+    await db
+      .delete(wishes)
+      .where(and(eq(wishes.id, id), eq(wishes.userId, userId)));
     if (item) {
       await this.updateProfileCompletion(item.userId);
     }
   }

Also applies to: 270-276

🤖 Prompt for AI Agents
In platforms/dreamSync/server/storage.ts around lines 66-73 (and also apply same
change at lines 270-276), deleteWishlistItem currently accepts only id and
deletes without checking ownership; change the API to accept (id: string,
userId: string) and update all implementations and callers to pass the caller's
userId, then modify the delete query/operation to include userId in the WHERE
clause (or equivalent filter) so deletion is scoped to records owned by that
user and return/throw appropriate result when no rows are affected. Ensure
types/interfaces are updated to reflect the new signature and update any tests
or usages.

Comment on lines +94 to +97
createGroupSuggestion(userId: string, groupId: string, reason: string, score: number): Promise<Suggestion>;
getUserGroupSuggestions(userId: string): Promise<(Suggestion & { group: GroupWithMembers })[]>;
updateGroupSuggestionStatus(suggestionId: string, status: string): Promise<Suggestion | undefined>;
deleteGroupSuggestion(suggestionId: string, userId: string): Promise<void>;
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Harden authorization: include userId in updateGroupSuggestionStatus.

Updating a suggestion by id alone enables cross-user tampering if not guarded upstream. Mirror deleteGroupSuggestion’s user scoping.

-  updateGroupSuggestionStatus(suggestionId: string, status: string): Promise<Suggestion | undefined>;
+  updateGroupSuggestionStatus(suggestionId: string, userId: string, status: string): Promise<Suggestion | undefined>;
📝 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
createGroupSuggestion(userId: string, groupId: string, reason: string, score: number): Promise<Suggestion>;
getUserGroupSuggestions(userId: string): Promise<(Suggestion & { group: GroupWithMembers })[]>;
updateGroupSuggestionStatus(suggestionId: string, status: string): Promise<Suggestion | undefined>;
deleteGroupSuggestion(suggestionId: string, userId: string): Promise<void>;
createGroupSuggestion(userId: string, groupId: string, reason: string, score: number): Promise<Suggestion>;
getUserGroupSuggestions(userId: string): Promise<(Suggestion & { group: GroupWithMembers })[]>;
updateGroupSuggestionStatus(suggestionId: string, userId: string, status: string): Promise<Suggestion | undefined>;
deleteGroupSuggestion(suggestionId: string, userId: string): Promise<void>;
🤖 Prompt for AI Agents
In platforms/dreamSync/server/storage.ts around lines 94 to 97, the
updateGroupSuggestionStatus method accepts only suggestionId which allows
cross-user tampering; change its signature to include userId (e.g.
updateGroupSuggestionStatus(suggestionId: string, userId: string, status:
string): Promise<Suggestion | undefined>) and update the implementation to scope
the update by both suggestionId and userId (mirror deleteGroupSuggestion’s
authorization check), returning undefined if no matching record is found; update
any call sites to pass the current userId.

Comment on lines +131 to +144
async upsertUser(userData: UpsertUser): Promise<User> {
const [user] = await db
.insert(users)
.values(userData)
.onConflictDoUpdate({
target: users.id,
set: {
...userData,
updatedAt: new Date(),
},
})
.returning();
return user;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Upsert is keyed on id, not the unique email — likely wrong semantics.

If callers upsert by email (unique), conflicts on email won’t be handled and will instead error. Target the unique email column.

-      .onConflictDoUpdate({
-        target: users.id,
+      .onConflictDoUpdate({
+        target: users.email,
         set: {
           ...userData,
           updatedAt: new Date(),
         },
       })
📝 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
async upsertUser(userData: UpsertUser): Promise<User> {
const [user] = await db
.insert(users)
.values(userData)
.onConflictDoUpdate({
target: users.id,
set: {
...userData,
updatedAt: new Date(),
},
})
.returning();
return user;
}
async upsertUser(userData: UpsertUser): Promise<User> {
const [user] = await db
.insert(users)
.values(userData)
.onConflictDoUpdate({
target: users.email,
set: {
...userData,
updatedAt: new Date(),
},
})
.returning();
return user;
}
🤖 Prompt for AI Agents
In platforms/dreamSync/server/storage.ts around lines 131 to 144, the upsert
currently keys conflicts on users.id which misses conflicts on the unique email
column; change the onConflictDoUpdate target to the unique email column (e.g.,
users.email or the actual unique index name) so inserts with an existing email
perform an update instead of erroring, and keep the set payload (including
updatedAt) as-is.

Comment on lines +292 to +309
// Get skills and interests for each user
const usersWithData = await Promise.all(
userSuggestions.map(async (item) => {
if (!item.user) return null;

const userSkills = await db.select().from(skills).where(eq(skills.userId, item.user.id));
const userInterests = await db.select().from(interests).where(eq(interests.userId, item.user.id));

return {
...item.user,
profile: item.profile,
skills: userSkills,
interests: userInterests,
relevanceScore: item.suggestion.relevanceScore,
reason: item.suggestion.reason,
};
})
);
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Eliminate N+1 queries when enriching user suggestions.

You query skills/interests per user in a loop. Batch with inArray and map by userId.

-    const usersWithData = await Promise.all(
-      userSuggestions.map(async (item) => {
-        if (!item.user) return null;
-        
-        const userSkills = await db.select().from(skills).where(eq(skills.userId, item.user.id));
-        const userInterests = await db.select().from(interests).where(eq(interests.userId, item.user.id));
-        
-        return {
-          ...item.user,
-          profile: item.profile,
-          skills: userSkills,
-          interests: userInterests,
-          relevanceScore: item.suggestion.relevanceScore,
-          reason: item.suggestion.reason,
-        };
-      })
-    );
+    const suggestedUsers = userSuggestions.filter((u) => u.user).map((u) => u.user!.id);
+    const [allSkills, allInterests] = await Promise.all([
+      db.select().from(skills).where(inArray(skills.userId, suggestedUsers)),
+      db.select().from(interests).where(inArray(interests.userId, suggestedUsers)),
+    ]);
+    const skillsByUser = allSkills.reduce<Record<string, Skill[]>>((acc, s) => ((acc[s.userId] ||= []).push(s), acc), {});
+    const interestsByUser = allInterests.reduce<Record<string, Interest[]>>((acc, i) => ((acc[i.userId] ||= []).push(i), acc), {});
+    const usersWithData = userSuggestions.map((item) => {
+      if (!item.user) return null;
+      return {
+        ...item.user,
+        profile: item.profile,
+        skills: skillsByUser[item.user.id] || [],
+        interests: interestsByUser[item.user.id] || [],
+        relevanceScore: item.suggestion.relevanceScore,
+        reason: item.suggestion.reason,
+      };
+    });
📝 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
// Get skills and interests for each user
const usersWithData = await Promise.all(
userSuggestions.map(async (item) => {
if (!item.user) return null;
const userSkills = await db.select().from(skills).where(eq(skills.userId, item.user.id));
const userInterests = await db.select().from(interests).where(eq(interests.userId, item.user.id));
return {
...item.user,
profile: item.profile,
skills: userSkills,
interests: userInterests,
relevanceScore: item.suggestion.relevanceScore,
reason: item.suggestion.reason,
};
})
);
// Get skills and interests for each user
const suggestedUsers = userSuggestions
.filter((u) => u.user)
.map((u) => u.user!.id);
const [allSkills, allInterests] = await Promise.all([
db.select().from(skills).where(inArray(skills.userId, suggestedUsers)),
db.select().from(interests).where(inArray(interests.userId, suggestedUsers)),
]);
const skillsByUser = allSkills.reduce<Record<string, Skill[]>>(
(acc, s) => ((acc[s.userId] ||= []).push(s), acc),
{}
);
const interestsByUser = allInterests.reduce<Record<string, Interest[]>>(
(acc, i) => ((acc[i.userId] ||= []).push(i), acc),
{}
);
const usersWithData = userSuggestions.map((item) => {
if (!item.user) return null;
return {
...item.user,
profile: item.profile,
skills: skillsByUser[item.user.id] || [],
interests: interestsByUser[item.user.id] || [],
relevanceScore: item.suggestion.relevanceScore,
reason: item.suggestion.reason,
};
});
🤖 Prompt for AI Agents
In platforms/dreamSync/server/storage.ts around lines 292 to 309, the code
causes N+1 queries by selecting skills and interests inside the
userSuggestions.map loop; instead, collect all user IDs from userSuggestions,
run two batched queries using inArray to fetch all skills and all interests for
those userIds, build lookup maps keyed by userId, then replace the per-user
selects with map lookups when constructing usersWithData so each user's
skills/interests are populated from the in-memory maps rather than additional DB
queries.

Comment on lines +322 to +327
const groupsWithData = groupSuggestions.map(item => ({
...item.group,
relevanceScore: item.suggestion.relevanceScore,
reason: item.suggestion.reason,
})).filter(group => group.id);

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Runtime error risk: spreading a possibly null group.

Left join can return null for item.group; spreading null throws. Filter before map (like you did for users).

-    const groupsWithData = groupSuggestions.map(item => ({
-      ...item.group,
-      relevanceScore: item.suggestion.relevanceScore,
-      reason: item.suggestion.reason,
-    })).filter(group => group.id);
+    const groupsWithData = groupSuggestions
+      .filter((item) => item.group)
+      .map((item) => ({
+        ...item.group!,
+        relevanceScore: item.suggestion.relevanceScore,
+        reason: item.suggestion.reason,
+      }));
📝 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
const groupsWithData = groupSuggestions.map(item => ({
...item.group,
relevanceScore: item.suggestion.relevanceScore,
reason: item.suggestion.reason,
})).filter(group => group.id);
const groupsWithData = groupSuggestions
.filter((item) => item.group)
.map((item) => ({
...item.group!,
relevanceScore: item.suggestion.relevanceScore,
reason: item.suggestion.reason,
}));
🤖 Prompt for AI Agents
In platforms/dreamSync/server/storage.ts around lines 322 to 327, the current
code spreads item.group which can be null from the left join, risking a runtime
crash; change the sequence to first filter out items with falsy item.group (and
optionally item.group.id) and then map to build the objects (or use a null-safe
access pattern), ensuring you only spread a confirmed non-null group and
preserve relevanceScore/reason; update types if necessary to reflect non-null
group after filtering.

id: varchar("id").primaryKey().default(sql`gen_random_uuid()`),
wishId: varchar("wish_id").notNull().references(() => wishes.id),
userId: varchar("user_id").notNull().references(() => users.id),
relevanceScore: integer("relevance_score").notNull(), // 1-100
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add validation for score fields

The relevanceScore and compatibilityScore fields should have CHECK constraints to ensure values stay within the expected range.

For wishUserSuggestions table:

   relevanceScore: integer("relevance_score").notNull(), // 1-100

Add to the table constraints:

 }, (table) => ({
   wishIdx: index("idx_wish_user_suggestions_wish_id").on(table.wishId),
   userIdx: index("idx_wish_user_suggestions_user_id").on(table.userId),
+  checkRelevanceScore: sql`CHECK (${table.relevanceScore} >= 1 AND ${table.relevanceScore} <= 100)`,
 }));

Apply similar constraints to wishGroupSuggestions and matches tables for their respective score fields.

Also applies to: 103-103, 113-113

🤖 Prompt for AI Agents
In platforms/dreamSync/shared/schema.ts around lines 93, 103 and 113, the
numeric score columns lack DB CHECK constraints; add CHECK constraints that
enforce relevanceScore and compatibilityScore to be between 1 and 100
(inclusive) on the wishUserSuggestions, wishGroupSuggestions and matches tables
respectively, give each constraint a clear unique name (e.g.
chk_<table>_<column>_range), implement them using the schema builder's
check/constraint API for integer columns, and update/create the corresponding
migration so the checks are applied in the database.

Comment on lines +111 to +112
userId1: varchar("user_id_1").notNull().references(() => users.id),
userId2: varchar("user_id_2").notNull().references(() => users.id),
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add database constraint to prevent self-matches

The matches table should prevent users from matching with themselves through a CHECK constraint.

Add a check constraint to the table definition:

 export const matches = pgTable("matches", {
   id: varchar("id").primaryKey().default(sql`gen_random_uuid()`),
   userId1: varchar("user_id_1").notNull().references(() => users.id),
   userId2: varchar("user_id_2").notNull().references(() => users.id),
   compatibilityScore: integer("compatibility_score").notNull(),
   matchReason: text("match_reason"),
   status: varchar("status").default("pending"), // pending, connected, rejected
   createdAt: timestamp("created_at").defaultNow(),
-});
+}, (table) => ({
+  checkDifferentUsers: sql`CHECK (${table.userId1} != ${table.userId2})`,
+}));
📝 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
userId1: varchar("user_id_1").notNull().references(() => users.id),
userId2: varchar("user_id_2").notNull().references(() => users.id),
export const matches = pgTable("matches", {
id: varchar("id").primaryKey().default(sql`gen_random_uuid()`),
userId1: varchar("user_id_1").notNull().references(() => users.id),
userId2: varchar("user_id_2").notNull().references(() => users.id),
compatibilityScore: integer("compatibility_score").notNull(),
matchReason: text("match_reason"),
status: varchar("status").default("pending"), // pending, connected, rejected
createdAt: timestamp("created_at").defaultNow(),
}, (table) => ({
checkDifferentUsers: sql`CHECK (${table.userId1} != ${table.userId2})`,
}));
🤖 Prompt for AI Agents
platforms/dreamSync/shared/schema.ts around lines 111-112: the matches table
currently allows userId1 and userId2 to be identical; add a CHECK constraint to
the table definition that enforces user_id_1 <> user_id_2 (name it e.g.
no_self_match) so users cannot match with themselves—implement this using your
ORM/table builder CHECK API or by adding the equivalent raw SQL constraint in
the table declaration and ensure the constraint is included in the migration.

Comment on lines +126 to +127
latitude: decimal("latitude"),
longitude: decimal("longitude"),
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Reuse coordinate type definitions

Groups table also uses latitude/longitude without precision specification. Apply the same fix as suggested for the profiles table.

-  latitude: decimal("latitude"),
-  longitude: decimal("longitude"),
+  latitude: decimal("latitude", { precision: 10, scale: 7 }),
+  longitude: decimal("longitude", { precision: 10, scale: 7 }),
📝 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
latitude: decimal("latitude"),
longitude: decimal("longitude"),
latitude: decimal("latitude", { precision: 10, scale: 7 }),
longitude: decimal("longitude", { precision: 10, scale: 7 }),
🤖 Prompt for AI Agents
In platforms/dreamSync/shared/schema.ts around lines 126-127, latitude and
longitude are declared without precision; update them to the same coordinated
column definition used in the profiles table by replacing decimal("latitude")
and decimal("longitude") with the shared coordinate definition (e.g.,
decimal("latitude", { precision: 10, scale: 7 }) and decimal("longitude", {
precision: 10, scale: 7 })) or import and reuse the existing coordinate
helper/type so both tables use the identical precision/scale configuration.

Comment on lines +282 to +283
export type Wish = typeof wishes.$inferSelect;
export type InsertWish = typeof wishes.$inferInsert;
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix duplicate type declarations

The Wish and InsertWish types are declared twice, which will cause TypeScript compilation errors.

Remove the duplicate declarations:

 // Additional types for wishlist functionality
 export type Wish = typeof wishes.$inferSelect;
 export type InsertWish = typeof wishes.$inferInsert;
 export type WishUserSuggestion = typeof wishUserSuggestions.$inferSelect;
 export type WishGroupSuggestion = typeof wishGroupSuggestions.$inferSelect;
 export type Profile = typeof profiles.$inferSelect;
 export type InsertProfile = z.infer<typeof insertProfileSchema>;
 export type Skill = typeof skills.$inferSelect;
 export type InsertSkill = z.infer<typeof insertSkillSchema>;
 export type Interest = typeof interests.$inferSelect;
 export type InsertInterest = z.infer<typeof insertInterestSchema>;
-export type Wish = typeof wishes.$inferSelect;
-export type InsertWish = z.infer<typeof insertWishSchema>;
 export type Match = typeof matches.$inferSelect;
 export type Group = typeof groups.$inferSelect;
 export type InsertGroup = z.infer<typeof insertGroupSchema>;

Also applies to: 292-293

🤖 Prompt for AI Agents
In platforms/dreamSync/shared/schema.ts around lines 282-283 (and similarly at
292-293) the types Wish and InsertWish are declared twice causing TypeScript
duplicate declaration errors; remove the redundant declarations so each type is
declared only once — keep the correct/intentional declaration and delete the
duplicate lines at 282-283 (and the duplicate at 292-293) to resolve the
conflict.


// Additional types for wishlist functionality
export type Wish = typeof wishes.$inferSelect;
export type InsertWish = typeof wishes.$inferInsert;
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Type inconsistency for InsertWish

After removing the duplicate, there's still an inconsistency. The first InsertWish uses $inferInsert while the second (duplicate) uses the Zod schema. Align with the pattern used for other insert types.

-export type InsertWish = typeof wishes.$inferInsert;
+export type InsertWish = z.infer<typeof insertWishSchema>;
📝 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
export type InsertWish = typeof wishes.$inferInsert;
export type InsertWish = z.infer<typeof insertWishSchema>;
🤖 Prompt for AI Agents
In platforms/dreamSync/shared/schema.ts around line 283, the InsertWish type is
inconsistent with the other insert types (one copy used the model's $inferInsert
while the duplicate used the Zod schema); update the duplicate definition so
InsertWish is defined using the model inference pattern (i.e., use the wishes
model's $inferInsert type) so it matches the other Insert* types across the
file.

@coodos coodos merged commit f33bba3 into main Sep 5, 2025
1 of 4 checks passed
@coodos coodos deleted the feat/dream-sync branch September 5, 2025 11:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants