Skip to content

Bugfix/hotfixes v4 | Switch to org routing; add workspace upload API; refresh signed links#563

Merged
AndlerRL merged 14 commits intodevelopfrom
bugfix/hotfixes-v4
Sep 23, 2025
Merged

Bugfix/hotfixes v4 | Switch to org routing; add workspace upload API; refresh signed links#563
AndlerRL merged 14 commits intodevelopfrom
bugfix/hotfixes-v4

Conversation

@AndlerRL
Copy link
Member

@AndlerRL AndlerRL commented Sep 22, 2025

Summary by Sourcery

Update routing terminology from 'public' to 'org', add Pro-mode flags, refactor provider/onboarding components, improve workspace document upload with a new API route, add a cron job to refresh document links, and fix related UI and routing bugs.

New Features:

  • Add 'org' route type to replace 'public' routing across the app
  • Introduce 'isPro' flag in thread creation, chat providers, and document uploads to support Pro mode
  • Add a new API endpoint and client wrapper (uploadWorkspaceDocument) for workspace document uploads with validation and error handling
  • Implement a GET cron job (refresh-workspace-document-links) to refresh signed URLs for workspace documents

Bug Fixes:

  • Correct fallback URLs and route checks to use 'org' instead of 'public' in SidebarLink, HeaderLink, and routing helpers
  • Disable workspace section action buttons while loading to prevent concurrent operations
  • Handle errors in workspace document upload flow with user-facing notifications

Enhancements:

  • Refactor Providers component by extracting AppProviders and OnboardingGate and remove custom NextStep theme settings
  • Simplify OnboardingCard markup in both apps for cleaner structure
  • Consolidate document upload logic into a shared wrapper and improve validation
  • Bump nextstepjs dependency to version 2.1.2

Summary by CodeRabbit

  • New Features

    • Workspace document upload with validation, versioning, and toasts; daily refresh of workspace document links; Google Translate picker in Preferences; email verification endpoint; subscription cancel/resume and streamlined billing responses; new Home page with onboarding flow.
  • Improvements

    • Site-wide route/SEO/navigation switched from “public” to “org”; expanded Preferences (email, verification, username, theme, font-size); default accessibility font size set to “normal”; UI tweaks (larger header logo, sidebar z-index and spacing).
  • Chores

    • Updated cron schedules and dependency bumps.

@AndlerRL AndlerRL self-assigned this Sep 22, 2025
@vercel
Copy link

vercel bot commented Sep 22, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
masterbots-pro Ready Ready Preview Comment Sep 23, 2025 0:58am
1 Skipped Deployment
Project Deployment Preview Comments Updated (UTC)
masterbots Skipped Skipped Sep 23, 2025 0:58am

@sourcery-ai
Copy link

sourcery-ai bot commented Sep 22, 2025

Reviewer's Guide

This PR refactors the React provider hierarchy to simplify onboarding and theme initialization, overhauls the workspace document upload flow with a new uploadWorkspaceDocument API and robust error handling, and standardizes route type naming from 'public' to 'org' across components and utilities. It also introduces a cron job for refreshing signed workspace document links, extends thread creation with an isPro flag, disables workspace section actions during processing, applies various UI and styling refinements, cleans up the workspace-chat hook by removing unused state and adding loading guards, and bumps the nextstepjs dependency.

Sequence diagram for the new workspace document upload API flow

sequenceDiagram
    participant U as actor User
    participant FE as Frontend (React)
    participant API as API Route (POST /api/documents/upload)
    participant Lib as uploadWorkspaceDocument (lib/api/documents.ts)
    participant Bucket as Google Cloud Bucket
    participant Sentry as Sentry

    U->>FE: Initiate document upload
    FE->>API: POST document, workspace, thread data
    API->>Lib: Call uploadWorkspaceDocument()
    Lib->>Bucket: uploadWorkspaceDocumentToBucket()
    Bucket-->>Lib: Return upload result
    Lib->>Sentry: Log error if upload fails
    Lib-->>API: Return {data, error}
    API-->>FE: Respond with upload result
    FE->>U: Show success or error message
Loading

Class diagram for workspace document upload API and types

classDiagram
    class WorkspaceDocumentUpload {
      +name: string
      +content: string
      +type: "text" | "image" | "spreadsheet"
    }
    class WorkspaceContextUpload {
      +organization: string
      +department: string
      +project: string
    }
    class uploadWorkspaceDocument {
      +uploadWorkspaceDocument(document, workspace, thread)
    }
    WorkspaceDocumentUpload <.. uploadWorkspaceDocument
    WorkspaceContextUpload <.. uploadWorkspaceDocument
Loading

Class diagram for thread creation with isPro flag

classDiagram
    class CreateThreadParams {
      +userId
      +parentThreadId
      +isPublic: boolean
      +isPro: boolean
      +chatbotId
      +threadId
      +slug
      +model
    }
    class createThread {
      +createThread(params: Partial<CreateThreadParams>)
    }
    CreateThreadParams <.. createThread
Loading

File-Level Changes

Change Details Files
Refactor provider composition and onboarding gate
  • Extract AppProviders and OnboardingGate as separate functions
  • Reorder providers: NextThemesProvider → SessionProvider → NextStepProvider → OnboardingGate → AppProviders
  • Remove custom NextStep theme object and inline defaults
apps/pro-web/components/layout/providers.tsx
Enhance workspace document upload flow with API and error handling
  • Replace direct uploadWorkspaceDocumentToBucket call with centralized uploadWorkspaceDocument helper
  • Unpack upload result, handle errors and display Sonner notifications
  • Add new API route for POST /api/documents/upload
  • Implement lib/api/documents.ts wrapper with validation and Sentry logging
apps/pro-web/components/routes/workspace/workspace-content.tsx
apps/pro-web/app/api/documents/upload/route.ts
apps/pro-web/lib/api/documents.ts
Standardize route type from 'public' to 'org'
  • Rename isPublic checks to isOrg and adjust conditional logic
  • Update URL builders and navigationParams type fields to 'org'
  • Adjust getRouteType and getRouteColor mappings in utils
  • Apply changes across sidebar, header, chat components, thread popups, browse views, category dashboard, and sitemaps
apps/pro-web/components/layout/sidebar/sidebar-link.tsx
apps/pro-web/components/layout/header/header.tsx
apps/pro-web/lib/utils.ts
apps/pro-web/components/routes/chat/chat-accordion.tsx
apps/pro-web/components/routes/browse/browse-chat-message-list.tsx
apps/pro-web/components/shared/category-dashboard.tsx
apps/pro-web/components/routes/thread/thread-popup.tsx
apps/pro-web/components/shared/skeletons/thread-popup-skeleton.tsx
Disable workspace section item actions during processing
  • Extend useWorkspaceChat hook state to expose processing and loading flags
  • Disable delete/expand/rewrite/edit buttons when isLoading or workspaceProcessingState ≠ 'idle'
apps/pro-web/components/routes/workspace/workspace-section-item.tsx
Add cron job to refresh workspace document links
  • Add refreshWorkspaceDocumentLinks implementation with GCS signed URL regeneration
  • Expose GET endpoint app/api/cron/refresh-workspace-document-links/route.ts
  • Export job in lib/cron/index.ts
apps/pro-web/lib/cron/refresh-workspace-document-links.ts
apps/pro-web/lib/cron/index.ts
apps/pro-web/app/api/cron/refresh-workspace-document-links/route.ts
Extend thread creation and MB chat for 'pro' flag
  • Add isPro parameter to createThread mutation/service
  • Pass isPro=true in MBChatProvider and new thread creation in workspace-content
apps/pro-web/services/hasura/hasura.service.ts
apps/pro-web/lib/hooks/use-mb-chat.tsx
Apply UI and styling refinements
  • Wrap OnboardingCard in relative div and simplify Card closing tag
  • Increase sidebar z-index from 40 to 50
  • Update FilterInput data-route to 'org'
  • Fix HeaderLink active logic for org vs pro routes
apps/pro-web/components/onboarding/onboarding-card.tsx
apps/web/components/onboarding/onboarding-card.tsx
apps/pro-web/components/layout/sidebar/sidebar.tsx
apps/pro-web/components/layout/sidebar/sidebar-filter-input.tsx
apps/pro-web/components/layout/header/header.tsx
Cleanup and optimize use-workspace-chat hook
  • Remove unused chatId and currentMetaPrompt state
  • Guard handleWorkspaceEdit and other actions to return early if loading or processing
apps/pro-web/lib/hooks/use-workspace-chat.tsx
Bump nextstepjs dependency version
  • Update nextstepjs from '^2.1.1' to '2.1.2'
apps/pro-web/package.json

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 22, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Replaces many route-type usages from "public" to "org"; adds workspace document upload API and client helper with versioned storage; introduces cron to refresh signed workspace document URLs; expands subscriptions API (cancel/resume); updates DB schema and Hasura services for is_pro/models; refactors onboarding/providers and profile layouts; adds Google Translate and OG image refactor.

Changes

Cohort / File(s) Summary
Route type: public → org
apps/pro-web/components/..., apps/pro-web/lib/{threads,metadata,utils}.ts, apps/pro-web/app/b/[botSlug]/*/page.tsx, apps/pro-web/app/org/sitemap.ts, apps/pro-web/app/robots.ts, apps/pro-web/components/shared/skeletons/thread-popup-skeleton.tsx
Replace route-type checks/URLs from 'public' to 'org' across browse/chat/thread flows, sidebar/header, skeletons, SEO (canonical/sitemap/robots), and helpers; adjust related styling branches.
Workspace document upload (API + client)
apps/pro-web/app/api/documents/upload/route.ts, apps/pro-web/lib/api/documents.ts, apps/pro-web/components/routes/workspace/workspace-content.tsx, apps/pro-web/components/routes/workspace/workspace-section-item.tsx, apps/pro-web/lib/hooks/use-workspace-{chat,documents}.ts, apps/pro-web/components/routes/pro/chat-panel-pro.tsx
Add POST upload route and client helper with validation; switch consumer upload flow to uploadWorkspaceDocument (version-based), show toasts, persist base64 to IndexedDB, handle errors, disable UI while processing; remove old bucket-upload import.
Cron: refresh workspace document links
apps/pro-web/lib/cron/{index.ts,refresh-workspace-document-links.ts}, apps/pro-web/app/api/cron/refresh-workspace-document-links/route.ts, apps/pro-web/vercel.json
Add refreshWorkspaceDocumentLinks cron module to regenerate GCS signed URLs, re-export it, change route handler to GET, and schedule nightly cron entry.
Payments API updates
apps/pro-web/app/api/payment/{subscription,plans}/route.tsx
Remove stripeSecret from plans response; transform subscription payload to simplified shape including cancel_at_period_end; add DELETE (cancel) and PATCH (resume) subscription endpoints with validation and structured results.
OG image API refactor
apps/pro-web/app/api/og/route.tsx, apps/pro-web/components/shared/og-image.tsx
Make OG image type-driven (ogType) and accept richer bot/profile/category metadata; infer ogType from query and pass flattened props to OGImage, supporting multiple OG layouts.
Auth: verify token
apps/pro-web/app/api/auth/verify-token/route.ts
New POST endpoint to create/store verification token, link to user, and send verification email; returns token or error.
Hasura service and metadata
apps/pro-web/services/hasura/hasura.service.ts, apps/pro-web/services/hasura/hasura.service.type.ts, apps/hasura/metadata/.../public_thread.yaml, apps/hasura/migrations/..._add_column_is_pro/*, apps/pro-web/lib/hooks/use-mb-chat.tsx
Add isPro to createThread payload and persist it; add updateUser API; extend UpdateUserArgs; expose is_pro in Hasura permissions; add DB migration for is_pro; callsites pass isPro: true.
ORM schema / relations overhaul
packages/mb-drizzle/src/drizzle/{schema,relations}.ts
Replace category→models (modelsEnum), refactor prompt→chatbot and enums, add thread→modelsEnum FK, adjust relation exports and relation shapes; reintroduce token table.
Profile layout refactor
apps/pro-web/app/u/layout.tsx (new), apps/pro-web/app/u/[userSlug]/layout.tsx (deleted), apps/pro-web/components/layout/profile/layout.tsx, apps/pro-web/components/layout/profile/hero.tsx, apps/pro-web/components/routes/profile/*
Move user fetching into top-level /u layout, delete nested server layout, fetch user client-side where needed, minor logging/format changes.
Preferences & translation
apps/pro-web/lib/constants/preferences.ts, apps/pro-web/components/routes/preferences/{preference-section.tsx,google-translation.tsx}, apps/pro-web/app/u/[userSlug]/s/{pref,subs}/page.tsx
Expand preferences (email, verification, username, theme, font-size, language options); add Google Translate component; simplify pref/subs pages and update PreferenceSection signature (remove variant).
Onboarding/providers and styling
apps/pro-web/components/layout/providers.tsx, apps/pro-web/components/onboarding/onboarding-card.tsx, apps/web/components/onboarding/onboarding-card.tsx, apps/pro-web/app/globals.css, apps/pro-web/package.json
Refactor Providers to inline AppProviders and OnboardingGate; restructure onboarding card DOM and gradients; add small/medium font-size CSS variants, NextStep route styles, hide Google Translate banner; bump nextstepjs to 2.1.2.
Home page (client)
apps/pro-web/components/routes/home/home-page.tsx
New client HomePage component with session-aware onboarding and browse flows.
SEO canonical updates
apps/pro-web/app/b/[botSlug]/.../page.tsx
Canonical URL type changed to 'org' in bot/thread pages.
Utilities and threads
apps/pro-web/lib/utils.ts, apps/pro-web/lib/threads.ts, apps/pro-web/lib/url.ts
Remove/replace 'public' route type with 'org', adjust getRouteType/getRouteColor and thread-opening logic, and have parsePath handle /org prefix.
Misc UI & small fixes
apps/pro-web/components/layout/sidebar/*, apps/pro-web/components/layout/header/*, apps/web/components/layout/header/*, various chat/thread components
Z-index/margin/className adjustments; header logo size increased; data-route and route-type condition updates; remove unused isPublic; tweak effect deps and small formatting edits.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Client
  participant API as /api/documents/upload (POST)
  participant Lib as uploadWorkspaceDocument
  participant Bucket as uploadWorkspaceDocumentToBucket
  participant DB as Hasura/DB

  Client->>API: POST { document, workspace, thread }
  API->>Lib: validate & call uploadWorkspaceDocument(...)
  Lib->>Bucket: upload to bucket (threadSlug, org, dept, proj, ...)
  Bucket-->>Lib: { documentMetadata }
  Lib-->>API: { data, error }
  API-->>Client: 200 | 400 | 500
  Client->>DB: createThread({ ..., isPro: true }) [if creating]
  Client->>Client: persist local version (IndexedDB), show toasts
Loading
sequenceDiagram
  autonumber
  participant Vercel as Cron (0 0 * * *)
  participant Route as GET /api/cron/refresh-workspace-document-links
  participant Job as refreshWorkspaceDocumentLinks()
  participant GCS as GCS Bucket
  participant DB as DB (threads)

  Vercel->>Route: invoke
  Route->>Job: start refresh job
  Job->>DB: fetch threads with expiring workspace docs
  loop per-thread (concurrency-limited)
    Job->>GCS: check object exists & generate new signed URL(s)
    Job->>DB: transactionally update thread.metadata.documents with new URLs/timestamps
  end
  Job-->>Route: summary (updated/errored)
  Route-->>Vercel: 200 JSON
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

Possibly related PRs

Suggested labels

bug, feature

Suggested reviewers

  • Bran18
  • sheriffjimoh

Poem

A rabbit taps keys by moonlit glow,
Swapping "public" for "org" in rows.
Documents versioned, signed, and bright,
Nightly crons renew their light.
Hops and fixes—ship it right! 🐇✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title succinctly highlights the PR’s primary changes—switching routing to "org", adding a workspace upload API, and refreshing signed links—which align with the provided summaries and objectives; it is specific, relevant, and presented as a single concise sentence. It includes a minor noisy prefix ("Bugfix/hotfixes v4") but that does not obscure the main intent.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch bugfix/hotfixes-v4

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

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey there - I've reviewed your changes - here's some feedback:

  • There’s a lot of repetitive routeType→boolean flag logic (isOrg, isPro, etc.) scattered across components—consider extracting this into a shared hook or util to reduce boilerplate and keep it consistent.
  • The refreshWorkspaceDocumentLinks and processThread functions are very verbose with console.log/debug comments—consider trimming or gating these behind a debug flag or logger abstraction to avoid noise in production logs.
  • The uploadWorkspaceDocument route and its client helper both implement the same validation logic; centralize those checks into a single utility to avoid duplication and drift.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- There’s a lot of repetitive routeType→boolean flag logic (isOrg, isPro, etc.) scattered across components—consider extracting this into a shared hook or util to reduce boilerplate and keep it consistent.
- The refreshWorkspaceDocumentLinks and processThread functions are very verbose with console.log/debug comments—consider trimming or gating these behind a debug flag or logger abstraction to avoid noise in production logs.
- The uploadWorkspaceDocument route and its client helper both implement the same validation logic; centralize those checks into a single utility to avoid duplication and drift.

## Individual Comments

### Comment 1
<location> `apps/pro-web/lib/utils.ts:308` </location>
<code_context>
+ * @returns 'chat' | 'org' | ''
  */

-type RouteType = 'chat' | 'public' | 'profile' | 'pro' | 'bot' | 'org'
+type RouteType = 'chat' | 'org' | 'profile' | 'pro' | 'bot' | 'org'

 export function getRouteType(pathname: string | null): RouteType {
</code_context>

<issue_to_address>
**issue:** Duplicate 'org' in RouteType union type.

Please remove the duplicate 'org' entry from the RouteType union.
</issue_to_address>

### Comment 2
<location> `apps/pro-web/lib/cron/refresh-workspace-document-links.ts:24-33` </location>
<code_context>
+			private_key: appConfig.features.storageSecretAccessKey,
+		},
+	})
+	const bucket = storage.bucket(appConfig.features.storageBucketName)
+
+	// Convert milliseconds to seconds for PostgreSQL interval
</code_context>

<issue_to_address>
**suggestion (bug_risk):** No error handling for invalid bucket credentials.

Add error handling to manage cases where credentials or bucket name are invalid, such as using try/catch or validating inputs before use.

```suggestion
	let storage: Storage
	let bucket: ReturnType<Storage['bucket']>

	// Validate credentials and bucket name before initializing Storage
	const { storageProjectId, storageClientEmail, storageSecretAccessKey, storageBucketName } = appConfig.features

	if (!storageProjectId || !storageClientEmail || !storageSecretAccessKey || !storageBucketName) {
		console.error('Missing required storage credentials or bucket name in appConfig.features')
		throw new Error('Invalid storage configuration: missing credentials or bucket name')
	}

	try {
		storage = new Storage({
			projectId: storageProjectId,
			credentials: {
				client_email: storageClientEmail,
				private_key: storageSecretAccessKey,
			},
		})
		bucket = storage.bucket(storageBucketName)
	} catch (err) {
		console.error('Failed to initialize Google Cloud Storage or bucket:', err)
		throw new Error('Storage initialization failed: ' + (err instanceof Error ? err.message : String(err)))
	}

	// Convert milliseconds to seconds for PostgreSQL interval
```
</issue_to_address>

### Comment 3
<location> `apps/pro-web/app/api/documents/upload/route.ts:50-53` </location>
<code_context>
+	}
+
+	// Validate document type
+	if (!['text', 'image', 'spreadsheet'].includes(document.type)) {
+		return new Response(
+			JSON.stringify({
</code_context>

<issue_to_address>
**suggestion:** Document type validation is strict and may block future types.

Consider allowing the list of valid document types to be configurable or easily extensible to support future additions.

```suggestion
	// Configurable list of valid document types
+	const VALID_DOCUMENT_TYPES = ['text', 'image', 'spreadsheet'];
+
+	// Validate document type
+	if (!VALID_DOCUMENT_TYPES.includes(document.type)) {
+		return new Response(
+			JSON.stringify({
```
</issue_to_address>

### Comment 4
<location> `apps/pro-web/app/api/cron/refresh-workspace-document-links/route.ts:4` </location>
<code_context>
 import { refreshWorkspaceDocumentLinks } from '@/lib/cron/refresh-workspace-document-links'
 import { NextResponse } from 'next/server'

-export async function POST() {
+export async function GET() {
 	try {
 		const result = await refreshWorkspaceDocumentLinks()
</code_context>

<issue_to_address>
**issue:** Changing from POST to GET for a mutation endpoint may violate REST conventions.

Using GET for a mutating action can lead to unintended caching and proxy behavior, and may not align with client expectations for safe operations.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

* @returns 'chat' | 'org' | ''
*/

type RouteType = 'chat' | 'public' | 'profile' | 'pro' | 'bot' | 'org'
Copy link

Choose a reason for hiding this comment

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

issue: Duplicate 'org' in RouteType union type.

Please remove the duplicate 'org' entry from the RouteType union.

Comment on lines +24 to +33
const storage = new Storage({
projectId: appConfig.features.storageProjectId,
credentials: {
client_email: appConfig.features.storageClientEmail,
private_key: appConfig.features.storageSecretAccessKey,
},
})
const bucket = storage.bucket(appConfig.features.storageBucketName)

// Convert milliseconds to seconds for PostgreSQL interval
Copy link

Choose a reason for hiding this comment

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

suggestion (bug_risk): No error handling for invalid bucket credentials.

Add error handling to manage cases where credentials or bucket name are invalid, such as using try/catch or validating inputs before use.

Suggested change
const storage = new Storage({
projectId: appConfig.features.storageProjectId,
credentials: {
client_email: appConfig.features.storageClientEmail,
private_key: appConfig.features.storageSecretAccessKey,
},
})
const bucket = storage.bucket(appConfig.features.storageBucketName)
// Convert milliseconds to seconds for PostgreSQL interval
let storage: Storage
let bucket: ReturnType<Storage['bucket']>
// Validate credentials and bucket name before initializing Storage
const { storageProjectId, storageClientEmail, storageSecretAccessKey, storageBucketName } = appConfig.features
if (!storageProjectId || !storageClientEmail || !storageSecretAccessKey || !storageBucketName) {
console.error('Missing required storage credentials or bucket name in appConfig.features')
throw new Error('Invalid storage configuration: missing credentials or bucket name')
}
try {
storage = new Storage({
projectId: storageProjectId,
credentials: {
client_email: storageClientEmail,
private_key: storageSecretAccessKey,
},
})
bucket = storage.bucket(storageBucketName)
} catch (err) {
console.error('Failed to initialize Google Cloud Storage or bucket:', err)
throw new Error('Storage initialization failed: ' + (err instanceof Error ? err.message : String(err)))
}
// Convert milliseconds to seconds for PostgreSQL interval

Comment on lines +50 to +53
// Validate document type
if (!['text', 'image', 'spreadsheet'].includes(document.type)) {
return new Response(
JSON.stringify({
Copy link

Choose a reason for hiding this comment

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

suggestion: Document type validation is strict and may block future types.

Consider allowing the list of valid document types to be configurable or easily extensible to support future additions.

Suggested change
// Validate document type
if (!['text', 'image', 'spreadsheet'].includes(document.type)) {
return new Response(
JSON.stringify({
// Configurable list of valid document types
+ const VALID_DOCUMENT_TYPES = ['text', 'image', 'spreadsheet'];
+
+ // Validate document type
+ if (!VALID_DOCUMENT_TYPES.includes(document.type)) {
+ return new Response(
+ JSON.stringify({

import { refreshWorkspaceDocumentLinks } from '@/lib/cron/refresh-workspace-document-links'
import { NextResponse } from 'next/server'

export async function POST() {
Copy link

Choose a reason for hiding this comment

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

issue: Changing from POST to GET for a mutation endpoint may violate REST conventions.

Using GET for a mutating action can lead to unintended caching and proxy behavior, and may not align with client expectations for safe operations.

@coderabbitai coderabbitai bot changed the title Bugfix/hotfixes v4 | @coderabbitai Bugfix/hotfixes v4 | Switch to org routing; add workspace upload API; refresh signed links Sep 22, 2025
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: 15

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (11)
apps/pro-web/components/shared/onboarding-chatbot-card.tsx (1)

29-35: Fix inconsistent route selector migration — replace leftover [data-route="public"] in pro-web and verify getRouteType returns "org"

  • apps/pro-web/app/globals.css: remaining [data-route="public"] rules found for .nextstep-card, .nextstep-card .nextstep-card-title, .nextstep-card .nextstep-button-primary, .nextstep-card .nextstep-step-indicator — change to [data-route="org"] or justify keeping them as "public".
  • apps/pro-web/app/globals.css already uses [data-route="org"] for .selected-bot-avatar / .selected-bot-text / .selected-bot-icon — confirm the app root/getRouteType emits "org" (not "public") so these styles apply.
  • getRouteType wasn't located in this run; verify its return values and the root element's data-route are updated.
apps/pro-web/app/u/[userSlug]/t/[category]/[domain]/[chatbot]/[threadSlug]/page.tsx (1)

103-121: generateMetadata can throw when topic/chatbot is missing

getCategory may return null, and topic.name will crash during metadata generation. Guard with fallbacks.

Apply:

   const baseMetadata = await generateMbMetadata(props)
   const params = await props.params
   // Add or override with your custom link tags
-  const chatbotName = (await botNames).get(params.chatbot as string) || ''
-  const topic = await getCategory({ chatbotName })
-  const domain = getCanonicalDomain(chatbotName)
+  const chatbotName = (await botNames).get(params.chatbot as string) || ''
+  const topic = await getCategory({ chatbotName }).catch(() => null)
+  const topicName = topic?.name || 'AI'
+  const domain = getCanonicalDomain(chatbotName || (params.chatbot as string))
   return {
     ...baseMetadata,
     alternates: {
       canonical: urlBuilders.threadUrl({
         type: 'org',
-        category: topic.name,
+        category: topicName,
         domain,
         chatbot: chatbotName,
         threadSlug: params.threadSlug as string,
       }),
apps/pro-web/app/u/[userSlug]/t/[category]/[domain]/[chatbot]/page.tsx (1)

95-115: Guard null chatbot in generateMetadata; fix OG image and domain source

  • Add an early null-check in generateMetadata and return fallback metadata (use chatbotName or params.chatbot for title and an OG image without ?chatbotId — e.g. ${process.env.BASE_URL||''}/api/og) so we never access chatbot.chatbotId when chatbot is null.
  • Use the resolved chatbotName when calling getCanonicalDomain (getCanonicalDomain(chatbotName)) instead of the raw slug (params.chatbot).
  • Affected examples (apply the pattern across all similar pages):
    • apps/pro-web/app/u/[userSlug]/t/[category]/[domain]/[chatbot]/page.tsx (generateMetadata)
    • apps/pro-web/app/b/[botSlug]/page.tsx
    • apps/pro-web/app/org/[category]/[domain]/[chatbot]/page.tsx
    • apps/pro-web/app/c/[category]/[domain]/[chatbot]/page.tsx
    • apps/pro-web/app/(pro)/[category]/[domain]/[chatbot]/page.tsx
    • apps/web/app/b/[botSlug]/page.tsx
    • apps/web/app/c/[category]/[domain]/[chatbot]/page.tsx
    • apps/web/app/(browse)/[category]/[domain]/[chatbot]/page.tsx
  • Apply the same guard+fallback and domain-source change (the diff in the original comment is the correct pattern) across all files that build OG URLs with ?chatbotId=${chatbot.chatbotId}.
apps/pro-web/components/routes/thread/profile-avatar.tsx (1)

43-46: Route-type check still uses “public”; update to “org”

getRouteType no longer returns “public”. This condition will fail on org pages and hide the avatar.

Apply this diff:

- {(routeType.match(/(public|bot)/) || isAdminMode) && (
+ {((routeType === 'org' || routeType === 'bot') || isAdminMode) && (
apps/pro-web/app/api/cron/refresh-workspace-document-links/route.ts (1)

4-22: GET without auth enables unauthenticated side effects

Publicly triggerable cron can be abused. Require a secret (header or query param) and disable caching.

Apply this diff:

-import { NextResponse } from 'next/server'
+import { NextResponse } from 'next/server'
+
+export const dynamic = 'force-dynamic'
+export const revalidate = 0
+
-export async function GET() {
+export async function GET(req: Request) {
+  const secret = process.env.CRON_SECRET
+  const provided =
+    new URL(req.url).searchParams.get('secret') ??
+    req.headers.get('x-cron-secret')
+  if (!secret || provided !== secret) {
+    return NextResponse.json({ success: false, error: 'Unauthorized' }, { status: 401 })
+  }
   try {
     const result = await refreshWorkspaceDocumentLinks()
     return NextResponse.json({
       success: true,
       message: 'Workspace Document links refreshed successfully',
       ...result,
     })
   } catch (error) {
apps/pro-web/lib/hooks/use-image-generation.tsx (1)

134-145: Bug: unreachable branch; union type doesn’t include 'pro'

mode is 'chat' | 'edit', but you compare to 'pro'. This always falls into the edit path and never appends the image.

Apply this diff:

- if (mode === 'pro') {
+ if (mode === 'chat') {
   //* Create image message and append to chat
   const imageMessage = imageHelpers.createImageMessage(generatedImage)
   append(imageMessage)
   resetState()
 } else {

If you intended a new 'pro' mode, extend the union type and update callers.

apps/pro-web/lib/hooks/use-workspace-documents.ts (3)

66-75: Client/server boundary regression: server action imported on client path.

Calling getAllUserThreadDocumentsMetadata at the top of the client hook forces a server‑only import into the browser bundle and runs even when not needed. This will break Next builds or bloat the client. Lazy‑load it only in the branch that needs it, or fetch via an API/RSC prop.

- try {
-   const remoteDocsRaw = (await getAllUserThreadDocumentsMetadata()) || []
-   const items = (await getAllItemsRaw()) as unknown as
+ try {
+   const items = (await getAllItemsRaw()) as unknown as
      | WorkspaceDocumentMetadata[]
      | []
-   if ((!items || items.length === 0) && !remoteDocsRaw?.length) {
+   if (!items || items.length === 0) {
      // If no local items, return fallbackDocs (may be empty)
      console.log('fallbackDocs (return 2)', fallbackDocs)
      return fallbackDocs
    }

And inside the global (no activeThread) branch:

- if (!activeThread) {
-   const remoteDocs = (remoteDocsRaw as unknown[])
+ if (!activeThread) {
+   const remoteDocsRaw = (await getAllUserThreadDocumentsMetadata()) || []
+   const remoteDocs = (remoteDocsRaw as unknown[])

Consider switching to an API route (GET /api/user/thread-docs) and fetching instead of importing server code from the client.


106-125: Merge bug: enriching all fallbackDocs with each local doc.

The inner loop assigns every matching local doc onto every fallback doc, producing duplicated/incorrect metadata. Restrict enrichment to the matching doc only and dedupe by id or key.

- const merged: WorkspaceDocumentMetadata[] = uniq([
-   ...fallbackDocs,
-   ...localDocs,
- ])
+ // Deduplicate by id (fallback to composite key)
+ const keyOf = (d: WorkspaceDocumentMetadata) =>
+   `${d.id || ''}::${d.name}::${d.project}::${d.type}`
+ const mergedMap = new Map<string, WorkspaceDocumentMetadata>()
+ for (const d of [...fallbackDocs, ...localDocs]) mergedMap.set(keyOf(d), d)
+ const merged: WorkspaceDocumentMetadata[] = Array.from(mergedMap.values())

  const isSameDoc = (
    a: WorkspaceDocumentMetadata,
    b: WorkspaceDocumentMetadata,
  ) => a.id === b.id || keyOf(a) === keyOf(b)

- for (const d of localDocs) {
-   if (byId.has(d.id) || keySet.has(keyOf(d))) {
-     for (const fd of fallbackDocs) {
-       Object.assign(fd, d)
-     }
-   }
- }
+ for (const d of localDocs) {
+   const target =
+     byId.get(d.id) ?? fallbackDocs.find((fd) => keyOf(fd) === keyOf(d))
+   if (target) Object.assign(target, d)
+ }

184-189: Make IndexedDB helpers return Promises and await them

updateItem/addItem in apps/{pro-web,web}/lib/hooks/use-indexed-db.ts call store.put/add but do not return a Promise — try/catch at call sites will not catch async IDB failures and writes can be lost or racey. Change addItem/updateItem to return a Promise (resolve on request.onsuccess or transaction.oncomplete; reject on request.onerror/transaction.onerror) and update callers to await or handle the returned Promise.

Affected locations (fix these callers):

  • apps/pro-web/lib/hooks/use-workspace-documents.ts — lines 184–189 and 261–266.
  • apps/pro-web/lib/hooks/use-mb-chat.tsx — lines ~409–416.
  • web counterparts in apps/web/lib/hooks/use-indexed-db.ts and callers (same pattern).
apps/pro-web/components/routes/thread/thread-popup.tsx (1)

261-267: “Continue Thread” link ignores org context.

When on org routes, this continues to build a personal link. Use the current routeType.

- href={`${urlBuilders.chatbotThreadListUrl({
-   type: 'personal',
+ href={`${urlBuilders.chatbotThreadListUrl({
+   type: routeType === 'org' ? 'org' : isProView ? 'pro' : 'personal',
    category: threadCategory,
    domain: canonicalDomain,
    chatbot: chatbotName as string,
 })}?continuousThreadId=${activeThread?.threadId}`}
apps/pro-web/components/routes/workspace/workspace-content.tsx (1)

613-693: First save stops after creating thread (document never uploaded).

Early return exits before upload; users must click Save again. Continue the flow after thread creation.

-          } finally {
-            setIsSaving(false)
-          }
-          return
+          } finally {
+            // keep saving flow to proceed with upload after thread creation
+          }
+          // continue to upload using the new threadSlug

If preferred, refactor thread creation into a helper that returns slug, then proceed with the unified upload path.

🧹 Nitpick comments (37)
apps/pro-web/components/shared/onboarding-chatbot-card.tsx (2)

31-33: Simplify condition with includes for readability

Equivalent logic, a bit cleaner.

-    if (routeType === 'org' || routeType === 'bot') {
+    if (['org', 'bot'].includes(routeType)) {

63-71: Alt text can render “undefined avatar”

If avatar exists but botName is missing, the alt becomes “undefined avatar”. Provide a fallback.

-                    <Image
+                    <Image
                       src={avatar}
-                      alt={`${botName} avatar`}
+                      alt={botName ? `${botName} avatar` : 'Chatbot avatar'}
                       height={128}
                       width={128}
                       className="object-cover rounded-full"
                     />
apps/pro-web/package.json (2)

13-14: Glob typo in format:check.

Missing slash after the group; current pattern likely skips nested paths.

Apply:

-    "format:check": "prettier --check \"{app,lib,components}**/*.{ts,tsx,mdx}\" --cache"
+    "format:check": "prettier --check \"{app,lib,components}/**/*.{ts,tsx,mdx}\" --cache"

5-15: Repo guideline: use Biome instead of Prettier/ESLint.

This package still wires Prettier/ESLint. Align with the repo standard (Biome) or add an alias script that invokes the workspace Biome task.

Proposed minimal script alignment:

   "scripts": {
+    "format-and-lint:fix": "biome check --write --unsafe .",
-    "lint": "next lint",
-    "lint:fix": "next lint --fix",
-    "format:write": "prettier --write \"{app,lib,components}/**/*.{ts,tsx,mdx}\" --cache",
-    "format:check": "prettier --check \"{app,lib,components}/**/*.{ts,tsx,mdx}\" --cache"
+    "lint": "biome check .",
+    "lint:fix": "biome check --write ."
   },

If Biome is managed at the repo root, alternatively add:

+  "scripts": {
+    "format-and-lint:fix": "bun run -C ../../ format-and-lint:fix"
+  },

Confirm the correct relative path for your workspace layout.

Also applies to: 111-118, 116-116

apps/pro-web/lib/hooks/use-workspace-chat.tsx (4)

490-490: Good guard against overlapping edits; consider surfacing feedback to callers.

Silent return is safe, but a short toast helps when calls are triggered programmatically.

-    if (isLoading || workspaceProcessingState !== 'idle') return
+    if (isLoading || workspaceProcessingState !== 'idle') {
+      customSonner?.info('Please wait for the current workspace operation to finish.')
+      return
+    }

194-205: Comment contradicts implemented behavior; clarify to avoid future regressions.

Current code parses from currentContent on first call, then uses the original snapshot for subsequent chunks. Align the comment.

-      // Use the original snapshot (captured at handleWorkspaceEdit start) only for FIRST streaming initialization.
-      // After that, we keep using currentContent for section parsing (structure may change) but DO NOT
-      // recalculate preserved before/after windows.
+      // Parse from currentContent only on the FIRST call to initialize streaming.
+      // For subsequent streaming chunks, parse against the original snapshot (captured at handleWorkspaceEdit start)
+      // to keep offsets stable. Do NOT recalculate preserved before/after windows.
-      // Only parse from currentContent if we haven't initialized streaming yet (first call).
+      // Only parse from currentContent if we haven't initialized streaming yet (first call).

20-27: toBase64 builds strings byte-by-byte; switch to chunked encoding with a fallback.

The reduce-based concat is O(n²) on large docs and can jank the UI.

-// Helper to safely encode a string to Base64, handling Unicode characters
-function toBase64(str: string) {
-  return btoa(
-    new Uint8Array(new TextEncoder().encode(str)).reduce(
-      (data, byte) => data + String.fromCharCode(byte),
-      '',
-    ),
-  )
-}
+// Helper to safely encode a string to Base64, handling Unicode characters (chunked for large inputs)
+function toBase64(str: string) {
+  if (typeof TextEncoder !== 'undefined') {
+    const bytes = new TextEncoder().encode(str)
+    let binary = ''
+    const chunk = 0x8000 // 32KB
+    for (let i = 0; i < bytes.length; i += chunk) {
+      binary += String.fromCharCode(...bytes.subarray(i, i + chunk))
+    }
+    return btoa(binary)
+  }
+  // Fallback for very old browsers
+  // eslint-disable-next-line deprecation/deprecation
+  return btoa(unescape(encodeURIComponent(str)))
+}

129-136: Noisy console logs; gate behind a debug flag.

Reduce production noise and bundle diffs by guarding logs (applies to other console.* in this file).

apps/pro-web/components/routes/workspace/workspace-section-item.tsx (3)

133-134: Consistent disablement across actions—nice. Add aria-disabled for extra a11y context.

Button handles disabled, but adding aria-disabled mirrors state for assistive tech consistently (esp. if Button renders non-native elements in some variants).

Also applies to: 157-158, 167-168, 177-178, 226-227, 249-250, 259-260, 269-270


68-88: Prefer requestAnimationFrame to focus after layout; verify focus success.

This aligns with our DOM‑timing guideline and avoids racey timeouts. Please verify locally (per prior preference) that refocus behaves as expected.

-    // Focus the chat textarea using the same pattern as chat-panel-pro.tsx
-    setTimeout(() => {
+    // Focus after layout/paint
+    requestAnimationFrame(() => {
       const textarea = document.querySelector(
         '[id^=prompt-textarea-]',
       ) as HTMLTextAreaElement
       if (textarea) {
         textarea.focus()
+        if (document.activeElement !== textarea) {
+          // Retry once if the first focus didn’t stick
+          requestAnimationFrame(() => textarea.focus())
+        }
         const currentValue = textarea.value
         const editPrompt = `Edit the "${section.title}" section to `
 
         if (!currentValue.includes(editPrompt) && setInput) {
           console.log('ℹ️ changing prompt to [editPrompt]: ', editPrompt)
           setInput(editPrompt)
         }
       }
-    }, 0)
+    })

24-34: Use section.id for actions instead of title to avoid duplicate‑title collisions.

Prop APIs mix ids and titles; standardize on id for delete/expand/rewrite to ensure deterministic targeting.

 interface WorkspaceSectionItemProps {
   section: MarkdownSection
   isActive: boolean
   onSectionClick: (sectionId: string) => void
-  onExpandSection: (sectionTitle: string) => void
-  onRewriteSection: (sectionTitle: string) => void
-  onDeleteSection: (sectionTitle: string) => void
+  onExpandSection: (sectionId: string) => void
+  onRewriteSection: (sectionId: string) => void
+  onDeleteSection: (sectionId: string) => void
   onRenameSection: (sectionId: string, newTitle: string) => void
   level: number
   children?: React.ReactNode
 }
@@
-              onDeleteSection(section.title)
+              onDeleteSection(section.id)
@@
-                onDeleteSection(section.title)
+                onDeleteSection(section.id)

Also applies to: 129-131, 221-224

apps/pro-web/components/routes/chat/chat-chatbot-details.tsx (1)

66-90: Use functional state updates; fix microcopy

Avoid stale-closure bugs when updating followers; fix message spacing/typo.

-      if (follow) {
-        setFollowers([
-          ...followers,
+      if (follow) {
+        setFollowers((prev) => [
+          ...prev,
           {
             followerId: followerId,
             followeeId: null,
             followeeIdChatbot: followeeId,
             chatbot: null,
             createdAt: new Date().toISOString(),
             userByFollowerId: null as unknown,
             user: null,
             __typename: 'SocialFollowing',
           } as SocialFollowing,
         ])
       } else {
-        setFollowers(
-          followers.filter(
+        setFollowers((prev) =>
+          prev.filter(
             (follower) =>
               !(
                 follower.followerId === followerId &&
                 follower.followeeIdChatbot === followeeId
               ),
-          ),
-        )
+          )
+        )
       }
 
       customSonner({
         type: 'success',
-        text: follow
-          ? `You have followed ${activeChatbot?.name} successfully`
-          : `You have  unfollowed  ${activeChatbot?.name}`,
+        text: follow
+          ? `You have followed ${activeChatbot?.name} successfully`
+          : `You have unfollowed ${activeChatbot?.name}`,
       })

Also applies to: 92-98

apps/pro-web/components/shared/skeletons/thread-popup-skeleton.tsx (1)

17-21: Route-type update to 'org' for browse view: looks good

Matches the routing shift and preserves skeleton layout behavior.

Extract a small helper to compute isBrowseView to de-duplicate logic across both components.

Also applies to: 42-46

apps/pro-web/components/routes/chat/chat-onboarding-chatbot-mobile.tsx (1)

13-18: CTA route-type check updated to 'org': OK; minor copy tweak

Approve the route-type change. Consider clearer copy: “Go to the sidebar and select a bot.”

-    return 'Go To Sidebar And Select One Bot'
+    return 'Go to the sidebar and select a bot'
apps/pro-web/lib/hooks/use-thread-visibility.tsx (1)

107-112: User-facing copy: “org” likely incorrect for visibility toggle

Data model/property is isPublic; showing “Thread is now org!” is confusing. Prefer “public” (or a clearer org‑scoped phrase agreed with PM/i18n).

Apply this diff:

- text: `Thread is now ${newIsPublic ? 'org' : 'private'}!`,
+ text: `Thread is now ${newIsPublic ? 'public' : 'private'}!`,
apps/pro-web/lib/threads.ts (2)

111-117: Remove noisy console logs or behind a debug flag.

Ship builds shouldn’t emit these logs; gate with NODE_ENV or a debug util.

- // console.log('pathname', { ... })
+ // Debug: uncomment if needed
+ // if (process.env.NODE_ENV !== 'production') console.log('pathname', { ... })

160-163: Mismatch: awaiting a void callback.

activatePopup is typed to return void; awaiting it is misleading. Either update its type to Promise or drop await.

- await activatePopup(thread)
+ activatePopup(thread)

Or:

- activatePopup: (thread: Thread) => void,
+ activatePopup: (thread: Thread) => Promise<void>,
apps/pro-web/components/onboarding/onboarding-card.tsx (1)

158-172: Hide decorative arrow from SRs.

Mark the arrow as aria-hidden to avoid noise for assistive tech.

- <div
+ <div
+   aria-hidden="true"
    className={cn(
apps/web/components/onboarding/onboarding-card.tsx (1)

158-172: Accessibility: hide decorative arrow.

Add aria-hidden.

- <div
+ <div aria-hidden="true"
apps/pro-web/lib/hooks/use-workspace-documents.ts (2)

151-173: Network/resource safeguards.

Consider an AbortController per fetch, a max size guard before base64 conversion, and concurrency limiting to avoid memory spikes on many/large documents.


299-363: Workspace filter may exclude docs before lists are backfilled.

Since updateWorkspaceFromDocuments mutates lists after value resolves, the first render can filter out valid docs. Optional: derive acceptance from documents themselves when lists are empty, then tighten after lists hydrate.

apps/pro-web/components/layout/providers.tsx (1)

122-133: OnboardingGate structure is fine; minor perf nit.

NextStep subtree (with AppProviders as child) will re-render when showOnboarding toggles. If flicker occurs, consider keeping AppProviders outside and rendering NextStep conditionally inside to preserve contexts.

apps/pro-web/lib/api/documents.ts (2)

23-28: Also validate thread slug

Calls below assume thread.slug. Add a guard to avoid passing undefined to the action.

   if (!document || !workspace || !thread) {
     return {
       data: null,
       error: 'Document, workspace, and thread data are required.',
     }
   }
+  if (!thread.slug) {
+    return { data: null, error: 'Thread slug is required.' }
+  }

65-70: Redact dev logging to avoid dumping signed URLs and full metadata

Even in dev, avoid logging signed URLs and full document lists.

-    console.info(
-      'Workspace document uploaded successfully to gCloud Bucket:',
-      uploadResults,
-    )
+    console.info('Workspace document uploaded', {
+      existed: uploadResults?.existed,
+      documentId: uploadResults?.document?.id,
+      threadSlug: thread.slug,
+    })
apps/pro-web/app/api/documents/upload/route.ts (1)

35-48: DRY: route and lib both validate; keep the 400s here and let lib focus on execution

Keeping the 400s at the edge and letting uploadWorkspaceDocument concentrate on execution reduces status-code ambiguity later. Current duplication is OK for now; ensure both sets stay in sync.

apps/pro-web/app/org/sitemap.ts (1)

29-37: Rename locals for clarity (optional)

Vars are still named public* after switching to org. Consider renaming to orgCategoryUrls / orgNestedUrls for readability.

Also applies to: 40-52

apps/pro-web/components/routes/thread/thread-list.tsx (1)

126-130: Replace placeholder biome-ignore reason and avoid floating promise.

Give a concrete reason in the directive and prefix the async call with void to silence unhandled-promise warnings.

- // biome-ignore lint/correctness/useExhaustiveDependencies: <explanation>
+ // biome-ignore lint/correctness/useExhaustiveDependencies: run once on mount to open thread from URL; getOpeningActiveThread is stable via useAsyncFn([])
 useEffect(() => {
   if (activeThread) return
-  getOpeningActiveThread()
+  void getOpeningActiveThread()
 }, [])
apps/pro-web/app/robots.ts (1)

23-37: Robots now emits org URLs; minor naming/consistency nits.

Logic is consistent with the org migration. Consider renaming publicCategoryUrls/publicNestedUrls to orgCategoryUrls/orgNestedUrls for clarity. Also confirm BASE_URL is set in all envs so the sitemap is absolute.

apps/pro-web/components/shared/category-dashboard.tsx (3)

142-145: Route-type styling pivot to pro.

LGTM. Optional: extract route accent classes to a helper to avoid repeating ternaries.


186-187: Consistent accent color mapping.

Same suggestion: centralize class selection (e.g., getAccent(routeType)).


193-194: Background classes duplication.

Consider using the same helper to pick bg/border pairs to keep variants in sync.

apps/pro-web/components/routes/thread/thread-popup.tsx (1)

312-316: Avoid repeated getRouteType calls in header.

Compute once, then derive booleans to reduce work and keep conditions in sync.

-const isOrg = getRouteType(pathname) === 'org'
-const isProfile = getRouteType(pathname) === 'profile'
-const isBot = getRouteType(pathname) === 'bot'
-const isPro = getRouteType(pathname) === 'pro'
+const routeTypeHeader = getRouteType(pathname)
+const isOrg = routeTypeHeader === 'org'
+const isProfile = routeTypeHeader === 'profile'
+const isBot = routeTypeHeader === 'bot'
+const isPro = routeTypeHeader === 'pro'
apps/pro-web/components/layout/sidebar/sidebar-link.tsx (1)

172-181: Fallback URL still uses legacy “/c”.

If “/c” is deprecated, prefer '/' (pro) or '/org' consistently; otherwise confirm it still resolves correctly.

-const fallbackUrl = isPro ? '/' : isOrg ? '/org' : '/c'
+const fallbackUrl = isPro ? '/' : isOrg ? '/org' : '/'
apps/pro-web/lib/utils.ts (2)

306-306: Fix JSDoc return type.

Docs don’t match actual return values.

- * @returns 'chat' | 'org' | ''
+ * @returns 'chat' | 'org' | 'profile' | 'pro' | 'bot'

316-326: Stale comment (“Check for chat routes”).

The block actually checks org routes. Update comment to prevent misreads.

-// ? Check for chat routes (starting with /c but not being /career)
+// ? Check for org routes (starting with /org, excluding specific subpaths)
apps/pro-web/lib/cron/refresh-workspace-document-links.ts (2)

214-220: Use Date for signed URL expiry to match GCS expectations.

Passing a Date avoids ambiguity around ms vs. ISO.

-const [signedUrl] = await file.getSignedUrl({
+const [signedUrl] = await file.getSignedUrl({
   version: 'v4',
   action: 'read',
-  expires: newExpiry,
+  expires: new Date(newExpiry),
 })

106-111: Type the bucket parameter.

Prefer the SDK type for stronger checks.

-import type { WorkspaceDocumentMetadata } from '@/types/thread.types'
+import type { WorkspaceDocumentMetadata } from '@/types/thread.types'
+import type { Bucket } from '@google-cloud/storage'
...
-  // biome-ignore lint/suspicious/noExplicitAny: <explanation>
-  bucket: any,
+  bucket: Bucket,
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ce8246e and 32b7a1b.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (47)
  • apps/pro-web/app/api/cron/refresh-workspace-document-links/route.ts (1 hunks)
  • apps/pro-web/app/api/documents/upload/route.ts (1 hunks)
  • apps/pro-web/app/b/[botSlug]/[threadSlug]/[threadQuestionSlug]/page.tsx (1 hunks)
  • apps/pro-web/app/b/[botSlug]/[threadSlug]/page.tsx (1 hunks)
  • apps/pro-web/app/b/[botSlug]/page.tsx (1 hunks)
  • apps/pro-web/app/org/sitemap.ts (2 hunks)
  • apps/pro-web/app/robots.ts (1 hunks)
  • apps/pro-web/app/u/[userSlug]/t/[category]/[domain]/[chatbot]/[threadSlug]/[threadQuestionSlug]/page.tsx (1 hunks)
  • apps/pro-web/app/u/[userSlug]/t/[category]/[domain]/[chatbot]/[threadSlug]/page.tsx (1 hunks)
  • apps/pro-web/app/u/[userSlug]/t/[category]/[domain]/[chatbot]/page.tsx (1 hunks)
  • apps/pro-web/components/layout/header/header.tsx (1 hunks)
  • apps/pro-web/components/layout/providers.tsx (2 hunks)
  • apps/pro-web/components/layout/sidebar/sidebar-filter-input.tsx (2 hunks)
  • apps/pro-web/components/layout/sidebar/sidebar-link.tsx (5 hunks)
  • apps/pro-web/components/layout/sidebar/sidebar.tsx (1 hunks)
  • apps/pro-web/components/onboarding/onboarding-card.tsx (3 hunks)
  • apps/pro-web/components/routes/browse/browse-chat-message-list.tsx (3 hunks)
  • apps/pro-web/components/routes/chat/chat-accordion.tsx (2 hunks)
  • apps/pro-web/components/routes/chat/chat-chatbot-details.tsx (1 hunks)
  • apps/pro-web/components/routes/chat/chat-message.tsx (1 hunks)
  • apps/pro-web/components/routes/chat/chat-onboarding-chatbot-mobile.tsx (1 hunks)
  • apps/pro-web/components/routes/pro/chat-message-pro.tsx (1 hunks)
  • apps/pro-web/components/routes/pro/chat-panel-pro.tsx (1 hunks)
  • apps/pro-web/components/routes/thread/profile-avatar.tsx (1 hunks)
  • apps/pro-web/components/routes/thread/thread-list.tsx (1 hunks)
  • apps/pro-web/components/routes/thread/thread-popup.tsx (2 hunks)
  • apps/pro-web/components/routes/thread/user-thread-panel.tsx (3 hunks)
  • apps/pro-web/components/routes/workspace/workspace-content.tsx (4 hunks)
  • apps/pro-web/components/routes/workspace/workspace-section-item.tsx (9 hunks)
  • apps/pro-web/components/shared/category-dashboard.tsx (2 hunks)
  • apps/pro-web/components/shared/onboarding-chatbot-card.tsx (1 hunks)
  • apps/pro-web/components/shared/skeletons/thread-popup-skeleton.tsx (2 hunks)
  • apps/pro-web/lib/api/documents.ts (1 hunks)
  • apps/pro-web/lib/cron/index.ts (1 hunks)
  • apps/pro-web/lib/cron/refresh-workspace-document-links.ts (1 hunks)
  • apps/pro-web/lib/hooks/use-image-generation.tsx (1 hunks)
  • apps/pro-web/lib/hooks/use-mb-chat.tsx (1 hunks)
  • apps/pro-web/lib/hooks/use-thread-visibility.tsx (1 hunks)
  • apps/pro-web/lib/hooks/use-workspace-chat.tsx (1 hunks)
  • apps/pro-web/lib/hooks/use-workspace-documents.ts (1 hunks)
  • apps/pro-web/lib/metadata.ts (1 hunks)
  • apps/pro-web/lib/threads.ts (4 hunks)
  • apps/pro-web/lib/utils.ts (1 hunks)
  • apps/pro-web/package.json (1 hunks)
  • apps/pro-web/services/hasura/hasura.service.ts (2 hunks)
  • apps/pro-web/vercel.json (1 hunks)
  • apps/web/components/onboarding/onboarding-card.tsx (3 hunks)
🧰 Additional context used
📓 Path-based instructions (7)
**/*.{ts,tsx,js,jsx,cjs,mjs,json,jsonc}

📄 CodeRabbit inference engine (CLAUDE.md)

Use Biome for all formatting and linting (run bun format-and-lint:fix) instead of Prettier/ESLint

Files:

  • apps/pro-web/app/api/cron/refresh-workspace-document-links/route.ts
  • apps/pro-web/app/b/[botSlug]/page.tsx
  • apps/pro-web/vercel.json
  • apps/pro-web/components/layout/sidebar/sidebar-filter-input.tsx
  • apps/pro-web/lib/hooks/use-workspace-chat.tsx
  • apps/pro-web/lib/metadata.ts
  • apps/pro-web/components/routes/pro/chat-panel-pro.tsx
  • apps/pro-web/components/routes/chat/chat-onboarding-chatbot-mobile.tsx
  • apps/pro-web/components/routes/thread/thread-list.tsx
  • apps/pro-web/components/routes/chat/chat-message.tsx
  • apps/pro-web/lib/api/documents.ts
  • apps/pro-web/components/routes/thread/profile-avatar.tsx
  • apps/pro-web/lib/cron/index.ts
  • apps/pro-web/services/hasura/hasura.service.ts
  • apps/pro-web/package.json
  • apps/pro-web/lib/cron/refresh-workspace-document-links.ts
  • apps/pro-web/lib/threads.ts
  • apps/pro-web/components/layout/sidebar/sidebar.tsx
  • apps/pro-web/components/routes/pro/chat-message-pro.tsx
  • apps/pro-web/components/shared/skeletons/thread-popup-skeleton.tsx
  • apps/pro-web/components/routes/thread/user-thread-panel.tsx
  • apps/pro-web/components/shared/onboarding-chatbot-card.tsx
  • apps/pro-web/components/routes/chat/chat-chatbot-details.tsx
  • apps/pro-web/components/layout/providers.tsx
  • apps/pro-web/app/api/documents/upload/route.ts
  • apps/pro-web/components/layout/header/header.tsx
  • apps/pro-web/components/routes/chat/chat-accordion.tsx
  • apps/pro-web/app/robots.ts
  • apps/pro-web/app/u/[userSlug]/t/[category]/[domain]/[chatbot]/[threadSlug]/[threadQuestionSlug]/page.tsx
  • apps/pro-web/app/u/[userSlug]/t/[category]/[domain]/[chatbot]/[threadSlug]/page.tsx
  • apps/pro-web/components/routes/browse/browse-chat-message-list.tsx
  • apps/pro-web/components/shared/category-dashboard.tsx
  • apps/pro-web/app/org/sitemap.ts
  • apps/pro-web/app/b/[botSlug]/[threadSlug]/page.tsx
  • apps/pro-web/components/layout/sidebar/sidebar-link.tsx
  • apps/pro-web/lib/hooks/use-image-generation.tsx
  • apps/pro-web/app/b/[botSlug]/[threadSlug]/[threadQuestionSlug]/page.tsx
  • apps/pro-web/components/onboarding/onboarding-card.tsx
  • apps/pro-web/lib/hooks/use-workspace-documents.ts
  • apps/pro-web/lib/hooks/use-thread-visibility.tsx
  • apps/pro-web/components/routes/thread/thread-popup.tsx
  • apps/pro-web/app/u/[userSlug]/t/[category]/[domain]/[chatbot]/page.tsx
  • apps/pro-web/components/routes/workspace/workspace-content.tsx
  • apps/pro-web/lib/hooks/use-mb-chat.tsx
  • apps/pro-web/components/routes/workspace/workspace-section-item.tsx
  • apps/pro-web/lib/utils.ts
  • apps/web/components/onboarding/onboarding-card.tsx
apps/{web,pro-web}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

apps/{web,pro-web}/**/*.{ts,tsx}: Use feature detection before invoking modern Browser APIs
Implement progressive enhancement: basic functionality first, then enhancements
Add timing delays or requestAnimationFrame when the DOM needs to settle before operations
Verify DOM operations completed successfully; don’t assume success (e.g., scroll/check actual state)
Use multiple approaches for DOM actions: try modern APIs first, then fallback to legacy methods

Files:

  • apps/pro-web/app/api/cron/refresh-workspace-document-links/route.ts
  • apps/pro-web/app/b/[botSlug]/page.tsx
  • apps/pro-web/components/layout/sidebar/sidebar-filter-input.tsx
  • apps/pro-web/lib/hooks/use-workspace-chat.tsx
  • apps/pro-web/lib/metadata.ts
  • apps/pro-web/components/routes/pro/chat-panel-pro.tsx
  • apps/pro-web/components/routes/chat/chat-onboarding-chatbot-mobile.tsx
  • apps/pro-web/components/routes/thread/thread-list.tsx
  • apps/pro-web/components/routes/chat/chat-message.tsx
  • apps/pro-web/lib/api/documents.ts
  • apps/pro-web/components/routes/thread/profile-avatar.tsx
  • apps/pro-web/lib/cron/index.ts
  • apps/pro-web/services/hasura/hasura.service.ts
  • apps/pro-web/lib/cron/refresh-workspace-document-links.ts
  • apps/pro-web/lib/threads.ts
  • apps/pro-web/components/layout/sidebar/sidebar.tsx
  • apps/pro-web/components/routes/pro/chat-message-pro.tsx
  • apps/pro-web/components/shared/skeletons/thread-popup-skeleton.tsx
  • apps/pro-web/components/routes/thread/user-thread-panel.tsx
  • apps/pro-web/components/shared/onboarding-chatbot-card.tsx
  • apps/pro-web/components/routes/chat/chat-chatbot-details.tsx
  • apps/pro-web/components/layout/providers.tsx
  • apps/pro-web/app/api/documents/upload/route.ts
  • apps/pro-web/components/layout/header/header.tsx
  • apps/pro-web/components/routes/chat/chat-accordion.tsx
  • apps/pro-web/app/robots.ts
  • apps/pro-web/app/u/[userSlug]/t/[category]/[domain]/[chatbot]/[threadSlug]/[threadQuestionSlug]/page.tsx
  • apps/pro-web/app/u/[userSlug]/t/[category]/[domain]/[chatbot]/[threadSlug]/page.tsx
  • apps/pro-web/components/routes/browse/browse-chat-message-list.tsx
  • apps/pro-web/components/shared/category-dashboard.tsx
  • apps/pro-web/app/org/sitemap.ts
  • apps/pro-web/app/b/[botSlug]/[threadSlug]/page.tsx
  • apps/pro-web/components/layout/sidebar/sidebar-link.tsx
  • apps/pro-web/lib/hooks/use-image-generation.tsx
  • apps/pro-web/app/b/[botSlug]/[threadSlug]/[threadQuestionSlug]/page.tsx
  • apps/pro-web/components/onboarding/onboarding-card.tsx
  • apps/pro-web/lib/hooks/use-workspace-documents.ts
  • apps/pro-web/lib/hooks/use-thread-visibility.tsx
  • apps/pro-web/components/routes/thread/thread-popup.tsx
  • apps/pro-web/app/u/[userSlug]/t/[category]/[domain]/[chatbot]/page.tsx
  • apps/pro-web/components/routes/workspace/workspace-content.tsx
  • apps/pro-web/lib/hooks/use-mb-chat.tsx
  • apps/pro-web/components/routes/workspace/workspace-section-item.tsx
  • apps/pro-web/lib/utils.ts
  • apps/web/components/onboarding/onboarding-card.tsx
apps/{web,pro-web}/**/*.tsx

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

apps/{web,pro-web}/**/*.tsx: Minimize useEffect dependencies to avoid infinite loops
Use refs for values that should not trigger re-renders
Consider component lifecycle timing when performing DOM operations from React components
Separate concerns: state updates vs. direct DOM manipulation in React components

Files:

  • apps/pro-web/app/b/[botSlug]/page.tsx
  • apps/pro-web/components/layout/sidebar/sidebar-filter-input.tsx
  • apps/pro-web/lib/hooks/use-workspace-chat.tsx
  • apps/pro-web/components/routes/pro/chat-panel-pro.tsx
  • apps/pro-web/components/routes/chat/chat-onboarding-chatbot-mobile.tsx
  • apps/pro-web/components/routes/thread/thread-list.tsx
  • apps/pro-web/components/routes/chat/chat-message.tsx
  • apps/pro-web/components/routes/thread/profile-avatar.tsx
  • apps/pro-web/components/layout/sidebar/sidebar.tsx
  • apps/pro-web/components/routes/pro/chat-message-pro.tsx
  • apps/pro-web/components/shared/skeletons/thread-popup-skeleton.tsx
  • apps/pro-web/components/routes/thread/user-thread-panel.tsx
  • apps/pro-web/components/shared/onboarding-chatbot-card.tsx
  • apps/pro-web/components/routes/chat/chat-chatbot-details.tsx
  • apps/pro-web/components/layout/providers.tsx
  • apps/pro-web/components/layout/header/header.tsx
  • apps/pro-web/components/routes/chat/chat-accordion.tsx
  • apps/pro-web/app/u/[userSlug]/t/[category]/[domain]/[chatbot]/[threadSlug]/[threadQuestionSlug]/page.tsx
  • apps/pro-web/app/u/[userSlug]/t/[category]/[domain]/[chatbot]/[threadSlug]/page.tsx
  • apps/pro-web/components/routes/browse/browse-chat-message-list.tsx
  • apps/pro-web/components/shared/category-dashboard.tsx
  • apps/pro-web/app/b/[botSlug]/[threadSlug]/page.tsx
  • apps/pro-web/components/layout/sidebar/sidebar-link.tsx
  • apps/pro-web/lib/hooks/use-image-generation.tsx
  • apps/pro-web/app/b/[botSlug]/[threadSlug]/[threadQuestionSlug]/page.tsx
  • apps/pro-web/components/onboarding/onboarding-card.tsx
  • apps/pro-web/lib/hooks/use-thread-visibility.tsx
  • apps/pro-web/components/routes/thread/thread-popup.tsx
  • apps/pro-web/app/u/[userSlug]/t/[category]/[domain]/[chatbot]/page.tsx
  • apps/pro-web/components/routes/workspace/workspace-content.tsx
  • apps/pro-web/lib/hooks/use-mb-chat.tsx
  • apps/pro-web/components/routes/workspace/workspace-section-item.tsx
  • apps/web/components/onboarding/onboarding-card.tsx
apps/**/lib/hooks/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Store custom React hooks under lib/hooks/

Files:

  • apps/pro-web/lib/hooks/use-workspace-chat.tsx
  • apps/pro-web/lib/hooks/use-image-generation.tsx
  • apps/pro-web/lib/hooks/use-workspace-documents.ts
  • apps/pro-web/lib/hooks/use-thread-visibility.tsx
  • apps/pro-web/lib/hooks/use-mb-chat.tsx
apps/**/components/routes/**

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Place page-specific components under components/routes/[feature]/

Files:

  • apps/pro-web/components/routes/pro/chat-panel-pro.tsx
  • apps/pro-web/components/routes/chat/chat-onboarding-chatbot-mobile.tsx
  • apps/pro-web/components/routes/thread/thread-list.tsx
  • apps/pro-web/components/routes/chat/chat-message.tsx
  • apps/pro-web/components/routes/thread/profile-avatar.tsx
  • apps/pro-web/components/routes/pro/chat-message-pro.tsx
  • apps/pro-web/components/routes/thread/user-thread-panel.tsx
  • apps/pro-web/components/routes/chat/chat-chatbot-details.tsx
  • apps/pro-web/components/routes/chat/chat-accordion.tsx
  • apps/pro-web/components/routes/browse/browse-chat-message-list.tsx
  • apps/pro-web/components/routes/thread/thread-popup.tsx
  • apps/pro-web/components/routes/workspace/workspace-content.tsx
  • apps/pro-web/components/routes/workspace/workspace-section-item.tsx
apps/**/services/**/*.ts

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Store external API integration code under services/

Files:

  • apps/pro-web/services/hasura/hasura.service.ts
apps/**/components/shared/**

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Place reusable UI elements under components/shared/

Files:

  • apps/pro-web/components/shared/skeletons/thread-popup-skeleton.tsx
  • apps/pro-web/components/shared/onboarding-chatbot-card.tsx
  • apps/pro-web/components/shared/category-dashboard.tsx
🧠 Learnings (16)
📚 Learning: 2025-04-26T10:58:44.341Z
Learnt from: AndlerRL
PR: bitcashorg/masterbots#465
File: apps/masterbots.ai/components/routes/browse/browse-list.tsx:1-1
Timestamp: 2025-04-26T10:58:44.341Z
Learning: The browse-list.tsx component and related components in the browse directory are redundant since thread-list.tsx has been developed to support all pages including public pages, and should be removed to simplify the codebase.

Applied to files:

  • apps/pro-web/components/layout/sidebar/sidebar-filter-input.tsx
  • apps/pro-web/components/routes/thread/thread-list.tsx
  • apps/pro-web/components/routes/chat/chat-message.tsx
  • apps/pro-web/components/routes/thread/profile-avatar.tsx
  • apps/pro-web/components/routes/pro/chat-message-pro.tsx
  • apps/pro-web/components/shared/skeletons/thread-popup-skeleton.tsx
  • apps/pro-web/components/routes/thread/user-thread-panel.tsx
  • apps/pro-web/components/routes/browse/browse-chat-message-list.tsx
  • apps/pro-web/components/routes/thread/thread-popup.tsx
📚 Learning: 2025-04-26T10:58:44.341Z
Learnt from: AndlerRL
PR: bitcashorg/masterbots#465
File: apps/masterbots.ai/components/routes/browse/browse-list.tsx:1-1
Timestamp: 2025-04-26T10:58:44.341Z
Learning: The browse-list.tsx component is redundant as thread-list.tsx has been developed to support all the same functionality including public pages. The codebase should be cleaned up to remove browse-list.tsx and related components in the browse directory, along with the BrowseProvider context.

Applied to files:

  • apps/pro-web/components/layout/sidebar/sidebar-filter-input.tsx
  • apps/pro-web/components/routes/thread/thread-list.tsx
  • apps/pro-web/components/routes/chat/chat-message.tsx
  • apps/pro-web/components/routes/thread/profile-avatar.tsx
  • apps/pro-web/components/routes/pro/chat-message-pro.tsx
  • apps/pro-web/components/shared/skeletons/thread-popup-skeleton.tsx
  • apps/pro-web/components/routes/thread/user-thread-panel.tsx
  • apps/pro-web/components/routes/browse/browse-chat-message-list.tsx
  • apps/pro-web/components/routes/thread/thread-popup.tsx
📚 Learning: 2025-02-17T16:51:19.102Z
Learnt from: Bran18
PR: bitcashorg/masterbots#376
File: apps/masterbots.ai/components/routes/chat/chat.tsx:92-102
Timestamp: 2025-02-17T16:51:19.102Z
Learning: For the Chat component in apps/masterbots.ai/components/routes/chat/chat.tsx, the developer prefers to verify potential optimizations through local testing before accepting suggestions, particularly for performance-related changes like useCallback usage in debounce implementations.

Applied to files:

  • apps/pro-web/lib/hooks/use-workspace-chat.tsx
  • apps/pro-web/components/routes/chat/chat-onboarding-chatbot-mobile.tsx
  • apps/pro-web/components/routes/chat/chat-message.tsx
  • apps/pro-web/components/routes/pro/chat-message-pro.tsx
  • apps/pro-web/components/routes/chat/chat-chatbot-details.tsx
📚 Learning: 2025-09-12T20:31:46.651Z
Learnt from: CR
PR: bitcashorg/masterbots#0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-12T20:31:46.651Z
Learning: Use custom hooks (useMBChat, useThread, useSidebar, useModel) to encapsulate complex state logic

Applied to files:

  • apps/pro-web/lib/hooks/use-workspace-chat.tsx
  • apps/pro-web/lib/hooks/use-mb-chat.tsx
📚 Learning: 2024-12-11T19:40:37.103Z
Learnt from: Bran18
PR: bitcashorg/masterbots#324
File: apps/masterbots.ai/components/routes/browse/browse-chatbot-mobile-details.tsx:145-146
Timestamp: 2024-12-11T19:40:37.103Z
Learning: In `apps/masterbots.ai/components/routes/browse/browse-chatbot-mobile-details.tsx`, the follower and following counts are currently hardcoded as these features are being developed in other PRs.

Applied to files:

  • apps/pro-web/components/routes/chat/chat-onboarding-chatbot-mobile.tsx
  • apps/pro-web/components/routes/chat/chat-message.tsx
  • apps/pro-web/components/routes/pro/chat-message-pro.tsx
  • apps/pro-web/components/routes/chat/chat-chatbot-details.tsx
📚 Learning: 2025-09-12T20:31:46.651Z
Learnt from: CR
PR: bitcashorg/masterbots#0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-12T20:31:46.651Z
Learning: Applies to **/ai-main-call.actions.{ts,tsx} : Route AI calls through ai-main-call.actions for unified AI API handling

Applied to files:

  • apps/pro-web/components/routes/chat/chat-onboarding-chatbot-mobile.tsx
  • apps/pro-web/components/shared/onboarding-chatbot-card.tsx
  • apps/pro-web/components/routes/chat/chat-chatbot-details.tsx
📚 Learning: 2024-11-07T14:40:39.595Z
Learnt from: Bran18
PR: bitcashorg/masterbots#301
File: apps/masterbots.ai/components/routes/chat/chat-combobox.tsx:33-33
Timestamp: 2024-11-07T14:40:39.595Z
Learning: In `apps/masterbots.ai/components/routes/chat/chat-combobox.tsx`, the 'MB' logo is temporarily represented by the string `'MB'` until the custom MB icon is created.

Applied to files:

  • apps/pro-web/components/routes/chat/chat-onboarding-chatbot-mobile.tsx
📚 Learning: 2024-11-20T04:00:45.413Z
Learnt from: AndlerRL
PR: bitcashorg/masterbots#300
File: apps/masterbots.ai/components/routes/thread/user-thread-panel.tsx:120-123
Timestamp: 2024-11-20T04:00:45.413Z
Learning: In React code, when reviewing `useEffect` dependencies, it's not always necessary to add functions like `handleThreadsChange` to the dependency array, as this may lead to unnecessary re-renders and performance issues. Only include dependencies that are essential and may change.

Applied to files:

  • apps/pro-web/components/routes/thread/thread-list.tsx
📚 Learning: 2025-09-12T20:31:46.651Z
Learnt from: CR
PR: bitcashorg/masterbots#0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-12T20:31:46.651Z
Learning: Applies to apps/{web,pro-web}/**/*.tsx : Minimize useEffect dependencies to avoid infinite loops

Applied to files:

  • apps/pro-web/components/routes/thread/thread-list.tsx
📚 Learning: 2024-10-23T19:11:47.520Z
Learnt from: AndlerRL
PR: bitcashorg/masterbots#287
File: apps/masterbots.ai/components/routes/thread/user-thread-panel.tsx:39-45
Timestamp: 2024-10-23T19:11:47.520Z
Learning: In the `UserThreadPanel` component (`apps/masterbots.ai/components/routes/thread/user-thread-panel.tsx`), the `count` state variable is used in the `loadMore` function and depends on `finalThreads.length`, so updating `count` in the `useEffect` is necessary.

Applied to files:

  • apps/pro-web/components/routes/thread/thread-list.tsx
  • apps/pro-web/components/routes/thread/user-thread-panel.tsx
📚 Learning: 2025-03-28T21:43:52.984Z
Learnt from: AndlerRL
PR: bitcashorg/masterbots#425
File: apps/masterbots.ai/lib/utils.ts:308-308
Timestamp: 2025-03-28T21:43:52.984Z
Learning: The 'pro' route type was intentionally added to the RouteType definition before its implementation logic, as the complete implementation exists in a separate branch (feat-pro-layout). This reflects an incremental development approach where type definitions may be updated in advance of their full implementation.

Applied to files:

  • apps/pro-web/components/routes/pro/chat-message-pro.tsx
  • apps/pro-web/components/shared/category-dashboard.tsx
  • apps/pro-web/lib/utils.ts
📚 Learning: 2024-11-28T07:00:08.096Z
Learnt from: AndlerRL
PR: bitcashorg/masterbots#313
File: apps/masterbots.ai/lib/hooks/use-sidebar.tsx:218-227
Timestamp: 2024-11-28T07:00:08.096Z
Learning: When navigating between categories and chatbots routes, we should use shallow push state with Next.js. For transitions between main pages (e.g., from `/` to `/c`), we should perform full redirects.

Applied to files:

  • apps/pro-web/components/routes/thread/user-thread-panel.tsx
  • apps/pro-web/components/layout/sidebar/sidebar-link.tsx
📚 Learning: 2025-08-30T08:10:50.198Z
Learnt from: CR
PR: bitcashorg/masterbots#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-30T08:10:50.198Z
Learning: Applies to apps/masterbots.ai/components/routes/**/*.{ts,tsx} : Route components must be organized under components/routes

Applied to files:

  • apps/pro-web/components/routes/chat/chat-chatbot-details.tsx
📚 Learning: 2024-11-28T07:10:40.413Z
Learnt from: AndlerRL
PR: bitcashorg/masterbots#313
File: apps/masterbots.ai/lib/hooks/use-sidebar.tsx:218-227
Timestamp: 2024-11-28T07:10:40.413Z
Learning: In the profile page `u/{username}/t`, when navigating between categories and chatbots, we should use shallow routing to enhance navigation performance.

Applied to files:

  • apps/pro-web/components/layout/sidebar/sidebar-link.tsx
  • apps/pro-web/components/routes/thread/thread-popup.tsx
📚 Learning: 2025-06-25T20:11:57.360Z
Learnt from: AndlerRL
PR: bitcashorg/masterbots#516
File: apps/masterbots.ai/lib/url.ts:592-615
Timestamp: 2025-06-25T20:11:57.360Z
Learning: Bot profiles use a different URL structure than user profiles. For bot profiles, the URL format is `/b/{chatbot}/{threadSlug}` and only requires chatbot, threadSlug, and optionally threadQuestionSlug parameters. Bot profiles should not use the user profile URL format (`/u/{usernameSlug}/t/{category}/{domain}/{chatbot}/{threadSlug}`) which requires usernameSlug, domain, and category parameters that are not available in bot profile contexts.

Applied to files:

  • apps/pro-web/components/layout/sidebar/sidebar-link.tsx
📚 Learning: 2025-09-12T20:31:46.651Z
Learnt from: CR
PR: bitcashorg/masterbots#0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-12T20:31:46.651Z
Learning: Editor components in Pro workspace follow controlled/uncontrolled patterns; manage section state carefully; consider auto-scroll timing

Applied to files:

  • apps/pro-web/components/routes/workspace/workspace-section-item.tsx
🧬 Code graph analysis (13)
apps/pro-web/lib/api/documents.ts (3)
apps/pro-web/types/thread.types.ts (1)
  • WorkspaceDocumentMetadata (12-27)
apps/pro-web/app/actions/thread.actions.ts (1)
  • uploadWorkspaceDocumentToBucket (294-463)
packages/mb-env/src/config.env.ts (1)
  • appConfig (2-62)
apps/pro-web/components/routes/thread/profile-avatar.tsx (1)
apps/pro-web/lib/hooks/use-thread-visibility.tsx (1)
  • useThreadVisibility (48-56)
apps/pro-web/lib/cron/refresh-workspace-document-links.ts (4)
packages/mb-env/src/config.env.ts (1)
  • appConfig (2-62)
packages/mb-drizzle/src/index.ts (1)
  • db (10-10)
packages/mb-drizzle/src/drizzle/schema.ts (1)
  • thread (591-642)
apps/pro-web/types/thread.types.ts (2)
  • ThreadMetadata (29-32)
  • WorkspaceDocumentMetadata (12-27)
apps/pro-web/components/routes/thread/user-thread-panel.tsx (1)
apps/pro-web/components/routes/chat/chat-chatbot-details.tsx (1)
  • ChatChatbotDetails (14-141)
apps/pro-web/app/api/documents/upload/route.ts (2)
packages/mb-drizzle/src/drizzle/schema.ts (1)
  • thread (591-642)
apps/pro-web/lib/api/documents.ts (1)
  • uploadWorkspaceDocument (18-100)
apps/pro-web/components/routes/browse/browse-chat-message-list.tsx (1)
apps/pro-web/lib/utils.ts (1)
  • getRouteType (310-348)
apps/pro-web/components/layout/sidebar/sidebar-link.tsx (1)
apps/pro-web/lib/url.ts (1)
  • urlBuilders (138-733)
apps/pro-web/components/onboarding/onboarding-card.tsx (1)
apps/pro-web/lib/utils.ts (1)
  • cn (9-11)
apps/pro-web/lib/hooks/use-workspace-documents.ts (2)
apps/pro-web/app/actions/thread.actions.ts (1)
  • getAllUserThreadDocumentsMetadata (490-506)
apps/pro-web/types/thread.types.ts (1)
  • WorkspaceDocumentMetadata (12-27)
apps/pro-web/components/routes/thread/thread-popup.tsx (1)
apps/pro-web/lib/utils.ts (1)
  • getRouteType (310-348)
apps/pro-web/components/routes/workspace/workspace-content.tsx (1)
apps/pro-web/lib/api/documents.ts (1)
  • uploadWorkspaceDocument (18-100)
apps/pro-web/components/routes/workspace/workspace-section-item.tsx (1)
apps/pro-web/lib/hooks/use-workspace-chat.tsx (1)
  • useWorkspaceChat (630-638)
apps/web/components/onboarding/onboarding-card.tsx (1)
apps/pro-web/lib/utils.ts (1)
  • cn (9-11)

Comment on lines +3 to +5
export async function POST(request: Request) {
const { document, workspace, thread } = await request.json()

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

Wrap JSON parsing to return 400 on invalid bodies

request.json() throws on invalid JSON, currently causing a 500. Handle and return 400.

-export async function POST(request: Request) {
-  const { document, workspace, thread } = await request.json()
+export async function POST(request: Request) {
+  let body: unknown
+  try {
+    body = await request.json()
+  } catch {
+    return new Response(JSON.stringify({ error: 'Invalid JSON body.' }), {
+      status: 400,
+      headers: { 'Content-Type': 'application/json' },
+    })
+  }
+  const { document, workspace, thread } = (body || {}) as {
+    document?: unknown
+    workspace?: unknown
+    thread?: { slug?: string }
+  }
📝 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 async function POST(request: Request) {
const { document, workspace, thread } = await request.json()
export async function POST(request: Request) {
let body: unknown
try {
body = await request.json()
} catch {
return new Response(JSON.stringify({ error: 'Invalid JSON body.' }), {
status: 400,
headers: { 'Content-Type': 'application/json' },
})
}
const { document, workspace, thread } = (body || {}) as {
document?: unknown
workspace?: unknown
thread?: { slug?: string }
}
🤖 Prompt for AI Agents
In apps/pro-web/app/api/documents/upload/route.ts around lines 3 to 5, the call
to await request.json() can throw on invalid JSON and currently bubbles up as a
500; wrap the JSON parsing in a try/catch, and if parsing throws return an HTTP
400 response (with a short JSON/plain message like "Invalid JSON body" and
appropriate Content-Type) instead of letting the exception propagate; keep the
rest of the handler unchanged so valid requests continue to process.

Comment on lines +6 to +18
if (!document || !workspace || !thread) {
return new Response(
JSON.stringify({
error: 'Document, workspace, and thread data are required.',
}),
{
status: 400,
headers: {
'Content-Type': 'application/json',
},
},
)
}
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

Require auth and basic authorization before write

Anyone can POST to this route and mutate thread metadata via the server action. Enforce session and ownership (or proper org-level permission) before upload.

-import { uploadWorkspaceDocument } from '@/lib/api/documents'
+import { uploadWorkspaceDocument } from '@/lib/api/documents'
+import { getServerSession } from 'next-auth'
+import { getThread } from '@/services/hasura'-  if (!document || !workspace || !thread) {
+  const session = await getServerSession()
+  if (!session) {
+    return new Response(JSON.stringify({ error: 'Unauthorized' }), {
+      status: 401,
+      headers: { 'Content-Type': 'application/json' },
+    })
+  }
+  if (!document || !workspace || !thread) {
     return new Response(
       JSON.stringify({
         error: 'Document, workspace, and thread data are required.',
       }),
       {
         status: 400,
         headers: {
           'Content-Type': 'application/json',
         },
       },
     )
   }
+  if (!thread.slug) {
+    return new Response(JSON.stringify({ error: 'Thread slug is required.' }), {
+      status: 400,
+      headers: { 'Content-Type': 'application/json' },
+    })
+  }
+  // Basic ownership/authorization check (adjust to your org/role model)
+  const threadRecord = await getThread({ threadSlug: thread.slug, jwt: '' })
+  if (!threadRecord) {
+    return new Response(JSON.stringify({ error: 'Thread not found.' }), {
+      status: 404,
+      headers: { 'Content-Type': 'application/json' },
+    })
+  }
+  if (threadRecord.userId && session.user?.id !== threadRecord.userId) {
+    return new Response(JSON.stringify({ error: 'Forbidden' }), {
+      status: 403,
+      headers: { 'Content-Type': 'application/json' },
+    })
+  }
📝 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 (!document || !workspace || !thread) {
return new Response(
JSON.stringify({
error: 'Document, workspace, and thread data are required.',
}),
{
status: 400,
headers: {
'Content-Type': 'application/json',
},
},
)
}
const session = await getServerSession()
if (!session) {
return new Response(JSON.stringify({ error: 'Unauthorized' }), {
status: 401,
headers: { 'Content-Type': 'application/json' },
})
}
if (!document || !workspace || !thread) {
return new Response(
JSON.stringify({
error: 'Document, workspace, and thread data are required.',
}),
{
status: 400,
headers: {
'Content-Type': 'application/json',
},
},
)
}
if (!thread.slug) {
return new Response(JSON.stringify({ error: 'Thread slug is required.' }), {
status: 400,
headers: { 'Content-Type': 'application/json' },
})
}
// Basic ownership/authorization check (adjust to your org/role model)
const threadRecord = await getThread({ threadSlug: thread.slug, jwt: '' })
if (!threadRecord) {
return new Response(JSON.stringify({ error: 'Thread not found.' }), {
status: 404,
headers: { 'Content-Type': 'application/json' },
})
}
if (threadRecord.userId && session.user?.id !== threadRecord.userId) {
return new Response(JSON.stringify({ error: 'Forbidden' }), {
status: 403,
headers: { 'Content-Type': 'application/json' },
})
}

Comment on lines +79 to +92
if (uploadResult.error) {
return new Response(
JSON.stringify({
data: null,
error: uploadResult.error,
}),
{
status: 500,
headers: {
'Content-Type': 'application/json',
},
},
)
}
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

Return 400 for validation errors coming back from the helper

Right now, any uploadResult.error returns 500. Some helper errors are client errors (400). Map clearly to avoid noisy 5xx.

-  if (uploadResult.error) {
-    return new Response(
-      JSON.stringify({
-        data: null,
-        error: uploadResult.error,
-      }),
-      {
-        status: 500,
-        headers: {
-          'Content-Type': 'application/json',
-        },
-      },
-    )
-  }
+  if (uploadResult.error) {
+    const isBadRequest =
+      /required|must be|exceeds max size|Thread slug is required/i.test(
+        uploadResult.error,
+      )
+    return new Response(
+      JSON.stringify({ data: null, error: uploadResult.error }),
+      {
+        status: isBadRequest ? 400 : 500,
+        headers: { 'Content-Type': 'application/json' },
+      },
+    )
+  }
📝 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 (uploadResult.error) {
return new Response(
JSON.stringify({
data: null,
error: uploadResult.error,
}),
{
status: 500,
headers: {
'Content-Type': 'application/json',
},
},
)
}
if (uploadResult.error) {
const isBadRequest =
/required|must be|exceeds max size|Thread slug is required/i.test(
uploadResult.error,
)
return new Response(
JSON.stringify({ data: null, error: uploadResult.error }),
{
status: isBadRequest ? 400 : 500,
headers: { 'Content-Type': 'application/json' },
},
)
}
🤖 Prompt for AI Agents
In apps/pro-web/app/api/documents/upload/route.ts around lines 79-92, the code
currently returns HTTP 500 for any uploadResult.error; change this to return 400
for client/validation errors by checking the error's status or classification
and mapping 4xx errors to 400 while keeping server errors as 500. Specifically,
inspect uploadResult.error.status (or uploadResult.error.code/kind if status is
not present) and if it falls in the 400-499 range (or matches a
validation/client error code), set the Response status to 400; otherwise use
500; preserve the JSON shape and Content-Type header. Ensure null-safe checks so
missing status/code still defaults to 500.

Comment on lines 55 to 63
(href === '/' &&
routeType.match(/(public|org)/) &&
routeType.match(/(pro|chat)/) &&
pathname.length > 1 &&
!pathname.startsWith('/')) ||
// For personal/chat route: href is "/c" and pathname starts with "/c/"
(href === '/' &&
routeType.match(/(pro|chat)/) &&
(pathname === '/c' || pathname.startsWith('/c/'))),
// For org route: href is "/org" and pathname starts with "/org/"
(href === '/org' &&
routeType.match(/(public|org)/) &&
(pathname === '/org' || pathname.startsWith('/org/'))),
)
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

❓ Verification inconclusive

Active-link logic bug: condition never true and regex over-matches.

  • !pathname.startsWith('/') is always false in Next.js (all paths start with “/”).
  • routeType.match(/(pro|chat)/) will also match profile (substring “pro”).

Fix with strict equality and correct org exclusion.

-      (href === '/' &&
-        routeType.match(/(pro|chat)/) &&
-        pathname.length > 1 &&
-        !pathname.startsWith('/')) ||
+      (href === '/' &&
+        (routeType === 'pro' || routeType === 'chat') &&
+        pathname !== '/org' &&
+        !pathname.startsWith('/org/')) ||
-      (href === '/org' &&
-        routeType.match(/(public|org)/) &&
-        (pathname === '/org' || pathname.startsWith('/org/'))),
+      (href === '/org' &&
+        (routeType === 'org' || routeType === 'public') &&
+        (pathname === '/org' || pathname.startsWith('/org/'))),

Add tests or QA scenarios for /, /c/..., /org, /org/..., /u/..., /b/....


Active-link logic bug — unreachable condition & over-broad regex

File: apps/pro-web/components/layout/header/header.tsx (lines 55–63)

  • !pathname.startsWith('/') is always false (Next.js pathnames start with '/').
  • routeType.match(/(pro|chat)/) over-matches (e.g., "profile" contains "pro").
-      (href === '/' &&
-        routeType.match(/(pro|chat)/) &&
-        pathname.length > 1 &&
-        !pathname.startsWith('/')) ||
+      (href === '/' &&
+        (routeType === 'pro' || routeType === 'chat') &&
+        pathname !== '/org' &&
+        !pathname.startsWith('/org/')) ||
-      (href === '/org' &&
-        routeType.match(/(public|org)/) &&
-        (pathname === '/org' || pathname.startsWith('/org/'))),
+      (href === '/org' &&
+        (routeType === 'org' || routeType === 'public') &&
+        (pathname === '/org' || pathname.startsWith('/org/'))),

Add tests/QA for: "/", "/c/...", "/org", "/org/...", "/u/...", "/b/...".

🤖 Prompt for AI Agents
In apps/pro-web/components/layout/header/header.tsx around lines 55-63, the
active-link condition contains an unreachable !pathname.startsWith('/') check
and an over-broad routeType.match(/(pro|chat)/) that will match substrings like
"profile"; change the logic to use strict equality checks for routeType (e.g.,
routeType === 'pro' || routeType === 'chat'), remove the impossible
!pathname.startsWith('/'), add explicit exclusions so the root href ('/') case
also ensures pathname !== '/org' and does not startWith('/org/'), and likewise
change the '/org' case to use (routeType === 'org' || routeType === 'public')
while keeping the (pathname === '/org' || pathname.startsWith('/org/')) check;
after updating, add QA/tests covering "/", "/c/...", "/org", "/org/...",
"/u/...", and "/b/..." to verify active-link behavior.

Comment on lines +46 to +52
// Validate document type
if (!['text', 'image', 'spreadsheet'].includes(document.type)) {
return {
data: null,
error: 'Document type must be text, image, or spreadsheet.',
}
}
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

Document type validation is inconsistent with storage implementation

uploadWorkspaceDocumentToBucket always writes .md with text/markdown. Accepting 'image' | 'spreadsheet' here will produce mis-labeled blobs and corrupt non-text payloads.

Quick fix (gate to text only until binary types are actually supported end-to-end):

+const ALLOWED_TYPES: WorkspaceDocumentUpload['type'][] = ['text']- // Validate document type
- if (!['text', 'image', 'spreadsheet'].includes(document.type)) {
-   return {
-     data: null,
-     error: 'Document type must be text, image, or spreadsheet.',
-   }
- }
+ // Validate document type (align with current bucket writer which stores text/markdown)
+ if (!ALLOWED_TYPES.includes(document.type)) {
+   return {
+     data: null,
+     error: 'Only text documents are supported at the moment.',
+   }
+ }

If you intend to support images/spreadsheets now, update uploadWorkspaceDocumentToBucket to set contentType/extension by type and avoid UTF‑8 encoding for binary data.

📝 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
// Validate document type
if (!['text', 'image', 'spreadsheet'].includes(document.type)) {
return {
data: null,
error: 'Document type must be text, image, or spreadsheet.',
}
}
const ALLOWED_TYPES: WorkspaceDocumentUpload['type'][] = ['text']
// Validate document type (align with current bucket writer which stores text/markdown)
if (!ALLOWED_TYPES.includes(document.type)) {
return {
data: null,
error: 'Only text documents are supported at the moment.',
}
}
🤖 Prompt for AI Agents
apps/pro-web/lib/api/documents.ts lines 46-52: the current validator allows
'text', 'image', 'spreadsheet' but uploadWorkspaceDocumentToBucket always writes
.md with content-type text/markdown and UTF-8 encoding, which will corrupt
binary payloads; restrict the validator to accept only 'text' for now by
returning the same error message when document.type !== 'text' (or update the
message to "Document type must be text."), and if you later add
images/spreadsheets, modify uploadWorkspaceDocumentToBucket to set the correct
file extension and contentType per type and ensure binary data is written
without UTF-8 encoding.

Comment on lines +262 to +265
console.log(`Final updated document ${document.name}:`)
console.log(` URL: ${currentVersionSignedUrl?.substring(0, 50)}...`)
console.log(` Expires: ${new Date(newExpiry).toISOString()}`)
console.log(` Current version: ${document.currentVersion}`)
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

Don’t log signed URLs (even substrings).

Signed URLs are sensitive. Remove from logs.

- console.log(`  URL: ${currentVersionSignedUrl?.substring(0, 50)}...`)
- console.log(`  Expires: ${new Date(newExpiry).toISOString()}`)
+ console.log(`  Expires: ${new Date(newExpiry).toISOString()}`)
📝 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
console.log(`Final updated document ${document.name}:`)
console.log(` URL: ${currentVersionSignedUrl?.substring(0, 50)}...`)
console.log(` Expires: ${new Date(newExpiry).toISOString()}`)
console.log(` Current version: ${document.currentVersion}`)
console.log(`Final updated document ${document.name}:`)
console.log(` Expires: ${new Date(newExpiry).toISOString()}`)
console.log(` Current version: ${document.currentVersion}`)
🤖 Prompt for AI Agents
In apps/pro-web/lib/cron/refresh-workspace-document-links.ts around lines 262 to
265, the code currently logs a signed URL substring which is sensitive; remove
any logging of the signed URL (even substrings). Replace that log with
non-sensitive information only — e.g., log the document name/id, a masked URL or
its host/pathname without query params, and the expiry timestamp and
currentVersion. Ensure you strip query parameters or completely omit the URL
before logging so no token/query values are emitted.

Comment on lines 62 to 69
const pathname = window.location.pathname
const pathNameParts = pathname.split('/')
const routeType = getRouteType(pathname)
const isPublic = routeType === 'public'
const isOrg = routeType === 'org'
const isProfile = routeType === 'profile'
const isBotProfile = routeType === 'bot'
const isPersonal = routeType === 'chat'
const isPersonal = routeType === 'pro'

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

Guard window usage (SSR-safe feature detection).

This module isn’t marked 'use client'. Accessing window without a guard can throw during SSR or RSC preeval. Add a runtime check and early‑return.

- const pathname = window.location.pathname
+ if (typeof window === 'undefined') return
+ const pathname = window.location.pathname
📝 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 pathname = window.location.pathname
const pathNameParts = pathname.split('/')
const routeType = getRouteType(pathname)
const isPublic = routeType === 'public'
const isOrg = routeType === 'org'
const isProfile = routeType === 'profile'
const isBotProfile = routeType === 'bot'
const isPersonal = routeType === 'chat'
const isPersonal = routeType === 'pro'
if (typeof window === 'undefined') return
const pathname = window.location.pathname
const pathNameParts = pathname.split('/')
const routeType = getRouteType(pathname)
const isOrg = routeType === 'org'
const isProfile = routeType === 'profile'
const isBotProfile = routeType === 'bot'
const isPersonal = routeType === 'pro'
🤖 Prompt for AI Agents
In apps/pro-web/lib/threads.ts around lines 62 to 69, the code directly accesses
window (window.location.pathname) which will throw during SSR/RSC
pre-evaluation; add a runtime guard such as if (typeof window === 'undefined')
return early with safe defaults (e.g., undefined/false or empty strings) or
memoized server-safe values, and wrap any subsequent window-dependent logic so
it only runs when the guard passes; alternatively mark the module 'use client'
if it must run only on the client and update exports accordingly.

*/

type RouteType = 'chat' | 'public' | 'profile' | 'pro' | 'bot' | 'org'
type RouteType = 'chat' | 'org' | 'profile' | 'pro' | 'bot' | 'org'
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 union member ('org').

Remove the duplicate to avoid confusing IDEs and type narrowing.

-type RouteType = 'chat' | 'org' | 'profile' | 'pro' | 'bot' | 'org'
+type RouteType = 'chat' | 'org' | 'profile' | 'pro' | 'bot'
📝 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
type RouteType = 'chat' | 'org' | 'profile' | 'pro' | 'bot' | 'org'
type RouteType = 'chat' | 'org' | 'profile' | 'pro' | 'bot'
🤖 Prompt for AI Agents
In apps/pro-web/lib/utils.ts around line 308, the RouteType union includes a
duplicated member 'org' which can confuse IDEs and type narrowing; remove the
duplicate so each union member is unique (e.g., type RouteType = 'chat' | 'org'
| 'profile' | 'pro' | 'bot') and run TypeScript compile to confirm no other
references rely on two entries.

Comment on lines 45 to 61
<div className="relative">
<Card
className={cn(
'fixed z-[9999] w-80 max-w-sm',
'transition-all duration-300 ease-in-out',
'animate-in fade-in-0 zoom-in-95',
'overflow-hidden border-2 shadow-lg backdrop-blur-sm bg-white/95 dark:bg-[#09090B]/95',
'border-gradient onboarding-card relative',
className,
)}
style={{
top: position.top,
left: position.left,
right: position.right,
bottom: position.bottom,
}}
>
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

Arrow/Card positioning bug mirrored here.

Same fixed Card + relative wrapper issue; apply the same wrapper‑fixed refactor as pro‑web to keep the arrow aligned with the Card.

- <div className="relative">
-   <Card
-     className={cn(
-       'fixed z-[9999] w-80 max-w-sm',
+ <div
+   className="fixed z-[9999] relative"
+   style={{
+     top: position.top,
+     left: position.left,
+     right: position.right,
+     bottom: position.bottom,
+   }}
+ >
+   <Card
+     className={cn(
+       'w-80 max-w-sm',
        'transition-all duration-300 ease-in-out',
        'animate-in fade-in-0 zoom-in-95',
        'overflow-hidden border-2 shadow-lg backdrop-blur-sm bg-white/95 dark:bg-[#09090B]/95',
        'border-gradient onboarding-card relative',
-       className,
-     )}
-     style={{
-       top: position.top,
-       left: position.left,
-       right: position.right,
-       bottom: position.bottom,
-     }}
+       className,
+     )}
      >
📝 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
<div className="relative">
<Card
className={cn(
'fixed z-[9999] w-80 max-w-sm',
'transition-all duration-300 ease-in-out',
'animate-in fade-in-0 zoom-in-95',
'overflow-hidden border-2 shadow-lg backdrop-blur-sm bg-white/95 dark:bg-[#09090B]/95',
'border-gradient onboarding-card relative',
className,
)}
style={{
top: position.top,
left: position.left,
right: position.right,
bottom: position.bottom,
}}
>
<div
className="fixed z-[9999] relative"
style={{
top: position.top,
left: position.left,
right: position.right,
bottom: position.bottom,
}}
>
<Card
className={cn(
'w-80 max-w-sm',
'transition-all duration-300 ease-in-out',
'animate-in fade-in-0 zoom-in-95',
'overflow-hidden border-2 shadow-lg backdrop-blur-sm bg-white/95 dark:bg-[#09090B]/95',
'border-gradient onboarding-card relative',
className,
)}
>
🤖 Prompt for AI Agents
In apps/web/components/onboarding/onboarding-card.tsx around lines 45 to 61, the
outer div is "relative" while the Card is positioned "fixed", causing the arrow
to misalign; refactor like pro-web by moving the fixed positioning and style
(top/left/right/bottom, z, width classes) from the Card to the outer wrapper
(make the wrapper fixed and keep Card positioned statically inside), keep the
Card's other classes (overflow, border, bg, etc.) and ensure the arrow element
remains absolutely positioned relative to the Card so it stays aligned.

@vercel vercel bot temporarily deployed to Preview – masterbots September 23, 2025 00:18 Inactive
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: 24

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (14)
apps/pro-web/app/api/generate-images/route.ts (2)

62-64: Avoid potential runtime crash: ensure performance.now() exists in Node runtime.

Route handlers typically run on Node, where performance may not be globally available in all environments without node:perf_hooks. Import it explicitly to prevent ReferenceErrors.

Apply:

+import { performance } from 'node:perf_hooks'

Also applies to: 121-125


97-101: Bug: using the same image as both image and mask will fail or neutralize edits.

Passing previousImage as mask is incorrect; masks must be a separate PNG with transparency. Add a distinct mask field and only include it when provided.

Apply:

@@
- let previousImage: string | undefined
- let editMode: boolean | undefined
+ let previousImage: string | undefined
+ let mask: string | undefined
+ let editMode: boolean | undefined
@@
- previousImage = body.previousImage
- editMode = body.editMode
+ previousImage = body.previousImage
+ mask = (body as any).mask
+ editMode = body.editMode
@@
- ...(editMode &&
-   previousImage && {
-     image: previousImage,
-     mask: previousImage,
-   }),
+ ...(editMode &&
+   previousImage && {
+     image: previousImage,
+     ...(mask && { mask }),
+   }),

Note: Update GenerateImageRequest to include optional mask?: string.

Also applies to: 42-46, 49-54

apps/pro-web/app/api/og/route.tsx (2)

83-90: Do not require both threadId and chatbotId; allow either based on ogType.

Current guard blocks valid bot_profile (chatbot-only) and user_thread/thread-only requests and still calls getThreadForOG unconditionally. This causes default fallbacks and wasted calls.

Apply this diff to validate each ID independently and only fetch what’s needed:

-    if (
-      !threadId ||
-      (threadId && !uuidRegex.test(threadId)) ||
-      !chatbotId ||
-      (chatbotId && !uuidRegex.test(chatbotId))
-    ) {
-      return defaultOgImage
-    }
+    const hasValidThreadId = !!threadId && uuidRegex.test(threadId)
+    const hasValidChatbotId = !!chatbotId && uuidRegex.test(chatbotId)
+    if (!hasValidThreadId && !hasValidChatbotId) {
+      return defaultOgImage
+    }

-    // TODO: Update this to use mb-genql package
-    const { thread }: { thread: Thread[] } = await getThreadForOG(threadId)
-    if (!thread?.length && !chatbotId) {
-      // Use metadata when thread not found
-      return defaultOgImage
-    }
+    // TODO: Update this to use mb-genql package
+    let thread: Thread[] = []
+    if (hasValidThreadId) {
+      const res = await getThreadForOG(threadId!)
+      thread = res.thread
+    }
+    if (!thread?.length && !hasValidChatbotId) {
+      // Use metadata when no thread and no chatbot to fall back on
+      return defaultOgImage
+    }
 
-    // Initialize chatbot data
-    let chatbotData: { chatbot: Chatbot[] } = { chatbot: [] }
-    if (chatbotId) {
-      chatbotData = await getChatbotForOG(chatbotId)
-    }
+    // Initialize chatbot data
+    let chatbotData: { chatbot: Chatbot[] } = { chatbot: [] }
+    if (hasValidChatbotId) {
+      chatbotData = await getChatbotForOG(chatbotId!)
+    }

Also applies to: 92-105


72-75: Guard against missing followers array to prevent runtime errors.

userData.followers.length throws if followers is undefined/null.

-          username={
-            userData.followers.length
-              ? `${userData.followers.length} followers`
-              : 'bot explorer'
-          }
+          username={
+            (userData.followers?.length ?? 0) > 0
+              ? `${userData.followers?.length ?? 0} followers`
+              : 'bot explorer'
+          }
apps/pro-web/components/onboarding/onboarding-card.tsx (1)

103-111: Add accessible label to icon-only Close button.

Icon-only controls need an ARIA label for screen readers.

-            <Button
+            <Button
               variant="ghost"
               size="icon"
               onClick={onClose}
-              className="w-6 h-6 text-muted-foreground hover:text-foreground -mt-1 -mr-1"
+              aria-label="Close onboarding"
+              title="Close"
+              className="w-6 h-6 text-muted-foreground hover:text-foreground -mt-1 -mr-1"
             >
apps/web/components/onboarding/onboarding-card.tsx (1)

103-111: Add accessible label to icon-only Close button.

Same accessibility gap as pro‑web.

-            <Button
+            <Button
               variant="ghost"
               size="icon"
               onClick={onClose}
-              className="w-6 h-6 text-muted-foreground hover:text-foreground -mt-1 -mr-1"
+              aria-label="Close onboarding"
+              title="Close"
+              className="w-6 h-6 text-muted-foreground hover:text-foreground -mt-1 -mr-1"
             >
apps/pro-web/app/api/payment/subscription/route.tsx (3)

31-33: Guard against missing PaymentIntent.invoice before retrieving the Invoice.

paymentIntent.invoice can be null/undefined for some flows; calling retrieve with an invalid id will throw. Add a pre-check.

-const invoice = await stripe.invoices.retrieve(
-  paymentIntent.invoice as string,
-)
+const invoiceId =
+  typeof paymentIntent.invoice === 'string'
+    ? paymentIntent.invoice
+    : paymentIntent.invoice?.id
+if (!invoiceId) {
+  return new Response(
+    JSON.stringify({ error: 'Payment Intent has no invoice' }),
+    { status: 400, headers: { 'Content-Type': 'application/json' } },
+  )
+}
+const invoice = await stripe.invoices.retrieve(invoiceId)

Also applies to: 49-53


72-77: Use Price (not deprecated Plan) and expand product correctly.

subscription.items.data[0].plan is legacy; amounts/intervals should come from price. Also expand items.data.price.product to safely access product name. This avoids type errors (unit_amount is on Price) and future Stripe API breakage.

-const subscription = await stripe.subscriptions.retrieve(
-  subscriptionId as string,
-  {
-    expand: ['items.data.plan', 'customer'], // Expand the plan details
-  },
-)
+const subscription = await stripe.subscriptions.retrieve(subscriptionId as string, {
+  expand: ['items.data.price.product', 'customer'],
+})
 
-const subCustomer = subscription.customer as Stripe.Customer
-const subPlan = subscription.items.data[0]?.plan as Stripe.Plan & Stripe.Plan.Tier
-const subProduct = subPlan?.product as Stripe.Product
+const subCustomer = subscription.customer as Stripe.Customer
+const item = subscription.items.data[0]
+const price = item?.price as Stripe.Price | undefined
+const prod = price?.product as string | Stripe.Product | undefined
+const productName =
+  typeof prod === 'string' ? 'Pro Plan' : (prod?.name ?? 'Pro Plan')
 const transformedSubscription = {
   customer: {
     name: subCustomer?.name || '',
   },
   plan: {
-    amount:
-      (typeof subPlan?.unit_amount === 'number'
-        ? subPlan?.unit_amount
-        : subPlan?.amount) || 0,
-    interval: subPlan?.interval || 'month',
-    product: {
-      name: subProduct?.name || 'Pro Plan',
-    },
+    amount: price?.unit_amount ?? 0,
+    interval: price?.recurring?.interval ?? 'month',
+    product: { name: productName },
   },
   current_period_start: subscription.current_period_start,
   current_period_end: subscription.current_period_end,
   status: subscription.status,
+  cancel_at_period_end: subscription.cancel_at_period_end,
}

Also applies to: 79-102


178-186: PUT: also switch to Price, avoid shadowing, and normalize amount/interval.

Mirror the GET fix: expand price.product, compute amount/interval from price, and avoid reusing customer identifier.

-const expandedSubscription = await stripe.subscriptions.retrieve(
-  subscription.id,
-  { expand: ['items.data.plan', 'customer'] },
-)
-const customer = expandedSubscription.customer as Stripe.Customer
-const plan = expandedSubscription.items.data[0]?.plan as Stripe.Plan & Stripe.Plan.Tier
-const product = plan?.product as Stripe.Product
+const expandedSubscription = await stripe.subscriptions.retrieve(subscription.id, {
+  expand: ['items.data.price.product', 'customer'],
+})
+const subCustomer = expandedSubscription.customer as Stripe.Customer
+const item = expandedSubscription.items.data[0]
+const price = item?.price as Stripe.Price | undefined
+const prod = price?.product as string | Stripe.Product | undefined
+const productName =
+  typeof prod === 'string' ? 'Pro Plan' : (prod?.name ?? 'Pro Plan')
 ...
 const transformedSubscription = {
   customer: {
-    name: customer?.name || '',
+    name: subCustomer?.name || '',
   },
   plan: {
-    amount: plan?.unit_amount || 0,
-    interval: plan?.interval || 'month',
-    product: { name: product?.name || 'Pro Plan' },
+    amount: price?.unit_amount ?? 0,
+    interval: price?.recurring?.interval ?? 'month',
+    product: { name: productName },
   },
   current_period_start: expandedSubscription.current_period_start,
   current_period_end: expandedSubscription.current_period_end,
   status: expandedSubscription.status,
   cancel_at_period_end: expandedSubscription.cancel_at_period_end,
}

Also applies to: 191-206

apps/pro-web/components/layout/profile/layout.tsx (1)

18-21: Make user prop optional to reflect runtime nullability and avoid unsafe casts.

You treat user as optional downstream (early-return in effect; userData can be null) but the prop is typed as required. Make it optional to align with usage and remove later as casts.

 }: {
   children: React.ReactNode
-  user: User
+  user?: User | null
 })
apps/pro-web/services/hasura/hasura.service.type.ts (2)

87-96: Add missing field in UpdateUserArgs._set to match usage (profilePicture).

updateUserPersonality sets profilePicture but the type here doesn’t allow it → TS error. Add the field.

Apply:

 export interface UpdateUserArgs {
   pkColumns: { userId: string | undefined }
   _set?: {
     bio?: string
     favouriteTopic?: string
+    profilePicture?: string
     username?: string
     email?: string
     slug?: string
   }
 }

98-105: Remove or consolidate duplicate UpdateUserDArgs.

This interface overlaps with UpdateUserArgs and invites drift. Prefer a single type.

Apply:

-export interface UpdateUserDArgs {
-  pkColumns: { userId: string | undefined }
-  _set?: {
-    username?: string
-    email?: string
-    slug?: string
-  }
-}

Optionally, make UpdateUserArgs._set a Partial of an explicit shape shared across callsites.

apps/hasura/metadata/databases/masterbots/tables/public_thread.yaml (1)

37-38: Fix session variable case in insert presets (user_id).

Use Hasura’s canonical session variable casing to avoid preset failures.

Apply:

-      set:
-        user_id: x-hasura-user-Id
+      set:
+        user_id: X-Hasura-User-Id

And the same change for the user role block.

Also applies to: 57-58

apps/pro-web/services/hasura/hasura.service.ts (1)

1448-1456: Add profilePicture?: string to UpdateUserArgs._set

Update UpdateUserArgs._set to include profilePicture?: string in:

  • apps/pro-web/services/hasura/hasura.service.type.ts
  • apps/web/services/hasura/hasura.service.type.ts

Run local typecheck after the change: bun run typecheck || bun tsc -b

🧹 Nitpick comments (51)
apps/pro-web/components/layout/sidebar/sidebar-toggle.tsx (2)

15-27: Prevent unintended close on first mount + add feature detection.

On the first render, prevPathname is undefined, so the effect can close the sidebar immediately if it starts open on small screens. Also add a window guard and align the breakpoint with CSS via matchMedia.

   // Close sidebar if it is open on pathname change.
-  // biome-ignore lint/correctness/useExhaustiveDependencies: <explanation>
+  // biome-ignore lint/correctness/useExhaustiveDependencies:
+  // We deliberately exclude isSidebarOpen, prevPathname, and toggleSidebar from deps.
+  // Intent: run only on route changes to avoid re-closing due to state updates.
   React.useEffect(() => {
-    const isSmallScreen = window.innerWidth < 1024
+    // Skip on first mount so we only react to actual route changes.
+    if (prevPathname == null) return
+    const isSmallScreen =
+      typeof window !== 'undefined' &&
+      !window.matchMedia('(min-width: 1024px)').matches
     if (
       isSidebarOpen &&
       pathname !== prevPathname &&
       isSmallScreen &&
       activeChatbot
     ) {
       toggleSidebar()
     }
   }, [pathname, activeChatbot])

29-41: Add ARIA state and ensure a 44×44px hit target (WCAG).

Expose the open/closed state and point to the controlled region. Consider bumping the touch target to 44px minimum on mobile.

   return (
     <Button
       variant="ghost"
-      className="flex p-0 mr-2 -ml-2 size-9 lg:hidden"
+      className="flex p-0 mr-2 -ml-2 size-9 lg:hidden min-w-[44px] min-h-[44px]"
+      aria-expanded={isSidebarOpen}
+      aria-controls="app-sidebar" {/* replace with actual sidebar element id */}
+      data-state={isSidebarOpen ? 'open' : 'closed'}
+      type="button"
       onClick={(e) => {
         e.stopPropagation()
         toggleSidebar()
       }}
     >
apps/pro-web/app/u/[userSlug]/s/page.tsx (1)

4-8: Remove unnecessary async/await and return; call redirect directly and destructure params.

redirect() throws; no need to return it. props.params isn’t a Promise—drop the await. This also tightens types.

-export default async function ProfilePageDefaultRedirect(props: PageProps) {
-  const params = await props.params
-  // Redirect to the user's profile page
-  return redirect(`/u/${params.userSlug}/t`)
-}
+export default function ProfilePageDefaultRedirect({ params }: PageProps) {
+  redirect(`/u/${params.userSlug}/t`)
+}
apps/pro-web/lib/hooks/use-accessibility.tsx (1)

28-31: Harden effect: sanitize unknown values and avoid writing the “normal” attribute.

Prevents bad persisted values from breaking styles and reduces FOUC by falling back to base CSS when at “normal”.

-	useEffect(() => {
-		document.documentElement.setAttribute('data-font-size', fontSize)
-	}, [fontSize])
+	useEffect(() => {
+		const root = document.documentElement
+		const allowed = new Set<FontSize>(['small', 'medium', 'normal', 'large', 'x-large'])
+		const safe = allowed.has(fontSize) ? fontSize : 'normal'
+
+		if (safe === 'normal') {
+			root.removeAttribute('data-font-size')
+		} else {
+			root.setAttribute('data-font-size', safe)
+		}
+		if (safe !== fontSize) {
+			setFontSize('normal')
+		}
+	}, [fontSize, setFontSize])
apps/pro-web/app/globals.css (1)

1050-1058: Route rename consistency: keep both only if intentional; plan cleanup.

Adding pro/chat and org/public pairs is fine for transition. Confirm that:

  • “public” is legacy-only and still emitted anywhere; otherwise remove “public” selectors.
  • “chat” vs “pro” duality is intentional; if “chat” is deprecated, drop it here too.

Optional: define org-specific CSS vars (aliasing current “public” ones) to reduce semantic drift and ease future cleanup.

apps/pro-web/components/routes/home/home-page.tsx (2)

15-15: Remove unused setSelectedCategories

It’s not used and will trip Biome’s unused-variable rule.

-  const { selectedCategories, setSelectedCategories } = useSidebar()
+  const { selectedCategories } = useSidebar()

18-27: Derive onboarding state from source-of-truth; tighten deps

Current logic only flips to true from false and relies on manual close, which can leave stale state if categories are selected elsewhere. Consider deriving directly from conditions and depending on stable booleans to minimize re-renders.

   useEffect(() => {
     if (status === 'loading') return
-    setShowOnboarding((prev) => {
-      //? set based on session
-      if (prev === false && !session?.user && selectedCategories.length === 0) {
-        return true
-      }
-      return prev
-    })
-  }, [session?.user, status, selectedCategories.length])
+    setShowOnboarding(!session?.user && selectedCategories.length === 0)
+  }, [status, selectedCategories.length, !!session?.user])

Confirm this matches the intended UX (auto-hide when a topic is picked anywhere). If you prefer manual close only, keep the current logic.

apps/pro-web/app/u/[userSlug]/s/pref/page.tsx (1)

5-5: Resolve conflicting Tailwind paddings (pb-10 vs py-[120px])

py-[120px] overrides pb-10. Keep one source of truth. If the intent is top 120 and bottom 40, split explicitly; otherwise drop pb-10.

Examples:

-<div className="max-w-screen-xl pb-10 mx-auto w-full py-[120px] px-[58px]">
+<div className="max-w-screen-xl pt-[120px] pb-10 mx-auto w-full px-[58px]">

or

-<div className="max-w-screen-xl pb-10 mx-auto w-full py-[120px] px-[58px]">
+<div className="max-w-screen-xl mx-auto w-full py-[120px] px-[58px]">

(Optional) Prefer design scale over arbitrary px-[58px] if your design system allows (e.g., px-14 or px-16).

apps/pro-web/app/u/[userSlug]/s/subs/page.tsx (1)

3-5: Rename function for clarity and drop unnecessary async

Function is named PreferencePage in a subscriptions route; rename to match intent and remove unused async.

-export default async function PreferencePage() {
-  return <SubscriptionPageComponent />
-}
+export default function SubscriptionPage() {
+  return <SubscriptionPageComponent />
+}
apps/pro-web/app/api/generate-images/route.ts (4)

10-15: Give yourself headroom below Vercel’s hard 120s limit.

Set internal timeout below maxDuration to avoid function termination mid‑cleanup.

Apply:

-const TIMEOUT_MILLIS = 2 * 60 * 1000
+const TIMEOUT_MILLIS = 110 * 1000

55-61: Harden request validation with a schema.

Consider Zod (or similar) to validate prompt, modelId, and base64 payload sizes before hitting providers.

Example (illustrative):

import { z } from 'zod'
const Req = z.object({
  prompt: z.string().min(1).max(4000),
  modelId: z.enum(['dall-e-2', 'dall-e-3', 'gpt-image-1']),
  previousImage: z.string().base64().optional(),
  mask: z.string().base64().optional(),
  editMode: z.boolean().optional(),
})
const body = Req.parse(await req.json())

91-107: Model allow‑list recommended.

Restrict modelId to supported values to fail fast and improve error messages.

Apply (conceptually):

- modelId = body.modelId
+ const allowed = new Set(['dall-e-2', 'dall-e-3', 'gpt-image-1'])
+ modelId = body.modelId
+ if (!allowed.has(modelId)) {
+   return NextResponse.json({ error: 'Unsupported modelId' }, { status: 400 })
+ }

47-54: Large body safety.

Base64 payloads can be huge. Enforce a per‑route body size limit or reject payloads exceeding a threshold to protect the function.

apps/pro-web/app/api/og/route.tsx (2)

32-32: Remove verbose logging or guard it behind an env flag.

Avoid logging full query strings in production to reduce noise and potential PII leakage.

-    console.log('searchParams for og:', searchParams.toString())
+    if (process.env.NODE_ENV !== 'production') {
+      console.debug('og searchParams:', searchParams.toString())
+    }

51-51: Avoid conflicting ogType defaults.

defaultContent sets ogType and you also pass ogType explicitly. Prefer a single source of truth to prevent drift.

Option A (remove from defaultContent):

-  ogType: 'bot_thread',

Option B (stop passing ogType where default is intended). Pick one based on desired behavior.

Also applies to: 18-22

apps/pro-web/components/routes/preferences/preference-section.tsx (8)

229-250: Guard against email edits at the state layer and tighten username validation.

Prevent email mutations even if the readOnly attr is tampered with, and validate username on trimmed value.

 function updateInput(
   inputId: string | undefined,
   e: React.ChangeEvent<HTMLInputElement>,
 ) {
   if (!inputId) return
-
-  const value = e.target.value
-  setInputValue((prev) => ({ ...prev, [inputId]: value }))
+  // Disallow changing email through state even if DOM is manipulated
+  if (inputId === 'email') return
+
+  const raw = e.target.value
+  const value = inputId === 'username' ? raw.trim() : raw
+  setInputValue((prev) => ({ ...prev, [inputId]: value }))
 
   if (inputId === 'username') {
     const usernameRegex = /^[a-zA-Z0-9_]{3,24}$/
 
     if (!usernameRegex.test(value)) {
       setErrorMessage(
         'Username must be 3–24 characters and contain only letters, numbers, or underscores.',
       )
       console.error('Invalid username format:', value)
       return
     }
   }
   setErrorMessage('')
 }

423-444: Fix label association and missing input id (a11y).

htmlFor should reference the input’s id; the input currently has no id.

-												<Label htmlFor="password">
+												<Label htmlFor={item.props?.inputId}>
 													{item.props?.inputName}
 												</Label>
 												<div className="relative w-full flex  space-x-4">
 													<Input
 														type="text"
+														id={item.props?.inputId}
 														className="w-full h-10 rounded-md dark:text-white text-black px-3"
 														placeholder={item.props?.inputPlaceholder || ''}

271-286: Revalidate username server-side entry point too; bail early if invalid.

Frontend regex should also gate the update action to avoid unnecessary requests.

 	try {
-			const { username } = inputValue
+			const { username } = inputValue
+			const usernameRegex = /^[a-zA-Z0-9_]{3,24}$/
+			if (username && !usernameRegex.test(username)) {
+				customSonner({
+					type: 'error',
+					text:
+						'Username must be 3–24 characters and contain only letters, numbers, or underscores.',
+				})
+				return
+			}
 			const slug = currentUser?.slug || session.user?.slug
 			const email = currentUser?.email

446-462: Disable profile action buttons when validation error is present.

Prevents submitting while showing an error.

-															disabled={isLoading || !currentUser}
+															disabled={isLoading || !currentUser || Boolean(errorMessage)}
-												disabled={isLoading || !currentUser}
+												disabled={isLoading || !currentUser || Boolean(errorMessage)}

Also applies to: 628-641


60-61: Rename typo sendindVEmail → sendingVEmail.

Improves readability and avoids future confusion.

-	const [sendindVEmail, setSendingVEmail] = useState(false)
+	const [sendingVEmail, setSendingVEmail] = useState(false)
-													{sendindVEmail ? (
+													{sendingVEmail ? (
...
-												{currentUser?.isVerified ? null : (
+												{currentUser?.isVerified ? null : (
 				<Button
 					id={item.props?.buttonId}
-					disabled={sendindVEmail}
+					disabled={sendingVEmail}
 					onClick={sendVerificationEmail}
 					className="mt-2"
 				>
-					{sendindVEmail && (
+					{sendingVEmail && (
 						<IconSpinner className="mr-1 size-4 animate-spin" />
 					)}
-					{!sendindVEmail && <Send className="mr-1 size-4" />}
+					{!sendingVEmail && <Send className="mr-1 size-4" />}
 					{item.props?.buttonText}
 				</Button>
 			)}

Also applies to: 483-511


41-41: Remove Biome ignore placeholder and type icon precisely.

Replace the placeholder ignore with a concrete type to satisfy Biome.

-import { AArrowDown, AArrowUp, MailCheck, Plus, Send } from 'lucide-react'
+import { AArrowDown, AArrowUp, MailCheck, Plus, Send } from 'lucide-react'
+import type { LucideIcon } from 'lucide-react'
-																(opt: {
-																	value: string
-																	label: string
-																	// biome-ignore lint/suspicious/noExplicitAny: <explanation>
-																	icon: any
-																}) => (
+																(opt: {
+																	value: string
+																	label: string
+																	icon: LucideIcon
+																}) => (

Also applies to: 561-567


304-357: Use finally for sendingVEmail state reset.

Simplifies state handling and guarantees reset.

-			setSendingVEmail(true)
+			setSendingVEmail(true)
...
-			setSendingVEmail(false)
+		} catch (error) {
+			console.error('Failed to send verification email:', error)
+			customSonner({
+				type: 'error',
+				text: 'Failed to send verification email. Please try again later.',
+			})
+		} finally {
+			setSendingVEmail(false)
-		} catch (error) {
-			console.error('Failed to send verification email:', error)
-			customSonner({
-				type: 'error',
-				text: 'Failed to send verification email. Please try again later.',
-			})
-			setSendingVEmail(false)
 		}

520-585: Consider adding a 'system' option to the theme dropdown.

The app defaultTheme is 'system'; expose it to prevent an unselectable current value.

Would you like me to add a “System” option wired to useTheme’s system mode?

apps/pro-web/lib/constants/preferences.ts (3)

88-108: Capitalize 'Large' label for consistency.

Minor UI copy polish.

 						{
 							value: 'large',
-							label: 'large',
+							label: 'Large',
 							icon: LetterText,
 						},

1-9: Single source of truth for language list; export the joined string.

Prevents drift with component/layout includedLanguages.

 import type { PreferenceItemType } from '@/types/types'
@@
 export const languageOptions = [
@@
-]
+]
+
+// Joined languages string for Google Translate initialization
+export const includedLanguagesString = languageOptions.map((l) => l.value).join(',')

Follow-up: wire GoogleTranslate default includedLanguages to includedLanguagesString.

Also applies to: 210-222


186-206: Copy mentions “Public Threads.” Align with current “org” semantics if applicable.

If “public” is deprecated in this UI, update copy to reduce confusion.

apps/pro-web/components/routes/preferences/google-translation.tsx (5)

25-37: Avoid private Google API (_getInstanceIfExists); use supported paths or hash/cookie fallback.

Remove the private call and rely on hash/cookie flow or the official combo element if present.

-		// biome-ignore lint/complexity/useOptionalChain: <explanation>
-		if (window.google && window.google.translate) {
-			try {
-				const translateInstance = window.google.translate._getInstanceIfExists()
-				if (translateInstance) {
-					translateInstance.translatePage(pageLanguage, langCode)
-					console.log('Translation triggered via Google API')
-					return
-				}
-			} catch (error) {
-				console.log('Method 1 failed:', error)
-			}
-		}
+		// If the Google Translate UI combo exists, drive it; else fall back to URL/cookie.
+		const combo = document.querySelector<HTMLSelectElement>('select.goog-te-combo')
+		if (combo) {
+			combo.value = langCode
+			combo.dispatchEvent(new Event('change'))
+			return
+		}

99-109: Type the global callback and prefer https for script src.

Removes linter ignores and prevents mixed-content/CSP surprises.

+		// Global callback for Google Translate script
+		declare global {
+			interface Window {
+				googleTranslateElementInit?: () => void
+			}
+		}
 		window.googleTranslateElementInit = initializeTranslate
 
 		if (!document.querySelector('script[src*="translate.google.com"]')) {
 			const script = document.createElement('script')
-			script.src =
-				'//translate.google.com/translate_a/element.js?cb=googleTranslateElementInit'
+			script.src =
+				'https://translate.google.com/translate_a/element.js?cb=googleTranslateElementInit'
 			script.async = true
 			document.head.appendChild(script)
 		} else if (window.google?.translate) {
 			initializeTranslate()
 		}

60-66: Cookie domain format.

Consider a leading dot to scope across subdomains if needed.

-			document.cookie = `googtrans=/en/${langCode}; domain=${window.location.hostname}; path=/`
+			document.cookie = `googtrans=/en/${langCode}; Domain=.${window.location.hostname}; Path=/`

158-160: Hide the embedded widget container in production.

Keep it accessible-hidden to avoid layout shifts.

-			<div ref={containerRef} />
+			<div ref={containerRef} aria-hidden className="sr-only" />

14-17: Default included languages: import from constants to prevent drift.

Use the shared includedLanguagesString.

-import type { GoogleTranslateProps } from '@/types'
+import type { GoogleTranslateProps } from '@/types'
+import { includedLanguagesString } from '@/lib/constants/preferences'
@@
-	includedLanguages = 'es,fr,de,it,pt,ru,ja,ko,zh,ar,en',
+	includedLanguages = includedLanguagesString,
apps/pro-web/components/onboarding/onboarding-card.tsx (1)

158-172: Guard against conflicting arrow orientations (top/bottom/left/right).

If callers pass multiple edges (e.g., top and left), classes conflict. Ensure only one orientation wins (e.g., prioritize left/right over top/bottom or vice‑versa), or compute a single “placement” value before render and branch once.

Would you like me to push a small refactor that derives a single placement from position and maps it to classes?

apps/web/components/onboarding/onboarding-card.tsx (1)

158-172: Guard against conflicting arrow orientations (top/bottom/left/right).

Mirror the placement unification here as well.

Use the same check script shared in the pro‑web comment to ensure parity.

apps/pro-web/app/api/payment/subscription/route.tsx (3)

125-128: Misleading log message.

This path is “get details,” not “create subscription.” Update the log for accurate diagnostics.

-console.error('Error creating subscription:', error)
+console.error('Error fetching subscription details:', error)

236-246: Validate inputs strictly (type coercion, schema).

at_period_end may arrive as a string. Add schema validation (e.g., Zod) and coerce to boolean. Also normalize and trim email.

If Zod is acceptable here, I can provide a small schema snippet.


60-71: Handle subscriptionId object/string and avoid invalid cast.

invoice.subscription can be an object or string. Normalize before retrieval.

-const subscriptionId = invoice.subscription
-const subscription = await stripe.subscriptions.retrieve(
-  subscriptionId as string,
-  { expand: ['items.data.price.product', 'customer'] },
-)
+const subscriptionId =
+  typeof invoice.subscription === 'string'
+    ? invoice.subscription
+    : invoice.subscription?.id
+if (!subscriptionId) {
+  return new Response(
+    JSON.stringify({ error: 'Subscription ID not found in invoice' }),
+    { status: 404, headers: { 'Content-Type': 'application/json' } },
+  )
+}
+const subscription = await stripe.subscriptions.retrieve(subscriptionId, {
+  expand: ['items.data.price.product', 'customer'],
+})

Also applies to: 72-78

apps/pro-web/components/layout/profile/layout.tsx (3)

50-53: Don’t rethrow inside effect; set local state and surface UI feedback instead.

Rethrowing here can create unhandled promise rejections. Prefer setting state and optionally surfacing a toast.

-    } catch (error) {
-      console.error('Failed to fetch user info:', error)
-      throw error
-    }
+    } catch (error) {
+      console.error('Failed to fetch user info:', error)
+      setUserData(null)
+      // TODO: optionally show a user-facing notification
+    }

55-59: Remove the Biome ignore and narrow dependencies; add basic unmount safety.

Avoid suppressing lint. Guard by slug and session slug, and add a simple unmount flag to reduce setState-after-unmount risk.

- // biome-ignore lint/correctness/useExhaustiveDependencies: <explanation>
- React.useEffect(() => {
-   if (user) return
-   getUser()
- }, [userSlug, session])
+ React.useEffect(() => {
+   if (user) return
+   let cancelled = false
+   ;(async () => {
+     if (!cancelled) await getUser()
+   })()
+   return () => {
+     cancelled = true
+   }
+ }, [userSlug, session?.user?.slug, user])

65-65: Avoid as User and pass a loading state to show skeletons (per prior learning).

Leverage UserCard’s loading prop via Hero. This removes the unsafe cast and shows a skeleton while data loads.

-        <Hero user={userData as User} />
+        <Hero user={userData ?? undefined} loading={!userData} />

Additionally update apps/pro-web/components/layout/profile/hero.tsx:

-export function Hero({ user }: { user: User }) {
-  if (!user) return null
-  return (
-    <div className="profile-hero-bg" id="hero-section">
-      <div className="relative z-[2] md:px-0 px-5">
-        <UserCard user={user} loading={false} />
-      </div>
-    </div>
-  )
-}
+export function Hero({
+  user,
+  loading = false,
+}: {
+  user?: User | null
+  loading?: boolean
+}) {
+  return (
+    <div className="profile-hero-bg" id="hero-section">
+      <div className="relative z-[2] md:px-0 px-5">
+        <UserCard user={user ?? undefined} loading={loading} />
+      </div>
+    </div>
+  )
+}
packages/mb-drizzle/src/drizzle/schema.ts (5)

62-110: Chatbot defaults/isPro LGTM; consider FK for createdBy + verify function.

  • createdBy is free-text; if this is a user reference, add a FK to user.userId (or keep as text but document allowed values).
  • Ensure get_chatbot_count() exists in the DB.

220-229: Function name mismatch risk: get_topic_count() on category.order.

If categories are distinct from topics now, confirm this function exists/returns correct ordering for category.


230-249: Redundant unique on primary key.

unique('prompt_prompt_id_key') duplicates the PK on promptId; drop it to avoid unnecessary index.

Apply this diff:

 		foreignKey({
 			columns: [table.type],
 			foreignColumns: [promptTypeEnum.value],
 			name: 'prompt_type_fkey',
 		})
 			.onUpdate('restrict')
 			.onDelete('restrict'),
-		unique('prompt_prompt_id_key').on(table.promptId),
 	],

352-359: token table LGTM; consider cleanup index.

If you’ll purge expired tokens by token_expiry, add an index on token_expiry for faster deletes/scans.


612-612: metadata now nullable—guard or default to {}.

Many callers assume object; consider default('{}'::jsonb) + notNull to avoid null handling.

Apply this diff:

-		metadata: jsonb(),
+		metadata: jsonb().default(sql`'{}'::jsonb`).notNull(),
apps/hasura/migrations/masterbots/1758582918191_alter_table_public_thread_add_column_is_pro/up.sql (1)

1-2: Prefer NOT NULL with default and backfill for is_pro; consider an index.

Allowing NULL creates a tri‑state boolean while code always filters isPro = true. Use NOT NULL with default false and backfill existing rows to false. Also consider an index to support frequent filters.

Follow‑up migration:

-- backfill existing nulls (run before making NOT NULL)
update public.thread set is_pro = false where is_pro is null;

-- enforce NOT NULL and (optionally) keep default
alter table public.thread
  alter column is_pro set not null,
  alter column is_pro set default false;

-- optional index to speed up common predicates
create index concurrently if not exists idx_thread_is_pro on public.thread(is_pro) where is_pro = true;
apps/pro-web/services/hasura/hasura.service.ts (2)

284-294: Consistent isPro gating across read paths — LGTM; add index/backfill check.

The uniform isPro: {_eq: true} filters are correct for pro‑web. Ensure the migration backfills and indexes is_pro to avoid regressing query latency.

Pair with:

  • Migration to set NOT NULL + default false and backfill nulls.
  • Index on (is_pro) or composite with (is_public, is_approved) if those filters are common.

Also applies to: 439-461, 827-829, 886-887, 1076-1078, 1273-1276, 1310-1310, 2012-2015


1863-1872: “Delete all” function filters to public+approved+pro only.

Name and JSDoc promise “all” user messages/threads, but where clause excludes private/unapproved/non‑pro. Clarify or widen.

Apply one of:

-// a function to delete all users messages and threads
+// delete all user's public, approved, pro messages and threads

or add a param to control scope and default to “all”.

apps/pro-web/app/api/auth/verify-token/route.ts (4)

13-27: Add strict input validation (schema) before use

Validate types and formats (e.g., UUID for userId, RFC‑5322 email). Use a schema validator to reject malformed input early.

Example (with zod):

import { z } from 'zod'
const Body = z.object({
  userId: z.string().min(1),
  email: z.string().email(),
  jwt: z.string().optional(),
})
const parsed = Body.safeParse(await req.json())
if (!parsed.success) {
  return NextResponse.json({ error: 'Invalid payload' }, { status: 400 })
}
const { userId, email, jwt } = parsed.data

87-95: Don’t leak internal error messages to clients

Returning error.message can expose internals. Log server-side; return a generic message and an error code.

Apply this diff:

-    return NextResponse.json(
-      {
-        success: false,
-        error:
-          error.message ||
-          'An error occurred while creating verification token',
-      },
-      { status: 500 },
-    )
+    console.error('verify-token failed:', error)
+    return NextResponse.json(
+      { success: false, error: 'Internal server error', code: 'VERIFICATION_CREATE_FAILED' },
+      { status: 500 },
+    )

11-12: Add rate limiting and abuse protections

Throttle by IP+email/userId to prevent token/email spam. Consider Upstash/Redis-backed limits and exponential backoff on repeated requests.


11-97: Extract shared token issuance + email verification into packages/mb-lib

Duplicate token generation, DB inserts/linking (insertTokenOne / insertUserTokenOne) and sendEmailVerification exist in apps/web and apps/pro-web — extract generation, DB insert, user-token linking, and email send into packages/mb-lib/src/auth/verification.ts and call it from the routes.

Primary locations to update: apps/web/app/api/auth/verify-token/route.ts, apps/pro-web/app/api/auth/verify-token/route.ts, apps/web/app/api/cron/unverified-users/route.ts, apps/pro-web/app/api/cron/unverified-users/route.ts, apps/web/lib/email-verification.ts, apps/pro-web/lib/email-verification.ts, apps/web/app/api/auth/signup/route.ts, apps/pro-web/app/api/auth/signup/route.ts

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 32b7a1b and c2c9a39.

⛔ Files ignored due to path filters (3)
  • packages/mb-genql/generated/schema.graphql is excluded by !**/generated/**
  • packages/mb-genql/generated/schema.ts is excluded by !**/generated/**
  • packages/mb-genql/generated/types.ts is excluded by !**/generated/**
📒 Files selected for processing (38)
  • apps/hasura/metadata/databases/masterbots/tables/public_thread.yaml (5 hunks)
  • apps/hasura/migrations/masterbots/1758582918191_alter_table_public_thread_add_column_is_pro/down.sql (1 hunks)
  • apps/hasura/migrations/masterbots/1758582918191_alter_table_public_thread_add_column_is_pro/up.sql (1 hunks)
  • apps/pro-web/app/api/auth/verify-token/route.ts (1 hunks)
  • apps/pro-web/app/api/generate-images/route.ts (1 hunks)
  • apps/pro-web/app/api/og/route.tsx (5 hunks)
  • apps/pro-web/app/api/payment/plans/route.tsx (0 hunks)
  • apps/pro-web/app/api/payment/subscription/route.tsx (3 hunks)
  • apps/pro-web/app/globals.css (3 hunks)
  • apps/pro-web/app/layout.tsx (1 hunks)
  • apps/pro-web/app/u/[userSlug]/layout.tsx (0 hunks)
  • apps/pro-web/app/u/[userSlug]/page.tsx (1 hunks)
  • apps/pro-web/app/u/[userSlug]/s/page.tsx (1 hunks)
  • apps/pro-web/app/u/[userSlug]/s/pref/page.tsx (1 hunks)
  • apps/pro-web/app/u/[userSlug]/s/subs/page.tsx (1 hunks)
  • apps/pro-web/app/u/[userSlug]/t/[category]/[domain]/[chatbot]/page.tsx (1 hunks)
  • apps/pro-web/app/u/[userSlug]/t/page.tsx (1 hunks)
  • apps/pro-web/app/u/layout.tsx (1 hunks)
  • apps/pro-web/components/layout/header/header.tsx (2 hunks)
  • apps/pro-web/components/layout/profile/hero.tsx (0 hunks)
  • apps/pro-web/components/layout/profile/layout.tsx (2 hunks)
  • apps/pro-web/components/layout/sidebar/sidebar-link.tsx (5 hunks)
  • apps/pro-web/components/layout/sidebar/sidebar-toggle.tsx (1 hunks)
  • apps/pro-web/components/layout/sidebar/sidebar.tsx (1 hunks)
  • apps/pro-web/components/onboarding/onboarding-card.tsx (3 hunks)
  • apps/pro-web/components/routes/home/home-page.tsx (1 hunks)
  • apps/pro-web/components/routes/preferences/google-translation.tsx (1 hunks)
  • apps/pro-web/components/routes/preferences/preference-section.tsx (6 hunks)
  • apps/pro-web/components/routes/profile/user-card.tsx (1 hunks)
  • apps/pro-web/components/routes/profile/user-thread-list.tsx (0 hunks)
  • apps/pro-web/lib/constants/preferences.ts (2 hunks)
  • apps/pro-web/lib/hooks/use-accessibility.tsx (1 hunks)
  • apps/pro-web/services/hasura/hasura.service.ts (13 hunks)
  • apps/pro-web/services/hasura/hasura.service.type.ts (1 hunks)
  • apps/web/components/layout/header/header.tsx (1 hunks)
  • apps/web/components/onboarding/onboarding-card.tsx (3 hunks)
  • packages/mb-drizzle/src/drizzle/relations.ts (5 hunks)
  • packages/mb-drizzle/src/drizzle/schema.ts (5 hunks)
💤 Files with no reviewable changes (4)
  • apps/pro-web/components/routes/profile/user-thread-list.tsx
  • apps/pro-web/components/layout/profile/hero.tsx
  • apps/pro-web/app/u/[userSlug]/layout.tsx
  • apps/pro-web/app/api/payment/plans/route.tsx
✅ Files skipped from review due to trivial changes (4)
  • apps/pro-web/app/u/[userSlug]/page.tsx
  • apps/pro-web/app/u/[userSlug]/t/page.tsx
  • apps/web/components/layout/header/header.tsx
  • apps/pro-web/components/routes/profile/user-card.tsx
🚧 Files skipped from review as they are similar to previous changes (3)
  • apps/pro-web/app/u/[userSlug]/t/[category]/[domain]/[chatbot]/page.tsx
  • apps/pro-web/components/layout/sidebar/sidebar-link.tsx
  • apps/pro-web/components/layout/sidebar/sidebar.tsx
🧰 Additional context used
📓 Path-based instructions (7)
apps/hasura/migrations/masterbots/**

📄 CodeRabbit inference engine (CLAUDE.md)

Hasura-managed migrations must reside in timestamped folders under apps/hasura/migrations/masterbots/

Files:

  • apps/hasura/migrations/masterbots/1758582918191_alter_table_public_thread_add_column_is_pro/up.sql
  • apps/hasura/migrations/masterbots/1758582918191_alter_table_public_thread_add_column_is_pro/down.sql
**/*.{ts,tsx,js,jsx,cjs,mjs,json,jsonc}

📄 CodeRabbit inference engine (CLAUDE.md)

Use Biome for all formatting and linting (run bun format-and-lint:fix) instead of Prettier/ESLint

Files:

  • apps/pro-web/lib/hooks/use-accessibility.tsx
  • apps/pro-web/app/api/auth/verify-token/route.ts
  • apps/pro-web/components/routes/preferences/preference-section.tsx
  • apps/pro-web/app/u/[userSlug]/s/subs/page.tsx
  • apps/pro-web/app/u/[userSlug]/s/page.tsx
  • apps/pro-web/services/hasura/hasura.service.type.ts
  • apps/pro-web/app/u/[userSlug]/s/pref/page.tsx
  • apps/web/components/onboarding/onboarding-card.tsx
  • apps/pro-web/components/routes/home/home-page.tsx
  • apps/pro-web/components/layout/header/header.tsx
  • apps/pro-web/app/api/payment/subscription/route.tsx
  • packages/mb-drizzle/src/drizzle/relations.ts
  • apps/pro-web/app/api/og/route.tsx
  • apps/pro-web/app/api/generate-images/route.ts
  • apps/pro-web/app/layout.tsx
  • apps/pro-web/components/layout/sidebar/sidebar-toggle.tsx
  • apps/pro-web/app/u/layout.tsx
  • apps/pro-web/components/layout/profile/layout.tsx
  • apps/pro-web/components/onboarding/onboarding-card.tsx
  • apps/pro-web/components/routes/preferences/google-translation.tsx
  • apps/pro-web/services/hasura/hasura.service.ts
  • packages/mb-drizzle/src/drizzle/schema.ts
  • apps/pro-web/lib/constants/preferences.ts
apps/{web,pro-web}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

apps/{web,pro-web}/**/*.{ts,tsx}: Use feature detection before invoking modern Browser APIs
Implement progressive enhancement: basic functionality first, then enhancements
Add timing delays or requestAnimationFrame when the DOM needs to settle before operations
Verify DOM operations completed successfully; don’t assume success (e.g., scroll/check actual state)
Use multiple approaches for DOM actions: try modern APIs first, then fallback to legacy methods

Files:

  • apps/pro-web/lib/hooks/use-accessibility.tsx
  • apps/pro-web/app/api/auth/verify-token/route.ts
  • apps/pro-web/components/routes/preferences/preference-section.tsx
  • apps/pro-web/app/u/[userSlug]/s/subs/page.tsx
  • apps/pro-web/app/u/[userSlug]/s/page.tsx
  • apps/pro-web/services/hasura/hasura.service.type.ts
  • apps/pro-web/app/u/[userSlug]/s/pref/page.tsx
  • apps/web/components/onboarding/onboarding-card.tsx
  • apps/pro-web/components/routes/home/home-page.tsx
  • apps/pro-web/components/layout/header/header.tsx
  • apps/pro-web/app/api/payment/subscription/route.tsx
  • apps/pro-web/app/api/og/route.tsx
  • apps/pro-web/app/api/generate-images/route.ts
  • apps/pro-web/app/layout.tsx
  • apps/pro-web/components/layout/sidebar/sidebar-toggle.tsx
  • apps/pro-web/app/u/layout.tsx
  • apps/pro-web/components/layout/profile/layout.tsx
  • apps/pro-web/components/onboarding/onboarding-card.tsx
  • apps/pro-web/components/routes/preferences/google-translation.tsx
  • apps/pro-web/services/hasura/hasura.service.ts
  • apps/pro-web/lib/constants/preferences.ts
apps/{web,pro-web}/**/*.tsx

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

apps/{web,pro-web}/**/*.tsx: Minimize useEffect dependencies to avoid infinite loops
Use refs for values that should not trigger re-renders
Consider component lifecycle timing when performing DOM operations from React components
Separate concerns: state updates vs. direct DOM manipulation in React components

Files:

  • apps/pro-web/lib/hooks/use-accessibility.tsx
  • apps/pro-web/components/routes/preferences/preference-section.tsx
  • apps/pro-web/app/u/[userSlug]/s/subs/page.tsx
  • apps/pro-web/app/u/[userSlug]/s/page.tsx
  • apps/pro-web/app/u/[userSlug]/s/pref/page.tsx
  • apps/web/components/onboarding/onboarding-card.tsx
  • apps/pro-web/components/routes/home/home-page.tsx
  • apps/pro-web/components/layout/header/header.tsx
  • apps/pro-web/app/api/payment/subscription/route.tsx
  • apps/pro-web/app/api/og/route.tsx
  • apps/pro-web/app/layout.tsx
  • apps/pro-web/components/layout/sidebar/sidebar-toggle.tsx
  • apps/pro-web/app/u/layout.tsx
  • apps/pro-web/components/layout/profile/layout.tsx
  • apps/pro-web/components/onboarding/onboarding-card.tsx
  • apps/pro-web/components/routes/preferences/google-translation.tsx
apps/**/lib/hooks/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Store custom React hooks under lib/hooks/

Files:

  • apps/pro-web/lib/hooks/use-accessibility.tsx
apps/**/components/routes/**

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Place page-specific components under components/routes/[feature]/

Files:

  • apps/pro-web/components/routes/preferences/preference-section.tsx
  • apps/pro-web/components/routes/home/home-page.tsx
  • apps/pro-web/components/routes/preferences/google-translation.tsx
apps/**/services/**/*.ts

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Store external API integration code under services/

Files:

  • apps/pro-web/services/hasura/hasura.service.type.ts
  • apps/pro-web/services/hasura/hasura.service.ts
🧠 Learnings (14)
📓 Common learnings
Learnt from: AndlerRL
PR: bitcashorg/masterbots#0
File: :0-0
Timestamp: 2025-09-04T21:31:18.830Z
Learning: Successfully created comprehensive GitHub issue #555 consolidating performance feedback and edge-case management for pro-web workspace features, including prioritized action items, testing requirements, and specific file references for implementation.
📚 Learning: 2025-09-12T20:29:24.733Z
Learnt from: AndlerRL
PR: bitcashorg/masterbots#561
File: apps/pro-web/lib/hooks/use-accessibility.tsx:7-7
Timestamp: 2025-09-12T20:29:24.733Z
Learning: When extending FontSize type in use-accessibility.tsx hooks, corresponding CSS rules must exist in globals.css for [data-font-size="value"] selectors, otherwise the unsupported font size values should be removed from the type definition to maintain consistency between TypeScript types and actual styling support.

Applied to files:

  • apps/pro-web/lib/hooks/use-accessibility.tsx
  • apps/pro-web/app/globals.css
📚 Learning: 2024-12-04T22:22:16.749Z
Learnt from: AndlerRL
PR: bitcashorg/masterbots#320
File: apps/masterbots.ai/services/admin/admin.service.ts:6-11
Timestamp: 2024-12-04T22:22:16.749Z
Learning: In `apps/masterbots.ai/services/admin/admin.service.ts`, the `verifyAdmin` function uses email domain verification as an additional security layer, complementing the role-based security implemented via JWTs and the GraphQL API with user roles "users" and "moderators".

Applied to files:

  • apps/pro-web/app/api/auth/verify-token/route.ts
📚 Learning: 2025-08-29T20:34:37.023Z
Learnt from: AndlerRL
PR: bitcashorg/masterbots#542
File: apps/web/components/routes/preferences/preference-section.tsx:228-249
Timestamp: 2025-08-29T20:34:37.023Z
Learning: In the preferences component (apps/web/components/routes/preferences/preference-section.tsx), email inputs are read-only and should not be changeable by users. Both updateInput and handleUpdateProfile functions need guards to prevent email changes even if users manipulate the DOM to remove the read-only attribute.

Applied to files:

  • apps/pro-web/components/routes/preferences/preference-section.tsx
📚 Learning: 2025-08-29T20:34:37.023Z
Learnt from: AndlerRL
PR: bitcashorg/masterbots#542
File: apps/web/components/routes/preferences/preference-section.tsx:228-249
Timestamp: 2025-08-29T20:34:37.023Z
Learning: In the preferences component (apps/web/components/routes/preferences/preference-section.tsx), email inputs are read-only and should not be changeable by users. Both updateInput and handleUpdateProfile functions need guards to prevent email changes even if users manipulate the DOM to remove the read-only attribute. The updateInput function should return early for email inputId, and handleUpdateProfile should always use currentUser?.email instead of inputValue.email.

Applied to files:

  • apps/pro-web/components/routes/preferences/preference-section.tsx
📚 Learning: 2025-04-26T10:58:44.341Z
Learnt from: AndlerRL
PR: bitcashorg/masterbots#465
File: apps/masterbots.ai/components/routes/browse/browse-list.tsx:1-1
Timestamp: 2025-04-26T10:58:44.341Z
Learning: The browse-list.tsx component is redundant as thread-list.tsx has been developed to support all the same functionality including public pages. The codebase should be cleaned up to remove browse-list.tsx and related components in the browse directory, along with the BrowseProvider context.

Applied to files:

  • apps/pro-web/components/routes/home/home-page.tsx
📚 Learning: 2025-04-26T10:58:44.341Z
Learnt from: AndlerRL
PR: bitcashorg/masterbots#465
File: apps/masterbots.ai/components/routes/browse/browse-list.tsx:1-1
Timestamp: 2025-04-26T10:58:44.341Z
Learning: The browse-list.tsx component and related components in the browse directory are redundant since thread-list.tsx has been developed to support all pages including public pages, and should be removed to simplify the codebase.

Applied to files:

  • apps/pro-web/components/routes/home/home-page.tsx
📚 Learning: 2024-11-07T14:40:39.595Z
Learnt from: Bran18
PR: bitcashorg/masterbots#301
File: apps/masterbots.ai/components/routes/chat/chat-combobox.tsx:33-33
Timestamp: 2024-11-07T14:40:39.595Z
Learning: In `apps/masterbots.ai/components/routes/chat/chat-combobox.tsx`, the 'MB' logo is temporarily represented by the string `'MB'` until the custom MB icon is created.

Applied to files:

  • apps/pro-web/components/layout/header/header.tsx
📚 Learning: 2025-06-10T05:48:25.508Z
Learnt from: Bran18
PR: bitcashorg/masterbots#504
File: apps/masterbots.ai/app/api/generate-images/route.ts:58-80
Timestamp: 2025-06-10T05:48:25.508Z
Learning: In apps/masterbots.ai/app/api/generate-images/route.ts, the conditional check for modelId === 'gpt-image-1' is correct and uses the OpenAI client directly to handle this advanced model, while other models like 'dall-e-2' and 'dall-e-3' use the AI SDK. This implementation pattern is appropriate for handling different model capabilities.

Applied to files:

  • apps/pro-web/app/api/generate-images/route.ts
📚 Learning: 2024-12-05T01:08:50.085Z
Learnt from: AndlerRL
PR: bitcashorg/masterbots#0
File: :0-0
Timestamp: 2024-12-05T01:08:50.085Z
Learning: The frontend uses shadcn/ui components and follows a strict component organization with proper separation of client/server components in Next.js.

Applied to files:

  • apps/pro-web/app/layout.tsx
📚 Learning: 2025-08-29T20:34:24.433Z
Learnt from: AndlerRL
PR: bitcashorg/masterbots#542
File: apps/web/components/layout/profile/layout.tsx:67-68
Timestamp: 2025-08-29T20:34:24.433Z
Learning: The UserCard component in apps/web/components/routes/profile/user-card.tsx accepts a loading prop to show skeleton/loading states. This should be used when user data is not yet available rather than passing null or using type assertions.

Applied to files:

  • apps/pro-web/app/u/layout.tsx
  • apps/pro-web/components/layout/profile/layout.tsx
📚 Learning: 2025-09-12T20:30:40.360Z
Learnt from: AndlerRL
PR: bitcashorg/masterbots#561
File: apps/web/components/layout/profile/layout.tsx:28-31
Timestamp: 2025-09-12T20:30:40.360Z
Learning: Created a centralized normalizeUserSlug utility function in apps/web/lib/utils.ts and apps/pro-web/lib/utils.ts that safely handles undefined parameter values, trims whitespace, and converts to lowercase. This should be used consistently across all userSlug parameter extractions to prevent crashes and ensure database query consistency.

Applied to files:

  • apps/pro-web/app/u/layout.tsx
📚 Learning: 2025-09-12T20:31:46.651Z
Learnt from: CR
PR: bitcashorg/masterbots#0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-12T20:31:46.651Z
Learning: Applies to **/services/hasura.service.ts : Abstract Hasura-specific details behind service methods (don’t leak Hasura specifics to callers)

Applied to files:

  • apps/pro-web/services/hasura/hasura.service.ts
📚 Learning: 2025-01-25T08:43:05.202Z
Learnt from: AndlerRL
PR: bitcashorg/masterbots#344
File: apps/masterbots.ai/services/hasura/hasura.service.ts:0-0
Timestamp: 2025-01-25T08:43:05.202Z
Learning: In the masterbots.ai database schema:
1. Previous "categories" are now called "topics"
2. Chatbots are now referred to as "domains"
3. Domains have context-dependent "categories" and "subcategories" based on user question context
4. Domains include "tags" that are used for filtering based on the user question context

Applied to files:

  • packages/mb-drizzle/src/drizzle/schema.ts
🧬 Code graph analysis (13)
apps/pro-web/app/api/auth/verify-token/route.ts (3)
packages/mb-lib/src/hasura/hasura.lib.ts (1)
  • getHasuraClient (19-54)
apps/pro-web/lib/email.ts (1)
  • sendEmailVerification (77-98)
apps/web/app/api/auth/verify-token/route.ts (1)
  • POST (11-97)
apps/pro-web/components/routes/preferences/preference-section.tsx (4)
apps/pro-web/types/index.ts (1)
  • PreferenceSectionProps (398-402)
apps/web/auth.ts (2)
  • session (174-189)
  • jwt (101-173)
apps/pro-web/lib/hooks/use-accessibility.tsx (1)
  • useAccessibility (39-47)
packages/mb-lib/src/text/text.lib.ts (1)
  • toSlug (12-44)
apps/pro-web/app/u/[userSlug]/s/subs/page.tsx (1)
apps/pro-web/app/u/[userSlug]/s/pref/page.tsx (1)
  • PreferencePage (3-9)
apps/pro-web/app/u/[userSlug]/s/pref/page.tsx (1)
apps/pro-web/app/u/[userSlug]/s/subs/page.tsx (1)
  • PreferencePage (3-5)
apps/web/components/onboarding/onboarding-card.tsx (1)
apps/pro-web/lib/utils.ts (1)
  • cn (9-11)
apps/pro-web/components/routes/home/home-page.tsx (2)
apps/pro-web/components/routes/browse/browse-list.tsx (1)
  • BrowseList (48-372)
apps/pro-web/components/shared/onboarding-section.tsx (1)
  • OnboardingSection (10-139)
packages/mb-drizzle/src/drizzle/relations.ts (1)
packages/mb-drizzle/src/drizzle/schema.ts (14)
  • models (29-45)
  • modelsEnum (47-60)
  • thread (593-644)
  • preference (112-169)
  • lengthEnum (259-262)
  • chatbot (62-110)
  • toneEnum (264-267)
  • typeEnum (269-272)
  • complexityEnum (254-257)
  • user (171-214)
  • prompt (230-248)
  • promptTypeEnum (250-252)
  • promptChatbot (432-458)
  • promptUser (460-486)
apps/pro-web/app/api/og/route.tsx (1)
apps/web/components/shared/og-image.tsx (1)
  • OgImageProps (4-18)
apps/pro-web/app/u/layout.tsx (3)
apps/pro-web/services/hasura/hasura.service.ts (1)
  • getUserBySlug (1317-1425)
apps/web/auth.ts (1)
  • session (174-189)
apps/pro-web/components/layout/profile/layout.tsx (1)
  • ProfileLayoutContent (15-81)
apps/pro-web/components/layout/profile/layout.tsx (3)
apps/web/auth.ts (1)
  • session (174-189)
apps/pro-web/services/hasura/hasura.service.ts (1)
  • getUserBySlug (1317-1425)
apps/pro-web/components/layout/profile/hero.tsx (1)
  • Hero (5-14)
apps/pro-web/components/onboarding/onboarding-card.tsx (1)
apps/pro-web/lib/utils.ts (1)
  • cn (9-11)
apps/pro-web/components/routes/preferences/google-translation.tsx (2)
apps/web/types/types.ts (1)
  • GoogleTranslateProps (671-674)
apps/pro-web/lib/constants/preferences.ts (1)
  • languageOptions (210-222)
apps/pro-web/services/hasura/hasura.service.ts (2)
apps/web/services/hasura/hasura.service.ts (2)
  • updateUser (2018-2071)
  • isUsernameTaken (2003-2016)
apps/pro-web/services/hasura/hasura.service.type.ts (1)
  • UpdateUserArgs (87-96)

Comment on lines +1 to +4
-- Could not auto-generate a down migration.
-- Please write an appropriate down migration for the SQL below:
-- alter table "public"."thread" add column "is_pro" boolean
-- null default 'false';
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

Provide a real down migration.

Current placeholder blocks rollback.

Apply:

--- Could not auto-generate a down migration.
--- Please write an appropriate down migration for the SQL below:
--- alter table "public"."thread" add column "is_pro" boolean
---  null default 'false';
+alter table "public"."thread" drop column if exists "is_pro";

Note: ensure metadata also removes column references in a matching metadata down step.

📝 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
-- Could not auto-generate a down migration.
-- Please write an appropriate down migration for the SQL below:
-- alter table "public"."thread" add column "is_pro" boolean
-- null default 'false';
alter table "public"."thread" drop column if exists "is_pro";
🤖 Prompt for AI Agents
In
apps/hasura/migrations/masterbots/1758582918191_alter_table_public_thread_add_column_is_pro/down.sql
lines 1-4, the file currently contains a placeholder and lacks a real rollback;
replace it with a down migration that drops the added column and ensure you also
add a corresponding metadata-down step that removes any references to "is_pro"
(e.g., from permission checks, relationships, computed fields, event triggers)
so the schema and Hasura metadata are fully reverted.

Comment on lines +15 to +27
if (!jwt) {
return NextResponse.json(
{ error: 'Authentication required to create verification token' },
{ status: 401 },
)
}

if (!userId || !email) {
return NextResponse.json(
{ error: 'Missing required fields: userId or email' },
{ status: 400 },
)
}
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

🧩 Analysis chain

Authenticate for real: verify the JWT and bind claims to userId/email

Only checking that a non-empty jwt exists is not authentication. Verify the JWT signature and ensure its subject/email match the provided userId/email. Also accept the Authorization: Bearer header and return a proper WWW-Authenticate header on 401.

Apply this diff:

-    if (!jwt) {
-      return NextResponse.json(
-        { error: 'Authentication required to create verification token' },
-        { status: 401 },
-      )
-    }
-
-    if (!userId || !email) {
-      return NextResponse.json(
-        { error: 'Missing required fields: userId or email' },
-        { status: 400 },
-      )
-    }
+    // Accept Authorization: Bearer <token> or body.jwt as fallback
+    const authHeader = req.headers.get('authorization') ?? undefined
+    const bearer = authHeader?.startsWith('Bearer ') ? authHeader.slice(7) : undefined
+    const token = bearer ?? jwt
+    if (!token) {
+      return NextResponse.json(
+        { error: 'Authentication required to create verification token' },
+        {
+          status: 401,
+          headers: { 'WWW-Authenticate': 'Bearer realm="verify-token", error="invalid_token"' },
+        },
+      )
+    }
+
+    if (!userId || !email) {
+      return NextResponse.json(
+        { error: 'Missing required fields: userId or email' },
+        { status: 400 },
+      )
+    }
+
+    // Verify JWT signature and bind to requested identity
+    const claims = await verifyJwt(token)
+    if (!claims || (claims.sub && claims.sub !== String(userId)) || (claims.email && claims.email.toLowerCase() !== String(email).toLowerCase())) {
+      return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
+    }

Helper (place near top of file or shared util):

// Minimal symmetric verification; prefer your existing auth util if available.
import { jwtVerify } from 'jose'

async function verifyJwt(t: string) {
  const secret = process.env.AUTH_JWT_SECRET ?? process.env.NEXTAUTH_SECRET
  if (!secret) throw new Error('JWT secret not configured')
  try {
    const { payload } = await jwtVerify(t, new TextEncoder().encode(secret))
    return payload as { sub?: string; email?: string }
  } catch {
    return null
  }
}

Run to reuse existing helpers if present:


🏁 Script executed:

#!/bin/bash
# Look for existing JWT verification utilities to avoid duplicating logic
rg -nP -C2 '(next-auth|jose|jwtVerify|getToken|verifyJwt|verifySession|assertServerAuth)'

Length of output: 77467


🏁 Script executed:

#!/bin/bash
set -euo pipefail
echo "===== packages/mb-lib/src/jwt/jwt.lib.ts ====="
sed -n '1,240p' packages/mb-lib/src/jwt/jwt.lib.ts || true
echo
echo "===== grep for exports in jwt lib ====="
rg -n "export .*verify|export .*getToken|export .*isTokenExpired" packages/mb-lib/src/jwt/jwt.lib.ts || true

Length of output: 4805


🏁 Script executed:

#!/bin/bash
set -euo pipefail
echo "===== apps/pro-web/app/api/auth/verify-token/route.ts ====="
sed -n '1,240p' apps/pro-web/app/api/auth/verify-token/route.ts || true
echo
echo "===== show import lines in file ====="
sed -n '1,40p' apps/pro-web/app/api/auth/verify-token/route.ts || true

Length of output: 3370


Authenticate for real: verify the JWT (reuse mb-lib) and bind claims to the requested identity

Replace the current presence-check with full verification: accept Authorization: Bearer (fallback to body.jwt), verify signature using the existing helpers in packages/mb-lib/src/jwt/jwt.lib.ts (verify / decodeToken and validateJwtSecret), then ensure the verified payload identifies the same user (compare payload.user.account or payload.sub to userId; if the token contains email, compare it case-insensitively to the request email). Return 401 with a WWW-Authenticate header for missing/invalid tokens and 403 when the token is valid but does not match the requested identity.

Location: apps/pro-web/app/api/auth/verify-token/route.ts (current jwt presence-check around lines ~15–27). Reuse: packages/mb-lib/src/jwt/jwt.lib.ts (exported verify / decodeToken / validateJwtSecret).

)
}

const client = getHasuraClient()
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

Avoid using an admin Hasura client; use least‑privileged service credentials

getHasuraClient() yields admin‑level access. Issue tokens via a narrowed service role/secret scoped to tokens/user_tokens only. Reduces blast radius if this route is abused.

Comment on lines +31 to +36
// Generate verification token
const expiryDays = 15
const verificationToken = crypto.randomBytes(32).toString('hex')
const tokenExpiry = new Date()
tokenExpiry.setDate(tokenExpiry.getDate() + expiryDays)

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

🧩 Analysis chain

Hash tokens at rest; store hash, email raw token

Store only a cryptographic hash of the token in the DB. If the DB leaks, plaintext tokens shouldn’t be usable. Keep emailing the raw token.

Apply this diff (uses SHA‑256; adjust column/schema as needed; if the column name must remain token, store the hash in token and update the verification endpoint accordingly):

-    const verificationToken = crypto.randomBytes(32).toString('hex')
+    const verificationTokenRaw = crypto.randomBytes(32).toString('hex')
+    const verificationTokenHash = crypto
+      .createHash('sha256')
+      .update(verificationTokenRaw)
+      .digest('hex')
@@
-          object: {
-            token: verificationToken,
-            tokenExpiry: tokenExpiry.toISOString(),
-          },
+          object: {
+            token: verificationTokenHash,
+            tokenExpiry: tokenExpiry.toISOString(),
+          },
@@
-          object: {
-            userId,
-            token: verificationToken,
-          },
+          object: {
+            userId,
+            token: verificationTokenHash,
+          },
@@
-    await sendEmailVerification(email, verificationToken)
+    await sendEmailVerification(email, verificationTokenRaw)

Please confirm the verification-consumer route hashes incoming tokens before comparing. If not, it must be updated in tandem.

Also applies to: 38-49, 58-70, 72-74


🏁 Script executed:

#!/bin/bash
set -euo pipefail
echo "PWD: $(pwd)"
echo
echo "=== Searching repo for verification/email token code ==="
rg -n --hidden -S "sendEmailVerification|verificationTokenRaw|verificationTokenHash|verificationToken|tokenExpiry|verify-token|verify_token|verifyToken" -g '!node_modules/**' -C3 || true
echo
echo "=== Searching for object: { token: ... } write patterns ==="
rg -n --hidden -S "object\s*:\s*{\s*token\s*:" -g '!node_modules/**' -C3 || true
echo
echo "=== Searching for hashing code (createHash / crypto.createHash) ==="
rg -n --hidden -S "crypto\.createHash|createHash\(" -g '!node_modules/**' -C3 || true
echo
echo "=== Searching for token consumption/compare (searchParams.get, req.body, req.query) ==="
rg -n --hidden -S "searchParams\.get\(|request\.nextUrl|req\.query\.|req\.body|request\.json\(" -g '!node_modules/**' -C3 || true
echo
echo "=== Show apps/pro-web/app/api/auth/verify-token/route.ts (if present) ==="
if [ -f "apps/pro-web/app/api/auth/verify-token/route.ts" ]; then
  sed -n '1,300p' "apps/pro-web/app/api/auth/verify-token/route.ts"
else
  echo "File not found: apps/pro-web/app/api/auth/verify-token/route.ts"
fi
echo
echo "=== Show files that reference sendEmailVerification (full file prints up to 240 lines) ==="
files=$(rg -l --hidden -S "sendEmailVerification" -g '!node_modules/**' || true)
if [ -n "$files" ]; then
  for f in $files; do
    echo "---- $f ----"
    sed -n '1,240p' "$f" || true
  done
else
  echo "No definitions/calls of sendEmailVerification found."
fi
echo
echo "=== Done ==="

Length of output: 3474


🏁 Script executed:

#!/bin/bash
set -euo pipefail
echo "PWD: $(pwd)"
echo

echo "=== Count TypeScript/JS files under apps ==="
find apps -type f \( -name '*.ts' -o -name '*.tsx' -o -name '*.js' -o -name '*.jsx' \) | wc -l || true
echo

echo "=== Look for getHasuraClient occurrences ==="
find apps -type f \( -name '*.ts' -o -name '*.tsx' -o -name '*.js' -o -name '*.jsx' \) -print0 \
  | xargs -0 grep -nH --line-number "getHasuraClient" || true
echo

echo "=== Find files with 'verify' in the filename/path under apps ==="
find apps -type f -iname "*verify*" -print || true
echo

echo "=== Grep for 'token' / 'verification' / 'verify' inside apps files (show matches) ==="
find apps -type f \( -name '*.ts' -o -name '*.tsx' -o -name '*.js' -o -name '*.jsx' \) -print0 \
  | xargs -0 grep -nH -E --line-number "token|verification|verify" || true
echo

echo "=== Show any candidate verification/confirm route files (first 240 lines) ==="
candidates=$(find apps -type f -iname "*verify*.ts" -o -iname "*verify*.tsx" -o -iname "*confirm*.ts" -o -iname "*confirm*.tsx" -o -iname "*verification*.ts" -o -iname "*verification*.tsx" || true)
if [ -n "$candidates" ]; then
  for f in $candidates; do
    echo "---- $f ----"
    sed -n '1,240p' "$f" || true
  done
else
  echo "No candidate verify/confirm files found under apps."
fi
echo
echo "=== Done ==="

Length of output: 54405


Hash tokens at rest; store SHA‑256 hash, email raw token

Tokens are currently generated and stored/compared as plaintext across the repo — store only a SHA‑256 hash in the DB and send the raw token in email; update all token-creation points to store the hash and update token-consumer routes to hash the incoming token before comparing.

Affected locations (fix both creation and consumption):

  • Creation: apps/pro-web/app/api/auth/verify-token/route.ts, apps/web/app/api/auth/verify-token/route.ts, apps/pro-web/lib/email-verification.ts, apps/web/lib/email-verification.ts, apps/pro-web/app/api/auth/signup/route.ts, apps/web/app/api/auth/signup/route.ts, apps/pro-web/app/api/cron/unverified-users/route.ts, apps/pro-web/app/api/auth/forgot-password/route.ts (and any other token-generation sites).
  • Consumption: apps/pro-web/app/api/auth/verify-email/route.ts, apps/web/app/api/auth/verify-email/route.ts, apps/pro-web/app/api/auth/reset-password/route.ts, apps/web/app/api/auth/reset-password/route.ts (and any endpoints that compare tokens).

Apply this diff (uses SHA‑256; adjust column/schema as needed; if the column name must remain token, store the hash in token and update the verification endpoint accordingly):

-    const verificationToken = crypto.randomBytes(32).toString('hex')
+    const verificationTokenRaw = crypto.randomBytes(32).toString('hex')
+    const verificationTokenHash = crypto
+      .createHash('sha256')
+      .update(verificationTokenRaw)
+      .digest('hex')
@@
-          object: {
-            token: verificationToken,
-            tokenExpiry: tokenExpiry.toISOString(),
-          },
+          object: {
+            token: verificationTokenHash,
+            tokenExpiry: tokenExpiry.toISOString(),
+          },
@@
-          object: {
-            userId,
-            token: verificationToken,
-          },
+          object: {
+            userId,
+            token: verificationTokenHash,
+          },
@@
-    await sendEmailVerification(email, verificationToken)
+    await sendEmailVerification(email, verificationTokenRaw)

Confirm the verification-consumer routes (verify-email, reset-password, etc.) hash incoming tokens with the same SHA‑256 before comparing to the stored value; update those routes in tandem.

📝 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
// Generate verification token
const expiryDays = 15
const verificationToken = crypto.randomBytes(32).toString('hex')
const tokenExpiry = new Date()
tokenExpiry.setDate(tokenExpiry.getDate() + expiryDays)
// Generate verification token
const expiryDays = 15
const verificationTokenRaw = crypto.randomBytes(32).toString('hex')
const verificationTokenHash = crypto
.createHash('sha256')
.update(verificationTokenRaw)
.digest('hex')
const tokenExpiry = new Date()
tokenExpiry.setDate(tokenExpiry.getDate() + expiryDays)
🤖 Prompt for AI Agents
In apps/pro-web/app/api/auth/verify-token/route.ts around lines 31-36, the code
currently generates and stores plaintext verification tokens; change token
handling to generate a raw token for email but store only its SHA-256 hash (hex)
in the DB, using a consistent algorithm/encoding (SHA-256 hex) and keep the same
expiry logic; update all token-creation sites listed in the review to compute
and save the hash instead of the raw token (or alter the DB column if needed),
and send the raw token in the verification email. Also update all
verification/consumer endpoints (verify-email, reset-password, etc.) to compute
the SHA-256 hex of the incoming token and compare that hash to the stored value
so consumers never compare plaintext tokens.

Comment on lines +58 to +70
// Then create the user-token relationship
await client.mutation({
insertUserTokenOne: {
__args: {
object: {
userId,
token: verificationToken,
},
},
userId: true,
token: true,
},
})
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

🧩 Analysis chain

Make DB operations atomic or compensate on failure

Two separate mutations + email send can leave orphan tokens on partial failure. Either perform a single nested mutation/transaction, or delete the token if the second mutation/email fails.

Example compensation pattern:

// After insertTokenOne succeeds
try {
  const linkRes = await client.mutation({
    insertUserTokenOne: {
      __args: { object: { userId, token: verificationTokenHash } },
      userId: true,
      token: true,
    },
  })
  await sendEmailVerification(email, verificationTokenRaw)
} catch (e) {
  // best-effort cleanup
  await client.mutation({
    deleteToken: {
      __args: { where: { token: { _eq: verificationTokenHash } } },
      affected_rows: true,
    },
  })
  throw e
}

If your Hasura schema supports nested inserts or transactions, prefer that. To discover available delete mutations:


🏁 Script executed:

#!/bin/bash
rg -nP -C2 'delete[A-Z]\w+|insert[A-Z]\w+One' packages apps | sed -n '1,120p'

Length of output: 7067


🏁 Script executed:

#!/bin/bash
set -euo pipefail
echo "1) Searching for token-related insert/delete mutations in the repo (apps/)..."
rg -nP -S '\b(insert[A-Z]\w+One|delete[A-Z]\w+)\b' apps | rg -i 'token|userToken|user_token' -n -S || true

echo
echo "2) Searching explicitly for insertUserTokenOne / deleteToken variants..."
rg -n -S 'insertUserTokenOne|insertUserToken|insert_token|deleteToken|delete_user_token|deleteUserToken' || true

echo
file='apps/pro-web/app/api/auth/verify-token/route.ts'
if [ -f "$file" ]; then
  echo "3) Showing $file (first 240 lines):"
  sed -n '1,240p' "$file"
else
  echo "3) $file not found — listing apps/pro-web tree:"
  ls -R apps/pro-web | sed -n '1,240p' || true
fi

Length of output: 6837


Make DB operations atomic or compensate on failure

apps/pro-web/app/api/auth/verify-token/route.ts currently inserts insertTokenOne, then calls insertUserTokenOne and sendEmailVerification with no rollback — partial failures can leave orphan Token rows. Use a single nested/transactional Hasura mutation if supported, or wrap the second mutation + email send in try/catch and call deleteToken (available in the schema) to remove the inserted token on error.

🤖 Prompt for AI Agents
In apps/pro-web/app/api/auth/verify-token/route.ts around lines 58 to 70, the
code inserts Token then separately inserts UserToken and sends email, which can
leave orphan Token rows on partial failure; fix by making the DB operations
atomic—preferably use a single nested/transactional Hasura mutation that inserts
Token and UserToken together in one request (if schema supports nested inserts)
or, if that’s not possible, wrap the insertUserTokenOne + sendEmailVerification
steps in a try/catch and on any error call the deleteToken mutation to remove
the previously inserted Token, then rethrow or return the error; also ensure you
log the error when performing the compensating delete and surface failures
appropriately.

Comment on lines +42 to +49
console.log({
userInfo,
})
if (!userInfo) {
throw new Error('User not found')
}
setUserData(userInfo?.user as User)
} catch (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

Remove client-side PII logging and handle missing user correctly.

console.log({ userInfo }) may leak PII (email, etc.). Also !userInfo is always falsey only when the wrapper is missing; check userInfo.user instead.

-      console.log({
-        userInfo,
-      })
-      if (!userInfo) {
-        throw new Error('User not found')
-      }
-      setUserData(userInfo?.user as User)
+      // Avoid logging PII in the client.
+      if (!userInfo?.user) {
+        console.warn('User not found for slug:', userSlug)
+        setUserData(null)
+        return
+      }
+      setUserData(userInfo.user as User)

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In apps/pro-web/components/layout/profile/layout.tsx around lines 42 to 49,
remove the console.log that prints userInfo to avoid leaking PII, change the
existence check from if (!userInfo) to if (!userInfo?.user) (or equivalent) so
you detect a missing wrapped user payload, and then throw or handle the missing
user consistently; finally call setUserData(userInfo.user as User) (not
userInfo?) and ensure no PII is logged anywhere in this handler.

Comment on lines +29 to +31
//? Show onboarding section for non-logged-in users
if (!session?.user) {
if (!showOnboarding && selectedCategories.length > 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

Prevent auth “loading” flash by gating render on status

While NextAuth is resolving, the component renders the logged‑out UI, causing visible flicker and potential hydration mismatches. Add an early return while status === 'loading'.

   //? Show onboarding section for non-logged-in users
+  // Avoid flashing logged-out UI while auth status is loading
+  if (status === 'loading') return null
   if (!session?.user) {
📝 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
//? Show onboarding section for non-logged-in users
if (!session?.user) {
if (!showOnboarding && selectedCategories.length > 0) {
//? Show onboarding section for non-logged-in users
// Avoid flashing logged-out UI while auth status is loading
if (status === 'loading') return null
if (!session?.user) {
if (!showOnboarding && selectedCategories.length > 0) {
🤖 Prompt for AI Agents
In apps/pro-web/components/routes/home/home-page.tsx around lines 29 to 31, the
component renders the logged-out UI while NextAuth is still resolving, causing a
flash; add an early return that checks the auth status and returns null (or a
loading placeholder) when status === 'loading' before any UI that assumes
session/user or showOnboarding is evaluated so the logged-out onboarding section
is not shown during authentication resolution.

Comment on lines +1 to +17
'use client'

import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select'
import { languageOptions } from '@/lib/constants/preferences'
import type { GoogleTranslateProps } from '@/types'
import { useEffect, useRef, useState } from 'react'

const GoogleTranslate: React.FC<GoogleTranslateProps> = ({
pageLanguage = 'en',
includedLanguages = 'es,fr,de,it,pt,ru,ja,ko,zh,ar,en',
}) => {
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

Privacy/compliance: gate third‑party translation behind user consent or a feature flag.

Third‑party script loads on preference view; ensure consent (esp. in EU) and allow opt‑out.

Would you like me to wire this behind a feature flag and consent check?

Also applies to: 99-109

Comment on lines +47 to +60
export const modelsEnum = pgTable(
'models_enum',
{
name: text().primaryKey().notNull(),
value: text().notNull(),
},
(table) => [
index('idx_models_enum_value').using(
'btree',
table.value.asc().nullsLast().op('text_ops'),
),
unique('models_enum_value_key').on(table.value),
],
)
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

Dual identifiers (name vs value) in modelsEnum will cause confusion.

Threads point to name; models point to value. Standardize on one (recommend value) and make the other derived or drop it.

Comment on lines +634 to +638
foreignKey({
columns: [table.model],
foreignColumns: [modelsEnum.name],
name: 'fk_model',
}),
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

🧩 Analysis chain

Critical: thread.model FK targets modelsEnum.name with incompatible type.

  • Type mismatch: thread.model is varchar(30) but modelsEnum.name is text—Postgres won’t allow this FK.
  • Inconsistency: models.model → modelsEnum.value, thread.model → modelsEnum.name.

Unify on modelsEnum.value and change thread.model to text.

Apply this diff to the FK:

-		foreignKey({
-			columns: [table.model],
-			foreignColumns: [modelsEnum.name],
-			name: 'fk_model',
-		}),
+		foreignKey({
+			columns: [table.model],
+			foreignColumns: [modelsEnum.value],
+			name: 'fk_model',
+		}),

Then update the column definition (outside this hunk) to match text:

// in thread columns block:
model: text().default('openAi').notNull(),

🏁 Script executed:

#!/bin/bash
# Verify current definitions and references of `model` and modelsEnum columns.
rg -n -C2 $'export const modelsEnum = pgTable' packages/mb-drizzle/src/drizzle/schema.ts
rg -n -C2 $'\bmodel:\s*varchar' packages/mb-drizzle/src/drizzle/schema.ts
rg -n -C3 'modelsEnum\.(name|value)' packages/mb-drizzle/src/drizzle/schema.ts packages/mb-drizzle/src/drizzle/relations.ts

Length of output: 2057


🏁 Script executed:

#!/bin/bash
set -euo pipefail
SCHEMA=packages/mb-drizzle/src/drizzle/schema.ts
REL=packages/mb-drizzle/src/drizzle/relations.ts

echo "=== occurrences of modelsEnum.name|value ==="
rg -n --hidden -S 'modelsEnum\.(name|value)' || true

echo
echo "=== schema.ts: modelsEnum block ==="
awk '/export const modelsEnum = pgTable\(/,/\)\s*;/' $SCHEMA || true

echo
echo "=== schema.ts: models table block ==="
awk '/export const models = pgTable\(/,/\)\s*;/' $SCHEMA || true

echo
echo "=== schema.ts: thread table block ==="
awk '/export const thread = pgTable\(/,/\)\s*;/' $SCHEMA || true

echo
echo "=== schema.ts: lines 630-642 (FK area) ==="
sed -n '630,642p' $SCHEMA || true

echo
echo "=== schema.ts: search for 'model:' column types ==="
rg -n --hidden -S -C2 '\bmodel\s*:\s*(varchar|text|varchar\(|text\()' $SCHEMA || true

echo
echo "=== relations.ts: modelsEnum occurrences (context) ==="
rg -n --hidden -S -C4 'modelsEnum: one\(modelsEnum' $REL || true

Length of output: 38526


Critical: unify thread.model to reference modelsEnum.value and make column type text

  • Problem: thread.model is varchar(30) and currently FK→ modelsEnum.name, while models.model FK→ modelsEnum.value — inconsistent and risks FK/type issues.
  • Fixes (apply in packages/mb-drizzle/src/drizzle):
# schema.ts: change the FK to reference value
-		foreignKey({
-			columns: [table.model],
-			foreignColumns: [modelsEnum.name],
-			name: 'fk_model',
-		}),
+		foreignKey({
+			columns: [table.model],
+			foreignColumns: [modelsEnum.value],
+			name: 'fk_model',
+		}),
# schema.ts: change thread.model column to text to match models.model
-		model: varchar({ length: 30 }).default('openAi').notNull(),
+		model: text().default('openAi').notNull(),
# relations.ts: update the relation to reference value (thread → modelsEnum)
-	modelsEnum: one(modelsEnum, {
-		fields: [thread.model],
-		references: [modelsEnum.name],
-	}),
+	modelsEnum: one(modelsEnum, {
+		fields: [thread.model],
+		references: [modelsEnum.value],
+	}),

Packages/mb-drizzle/src/drizzle/schema.ts locations: thread.model column (~lines 604–607) and FK (~lines 634–638). relations.ts relation around ~lines 313–316.

📝 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
foreignKey({
columns: [table.model],
foreignColumns: [modelsEnum.name],
name: 'fk_model',
}),
// packages/mb-drizzle/src/drizzle/schema.ts (thread.model column ~lines 604–607)
model: text().default('openAi').notNull(),
Suggested change
foreignKey({
columns: [table.model],
foreignColumns: [modelsEnum.name],
name: 'fk_model',
}),
// packages/mb-drizzle/src/drizzle/schema.ts (FK ~lines 634–638)
foreignKey({
columns: [table.model],
foreignColumns: [modelsEnum.value],
name: 'fk_model',
}),
Suggested change
foreignKey({
columns: [table.model],
foreignColumns: [modelsEnum.name],
name: 'fk_model',
}),
// packages/mb-drizzle/src/drizzle/relations.ts (~lines 313–316)
modelsEnum: one(modelsEnum, {
fields: [thread.model],
references: [modelsEnum.value],
}),
🤖 Prompt for AI Agents
In packages/mb-drizzle/src/drizzle/schema.ts around lines 604–607 and 634–638,
thread.model is defined as varchar(30) and its FK points to modelsEnum.name;
change the thread.model column to type text and update its foreignKey to
reference modelsEnum.value (not name) so it matches models.model's FK; also
update the relation in packages/mb-drizzle/src/drizzle/relations.ts around lines
~313–316 to use modelsEnum.value for the join/mapping instead of modelsEnum.name
to keep types and FK targets consistent.

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: 0

🧹 Nitpick comments (4)
apps/pro-web/components/shared/onboarding-section.tsx (4)

57-57: Remove duplicated early-return check.

if (!isOpen) return null appears twice (Line 25 and Line 57). Keep the first and drop the second.

- if (!isOpen) return null

41-49: Defensive map: tolerate categories with missing/empty chatbots.

Guard against category.chatbots being undefined or sparse to avoid runtime errors.

-    const selectedChatbots = allCategories
-      .filter((category) =>
-        localSelectedCategories.includes(category.categoryId),
-      )
-      .flatMap((category) =>
-        category.chatbots.map((chatbot) => chatbot.chatbot.chatbotId),
-      )
+    const selectedChatbots = allCategories
+      .filter((c) => localSelectedCategories.includes(c.categoryId))
+      .flatMap((c) =>
+        (c.chatbots ?? [])
+          .map((item) => item.chatbot?.chatbotId)
+          .filter((id): id is number => typeof id === 'number'),
+      )

89-93: Announce selection count to screen readers.

Add a polite live region so the count updates are conveyed when toggling.

- <div className="text-sm text-zinc-600 dark:text-zinc-400">
+ <div className="text-sm text-zinc-600 dark:text-zinc-400" aria-live="polite">

167-177: Make the toggle button state explicit for a11y.

Expose selection via aria-pressed and a concise label.

-  <button
+  <button
     type="button"
+    aria-pressed={isSelected}
+    aria-label={`${category.name}${isSelected ? ' selected' : ''}`}
     className={cn(
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c2c9a39 and 312fd0f.

📒 Files selected for processing (3)
  • apps/pro-web/app/u/[userSlug]/t/page.tsx (0 hunks)
  • apps/pro-web/components/shared/onboarding-section.tsx (1 hunks)
  • apps/pro-web/lib/constants/preferences.ts (2 hunks)
💤 Files with no reviewable changes (1)
  • apps/pro-web/app/u/[userSlug]/t/page.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/pro-web/lib/constants/preferences.ts
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx,js,jsx,cjs,mjs,json,jsonc}

📄 CodeRabbit inference engine (CLAUDE.md)

Use Biome for all formatting and linting (run bun format-and-lint:fix) instead of Prettier/ESLint

Files:

  • apps/pro-web/components/shared/onboarding-section.tsx
apps/{web,pro-web}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

apps/{web,pro-web}/**/*.{ts,tsx}: Use feature detection before invoking modern Browser APIs
Implement progressive enhancement: basic functionality first, then enhancements
Add timing delays or requestAnimationFrame when the DOM needs to settle before operations
Verify DOM operations completed successfully; don’t assume success (e.g., scroll/check actual state)
Use multiple approaches for DOM actions: try modern APIs first, then fallback to legacy methods

Files:

  • apps/pro-web/components/shared/onboarding-section.tsx
apps/{web,pro-web}/**/*.tsx

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

apps/{web,pro-web}/**/*.tsx: Minimize useEffect dependencies to avoid infinite loops
Use refs for values that should not trigger re-renders
Consider component lifecycle timing when performing DOM operations from React components
Separate concerns: state updates vs. direct DOM manipulation in React components

Files:

  • apps/pro-web/components/shared/onboarding-section.tsx
apps/**/components/shared/**

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Place reusable UI elements under components/shared/

Files:

  • apps/pro-web/components/shared/onboarding-section.tsx
🔇 Additional comments (1)
apps/pro-web/components/shared/onboarding-section.tsx (1)

6-6: Cannot verify imports/exports in sandbox — run verification locally

Import path consolidation to '@/types' looks good, but the sandbox rg returned "No files were searched", so I cannot confirm removal of any '@/types/index' imports or that OnboardingSectionProps and CategoryCardProps are exported from '@/types'. Run this from the repo root and paste the output:

#!/bin/bash
set -euo pipefail

echo "Searching for '@/types/index' imports (git grep)..."
git grep -n "from '@/types/index'" || echo "None found."

echo
echo "Searching for imports from '@/types' (git grep)..."
git grep -n "from '@/types'" || echo "None found."

echo
echo "Verifying exports for OnboardingSectionProps and CategoryCardProps (rg)..."
rg -n --hidden -uu -S "(export\s+(type|interface)\s+OnboardingSectionProps|export\s+\{[^}]*OnboardingSectionProps|export\s+(type|interface)\s+CategoryCardProps|export\s+\{[^}]*CategoryCardProps)" || echo "No exact export lines found."

echo
echo "Fallback: show occurrences (±3 lines) of the identifiers:"
rg -n --hidden -uu -S -C3 "\b(OnboardingSectionProps|CategoryCardProps)\b" || echo "No occurrences found."

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: 3

🧹 Nitpick comments (5)
apps/pro-web/components/shared/og-image.tsx (5)

28-35: Unused destructured variable: domain

domain is destructured but never used, which will trip Biome.

Apply this diff:

-    domain,

If you intended to show the domain, consider rendering it (e.g., as a subtle footer label) instead of removing it.


55-59: Use isLightTheme for container background for consistency

Containers hardcode a dark background (#111) even when isLightTheme is true. Align with the background logic used in the category_profile layout.

Apply this diff in each container:

-  background: '#111',
+  background: isLightTheme ? '#fff' : '#111',

Also applies to: 277-281, 499-503


651-651: Category index likely off‑by‑one; prefer provided category first

Accessing categories?.[1] will often be undefined. Prefer explicit category prop, then first item.

Apply this diff:

-              {thread?.chatbot?.categories?.[1]?.category?.name || 'Technology'}
+              {category || thread?.chatbot?.categories?.[0]?.category?.name || 'Technology'}

4-18: Prop naming consistency (user_avatar vs botAvatar)

Mixed snake_case and camelCase props hinder ergonomics. Maintain backward compatibility but expose a camelCase alias internally.

Apply this minimal internal alias (no public API break):

 export default function OgImage(props: OgImageProps) {
   const {
     thread,
     question,
     answer,
     username,
     user_avatar,
     isLightTheme = false,
     ogType,
     botName,
     botAvatar,
     category,
-    domain,
     description,
     title,
   } = props
+  // TODO: prefer camelCase everywhere; keep snake_case for backward compat
+  const userAvatar = user_avatar

And use userAvatar where relevant.

Also applies to: 21-35


268-486: Optional: switch on ogType for readability and exhaustiveness

Multiple if blocks make it easier to miss the default. A switch enforces exhaustiveness in TypeScript and simplifies future additions.

If desired, refactor:

-  if (ogType === 'user_thread') {
+  switch (ogType) {
+    case 'user_thread': {
       // existing user_thread JSX...
-  }
+      break
+    }
+    case 'bot_thread': {
+      // existing bot_thread JSX...
+      break
+    }
+    case 'bot_profile': {
+      // existing bot_profile JSX...
+      break
+    }
+    case 'category_profile': {
+      // existing category_profile JSX...
+      break
+    }
+    default: {
+      // fallback JSX...
+    }
+  }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 312fd0f and 325dac3.

📒 Files selected for processing (1)
  • apps/pro-web/components/shared/og-image.tsx (1 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx,js,jsx,cjs,mjs,json,jsonc}

📄 CodeRabbit inference engine (CLAUDE.md)

Use Biome for all formatting and linting (run bun format-and-lint:fix) instead of Prettier/ESLint

Files:

  • apps/pro-web/components/shared/og-image.tsx
apps/{web,pro-web}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

apps/{web,pro-web}/**/*.{ts,tsx}: Use feature detection before invoking modern Browser APIs
Implement progressive enhancement: basic functionality first, then enhancements
Add timing delays or requestAnimationFrame when the DOM needs to settle before operations
Verify DOM operations completed successfully; don’t assume success (e.g., scroll/check actual state)
Use multiple approaches for DOM actions: try modern APIs first, then fallback to legacy methods

Files:

  • apps/pro-web/components/shared/og-image.tsx
apps/{web,pro-web}/**/*.tsx

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

apps/{web,pro-web}/**/*.tsx: Minimize useEffect dependencies to avoid infinite loops
Use refs for values that should not trigger re-renders
Consider component lifecycle timing when performing DOM operations from React components
Separate concerns: state updates vs. direct DOM manipulation in React components

Files:

  • apps/pro-web/components/shared/og-image.tsx
apps/**/components/shared/**

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Place reusable UI elements under components/shared/

Files:

  • apps/pro-web/components/shared/og-image.tsx
🧬 Code graph analysis (1)
apps/pro-web/components/shared/og-image.tsx (2)
packages/mb-genql/generated/schema.ts (1)
  • Thread (2261-2293)
apps/web/components/shared/og-bg-image.tsx (1)
  • OgBgImage (1-394)
🔇 Additional comments (1)
apps/pro-web/components/shared/og-image.tsx (1)

1-1: Import path resolves in pro-web — no action required

apps/pro-web/components/shared/og-bg-image.tsx exists and apps/pro-web/tsconfig.json maps "@/" → ["./"], so '@/components/shared/og-bg-image' correctly resolves to the local file.

Comment on lines +21 to +35
const {
thread,
question,
answer,
username,
user_avatar,
isLightTheme = false,
ogType = '',
botName,
botAvatar,
category,
domain,
description,
title,
} = 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

Don’t default ogType to '' (widens to string and weakens type‑safety)

Defaulting to an empty string de-narrows the discriminant and defeats control‑flow narrowing in the branches.

Apply this diff:

-    ogType = '',
+    ogType,
📝 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 {
thread,
question,
answer,
username,
user_avatar,
isLightTheme = false,
ogType = '',
botName,
botAvatar,
category,
domain,
description,
title,
} = props
const {
thread,
question,
answer,
username,
user_avatar,
isLightTheme = false,
ogType,
botName,
botAvatar,
category,
domain,
description,
title,
} = props
🤖 Prompt for AI Agents
In apps/pro-web/components/shared/og-image.tsx around lines 21–35, the prop
destructuring currently sets ogType = '' which widens its type to string and
breaks discriminant narrowing; remove the default assignment (leave ogType
undefined by omitting = '') so the original prop type (a narrow union or
optional) is preserved, and if needed update the component prop types to mark
ogType as optional or provide a narrow default value matching the union (e.g.,
'card' or 'tweet') rather than an empty string.

Comment on lines +37 to +43
//? Ensures relative image URLs become absolute
const makeAbsolute = (url?: string) => {
if (!url) return undefined
if (url.startsWith('http://') || url.startsWith('https://')) return url
const base = process.env.BASE_URL || 'http://localhost:3000'
return url.startsWith('/') ? `${base}${url}` : `${base}/${url}`
}
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 makeAbsolute: avoid localhost in prod; support NEXT_PUBLIC_SITE_URL/VERCEL_URL; strip trailing slashes

Current logic may emit localhost URLs in production and can generate double slashes. Prefer canonical site URL envs and sanitize.

Apply this diff:

-  const makeAbsolute = (url?: string) => {
+  const makeAbsolute = (url?: string) => {
     if (!url) return undefined
     if (url.startsWith('http://') || url.startsWith('https://')) return url
-    const base = process.env.BASE_URL || 'http://localhost:3000'
-    return url.startsWith('/') ? `${base}${url}` : `${base}/${url}`
+    const rawBase =
+      process.env.NEXT_PUBLIC_SITE_URL ||
+      (process.env.VERCEL_URL ? `https://${process.env.VERCEL_URL}` : process.env.BASE_URL) ||
+      'http://localhost:3000'
+    const base = rawBase.replace(/\/+$/, '')
+    return url.startsWith('/') ? `${base}${url}` : `${base}/${url}`
   }

Optionally accept a baseUrl prop from the OG route and prefer it over envs to guarantee correctness across deployments.

📝 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
//? Ensures relative image URLs become absolute
const makeAbsolute = (url?: string) => {
if (!url) return undefined
if (url.startsWith('http://') || url.startsWith('https://')) return url
const base = process.env.BASE_URL || 'http://localhost:3000'
return url.startsWith('/') ? `${base}${url}` : `${base}/${url}`
}
//? Ensures relative image URLs become absolute
const makeAbsolute = (url?: string) => {
if (!url) return undefined
if (url.startsWith('http://') || url.startsWith('https://')) return url
const rawBase =
process.env.NEXT_PUBLIC_SITE_URL ||
(process.env.VERCEL_URL ? `https://${process.env.VERCEL_URL}` : process.env.BASE_URL) ||
'http://localhost:3000'
const base = rawBase.replace(/\/+$/, '')
return url.startsWith('/') ? `${base}${url}` : `${base}/${url}`
}
🤖 Prompt for AI Agents
In apps/pro-web/components/shared/og-image.tsx around lines 37 to 43,
makeAbsolute currently can emit localhost in production, doesn't prefer
canonical site envs, and can produce double slashes; update it to (1) accept an
optional baseUrl parameter (prefer that if provided), (2) resolve base from
NEXT_PUBLIC_SITE_URL || VERCEL_URL || process.env.BASE_URL and avoid falling
back to 'http://localhost:3000' when NODE_ENV === 'production', (3) normalize
the chosen base by removing any trailing slash and trim leading/trailing
whitespace, and (4) join paths safely so you never produce double slashes
(ensure if the input URL starts with '/' you concatenate without adding an extra
slash, otherwise insert a single slash). Also continue to return undefined for
falsy url.

Comment on lines +352 to +367
{/* User profile (bottom left corner) */}
<div style={{ display: 'flex', alignItems: 'center' }}>
{botAvatar ? (
<img
alt="bot avatar"
style={{
width: '48px',
height: '48px',
borderRadius: '50%',
border: '2px solid #00ff66',
objectFit: 'cover',
marginRight: '16px',
}}
src={makeAbsolute(botAvatar) || ''}
/>
) : (
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

Wrong avatar source in user_thread “User profile” block

This block is labeled “User profile” but renders botAvatar and alt="bot avatar". It should render the user’s avatar.

Apply this diff:

-          {/* User profile (bottom left corner) */}
+          {/* User profile (bottom left corner) */}
           <div style={{ display: 'flex', alignItems: 'center' }}>
-            {botAvatar ? (
+            {user_avatar ? (
               <img
-                alt="bot avatar"
+                alt="user avatar"
                 style={{
                   width: '48px',
                   height: '48px',
                   borderRadius: '50%',
                   border: '2px solid #00ff66',
                   objectFit: 'cover',
                   marginRight: '16px',
                 }}
-                src={makeAbsolute(botAvatar) || ''}
+                src={makeAbsolute(user_avatar) || ''}
               />
             ) : (

Also consider updating the comment/title for the right-side block to “User Profile Block” for clarity.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In apps/pro-web/components/shared/og-image.tsx around lines 352 to 367, the
"User profile" block incorrectly uses botAvatar and alt="bot avatar"; change it
to render the user's avatar by using userAvatar in the conditional, src
(makeAbsolute(userAvatar) || ''), and alt to something like "user avatar" (or
the user's name if available), and ensure the existence check matches
userAvatar; also update the adjacent/right-side comment/title to read "User
Profile Block" for clarity.

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: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/pro-web/components/onboarding/dashboard-onboarding.tsx (1)

31-41: Avoid marking onboarding as “shown” until the tour is actually visible

If startNextStep fails (DOM not ready), setHasShownOnboarding(true) prevents retries within the same mount and the tour may never appear. Gate the flag on visibility, and only then allow completion to set localStorage.

Apply this diff:

 useEffect(() => {
-  if (
-    currentTour === 'dashboardOnboarding' &&
-    !isNextStepVisible &&
-    hasShownOnboarding
-  ) {
-    handleComplete()
-  }
+  if (currentTour === 'dashboardOnboarding') {
+    if (isNextStepVisible && !hasShownOnboarding) {
+      setHasShownOnboarding(true)
+    } else if (!isNextStepVisible && hasShownOnboarding) {
+      handleComplete()
+    }
+  }
 }, [currentTour, isNextStepVisible, hasShownOnboarding, handleComplete])
🧹 Nitpick comments (5)
apps/pro-web/components/onboarding/dashboard-onboarding.tsx (2)

42-45: Stale comment

Update reference from “/c route” to “pro route”.

-    // Check if user is on /c route and hasn't seen onboarding
+    // Trigger on pro routes only if the user hasn't seen onboarding

44-58: Feature-detect localStorage and prefer rAF before setTimeout; also clean up both

Progressive enhancement: don’t assume localStorage/rAF exist; use rAF first to let the DOM settle, with a timeout fallback. Also remove the eager setHasShownOnboarding(true) here (moved to visibility check above).

-    if (isProRoute && userId && !hasShownOnboarding) {
-      const hasSeenOnboarding = localStorage.getItem(
-        `dashboard-onboarding-${userId}`,
-      )
-      if (!hasSeenOnboarding) {
-        // Small delay to ensure the DOM elements are rendered
-        const timer = setTimeout(() => {
-          startNextStep('dashboardOnboarding')
-          setHasShownOnboarding(true)
-        }, 1000)
-
-        return () => clearTimeout(timer)
-      }
-    }
-  }, [isProRoute, userId, hasShownOnboarding, startNextStep])
+    if (isProRoute && userId && !hasShownOnboarding) {
+      const storage =
+        typeof window !== 'undefined' && 'localStorage' in window
+          ? window.localStorage
+          : null
+      const hasSeenOnboarding = storage?.getItem(
+        `dashboard-onboarding-${userId}`,
+      )
+      if (!hasSeenOnboarding) {
+        // Prefer next paint; fallback to timeout if rAF is unavailable
+        let rafId: number | undefined
+        let timeoutId: number | undefined
+        const start = () => startNextStep('dashboardOnboarding')
+
+        if (
+          typeof window !== 'undefined' &&
+          typeof window.requestAnimationFrame === 'function'
+        ) {
+          rafId = window.requestAnimationFrame(start)
+        } else {
+          timeoutId = window.setTimeout(start, 1000)
+        }
+
+        return () => {
+          if (
+            rafId !== undefined &&
+            typeof window.cancelAnimationFrame === 'function'
+          ) {
+            window.cancelAnimationFrame(rafId)
+          }
+          if (timeoutId !== undefined) {
+            window.clearTimeout(timeoutId)
+          }
+        }
+      }
+    }
+  }, [isProRoute, userId, hasShownOnboarding, startNextStep])
apps/pro-web/types/index.ts (2)

656-659: Document expected formats for clarity

Add JSDoc to specify ISO codes and comma-separated list expectations.

Apply this diff:

-export interface GoogleTranslateProps {
-	pageLanguage?: string
-	includedLanguages?: string
-}
+/**
+ * Props for the Google Translate widget.
+ * - pageLanguage: ISO 639-1 code (e.g., "en").
+ * - includedLanguages: comma-separated ISO 639-1 codes (e.g., "en,es,fr").
+ */
+export interface GoogleTranslateProps {
+	pageLanguage?: string
+	includedLanguages?: string
+}

656-659: Deduplicate GoogleTranslateProps across apps to avoid drift

Identical interface found in apps/web/types/types.ts (lines 671–674) and apps/pro-web/types/index.ts (lines 656–659). Extract to a shared types module (or export from one place and re-export/import in the other) to prevent divergence.

apps/pro-web/components/onboarding/onboarding-tour-card.tsx (1)

44-59: Update outdated comment and consider hydration‑safe dark mode.

  • The comment above still says “chat = purple, public = green”; update to “pro = purple, org/profile/bot = green”.
  • Optional: avoid next‑themes hydration flicker by using Tailwind dark: variants instead of reading theme in render.

Apply comment tweak:

-  // Route-based styling (chat = purple, public = green)
+  // Route-based styling (pro = purple, org/profile/bot = green)

Hydration‑safe background (optional):

-import { useTheme } from 'next-themes'
+// import removed if not used elsewhere

-const { theme } = useTheme()
-// Background image class matching your site's pattern
-const bgImage =
-  theme === 'dark'
-    ? 'bg-[url(/background.webp)]'
-    : 'bg-[url(/background-light.webp)]'
+// Background image via CSS dark: variant to avoid hydration mismatch
+const bgImage = 'dark:bg-[url(/background.webp)] bg-[url(/background-light.webp)]'
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 325dac3 and 24248a5.

📒 Files selected for processing (5)
  • apps/pro-web/components/onboarding/dashboard-onboarding.tsx (4 hunks)
  • apps/pro-web/components/onboarding/onboarding-tour-card.tsx (3 hunks)
  • apps/pro-web/components/routes/chat/chat-chatbot-details.tsx (2 hunks)
  • apps/pro-web/lib/url.ts (1 hunks)
  • apps/pro-web/types/index.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/pro-web/components/routes/chat/chat-chatbot-details.tsx
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx,js,jsx,cjs,mjs,json,jsonc}

📄 CodeRabbit inference engine (CLAUDE.md)

Use Biome for all formatting and linting (run bun format-and-lint:fix) instead of Prettier/ESLint

Files:

  • apps/pro-web/types/index.ts
  • apps/pro-web/components/onboarding/dashboard-onboarding.tsx
  • apps/pro-web/lib/url.ts
  • apps/pro-web/components/onboarding/onboarding-tour-card.tsx
apps/{web,pro-web}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

apps/{web,pro-web}/**/*.{ts,tsx}: Use feature detection before invoking modern Browser APIs
Implement progressive enhancement: basic functionality first, then enhancements
Add timing delays or requestAnimationFrame when the DOM needs to settle before operations
Verify DOM operations completed successfully; don’t assume success (e.g., scroll/check actual state)
Use multiple approaches for DOM actions: try modern APIs first, then fallback to legacy methods

Files:

  • apps/pro-web/types/index.ts
  • apps/pro-web/components/onboarding/dashboard-onboarding.tsx
  • apps/pro-web/lib/url.ts
  • apps/pro-web/components/onboarding/onboarding-tour-card.tsx
apps/**/types/**/*.ts

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Place application-specific TypeScript definitions under types/

Files:

  • apps/pro-web/types/index.ts
apps/{web,pro-web}/**/*.tsx

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

apps/{web,pro-web}/**/*.tsx: Minimize useEffect dependencies to avoid infinite loops
Use refs for values that should not trigger re-renders
Consider component lifecycle timing when performing DOM operations from React components
Separate concerns: state updates vs. direct DOM manipulation in React components

Files:

  • apps/pro-web/components/onboarding/dashboard-onboarding.tsx
  • apps/pro-web/components/onboarding/onboarding-tour-card.tsx
🧠 Learnings (2)
📚 Learning: 2025-03-28T21:43:52.984Z
Learnt from: AndlerRL
PR: bitcashorg/masterbots#425
File: apps/masterbots.ai/lib/utils.ts:308-308
Timestamp: 2025-03-28T21:43:52.984Z
Learning: The 'pro' route type was intentionally added to the RouteType definition before its implementation logic, as the complete implementation exists in a separate branch (feat-pro-layout). This reflects an incremental development approach where type definitions may be updated in advance of their full implementation.

Applied to files:

  • apps/pro-web/components/onboarding/dashboard-onboarding.tsx
  • apps/pro-web/components/onboarding/onboarding-tour-card.tsx
📚 Learning: 2024-11-28T18:35:38.503Z
Learnt from: sheriffjimoh
PR: bitcashorg/masterbots#313
File: apps/masterbots.ai/lib/hooks/use-sidebar.tsx:218-227
Timestamp: 2024-11-28T18:35:38.503Z
Learning: In Next.js App Router, shallow routing is not supported with `useRouter` from `next/navigation`, so `window.history.pushState` should be used for shallow navigation between routes.

Applied to files:

  • apps/pro-web/components/onboarding/dashboard-onboarding.tsx
🧬 Code graph analysis (3)
apps/pro-web/types/index.ts (1)
apps/web/types/types.ts (1)
  • GoogleTranslateProps (671-674)
apps/pro-web/components/onboarding/dashboard-onboarding.tsx (1)
apps/pro-web/lib/utils.ts (1)
  • getRouteType (310-348)
apps/pro-web/components/onboarding/onboarding-tour-card.tsx (1)
apps/pro-web/lib/utils.ts (1)
  • getRouteType (310-348)
🔇 Additional comments (4)
apps/pro-web/components/onboarding/dashboard-onboarding.tsx (2)

3-3: Route-type utility import — LGTM

This aligns the component with the new routing model.


19-21: Pro-route gating via getRouteType — LGTM

Using routeType/isProRoute keeps onboarding scoped correctly to pro routes.

apps/pro-web/components/onboarding/onboarding-tour-card.tsx (2)

10-10: Good move to centralize route logic via getRouteType.

This aligns with the ongoing route-type abstraction and keeps styling decisions consistent across components.


33-35: isProRoute derivation is correct — utils explicitly excludes some /org paths

apps/pro-web/lib/utils.ts explicitly excludes /org/career and /org/content-creation (lines ~319–322); those paths therefore resolve to 'pro' and will render the pro (purple) styling — not org (green). Confirm this behavior is intentional; if not, remove the exceptions or adjust getRouteType.

  • Optional: centralize isPro(routeType) helper to avoid scattering semantics (getRouteType is used across many pro-web components).
  • Minor: RouteType union in apps/pro-web/lib/utils.ts duplicates 'org' — tidy the type.

Comment on lines +869 to +873
const isOrgPage = segments[0] === 'org'

if (isOrgPage) {
segments.shift()
}
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

❓ Verification inconclusive

Fix org-prefix handling: recompute route type after stripping "org".

isProfileThread and isBotProfile are computed before removing the "org" segment, so /org/u/.../t and /org/b/... paths won’t match and will be misparsed by the default branch. Recompute against the post‑normalized segments (or normalize first).

Apply this diff:

 export function parsePath(pathname: string): PathParams {
-  const segments = pathname.split('/').filter(Boolean)
-  const isProfileThread = segments[0] === 'u' && segments[2] === 't'
-  const isBotProfile = segments[0] === 'b'
-  const isOrgPage = segments[0] === 'org'
-
-  if (isOrgPage) {
-    segments.shift()
-  }
+  const segments = pathname.split('/').filter(Boolean)
+  // Normalize "org" prefix
+  const seg = segments[0] === 'org' ? segments.slice(1) : segments
+  const isProfileThread = seg[0] === 'u' && seg[2] === 't'
+  const isBotProfile = seg[0] === 'b'
 
-  if (isBotProfile) {
+  if (isBotProfile) {
     return {
-      chatbot: segments[1],
-      threadSlug: segments[2],
-      threadQuestionSlug: segments[3] || '',
+      chatbot: seg[1],
+      threadSlug: seg[2],
+      threadQuestionSlug: seg[3] || '',
       category: '',
       domain: '',
     }
   }
 
-  if (isProfileThread) {
+  if (isProfileThread) {
     return {
-      category: segments[3],
-      domain: segments[4],
-      chatbot: segments[5],
-      threadSlug: segments[6],
-      threadQuestionSlug: segments[7],
+      category: seg[3],
+      domain: seg[4],
+      chatbot: seg[5],
+      threadSlug: seg[6],
+      threadQuestionSlug: seg[7],
     }
   }
 
   return {
-    category: segments[0],
-    domain: segments[1],
-    chatbot: segments[2],
-    threadSlug: segments[3],
-    threadQuestionSlug: segments[4],
+    category: seg[0],
+    domain: seg[1],
+    chatbot: seg[2],
+    threadSlug: seg[3],
+    threadQuestionSlug: seg[4],
   }
 }

Please add unit cases for:

  • /org/u/alice/t/devops/kubernetes/build-bot/abc/def
  • /org/b/build-bot/abc/def?x=1
  • /org/devops/kubernetes/build-bot/abc/def

Fix org-prefix handling: normalize segments before route-type checks

isProfileThread and isBotProfile are computed against the raw segments; strip the "org" prefix (or recompute) first so /org/... routes parse correctly.

File: apps/pro-web/lib/url.ts (around lines 869-873)

 export function parsePath(pathname: string): PathParams {
-  const segments = pathname.split('/').filter(Boolean)
-  const isProfileThread = segments[0] === 'u' && segments[2] === 't'
-  const isBotProfile = segments[0] === 'b'
-  const isOrgPage = segments[0] === 'org'
-
-  if (isOrgPage) {
-    segments.shift()
-  }
+  const segments = pathname.split('/').filter(Boolean)
+  // Normalize "org" prefix
+  const seg = segments[0] === 'org' ? segments.slice(1) : segments
+  const isProfileThread = seg[0] === 'u' && seg[2] === 't'
+  const isBotProfile = seg[0] === 'b'
 
-  if (isBotProfile) {
+  if (isBotProfile) {
     return {
-      chatbot: segments[1],
-      threadSlug: segments[2],
-      threadQuestionSlug: segments[3] || '',
+      chatbot: seg[1],
+      threadSlug: seg[2],
+      threadQuestionSlug: seg[3] || '',
       category: '',
       domain: '',
     }
   }
 
-  if (isProfileThread) {
+  if (isProfileThread) {
     return {
-      category: segments[3],
-      domain: segments[4],
-      chatbot: segments[5],
-      threadSlug: segments[6],
-      threadQuestionSlug: segments[7],
+      category: seg[3],
+      domain: seg[4],
+      chatbot: seg[5],
+      threadSlug: seg[6],
+      threadQuestionSlug: seg[7],
     }
   }
 
   return {
-    category: segments[0],
-    domain: segments[1],
-    chatbot: segments[2],
-    threadSlug: segments[3],
-    threadQuestionSlug: segments[4],
+    category: seg[0],
+    domain: seg[1],
+    chatbot: seg[2],
+    threadSlug: seg[3],
+    threadQuestionSlug: seg[4],
   }
 }

Add unit tests for:

  • /org/u/alice/t/devops/kubernetes/build-bot/abc/def
  • /org/b/build-bot/abc/def?x=1
  • /org/devops/kubernetes/build-bot/abc/def
📝 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 isOrgPage = segments[0] === 'org'
if (isOrgPage) {
segments.shift()
}
export function parsePath(pathname: string): PathParams {
const segments = pathname.split('/').filter(Boolean)
// Normalize "org" prefix
const seg = segments[0] === 'org' ? segments.slice(1) : segments
const isProfileThread = seg[0] === 'u' && seg[2] === 't'
const isBotProfile = seg[0] === 'b'
if (isBotProfile) {
return {
chatbot: seg[1],
threadSlug: seg[2],
threadQuestionSlug: seg[3] || '',
category: '',
domain: '',
}
}
if (isProfileThread) {
return {
category: seg[3],
domain: seg[4],
chatbot: seg[5],
threadSlug: seg[6],
threadQuestionSlug: seg[7],
}
}
return {
category: seg[0],
domain: seg[1],
chatbot: seg[2],
threadSlug: seg[3],
threadQuestionSlug: seg[4],
}
}
🤖 Prompt for AI Agents
In apps/pro-web/lib/url.ts around lines 869 to 873, the code strips the "org"
prefix after computing route-type booleans so isProfileThread and isBotProfile
are using raw segments; move or repeat the org-prefix normalization before any
route-type checks (or recompute those booleans after segments.shift()), ensuring
all subsequent parsing uses the normalized segments; then add unit tests
covering the three example routes
(/org/u/alice/t/devops/kubernetes/build-bot/abc/def,
/org/b/build-bot/abc/def?x=1, /org/devops/kubernetes/build-bot/abc/def) to
validate correct parsing.

@vercel vercel bot temporarily deployed to Preview – masterbots September 23, 2025 00:54 Inactive
@AndlerRL AndlerRL merged commit e607d17 into develop Sep 23, 2025
3 of 4 checks passed
@AndlerRL AndlerRL deleted the bugfix/hotfixes-v4 branch September 23, 2025 00:55
AndlerRL added a commit that referenced this pull request Sep 29, 2025
* fix: increase execution time - image generation

* chore: increase maxDuration

* fix: attachment upload management (#517)

* fix: attachment upload management

* chore: clean up

* perf(impr): log failed chunks

* feat: attachment upt cron n public browse tweaks (#520)

* feat: reresh attch cron + user attach async dep upt

* fix: load more missing props

* fix: attachment expires typeof sql value

* fix: public browse search + load more deps

* perf(impr): trnx support in attch link refresh cron

* [masterbots.ai] feat: stripe subscription plans and promocode support (#513)

* fix whirlpool animation

* feat: add validation for promo codes and subscriptions

* fix: initital coderabbitai recomendations

* chore: add improve to subscription flow

* chore: add card switcher

* chore: impr pro card

* chore: fix custom class

* chore: add free card newcolor

* [masterbots.ai] feat: thread context file (#519)

* feat(wip): thread context file

* fix: typo

* fix: attachment prop dep

* fix: user attachments upt concurrency

* feat(wip): thread context focus on open

* fix: content editable selection on first render

* fix: new line after enabling content editable

* fix: paste text in editable content + paste context

* fix: paste context + show attachment text content in public

* fix: show continue message cta in cutoff content trigger

* fix: contentEditable text encoding

* fix: hook race condition build

* [masterbots.ai] fix: bug fix and improvements (#523)

* fix whirlpool animation

* feat: add validation for promo codes and subscriptions

* fix: initital coderabbitai recomendations

* chore: add improve to subscription flow

* chore: add card switcher

* chore: impr pro card

* chore: fix custom class

* chore: add free card newcolor

* chore: add improvements and medium level bug fix

* feat: add example question modal

* chore: fix biome format

* chore: add modal and botcard redesing

* chore: fix sizes

* chore: enable example questions feat flag

* fix(impr): onboarding card responsiveness + space & bg style

---------

Co-authored-by: Roberto 'Andler' Lucas <contact@andler.dev>

* chore: enable subscription page navigation

* fix: attachment css + optimistic attch upt (#528)

* fix: attachment css + optimistic attch upt

* fix: messageAttachment ref assig loc

* chore: attch cnt read for dev

* [masterbots.ai] feat: onboarding  and ui ux tweaks (#526)

* chore: fix color and new card style

* chore: restore white line

* fix: flicker + include new images

* chore: fix regresion issues

* chore: biome corrections

* fix: layout tweaks + onboard card render

* fix: tailwind class

* fix: tailwind class

---------

Co-authored-by: Roberto 'Andler' Lucas <contact@andler.dev>

* [masterbots.ai] chore(fix): change logic flow (#531)

* perf: dynamic imports (#530)

* perf(wip): dynamic imports

* chore(fix): rm static skeleton

* perf: dynamic imports in chat routes

* fix: tailwind class

* fix: persistent route (#529)

* fix: add persistent route build

* chore: remove comment section

* feat: phase 4, topics & chatbot order fields + new & upt seeds (#522)

* feat(wip): phase 4, topics + cat & chatbot order fields

* chore: add order field in gen types

* chore(wip): upt init config seed, phase 4, category order upt + wip chatbot new descriptions

* style: format and lint

* chore: upt chatbot new descriptions + new bots and prompts

* chore: upt chatbot prompt junction seeds inserts

* chore: upt chatbot categories juntion seeds

* chore: upt chatbots & relationships

* chore: upt chatbots & relationships

* fix: order field permissions + gen

* fix: onboarding chatbot description

* [masterbots.ai] fix: thread popup for threadquestion page (#516)

* update

* fix: update

* update

* update

* update

* update

* fix: thread style

* fix: thread style

* fix: added popup to threadQuestionPage

* fix: update

* fix: sub thread slug update on url:

* fix: update thread question slug navigation

* fix: update thread question slug navigation

* fix: update thread question slug navigation

* fix: update

* fix: update thread path

* fix: update

* fix: update package data

* fix: use proper error handling component on server

* fix: update

* fix: make the selected thread in popup  open by the default

* fix: focus the last sub-thread when know sub-thread slug is on the pathname

* fix: popup flickering

* fix: handle bot page routing and  threadquestion page

* fix: disable link on the bot view page

* fix: question not displaying

* fix: question not displaying

* fix: chatbot page loading more unrelated threads

---------

Co-authored-by: Roberto 'Andler' Lucas <contact@andler.dev>

* [hasura] fix(restore): is_pro + pro-exclusive chatbot table params

* revert: "fix: persistent route (#529)" (#533)

This reverts commit 5a464ab.

* chore: upt canonical domains (#532)

* chore: upt canonical domains

* fix: getRouteType

* fix: missing canonical domains and topics

* fix: category slug

* fix: hydration warns + continue thread upt after start

* fix: upt init config chatbot inserts

* chore: upt biblebot expertise + chefbot n growbot descr puntuation

* fix: coderabbitai observation

* [materbots.ai] perf: impr onboarding cards ui (#535)

* [masterbots.ai] chore(style): logo updates (#525)

* update

* fix: update

* update

* update

* update

* update

* feat: import  logos

* feat: added logo to app header

* feat: app logo

* chore: user page text

* fix: update

* feat: added bg to auth pages

* fix: update

* fix: wait for page to mount to load custom logos

* fix: update

* fix: update

* fix: update

* fix: update theme to use resolvedTheme instead

* fix: update assets

---------

Co-authored-by: Roberto 'Andler' Lucas <contact@andler.dev>

* perf(impr): overall layout css spacing at mob + footer mob loc

* fix: thread search context (#534)

* update

* fix: update

* update

* update

* update

* update

* fix: thread fetching render on search

* fix: thread fetching render on search

* fix: rm search input from botpage welcome view

* fix: introduce global search component

* fix: clean up browe(public) search

* fix: update

* fix: search from  db on  personal pages instead of the eveilable threads

* fix: search update

* fix: clean up

* chore: upt gemini models ref, web (#538)

* [masterbots.ai] feat: dashboard modal (#536)

* chore:add dashboard modal

* chore: add dashboard to public

* chore:upt design

* chore: add new flow

* chore: build issue + new ui

* chore: fix local storage - user pref

* fix: mobile chat panel a11y

* fix: mobile chat panel a11y

---------

Co-authored-by: Roberto 'Andler' Lucas <contact@andler.dev>

* chore: gpt reasoning model upt (#543)

* fix: new gpt 5 models selection & default

* fix: profile layout render

* [masterbots.ai] feat: impr sidebar nav onboarding (#540)

* chore:add dashboard modal

* chore: add dashboard to public

* chore:upt design

* chore: add new flow

* chore: build issue + new ui

* chore: fix local storage - user pref

* feat: sidebar nav onboarding

* chore: fix build

* chore: upt local storage conditions and debug

* fix: add persistent localstorage selection

* feat: add consistent selection

* fix: onboarding display logic

---------

Co-authored-by: brandonfernandezf <brandon@bitcash.org>
Co-authored-by: bran18 <andreyfdez18@gmail.com>
Co-authored-by: Brandon Fernández <31634868+Bran18@users.noreply.github.com>

* fix: create response stream object model temperature + impr output prompt

* chore: upt default llm

* chore: upt canonical domains + christbot, biblebot and new bot updated

* chore: upt core app folder name

* [pro-web] feat: masterbots pro, working documents (#423)

* wip: pro version layout

* perf(impr): pro navigation + layout

* feat: hasura chatbot pro properties + web TS formatting

* revert: showVerificationNotice condition at sign-up form

* fix: subcategory missing fields

* fix: thread + msg init seeds

* feat(pro): implement specialized pro chat interface components

- Add Pro-specific message, panel and action components
- Create Pro extended layout with improved UI
- Update workspace hooks for Pro interface support
- Add tabs component and markdown utilities
- Update package dependencies

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* feat(workspace): integrate workspace with chat UI

- Display workspace editor above chat input when workspace is active
- Keep chat input available for workspace mode to handle queries
- Update placeholder text to better reflect dual-purpose usage
- Remove separate workspace input component to avoid redundancy

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* feat(workspace): improve workspace integration with chat UI

- Extend workspace area to fill available height
- Standardize input handling by using PromptForm for both modes
- Add conditional logic to handle different input behaviors based on mode
- Improve placeholder text for better user context

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* feat(workspace): always show UI controls with toolbar and workspace

- Display expertise, reasoning, web search and workspace toggle buttons regardless of workspace mode
- Keep workspace toggle button in the main toolbar with other feature toggles
- Keep main right side controls visible regardless of mode
- Only conditionally show chat-specific buttons when in chat mode

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: prevent maximum update depth exceeded in workspace component

- Enhanced useEffect hooks with better null checking and early returns
- Added conditionals to skip unnecessary state updates
- Fixed cascading dropdown behavior for Organization→Department→Project→Document
- Added value comparison in onChange handlers to prevent update loops
- Improved setDocumentContent to only update when values change
- Enhanced logging for better debugging

* fix: resolve maximum update depth exceeded error

- Fixed circular dependency in React hooks
- Added memoization for document options to prevent unnecessary re-renders
- Optimized useEffect hooks with proper dependency arrays
- Added proper state updates batching with requestAnimationFrame
- Improved state synchronization between components
- Added checks to prevent unnecessary state changes

* feat: improved document selector empty state

- Added '---' placeholder for document selector when no documents are available
- Enhanced document options handling in chat-panel-pro.tsx
- Added better disabled state checks for empty document lists
- Improved validation of document selection to handle edge cases

* fix: document selector displaying blank

- Fixed document selector initialization issue
- Added proper handling of null filteredDocumentList
- Added document dropdown state synchronization
- Added extra diagnostics for document selection
- Improved reliability of document type switching

* fix: document selection not working for Campaign A

- Added force re-render in document select dropdown with key
- Fixed document options memoization to create new references
- Added deep copy of document sources to prevent reference issues
- Enhanced debugging to trace document options availability
- Added special handling for Campaign A document options
- Improved option type handling and selection state updates

* fix: text document dropdown not working

- Fixed text document dropdown selection to match spreadsheet functionality
- Added deep copy with JSON stringify/parse for consistent references
- Added force re-render mechanism to ensure dropdown updates
- Enhanced document type selection to handle all document types consistently
- Improved debugging with render IDs to trace document selection issues
- Fixed reference handling for document type changes

* feat: integrate Yoopta rich text editor with context-based state management

  - Add useRichEditor state and toggleRichEditor function to workspace context
  - Update workspace-content to use context-based editor preference
  - Create placeholder implementation for Yoopta editor
  - Add documentation with installation instructions

  The integration persists user preferences for the rich editor across
  sessions and provides an intuitive, minimalist toggle UI.

* fix: prevent runtime errors in thread list and rich text editor

* Revert "fix: prevent runtime errors in thread list and rich text editor"

This reverts commit e61341d.

* Revert "feat: integrate Yoopta rich text editor with context-based state management"

This reverts commit 0a79633.

* chore: add context-attachment component

* fix: workspace prompt exe w meta prompt

* chore: sync thread & msgs seeds

* chore: sync thread & msgs seeds

* fix: render ai request to selected working document

* chore(impr): add claude code monorepo ctx

* wip: save content live at cursor pos

* wip: working document live upt

* fix: pro infinite loop in output

* fix: section doc expand

* feat(wip): working document, fix init doc obj

* fix: missing pkg dep

* fix: pro user message

* fix: pro user message

* fix: workspace initial messages

* feat: edit, rewrite, expand workspace ctas

* fix: react render hoisting

* perf: impr project doc nav + saving working doc state in server cache + thread linking

* fix: web build

* feat(wip): thread pop-up chat connect workspace

* feat(wip): chat workspace connection

* fix(wip): workspace ui + chat connect w workspace

* fix: connect workspace chat w/mb chat

* fix: breadcrumb nav consistency w/state

* fix: breadcrumb nav consistency w/state

* fix: workspace doc thread creation + save doc version

* feat(impr): thread popup layout

---------

Co-authored-by: Jun Dam <jun@bitcash.org>
Co-authored-by: Claude <noreply@anthropic.com>

* [web] feat: refactor subscription flow (#549)

* chore: refactor subscription flow

* chore: fix import

* chore: add recent payment

* chore: revert condition

---------

Co-authored-by: Roberto 'Andler' Lucas <contact@andler.dev>

* fix: new thread pop-up ui aligment

* [pro-web] fix: pro workspace ui state & navigation (#552)

* fix: payment api missing routes

* fix: breadcrumb connect with thread documents

* fix: fetch missing docuemnts

* fix: document select & sync remote w local

* fix: document select & sync remote w local

* fix: header breadcrumb navigation + prompt + document creation impr

* fix: tailwindcss conflict

* fix: ts typo

* [pro-web] refactor: ai config, default gpt-5-mini, update routing and workspace (#554)

* fix: active section focus content

* fix: connect workspace prompt to chat panel and ctas

* fix(wip): attach new content to working document in workspace mode

* fix: implement preserved content structure for streaming LLM updates

- Add refs to preserve before/after selection content during streaming updates
- Use validStart position as anchor point for stable text replacement
- Ensure content structure is preserved across multiple LLM iterations
- Reset preserved content refs at start and end of operations
- Apply same logic to both section-based and full document editing modes

* fix: generated ai content in correct working document pos

* fix(wip): working document stream updates + preview section changes

* fix(wip): working document stream updates + preview section changes

* fix: del component unused prop

* fix: followingImages prompt loc in use-mb-chat

* fix: generated-imgage loader

* fix: following img prompt data

* fix: following img prompt data

* perf(impr): following img prompt

* fix: document update padEnd string in LLM responses

* fix: llm document stream results (#557)

* fix: llm document stream results

* fix: debounce save full source

* fix(pro-web): add missing onboarding logic

* [web] feat: upt dynamic og (#545)

* chore(wip): upt dynamic og

* fix: og image render issues

* chore: refactor og version

* chore: add suggestion

* chore: fix build

* chore: remove extra fallback

* chore: fix

---------

Co-authored-by: bran18 <andreyfdez18@gmail.com>
Co-authored-by: Brandon Fernández <31634868+Bran18@users.noreply.github.com>

* [pro-web] fix: refactor doc sourcing; add category dashboard; update hooks & header (#560)

* fix(wip): reactive doc breadcrumb header nav

* fix: breadcrumb navigation with active thread n user docs

* fix: hook arg dep

* fix(wip): build user breadcrumb nav from remote db

* feat: edit section prompt cta + auto-scroll in full source + delete doc section

* perf(impr): copilot instructions

* perf(impr): workspace document prompts

* fix: createWorkspaceMetaPrompt args

* fix(feat): add cron refresh workspace docs api route + add to active workspace doc cta

* fix: init config seeds statement typos

* [web/pro-web] feat: impr user profile and preferences (#561)

* Remove .env file

* Remove .env file

* Remove .env file

* update

* fix: update

* update

* update

* fix: rm breadcrumb on profile page

* fix: arrow pointed right for closed/collapsed and arrow pointed down for open/expanded

* feat: added user profile to preference

* fix: update

* fix: server pages restrictions

* fix: verify email ui

* feat: endpoint route for verify token

* feat: verify email setup

* fix: updating session and validate username

* fix: username update

* fix: prefrences updates

* feat: added theme and font-size setting

* feat: added theme and font-size setting

* fix: merged develop

* fix: update

* fix: update

* fix: google language translation updates

* fix: app clean up

* fix: update anon permision to see Isverified column

* fix:  lint update

* fix: username update func

* fix: rm bredcrum on pref page

* fix: font icons

* fix: update

* fix: build error

* fix: error build

* fix: error build

* fix: rem master.ai folder

* fix: added isUsernameTaken verifycation to  username on auth

* fix: add ts def

* fix: update

* fix: update font size in gloabl.css

---------

Co-authored-by: Jun Dam <jun@100xinvestors.com>
Co-authored-by: Roberto Lucas <andler@bitcash.org>
Co-authored-by: Roberto 'Andler' Lucas <contact@andler.dev>

* fix: thread questions's slug (chat & bot) (#562)

* fix: chat threadquestion showing cinema

* fix: bot page thread question url

* [pro-web] fix: switch to org routing; impr workspace file mgmt + refresh signed links (#563)

* fix: get user remote documents + refresh workspace document cron

* fix(feat): add cron refresh workspace docs api route + add to active workspace doc cta

* fix: init config seeds statement typos

* fix: providers render + onboarding card & sidebar ui tweaks

* fix: refresh workspace document old versions + wip pro flag in threads

* chore: sync latest changes with pro web

* feat: add is_pro flag on mb threads

* fix(pro): import typo

* fix(pro-web): og-image version

* fix(pro-web): upt route type conditions

* fix: missing ts def

* fix(pro-web): typo

* fix: import typo

* [pro-web] fix: workspace mode ui tweak + doc tree render (#565)

* fix: workspace mode ui tweak + doc tree render

* fix: tailwindcss contradicting classname

* chore: route /c clean up in pro + lazy load & loader in pro

---------

Co-authored-by: bran18 <andreyfdez18@gmail.com>
Co-authored-by: Brandon Fernández <31634868+Bran18@users.noreply.github.com>
Co-authored-by: Jimoh sherifdeen <63134009+sheriffjimoh@users.noreply.github.com>
Co-authored-by: brandonfernandezf <brandon@bitcash.org>
Co-authored-by: Jun Dam <jun@bitcash.org>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Jun Dam <jun@100xinvestors.com>
AndlerRL added a commit that referenced this pull request Dec 10, 2025
* fix: increase execution time - image generation

* chore: increase maxDuration

* fix: attachment upload management (#517)

* fix: attachment upload management

* chore: clean up

* perf(impr): log failed chunks

* feat: attachment upt cron n public browse tweaks (#520)

* feat: reresh attch cron + user attach async dep upt

* fix: load more missing props

* fix: attachment expires typeof sql value

* fix: public browse search + load more deps

* perf(impr): trnx support in attch link refresh cron

* [masterbots.ai] feat: stripe subscription plans and promocode support (#513)

* fix whirlpool animation

* feat: add validation for promo codes and subscriptions

* fix: initital coderabbitai recomendations

* chore: add improve to subscription flow

* chore: add card switcher

* chore: impr pro card

* chore: fix custom class

* chore: add free card newcolor

* [masterbots.ai] feat: thread context file (#519)

* feat(wip): thread context file

* fix: typo

* fix: attachment prop dep

* fix: user attachments upt concurrency

* feat(wip): thread context focus on open

* fix: content editable selection on first render

* fix: new line after enabling content editable

* fix: paste text in editable content + paste context

* fix: paste context + show attachment text content in public

* fix: show continue message cta in cutoff content trigger

* fix: contentEditable text encoding

* fix: hook race condition build

* [masterbots.ai] fix: bug fix and improvements (#523)

* fix whirlpool animation

* feat: add validation for promo codes and subscriptions

* fix: initital coderabbitai recomendations

* chore: add improve to subscription flow

* chore: add card switcher

* chore: impr pro card

* chore: fix custom class

* chore: add free card newcolor

* chore: add improvements and medium level bug fix

* feat: add example question modal

* chore: fix biome format

* chore: add modal and botcard redesing

* chore: fix sizes

* chore: enable example questions feat flag

* fix(impr): onboarding card responsiveness + space & bg style

---------

Co-authored-by: Roberto 'Andler' Lucas <contact@andler.dev>

* chore: enable subscription page navigation

* fix: attachment css + optimistic attch upt (#528)

* fix: attachment css + optimistic attch upt

* fix: messageAttachment ref assig loc

* chore: attch cnt read for dev

* [masterbots.ai] feat: onboarding  and ui ux tweaks (#526)

* chore: fix color and new card style

* chore: restore white line

* fix: flicker + include new images

* chore: fix regresion issues

* chore: biome corrections

* fix: layout tweaks + onboard card render

* fix: tailwind class

* fix: tailwind class

---------

Co-authored-by: Roberto 'Andler' Lucas <contact@andler.dev>

* [masterbots.ai] chore(fix): change logic flow (#531)

* perf: dynamic imports (#530)

* perf(wip): dynamic imports

* chore(fix): rm static skeleton

* perf: dynamic imports in chat routes

* fix: tailwind class

* fix: persistent route (#529)

* fix: add persistent route build

* chore: remove comment section

* feat: phase 4, topics & chatbot order fields + new & upt seeds (#522)

* feat(wip): phase 4, topics + cat & chatbot order fields

* chore: add order field in gen types

* chore(wip): upt init config seed, phase 4, category order upt + wip chatbot new descriptions

* style: format and lint

* chore: upt chatbot new descriptions + new bots and prompts

* chore: upt chatbot prompt junction seeds inserts

* chore: upt chatbot categories juntion seeds

* chore: upt chatbots & relationships

* chore: upt chatbots & relationships

* fix: order field permissions + gen

* fix: onboarding chatbot description

* [masterbots.ai] fix: thread popup for threadquestion page (#516)

* update

* fix: update

* update

* update

* update

* update

* fix: thread style

* fix: thread style

* fix: added popup to threadQuestionPage

* fix: update

* fix: sub thread slug update on url:

* fix: update thread question slug navigation

* fix: update thread question slug navigation

* fix: update thread question slug navigation

* fix: update

* fix: update thread path

* fix: update

* fix: update package data

* fix: use proper error handling component on server

* fix: update

* fix: make the selected thread in popup  open by the default

* fix: focus the last sub-thread when know sub-thread slug is on the pathname

* fix: popup flickering

* fix: handle bot page routing and  threadquestion page

* fix: disable link on the bot view page

* fix: question not displaying

* fix: question not displaying

* fix: chatbot page loading more unrelated threads

---------

Co-authored-by: Roberto 'Andler' Lucas <contact@andler.dev>

* [hasura] fix(restore): is_pro + pro-exclusive chatbot table params

* revert: "fix: persistent route (#529)" (#533)

This reverts commit 5a464ab13a01165450fe602f3ec7964e43b14921.

* chore: upt canonical domains (#532)

* chore: upt canonical domains

* fix: getRouteType

* fix: missing canonical domains and topics

* fix: category slug

* fix: hydration warns + continue thread upt after start

* fix: upt init config chatbot inserts

* chore: upt biblebot expertise + chefbot n growbot descr puntuation

* fix: coderabbitai observation

* [materbots.ai] perf: impr onboarding cards ui (#535)

* [masterbots.ai] chore(style): logo updates (#525)

* update

* fix: update

* update

* update

* update

* update

* feat: import  logos

* feat: added logo to app header

* feat: app logo

* chore: user page text

* fix: update

* feat: added bg to auth pages

* fix: update

* fix: wait for page to mount to load custom logos

* fix: update

* fix: update

* fix: update

* fix: update theme to use resolvedTheme instead

* fix: update assets

---------

Co-authored-by: Roberto 'Andler' Lucas <contact@andler.dev>

* perf(impr): overall layout css spacing at mob + footer mob loc

* fix: thread search context (#534)

* update

* fix: update

* update

* update

* update

* update

* fix: thread fetching render on search

* fix: thread fetching render on search

* fix: rm search input from botpage welcome view

* fix: introduce global search component

* fix: clean up browe(public) search

* fix: update

* fix: search from  db on  personal pages instead of the eveilable threads

* fix: search update

* fix: clean up

* chore: upt gemini models ref, web (#538)

* [masterbots.ai] feat: dashboard modal (#536)

* chore:add dashboard modal

* chore: add dashboard to public

* chore:upt design

* chore: add new flow

* chore: build issue + new ui

* chore: fix local storage - user pref

* fix: mobile chat panel a11y

* fix: mobile chat panel a11y

---------

Co-authored-by: Roberto 'Andler' Lucas <contact@andler.dev>

* chore: gpt reasoning model upt (#543)

* fix: new gpt 5 models selection & default

* fix: profile layout render

* [masterbots.ai] feat: impr sidebar nav onboarding (#540)

* chore:add dashboard modal

* chore: add dashboard to public

* chore:upt design

* chore: add new flow

* chore: build issue + new ui

* chore: fix local storage - user pref

* feat: sidebar nav onboarding

* chore: fix build

* chore: upt local storage conditions and debug

* fix: add persistent localstorage selection

* feat: add consistent selection

* fix: onboarding display logic

---------

Co-authored-by: brandonfernandezf <brandon@bitcash.org>
Co-authored-by: bran18 <andreyfdez18@gmail.com>
Co-authored-by: Brandon Fernández <31634868+Bran18@users.noreply.github.com>

* fix: create response stream object model temperature + impr output prompt

* chore: upt default llm

* chore: upt canonical domains + christbot, biblebot and new bot updated

* chore: upt core app folder name

* [pro-web] feat: masterbots pro, working documents (#423)

* wip: pro version layout

* perf(impr): pro navigation + layout

* feat: hasura chatbot pro properties + web TS formatting

* revert: showVerificationNotice condition at sign-up form

* fix: subcategory missing fields

* fix: thread + msg init seeds

* feat(pro): implement specialized pro chat interface components

- Add Pro-specific message, panel and action components
- Create Pro extended layout with improved UI
- Update workspace hooks for Pro interface support
- Add tabs component and markdown utilities
- Update package dependencies

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* feat(workspace): integrate workspace with chat UI

- Display workspace editor above chat input when workspace is active
- Keep chat input available for workspace mode to handle queries
- Update placeholder text to better reflect dual-purpose usage
- Remove separate workspace input component to avoid redundancy

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* feat(workspace): improve workspace integration with chat UI

- Extend workspace area to fill available height
- Standardize input handling by using PromptForm for both modes
- Add conditional logic to handle different input behaviors based on mode
- Improve placeholder text for better user context

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* feat(workspace): always show UI controls with toolbar and workspace

- Display expertise, reasoning, web search and workspace toggle buttons regardless of workspace mode
- Keep workspace toggle button in the main toolbar with other feature toggles
- Keep main right side controls visible regardless of mode
- Only conditionally show chat-specific buttons when in chat mode

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: prevent maximum update depth exceeded in workspace component

- Enhanced useEffect hooks with better null checking and early returns
- Added conditionals to skip unnecessary state updates
- Fixed cascading dropdown behavior for Organization→Department→Project→Document
- Added value comparison in onChange handlers to prevent update loops
- Improved setDocumentContent to only update when values change
- Enhanced logging for better debugging

* fix: resolve maximum update depth exceeded error

- Fixed circular dependency in React hooks
- Added memoization for document options to prevent unnecessary re-renders
- Optimized useEffect hooks with proper dependency arrays
- Added proper state updates batching with requestAnimationFrame
- Improved state synchronization between components
- Added checks to prevent unnecessary state changes

* feat: improved document selector empty state

- Added '---' placeholder for document selector when no documents are available
- Enhanced document options handling in chat-panel-pro.tsx
- Added better disabled state checks for empty document lists
- Improved validation of document selection to handle edge cases

* fix: document selector displaying blank

- Fixed document selector initialization issue
- Added proper handling of null filteredDocumentList
- Added document dropdown state synchronization
- Added extra diagnostics for document selection
- Improved reliability of document type switching

* fix: document selection not working for Campaign A

- Added force re-render in document select dropdown with key
- Fixed document options memoization to create new references
- Added deep copy of document sources to prevent reference issues
- Enhanced debugging to trace document options availability
- Added special handling for Campaign A document options
- Improved option type handling and selection state updates

* fix: text document dropdown not working

- Fixed text document dropdown selection to match spreadsheet functionality
- Added deep copy with JSON stringify/parse for consistent references
- Added force re-render mechanism to ensure dropdown updates
- Enhanced document type selection to handle all document types consistently
- Improved debugging with render IDs to trace document selection issues
- Fixed reference handling for document type changes

* feat: integrate Yoopta rich text editor with context-based state management

  - Add useRichEditor state and toggleRichEditor function to workspace context
  - Update workspace-content to use context-based editor preference
  - Create placeholder implementation for Yoopta editor
  - Add documentation with installation instructions

  The integration persists user preferences for the rich editor across
  sessions and provides an intuitive, minimalist toggle UI.

* fix: prevent runtime errors in thread list and rich text editor

* Revert "fix: prevent runtime errors in thread list and rich text editor"

This reverts commit e61341d875f4f5fcbc05452bdb86de9fd20799b3.

* Revert "feat: integrate Yoopta rich text editor with context-based state management"

This reverts commit 0a79633458e6682bb1cc99ed8f0a6419b3eecba7.

* chore: add context-attachment component

* fix: workspace prompt exe w meta prompt

* chore: sync thread & msgs seeds

* chore: sync thread & msgs seeds

* fix: render ai request to selected working document

* chore(impr): add claude code monorepo ctx

* wip: save content live at cursor pos

* wip: working document live upt

* fix: pro infinite loop in output

* fix: section doc expand

* feat(wip): working document, fix init doc obj

* fix: missing pkg dep

* fix: pro user message

* fix: pro user message

* fix: workspace initial messages

* feat: edit, rewrite, expand workspace ctas

* fix: react render hoisting

* perf: impr project doc nav + saving working doc state in server cache + thread linking

* fix: web build

* feat(wip): thread pop-up chat connect workspace

* feat(wip): chat workspace connection

* fix(wip): workspace ui + chat connect w workspace

* fix: connect workspace chat w/mb chat

* fix: breadcrumb nav consistency w/state

* fix: breadcrumb nav consistency w/state

* fix: workspace doc thread creation + save doc version

* feat(impr): thread popup layout

---------

Co-authored-by: Jun Dam <jun@bitcash.org>
Co-authored-by: Claude <noreply@anthropic.com>

* [web] feat: refactor subscription flow (#549)

* chore: refactor subscription flow

* chore: fix import

* chore: add recent payment

* chore: revert condition

---------

Co-authored-by: Roberto 'Andler' Lucas <contact@andler.dev>

* fix: new thread pop-up ui aligment

* [pro-web] fix: pro workspace ui state & navigation (#552)

* fix: payment api missing routes

* fix: breadcrumb connect with thread documents

* fix: fetch missing docuemnts

* fix: document select & sync remote w local

* fix: document select & sync remote w local

* fix: header breadcrumb navigation + prompt + document creation impr

* fix: tailwindcss conflict

* fix: ts typo

* [pro-web] refactor: ai config, default gpt-5-mini, update routing and workspace (#554)

* fix: active section focus content

* fix: connect workspace prompt to chat panel and ctas

* fix(wip): attach new content to working document in workspace mode

* fix: implement preserved content structure for streaming LLM updates

- Add refs to preserve before/after selection content during streaming updates
- Use validStart position as anchor point for stable text replacement
- Ensure content structure is preserved across multiple LLM iterations
- Reset preserved content refs at start and end of operations
- Apply same logic to both section-based and full document editing modes

* fix: generated ai content in correct working document pos

* fix(wip): working document stream updates + preview section changes

* fix(wip): working document stream updates + preview section changes

* fix: del component unused prop

* fix: followingImages prompt loc in use-mb-chat

* fix: generated-imgage loader

* fix: following img prompt data

* fix: following img prompt data

* perf(impr): following img prompt

* fix: document update padEnd string in LLM responses

* fix: llm document stream results (#557)

* fix: llm document stream results

* fix: debounce save full source

* fix(pro-web): add missing onboarding logic

* [web] feat: upt dynamic og (#545)

* chore(wip): upt dynamic og

* fix: og image render issues

* chore: refactor og version

* chore: add suggestion

* chore: fix build

* chore: remove extra fallback

* chore: fix

---------

Co-authored-by: bran18 <andreyfdez18@gmail.com>
Co-authored-by: Brandon Fernández <31634868+Bran18@users.noreply.github.com>

* [pro-web] fix: refactor doc sourcing; add category dashboard; update hooks & header (#560)

* fix(wip): reactive doc breadcrumb header nav

* fix: breadcrumb navigation with active thread n user docs

* fix: hook arg dep

* fix(wip): build user breadcrumb nav from remote db

* feat: edit section prompt cta + auto-scroll in full source + delete doc section

* perf(impr): copilot instructions

* perf(impr): workspace document prompts

* fix: createWorkspaceMetaPrompt args

* fix(feat): add cron refresh workspace docs api route + add to active workspace doc cta

* fix: init config seeds statement typos

* [web/pro-web] feat: impr user profile and preferences (#561)

* Remove .env file

* Remove .env file

* Remove .env file

* update

* fix: update

* update

* update

* fix: rm breadcrumb on profile page

* fix: arrow pointed right for closed/collapsed and arrow pointed down for open/expanded

* feat: added user profile to preference

* fix: update

* fix: server pages restrictions

* fix: verify email ui

* feat: endpoint route for verify token

* feat: verify email setup

* fix: updating session and validate username

* fix: username update

* fix: prefrences updates

* feat: added theme and font-size setting

* feat: added theme and font-size setting

* fix: merged develop

* fix: update

* fix: update

* fix: google language translation updates

* fix: app clean up

* fix: update anon permision to see Isverified column

* fix:  lint update

* fix: username update func

* fix: rm bredcrum on pref page

* fix: font icons

* fix: update

* fix: build error

* fix: error build

* fix: error build

* fix: rem master.ai folder

* fix: added isUsernameTaken verifycation to  username on auth

* fix: add ts def

* fix: update

* fix: update font size in gloabl.css

---------

Co-authored-by: Jun Dam <jun@100xinvestors.com>
Co-authored-by: Roberto Lucas <andler@bitcash.org>
Co-authored-by: Roberto 'Andler' Lucas <contact@andler.dev>

* fix: thread questions's slug (chat & bot) (#562)

* fix: chat threadquestion showing cinema

* fix: bot page thread question url

* [pro-web] fix: switch to org routing; impr workspace file mgmt + refresh signed links (#563)

* fix: get user remote documents + refresh workspace document cron

* fix(feat): add cron refresh workspace docs api route + add to active workspace doc cta

* fix: init config seeds statement typos

* fix: providers render + onboarding card & sidebar ui tweaks

* fix: refresh workspace document old versions + wip pro flag in threads

* chore: sync latest changes with pro web

* feat: add is_pro flag on mb threads

* fix(pro): import typo

* fix(pro-web): og-image version

* fix(pro-web): upt route type conditions

* fix: missing ts def

* fix(pro-web): typo

* fix: import typo

* [pro-web] fix: workspace mode ui tweak + doc tree render (#565)

* fix: workspace mode ui tweak + doc tree render

* fix: tailwindcss contradicting classname

* chore: route /c clean up in pro + lazy load & loader in pro

* chore: upt prompt agent files (#566)

* [pro-web] fix: workspace chat state reset and thread creation logic (#567)

* fix: workspace chat state reset and thread creation logic

- Add error handling to reset workspaceProcessingState when errors occur
- Fix state cleanup to handle both successful and failed operations
- Remove premature thread creation from document save function
- Allow thread creation to happen naturally through use-mb-chat flow
- Ensure CTAs (edit/rewrite/expand) are re-enabled after errors or success

This fixes the issue where edit/rewrite/expand CTAs remained disabled
after errors and resolves thread creation inconsistencies in workspace mode.

Co-Authored-By: Andler Lucas <andre.rlucas@outlook.com>

* fix: handle undefined threadSlug in workspace document save

- Add early return when no thread exists to save document locally only
- Save to IndexedDB when thread hasn't been created yet
- Prevents TypeScript error from passing undefined to uploadWorkspaceDocument
- Thread will be created when first message is sent via use-mb-chat flow

Co-Authored-By: Andler Lucas <andre.rlucas@outlook.com>

* feat: add complete workspace document metadata when creating threads

- Include organization, department, URL, content, size, versions, expires, and messageIds
- Update threadSlug after thread is created to maintain consistency
- Ensures documents attached to newly created threads have full metadata
- Matches the comprehensive metadata structure used in saveDocument function

Co-Authored-By: Andler Lucas <andre.rlucas@outlook.com>

* feat: upload workspace documents to bucket when creating threads

- Upload documents to bucket using uploadWorkspaceDocument API
- Get proper URLs and metadata from bucket upload
- Fallback to original document data if upload fails
- Ensures workspace documents have same treatment as attachments
- Documents now get bucket URLs, checksums, and versioning like manual saves

Co-Authored-By: Andler Lucas <andre.rlucas@outlook.com>

* fix: resolve TypeScript type mismatch for workspace document versions

- Initialize uploadedDocuments as empty WorkspaceDocumentMetadata array
- Explicitly cast upload result to WorkspaceDocumentMetadata type
- Cast fallback documents with proper versions type
- Fixes Vercel build error with versions field type incompatibility

Co-Authored-By: Andler Lucas <andre.rlucas@outlook.com>

* refactor: use API route for document uploads and optimize upload logic

- Replace direct uploadWorkspaceDocument call with /api/documents/upload
- Only upload documents when new or content has changed
- Check existing documents in thread metadata before uploading
- Merge messageIds when document already exists
- Use fetch API for consistency with other API calls
- Remove uploadWorkspaceDocument import (unused after refactor)

Co-Authored-By: Andler Lucas <andre.rlucas@outlook.com>

* chore: remove accidentally committed bin/task binary

- Remove 18MB taskfile binary that was accidentally committed
- Add bin/task to .gitignore to prevent future commits
- Taskfile binary should be installed per-environment, not versioned

Co-Authored-By: Andler Lucas <andre.rlucas@outlook.com>

* fix: move document upload outside throttle for immediate execution

- Move workspace document upload outside throttled block
- Upload happens synchronously before thread metadata update
- Add detailed logging for upload tracking
- Capture thread slug before any async operations
- Ensures documents are uploaded and attached to thread immediately
- Fixes draft status not clearing after first message

Co-Authored-By: Andler Lucas <andre.rlucas@outlook.com>

* fix: delay workspace state cleanup to allow onFinish to complete

- Add 500ms delay before cleaning up workspace state
- Ensures onFinish in use-mb-chat has access to workspace values
- Prevents newDocuments array from being empty due to premature state reset
- Fixes document upload not triggering due to guard conditions

Co-Authored-By: Andler Lucas <andre.rlucas@outlook.com>

* docs: add next session implementation plan for workspace document upload fix

- Documents detailed analysis of the architectural issue
- Outlines correct flow: workspace hooks should handle uploads, not use-mb-chat
- Provides step-by-step implementation plan for next session
- Reverted incorrect changes to use-mb-chat.tsx
- Kept correct fixes for CTA reset and workspace state cleanup

Co-Authored-By: Andler Lucas <andre.rlucas@outlook.com>

* fix: improve workspace error handling and add document upload on thread creation

- Enhanced error handling to immediately reset state and show user feedback
- Added thread creation detection to automatically upload workspace documents
- Documents are now properly associated with threads when first message is sent
- CTAs (edit, rewrite, expand) are properly re-enabled after errors
- Follows correct architecture: workspace layer handles document upload
- Prevents Draft badge persistence by ensuring proper metadata updates

Co-Authored-By: Andler Lucas <andre.rlucas@outlook.com>

* chore: remove session planning document

Co-Authored-By: Andler Lucas <andre.rlucas@outlook.com>

* refactor: remove workspace document upload logic from use-mb-chat.tsx

- Remove workspace document upload logic from use-mb-chat.tsx
- Keep only the proper implementation in use-workspace-chat.tsx
- Follows correct architecture: workspace layer handles document lifecycle
- use-mb-chat.tsx restored to focus only on general chat functionality
- Prevents duplicate upload logic and timing issues

Co-Authored-By: Andler Lucas <andre.rlucas@outlook.com>

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Andler Lucas <andre.rlucas@outlook.com>

* [pro-web] feat: add reusable popup for create new organizations, departments, and projects (#569)

* feat(pro-web): add CreateEntityAlert component to replace window.prompt

- Create reusable CreateEntityAlert component with shadcn/ui AlertDialog
- Support organization, department, and project creation
- Add entity-specific icons (Building2, FolderTree, Briefcase)
- Include informative hints below input field
- Replace window.prompt calls in header.tsx with dialog
- Maintain existing entity creation logic and state updates
- Add input validation and keyboard accessibility (Enter/ESC)
- Follow DocumentCreateAlert styling patterns

Requested by: @derianrddev
Link to Devin run: https://app.devin.ai/sessions/f76bee4bc20040208c2b7ad39a95ba3e

Co-Authored-By: dmrodriguez2000@gmail.com <dmrodriguez2000@gmail.com>

* refactor(pro-web): use react-hook-form with Zod validation in CreateEntityAlert

- Install react-hook-form@7.63.0 and @hookform/resolvers@5.2.2
- Replace useState/useRef with useForm hook
- Add Zod schema: z.string().trim().min(1).max(64)
- Configure form with zodResolver and mode: 'onChange'
- Disable Create button when !formState.isValid || formState.isSubmitting
- Wrap form fields in <form> element with handleSubmit
- Maintain all existing UI elements and accessibility features
- Keep icon, title, hint text, and keyboard shortcuts (Enter/ESC)

Requested by: @derianrddev

Co-Authored-By: dmrodriguez2000@gmail.com <dmrodriguez2000@gmail.com>

* feat(pro-web): add error validation display in CreateEntityAlert

- Add custom error messages to Zod schema (min/max)
- Display validation errors below input in red with role='alert'
- Add aria-invalid attribute to Input when errors exist
- Add aria-describedby linking to hint and error message IDs
- Maintain Create button disabled state based on form validity
- Improve accessibility for screen readers

Requested by: @derianrddev

Co-Authored-By: dmrodriguez2000@gmail.com <dmrodriguez2000@gmail.com>

* feat(pro-web): replace CreateEntityAlert with button component and remove Enter key handling

* refactor(pro-web): add description to CreateEntityAlert and clean up code

* refactor(pro-web): improve comments for entity creation handling in Header component

* feat(pro-web): enhance DocumentCrumb and Crumb components with icon for new item action

* fix(pro-web): use requestAnimationFrame for setting focus on name input in CreateEntityAlert

* refactor(pro-web): rename DocumentCreateAlert component and file for consistency

* feat(pro-web): improve CreateEntityAlert UX and consistency

- Add loading state with spinner and 'Creating...' text for better feedback
- Rename 'open' prop to 'isOpen' for consistency across alert components
- Disable cancel button during submission to prevent conflicts
- Import Loader2 icon from lucide-react
- Add aria-hidden attribute to spinner for accessibility
- Update header.tsx to use renamed prop

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: dmrodriguez2000@gmail.com <dmrodriguez2000@gmail.com>

* [pro-web] refactor: move document type filter outside breadcrumb as separate filter (#570)

* feat(pro-web): add standalone DocumentTypeFilter component

Add dedicated document type filter component with icon-based
dropdown for filtering documents by type (all, text, image,
spreadsheet)

* refactor(pro-web): remove DocumentTypeCrumb component from breadcrumb

Remove DocumentTypeCrumb component as document type filtering
is now handled by a separate DocumentTypeFilter component

* refactor(pro-web): replace DocumentTypeCrumb with DocumentTypeFilter component

Move document type filter from breadcrumb to separate header
section for better UX and UI organization

* fix(pro-web): improve documentList merging logic across document types

Update documentList computation to properly merge text, image,
and spreadsheet documents per project

* refactor(pro-web): improve header responsive layout and accessibility

- Optimize breadcrumb spacing for medium screens (md breakpoint)

- Make DocumentTypeFilter compact showing icon-only on md/lg

- Add aria-hidden attributes for better accessibility

- Add useMemo optimization for activeTypeData

- Rename DocType to DocumentType for consistency

- Adjust header margins and gaps for responsive behavior

- Add title attribute to filter button for tooltip support

* fix(pro-web): sync documentType state with filter changes

Keep documentType state in sync with activeDocumentType when user
changes the document type filter to ensure consistent state across
the header component

* refactor(pro-web): separate document type state for filter and dialog

Rename documentType to alertDocumentType to clearly separate the
document creation dialog state from the filter state. Set alert
type from active filter when opening new document dialog

* fix(pro-web): add activeDocumentType to persistence dependencies

Ensure document type filter ('all', 'text', 'image', 'spreadsheet')

is properly persisted when changed by adding it to the useEffect

dependencies that trigger localStorage and server state sync.

* [pro-web] perf(wip): optimize workspace editor performance (#568)

* perf(pro-web): optimize workspace editor performance

- Remove expensive parseMarkdownSections() on every keystroke in Full Source tab
- Defer section parsing until view switching for better typing responsiveness
- Increase API debounce from 125ms to 2s with 5s maxWait for workspace/state
- Add dynamic debounce times based on AI generation state (1s-3s vs 400ms-1s)
- Add proper typing state tracking to prevent external updates during typing

Fixes:
- Full Source tab typing latency and missed input events
- Document Sections tab becoming unresponsive after AI actions
- Excessive api/workspace/state calls during streaming generation
- Browser crashes due to rapid updates during AI generation

Performance improvements:
- Full Source tab now has smooth, responsive typing
- Reduced API calls during AI streaming by ~15x
- Better handling of concurrent updates during generation

Co-Authored-By: Andler Lucas <andre.rlucas@outlook.com>

* fix(pro-web): prevent jarring section updates during typing and AI generation

- Skip section re-parsing during active user typing
- Skip section re-parsing during AI generation (workspaceProcessingState !== 'idle')
- Add debounced section parsing (500ms) for non-active updates
- Trigger section parse after user stops typing (1s delay)
- Trigger section parse after AI streaming completes
- Add workspaceProcessingState to parsing effect dependencies

This prevents the 'crazy' section tree updates during:
1. AI streaming - sections won't flicker while AI generates content
2. User typing - new headings won't create sections immediately

Sections will update smoothly:
- 1 second after user stops typing
- After AI generation completes
- When switching views

Co-Authored-By: Andler Lucas <andre.rlucas@outlook.com>

* fix(pro-web): resolve stale closure issue in section parsing timeouts

- Add fullMarkdownRef and activeSectionRef to avoid stale closures
- Update markUserTyping to use refs for latest values
- Update handleStreamingComplete to use refs for latest values
- Remove fullMarkdown/activeSection from callback dependencies
- Keep refs synced with state via useEffect

This fixes the regression where:
1. User edits content
2. Timeout fires with stale fullMarkdown value
3. Old content overwrites new changes
4. Second edit works because timeout now has correct value

Now timeouts always read the latest values from refs.

Co-Authored-By: Andler Lucas <andre.rlucas@outlook.com>

* fix: workspace content scrolling wrapper

* perf(wip): fix ai content generate section upt

* fix: textarea workspace typing

* fix: workspace section item cta index in ui tree

* fix(wip): workspace text gen

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Andler Lucas <andre.rlucas@outlook.com>
Co-authored-by: Roberto 'Andler' Lucas <contact@andler.dev>

* [web/pro-web] fix: enable individual chatbot deselection and improve sidebar filter checkbox behavior (#572)

* fix(sidebar): implement indeterminate checkbox states for category filtering

- Add indeterminate state for category checkboxes (partial bot selection)
- Show categories when any bot is selected, not just when fully selected
- Simplify toggleChatbotSelection by removing auto-category logic
- Add memoized allBotsSelected and someBotsSelected calculations
- Update filteredCategories to check hasSomeBotsSelected condition
- Standardize urlBuilders parameter formatting
- Apply changes to both web and pro-web apps

* feat(sidebar): add custom checkbox config with indeterminate icon support

* feat(sidebar): persist chatbot selections to localStorage

- Add chatbot storage support to useCategorySelections hook
- Sync selectedChatbots state with localStorage in both apps
- Update category dashboard to preserve existing chatbot selections
- Implement incremental chatbot selection (add/remove instead of replace)
- Add deep comparison to prevent unnecessary localStorage writes
- Update onboarding section to handle chatbot state properly
- Apply changes to both web and pro-web apps for consistency

* fix(sidebar): auto-sync category selection based on chatbot selections

- Automatically update selectedCategories when chatbots are selected/deselected
- Include category in selection if at least one of its bots is selected
- Add categories dependency to chatbot sync effect
- Ensure category state stays in sync with chatbot selections
- Apply to both web and pro-web apps

* refactor(sidebar): centralize category/chatbot selection hooks in mb-lib

Move useLocalStorage, useCategorySelections to mb-lib

Extract filtering, initialization and sync hooks

Add array utility functions for normalized comparisons

Update imports in both web and pro-web apps

* refactor(mb-lib): migrate useSyncChatbotsToStorage to lodash utilities

Replace arraysHaveSameElements() with lodash isEqual()

Replace normalizeArray() with lodash sortBy() + uniq() for normalization and deduplication

Remove obsolete array-utils.ts

* fix: rm legacy seeds from main seeds

* [pro-web] feat: add pro onboarding ui (#574)

* feat(pro-web): add Pro Onboarding components (DialogWizard + StepQ1)

• Create ProOnboarding container that mounts DialogWizard with steps array

• Implement StepQ1 with Accordion sections (Local/Service/Online/Other) and selectable pill Buttons

• Define types (Group, GroupItem) and provide static groups dataset with specialties

• Add Lucide icon map + getIcon helper for each business type

• Include sticky step header/footer and scrollable content area

• Add keyboard accessibility (Enter/Space) and aria-pressed state handling

• Open all accordions by default; single-select state with Continue and Skip actions

* refactor(pro-web): move Education Services under Service Business and keep only one Crypto card

* feat(pro-web): create Bot Preselection array (rules + priorities)

• Define base types (BotRule, BotEntry) and static botsPreselection dataset

• Per-bot rules with OR lists (category/type/subtype) and priority resolution

• Route business profiles → deterministic shortlist of suggested bots

• Declarative, maintainable config (add/edit without coupled logic)

* feat(pro-web): add Pro Onboarding context + StepQ2 (subtype picker)

• Introduce context/provider + hook (selectedType, selectedSubType; setters)

• Implement StepQ2: load subTypes from businesses by selectedType

• Render pills grid; keyboard a11y (Enter/Space) + aria-pressed

• Add pills header with icon from icon map + selected type label

• General refactor: clarified variable names across steps

• Extract businesses and icon map constants to a separate file

• Update StepQ1 to persist selectedType and log selection

* feat(pro-web): add StepQ3 component and integrate into onboarding flow

• Create StepQ3 component to collect organization name (zod + react-hook-form)

• Extend ProOnboardingData with orgName and add setOrgName setter

• Wire StepQ3 into wizard flow

* feat(pro-web): auto-advance on pill/card selection (Q1/Q2) and remove continue

* fix(pro-web): update navigation methods in StepQ1 and StepQ3 components

* feat(pro-web): add progress bar to DialogWizard for step tracking

* feat(pro-web): enhance onboarding business types with structured subtypes and update context state

- Refactored business subtypes in onboarding constants to use structured objects with id and label.
- Updated the ProOnboardingData interface to include selectedCategory.
- Added setSelectedCategory function to manage selected category state in onboarding context.

* feat(pro-web): add Avatar and HoverCard components from shadcn/ui

* feat(pro-web): add StepQ4 component and improve onboarding flow

- Introduced BotCard component for displaying chatbots with selection functionality.
- Updated ProOnboarding to include a new step (StepQ4) for selecting recommended chatbots.
- Modified StepQ1 to change default selection logic for business items.
- Enhanced StepQ3 to handle navigation more intuitively based on current step.
- Updated bots preselection logic to simplify business types related to crypto.
- Refactored businesses data structure for consistency in IDs.
- Created mapping utility to match user answers with recommended bots.
- Extended useProOnboarding hook to manage selected bots state.

* feat(pro-web): refactor onboarding components and replace OnboardingSection with ProOnboarding

* feat(pro-web): enhance StepQ4 to save selected chatbots, categories and org name to LocalStorage

* feat(pro-web): replace HoverCard with Tooltip for bot name display in BotCard component

* fix(pro-web): adjust layout and styling in StepQ2 component for improved user experience

* feat(pro-web): enhance BotCard component with improved tooltip functionality and markdown rendering

* feat(pro-web): update SignUpPage message and redirect to signup after closing StepQ4 wizard

* docs: add comprehensive hasura readme with seed debugging and onboarding (#576)

* Initial plan

* Add comprehensive Hasura README with seed debugging and explanations

Co-authored-by: AndlerRL <35474730+AndlerRL@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: AndlerRL <35474730+AndlerRL@users.noreply.github.com>

* fix: Sep/22nd release merge regressions and issues (#571)

* fix: chat threadquestion showing cinema

* fix: bot page thread question url

* fix: threads not found message

* fix(pro): threads not found message

* fix: restored hero bg banner

* fix: update

* fix: added isverified colum perm

* fix: update

* fix: theme toggle resize

* fix: added select all category

* fix: update

* fix: select all fix

* revert: hasura latest thread table permissions

---------

Co-authored-by: Roberto 'Andler' Lucas <contact@andler.dev>

* [pro-web] perf(fix): workspace + chat rendering, ui tweaks & chat and workspace refactor (#573)

* refactor(wip): use-workspace-chat streaming update

* perf: impr typing and text gen rendering

* fix: build + reduce hook deps + wip view model togg

* fix(wip): document state reset to parent active section

* fix(wip): workspace content integrity

* fix: save document content condition

* fix: replaceSectionContent h1 to h3

* perf(impr): single chat panel pro for pro + workspace chat guards

* chore: rm unused component + prompt form disable typo

* fix(wip): workspace hook re-render in chat

* perf: add startTransition to crit hooks

* fix: slow chat mode in thread + smal ui color state tweaks

* perf: impr is workspacemode space w/smooth transition

* fix: debounce workspace state update

* fix: current thread in thread popup

* chore: upt output instructions details (#548)

* [pro-web] feat: persist the organizations and selected chatbots in the backend (#579)

* fix(hasura): remove chatbot_id column from preference table and update relationships

* fix(hasura): update user permissions to enforce user_id checks in insert and select permissions

* feat(hasura): create organizations table with permissions and relationships

* feat(pro-web): implement GET and POST endpoints for organizations management

* feat(hasura): add default value for id column in organizations table

* feat(pro-web): implement PATCH endpoint for updating organizations

* feat(pro-web): enhance organization management functionality with new hooks and updates

* feat(pro-web): add functionality to persist organization and their categories/chatbots during signup

* refactor(pro-web): simplify header, create orgs with ProOnboarding, and remove CategoryDashboard

* feat(hasura): remove chatbots and categories columns from organizations table

* feat(hasura): create departments table with id and name columns

* feat(hasura): add department_id column and foreign key constraint to chatbot table

* feat(hasura): create organization_chatbot table

* feat(hasura): update metadata for organizations, chatbot, departments, and organizations_chatbot

* feat(hasura): add default departments to the seed data

* chore: update drizzle and graphql types

* refactor(hasura): rename org/dept tables, PKs, and update org_chatbot refs

* refactor(pro-web): group chatbots into default departments (general, marketing, product/service)

* fix(pro-web): add form ID to organization form and link submit button

* feat(hasura): add department field to getAllChatbots function

* refactor(hasura): remove department field from getCategories and getAllChatbots functions

* refactor(hasura): remove departmentId from getBrowseThreads function metadata

* refactor(pro-web): use 'id' instead of 'organizationId' and adjust department in getDepartment

* fix(web): replace chatbot __scalar in sitemap chatbots

* fix: organization table insert columns permissions @coderabbitai[bot]

Apply suggestion from @coderabbitai[bot]

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

---------

Co-authored-by: Roberto Lucas <andler@bitcash.org>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* [pro-web] refactor: add transactional org/chatbot insertion and improve validation (#582)

* fix(pro-web): correct SEO description handling for department selection

* refactor(pro-web): optimize organization and chatbot insertion using transactions

* fix(pro-web): validate chatbot existence before updating organization chatbots

* feat(pro-web): add keyword parameter in BrowseList

* fix(pro-web): include allDepartments in useSidebar for ChatLayoutSection

* fix(pro-web): handle null department name in search placeholder

* fix(pro-web): handle errors in getDepartments and ensure safe return of department data

* fix: 578 workspace hotfixes v5 + workspace state refactor (#580)

* feat(workspace): phase 0 diagnostics

- workspace-debug.ts: debug flags & timing utils
- use-workspace: toggleWorkspace, addDocument,
  setDocumentContent, updateWorkspaceStateData
- use-workspace-content-hook: handleSaveDocument
  with checksum logging
- markdown-utils: parseMarkdownSections,
  replaceSectionContent
- use-mb-chat: onFinish streaming
- logs gated by window.__DEBUG_WORKSPACE__
- zero perf impact when disabled

refs #578

* fix: get user workspace documents

- Refactor how to get documents.
- Created default initial states for users upon landing.
- Created server initial state for workspaces.

* fix: table markdown nav

* feat(wip): tanstack query migration

* chore(feat): tanstack query integration flag, comparison

* fix: tanstack query server first & chk

* fix: pro web build and auth redir with init workspace state

* fix: tanstack query stream

* fix: tanstack document content integrity

* fix(refactor): tanstack streaming & doc save + handleDocumentUpdate

* chore: rm tst page

* refactor(feat): organization workspace

* feat(wip): upt workspace layout

* chore(fix): align upt workspace sidebar per organizations + onboarding

* fix: workspace build

* feat(wip): upt workspace layout

* feat(wip): workspace upt layout

* fix: pro missing hook var

* fix: gpt-5-nano & mini output headings + organization list

* [pro-web] refactor: workspace document mode queries with server-first persistence (#583)

* fix: workspadce doc init state + simplify workspace hook

* fix: workspace document state track

* fix: thread routing w/dept + doc ai gen & manual content upt

* fix(wip): query mutations & invalidations

* fix: document update integrity and gen

* fix: query mutations & invalidations

* fix: query mutations & invalidations

* fix: breadcrumb navigation on file and project creation

* feat(wip): workspace ai actions

* feat(wip): workspace ai actions

* fix: pro navigation + workspace ai actions, document mode

* fix: css build

* fix: css build

* fix: css build

* [pro-web] fix: updating accurate document section (#585)

* fix: updating accurate document section

* style(docs): add code comments for document update logic reminder

* [pro-web] fix: profile page preferences & layout (#587)

* fix: profile page preferences & layout UI

* fix: typo

* fix: user pro subscription guard

* perf(impr): mb pro onboarding init trigger (#588)

* [pro-web] fix: chk disable onboard in browse list

* [pro-web] fix: upt user workspace state with anon values (#589)

* fix: upt user workspace state with anon values

* style: normalize doc key with helper fn

* [pro-web] feat: workspace media tab, add img gen w/gpt-image-1 & nano banana (#584)

* chore(pro-web): update package dependencies to include @google/genai

* feat(pro-web): integrate Google Gemini model support for image generation

* feat(pro-web): update workspace context to include active tab management

* feat(pro-web): add WorkspaceMediaProvider and context to manage media-related state in the workspace

* feat(pro-web): integrate media tab in PromptForm for dynamic placeholder and submission handling

* feat(pro-web): enhance MediaWorkspace with image generation and context integration

* refactor(pro-web): align selected model with generation logic and stabilize prompt handler

* refactor(pro-web): add error handling for missing Google API key in image generation

* refactor(pro-web): streamline WorkspaceMediaHandlers interface for improved type safety and clarity

* refactor(pro-web): improve media tab context and move library/version history to workspace tab menu

* feat(pro-web): add centered frame size selector above canvas

* refactor(pro-web): revert frame size updates, move selector to menubar and improve responsiveness

* feat(pro-web): unify media selection wizard steps for size and template

* feat(pro-web): update media sidebar buttons, add tooltips and improve template flow

* feat(pro-web): expand frame sizes options and update your descriptions

* chore(pro-web): add new template images for various social media formats

* feat(pro-web): implement media templates API and integrate into workspace

- Add new API route to fetch available media templates from the filesystem.
- Update MediaWorkspace component to load templates on mount and manage loading state.
- Refactor SelectTemplateStep to display templates with loading and empty states.
- Enhance SelectSizeStep with ScrollArea for better usability.
- Introduce helper functions for template parsing and filtering by frame and social media.
- Update context to include openTemplates action for dialog management.

* feat(pro-web): hide progress bar when single step

* feat(pro-web): add reference image upload functionality with drag-and-drop support

* refactor(pro-web): split MediaWorkspace into reusable components

- Added MediaCanvas component for displaying generated images and templates.
- Introduced ReferenceImagesPanel for managing reference images with upload functionality.
- Created MediaSidebar for navigation between templates and social media actions.
- Implemented wizards for selecting sizes and templates, including LibraryStep for image selection.
- Enhanced hooks to manage workspace media state and integrate new components.

---------

Co-authored-by: Roberto 'Andler' Lucas <contact@andler.dev>

* [pro-web] chore: upt doc ai actions prompts + restructure workspace nav state (#592)

* chore: simplify ai actions document prompts

* fix: isWorkspaceActive prompts condition

* fix: workspace structure core nav + doc aai action prompt upt

* [pro-web] fix: workspace document display, drafts & navigation (#593)

* fix: normalize ai heading content

* fix: freeze active workspace section while stream

* refactor: workspace doc tab components reloc + doc nav impr

* fix: impr workspace doc ai action prompts

* fix: document drafts

* fix: document drafts

* fix: doc thread nav + draft nav + new doc creation + save new doc to server

* fix: workspace thread nav + whitelist user impr & clean-up

* fix: whitelist user impr

* fix: profile route relocated + workspace context leak

* fix: new document from thread nav

* fix: user docs filter + change docs alert a11y impr

* fix: get server init state in workspace structure

* fix: doc debounced save rm from doc-text-edit

* chore(style): add onsucess log in upt doc content cb fn

* fix: ts build

* [pro-web] feat: enhance media workspace with multi-ref img, template type filtering, and size reframing (#594)

* feat(pro-web): integrate TanStack Query for media templates fetching in MediaWorkspace

* refactor(pro-web): rename openFrameSizes to openSizeSelector for consistency in media actions

* refactor(pro-web): centralize media workspace types in dedicated file

* refactor(pro-web): optimize reference image management with useCallback

* refactor(pro-web): simplify parsing logic in parseTemplateFilename function

* refactor(pro-web): replace object URLs with base64 for image previews

* refactor(pro-web): update template type import path for consistency

* feat(pro-web): add multi-reference image support and eliminate prop drilling

- Add support for multiple reference images (up to 4) in Gemini image generation
- Centralize all state management in useWorkspaceMedia provider
- Remove prop drilling across all media tab components
- Add parseBase64Image helper for handling image data URLs
- Update API route to process reference images in Gemini content parts
- Refactor MediaWorkspace, MediaCanvas, MediaSidebar to use hook directly
- Move referenceImages and currentVersion state to provider
- Implement addReferenceImage, addMultipleReferenceImages, removeReferenceImage actions
- Update GenerateImageRequest type with optional referenceImages field
- Pass reference images through full generation pipeline

* feat(pro-web): add dynamic filter dropdown with adaptive layout

- Add Filter by Type dropdown with all social media and aspect ratio options
- Implement smart badge visibility based on active filter
- Add conditional layout: grid for square filter, masonry for others
- Capitalize social media names in badges
- Adjust column count based on filtered template count

* refactor(pro-web): enhance canvas empty state, simplify template step layout, persist filter type

* feat(pro-web): implement aspect ratio control and size change workflow

- Add aspectRatio parameter to Gemini image generation API
- Remove standalone size selector dialog in favor of menu-based workflow
- Add generateImageForSize action for dynamic aspect ratio changes
- Implement comprehensive reframing prompt for size transformations
- Add mediaSizeOptions with social media format presets
- Update canvas aspect ratios to match social media platforms
- Refactor media wizards to auto-set size from template selection
- Remove FrameSize description field from types

* chore: add gemini 3 pro image model to default img gen

* feat(pro-web): add aspect ratio validation and 2K resolution for Gemini API

- Define AspectRatio type with valid Gemini API values (1:1, 2:3, 3:2, etc.)
- Add runtime validation in generate-images route with clear error messages
- Update FrameSize interface to use strongly-typed AspectRatio
- Update GenerateImageRequest and GenerateImageOptions interfaces
- Configure Gemini imageSize to 2K for high-quality output
- Add type safety across media workspace components
- Fix error handling in imageUrlToBase64 helper
- Remove debug console.log statements

* fix(pro-web): improve frame size matching with semantic purpose

---------

Co-authored-by: Roberto 'Andler' Lucas <contact@andler.dev>

* chore: upt react ver

* fix: bun lock conflicts

* [pro-web] fix: workspace structure and nav state (#596)

* fix: dept from path guard

* fix: workspace structure chk missing project in dept

* fix: department n threads navigation

* fix: workspace active navigation persistent state

* style: header component clean up

* fix: css build

* fix: new proj among dept + thread refresh & filter

* chore: upt nextjs ver

* chore: upt react ver

* chore: rm legacy component

* chore: rm legacy component

* chore: rm legacy files

* [pro-web] feat: enhance media tab with version history UI & reference image storage (#595)

* feat(pro-web): simplify image reframing prompt

* feat(pro-web): add Gemini 3 Pro model to available image models

* feat(pro-web): enhance download/share functionality with loading state and mobile sharing support

* feat(pro-web): implement checksum-based reference image uploads with GCS storage

- Add server actions for reference image upload with checksum deduplication
- Replace client-side base64 uploads with server-side GCS bucket storage
- Upload images individually to avoid body size limits (5MB per request)
- Update workspace media context to use ReferenceImagePayload with checksums
- Modify image generation API to resolve checksums to base64 on server
- Add reference images debug page with dark/light mode support
- Configure Next.js serverActions bodySizeLimit to 5mb
- Maintain backward compatibility with legacy base64 approach

Storage: reference-images/${checksum} with 7-day signed URLs
Resolves payload size issues and enables efficient image reuse

* feat(pro-web): redesign version history dialog UI

- Create VersionHistoryDialog with current/previous version display
- Add carousel navigation for version browsing
- Implement restore and duplicate version actions
- Extract version history logic from media-canvas component
- Improve responsive design and user interactions

* feat(pro-web): implement smooth card stack animation for version history

- Add framer-motion animations with global progress tracking
- Implement horizontal slide with smooth sin curve for card transitions
- Add vertical arc movement as cards emerge from stack
- Apply scale interpolation with slight overshoot effect
- Add rotation straightening during transitions with directional snap
- Calculate shortest cycling direction for better UX
- Implement dynamic z-index management during animations
- Add stable random rotations for stacked cards
- Improve card depth calculation and visibility logic
- Enhance visual feedback with indigo ring for current card

* refactor(pro-web): extract version card logic to separate component

* refactor(media-tab): redesign version history UI from stacked cards to carousel layout

- Delete version-card.tsx component with complex motion animations
- Replace stack navigation (prev/next cycling) with carousel thumbnails
- Add side-by-side preview comparison (current vs selected)
- Implement direct thumbnail selection instead of animated card flipping
- Include carousel pagination with dots and arrow controls
- Enhance dialog structure with bordered sections and action buttons

* feat(media-tab): implement version history carousel with flying image transitions

* chore: update next.js version from 15.2.4 to 15.5.7 in package.json

* refactor(media-tab): update image display styles in version history dialog

* refactor(media-tab): improve styling for version history dialog header and images

* refactor(media-tab): adjust image display properties in FlyingImage component

* fix(media-tab): improve reference image upload reliability

Replace custom checksum with native crypto.createHash

Add upload state guard to prevent concurrent uploads

Improve error handling with early return and state reset

Adjust body size limit comment for clarity

Add TODO for library image checksum handling

* fix(reference-images): validate checksum format in fetchReferenceImageByChecksum function

---------

Co-authored-by: bran18 <andreyfdez18@gmail.com>
Co-authored-by: Brandon Fernández <31634868+Bran18@users.noreply.github.com>
Co-authored-by: Jimoh sherifdeen <63134009+sheriffjimoh@users.noreply.github.com>
Co-authored-by: brandonfernandezf <brandon@bitcash.org>
Co-authored-by: Jun Dam <jun@bitcash.org>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Jun Dam <jun@100xinvestors.com>
Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: dmrodriguez2000@gmail.com <dmrodriguez2000@gmail.com>
Co-authored-by: Derian <59376626+derianrddev@users.noreply.github.com>
Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
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.

1 participant