-
Notifications
You must be signed in to change notification settings - Fork 1
Bugfix/workspace navigation v2 | Refactor metadata flow and simplify workspace logic #598
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Reviewer's GuideRefines workspace-aware thread fetching and navigation by filtering threads by active workspace metadata, making organization/project targeting more flexible and consistent, tightening workspace structure initialization, and adjusting onboarding and workspace selection behaviors. Sequence diagram for workspace-aware thread loading in UserThreadPanelsequenceDiagram
actor User
participant UserThreadPanel
participant WorkspaceContext
participant HasuraService
User->>UserThreadPanel: Scroll or search to load threads
UserThreadPanel->>WorkspaceContext: get activeOrganization, activeWorkspaceDepartment, activeProject
WorkspaceContext-->>UserThreadPanel: activeOrganization, activeWorkspaceDepartment, activeProject
UserThreadPanel->>UserThreadPanel: build organizationData
UserThreadPanel->>HasuraService: getThreads(jwt, userId, limit, departmentId, chatbotName, keyword, organizationData)
HasuraService->>HasuraService: build metadata filter using defined organizationData fields
HasuraService-->>UserThreadPanel: threads, count
UserThreadPanel->>UserThreadPanel: update state.threads, state.count
UserThreadPanel->>UserThreadPanel: derive threads via useMemo and filter by active workspace metadata
UserThreadPanel-->>User: render filtered threads for active workspace
Class diagram for workspace-aware thread fetching and context typesclassDiagram
class ThreadOrganizationMetadata {
+string organization
+string department
+string project
}
class GetThreadsParams {
+string jwt
+string userId
+number limit
+string departmentId
+string chatbotName
+string keyword
+ThreadOrganizationMetadata organizationData
}
class HasuraService {
+getThreads(jwt, userId, limit, departmentId, chatbotName, keyword, organizationData)
}
class WorkspaceContextValue {
+string activeOrganization
+tuple activeDepartment
+string activeProject
+map projectsByDept
+array organizationList
+setActiveOrganization(organization)
+setActiveDepartment(departmentId)
+setActiveProject(project)
+setActiveDocument(documentId)
+setShouldRefreshThreads(shouldRefresh)
}
class UserThreadPanelComponent {
+number initialCount
+array initialThreads
+state threads
+state count
+loadMore()
}
HasuraService ..> ThreadOrganizationMetadata : uses
HasuraService ..> GetThreadsParams : uses
UserThreadPanelComponent ..> HasuraService : calls getThreads
UserThreadPanelComponent ..> WorkspaceContextValue : reads workspace state
WorkspaceContextValue ..> ThreadOrganizationMetadata : provides filtering metadata
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
WalkthroughActive department no longer falls back to "General" (can be null); user preferences (latest organization/project) are fetched and injected into thread queries; onboarding gating simplified; DEPARTMENT slug map and workspace hooks updated; Hasura preference schema/metadata/migrations changed; thread activation now sets activeDocument and a new helper opens threads from routes. Changes
Sequence Diagram(s)sequenceDiagram
participant Browser
participant SSR as Pro-web (server/SSR)
participant Hasura
participant DB
Browser->>SSR: Request page (may include route with threadSlug)
SSR->>Hasura: getUserPreferences(userId, jwt)
Hasura-->>SSR: userPreferences (latestOrganization/latestProject)
SSR->>Hasura: getThreads(..., ... , ...organizationData?)
Hasura-->>SSR: threads
SSR-->>Browser: Rendered page with threads
Browser->>SSR: Navigate to route with threadSlug
SSR->>Hasura: getThread(threadSlug)
alt thread found
Hasura-->>SSR: thread
SSR-->>Browser: activatePopup(thread) → setActiveDocument(...)
else not found
Hasura-->>SSR: null
SSR-->>Browser: show error (sonner)
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes
Possibly related PRs
Suggested labels
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this 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:
- In
getThreadsyou’ve changedorganizationDatato be optional/partial but unconditionally accessorganizationData.organization/department/projectwhen building the_containsfilter; add a null/undefined guard so callers that don’t passorganizationDatadon’t trigger a runtime error. - The
threadsmemoized filter inuser-thread-panel.tsxassumesthread.metadata.organizationDataalways exists and is aThreadOrganizationMetadata; consider defensive checks (e.g. early returntrue/falsewhenorganizationDatais missing or malformed) to avoid runtime exceptions and to make the intended behavior for legacy or untagged threads explicit. - There is a leftover
console.log('newThreads + getThreadsParams', ...)inuser-thread-panel.tsx; it would be better to remove or gate this behind a debug flag to avoid noisy logs in production.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- In `getThreads` you’ve changed `organizationData` to be optional/partial but unconditionally access `organizationData.organization/department/project` when building the `_contains` filter; add a null/undefined guard so callers that don’t pass `organizationData` don’t trigger a runtime error.
- The `threads` memoized filter in `user-thread-panel.tsx` assumes `thread.metadata.organizationData` always exists and is a `ThreadOrganizationMetadata`; consider defensive checks (e.g. early return `true`/`false` when `organizationData` is missing or malformed) to avoid runtime exceptions and to make the intended behavior for legacy or untagged threads explicit.
- There is a leftover `console.log('newThreads + getThreadsParams', ...)` in `user-thread-panel.tsx`; it would be better to remove or gate this behind a debug flag to avoid noisy logs in production.
## Individual Comments
### Comment 1
<location> `apps/pro-web/components/routes/thread/user-thread-panel.tsx:186-195` </location>
<code_context>
+ const threads =
</code_context>
<issue_to_address>
**issue (bug_risk):** Filtering threads by organization/department/project will hide all threads when those values are unset.
The `useMemo` filter runs unconditionally on `state.threads`/`initialThreads` with `organizationData`, so when `activeOrganization` (or department/project) is unset, `threadOrgData.organization === activeOrganization` will fail for all threads and the list becomes empty, even though the backend did not filter by org.
Consider only applying this filter when there is an active workspace context (similar to the previous `hasWorkspaceContext` flag), or at least skipping the filter when `activeOrganization` is null/undefined or no scoping is intended. Otherwise users will see no threads before choosing an org/department/project.
</issue_to_address>
### Comment 2
<location> `apps/pro-web/services/hasura/hasura.service.ts:361` </location>
<code_context>
keyword,
organizationData,
}: GetThreadsParams & {
- organizationData?: ThreadOrganizationMetadata
+ organizationData?: Partial<ThreadOrganizationMetadata>
}) {
const client = getHasuraClient({ jwt })
</code_context>
<issue_to_address>
**issue (bug_risk):** Using Partial<ThreadOrganizationMetadata> in getThreads requires guarding against organizationData being undefined.
`organizationData` is now optional and `Partial`, but the query assumes it’s always defined:
```ts
organizationData: {
...(organizationData.organization ? { organization: organizationData.organization } : {}),
...(organizationData.department ? { department: organizationData.department } : {}),
...(organizationData.project ? { project: organizationData.project } : {}),
},
```
If callers omit `organizationData`, this will throw. Please add a guard (e.g. default `organizationData = {}` or only build the `organizationData` filter when it’s truthy) and include that filter conditionally in the `where` clause.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
apps/pro-web/services/hasura/hasura.service.ts (1)
413-434: Potential issue:whereclause may be overwritten instead of merged.The
baseThreadsArgumentsobject has two separate...(condition ? { where: {...} } : {})spread operations (lines 366-412 and lines 413-434). WhenorganizationDatais provided along with other filters likeuserIdorchatbotName, the second spread will overwrite the firstwhereclause rather than merging them.This could cause threads to be filtered only by
organizationDatametadata while ignoringuserId,chatbotName,departmentId, etc.Consider deep-merging the where clauses or restructuring the filter construction:
const baseThreadsArguments = { - ...(chatbotName || departmentId || keyword || userId || domain - ? { - where: { - // ... first where conditions - }, - } - : ...), - ...(organizationData - ? { - where: { - metadata: { - _contains: { ... }, - }, - }, - } - : {}), + where: { + ...(chatbotName ? { chatbot: { name: { _eq: chatbotName } } } : {}), + ...(departmentId ? { chatbot: { department: { departmentId: { _eq: departmentId } } } } : {}), + ...(userId ? { userId: { _eq: userId } } : {}), + isPro: { _eq: true }, + ...(organizationData ? { + metadata: { + _contains: { + organizationData: { + ...(organizationData.organization ? { organization: organizationData.organization } : {}), + ...(organizationData.department ? { department: organizationData.department } : {}), + ...(organizationData.project ? { project: organizationData.project } : {}), + }, + }, + }, + } : {}), + }, }apps/pro-web/lib/hooks/use-mb-chat.tsx (1)
1044-1051:ThreadOrganizationMetadatainterface declaresdepartmentas a required string, but the code now allowsundefined.Removing the
'General'fallback at line 1046 meansactiveDepartment?.[0]may beundefined. However, theThreadOrganizationMetadatainterface (line 52-56 ofthread.types.ts) definesdepartmentas a non-optionalstring. This creates a type mismatch: metadata with undefined department violates the interface contract.Update the interface to reflect that department can be optional:
export interface ThreadOrganizationMetadata { organization: string department?: string // now optional project: string }Also verify that code accessing
threadOrgData.department(e.g., atuser-thread-panel.tsx:199) handles the undefined case correctly.
🧹 Nitpick comments (1)
apps/pro-web/components/routes/thread/user-thread-panel.tsx (1)
409-412: Remove debugconsole.logbefore merging.Debug logging should be removed or converted to conditional dev-mode logging.
- console.log('newThreads + getThreadsParams', { - newThreads, - getThreadsParams, - })
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (9)
apps/pro-web/app/actions/thread.actions.ts(1 hunks)apps/pro-web/components/routes/pro/pro.tsx(1 hunks)apps/pro-web/components/routes/thread/user-thread-panel.tsx(6 hunks)apps/pro-web/components/routes/workspace/workspace-breadcrumb.tsx(3 hunks)apps/pro-web/lib/hooks/use-mb-chat.tsx(1 hunks)apps/pro-web/lib/hooks/use-workspace.tsx(2 hunks)apps/pro-web/lib/queries/use-workspace-mutations.ts(1 hunks)apps/pro-web/lib/queries/use-workspace-structure.ts(2 hunks)apps/pro-web/services/hasura/hasura.service.ts(2 hunks)
🧰 Additional context used
📓 Path-based instructions (12)
apps/{web,pro-web}/services/**
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Place external API integrations under services/
Files:
apps/pro-web/services/hasura/hasura.service.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
**/*.{ts,tsx}: Avoid default exports; prefer named exports
Use meaningful variable names; prefix booleans with is/has/does/should
Use interface for objects/classes; type for unions/tuples/aliases; enum for fixed sets; const for literals
Avoid any; prefer specific types
Leverage type inference where clear; annotate function params and return types
Prefer type assertions with 'as' over angle-bracket syntax
Use type guards to narrow types in conditionals
**/*.{ts,tsx}: Avoid default exports; prefer named exports
Use modern JavaScript features with TypeScript across the codebase
Use meaningful names; prefix booleans with is/has/does/should (e.g., isDisabled)
Receive an object and return an object (RORO) for functions interfacing with external services
Type definitions: use interface for objects/classes; type for unions/tuples/aliases; const for literals; enum for fixed sets
Avoid any; prefer specific types
Leverage type inference; omit explicit annotations when obvious
Add explicit type annotations for function parameters and return values
Prefer type assertions with as over angle-bracket syntax
Use type guards to narrow types within conditionals
Favor composition over inheritance in code structure
**/*.{ts,tsx}: Avoid default exports; prefer named exports
Prefer RORO (Receive an object, return an object) for function signatures, especially for external service interactions
Use interface for object shapes/classes; type for unions/tuples/aliases; const for literals; enum for fixed enumerations
Avoid using any; prefer precise types
Leverage type inference when clear; omit redundant annotations
Explicitly annotate function parameters and return types
Prefer type assertions with as over angle-bracket syntax
Use type guards to narrow types safely
Use meaningful variable names; prefix booleans with is/has/does/should
Favor composition over inheritance
Use lowercase dash-separated names for files and folders; use extensions like .config.ts, .test.ts, .context.tsx, .typ...
Files:
apps/pro-web/services/hasura/hasura.service.tsapps/pro-web/lib/queries/use-workspace-structure.tsapps/pro-web/lib/queries/use-workspace-mutations.tsapps/pro-web/app/actions/thread.actions.tsapps/pro-web/lib/hooks/use-mb-chat.tsxapps/pro-web/components/routes/workspace/workspace-breadcrumb.tsxapps/pro-web/components/routes/pro/pro.tsxapps/pro-web/lib/hooks/use-workspace.tsxapps/pro-web/components/routes/thread/user-thread-panel.tsx
apps/{web,pro-web}/services/**/*.ts
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Use RORO (Receive an object, Return an object) for functions, especially when calling external services
Place external API integrations under services/
Files:
apps/pro-web/services/hasura/hasura.service.ts
apps/{web,pro-web}/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
apps/{web,pro-web}/**/*.{ts,tsx}: Use lowercase kebab-case for directories and filenames (components can omit extension)
Verify DOM operations and consider timing (RAF/delays) when manipulating the DOM in React
Use Tailwind CSS utility classes for styling React components
apps/{web,pro-web}/**/*.{ts,tsx}: Use TypeScript to define React props types (interfaces for props)
Use custom hooks (useMBChat, useThread, useSidebar, useModel) for domain-specific state and keep providers focused and composed hierarchically
Separate presentation from business logic and compose small components
Use hasura.service.ts for all GraphQL operations from UI layers
apps/{web,pro-web}/**/*.{ts,tsx}: Prefer Server Components; use the use client directive only when client-side features are required
Implement Nested Layouts using the App Router
Use Streaming and Suspense in Next.js 14/15 where appropriate
Follow Next.js file conventions as per documentation
Use IndexedDB for local caching to enable immediate UI updates
Files:
apps/pro-web/services/hasura/hasura.service.tsapps/pro-web/lib/queries/use-workspace-structure.tsapps/pro-web/lib/queries/use-workspace-mutations.tsapps/pro-web/app/actions/thread.actions.tsapps/pro-web/lib/hooks/use-mb-chat.tsxapps/pro-web/components/routes/workspace/workspace-breadcrumb.tsxapps/pro-web/components/routes/pro/pro.tsxapps/pro-web/lib/hooks/use-workspace.tsxapps/pro-web/components/routes/thread/user-thread-panel.tsx
**/*
📄 CodeRabbit inference engine (CLAUDE.md)
**/*: Use lowercase with dash-separated directory and file names; use specific extensions: .config.ts, .test.ts, .context.tsx, .type.ts, .hook.ts; components may omit extension
Use Bun for package management and scripts instead of npm/yarn
Use Biome for formatting and linting via bun format-and-lint:fix; avoid Prettier/ESLint configs
Files:
apps/pro-web/services/hasura/hasura.service.tsapps/pro-web/lib/queries/use-workspace-structure.tsapps/pro-web/lib/queries/use-workspace-mutations.tsapps/pro-web/app/actions/thread.actions.tsapps/pro-web/lib/hooks/use-mb-chat.tsxapps/pro-web/components/routes/workspace/workspace-breadcrumb.tsxapps/pro-web/components/routes/pro/pro.tsxapps/pro-web/lib/hooks/use-workspace.tsxapps/pro-web/components/routes/thread/user-thread-panel.tsx
apps/{web,pro-web}/app/**
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
apps/{web,pro-web}/app/**: Prefer Next.js Server Components; use 'use client' only when necessary
Leverage Streaming and Suspense for responsiveness in Next.js 15
Follow Next.js file conventions in the App Router
apps/{web,pro-web}/app/**: Leverage Server Components by default; use the use client directive only when client-side features are needed
Implement Nested Layouts using the App Router
Use Streaming and Suspense features in Next.js 14/15 where appropriate
Follow Next.js file conventions for routing and layout in the App Router
Optimize data fetching: fetch on the server, fetch where needed, and use preload patterns to avoid waterfalls
Files:
apps/pro-web/app/actions/thread.actions.ts
apps/{web,pro-web}/lib/hooks/**
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
apps/{web,pro-web}/lib/hooks/**: Place custom React hooks under lib/hooks/
Encapsulate complex state in custom hooks and separate presentation from business logicStore custom React hooks under lib/hooks/
Files:
apps/pro-web/lib/hooks/use-mb-chat.tsxapps/pro-web/lib/hooks/use-workspace.tsx
apps/{web,pro-web}/**/*.tsx
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
apps/{web,pro-web}/**/*.tsx: Declare React components with the function keyword
Name React components using PascalCase
Order component files: imports → constants → component → styled components → types/interfaces
Use functional components and hooks; type props with a TypeScript interface
Use regular function references (not inline arrows) for React event handlers to avoid extra re-renders
apps/{web,pro-web}/**/*.tsx: Use regular function references for event handlers instead of inline arrow functions in JSX
Declare React components with the function keyword (function ComponentName() {})
Use PascalCase for React component names
Order React component files: imports, constants, component, styled components, types/interfaces
Style components with Tailwind CSS utility classes
Files:
apps/pro-web/lib/hooks/use-mb-chat.tsxapps/pro-web/components/routes/workspace/workspace-breadcrumb.tsxapps/pro-web/components/routes/pro/pro.tsxapps/pro-web/lib/hooks/use-workspace.tsxapps/pro-web/components/routes/thread/user-thread-panel.tsx
**/*.tsx
📄 CodeRabbit inference engine (AGENTS.md)
**/*.tsx: Avoid inline arrow functions in JSX props; use stable function references for event handlers
Declare React components with the function keyword (functional components)
Use PascalCase for React component names
Use functional components and hooks instead of class components
Type component props with TypeScript interfaces
Files:
apps/pro-web/lib/hooks/use-mb-chat.tsxapps/pro-web/components/routes/workspace/workspace-breadcrumb.tsxapps/pro-web/components/routes/pro/pro.tsxapps/pro-web/lib/hooks/use-workspace.tsxapps/pro-web/components/routes/thread/user-thread-panel.tsx
apps/{web,pro-web}/lib/hooks/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Place custom React hooks in lib/hooks/
Files:
apps/pro-web/lib/hooks/use-mb-chat.tsxapps/pro-web/lib/hooks/use-workspace.tsx
apps/{web,pro-web}/components/routes/**
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Place page-specific components under components/routes/[feature]/
Organize page-specific components under components/routes/
Files:
apps/pro-web/components/routes/workspace/workspace-breadcrumb.tsxapps/pro-web/components/routes/pro/pro.tsxapps/pro-web/components/routes/thread/user-thread-panel.tsx
apps/{web,pro-web}/components/**/*.tsx
📄 CodeRabbit inference engine (AGENTS.md)
Order component files: imports → constants → component → styled components → types/interfaces
Files:
apps/pro-web/components/routes/workspace/workspace-breadcrumb.tsxapps/pro-web/components/routes/pro/pro.tsxapps/pro-web/components/routes/thread/user-thread-panel.tsx
🧠 Learnings (27)
📓 Common learnings
Learnt from: AndlerRL
Repo: bitcashorg/masterbots PR: 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.
Learnt from: AndlerRL
Repo: bitcashorg/masterbots PR: 0
File: :0-0
Timestamp: 2025-11-07T18:41:56.562Z
Learning: Successfully created comprehensive GitHub issue #581 consolidating 16 unresolved observations from PR #579 review, including critical TDZ crash, migration bugs, transaction safety issues, and validation improvements, with specific file paths, line numbers, and action items organized by priority (4 critical, 8 major, 2 minor, 2 acknowledged for future PRs).
Learnt from: AndlerRL
Repo: bitcashorg/masterbots PR: 388
File: apps/masterbots.ai/lib/clickable-results.ts:148-149
Timestamp: 2025-02-24T20:46:17.229Z
Learning: Issue #389 is being used to track items for a code audit in the masterbots project.
Learnt from: AndlerRL
Repo: bitcashorg/masterbots PR: 0
File: :0-0
Timestamp: 2025-11-21T19:47:50.195Z
Learning: For repo bitcashorg/masterbots (apps/pro-web), AndlerRL prefers integrating TanStack Query into React Context Hooks wherever they fit to manage shared server state and side effects, with a longer-term plan to align broader state management with TanStack Query and potentially replace React Context hooks. Captured from PR #584 discussion on Nov 21, 2025.
Learnt from: AndlerRL
Repo: bitcashorg/masterbots PR: 542
File: apps/web/components/layout/profile/layout.tsx:67-68
Timestamp: 2025-08-29T20:34:24.433Z
Learning: AndlerRL prefers skeleton loading states over conditional rendering for better UX when components are waiting for data to load. Components should show loading indicators rather than blank spaces.
📚 Learning: 2025-09-30T22:11:18.270Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-30T22:11:18.270Z
Learning: Applies to **/services/hasura.service.ts : Use hasura.service.ts as the single interaction point for GraphQL operations with Hasura
Applied to files:
apps/pro-web/services/hasura/hasura.service.ts
📚 Learning: 2025-09-30T22:08:30.965Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-30T22:08:30.965Z
Learning: Applies to **/services/hasura.service.ts : Use hasura.service.ts as the single point for all GraphQL interactions
Applied to files:
apps/pro-web/services/hasura/hasura.service.ts
📚 Learning: 2025-09-30T22:09:37.969Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-30T22:09:37.969Z
Learning: Applies to apps/{web,pro-web}/**/*.{ts,tsx} : Use hasura.service.ts for all GraphQL operations from UI layers
Applied to files:
apps/pro-web/services/hasura/hasura.service.ts
📚 Learning: 2025-09-30T22:09:37.969Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-30T22:09:37.969Z
Learning: Applies to apps/**/services/hasura.service.ts : Use hasura.service.ts as the single point of GraphQL interaction in each app
Applied to files:
apps/pro-web/services/hasura/hasura.service.ts
📚 Learning: 2025-11-07T18:38:42.273Z
Learnt from: AndlerRL
Repo: bitcashorg/masterbots PR: 579
File: apps/pro-web/app/api/auth/signup/route.ts:151-157
Timestamp: 2025-11-07T18:38:42.273Z
Learning: In apps/pro-web/app/actions/admin.actions.ts, the insertUserOrganizations function should use Drizzle DB transactions to ensure atomicity when inserting organizations and their associated chatbots, preventing partial persistence issues.
Applied to files:
apps/pro-web/lib/queries/use-workspace-mutations.ts
📚 Learning: 2025-11-06T22:31:20.903Z
Learnt from: derianrddev
Repo: bitcashorg/masterbots PR: 579
File: apps/hasura/migrations/masterbots/1762286346002_alter_table_public_chatbot_add_column_department_id/up.sql:1-2
Timestamp: 2025-11-06T22:31:20.903Z
Learning: In the masterbots codebase, the `department_id` column in the `public.chatbot` table is intentionally nullable because not all chatbots are part of the onboarding flow. Only chatbots used in onboarding have an associated department.
Applied to files:
apps/pro-web/app/actions/thread.actions.tsapps/pro-web/lib/hooks/use-mb-chat.tsx
📚 Learning: 2025-10-24T20:40:56.510Z
Learnt from: AndlerRL
Repo: bitcashorg/masterbots PR: 573
File: apps/pro-web/components/routes/pro/pro.tsx:49-49
Timestamp: 2025-10-24T20:40:56.510Z
Learning: In apps/pro-web/lib/hooks/use-mb-chat.tsx, the useMBChat() hook returns a tuple [MBChatHookState, MBChatHookActions], not a direct object. Use array destructuring to access state (index 0) and actions (index 1), e.g., const [state, actions] = useMBChat() or const [{ isLoading }] = useMBChat().
Applied to files:
apps/pro-web/lib/hooks/use-mb-chat.tsx
📚 Learning: 2025-09-30T22:09:37.969Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-30T22:09:37.969Z
Learning: Applies to apps/{web,pro-web}/**/*.{ts,tsx} : Use custom hooks (useMBChat, useThread, useSidebar, useModel) for domain-specific state and keep providers focused and composed hierarchically
Applied to files:
apps/pro-web/lib/hooks/use-mb-chat.tsxapps/pro-web/components/routes/pro/pro.tsxapps/pro-web/lib/hooks/use-workspace.tsxapps/pro-web/components/routes/thread/user-thread-panel.tsx
📚 Learning: 2025-02-17T16:51:19.102Z
Learnt from: Bran18
Repo: bitcashorg/masterbots PR: 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-mb-chat.tsx
📚 Learning: 2024-11-07T14:40:39.595Z
Learnt from: Bran18
Repo: bitcashorg/masterbots PR: 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/lib/hooks/use-mb-chat.tsx
📚 Learning: 2024-11-14T21:12:19.789Z
Learnt from: AndlerRL
Repo: bitcashorg/masterbots PR: 302
File: apps/masterbots.ai/lib/hooks/use-mb-chat.ts:0-0
Timestamp: 2024-11-14T21:12:19.789Z
Learning: In `apps/masterbots.ai/lib/hooks/use-mb-chat.ts`, for handling message ordering issues, prefer simple solutions like using `setTimeout` over adding sequence numbers to the message schema, to keep the codebase simple.
Applied to files:
apps/pro-web/lib/hooks/use-mb-chat.tsx
📚 Learning: 2025-04-01T03:48:44.950Z
Learnt from: AndlerRL
Repo: bitcashorg/masterbots PR: 428
File: apps/masterbots.ai/lib/hooks/use-mb-chat.tsx:196-216
Timestamp: 2025-04-01T03:48:44.950Z
Learning: The `getCurrentSearchParams` function in `use-mb-chat.tsx` is intentionally using `window.location.search` to handle cases where the `useSearchParams` hook doesn't update when the page is reloaded. This is safe because the component has the 'use client' directive.
Applied to files:
apps/pro-web/lib/hooks/use-mb-chat.tsx
📚 Learning: 2025-04-26T10:58:44.341Z
Learnt from: AndlerRL
Repo: bitcashorg/masterbots PR: 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/workspace/workspace-breadcrumb.tsxapps/pro-web/lib/hooks/use-workspace.tsxapps/pro-web/components/routes/thread/user-thread-panel.tsx
📚 Learning: 2025-04-26T10:58:44.341Z
Learnt from: AndlerRL
Repo: bitcashorg/masterbots PR: 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/workspace/workspace-breadcrumb.tsxapps/pro-web/components/routes/thread/user-thread-panel.tsx
📚 Learning: 2025-08-26T06:40:51.075Z
Learnt from: AndlerRL
Repo: bitcashorg/masterbots PR: 0
File: :0-0
Timestamp: 2025-08-26T06:40:51.075Z
Learning: In the masterbots project, the pro-web app is missing onboarding elements that exist in the main web app, and pro-web has Thread Pop-up layout improvements that should be shared with the web app.
Applied to files:
apps/pro-web/components/routes/pro/pro.tsx
📚 Learning: 2025-09-30T22:08:30.965Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-30T22:08:30.965Z
Learning: Applies to apps/{web,pro-web}/**/*.tsx : Use functional components and hooks; type props with a TypeScript interface
Applied to files:
apps/pro-web/components/routes/pro/pro.tsxapps/pro-web/lib/hooks/use-workspace.tsx
📚 Learning: 2025-09-30T22:09:37.969Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-30T22:09:37.969Z
Learning: Applies to apps/{web,pro-web}/**/*.{ts,tsx} : Separate presentation from business logic and compose small components
Applied to files:
apps/pro-web/components/routes/pro/pro.tsx
📚 Learning: 2025-09-04T21:31:18.830Z
Learnt from: AndlerRL
Repo: bitcashorg/masterbots PR: 0
File: :0-0
Timestamp: 2025-09-04T21:31:18.830Z
Learning: The pro-web workspace system has identified performance bottlenecks in markdown parsing, AI streaming response handling, selection range management, Base64 document processing, and multiple debounced state operations that require optimization and better coordination.
Applied to files:
apps/pro-web/lib/hooks/use-workspace.tsx
📚 Learning: 2025-09-30T22:09:37.969Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-30T22:09:37.969Z
Learning: Pro workspace editors: controlled/uncontrolled patterns, Markdown utilities, section state sync, and timed DOM operations
Applied to files:
apps/pro-web/lib/hooks/use-workspace.tsx
📚 Learning: 2025-09-04T21:31:18.830Z
Learnt from: AndlerRL
Repo: bitcashorg/masterbots PR: 0
File: :0-0
Timestamp: 2025-09-04T21:31:18.830Z
Learning: The pro-web workspace features have complex performance considerations including markdown parsing optimization, AI streaming response handling, state persistence coordination, and selection range management that require careful monitoring and optimization.
Applied to files:
apps/pro-web/lib/hooks/use-workspace.tsx
📚 Learning: 2025-09-04T21:31:18.830Z
Learnt from: AndlerRL
Repo: bitcashorg/masterbots PR: 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.
Applied to files:
apps/pro-web/lib/hooks/use-workspace.tsx
📚 Learning: 2025-09-30T22:08:30.965Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-30T22:08:30.965Z
Learning: Applies to apps/{web,pro-web}/**/*.tsx : Order component files: imports → constants → component → styled components → types/interfaces
Applied to files:
apps/pro-web/lib/hooks/use-workspace.tsx
📚 Learning: 2024-10-23T19:11:47.520Z
Learnt from: AndlerRL
Repo: bitcashorg/masterbots PR: 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/user-thread-panel.tsx
📚 Learning: 2024-10-14T18:40:46.645Z
Learnt from: AndlerRL
Repo: bitcashorg/masterbots PR: 283
File: apps/masterbots.ai/components/routes/chat/admin-mode-approve.tsx:11-15
Timestamp: 2024-10-14T18:40:46.645Z
Learning: In the project, the `adminApproveThread` function within the `useThread` hook manages error handling and loading state, so components using it don't need to implement additional error handling unless they introduce side effects.
Applied to files:
apps/pro-web/components/routes/thread/user-thread-panel.tsx
📚 Learning: 2024-11-20T04:00:45.413Z
Learnt from: AndlerRL
Repo: bitcashorg/masterbots PR: 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/user-thread-panel.tsx
📚 Learning: 2025-04-30T08:45:27.463Z
Learnt from: AndlerRL
Repo: bitcashorg/masterbots PR: 470
File: apps/masterbots.ai/components/routes/thread/user-thread-panel.tsx:194-201
Timestamp: 2025-04-30T08:45:27.463Z
Learning: In the `UserThreadPanel` component, `count` and `totalThreads` serve different purposes: `count` represents the total available threads in the database based on filter criteria, while `totalThreads` represents the number of threads currently loaded into the UI. These distinct values are used to determine when to stop the infinite scroll via the guard condition `if (totalThreads === count) return`.
Applied to files:
apps/pro-web/components/routes/thread/user-thread-panel.tsx
🧬 Code graph analysis (5)
apps/pro-web/services/hasura/hasura.service.ts (1)
apps/pro-web/types/thread.types.ts (1)
ThreadOrganizationMetadata(52-56)
apps/pro-web/lib/queries/use-workspace-mutations.ts (1)
apps/pro-web/types/thread.types.ts (1)
OrganizationData(16-20)
apps/pro-web/components/routes/workspace/workspace-breadcrumb.tsx (1)
apps/pro-web/lib/hooks/use-sidebar.tsx (1)
useSidebar(63-69)
apps/pro-web/components/routes/pro/pro.tsx (1)
apps/pro-web/components/routes/pro/onboarding-panel.tsx (1)
OnboardingWelcome(7-78)
apps/pro-web/components/routes/thread/user-thread-panel.tsx (2)
apps/pro-web/types/thread.types.ts (1)
ThreadOrganizationMetadata(52-56)apps/pro-web/services/hasura/hasura.service.ts (1)
getThreads(350-511)
🔇 Additional comments (11)
apps/pro-web/services/hasura/hasura.service.ts (1)
360-362: Type change toPartial<ThreadOrganizationMetadata>is correct.This allows callers to provide only the fields they have available, enabling more flexible filtering.
apps/pro-web/lib/hooks/use-workspace.tsx (2)
253-265: LGTM – Guard and per-organization scoping are correct.The early return guard at lines 253-258 prevents errors when organization data is incomplete. The change to flatten only
projectsByDept[activeOrganization]correctly scopes the project list to the active organization rather than flattening all organizations.
337-339: Department may beundefinedwhen passed toaddDocumentmutation, but this is handled safely.While
activeDepartment?.[0]can beundefined, the mutation includes a fallback chain at lines 520-521 ofuse-workspace-mutations.tsthat ensures the final document always receives a valid department value:department || currentStructure.activeDepartment?.[0] || 'default'. The same fallback applies in the optimistic update. No action needed.apps/pro-web/components/routes/thread/user-thread-panel.tsx (2)
127-129: LGTM – Initial count from props is correct.Using
initialCountinstead of hardcoded0ensures proper initialization when server-rendered data is available.
257-261: ConsistentorganizationDatastructure withundefinedfor missing values.Passing
undefinedexplicitly for missing fields works correctly with the updatedgetThreadsimplementation that conditionally includes only defined fields in the filter.apps/pro-web/app/actions/thread.actions.ts (1)
722-738: LGTM – Department fallback change aligns with workspace navigation fix.The change from returning a default tuple
['General', 1]to returningnullwhen no department name exists is intentional and correct. The downstreamactiveProjectcalculation at line 735 correctly guards against a nullactiveDepartmentwith the checkactiveOrganization && activeDepartment.Verified that all consumers of
WorkspaceInitialState.activeDepartmentthroughout the codebase properly handle thenullcase, including optional chaining (activeDepartment?.[0]), direct null guards (if (activeDepartment)), and logical AND checks before accessing tuple elements. No unsafe accesses found.apps/pro-web/lib/queries/use-workspace-structure.ts (2)
252-254: LGTM!Good use of
Setto deduplicate the organization list before sorting. This ensures consistent state regardless of merge order from IDB/Cache/Server sources.
337-341: LGTM!Iterating over
finalStructure.organizationListinstead ofprojectsByOrg.entries()ensures all organizations are processed during the merge, not just those with existing projects. The['Project 1']fallback aligns with the default project creation inuseAddOrganization.apps/pro-web/components/routes/pro/pro.tsx (1)
116-122: Verify the broadened onboarding trigger condition.The condition
!activeOrganization || organizationList?.length === 0will display the onboarding panel whenever no organization is actively selected, even if organizations exist in the list.If the intent is to only show onboarding for users without any organizations, consider:
- {!activeOrganization || organizationList?.length === 0 ? ( + {organizationList?.length === 0 ? (Otherwise, if showing onboarding when no org is selected is intentional (e.g., prompting users to select/create), this looks correct.
apps/pro-web/components/routes/workspace/workspace-breadcrumb.tsx (2)
20-23: LGTM!Good addition of
startTransitionfor deferring non-urgent UI updates. MovingsetActiveDepartmenttouseSidebaraligns with the coding guidelines for using custom hooks for domain-specific state.
291-308: LGTM!Good use of
startTransitionto defer non-urgent UI state updates (popup/thread cleanup) while keeping navigation state changes synchronous. The early return for same-organization selection prevents unnecessary re-renders.
| const threads = | ||
| state.threads.length > initialThreads.length || isAdminMode || searchTerm | ||
| ? state.threads | ||
| : initialThreads | ||
| const count = | ||
| state.count > initialCount || isAdminMode || searchTerm | ||
| ? state.count | ||
| : initialCount | ||
| const hasWorkspaceContext = | ||
| Boolean(activeOrganization) && | ||
| Boolean(activeWorkspaceDepartment?.[0]) && | ||
| Boolean(activeProject) | ||
| useMemo( | ||
| () => | ||
| (state.threads.length > initialThreads.length || | ||
| isAdminMode || | ||
| searchTerm | ||
| ? state.threads | ||
| : initialThreads | ||
| ).filter((thread) => { | ||
| const threadOrgData = thread.metadata | ||
| .organizationData as ThreadOrganizationMetadata | ||
| return ( | ||
| threadOrgData.organization === activeOrganization && | ||
| (threadOrgData.department === activeWorkspaceDepartment?.[0] || | ||
| (activeProject && threadOrgData.project === activeProject)) | ||
| ) | ||
| }), | ||
| [ | ||
| state.threads, | ||
| initialThreads, | ||
| isAdminMode, | ||
| searchTerm, | ||
| activeOrganization, | ||
| activeWorkspaceDepartment, | ||
| activeProject, | ||
| ], | ||
| ) ?? [] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Potential runtime error: Missing null checks on thread.metadata.organizationData.
The filter at lines 195-201 directly accesses thread.metadata.organizationData without null checks. If a thread has no metadata or no organizationData, this will throw a TypeError.
Apply null-safe access:
.filter((thread) => {
- const threadOrgData = thread.metadata
- .organizationData as ThreadOrganizationMetadata
+ const threadOrgData = thread.metadata
+ ?.organizationData as ThreadOrganizationMetadata | undefined
+ if (!threadOrgData) return false
return (
threadOrgData.organization === activeOrganization &&
(threadOrgData.department === activeWorkspaceDepartment?.[0] ||
(activeProject && threadOrgData.project === activeProject))
)
}),🤖 Prompt for AI Agents
In apps/pro-web/components/routes/thread/user-thread-panel.tsx around lines 186
to 212, the filter callback directly accesses thread.metadata.organizationData
which can be null/undefined and cause a TypeError; update the filter to guard
against missing metadata by using null-safe checks (optional chaining and/or
early return) so threads without metadata or organizationData are skipped (e.g.,
check thread.metadata?.organizationData and return false if missing) and then
safely read organization/project/department fields for the comparison.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 5
♻️ Duplicate comments (3)
apps/pro-web/app/(pro)/[category]/[domain]/[chatbot]/[threadSlug]/page.tsx (1)
64-80: Missing error handling for getUserPreferences.The error returned by
getUserPreferencesis ignored. If the preferences fetch fails, threads will load without organization filtering. Consider logging the error for debugging.Apply this diff to add error logging:
const { data: userPreferences, error } = await getUserPreferences( userId, jwt as string, ) + if (error) { + console.error('Failed to fetch user preferences:', error) + } const latestOrganization = userPreferences?.latestOrganization
Code duplication: getUserPreferences pattern repeated.
This exact pattern appears in multiple page files. See comment on
apps/pro-web/app/(pro)/page.tsxfor refactoring suggestion.apps/pro-web/app/(pro)/[category]/[domain]/[chatbot]/[threadSlug]/[threadQuestionSlug]/page.tsx (1)
64-80: Missing error handling for getUserPreferences.The error returned by
getUserPreferencesis ignored. If the preferences fetch fails, threads will load without organization filtering. Consider logging the error for debugging.Apply this diff to add error logging:
const { data: userPreferences, error } = await getUserPreferences( userId, jwt as string, ) + if (error) { + console.error('Failed to fetch user preferences:', error) + } const latestOrganization = userPreferences?.latestOrganization
Code duplication: getUserPreferences pattern repeated.
This exact pattern appears in multiple page files. See comment on
apps/pro-web/app/(pro)/page.tsxfor refactoring suggestion.apps/pro-web/app/(pro)/[category]/[domain]/[chatbot]/page.tsx (1)
65-81: Missing error handling for getUserPreferences.The error returned by
getUserPreferencesis ignored. If the preferences fetch fails, threads will load without organization filtering. Consider logging the error for debugging.Apply this diff to add error logging:
const { data: userPreferences, error } = await getUserPreferences( userId, jwt as string, ) + if (error) { + console.error('Failed to fetch user preferences:', error) + } const latestOrganization = userPreferences?.latestOrganization
Code duplication: getUserPreferences pattern repeated.
This exact pattern appears in multiple page files. See comment on
apps/pro-web/app/(pro)/page.tsxfor refactoring suggestion.
🧹 Nitpick comments (3)
apps/pro-web/app/(pro)/[category]/page.tsx (2)
44-60: Consider explicit error handling for getUserPreferences.While Next.js error boundaries will catch failures, explicit error handling would make the behavior clearer and allow for logging or graceful degradation if the preferences service is unavailable.
- const { data: userPreferences } = await getUserPreferences( - session.user.id, - jwt, - ) + let userPreferences + try { + const result = await getUserPreferences(session.user.id, jwt) + userPreferences = result.data + } catch (error) { + console.error('Failed to fetch user preferences:', error) + userPreferences = null + }
44-54: Refactor: Rename variable for clarity.The variable
latestOrganizationis misleading—it holds the request payload structure, not the organization value itself. Rename toorganizationParamsororganizationPayloadfor clarity.The current type structure is correct:
latestOrganizationis a string (organization name) from preferences, and it's properly wrapped as{ organizationData: { organization: latestOrganization } }to match the expectedThreadOrganizationMetadatashape.packages/mb-drizzle/src/drizzle/schema.ts (1)
100-101: Consider adding validation for lang and fontSize fields.The new
langandfontSizefields are stored as free text without validation. Consider adding enum tables or check constraints to ensure data integrity and prevent invalid values.// Option 1: Create enum tables (similar to toneEnum, lengthEnum, etc.) export const langEnum = pgTable('lang_enum', { value: text().primaryKey().notNull(), name: text().notNull(), // e.g., "English", "Spanish" }) export const fontSizeEnum = pgTable('font_size_enum', { value: text().primaryKey().notNull(), }) // Then add foreign key constraints in preference table foreignKey({ columns: [table.lang], foreignColumns: [langEnum.value], name: 'preference_lang_fkey', }) .onUpdate('restrict') .onDelete('restrict'), // Option 2: Add check constraints if the set of values is small and stable check('preference_font_size_check', sql`font_size IN ('small', 'medium', 'large')`)Alternatively, if flexible values are intentional for future expansion, document the expected values in comments or application-level validation.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (3)
packages/mb-genql/generated/schema.graphqlis excluded by!**/generated/**packages/mb-genql/generated/schema.tsis excluded by!**/generated/**packages/mb-genql/generated/types.tsis excluded by!**/generated/**
📒 Files selected for processing (17)
apps/hasura/metadata/databases/masterbots/tables/public_organization.yaml(1 hunks)apps/hasura/migrations/masterbots/1765857892283_alter_table_public_preference_add_column_latest_organization/down.sql(1 hunks)apps/hasura/migrations/masterbots/1765857892283_alter_table_public_preference_add_column_latest_organization/up.sql(1 hunks)apps/pro-web/app/(pro)/[category]/[domain]/[chatbot]/[threadSlug]/[threadQuestionSlug]/page.tsx(2 hunks)apps/pro-web/app/(pro)/[category]/[domain]/[chatbot]/[threadSlug]/page.tsx(2 hunks)apps/pro-web/app/(pro)/[category]/[domain]/[chatbot]/page.tsx(2 hunks)apps/pro-web/app/(pro)/[category]/page.tsx(2 hunks)apps/pro-web/app/(pro)/page.tsx(2 hunks)apps/pro-web/app/u/[userSlug]/t/[category]/page.tsx(2 hunks)apps/pro-web/app/u/[userSlug]/t/page.tsx(2 hunks)apps/pro-web/auth.ts(1 hunks)apps/pro-web/components/routes/thread/user-thread-panel.tsx(6 hunks)apps/pro-web/lib/queries/use-workspace-mutations.ts(6 hunks)apps/pro-web/services/hasura/hasura.service.ts(4 hunks)apps/pro-web/types/index.ts(1 hunks)apps/web/auth.ts(1 hunks)packages/mb-drizzle/src/drizzle/schema.ts(1 hunks)
✅ Files skipped from review due to trivial changes (1)
- apps/hasura/migrations/masterbots/1765857892283_alter_table_public_preference_add_column_latest_organization/down.sql
🚧 Files skipped from review as they are similar to previous changes (3)
- apps/pro-web/lib/queries/use-workspace-mutations.ts
- apps/pro-web/services/hasura/hasura.service.ts
- apps/pro-web/components/routes/thread/user-thread-panel.tsx
🧰 Additional context used
📓 Path-based instructions (9)
apps/hasura/migrations/masterbots/**
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Store and manage Hasura migrations under apps/hasura/migrations/masterbots/
Store Hasura-managed migrations in timestamped folders under apps/hasura/migrations/masterbots/
Keep Hasura-managed database migrations in timestamped folders under apps/hasura/migrations/masterbots/
Files:
apps/hasura/migrations/masterbots/1765857892283_alter_table_public_preference_add_column_latest_organization/up.sql
**/*
📄 CodeRabbit inference engine (CLAUDE.md)
**/*: Use lowercase with dash-separated directory and file names; use specific extensions: .config.ts, .test.ts, .context.tsx, .type.ts, .hook.ts; components may omit extension
Use Bun for package management and scripts instead of npm/yarn
Use Biome for formatting and linting via bun format-and-lint:fix; avoid Prettier/ESLint configs
Files:
apps/hasura/migrations/masterbots/1765857892283_alter_table_public_preference_add_column_latest_organization/up.sqlapps/pro-web/app/u/[userSlug]/t/[category]/page.tsxapps/pro-web/app/u/[userSlug]/t/page.tsxapps/pro-web/app/(pro)/[category]/page.tsxapps/pro-web/app/(pro)/[category]/[domain]/[chatbot]/[threadSlug]/page.tsxapps/pro-web/app/(pro)/[category]/[domain]/[chatbot]/[threadSlug]/[threadQuestionSlug]/page.tsxapps/pro-web/app/(pro)/[category]/[domain]/[chatbot]/page.tsxapps/pro-web/auth.tsapps/pro-web/types/index.tsapps/hasura/metadata/databases/masterbots/tables/public_organization.yamlpackages/mb-drizzle/src/drizzle/schema.tsapps/web/auth.tsapps/pro-web/app/(pro)/page.tsx
apps/{web,pro-web}/**/*.tsx
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
apps/{web,pro-web}/**/*.tsx: Declare React components with the function keyword
Name React components using PascalCase
Order component files: imports → constants → component → styled components → types/interfaces
Use functional components and hooks; type props with a TypeScript interface
Use regular function references (not inline arrows) for React event handlers to avoid extra re-renders
apps/{web,pro-web}/**/*.tsx: Use regular function references for event handlers instead of inline arrow functions in JSX
Declare React components with the function keyword (function ComponentName() {})
Use PascalCase for React component names
Order React component files: imports, constants, component, styled components, types/interfaces
Style components with Tailwind CSS utility classes
Files:
apps/pro-web/app/u/[userSlug]/t/[category]/page.tsxapps/pro-web/app/u/[userSlug]/t/page.tsxapps/pro-web/app/(pro)/[category]/page.tsxapps/pro-web/app/(pro)/[category]/[domain]/[chatbot]/[threadSlug]/page.tsxapps/pro-web/app/(pro)/[category]/[domain]/[chatbot]/[threadSlug]/[threadQuestionSlug]/page.tsxapps/pro-web/app/(pro)/[category]/[domain]/[chatbot]/page.tsxapps/pro-web/app/(pro)/page.tsx
apps/{web,pro-web}/app/**
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
apps/{web,pro-web}/app/**: Prefer Next.js Server Components; use 'use client' only when necessary
Leverage Streaming and Suspense for responsiveness in Next.js 15
Follow Next.js file conventions in the App Router
apps/{web,pro-web}/app/**: Leverage Server Components by default; use the use client directive only when client-side features are needed
Implement Nested Layouts using the App Router
Use Streaming and Suspense features in Next.js 14/15 where appropriate
Follow Next.js file conventions for routing and layout in the App Router
Optimize data fetching: fetch on the server, fetch where needed, and use preload patterns to avoid waterfalls
Files:
apps/pro-web/app/u/[userSlug]/t/[category]/page.tsxapps/pro-web/app/u/[userSlug]/t/page.tsxapps/pro-web/app/(pro)/[category]/page.tsxapps/pro-web/app/(pro)/[category]/[domain]/[chatbot]/[threadSlug]/page.tsxapps/pro-web/app/(pro)/[category]/[domain]/[chatbot]/[threadSlug]/[threadQuestionSlug]/page.tsxapps/pro-web/app/(pro)/[category]/[domain]/[chatbot]/page.tsxapps/pro-web/app/(pro)/page.tsx
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
**/*.{ts,tsx}: Avoid default exports; prefer named exports
Use meaningful variable names; prefix booleans with is/has/does/should
Use interface for objects/classes; type for unions/tuples/aliases; enum for fixed sets; const for literals
Avoid any; prefer specific types
Leverage type inference where clear; annotate function params and return types
Prefer type assertions with 'as' over angle-bracket syntax
Use type guards to narrow types in conditionals
**/*.{ts,tsx}: Avoid default exports; prefer named exports
Use modern JavaScript features with TypeScript across the codebase
Use meaningful names; prefix booleans with is/has/does/should (e.g., isDisabled)
Receive an object and return an object (RORO) for functions interfacing with external services
Type definitions: use interface for objects/classes; type for unions/tuples/aliases; const for literals; enum for fixed sets
Avoid any; prefer specific types
Leverage type inference; omit explicit annotations when obvious
Add explicit type annotations for function parameters and return values
Prefer type assertions with as over angle-bracket syntax
Use type guards to narrow types within conditionals
Favor composition over inheritance in code structure
**/*.{ts,tsx}: Avoid default exports; prefer named exports
Prefer RORO (Receive an object, return an object) for function signatures, especially for external service interactions
Use interface for object shapes/classes; type for unions/tuples/aliases; const for literals; enum for fixed enumerations
Avoid using any; prefer precise types
Leverage type inference when clear; omit redundant annotations
Explicitly annotate function parameters and return types
Prefer type assertions with as over angle-bracket syntax
Use type guards to narrow types safely
Use meaningful variable names; prefix booleans with is/has/does/should
Favor composition over inheritance
Use lowercase dash-separated names for files and folders; use extensions like .config.ts, .test.ts, .context.tsx, .typ...
Files:
apps/pro-web/app/u/[userSlug]/t/[category]/page.tsxapps/pro-web/app/u/[userSlug]/t/page.tsxapps/pro-web/app/(pro)/[category]/page.tsxapps/pro-web/app/(pro)/[category]/[domain]/[chatbot]/[threadSlug]/page.tsxapps/pro-web/app/(pro)/[category]/[domain]/[chatbot]/[threadSlug]/[threadQuestionSlug]/page.tsxapps/pro-web/app/(pro)/[category]/[domain]/[chatbot]/page.tsxapps/pro-web/auth.tsapps/pro-web/types/index.tspackages/mb-drizzle/src/drizzle/schema.tsapps/web/auth.tsapps/pro-web/app/(pro)/page.tsx
apps/{web,pro-web}/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
apps/{web,pro-web}/**/*.{ts,tsx}: Use lowercase kebab-case for directories and filenames (components can omit extension)
Verify DOM operations and consider timing (RAF/delays) when manipulating the DOM in React
Use Tailwind CSS utility classes for styling React components
apps/{web,pro-web}/**/*.{ts,tsx}: Use TypeScript to define React props types (interfaces for props)
Use custom hooks (useMBChat, useThread, useSidebar, useModel) for domain-specific state and keep providers focused and composed hierarchically
Separate presentation from business logic and compose small components
Use hasura.service.ts for all GraphQL operations from UI layers
apps/{web,pro-web}/**/*.{ts,tsx}: Prefer Server Components; use the use client directive only when client-side features are required
Implement Nested Layouts using the App Router
Use Streaming and Suspense in Next.js 14/15 where appropriate
Follow Next.js file conventions as per documentation
Use IndexedDB for local caching to enable immediate UI updates
Files:
apps/pro-web/app/u/[userSlug]/t/[category]/page.tsxapps/pro-web/app/u/[userSlug]/t/page.tsxapps/pro-web/app/(pro)/[category]/page.tsxapps/pro-web/app/(pro)/[category]/[domain]/[chatbot]/[threadSlug]/page.tsxapps/pro-web/app/(pro)/[category]/[domain]/[chatbot]/[threadSlug]/[threadQuestionSlug]/page.tsxapps/pro-web/app/(pro)/[category]/[domain]/[chatbot]/page.tsxapps/pro-web/auth.tsapps/pro-web/types/index.tsapps/web/auth.tsapps/pro-web/app/(pro)/page.tsx
**/*.tsx
📄 CodeRabbit inference engine (AGENTS.md)
**/*.tsx: Avoid inline arrow functions in JSX props; use stable function references for event handlers
Declare React components with the function keyword (functional components)
Use PascalCase for React component names
Use functional components and hooks instead of class components
Type component props with TypeScript interfaces
Files:
apps/pro-web/app/u/[userSlug]/t/[category]/page.tsxapps/pro-web/app/u/[userSlug]/t/page.tsxapps/pro-web/app/(pro)/[category]/page.tsxapps/pro-web/app/(pro)/[category]/[domain]/[chatbot]/[threadSlug]/page.tsxapps/pro-web/app/(pro)/[category]/[domain]/[chatbot]/[threadSlug]/[threadQuestionSlug]/page.tsxapps/pro-web/app/(pro)/[category]/[domain]/[chatbot]/page.tsxapps/pro-web/app/(pro)/page.tsx
apps/{web,pro-web}/types/**
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Place shared TypeScript definitions under types/
Keep TypeScript type definitions under types/
Files:
apps/pro-web/types/index.ts
apps/{web,pro-web}/types/**/*.ts
📄 CodeRabbit inference engine (AGENTS.md)
Place shared TypeScript types under types/
Files:
apps/pro-web/types/index.ts
🧠 Learnings (34)
📓 Common learnings
Learnt from: AndlerRL
Repo: bitcashorg/masterbots PR: 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.
Learnt from: AndlerRL
Repo: bitcashorg/masterbots PR: 0
File: :0-0
Timestamp: 2025-11-07T18:41:56.562Z
Learning: Successfully created comprehensive GitHub issue #581 consolidating 16 unresolved observations from PR #579 review, including critical TDZ crash, migration bugs, transaction safety issues, and validation improvements, with specific file paths, line numbers, and action items organized by priority (4 critical, 8 major, 2 minor, 2 acknowledged for future PRs).
Learnt from: AndlerRL
Repo: bitcashorg/masterbots PR: 0
File: :0-0
Timestamp: 2025-11-21T19:47:50.195Z
Learning: For repo bitcashorg/masterbots (apps/pro-web), AndlerRL prefers integrating TanStack Query into React Context Hooks wherever they fit to manage shared server state and side effects, with a longer-term plan to align broader state management with TanStack Query and potentially replace React Context hooks. Captured from PR #584 discussion on Nov 21, 2025.
Learnt from: derianrddev
Repo: bitcashorg/masterbots PR: 579
File: apps/hasura/migrations/masterbots/1762286346002_alter_table_public_chatbot_add_column_department_id/up.sql:1-2
Timestamp: 2025-11-06T22:31:20.903Z
Learning: In the masterbots codebase, the `department_id` column in the `public.chatbot` table is intentionally nullable because not all chatbots are part of the onboarding flow. Only chatbots used in onboarding have an associated department.
Learnt from: AndlerRL
Repo: bitcashorg/masterbots PR: 542
File: apps/web/components/layout/profile/layout.tsx:67-68
Timestamp: 2025-08-29T20:34:24.433Z
Learning: AndlerRL prefers skeleton loading states over conditional rendering for better UX when components are waiting for data to load. Components should show loading indicators rather than blank spaces.
📚 Learning: 2025-09-30T22:11:18.270Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-30T22:11:18.270Z
Learning: Applies to apps/hasura/migrations/masterbots/** : Keep Hasura-managed database migrations in timestamped folders under apps/hasura/migrations/masterbots/
Applied to files:
apps/hasura/migrations/masterbots/1765857892283_alter_table_public_preference_add_column_latest_organization/up.sql
📚 Learning: 2025-09-30T22:09:37.969Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-30T22:09:37.969Z
Learning: Applies to apps/hasura/migrations/masterbots/** : Store Hasura-managed migrations in timestamped folders under apps/hasura/migrations/masterbots/
Applied to files:
apps/hasura/migrations/masterbots/1765857892283_alter_table_public_preference_add_column_latest_organization/up.sql
📚 Learning: 2025-09-30T22:08:30.965Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-30T22:08:30.965Z
Learning: Applies to apps/hasura/migrations/masterbots/** : Store and manage Hasura migrations under apps/hasura/migrations/masterbots/
Applied to files:
apps/hasura/migrations/masterbots/1765857892283_alter_table_public_preference_add_column_latest_organization/up.sql
📚 Learning: 2024-10-23T19:11:47.520Z
Learnt from: AndlerRL
Repo: bitcashorg/masterbots PR: 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/app/u/[userSlug]/t/[category]/page.tsxapps/pro-web/app/u/[userSlug]/t/page.tsxapps/pro-web/app/(pro)/[category]/[domain]/[chatbot]/[threadSlug]/page.tsxapps/pro-web/app/(pro)/[category]/[domain]/[chatbot]/[threadSlug]/[threadQuestionSlug]/page.tsx
📚 Learning: 2025-04-26T10:58:44.341Z
Learnt from: AndlerRL
Repo: bitcashorg/masterbots PR: 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/app/u/[userSlug]/t/[category]/page.tsxapps/pro-web/app/u/[userSlug]/t/page.tsxapps/pro-web/app/(pro)/[category]/page.tsxapps/pro-web/app/(pro)/[category]/[domain]/[chatbot]/[threadSlug]/page.tsx
📚 Learning: 2025-04-26T10:58:44.341Z
Learnt from: AndlerRL
Repo: bitcashorg/masterbots PR: 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/app/u/[userSlug]/t/[category]/page.tsxapps/pro-web/app/u/[userSlug]/t/page.tsxapps/pro-web/app/(pro)/[category]/page.tsxapps/pro-web/app/(pro)/[category]/[domain]/[chatbot]/[threadSlug]/page.tsxapps/pro-web/app/(pro)/[category]/[domain]/[chatbot]/[threadSlug]/[threadQuestionSlug]/page.tsxapps/pro-web/app/(pro)/page.tsx
📚 Learning: 2025-11-21T18:02:42.653Z
Learnt from: AndlerRL
Repo: bitcashorg/masterbots PR: 590
File: apps/web/lib/hooks/use-profile.tsx:79-81
Timestamp: 2025-11-21T18:02:42.653Z
Learning: Preference: In apps/web/lib/hooks/use-profile.tsx, avoid client-side re-fetching the current user in ProfileProvider. Initialize sessionUser from the server-provided NextAuth session (initialSession) and treat it as the source of truth; remove the useEffect that calls getUserByID. Persist client edits via NextAuth’s session update when needed.
Applied to files:
apps/pro-web/app/u/[userSlug]/t/[category]/page.tsxapps/pro-web/app/u/[userSlug]/t/page.tsxapps/pro-web/app/(pro)/[category]/page.tsxapps/pro-web/app/(pro)/[category]/[domain]/[chatbot]/[threadSlug]/page.tsxapps/pro-web/app/(pro)/[category]/[domain]/[chatbot]/[threadSlug]/[threadQuestionSlug]/page.tsxapps/pro-web/app/(pro)/[category]/[domain]/[chatbot]/page.tsxapps/pro-web/auth.tsapps/web/auth.tsapps/pro-web/app/(pro)/page.tsx
📚 Learning: 2025-09-30T22:09:37.969Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-30T22:09:37.969Z
Learning: Applies to apps/{web,pro-web}/**/*.{ts,tsx} : Use custom hooks (useMBChat, useThread, useSidebar, useModel) for domain-specific state and keep providers focused and composed hierarchically
Applied to files:
apps/pro-web/app/u/[userSlug]/t/[category]/page.tsxapps/pro-web/app/u/[userSlug]/t/page.tsxapps/pro-web/app/(pro)/[category]/page.tsxapps/pro-web/app/(pro)/[category]/[domain]/[chatbot]/[threadSlug]/page.tsxapps/pro-web/app/(pro)/[category]/[domain]/[chatbot]/[threadSlug]/[threadQuestionSlug]/page.tsxapps/pro-web/app/(pro)/[category]/[domain]/[chatbot]/page.tsxapps/pro-web/app/(pro)/page.tsx
📚 Learning: 2024-10-14T18:40:46.645Z
Learnt from: AndlerRL
Repo: bitcashorg/masterbots PR: 283
File: apps/masterbots.ai/components/routes/chat/admin-mode-approve.tsx:11-15
Timestamp: 2024-10-14T18:40:46.645Z
Learning: In the project, the `adminApproveThread` function within the `useThread` hook manages error handling and loading state, so components using it don't need to implement additional error handling unless they introduce side effects.
Applied to files:
apps/pro-web/app/u/[userSlug]/t/[category]/page.tsx
📚 Learning: 2025-11-21T18:00:08.455Z
Learnt from: AndlerRL
Repo: bitcashorg/masterbots PR: 590
File: apps/web/lib/hooks/use-profile.tsx:60-78
Timestamp: 2025-11-21T18:00:08.455Z
Learning: In apps/web/lib/hooks/use-profile.tsx: The sessionUser state should be populated directly from session.user (JWT) and not by fetching from Hasura on the client side. The useEffect should only synchronize sessionUser with session.user. Client-side Hasura fetches for user data should be avoided; user data comes from the JWT/session. Only when users update their profile should we refresh both the database and JWT token (via NextAuth session update trigger).
Applied to files:
apps/pro-web/app/u/[userSlug]/t/page.tsxapps/pro-web/app/(pro)/[category]/page.tsxapps/pro-web/app/(pro)/[category]/[domain]/[chatbot]/[threadSlug]/page.tsxapps/pro-web/app/(pro)/[category]/[domain]/[chatbot]/[threadSlug]/[threadQuestionSlug]/page.tsxapps/pro-web/app/(pro)/[category]/[domain]/[chatbot]/page.tsxapps/pro-web/app/(pro)/page.tsx
📚 Learning: 2025-09-12T20:30:40.360Z
Learnt from: AndlerRL
Repo: bitcashorg/masterbots PR: 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/[userSlug]/t/page.tsxapps/web/auth.ts
📚 Learning: 2024-11-07T20:08:13.609Z
Learnt from: Bran18
Repo: bitcashorg/masterbots PR: 301
File: apps/masterbots.ai/components/routes/browse/browse-user-details.tsx:65-67
Timestamp: 2024-11-07T20:08:13.609Z
Learning: In the `BrowseUserDetails` component (`browse-user-details.tsx`), when displaying usernames, truncate and display ellipsis only on large screens (LG/XL) without creating custom reactive hooks. On smaller screens, do not include ellipsis for responsive reasons.
Applied to files:
apps/pro-web/app/u/[userSlug]/t/page.tsx
📚 Learning: 2025-09-30T22:09:37.969Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-30T22:09:37.969Z
Learning: Applies to apps/{web,pro-web}/**/*.{ts,tsx} : Use hasura.service.ts for all GraphQL operations from UI layers
Applied to files:
apps/pro-web/app/(pro)/[category]/page.tsxapps/pro-web/app/(pro)/[category]/[domain]/[chatbot]/[threadSlug]/page.tsxapps/pro-web/app/(pro)/[category]/[domain]/[chatbot]/[threadSlug]/[threadQuestionSlug]/page.tsxapps/pro-web/app/(pro)/[category]/[domain]/[chatbot]/page.tsxapps/pro-web/app/(pro)/page.tsx
📚 Learning: 2025-11-28T03:44:16.389Z
Learnt from: sheriffjimoh
Repo: bitcashorg/masterbots PR: 590
File: apps/web/lib/hooks/use-profile.tsx:60-78
Timestamp: 2025-11-28T03:44:16.389Z
Learning: In apps/web/components/routes/preferences/preference-section.tsx: After updating user profile in the database via updateUserDetails, must call session.update() (from useSession hook) with the updated fields to trigger NextAuth's JWT callback with trigger='update'. This refreshes the JWT token and session automatically. Do not manually call updateSessionUser; the useEffect in use-profile.tsx will sync sessionUser from the refreshed session.user.
Applied to files:
apps/pro-web/app/(pro)/[category]/page.tsxapps/pro-web/app/(pro)/[category]/[domain]/[chatbot]/[threadSlug]/page.tsxapps/pro-web/app/(pro)/[category]/[domain]/[chatbot]/[threadSlug]/[threadQuestionSlug]/page.tsxapps/pro-web/app/(pro)/[category]/[domain]/[chatbot]/page.tsxapps/pro-web/auth.tsapps/web/auth.tsapps/pro-web/app/(pro)/page.tsx
📚 Learning: 2025-09-30T22:08:30.965Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-30T22:08:30.965Z
Learning: Applies to **/services/hasura.service.ts : Use hasura.service.ts as the single point for all GraphQL interactions
Applied to files:
apps/pro-web/app/(pro)/[category]/page.tsxapps/pro-web/app/(pro)/[category]/[domain]/[chatbot]/page.tsxapps/pro-web/app/(pro)/page.tsx
📚 Learning: 2025-09-30T22:11:18.270Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-30T22:11:18.270Z
Learning: Applies to **/services/hasura.service.ts : Use hasura.service.ts as the single interaction point for GraphQL operations with Hasura
Applied to files:
apps/pro-web/app/(pro)/[category]/page.tsxapps/pro-web/app/(pro)/[category]/[domain]/[chatbot]/page.tsxapps/pro-web/app/(pro)/page.tsx
📚 Learning: 2025-09-30T22:09:37.969Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-30T22:09:37.969Z
Learning: Applies to apps/**/services/hasura.service.ts : Use hasura.service.ts as the single point of GraphQL interaction in each app
Applied to files:
apps/pro-web/app/(pro)/[category]/page.tsx
📚 Learning: 2025-02-17T16:51:19.102Z
Learnt from: Bran18
Repo: bitcashorg/masterbots PR: 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/app/(pro)/[category]/[domain]/[chatbot]/[threadSlug]/page.tsxapps/pro-web/app/(pro)/[category]/[domain]/[chatbot]/[threadSlug]/[threadQuestionSlug]/page.tsxapps/pro-web/app/(pro)/[category]/[domain]/[chatbot]/page.tsx
📚 Learning: 2024-12-05T01:08:50.085Z
Learnt from: AndlerRL
Repo: bitcashorg/masterbots PR: 0
File: :0-0
Timestamp: 2024-12-05T01:08:50.085Z
Learning: The project is a monorepo using Turborepo with Next.js frontend (apps/masterbots.ai), Hasura backend (apps/hasura), and shared packages for types, GraphQL operations, and utilities.
Applied to files:
apps/pro-web/app/(pro)/[category]/[domain]/[chatbot]/[threadSlug]/page.tsxapps/pro-web/app/(pro)/[category]/[domain]/[chatbot]/[threadSlug]/[threadQuestionSlug]/page.tsxapps/pro-web/app/(pro)/[category]/[domain]/[chatbot]/page.tsx
📚 Learning: 2024-12-05T01:14:50.365Z
Learnt from: AndlerRL
Repo: bitcashorg/masterbots PR: 0
File: :0-0
Timestamp: 2024-12-05T01:14:50.365Z
Learning: The masterbots.ai project follows Next.js 14 App Router conventions with server components as default and client components marked explicitly with 'use client' directive when needed.
Applied to files:
apps/pro-web/app/(pro)/[category]/[domain]/[chatbot]/[threadSlug]/page.tsxapps/pro-web/app/(pro)/[category]/[domain]/[chatbot]/[threadSlug]/[threadQuestionSlug]/page.tsx
📚 Learning: 2025-09-30T22:11:18.270Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-30T22:11:18.270Z
Learning: Applies to apps/{web,pro-web}/**/ai-main-call.actions.{ts,tsx} : Route AI API handling through ai-main-call.actions for unified processing
Applied to files:
apps/pro-web/app/(pro)/[category]/[domain]/[chatbot]/page.tsx
📚 Learning: 2025-11-07T18:38:42.273Z
Learnt from: AndlerRL
Repo: bitcashorg/masterbots PR: 579
File: apps/pro-web/app/api/auth/signup/route.ts:151-157
Timestamp: 2025-11-07T18:38:42.273Z
Learning: In apps/pro-web/app/actions/admin.actions.ts, the insertUserOrganizations function should use Drizzle DB transactions to ensure atomicity when inserting organizations and their associated chatbots, preventing partial persistence issues.
Applied to files:
apps/pro-web/app/(pro)/[category]/[domain]/[chatbot]/page.tsx
📚 Learning: 2025-09-30T22:09:37.969Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-30T22:09:37.969Z
Learning: Use NextAuth.js with custom user management; JWT for API auth; roles/subscriptions for access control
Applied to files:
apps/pro-web/app/(pro)/[category]/[domain]/[chatbot]/page.tsx
📚 Learning: 2025-08-29T20:34:37.023Z
Learnt from: AndlerRL
Repo: bitcashorg/masterbots PR: 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/auth.ts
📚 Learning: 2025-09-30T22:11:18.270Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-30T22:11:18.270Z
Learning: Applies to apps/{web,pro-web}/**/*.{ts,tsx} : Use IndexedDB for local caching to enable immediate UI updates
Applied to files:
apps/pro-web/auth.tsapps/pro-web/app/(pro)/page.tsx
📚 Learning: 2025-09-30T22:11:18.270Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-30T22:11:18.270Z
Learning: Applies to apps/{web,pro-web}/types/**/*.ts : Place shared TypeScript types under types/
Applied to files:
apps/pro-web/types/index.ts
📚 Learning: 2025-09-30T22:09:37.969Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-30T22:09:37.969Z
Learning: Applies to apps/{web,pro-web}/**/*.{ts,tsx} : Use TypeScript to define React props types (interfaces for props)
Applied to files:
apps/pro-web/types/index.ts
📚 Learning: 2025-09-30T22:09:37.969Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-30T22:09:37.969Z
Learning: Applies to apps/{web,pro-web}/types/** : Keep TypeScript type definitions under types/
Applied to files:
apps/pro-web/types/index.ts
📚 Learning: 2025-09-30T22:08:30.965Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-30T22:08:30.965Z
Learning: Applies to apps/{web,pro-web}/types/** : Place shared TypeScript definitions under types/
Applied to files:
apps/pro-web/types/index.ts
📚 Learning: 2025-09-30T22:08:30.965Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-30T22:08:30.965Z
Learning: Applies to apps/{web,pro-web}/**/*.tsx : Order component files: imports → constants → component → styled components → types/interfaces
Applied to files:
apps/pro-web/types/index.tsapps/pro-web/app/(pro)/page.tsx
📚 Learning: 2025-09-30T22:08:30.965Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-30T22:08:30.965Z
Learning: Applies to apps/{web,pro-web}/**/*.tsx : Use functional components and hooks; type props with a TypeScript interface
Applied to files:
apps/pro-web/types/index.ts
📚 Learning: 2025-09-30T22:11:18.270Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-30T22:11:18.270Z
Learning: Applies to apps/{web,pro-web}/**/*.{ts,tsx} : Follow Next.js file conventions as per documentation
Applied to files:
apps/pro-web/app/(pro)/page.tsx
📚 Learning: 2025-09-30T22:11:18.270Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-30T22:11:18.270Z
Learning: Applies to apps/{web,pro-web}/**/*.{ts,tsx} : Use Streaming and Suspense in Next.js 14/15 where appropriate
Applied to files:
apps/pro-web/app/(pro)/page.tsx
🧬 Code graph analysis (4)
apps/pro-web/app/(pro)/[category]/[domain]/[chatbot]/[threadSlug]/page.tsx (1)
apps/pro-web/services/hasura/hasura.service.ts (2)
getUserPreferences(2280-2304)getThreads(349-510)
apps/pro-web/app/(pro)/[category]/[domain]/[chatbot]/[threadSlug]/[threadQuestionSlug]/page.tsx (1)
apps/pro-web/services/hasura/hasura.service.ts (2)
getUserPreferences(2280-2304)getThreads(349-510)
apps/pro-web/app/(pro)/[category]/[domain]/[chatbot]/page.tsx (1)
apps/pro-web/services/hasura/hasura.service.ts (2)
getUserPreferences(2280-2304)getThreads(349-510)
apps/pro-web/app/(pro)/page.tsx (1)
apps/pro-web/services/hasura/hasura.service.ts (2)
getUserPreferences(2280-2304)getThreads(349-510)
🔇 Additional comments (5)
apps/pro-web/app/u/[userSlug]/t/page.tsx (2)
4-4: Good addition of pagination limit for own-profile thread loading.The import and application of
PAGE_SM_SIZEproperly constrains the initial thread fetch for authenticated users viewing their own profile, aligning with the PR's pagination improvements.Also applies to: 55-55
43-45: Passlimit: PAGE_SM_SIZEtogetBrowseThreadsfor consistency with own-profile pagination.The browse-profile path loads 20 threads (getBrowseThreads default), while the own-profile path loads 5 threads (PAGE_SM_SIZE). Both functions support the limit parameter; pass
limit: PAGE_SM_SIZEto getBrowseThreads on line 43 to align pagination behavior.apps/pro-web/app/u/[userSlug]/t/[category]/page.tsx (1)
62-62: LGTM: Scoped pagination for own-profile threads.Adding
limit: PAGE_SM_SIZEappropriately constrains the initial thread fetch for own-profile views, improving performance.apps/pro-web/types/index.ts (1)
10-10: No import breakage from PreferenceInsertInput addition.The search found no code importing a
Preferencestype from@/types. The onlyPreferencesreferences in the codebase are a component from@/components/routes/preferences/preferencesand a local interface inuse-preferences.tsx.PreferenceInsertInputis properly used inhasura.service.tsfor function parameters. The change is safe.Likely an incorrect or invalid review comment.
packages/mb-drizzle/src/drizzle/schema.ts (1)
92-95: No breaking issues found. The preference fields are made nullable in the schema, but the application already handles this correctly:
- Default values are provided during signup (
preferredTone: 'neutral',preferredLength: 'detailed',preferredType: 'mix',preferredComplexity: 'general')- The preference UI components don't directly access these fields—only
langandfontSizeare managed through the preference context- Generated GraphQL types already reflect these fields as nullable (
String | null)- Foreign key constraints on nullable columns are valid; NULL values bypass the constraint check while non-NULL values must satisfy the enum references
The nullable schema change is compatible with the application's current code.
| - name: organization_preference | ||
| using: | ||
| manual_configuration: | ||
| column_mapping: | ||
| name: latest_organization | ||
| insertion_order: null | ||
| remote_table: | ||
| name: preference | ||
| schema: public |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
Fragile relationship: references organization by name instead of ID.
This relationship maps organization.name (text) to preference.latest_organization (text), which is fragile for the same reasons outlined in the migration review:
- No referential integrity if names aren't constrained.
- Name changes would break existing preferences.
- Less performant than ID-based joins.
If you refactor the migration to use latest_organization_id instead of latest_organization, update this relationship accordingly:
- name: organization_preference
using:
manual_configuration:
column_mapping:
- name: latest_organization
+ organization_id: latest_organization_id
insertion_order: null
remote_table:
name: preference
schema: publicCommittable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In apps/hasura/metadata/databases/masterbots/tables/public_organization.yaml
around lines 5-13, the manual relationship maps organization.name to
preference.latest_organization (text) which is fragile; update the relationship
to map organization.id to preference.latest_organization_id instead. Change the
column_mapping to use id: latest_organization_id, ensure the remote_table column
name is updated to the new preference column, and confirm the preference table
has the latest_organization_id column with the correct type and a foreign key
constraint to public.organization.id (or add it if missing) so the Hasura
relationship is ID-based and consistent.
🧩 Analysis chain
🏁 Script executed:
cat -n apps/hasura/metadata/databases/masterbots/tables/public_organization.yamlRepository: bitcashorg/masterbots
Length of output: 2115
🏁 Script executed:
find . -type f \( -name "*.sql" -o -name "*.yaml" \) | xargs grep -l "preference" | head -20Repository: bitcashorg/masterbots
Length of output: 1729
🏁 Script executed:
cat -n apps/hasura/metadata/databases/masterbots/tables/public_preference.yamlRepository: bitcashorg/masterbots
Length of output: 3958
🏁 Script executed:
head -100 apps/hasura/migrations/masterbots/1698804096149_init/up.sql | grep -A 30 "CREATE TABLE.*preference"Repository: bitcashorg/masterbots
Length of output: 47
🏁 Script executed:
rg -n "CREATE TABLE.*preference" --type sqlRepository: bitcashorg/masterbots
Length of output: 261
🏁 Script executed:
sed -n '61,120p' packages/mb-drizzle/src/drizzle/0000_pink_lionheart.sqlRepository: bitcashorg/masterbots
Length of output: 2011
🏁 Script executed:
cat -n apps/hasura/metadata/databases/masterbots/tables/public_organization.yaml | head -20Repository: bitcashorg/masterbots
Length of output: 672
🏁 Script executed:
rg -n "CREATE TABLE.*organization" --type sqlRepository: bitcashorg/masterbots
Length of output: 963
🏁 Script executed:
rg -n "latest_organization" --type sqlRepository: bitcashorg/masterbots
Length of output: 449
🏁 Script executed:
cat apps/hasura/migrations/masterbots/1765857892283_alter_table_public_preference_add_column_latest_organization/up.sqlRepository: bitcashorg/masterbots
Length of output: 143
Change organization_preference from object_relationship to array_relationship.
The latest_organization column in the preference table has no unique constraint, allowing multiple preference records to share the same organization name. This creates a one-to-many relationship from organization to preferences, which must be modeled as an array_relationship rather than object_relationship. An object_relationship will only return the first matching preference, silently omitting others.
Apply this change:
-object_relationships:
+array_relationships:
- name: organization_preference
using:
manual_configuration:
column_mapping:
name: latest_organization
insertion_order: null
remote_table:
name: preference
schema: publicCommittable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In apps/hasura/metadata/databases/masterbots/tables/public_organization.yaml
around lines 5-13, the relationship currently defined for
"organization_preference" is modeled as an object_relationship but must be an
array_relationship; change the relationship type from object_relationship to
array_relationship by moving this relationship entry under the
array_relationships section (preserving the existing name, manual_configuration,
column_mapping, insertion_order and remote_table fields) so the organization ->
preferences relation returns an array of matching preference rows instead of a
single object.
| alter table "public"."preference" add column "latest_organization" text | ||
| null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add foreign key constraint to ensure referential integrity.
The latest_organization column lacks a foreign key constraint to public.organization, allowing insertion of invalid organization names and creating orphaned references if organizations are deleted or renamed.
Apply this diff to add the constraint:
alter table "public"."preference" add column "latest_organization" text
- null;
+ null constraint fk_preference_latest_organization
+ references "public"."organization"("name") on delete set null;Note: This assumes organization.name is unique. If not, see the next comment about using organization_id instead.
📝 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.
| alter table "public"."preference" add column "latest_organization" text | |
| null; | |
| alter table "public"."preference" add column "latest_organization" text | |
| null constraint fk_preference_latest_organization | |
| references "public"."organization"("name") on delete set null; |
🤖 Prompt for AI Agents
In
apps/hasura/migrations/masterbots/1765857892283_alter_table_public_preference_add_column_latest_organization/up.sql
around lines 1-2, the new latest_organization text column has no foreign key
constraint to public.organization, allowing invalid/orphaned values; add a
foreign key constraint referencing public.organization(name) (ensuring
organization.name is unique) or, if name is not unique, change to storing
organization_id and add a foreign key to public.organization(id); alter the
migration to ALTER TABLE public.preference ADD CONSTRAINT
fk_preference_latest_org FOREIGN KEY (latest_organization) REFERENCES
public.organization(name) or the equivalent organization_id-based constraint and
ensure any existing data respects the constraint (or add a safe data
migration/cleanup step before applying).
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# First, find all migration files to understand the schema
echo "=== Finding migration files ==="
find apps/hasura/migrations/masterbots -type f -name "*.sql" | head -20
echo -e "\n=== Checking organization table definition ==="
# Look for organization table creation
find apps/hasura/migrations/masterbots -type f -name "*.sql" -exec grep -l "create.*table.*organization" -i {} \;
echo -e "\n=== Searching for unique constraints on organization.name ==="
find apps/hasura/migrations/masterbots -type f -name "*.sql" -exec rg -l "unique.*name|unique.*constraint" {} \; | head -10Repository: bitcashorg/masterbots
Length of output: 2602
🏁 Script executed:
#!/bin/bash
# Search for references to organization in migration files
echo "=== Looking for foreign keys and references to organization ==="
find apps/hasura/migrations/masterbots -type f -name "*.sql" -exec rg -H "organization_id|references.*organization" {} \;
echo -e "\n=== Checking the preference table related migrations ==="
find apps/hasura/migrations/masterbots -type f -name "*preference*" | sortRepository: bitcashorg/masterbots
Length of output: 1081
🏁 Script executed:
#!/bin/bash
# Examine the migration file in question and surrounding files
echo "=== Migration file content ==="
cat apps/hasura/migrations/masterbots/1765857892283_alter_table_public_preference_add_column_latest_organization/up.sql
echo -e "\n=== Checking down migration ==="
cat apps/hasura/migrations/masterbots/1765857892283_alter_table_public_preference_add_column_latest_organization/down.sql 2>/dev/null || echo "No down migration found"
echo -e "\n=== Looking at preference table definition ==="
find apps/hasura/migrations/masterbots -type f -name "*preference*" | while read f; do echo "File: $f"; cat "$f"; echo "---"; doneRepository: bitcashorg/masterbots
Length of output: 450
🏁 Script executed:
#!/bin/bash
# Check for unique constraints on organization table specifically
echo "=== Organization table creation ==="
cat apps/hasura/migrations/masterbots/1761768880712_create_table_public_organization/up.sql
echo -e "\n=== Check for any constraints added to organization.name ==="
find apps/hasura/migrations/masterbots -type f -name "*.sql" -exec rg -H "organization.*name|alter.*organization" -A3 -B1 {} \;
echo -e "\n=== Search for all columns named 'organization' to see common patterns ==="
find apps/hasura/migrations/masterbots -type f -name "*.sql" -exec rg -H "\"organization\"" {} \; | head -30
echo -e "\n=== Check if latest_organization is used elsewhere ==="
rg -r "latest_organization" apps/hasura/migrations/masterbotsRepository: bitcashorg/masterbots
Length of output: 3134
Use organization_id instead of storing organization by name.
The organization.name column has no UNIQUE constraint, making name-based references fragile. Additionally, the codebase follows the pattern of using organization_id (UUID) for organization relationships, as seen in the organization_chatbot table. Storing the latest organization as a text column without a foreign key constraint introduces data integrity risks.
Refactor to use an ID-based foreign key instead:
alter table "public"."preference" add column "latest_organization_id" uuid
null constraint fk_preference_latest_organization
references "public"."organization"("organization_id") on delete set null;🤖 Prompt for AI Agents
In
apps/hasura/migrations/masterbots/1765857892283_alter_table_public_preference_add_column_latest_organization/up.sql
around lines 1-2, the migration adds latest_organization as text which is
fragile and not referentially safe; replace this with a UUID foreign key column
latest_organization_id that references public.organization(organization_id) with
an ON DELETE SET NULL behavior and a named constraint (e.g.,
fk_preference_latest_organization), and remove or avoid adding the text column
so the preference table stores an organization_id relation consistent with the
rest of the schema.
apps/pro-web/app/(pro)/page.tsx
Outdated
| const { data: userPreferences } = await getUserPreferences( | ||
| session.user.id, | ||
| jwt, | ||
| ) | ||
| const latestOrganization = userPreferences?.latestOrganization | ||
| ? { | ||
| organizationData: { | ||
| organization: userPreferences.latestOrganization, | ||
| }, | ||
| } | ||
| : {} | ||
| const { threads, count } = await getThreads({ | ||
| jwt, | ||
| userId: session?.user.id, | ||
| limit: PAGE_SIZE, | ||
| ...latestOrganization, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing error handling for getUserPreferences.
The error returned by getUserPreferences is destructured but never used. If the preferences fetch fails, the error is silently ignored, and threads will load without organization filtering. Consider logging the error for debugging purposes.
Apply this diff to add error logging:
const { data: userPreferences, error } = await getUserPreferences(
session.user.id,
jwt,
)
+ if (error) {
+ console.error('Failed to fetch user preferences:', error)
+ }
const latestOrganization = userPreferences?.latestOrganizationConsider extracting repeated getUserPreferences logic.
This pattern of fetching user preferences and deriving latestOrganization is repeated across multiple page files. Consider extracting it into a shared helper function to reduce duplication and ensure consistent behavior.
📝 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.
| const { data: userPreferences } = await getUserPreferences( | |
| session.user.id, | |
| jwt, | |
| ) | |
| const latestOrganization = userPreferences?.latestOrganization | |
| ? { | |
| organizationData: { | |
| organization: userPreferences.latestOrganization, | |
| }, | |
| } | |
| : {} | |
| const { threads, count } = await getThreads({ | |
| jwt, | |
| userId: session?.user.id, | |
| limit: PAGE_SIZE, | |
| ...latestOrganization, | |
| const { data: userPreferences, error } = await getUserPreferences( | |
| session.user.id, | |
| jwt, | |
| ) | |
| if (error) { | |
| console.error('Failed to fetch user preferences:', error) | |
| } | |
| const latestOrganization = userPreferences?.latestOrganization | |
| ? { | |
| organizationData: { | |
| organization: userPreferences.latestOrganization, | |
| }, | |
| } | |
| : {} | |
| const { threads, count } = await getThreads({ | |
| jwt, | |
| userId: session?.user.id, | |
| limit: PAGE_SIZE, | |
| ...latestOrganization, |
There was a problem hiding this 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
🧹 Nitpick comments (1)
apps/hasura/metadata/databases/masterbots/tables/public_preference.yaml (1)
42-43: Minor:user_idappears redundant in columns list when usingset.The
setblock automatically populatesuser_idwithX-Hasura-User-Idduring insertion. Includinguser_idin thecolumnslist (line 54) may be unnecessary since the value is force-set and the user cannot override it.Consider removing
user_idfrom the columns list if it should always be auto-set:columns: - deep_expertise - favorite - font_size - lang - latest_organization - preferred_complexity - preferred_length - preferred_tone - preferred_type - - user_id - web_searchAlso applies to: 54-54
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
apps/hasura/metadata/databases/masterbots/tables/public_preference.yaml(6 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*
📄 CodeRabbit inference engine (CLAUDE.md)
**/*: Use lowercase with dash-separated directory and file names; use specific extensions: .config.ts, .test.ts, .context.tsx, .type.ts, .hook.ts; components may omit extension
Use Bun for package management and scripts instead of npm/yarn
Use Biome for formatting and linting via bun format-and-lint:fix; avoid Prettier/ESLint configs
Files:
apps/hasura/metadata/databases/masterbots/tables/public_preference.yaml
🧠 Learnings (1)
📓 Common learnings
Learnt from: AndlerRL
Repo: bitcashorg/masterbots PR: 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.
Learnt from: AndlerRL
Repo: bitcashorg/masterbots PR: 0
File: :0-0
Timestamp: 2025-11-07T18:41:56.562Z
Learning: Successfully created comprehensive GitHub issue #581 consolidating 16 unresolved observations from PR #579 review, including critical TDZ crash, migration bugs, transaction safety issues, and validation improvements, with specific file paths, line numbers, and action items organized by priority (4 critical, 8 major, 2 minor, 2 acknowledged for future PRs).
Learnt from: AndlerRL
Repo: bitcashorg/masterbots PR: 0
File: :0-0
Timestamp: 2025-11-21T19:47:50.195Z
Learning: For repo bitcashorg/masterbots (apps/pro-web), AndlerRL prefers integrating TanStack Query into React Context Hooks wherever they fit to manage shared server state and side effects, with a longer-term plan to align broader state management with TanStack Query and potentially replace React Context hooks. Captured from PR #584 discussion on Nov 21, 2025.
Learnt from: AndlerRL
Repo: bitcashorg/masterbots PR: 0
File: :0-0
Timestamp: 2025-09-04T21:31:18.830Z
Learning: The pro-web workspace system has identified performance bottlenecks in markdown parsing, AI streaming response handling, selection range management, Base64 document processing, and multiple debounced state operations that require optimization and better coordination.
Learnt from: derianrddev
Repo: bitcashorg/masterbots PR: 579
File: apps/hasura/migrations/masterbots/1762286346002_alter_table_public_chatbot_add_column_department_id/up.sql:1-2
Timestamp: 2025-11-06T22:31:20.903Z
Learning: In the masterbots codebase, the `department_id` column in the `public.chatbot` table is intentionally nullable because not all chatbots are part of the onboarding flow. Only chatbots used in onboarding have an associated department.
Learnt from: AndlerRL
Repo: bitcashorg/masterbots PR: 542
File: apps/web/components/layout/profile/layout.tsx:67-68
Timestamp: 2025-08-29T20:34:24.433Z
Learning: AndlerRL prefers skeleton loading states over conditional rendering for better UX when components are waiting for data to load. Components should show loading indicators rather than blank spaces.
🔇 Additional comments (2)
apps/hasura/metadata/databases/masterbots/tables/public_preference.yaml (2)
29-29: LGTM:latest_organizationcolumn consistently added across all permissions.The
latest_organizationfield is properly integrated into insert, select, and update permissions for both moderator and user roles, maintaining consistency with the schema migration.Also applies to: 49-49, 71-71, 88-88, 108-108, 125-125
96-98: Good security improvement: User select filter now properly scoped.Adding the
user_idequality filter ensures users can only query their own preferences, preventing unauthorized access to other users' preference data.
| - preferred_length | ||
| - preferred_tone | ||
| - preferred_type | ||
| - user_id |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Critical: Allowing user_id updates creates a security vulnerability.
Permitting user_id to be updated in both moderator and user roles allows preferences to be reassigned to different users. This could enable:
- Users modifying the
user_idon their preferences to associate them with other users - Potential privilege escalation or data corruption
- Breaking the integrity of user-preference ownership
The user_id foreign key should be immutable after creation.
Remove user_id from the updatable columns:
- role: moderator
permission:
columns:
- deep_expertise
- favorite
- font_size
- lang
- latest_organization
- preferred_complexity
- preferred_length
- preferred_tone
- preferred_type
- - user_id
- web_search - role: user
permission:
columns:
- deep_expertise
- favorite
- font_size
- lang
- latest_organization
- preferred_complexity
- preferred_length
- preferred_tone
- preferred_type
- - user_id
- web_searchAlso applies to: 130-130
🤖 Prompt for AI Agents
In apps/hasura/metadata/databases/masterbots/tables/public_preference.yaml
around lines 113 and 130, remove "user_id" from the list of updatable columns
for roles (both moderator and user) so the foreign key cannot be changed after
creation; ensure "user_id" remains allowed for inserts (creation) but is omitted
from any update permission blocks and related computed/update checks so
ownership cannot be reassigned.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (8)
apps/pro-web/lib/threads.ts (4)
62-66: Fix type signature for asyncactivatePopupparameter.The
activatePopupparameter is typed as(thread: Thread) => void, but on line 169 it's called withawait. IfactivatePopupperforms async operations, its return type should bePromise<void>.Apply this diff to correct the type signature:
export async function getOpeningActiveThreadHelper( currentActiveThread: Thread | null, sonner: ReturnType<typeof useSonner>['customSonner'], - activatePopup: (thread: Thread) => void, + activatePopup: (thread: Thread) => Promise<void>, ) {
77-116: Refactor pathname parsing to reduce fragility and improve maintainability.The current approach destructures
pathNamePartsfour times with different position assumptions for each route type. This is fragile because:
- All four destructurings operate on the same array regardless of the actual route type
- Changes to URL structure will break this silently
- It's unclear which destructuring is relevant at any given time
Consider refactoring to extract only the relevant parts based on the determined
routeType:let threadSlug = '' let questionSlug = '' switch (routeType) { case 'org': threadSlug = pathNameParts[5] questionSlug = pathNameParts[6] break case 'pro': threadSlug = pathNameParts[4] questionSlug = pathNameParts[5] break case 'bot': threadSlug = pathNameParts[3] questionSlug = pathNameParts[4] break case 'profile': threadSlug = pathNameParts[7] questionSlug = pathNameParts[8] break }This approach makes the URL structure assumptions explicit and reduces the risk of silent failures.
143-154: Add error handling forgetThreadcall.If
getThreadthrows an exception (e.g., network error, server error), the uncaught error will propagate and potentially crash the UI. Wrap the async operation in a try-catch block to handle errors gracefully.Apply this diff to add error handling:
- const thread = await getThread({ - threadSlug, - isPersonal, - }) + let thread: Thread | null = null + try { + thread = await getThread({ + threadSlug, + isPersonal, + }) + } catch (error) { + sonner({ + type: 'error', + text: 'Error loading the thread. Please try again.', + }) + return + } if (!thread) { sonner({ type: 'error', text: 'Error finding the thread that you were looking for.', }) return }
162-168: Remove or replace console.log statement.Console.log statements should not be present in production code. Either remove this logging or replace it with a proper logging mechanism.
Apply this diff:
- console.log( - 'scrolling to', - publicThreadQuestionSlug || - personalThreadQuestionSlug || - userProfileThreadQuestionSlug || - chatbotProfileThreadQuestionSlug, - ) await activatePopup(thread)apps/pro-web/components/routes/chat/chat-message-actions.tsx (2)
299-313: Add error handling for mutations.The
updateDocumentMutation.mutateAsyncandsaveDocumentMutation.mutateAsynccalls are not wrapped in try-catch blocks. If these mutations fail, it will cause an unhandled promise rejection and the user will still see the success message on line 317-320.Apply this diff to add error handling:
+ try { // TanStack Query mutation handles content update await updateDocumentMutation.mutateAsync({ project: activeProject, documentName: activeDocument, content: newContent, threadSlug: activeThread?.slug || null, }) await saveDocumentMutation.mutateAsync({ project: activeProject, documentName: activeDocument, content: newContent, documentType: activeDocumentType as WorkspaceDocumentType, organization: activeOrganization || '', department: activeDepartment ? activeDepartment[0] : undefined, threadSlug: activeThread?.slug, }) setActiveDocument(activeDocument) toggleWorkspace(true) customSonner({ type: 'success', text: 'Message added to active document!', }) + } catch (error) { + console.error('Error adding message to document:', error) + customSonner({ + type: 'error', + text: 'Failed to add message to document', + }) + }
75-79: Remove unuseduseTanstackfeature flag or implement conditional logic.The
useTanstackvariable is defined at line 76 but never referenced in the component. The mutations at lines 212 and 299 are called unconditionally despite comments claiming they should only execute when the flag is ON. Either remove the unused flag and misleading comments, or implement the intended conditional logic to branch between TanStack and legacy implementations.apps/pro-web/app/(pro)/[category]/[domain]/page.tsx (1)
36-36: Inconsistent department lookup in generateMetadata.Line 36 in
generateMetadatadoes not apply the same.toLowerCase()transformation used in the main component (line 24). This inconsistency could cause metadata generation to fail when the category parameter has uppercase characters.Apply the same case-insensitive matching:
const department = departments.find( - (department) => toSlug(department.name) === params.category, + (department) => toSlug(department.name) === params.category.toLowerCase(), )apps/pro-web/components/routes/workspace/workspace-breadcrumb.tsx (1)
71-87: Edge case:deptIdbecomes 0 when department name is not found.If
deptOptions.findIndex((d) => d === deptName)returns-1(not found), adding 1 results indeptId = 0. This could cause incorrect behavior downstream when comparing withactiveDepartment?.[1]or passing tochangeDepartment.Consider adding a guard or using a different approach:
const handleDepartmentSelect = (deptName: string) => { // Find department ID from the name - const deptId = deptOptions.findIndex((d) => d === deptName) + 1 + const deptIndex = deptOptions.findIndex((d) => d === deptName) + if (deptIndex === -1) { + console.warn(`Department "${deptName}" not found in options`) + return + } + const deptId = deptIndex + 1
♻️ Duplicate comments (1)
apps/pro-web/app/(pro)/page.tsx (1)
40-52: Type mismatch:departmentfield is missing from organizationData.The
ThreadOrganizationMetadatainterface requires all three fields (organization,department,project), but onlyorganizationandprojectare provided. The type assertionas ThreadOrganizationMetadatamasks this incompatibility.Based on the relevant code snippet from
hasura.service.ts(lines 349-510),getThreadsacceptsPartial<ThreadOrganizationMetadata>, so the assertion should reflect that:const latestOrganizationData = userPreferences?.latestOrganization || userPreferences?.latestProject ? { organizationData: { organization: userPreferences.latestOrganization, project: userPreferences.latestProject, - } as ThreadOrganizationMetadata, + } as Partial<ThreadOrganizationMetadata>, } : {}Additionally, the
errorfromgetUserPreferencesis destructured but not used. Consider logging it for debugging purposes.
🧹 Nitpick comments (15)
apps/pro-web/components/routes/pro/onboarding-panel.tsx (1)
30-30: Layout adjustment looks good.The reduced viewport height (84vh) and
mb-autoeffectively adjust the onboarding panel's vertical positioning. This aligns with the PR's goal to improve onboarding visibility.Optionally, consider extracting the magic numbers (84vh, 200px) as named constants or CSS variables to document their purpose and make future adjustments easier:
const ONBOARDING_CONTAINER_HEIGHT = 'calc(84vh - 200px)' // Accounts for header + footerThen use:
-<div className="flex justify-center items-center min-h-[calc(84vh-200px)] mb-auto"> +<div className={`flex justify-center items-center min-h-[${ONBOARDING_CONTAINER_HEIGHT}] mb-auto`}>However, this is purely for maintainability and can be deferred.
apps/web/components/routes/home/home-page.tsx (2)
44-44: LGTM – minor height adjustment for onboarding layout.The reduction from
100vhto94vhappears intentional for better vertical centering of the welcome message. Consider documenting why this specific value was chosen (e.g., accounting for header/footer height) if it addresses a specific layout constraint.
52-58: Extract inline arrow function to stable handler reference.The inline arrow function creates a new function instance on every render. Per coding guidelines, event handlers should use regular function references.
Apply this diff to extract the handler:
export default function HomePage({ initialThreads, initialCount, }: HomePageProps) { const { data: session, status } = useSession() const { selectedCategories, setSelectedCategories } = useSidebar() const [showOnboarding, setShowOnboarding] = useState(false) + function handleShowOnboarding() { + setShowOnboarding(true) + } + 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])<button type="button" - onClick={() => setShowOnboarding(true)} + onClick={handleShowOnboarding} className="px-6 py-3 text-white bg-green-600 rounded-lg transition-colors hover:bg-green-700" > Select Topics </button>As per coding guidelines for React event handlers.
apps/pro-web/lib/hooks/use-workspace-chat.tsx (1)
183-194: LGTM! Async control flow properly implemented.The changes correctly ensure that pending saves are fully cancelled before initiating a new save operation, and standardize the API to use TanStack Query's
mutateAsync. This prevents race conditions and aligns with the mutation patterns used elsewhere in the codebase.Consider wrapping these operations in a try-catch block for more explicit error handling:
if (shouldIncrementVersion) { - await saveDocumentMutation.cancelPendingSaves() - await saveDocumentMutation.mutateAsync({ - project: activeProject, - documentName: activeDocument, - department: activeDepartment?.[0], - organization: activeOrganization, - content, - documentType: - activeDocumentType === 'all' ? 'text' : activeDocumentType, - threadSlug: activeThread?.slug, - existingDoc: documentData, - }) + try { + await saveDocumentMutation.cancelPendingSaves() + await saveDocumentMutation.mutateAsync({ + project: activeProject, + documentName: activeDocument, + department: activeDepartment?.[0], + organization: activeOrganization, + content, + documentType: + activeDocumentType === 'all' ? 'text' : activeDocumentType, + threadSlug: activeThread?.slug, + existingDoc: documentData, + }) + } catch (error) { + console.error('❌ Failed to save document after streaming:', error) + customSonner({ + type: 'error', + text: 'Failed to save document changes', + }) + } }While
useAsyncwill catch errors, explicit handling provides better user feedback when autosave operations fail during streaming completion.apps/pro-web/lib/threads.ts (1)
155-161: Remove redundant condition checks.The conditions checking for
threadSlugorquestionSlugexistence are redundant because the function already performs early returns (lines 126-135) if these values don't exist. By the time execution reaches line 155, at least one of these values is guaranteed to exist for the active route type.Simplify the conditional:
- if ( - (isOrg && (publicThreadQuestionSlug || publicThreadSlug)) || - (isProfile && (userProfileThreadQuestionSlug || userProfileThreadSlug)) || - (isPersonal && (personalThreadQuestionSlug || personalThreadSlug)) || - (isBotProfile && - (chatbotProfileThreadQuestionSlug || chatbotProfileThreadSlug)) - ) { + if (isOrg || isProfile || isPersonal || isBotProfile) { console.log( 'scrolling to', publicThreadQuestionSlug || personalThreadQuestionSlug || userProfileThreadQuestionSlug || chatbotProfileThreadQuestionSlug, ) await activatePopup(thread) }packages/mb-drizzle/src/drizzle/schema.ts (1)
408-427: Consider adding a unique constraint on(name, userId).The PR objectives mention "Prevent creating organizations with duplicate names during workspace mutations." Without a database-level unique constraint on
(name, userId), this relies solely on application-layer validation which could be bypassed or have race conditions.export const organization = pgTable( 'organization', { organizationId: uuid('organization_id') .default(sql`uuid_generate_v4()`) .primaryKey() .notNull(), name: text().notNull(), userId: uuid('user_id').notNull(), }, (table) => [ foreignKey({ columns: [table.userId], foreignColumns: [user.userId], name: 'organization_user_id_fkey', }) .onUpdate('cascade') .onDelete('cascade'), + unique('organization_name_user_id_key').on(table.name, table.userId), ], )apps/pro-web/components/routes/chat/chat-message-actions.tsx (2)
18-21: Remove unused import.The
parseMarkdownSectionsimport is not used anywhere in this file.Apply this diff to remove the unused import:
import { createStructuredMarkdown, - parseMarkdownSections, } from '@/lib/markdown-utils'
283-283: Consider using consistent content duplication detection.Line 283 uses a simple substring check (
currentContent.includes(message.content)), while theisMessageOnDocumentlogic (lines 256-271) uses a more sophisticated approach withMIN_MATCH_LENGTHpartial matching.Consider extracting the sophisticated matching logic into a helper function and reusing it here for consistency:
const isContentInDocument = (content: string, documentContent: string): boolean => { const MIN_MATCH_LENGTH = 128 const normalizedContent = content.trim() if (!normalizedContent || !documentContent) return false if (documentContent.includes(normalizedContent)) return true for (let i = 0; i < normalizedContent.length - MIN_MATCH_LENGTH + 1; i++) { const part = normalizedContent.slice(i, i + MIN_MATCH_LENGTH) if (part.length >= MIN_MATCH_LENGTH && documentContent.includes(part)) return true } return false }Then use it at line 283:
- if (currentContent.includes(message.content)) { + if (isContentInDocument(message.content, currentContent)) {apps/pro-web/components/routes/workspace/workspace-content.tsx (1)
69-71: Double type assertion indicates potential type mismatch.The
payload as unknown as SaveDocumentInputcast bypasses TypeScript's type checking entirely. Thepayloadparameter is typed asRecord<string, unknown>, which may not matchSaveDocumentInputat runtime.Consider narrowing the type more safely:
case 'document-save': - saveDocumentMutation.mutate(payload as unknown as SaveDocumentInput) + if (payload && isSaveDocumentInput(payload)) { + saveDocumentMutation.mutate(payload) + } breakAlternatively, update the
handleTabMenuActionsignature to accept properly typed payloads for each action.apps/pro-web/app/(pro)/[category]/page.tsx (1)
10-14: Consider extracting repeatedgetUserPreferences+latestOrganizationDatalogic.This pattern of fetching user preferences and constructing
latestOrganizationDatais repeated acrosspage.tsx,[category]/page.tsx, and[category]/[domain]/[chatbot]/page.tsx. Extracting this into a shared helper would reduce duplication and ensure consistent behavior.apps/pro-web/app/(pro)/[category]/[domain]/[chatbot]/[threadSlug]/page.tsx (2)
65-78: Conditional check may not behave as intended when both fields are empty strings.The condition
userPreferences?.latestOrganization || userPreferences?.latestProjectwill be falsy if both are empty strings (""), but truthy if either contains any non-empty value. If the database stores empty strings instead ofnull, consider using explicit null checks.Additionally, destructuring only
datafromgetUserPreferencessilently ignores errors. Consider logging or handling the error case.- const { data: userPreferences } = await getUserPreferences( + const { data: userPreferences, error: preferencesError } = await getUserPreferences( userId, jwt as string, ) + if (preferencesError) { + console.warn('Failed to fetch user preferences:', preferencesError) + } const latestOrganizationData = - userPreferences?.latestOrganization || userPreferences?.latestProject + userPreferences?.latestOrganization != null || userPreferences?.latestProject != null ? {
72-76: Type assertion may mask undefined values.
ThreadOrganizationMetadataexpectsorganization,department, andprojectto be strings, butchatbot.department?.namecould beundefined. Theas ThreadOrganizationMetadataassertion bypasses TypeScript's check here.The
getThreadsfunction acceptsPartial<ThreadOrganizationMetadata>which handles undefined fields, so consider removing the assertion or using the partial type.organizationData: { organization: userPreferences.latestOrganization, department: chatbot.department?.name, project: userPreferences.latestProject, - } as ThreadOrganizationMetadata, + },apps/pro-web/components/routes/workspace/workspace-breadcrumb.tsx (1)
293-310: Consider extracting magic string'None'to a constant.The comparison
organization === 'None'uses a magic string. For maintainability, consider defining this as a constant if it's used elsewhere in the codebase.The
startTransitionusage for deferring popup and thread state updates is appropriate for keeping the UI responsive.apps/pro-web/components/routes/thread/user-thread-panel.tsx (2)
186-209: Remove commented-out code after verification.This block is marked with
TODO: RM after test latestOrganization preference implementation. If the new server-side filtering vialatestOrganizationpreference is confirmed working, this dead code should be removed to improve readability.
402-405: Remove debug logging before merge.The
console.log('newThreads + getThreadsParams', ...)statement appears to be debug code. Consider removing it or converting to a conditional debug flag to avoid noise in production logs.- console.log('newThreads + getThreadsParams', { - newThreads, - getThreadsParams, - })
| const { data: userPreferences } = await getUserPreferences( | ||
| userId, | ||
| jwt as string, | ||
| ) | ||
| const latestOrganizationData = | ||
| userPreferences?.latestOrganization || userPreferences?.latestProject | ||
| ? { | ||
| organizationData: { | ||
| organization: userPreferences.latestOrganization, | ||
| department: chatbot.department?.name, | ||
| project: userPreferences.latestProject, | ||
| } as ThreadOrganizationMetadata, | ||
| } | ||
| : {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Type assertion should use Partial<ThreadOrganizationMetadata>.
The chatbot.department?.name can be undefined, making the full ThreadOrganizationMetadata type assertion incorrect. The getThreads function accepts Partial<ThreadOrganizationMetadata>, so the type should reflect that:
const latestOrganizationData =
userPreferences?.latestOrganization || userPreferences?.latestProject
? {
organizationData: {
organization: userPreferences.latestOrganization,
department: chatbot.department?.name,
project: userPreferences.latestProject,
- } as ThreadOrganizationMetadata,
+ } as Partial<ThreadOrganizationMetadata>,
}
: {}This also applies the same error handling pattern suggested for the other page files—consider logging when getUserPreferences returns an error.
🤖 Prompt for AI Agents
In apps/pro-web/app/(pro)/[category]/[domain]/[chatbot]/page.tsx around lines 66
to 79, change the type assertion from ThreadOrganizationMetadata to
Partial<ThreadOrganizationMetadata> for latestOrganizationData because
chatbot.department?.name may be undefined; construct the object as
Partial<ThreadOrganizationMetadata> (or remove the assertion and let the object
infer the partial type) and pass that into getThreads, and also detect and log
an error when getUserPreferences returns an error (e.g., check for an error
result and use your logger to record it) before proceeding.
| const { data: userPreferences } = await getUserPreferences( | ||
| session.user.id, | ||
| jwt, | ||
| ) | ||
| const latestOrganizationData = | ||
| userPreferences?.latestOrganization || userPreferences?.latestProject | ||
| ? { | ||
| organizationData: { | ||
| organization: userPreferences.latestOrganization, | ||
| department: department?.name, | ||
| project: userPreferences.latestProject, | ||
| } as ThreadOrganizationMetadata, | ||
| } | ||
| : {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Type assertion should use Partial<ThreadOrganizationMetadata>.
Same issue as the other page files: department?.name can be undefined, and the getThreads function accepts Partial<ThreadOrganizationMetadata>:
const latestOrganizationData =
userPreferences?.latestOrganization || userPreferences?.latestProject
? {
organizationData: {
organization: userPreferences.latestOrganization,
department: department?.name,
project: userPreferences.latestProject,
- } as ThreadOrganizationMetadata,
+ } as Partial<ThreadOrganizationMetadata>,
}
: {}🤖 Prompt for AI Agents
In apps/pro-web/app/(pro)/[category]/page.tsx around lines 45 to 58, the inline
type assertion casts an object to ThreadOrganizationMetadata even though
department?.name can be undefined and getThreads expects
Partial<ThreadOrganizationMetadata>; change the assertion to
Partial<ThreadOrganizationMetadata> (or construct the object without a
full-cast) so the department property may be undefined safely and types align
with getThreads.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 13
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (10)
apps/pro-web/components/routes/preferences/preference-section.tsx (1)
247-260: Incorrect placement of console.error statement.The
console.erroron line 258 executes after the validation passes and the function returns early. This means it logs "Invalid username format" when the username is actually valid, which is backwards logic.Apply this diff to remove the misplaced error log:
if (value.length < 3 || value.length > 24 || !usernameRegex.test(value)) { setErrorMessage({ inputId, message: 'Username must be 3-24 characters long and can only contain letters, numbers, and underscores.', }) return } - console.error('Invalid username format:', value) - return } setErrorMessage({ inputId: '', message: '' })apps/pro-web/components/onboarding/steps/step-q4.tsx (1)
128-177: Potential state update on unmounted component and missing error handling.Two issues in
handleConfirm:
setLoading(false)is called afterprops.close(). Ifclose()unmounts the component, this triggers a state update on an unmounted component.- No error handling around
addOrganization()— if it throws,loadingstaystrueindefinitely and the wizard never closes.Consider wrapping in try/finally and reordering:
const handleConfirm = async () => { setLoading(true) + try { // Convert bot names to OrganizationChatbot format const organizationChatbots = recommendedBots.map((bot) => ({ chatbotId: Number(bot.id), isActive: data.selectedBots.includes(bot.name), })) // Save organization with chatbots to workspace (which persists to LocalStorage) if (data.orgName) { await addOrganization(data.orgName, organizationChatbots) setShouldRefreshThreads(true) if (activeThread) { setIsOpenPopup(false) setActiveThread(null) const chatbotName = activeChatbot?.name || '' navigateTo({ urlType: chatbotName ? 'chatbotThreadListUrl' : 'topicThreadListUrl', navigationParams: { type: 'pro', category: activeChatbot?.department?.name || '', domain: getCanonicalDomain(chatbotName), chatbot: chatbotName, }, shallow: true, }) } } console.log('Q4 confirmed', { category: data.selectedCategory, type: data.selectedType, subType: data.selectedSubType, orgName: data.orgName, selectedBots: data.selectedBots, organizationChatbots, }) - // Close the wizard - props.close() - setLoading(false) - // Redirect to signup only if user is not logged in if (!session) { router.push('/auth/signup') + } else { + props.close() } + } catch (error) { + console.error('Failed to confirm organization:', error) + } finally { + setLoading(false) } }apps/pro-web/components/routes/thread/thread-popup.tsx (2)
78-79: Update outdated comment.The comment references a feature flag that was removed in this PR. The mutation is now always used without conditional gating.
Apply this diff to update the comment:
- // TanStack mutation for document content updates (only used when flag is ON) + // TanStack mutation for document content updates const updateDocumentMutation = useUpdateDocumentContent()
169-169: Critical inconsistency: Line 169 still uses 'general' fallback.Line 169 uses
|| 'general'as the fallback, but lines 324 and 347 were changed to use|| ''(empty string) for the same property. This creates inconsistent department/category handling within the same file and contradicts the PR objective to avoid hardcoded department defaults.The
threadCategoryvariable is used in the URL builder at line 266, which could cause navigation mismatches with the updated navigation calls that now use empty strings.Apply this diff to align with the other changes:
- const threadCategory = currentThread?.chatbot.department?.name || 'general' + const threadCategory = currentThread?.chatbot.department?.name || ''apps/pro-web/components/routes/thread/user-thread-panel.tsx (4)
385-389: Potential bug:totalThreadscalculation appears incorrect when replacing threads.When
threadsis being replaced entirely withnewThreads(not appended),totalThreadsshould equalnewThreads.length, notthreads?.length + newThreads.length. The current calculation would incorrectly double-count threads.Based on learnings,
totalThreadsrepresents "the number of threads currently loaded into the UI" - so it should match the actual loaded array length.if (fetchIdRef.current === currentFetchId) { setState({ threads: newThreads, - totalThreads: threads?.length + newThreads.length, + totalThreads: newThreads.length, count, }) }
344-350: SametotalThreadscalculation issue in admin mode path.The
totalThreadsshould benewThreads.lengthsince threads are being replaced, not appended.setState({ threads: newThreads, - totalThreads: threads?.length + newThreads.length, + totalThreads: newThreads.length, count, })
462-466: SametotalThreadscalculation issue in search function.When replacing threads with search results,
totalThreadsshould reflect only the new results.setState({ threads: moreThreads?.threads || [], count: moreThreads?.count || 0, - totalThreads: threads.length + (moreThreads?.threads?.length || 0), + totalThreads: moreThreads?.threads?.length || 0, })
189-189: Remove debug console.log.- console.log('🟡 Loading More Content')apps/pro-web/lib/hooks/use-workspace-document-content-hook.ts (1)
370-451: Consider improving threadSlug handling and mutation rollback safety.The function has several areas for improvement:
Redundant threadSlug declaration (Line 393): The guard at line 372 already checks
!activeThread?.slug, so the localthreadSlugvariable at line 393 could still be undefined due to optional chaining. Consider using a type assertion or direct reference.Partial rollback state: The three async operations (lines 394-414, 415-421, 422-431) are chained without transaction semantics. If
saveDocumentMutationfails afterupdateDocumentMutationsucceeds, the document will be updated in the query cache but not fully persisted, creating inconsistent state.Apply this diff to improve safety:
const handleRollback = useCallback( async (versionNumber: number) => { - if (!projectName || !documentName || !activeThread?.slug) return + const threadSlug = activeThread?.slug + if (!projectName || !documentName || !threadSlug) return + try { const versionToRollback = versions.find( (v) => v.version === versionNumber, ) if (!versionToRollback || !versionToRollback.url) { customSonner({ type: 'error', text: 'Version content not found.' }) return } const response = await fetch(versionToRollback.url) if (!response.ok) { customSonner({ type: 'error', text: 'Failed to fetch version content.', }) return } const newContent = await response.text() const updated = versions.map((v) => v) - const threadSlug = activeThread?.slug await updateThreadDocumentsMetadata({ threadSlug, documents: [ { id: nanoid(16), url: versionToRollback.url, content: versionToRollback.content, expires: new Date( Date.now() + 7 * 24 * 60 * 60 * 1000, ).toISOString(), threadSlug, organization: activeOrganization as string, department: activeDepartment?.[0] as string, project: projectName, name: documentName, type: documentType, currentVersion: versionNumber, versions: updated, } as WorkspaceDocumentMetadata, ], }) + + // Update document content and save in sequence const { newDocument } = await updateDocumentMutation.mutateAsync({ project: projectName, documentName: documentName, content: newContent, threadSlug, version: versionNumber, }) + + // Save the rolled-back document await saveDocumentMutation.mutateAsync({ project: projectName, documentName: documentName, content: newContent, documentType: documentType as WorkspaceDocumentType, organization: activeOrganization || '', department: activeDepartment ? activeDepartment[0] : undefined, threadSlug, existingDoc: newDocument, }) + customSonner({ type: 'success', text: `Rolled back to version ${versionNumber}.`, }) } catch (e) { console.error('Rollback failed', e) - customSonner({ type: 'error', text: 'Rollback failed.' }) + customSonner({ + type: 'error', + text: `Rollback failed: ${e instanceof Error ? e.message : 'Unknown error'}` + }) } },Note: Consider implementing a rollback mechanism or optimistic updates if partial failures are a concern in production.
apps/pro-web/lib/hooks/use-workspace-chat.tsx (1)
150-214: Strengthen data validation and add error handling for streaming completion.The streaming completion block has several concerns:
Weak content validation (Line 162): The check
!contentwill be true for empty string'', which is a valid (albeit empty) document state. This may not be the intended behavior.Missing error handling for mutations (Lines 173-194): The chained
updateDocumentMutationandsaveDocumentMutationcalls lack try-catch blocks. If either fails, the streaming state will be inconsistent and the user won't see an error message.Unsafe navigation parameters (Lines 178, 204-205):
activeChatbot?.nameis used without null checks. If undefined, this will create malformed URLs.Apply this diff to improve safety:
if (!newAssistantMessage) { // Message is null - streaming has completed if (streamingStateRef.current.isStreaming && !isLoading) { console.log('✅ Streaming completed - incrementing version') - // Increment version on streaming completion when using TanStack - const content = documentData?.content + + const content = documentData?.content ?? '' if ( !activeOrganization || !activeProject || !activeDocument || - !content + content === undefined ) { console.warn('Missing required data for streaming completion') return } streamingStateRef.current.isStreaming = false streamingStateRef.current.streamStarted = false streamingActiveSectionRef.current = null - const threadSlug = activeThread?.slug - const { newDocument } = await updateDocumentMutation.mutateAsync({ - project: activeProject, - documentName: activeDocument, - content, - threadSlug, - chatbot: activeChatbot?.name, - incrementVersion: true, // Create new version on stream completion - }) - - await saveDocumentMutation.cancelPendingSaves() - const { saved, document } = await saveDocumentMutation.mutateAsync({ - project: activeProject, - documentName: activeDocument, - department: activeDepartment?.[0], - organization: activeOrganization, - content, - documentType: - activeDocumentType === 'all' ? 'text' : activeDocumentType, - threadSlug, - chatbot: activeChatbot?.name, - existingDoc: newDocument, - }) - - if (document?.threadSlug && saved) { - // When document is saved successfully, update thread URL if there is a threadSlug - // ? This is however, colliding with another pathname update on an useEffect hence, it returns to a previous activeThread slug... - // ? Making the pathname confusion BUT on saving, it is actually saving with the new document slug. - navigateTo({ - urlType: 'threadUrl', - navigationParams: { - type: 'pro', - category: activeChatbot?.department?.name || '', - domain: getCanonicalDomain(activeChatbot?.name || ''), - chatbot: activeChatbot?.name || '', - threadSlug: document.threadSlug, - }, - shallow: true, - }) + try { + const threadSlug = activeThread?.slug + const chatbotName = activeChatbot?.name + + const { newDocument } = await updateDocumentMutation.mutateAsync({ + project: activeProject, + documentName: activeDocument, + content, + threadSlug, + chatbot: chatbotName, + incrementVersion: true, + }) + + await saveDocumentMutation.cancelPendingSaves() + const { saved, document } = await saveDocumentMutation.mutateAsync({ + project: activeProject, + documentName: activeDocument, + department: activeDepartment?.[0], + organization: activeOrganization, + content, + documentType: + activeDocumentType === 'all' ? 'text' : activeDocumentType, + threadSlug, + chatbot: chatbotName, + existingDoc: newDocument, + }) + + if (document?.threadSlug && saved && chatbotName) { + navigateTo({ + urlType: 'threadUrl', + navigationParams: { + type: 'pro', + category: activeChatbot?.department?.name || '', + domain: getCanonicalDomain(chatbotName), + chatbot: chatbotName, + threadSlug: document.threadSlug, + }, + shallow: true, + }) + } + } catch (error) { + console.error('❌ Failed to save document after streaming:', error) + customSonner({ + type: 'error', + text: `Failed to save document: ${error instanceof Error ? error.message : 'Unknown error'}`, + }) } } return }
🧹 Nitpick comments (8)
apps/pro-web/components/routes/preferences/preference-section.tsx (1)
395-397: Remove debug console.log statement.Debug logging statements should be removed before merging to production.
Apply this diff:
- console.log({ - preferences, - }) - return (apps/pro-web/app/api/test/encrypt/route.ts (1)
72-91: CRC implementation may be incorrect for binary data.The CRC32 calculation operates on string characters using
charCodeAt, which treats each character as a UTF-16 code unit. For binary data or base64 strings, CRC32 should operate on byte arrays to produce correct checksums.If CRC32 is needed for document integrity validation, use a byte-based implementation:
function calculateCRC(data: Uint8Array) { const polynomial = 0xedb88320 let crc = 0xffffffff for (let i = 0; i < data.length; i++) { crc ^= data[i] for (let j = 0; j < 8; j++) { crc = (crc >>> 1) ^ (crc & 1 ? polynomial : 0) } } return crc ^ 0xffffffff }Then convert your string to bytes before calling:
const bytes = new TextEncoder().encode(data) const crcValue = calculateCRC(bytes)apps/pro-web/components/routes/thread/user-thread-panel.tsx (2)
51-51: Unused import:ThreadOrganizationMetadatais not referenced in this file.The type is imported but the
organizationDataobjects are constructed inline without explicit type annotations. Consider removing this import if it's not needed, or explicitly type theorganizationDataobjects for better type safety.-import type { ThreadOrganizationMetadata } from '@/types/thread.types'Alternatively, use the type explicitly:
const organizationData: Partial<ThreadOrganizationMetadata> = { organization: activeOrganization || undefined, department: activeWorkspaceDepartment?.[0], project: activeProject || undefined, }
519-521: Consider accessibility implications of usingh-0to hide content.Using
h-0to hide the container may cause screen readers to still announce the element. Consider usinghiddenclass or conditional rendering for better accessibility when the list should not be shown.<ul - className={cn('transition-all flex flex-col size-full gap-3 pb-36', { - 'items-center justify-center': showChatbotDetails || showNoResults, - 'pb-0 h-0': !activeOrganization && !organizationList?.length, - })} + className={cn('transition-all flex flex-col size-full gap-3 pb-36', { + 'items-center justify-center': showChatbotDetails || showNoResults, + hidden: !activeOrganization && !organizationList?.length, + })} >apps/pro-web/components/routes/chat/chat-message-actions.tsx (1)
18-21: Remove unused imports.Both
parseMarkdownSectionsandcreateStructuredMarkdownare imported but never used in this file. The code now uses raw message content directly instead of generating structured markdown.Apply this diff to remove the unused imports:
-import { - createStructuredMarkdown, - parseMarkdownSections, -} from '@/lib/markdown-utils'apps/pro-web/lib/queries/use-workspace-mutations.ts (1)
220-228: Consider extracting URL parsing logic for reusability.The department derivation from URL pathname is duplicated logic. The pattern of splitting the pathname and matching against
DEPARTMENT_SLUG_MAPcould be extracted into a reusable utility function, improving maintainability.// Consider extracting to a utility: function getDepartmentFromPathname(): [string, number] | null { const [, departmentParam] = window?.location?.pathname?.split('/') || [] if (!departmentParam) return null return DEPARTMENT_SLUG_MAP[departmentParam.toLowerCase()] || null }apps/pro-web/lib/hooks/use-workspace-document-content-hook.ts (1)
114-198: Consider adding error handling around the save mutation.The
saveDocumentContentfunction performs validation and waits for document readiness, but the mutation call at line 183 lacks error handling. If the mutation fails, the error will propagate to the caller without logging or user feedback.Apply this diff to add error handling:
// Wait for document to be ready const isReady = await waitForDocumentReady() if (!isReady) { console.error( '❌ Failed to save: document queries did not settle in time', ) return } // Get existing document from workspace structure for version continuity const workspaceStructure = queryClient.getQueryData<{ documents: WorkspaceDocumentMetadata[] }>(workspaceKeys.structure(session?.user?.id || 'anonymous')) const existingDoc = workspaceStructure?.documents?.find( (doc: WorkspaceDocumentMetadata) => doc.name === documentName && doc.project === projectName, ) const updatedDocument = { project: projectName, documentName: documentName, content: newMd, documentType: documentType, organization: activeOrganization || undefined, department: activeDepartment?.[0], threadSlug: activeThread?.slug || undefined, existingDoc: existingDoc || undefined, } // Throttle the actual mutation to avoid excessive saves - await saveDocumentMutation.cancelPendingSaves() - await saveDocumentMutation.mutateAsync(updatedDocument) + try { + await saveDocumentMutation.cancelPendingSaves() + await saveDocumentMutation.mutateAsync(updatedDocument) + } catch (error) { + console.error('❌ Failed to save document content:', error) + // Optionally show user feedback via customSonner + } },apps/pro-web/components/routes/workspace/workspace-breadcrumb.tsx (1)
305-339: Consider defensive access to projectsByDept.At lines 308-310, the code accesses
projectsByDept[organization]?.[activeDepartment?.[0]]which assumesprojectsByDeptis defined. While the workspace context should provide this, adding a defensive check would make the code more robust.Apply this diff:
onSelect={(organization) => { if (organization === activeOrganization) return - const projectsByOrg = activeDepartment?.[0] - ? projectsByDept[organization]?.[activeDepartment?.[0]] - : [] + const projectsByOrg = + activeDepartment?.[0] && projectsByDept + ? projectsByDept[organization]?.[activeDepartment?.[0]] || [] + : [] setActiveOrganization( organization === 'None' ? null : organization, ) setActiveProject(projectsByOrg?.[0] || null) setActiveDocument(null) setShouldRefreshThreads(true)Regarding startTransition usage (Line 334): The use of
startTransitionto defersetIsOpenPopupandsetActiveThreadupdates is appropriate, as these are non-urgent UI updates that should not block the navigation. ✅
| import crypto from 'node:crypto' | ||
| import { computeChecksum } from '@/lib/checksum' | ||
| import { decodeBase64Content } from '@/lib/utils' | ||
|
|
||
| export async function POST(request: Request) { | ||
| const data = | ||
| 'data:text/markdown;base64,IyBVbnRpdGxlZCBEb2N1bWVudAoKKipQcm9qZWN0OioqIFByb2plY3QgMQoKIyMgT3ZlcnZpZXcKIyMjIFB1cnBvc2UKVGhpcyBkb2N1bWVudCBwcm92aWRlcyBhIGNvbmNpc2UsIGF1dGhvcml0YXRpdmUgcmVmZXJlbmNlIGZvciBQcm9qZWN0IDE6IGl0cyByYXRpb25hbGUsIGludGVuZGVkIG91dGNvbWVzLCBhbmQgaG93IHN0YWtlaG9sZGVycyBzaG91bGQgdXNlIHRoZSBtYXRlcmlhbC4gSXQgY2xhcmlmaWVzIHdoeSB0aGUgcHJvamVjdCBleGlzdHMsIHRoZSBwcm9ibGVtcyBpdCBhZGRyZXNzZXMsIGFuZCB0aGUgZGVjaXNpb25zIG9yIGFjdGlvbnMgdGhlIGRvY3VtZW50IGlzIGludGVuZGVkIHRvIGluZm9ybS4gVGhlIGdvYWwgaXMgdG8gYWxpZ24gYWxsIHJlYWRlcnMgb24gY29udGV4dCBhbmQgZXhwZWN0YXRpb25zIGJlZm9yZSB0aGV5IHByb2NlZWQgdG8gdGhlIGRldGFpbGVkIHNlY3Rpb25zLgoKIyMjIFNjb3BlClRoaXMgb3ZlcnZpZXcgZGVmaW5lcyB0aGUgYm91bmRhcmllcyBvZiB0aGUgcHJvamVjdCBhbmQgdGhlIGRvY3VtZW50LiBJdCBzdW1tYXJpemVzOgotIFdoYXQgaXMgaW5jbHVkZWQgKHByaW1hcnkgb2JqZWN0aXZlcywgY29yZSBkZWxpdmVyYWJsZXMsIG1ham9yIG1pbGVzdG9uZXMsIGFuZCBwcmluY2lwYWwgY29uc3RyYWludHMpLgotIFdoYXQgaXMgZXhjbHVkZWQgKG91dC1vZi1zY29wZSBhY3Rpdml0aWVzLCBhc3N1bXB0aW9ucyB0aGF0IHdpbGwgbm90IGJlIHZhbGlkYXRlZCBoZXJlKS4KLSBUaGUgdGltZSBob3Jpem9uIGNvdmVyZWQgKHBoYXNlcyBhbmQgYXBwcm94aW1hdGUgZHVyYXRpb25zKSBhbmQgdGhlIG9yZ2FuaXphdGlvbmFsIHVuaXRzIHByaW1hcmlseSBpbnZvbHZlZC4KCkJlaW5nIGV4cGxpY2l0IGFib3V0IHNjb3BlIGhlbHBzIHByZXZlbnQgc2NvcGUgY3JlZXAgYW5kIGVuc3VyZXMgcmVhZGVycyB1bmRlcnN0YW5kIHdoZXJlIHRvIGZvY3VzIHRoZWlyIGF0dGVudGlvbi4KCiMjIyBUYXJnZXQgYXVkaWVuY2UKVGhpcyBkb2N1bWVudCBpcyB3cml0dGVuIGZvciBzdGFrZWhvbGRlcnMgd2hvIG5lZWQgYSBjbGVhciwgYWN0aW9uYWJsZSB1bmRlcnN0YW5kaW5nIG9mIFByb2plY3QgMSwgaW5jbHVkaW5nOgotIFByb2plY3Qgc3BvbnNvcnMgYW5kIGV4ZWN1dGl2ZSBzdGFrZWhvbGRlcnMgKGZvciBzdHJhdGVnaWMgYWxpZ25tZW50KS4KLSBQcm9qZWN0IG1hbmFnZXJzIGFuZCB0ZWFtIGxlYWRzIChmb3IgcGxhbm5pbmcgYW5kIGV4ZWN1dGlvbikuCi0gRnVuY3Rpb25hbCBjb250cmlidXRvcnMgYW5kIHN1YmplY3QtbWF0dGVyIGV4cGVydHMgKGZvciBpbnB1dCBhbmQgaW1wbGVtZW50YXRpb24pLgotIEV4dGVybmFsIHBhcnRuZXJzIG9yIHZlbmRvcnMgKGZvciBjb250cmFjdHVhbCBhbGlnbm1lbnQgd2hlcmUgYXBwbGljYWJsZSkuCgpFYWNoIHN1YnNlcXVlbnQgc2VjdGlvbiBjb250YWlucyB0aGUgbGV2ZWwgb2YgZGV0YWlsIG5lZWRlZCBieSB0aGVzZSBncm91cHM7IHJlYWRlcnMgc2hvdWxkIHJldmlldyB0aGUgcGFydHMgbW9zdCByZWxldmFudCB0byB0aGVpciByb2xlLgoKIyMjIEJhY2tncm91bmQgYW5kIGNvbnRleHQKUHJvdmlkZSBhIGJyaWVmIGhpc3RvcnkgYW5kIHNpdHVhdGlvbmFsIGNvbnRleHQgdGhhdCBsZWQgdG8gaW5pdGlhdGluZyBQcm9qZWN0IDE6Ci0gVGhlIGJ1c2luZXNzIGRyaXZlcnMgb3IgcHJvYmxlbXMgdGhlIHByb2plY3QgYWRkcmVzc2VzIChlLmcuLCBvcGVyYXRpb25hbCBpbmVmZmljaWVuY3ksIHJlZ3VsYXRvcnkgcmVxdWlyZW1lbnQsIG1hcmtldCBvcHBvcnR1bml0eSkuCi0gUmVsZXZhbnQgcHJpb3Igd29yaywgZGVjaXNpb25zLCBvciBkYXRhIHRoYXQgc2hhcGVkIHRoZSBwcm9qZWN04oCZcyBzY29wZS4KLSBLZXkgY29uc3RyYWludHMgb3IgZGVwZW5kZW5jaWVzICh0ZWNobm9sb2d5LCBidWRnZXQsIHJlZ3VsYXRvcnkgdGltZWxpbmVzLCBjcm9zcy10ZWFtIGRlcGVuZGVuY2llcykuCgpUaGlzIGNvbnRleHQgZW5zdXJlcyBkb3duc3RyZWFtIGRlY2lzaW9ucyBhcmUgaW5mb3JtZWQgYnkgdGhlIGFudGVjZWRlbnQgY29uZGl0aW9ucyBhbmQgY29uc3RyYWludHMuCgojIyMgT2JqZWN0aXZlcyBhbmQgc3VjY2VzcyBjcml0ZXJpYQpMaXN0IHRoZSBwcmltYXJ5IG9iamVjdGl2ZXMgdGhlIHByb2plY3Qgc2Vla3MgdG8gYWNoaWV2ZSBhbmQgdGhlIG1lYXN1cmFibGUgc3VjY2VzcyBjcml0ZXJpYToKLSBTdHJhdGVnaWMgb2JqZWN0aXZlcyAoZS5nLiwgaW1wcm92ZSBwcm9jZXNzIFggYnkgWSUsIHJlZHVjZSBjb3N0IGJ5IFolLCBlbmFibGUgZmVhdHVyZSBBKS4KLSBNZWFzdXJhYmxlIEtQSXMgYW5kIHRhcmdldCB0aHJlc2hvbGRzLgotIFF1YWxpdGF0aXZlIG91dGNvbWVzIChjdXN0b21lciBleHBlcmllbmNlIGltcHJvdmVtZW50cywgcmlzayByZWR1Y3Rpb24pLgpTdWNjZXNzIGNyaXRlcmlhIHByb3ZpZGUgYSBjbGVhciB3YXkgdG8gZXZhbHVhdGUgd2hldGhlciB0aGUgcHJvamVjdCBoYXMgbWV0IGV4cGVjdGF0aW9ucyBhdCBlYWNoIG1pbGVzdG9uZSBhbmQgYXQgY2xvc2VvdXQuCgojIyMgRXhwZWN0ZWQgZGVsaXZlcmFibGVzIGFuZCBvdXRjb21lcwpTdW1tYXJpemUgdGhlIG1haW4gb3V0cHV0cyBzdGFrZWhvbGRlcnMgc2hvdWxkIGV4cGVjdCBmcm9tIHRoZSBwcm9qZWN0IGFuZCBmcm9tIHRoaXMgZG9jdW1lbnQ6Ci0gVGFuZ2libGUgZGVsaXZlcmFibGVzIChyZXBvcnRzLCB0ZWNobmljYWwgY29tcG9uZW50cywgcHJvY2VzcyBkZXNpZ25zLCB0cmFpbmluZyBtYXRlcmlhbHMpLgotIEltcGxlbWVudGF0aW9uIG9yIHRyYW5zaXRpb24gYXJ0aWZhY3RzIChkZXBsb3ltZW50IHBsYW5zLCBoYW5kb3ZlciBjaGVja2xpc3RzKS4KLSBFeHBlY3RlZCBiZW5lZml0cyByZWFsaXphdGlvbiB0aW1lbGluZSAod2hlbiB0aGUgb3JnYW5pemF0aW9uIHNob3VsZCBleHBlY3QgdG8gc2VlIG1lYXN1cmVkIGltcHJvdmVtZW50cykuCiMjIyBIb3cgdG8gdXNlIHRoaXMgZG9jdW1lbnQKRXhwbGFpbiB0aGUgcmVjb21tZW5kZWQgcmVhZGluZyBhcHByb2FjaDoKLSBSZWFkIHRoZSBPdmVydmlldyBhbmQgRGV0YWlscyBzZXF1ZW50aWFsbHkgZm9yIGZ1bGwgY29udGV4dC4KLSBVc2UgdGhlIERldGFpbHMgc2VjdGlvbiBmb3IgaW1wbGVtZW50YXRpb24tbGV2ZWwgZ3VpZGFuY2U7IHVzZSBDb25jbHVzaW9uIGZvciBuZXh0IHN0ZXBzIGFuZCBkZWNpc2lvbiBhY3Rpb25zLgotIFJlZmVyZW5jZSByZWxldmFudCBhcHBlbmRpY2VzIG9yIGxpbmtlZCBhcnRpZmFjdHMgZm9yIGRhdGEsIHRlbXBsYXRlcywgb3IgdGVjaG5pY2FsIHNwZWNpZmljYXRpb25zLgotIFByb3ZpZGUgYSBwb2ludCBvZiBjb250YWN0IGZvciBjbGFyaWZpY2F0aW9ucyBhbmQgd2hlcmUgdG8gcmVjb3JkIGZlZWRiYWNrIG9yIGNoYW5nZSByZXF1ZXN0cy4KCiMjIyBBc3N1bXB0aW9ucyBhbmQgcmlza3MgKGhpZ2ggbGV2ZWwpCkhpZ2hsaWdodCB0aGUgcHJpbmNpcGFsIGFzc3VtcHRpb25zIHRoYXQgdW5kZXJwaW4gdGhlIGRvY3VtZW50IGFuZCB0aGUgbW9zdCBzaWduaWZpY2FudCByaXNrczoKLSBDb3JlIGFzc3VtcHRpb25zIChyZXNvdXJjZSBhdmFpbGFiaWxpdHksIHN0YWtlaG9sZGVyIGVuZ2FnZW1lbnQsIGRhdGEgcXVhbGl0eSkuCi0gSGlnaC1sZXZlbCByaXNrcyB0aGF0IGNvdWxkIGFmZmVjdCBzY29wZSwgc2NoZWR1bGUsIG9yIG91dGNvbWVzIChkZXBlbmRlbmNpZXMgb24gZXh0ZXJuYWwgdGVhbXMsIHJlZ3VsYXRvcnkgdGltZWxpbmVzLCB0ZWNobm9sb2d5IHJlYWRpbmVzcykuClRoaXMgc2VjdGlvbiBvcmllbnRzIHJlYWRlcnMgdG8gdW5jZXJ0YWludGllcyB0aGF0IHNob3VsZCBiZSBtb25pdG9yZWQgYW5kIG1hbmFnZWQuCgojIyMgUmV2aXNpb24gYW5kIGdvdmVybmFuY2UKRGVzY3JpYmUgaG93IHRoZSBkb2N1bWVudCB3aWxsIGJlIG1haW50YWluZWQgYW5kIGdvdmVybmVkOgotIE93bmVyKHMpIHJlc3BvbnNpYmxlIGZvciB1cGRhdGVzLgotIFJldmlldyBjYWRlbmNlIChlLmcuLCBhZnRlciBtYWpvciBtaWxlc3RvbmVzIG9yIG1vbnRobHkpLgotIENoYW5nZSBjb250cm9sIGV4cGVjdGF0aW9ucyAoaG93IHRvIHN1Ym1pdCBhbmQgYXBwcm92ZSBjaGFuZ2VzKS4KCiMjIERldGFpbHMKRGV0YWlsZWQgY29udGVudCBnb2VzIGhlcmUuCgojIyBDb25jbHVzaW9uClN1bW1hcnkgYW5kIG5leHQgc3RlcHMuCg==' | ||
| const decryptedData = decodeBase64Content(data) | ||
| const rawSha256Value = calculateSHA256(data) | ||
| const rawMd5Value = calculateMD5(data) | ||
| const rawCrcValue = calculateCRC(data) | ||
| const rawBasicChecksum = computeChecksum(data) | ||
| const decryptedSha256Value = calculateSHA256(decryptedData) | ||
| const decryptedMd5Value = calculateMD5(decryptedData) | ||
| const decryptedCrcValue = calculateCRC(decryptedData) | ||
| const decryptedBasicChecksum = computeChecksum(decryptedData) | ||
|
|
||
| console.log(`MD5 Checksum: ${rawMd5Value}`) | ||
| console.log(`CRC Checksum: ${rawCrcValue}`) | ||
| console.log(`SHA-256 Checksum: ${rawSha256Value}`) | ||
| console.log(`Basic Checksum: ${rawBasicChecksum}`) | ||
|
|
||
| console.log(`MD5 Checksum [decrypted]: ${decryptedMd5Value}`) | ||
| console.log(`CRC Checksum [decrypted]: ${decryptedCrcValue}`) | ||
| console.log(`SHA-256 Checksum [decrypted]: ${decryptedSha256Value}`) | ||
| console.log(`Basic Checksum [decrypted]: ${decryptedBasicChecksum}`) | ||
|
|
||
| console.log({ | ||
| rawSize: new Blob([decryptedData]).size, | ||
| basicDecryptedSize: new Blob([decryptedBasicChecksum.toString()]).size, | ||
| crcDecryptedSize: new Blob([decryptedCrcValue.toString()]).size, | ||
| md5DecryptedSize: new Blob([decryptedMd5Value.toString()]).size, | ||
| sha256DecryptedSize: new Blob([decryptedSha256Value.toString()]).size, | ||
|
|
||
| base64Size: new Blob([data]).size, | ||
| basicBase64Size: new Blob([rawBasicChecksum.toString()]).size, | ||
| crcBase64Size: new Blob([rawCrcValue.toString()]).size, | ||
| md5Base64Size: new Blob([rawMd5Value.toString()]).size, | ||
| sha256Base64Size: new Blob([rawSha256Value.toString()]).size, | ||
| }) | ||
|
|
||
| return new Response(null, { status: 200 }) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove test/debug endpoint from production code.
This test route exposes debugging functionality without authentication and is unrelated to the PR's workspace navigation objectives. Test endpoints should not exist in production code paths.
Issues:
- Public endpoint at
/api/test/encryptwith no authentication or rate limiting. - Uses deprecated MD5 hashing algorithm.
- Logs debug information to server console.
- Hardcoded test data; doesn't use the request body.
- Returns null response—serves no production purpose.
- Completely out of scope for a PR focused on workspace navigation and metadata flow.
If checksum validation is needed for the workspace/document features, integrate it properly with authentication and move utilities to a shared library. Otherwise, remove this file entirely.
| export async function POST(request: Request) { | ||
| const data = | ||
| 'data:text/markdown;base64,IyBVbnRpdGxlZCBEb2N1bWVudAoKKipQcm9qZWN0OioqIFByb2plY3QgMQoKIyMgT3ZlcnZpZXcKIyMjIFB1cnBvc2UKVGhpcyBkb2N1bWVudCBwcm92aWRlcyBhIGNvbmNpc2UsIGF1dGhvcml0YXRpdmUgcmVmZXJlbmNlIGZvciBQcm9qZWN0IDE6IGl0cyByYXRpb25hbGUsIGludGVuZGVkIG91dGNvbWVzLCBhbmQgaG93IHN0YWtlaG9sZGVycyBzaG91bGQgdXNlIHRoZSBtYXRlcmlhbC4gSXQgY2xhcmlmaWVzIHdoeSB0aGUgcHJvamVjdCBleGlzdHMsIHRoZSBwcm9ibGVtcyBpdCBhZGRyZXNzZXMsIGFuZCB0aGUgZGVjaXNpb25zIG9yIGFjdGlvbnMgdGhlIGRvY3VtZW50IGlzIGludGVuZGVkIHRvIGluZm9ybS4gVGhlIGdvYWwgaXMgdG8gYWxpZ24gYWxsIHJlYWRlcnMgb24gY29udGV4dCBhbmQgZXhwZWN0YXRpb25zIGJlZm9yZSB0aGV5IHByb2NlZWQgdG8gdGhlIGRldGFpbGVkIHNlY3Rpb25zLgoKIyMjIFNjb3BlClRoaXMgb3ZlcnZpZXcgZGVmaW5lcyB0aGUgYm91bmRhcmllcyBvZiB0aGUgcHJvamVjdCBhbmQgdGhlIGRvY3VtZW50LiBJdCBzdW1tYXJpemVzOgotIFdoYXQgaXMgaW5jbHVkZWQgKHByaW1hcnkgb2JqZWN0aXZlcywgY29yZSBkZWxpdmVyYWJsZXMsIG1ham9yIG1pbGVzdG9uZXMsIGFuZCBwcmluY2lwYWwgY29uc3RyYWludHMpLgotIFdoYXQgaXMgZXhjbHVkZWQgKG91dC1vZi1zY29wZSBhY3Rpdml0aWVzLCBhc3N1bXB0aW9ucyB0aGF0IHdpbGwgbm90IGJlIHZhbGlkYXRlZCBoZXJlKS4KLSBUaGUgdGltZSBob3Jpem9uIGNvdmVyZWQgKHBoYXNlcyBhbmQgYXBwcm94aW1hdGUgZHVyYXRpb25zKSBhbmQgdGhlIG9yZ2FuaXphdGlvbmFsIHVuaXRzIHByaW1hcmlseSBpbnZvbHZlZC4KCkJlaW5nIGV4cGxpY2l0IGFib3V0IHNjb3BlIGhlbHBzIHByZXZlbnQgc2NvcGUgY3JlZXAgYW5kIGVuc3VyZXMgcmVhZGVycyB1bmRlcnN0YW5kIHdoZXJlIHRvIGZvY3VzIHRoZWlyIGF0dGVudGlvbi4KCiMjIyBUYXJnZXQgYXVkaWVuY2UKVGhpcyBkb2N1bWVudCBpcyB3cml0dGVuIGZvciBzdGFrZWhvbGRlcnMgd2hvIG5lZWQgYSBjbGVhciwgYWN0aW9uYWJsZSB1bmRlcnN0YW5kaW5nIG9mIFByb2plY3QgMSwgaW5jbHVkaW5nOgotIFByb2plY3Qgc3BvbnNvcnMgYW5kIGV4ZWN1dGl2ZSBzdGFrZWhvbGRlcnMgKGZvciBzdHJhdGVnaWMgYWxpZ25tZW50KS4KLSBQcm9qZWN0IG1hbmFnZXJzIGFuZCB0ZWFtIGxlYWRzIChmb3IgcGxhbm5pbmcgYW5kIGV4ZWN1dGlvbikuCi0gRnVuY3Rpb25hbCBjb250cmlidXRvcnMgYW5kIHN1YmplY3QtbWF0dGVyIGV4cGVydHMgKGZvciBpbnB1dCBhbmQgaW1wbGVtZW50YXRpb24pLgotIEV4dGVybmFsIHBhcnRuZXJzIG9yIHZlbmRvcnMgKGZvciBjb250cmFjdHVhbCBhbGlnbm1lbnQgd2hlcmUgYXBwbGljYWJsZSkuCgpFYWNoIHN1YnNlcXVlbnQgc2VjdGlvbiBjb250YWlucyB0aGUgbGV2ZWwgb2YgZGV0YWlsIG5lZWRlZCBieSB0aGVzZSBncm91cHM7IHJlYWRlcnMgc2hvdWxkIHJldmlldyB0aGUgcGFydHMgbW9zdCByZWxldmFudCB0byB0aGVpciByb2xlLgoKIyMjIEJhY2tncm91bmQgYW5kIGNvbnRleHQKUHJvdmlkZSBhIGJyaWVmIGhpc3RvcnkgYW5kIHNpdHVhdGlvbmFsIGNvbnRleHQgdGhhdCBsZWQgdG8gaW5pdGlhdGluZyBQcm9qZWN0IDE6Ci0gVGhlIGJ1c2luZXNzIGRyaXZlcnMgb3IgcHJvYmxlbXMgdGhlIHByb2plY3QgYWRkcmVzc2VzIChlLmcuLCBvcGVyYXRpb25hbCBpbmVmZmljaWVuY3ksIHJlZ3VsYXRvcnkgcmVxdWlyZW1lbnQsIG1hcmtldCBvcHBvcnR1bml0eSkuCi0gUmVsZXZhbnQgcHJpb3Igd29yaywgZGVjaXNpb25zLCBvciBkYXRhIHRoYXQgc2hhcGVkIHRoZSBwcm9qZWN04oCZcyBzY29wZS4KLSBLZXkgY29uc3RyYWludHMgb3IgZGVwZW5kZW5jaWVzICh0ZWNobm9sb2d5LCBidWRnZXQsIHJlZ3VsYXRvcnkgdGltZWxpbmVzLCBjcm9zcy10ZWFtIGRlcGVuZGVuY2llcykuCgpUaGlzIGNvbnRleHQgZW5zdXJlcyBkb3duc3RyZWFtIGRlY2lzaW9ucyBhcmUgaW5mb3JtZWQgYnkgdGhlIGFudGVjZWRlbnQgY29uZGl0aW9ucyBhbmQgY29uc3RyYWludHMuCgojIyMgT2JqZWN0aXZlcyBhbmQgc3VjY2VzcyBjcml0ZXJpYQpMaXN0IHRoZSBwcmltYXJ5IG9iamVjdGl2ZXMgdGhlIHByb2plY3Qgc2Vla3MgdG8gYWNoaWV2ZSBhbmQgdGhlIG1lYXN1cmFibGUgc3VjY2VzcyBjcml0ZXJpYToKLSBTdHJhdGVnaWMgb2JqZWN0aXZlcyAoZS5nLiwgaW1wcm92ZSBwcm9jZXNzIFggYnkgWSUsIHJlZHVjZSBjb3N0IGJ5IFolLCBlbmFibGUgZmVhdHVyZSBBKS4KLSBNZWFzdXJhYmxlIEtQSXMgYW5kIHRhcmdldCB0aHJlc2hvbGRzLgotIFF1YWxpdGF0aXZlIG91dGNvbWVzIChjdXN0b21lciBleHBlcmllbmNlIGltcHJvdmVtZW50cywgcmlzayByZWR1Y3Rpb24pLgpTdWNjZXNzIGNyaXRlcmlhIHByb3ZpZGUgYSBjbGVhciB3YXkgdG8gZXZhbHVhdGUgd2hldGhlciB0aGUgcHJvamVjdCBoYXMgbWV0IGV4cGVjdGF0aW9ucyBhdCBlYWNoIG1pbGVzdG9uZSBhbmQgYXQgY2xvc2VvdXQuCgojIyMgRXhwZWN0ZWQgZGVsaXZlcmFibGVzIGFuZCBvdXRjb21lcwpTdW1tYXJpemUgdGhlIG1haW4gb3V0cHV0cyBzdGFrZWhvbGRlcnMgc2hvdWxkIGV4cGVjdCBmcm9tIHRoZSBwcm9qZWN0IGFuZCBmcm9tIHRoaXMgZG9jdW1lbnQ6Ci0gVGFuZ2libGUgZGVsaXZlcmFibGVzIChyZXBvcnRzLCB0ZWNobmljYWwgY29tcG9uZW50cywgcHJvY2VzcyBkZXNpZ25zLCB0cmFpbmluZyBtYXRlcmlhbHMpLgotIEltcGxlbWVudGF0aW9uIG9yIHRyYW5zaXRpb24gYXJ0aWZhY3RzIChkZXBsb3ltZW50IHBsYW5zLCBoYW5kb3ZlciBjaGVja2xpc3RzKS4KLSBFeHBlY3RlZCBiZW5lZml0cyByZWFsaXphdGlvbiB0aW1lbGluZSAod2hlbiB0aGUgb3JnYW5pemF0aW9uIHNob3VsZCBleHBlY3QgdG8gc2VlIG1lYXN1cmVkIGltcHJvdmVtZW50cykuCiMjIyBIb3cgdG8gdXNlIHRoaXMgZG9jdW1lbnQKRXhwbGFpbiB0aGUgcmVjb21tZW5kZWQgcmVhZGluZyBhcHByb2FjaDoKLSBSZWFkIHRoZSBPdmVydmlldyBhbmQgRGV0YWlscyBzZXF1ZW50aWFsbHkgZm9yIGZ1bGwgY29udGV4dC4KLSBVc2UgdGhlIERldGFpbHMgc2VjdGlvbiBmb3IgaW1wbGVtZW50YXRpb24tbGV2ZWwgZ3VpZGFuY2U7IHVzZSBDb25jbHVzaW9uIGZvciBuZXh0IHN0ZXBzIGFuZCBkZWNpc2lvbiBhY3Rpb25zLgotIFJlZmVyZW5jZSByZWxldmFudCBhcHBlbmRpY2VzIG9yIGxpbmtlZCBhcnRpZmFjdHMgZm9yIGRhdGEsIHRlbXBsYXRlcywgb3IgdGVjaG5pY2FsIHNwZWNpZmljYXRpb25zLgotIFByb3ZpZGUgYSBwb2ludCBvZiBjb250YWN0IGZvciBjbGFyaWZpY2F0aW9ucyBhbmQgd2hlcmUgdG8gcmVjb3JkIGZlZWRiYWNrIG9yIGNoYW5nZSByZXF1ZXN0cy4KCiMjIyBBc3N1bXB0aW9ucyBhbmQgcmlza3MgKGhpZ2ggbGV2ZWwpCkhpZ2hsaWdodCB0aGUgcHJpbmNpcGFsIGFzc3VtcHRpb25zIHRoYXQgdW5kZXJwaW4gdGhlIGRvY3VtZW50IGFuZCB0aGUgbW9zdCBzaWduaWZpY2FudCByaXNrczoKLSBDb3JlIGFzc3VtcHRpb25zIChyZXNvdXJjZSBhdmFpbGFiaWxpdHksIHN0YWtlaG9sZGVyIGVuZ2FnZW1lbnQsIGRhdGEgcXVhbGl0eSkuCi0gSGlnaC1sZXZlbCByaXNrcyB0aGF0IGNvdWxkIGFmZmVjdCBzY29wZSwgc2NoZWR1bGUsIG9yIG91dGNvbWVzIChkZXBlbmRlbmNpZXMgb24gZXh0ZXJuYWwgdGVhbXMsIHJlZ3VsYXRvcnkgdGltZWxpbmVzLCB0ZWNobm9sb2d5IHJlYWRpbmVzcykuClRoaXMgc2VjdGlvbiBvcmllbnRzIHJlYWRlcnMgdG8gdW5jZXJ0YWludGllcyB0aGF0IHNob3VsZCBiZSBtb25pdG9yZWQgYW5kIG1hbmFnZWQuCgojIyMgUmV2aXNpb24gYW5kIGdvdmVybmFuY2UKRGVzY3JpYmUgaG93IHRoZSBkb2N1bWVudCB3aWxsIGJlIG1haW50YWluZWQgYW5kIGdvdmVybmVkOgotIE93bmVyKHMpIHJlc3BvbnNpYmxlIGZvciB1cGRhdGVzLgotIFJldmlldyBjYWRlbmNlIChlLmcuLCBhZnRlciBtYWpvciBtaWxlc3RvbmVzIG9yIG1vbnRobHkpLgotIENoYW5nZSBjb250cm9sIGV4cGVjdGF0aW9ucyAoaG93IHRvIHN1Ym1pdCBhbmQgYXBwcm92ZSBjaGFuZ2VzKS4KCiMjIERldGFpbHMKRGV0YWlsZWQgY29udGVudCBnb2VzIGhlcmUuCgojIyBDb25jbHVzaW9uClN1bW1hcnkgYW5kIG5leHQgc3RlcHMuCg==' | ||
| const decryptedData = decodeBase64Content(data) | ||
| const rawSha256Value = calculateSHA256(data) | ||
| const rawMd5Value = calculateMD5(data) | ||
| const rawCrcValue = calculateCRC(data) | ||
| const rawBasicChecksum = computeChecksum(data) | ||
| const decryptedSha256Value = calculateSHA256(decryptedData) | ||
| const decryptedMd5Value = calculateMD5(decryptedData) | ||
| const decryptedCrcValue = calculateCRC(decryptedData) | ||
| const decryptedBasicChecksum = computeChecksum(decryptedData) | ||
|
|
||
| console.log(`MD5 Checksum: ${rawMd5Value}`) | ||
| console.log(`CRC Checksum: ${rawCrcValue}`) | ||
| console.log(`SHA-256 Checksum: ${rawSha256Value}`) | ||
| console.log(`Basic Checksum: ${rawBasicChecksum}`) | ||
|
|
||
| console.log(`MD5 Checksum [decrypted]: ${decryptedMd5Value}`) | ||
| console.log(`CRC Checksum [decrypted]: ${decryptedCrcValue}`) | ||
| console.log(`SHA-256 Checksum [decrypted]: ${decryptedSha256Value}`) | ||
| console.log(`Basic Checksum [decrypted]: ${decryptedBasicChecksum}`) | ||
|
|
||
| console.log({ | ||
| rawSize: new Blob([decryptedData]).size, | ||
| basicDecryptedSize: new Blob([decryptedBasicChecksum.toString()]).size, | ||
| crcDecryptedSize: new Blob([decryptedCrcValue.toString()]).size, | ||
| md5DecryptedSize: new Blob([decryptedMd5Value.toString()]).size, | ||
| sha256DecryptedSize: new Blob([decryptedSha256Value.toString()]).size, | ||
|
|
||
| base64Size: new Blob([data]).size, | ||
| basicBase64Size: new Blob([rawBasicChecksum.toString()]).size, | ||
| crcBase64Size: new Blob([rawCrcValue.toString()]).size, | ||
| md5Base64Size: new Blob([rawMd5Value.toString()]).size, | ||
| sha256Base64Size: new Blob([rawSha256Value.toString()]).size, | ||
| }) | ||
|
|
||
| return new Response(null, { status: 200 }) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
POST handler ignores request and uses hardcoded data.
The handler accepts a Request parameter but never uses it. All processing is done on hardcoded base64 data, making this a non-functional API endpoint.
Additional issues:
- Route name "encrypt" is misleading—this decodes/decrypts data.
- No error handling for
decodeBase64Contentor checksum calculations. - Console logging for debugging rather than returning structured data.
- Returns null body with HTTP 200.
If this is for testing checksum algorithms, it should be in a test file (e.g., __tests__/checksum.test.ts), not a production API route.
🤖 Prompt for AI Agents
In apps/pro-web/app/api/test/encrypt/route.ts around lines 5 to 43, the POST
handler ignores the incoming Request and uses hardcoded base64 data (and returns
null), which breaks the API and mixes testing code into a route; change the
handler to read and validate the request body (expecting base64 content or a
JSON payload with a data field), replace the hardcoded data with the supplied
input, perform decode/checksum operations with try/catch around
decodeBase64Content and each checksum call to return 4xx for bad input or 5xx
for processing errors, return a structured JSON response containing the
decrypted data metadata and checksum results (no console.debug logs), and if
this file is only for algorithm tests move the logic into a proper test under
__tests__/checksum.test.ts and keep the API route minimal or rename the route to
reflect decoding if it must exist.
| function calculateSHA256(data: crypto.BinaryLike) { | ||
| // Create a SHA-256 hash object | ||
| const hash = crypto.createHash('sha256') | ||
|
|
||
| // Update the hash object with the data | ||
| hash.update(data) | ||
|
|
||
| // Generate the SHA-256 checksum | ||
| // in hexadecimal format | ||
| return hash.digest('hex') | ||
| } | ||
|
|
||
| // Calculate an MD5 checksum for the | ||
| // given data using Node.js's crypto library | ||
| function calculateMD5(data: crypto.BinaryLike) { | ||
| // Create an MD5 hash object | ||
| const hash = crypto.createHash('md5') | ||
|
|
||
| // Update the hash object with the data | ||
| hash.update(data) | ||
|
|
||
| // Generate the MD5 checksum in hexadecimal format | ||
| return hash.digest('hex') | ||
| } | ||
|
|
||
| function calculateCRC(data: string) { | ||
| const polynomial = 0xedb88320 | ||
| let crc = 0xffffffff | ||
|
|
||
| // Iterate through each character in the data | ||
| for (let i = 0; i < data.length; i++) { | ||
| // XOR the current character | ||
| // with the current CRC value | ||
| crc ^= data.charCodeAt(i) | ||
|
|
||
| // Perform bitwise operations | ||
| // to calculate the new CRC value | ||
| for (let j = 0; j < 8; j++) { | ||
| crc = (crc >>> 1) ^ (crc & 1 ? polynomial : 0) | ||
| } | ||
| } | ||
|
|
||
| // Perform a final XOR operation and return the CRC value | ||
| return crc ^ 0xffffffff | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
Extract checksum utilities and avoid deprecated MD5.
These checksum functions are defined inline but should be in a shared utility module (e.g., lib/checksum.ts). You already import computeChecksum from @/lib/checksum—these functions belong alongside it.
Concerns:
- MD5 is cryptographically broken and should not be used for security purposes. If you need cryptographic hashing, use SHA-256 exclusively.
- The
calculateCRCimplementation operates on string characters (charCodeAt), which may produce incorrect results for binary data. CRC32 should operate on byte arrays.
If these checksum algorithms are needed for document validation or integrity checks in workspace features, extract them to lib/checksum.ts with proper binary handling and remove MD5. Otherwise, delete this experimental code.
| const threadSlug = activeThread?.slug | ||
| const { newDocument } = await updateDocumentMutation.mutateAsync({ | ||
| project: activeProject, | ||
| documentName: activeDocument, | ||
| content: newContent, | ||
| threadSlug: activeThread?.slug || null, | ||
| threadSlug, | ||
| }) | ||
| await saveDocumentMutation.saveImmediately({ | ||
| await saveDocumentMutation.mutateAsync({ | ||
| project: activeProject, | ||
| documentName: activeDocument, | ||
| content: newContent, | ||
| documentType: activeDocumentType as WorkspaceDocumentType, | ||
| organization: activeOrganization || '', | ||
| department: activeDepartment ? activeDepartment[0] : undefined, | ||
| threadSlug: activeThread?.slug, | ||
| threadSlug, | ||
| existingDoc: newDocument, | ||
| }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add error handling for async mutations.
The mutateAsync calls are not wrapped in a try-catch block, which means errors will go unhandled and the user won't receive feedback if the mutations fail. This is inconsistent with the onCreateDocumentWorkspace function (lines 199-247), which properly wraps similar mutations in try-catch.
Apply this diff to add proper error handling:
- const threadSlug = activeThread?.slug
- const { newDocument } = await updateDocumentMutation.mutateAsync({
- project: activeProject,
- documentName: activeDocument,
- content: newContent,
- threadSlug,
- })
- await saveDocumentMutation.mutateAsync({
- project: activeProject,
- documentName: activeDocument,
- content: newContent,
- documentType: activeDocumentType as WorkspaceDocumentType,
- organization: activeOrganization || '',
- department: activeDepartment ? activeDepartment[0] : undefined,
- threadSlug,
- existingDoc: newDocument,
- })
- setActiveDocument(activeDocument)
- toggleWorkspace(true)
-
- customSonner({
- type: 'success',
- text: 'Message added to active document!',
- })
+ try {
+ const threadSlug = activeThread?.slug
+ const { newDocument } = await updateDocumentMutation.mutateAsync({
+ project: activeProject,
+ documentName: activeDocument,
+ content: newContent,
+ threadSlug,
+ })
+ await saveDocumentMutation.mutateAsync({
+ project: activeProject,
+ documentName: activeDocument,
+ content: newContent,
+ documentType: activeDocumentType as WorkspaceDocumentType,
+ organization: activeOrganization || '',
+ department: activeDepartment ? activeDepartment[0] : undefined,
+ threadSlug,
+ existingDoc: newDocument,
+ })
+ setActiveDocument(activeDocument)
+ toggleWorkspace(true)
+
+ customSonner({
+ type: 'success',
+ text: 'Message added to active document!',
+ })
+ } catch (error) {
+ console.error('Error adding message to document:', error)
+ customSonner({
+ type: 'error',
+ text: 'Failed to add message to document',
+ })
+ }📝 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.
| const threadSlug = activeThread?.slug | |
| const { newDocument } = await updateDocumentMutation.mutateAsync({ | |
| project: activeProject, | |
| documentName: activeDocument, | |
| content: newContent, | |
| threadSlug: activeThread?.slug || null, | |
| threadSlug, | |
| }) | |
| await saveDocumentMutation.saveImmediately({ | |
| await saveDocumentMutation.mutateAsync({ | |
| project: activeProject, | |
| documentName: activeDocument, | |
| content: newContent, | |
| documentType: activeDocumentType as WorkspaceDocumentType, | |
| organization: activeOrganization || '', | |
| department: activeDepartment ? activeDepartment[0] : undefined, | |
| threadSlug: activeThread?.slug, | |
| threadSlug, | |
| existingDoc: newDocument, | |
| }) | |
| try { | |
| const threadSlug = activeThread?.slug | |
| const { newDocument } = await updateDocumentMutation.mutateAsync({ | |
| project: activeProject, | |
| documentName: activeDocument, | |
| content: newContent, | |
| threadSlug, | |
| }) | |
| await saveDocumentMutation.mutateAsync({ | |
| project: activeProject, | |
| documentName: activeDocument, | |
| content: newContent, | |
| documentType: activeDocumentType as WorkspaceDocumentType, | |
| organization: activeOrganization || '', | |
| department: activeDepartment ? activeDepartment[0] : undefined, | |
| threadSlug, | |
| existingDoc: newDocument, | |
| }) | |
| setActiveDocument(activeDocument) | |
| toggleWorkspace(true) | |
| customSonner({ | |
| type: 'success', | |
| text: 'Message added to active document!', | |
| }) | |
| } catch (error) { | |
| console.error('Error adding message to document:', error) | |
| customSonner({ | |
| type: 'error', | |
| text: 'Failed to add message to document', | |
| }) | |
| } |
🤖 Prompt for AI Agents
In apps/pro-web/components/routes/chat/chat-message-actions.tsx around lines 296
to 312, the two mutateAsync calls (updateDocumentMutation.mutateAsync and
saveDocumentMutation.mutateAsync) are not wrapped in error handling; wrap both
calls in a try-catch block, await them inside the try, and in the catch log the
error and surface user feedback (e.g., call the same toast/error handler used in
onCreateDocumentWorkspace) and abort further processing or return early; ensure
any resources/state updates that must be reverted on failure are handled in the
catch as well.
| console.log('newThreads + getThreadsParams', { | ||
| newThreads, | ||
| getThreadsParams, | ||
| }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
Remove debug console.log before merging.
Debug logging should be removed for production code.
- console.log('newThreads + getThreadsParams', {
- newThreads,
- getThreadsParams,
- })📝 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.
| console.log('newThreads + getThreadsParams', { | |
| newThreads, | |
| getThreadsParams, | |
| }) |
🤖 Prompt for AI Agents
In apps/pro-web/components/routes/thread/user-thread-panel.tsx around lines 377
to 380, remove the temporary debug console.log statement ("newThreads +
getThreadsParams" ...) before merging; replace it with no logging or, if runtime
diagnostics are required, use the existing app logging utility at appropriate
log level (e.g., logger.debug) and ensure sensitive data is not emitted.
| existingDoc, | ||
| } = input | ||
|
|
||
| console.log('is there a thread slug when saving?', threadSlug) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
Remove debug console.log statement.
This debug log should be removed as the file already uses the logWorkspace() function for structured logging, and the hasThreadSlug information is already logged on line 121.
Apply this diff to remove the debug statement:
- console.log('is there a thread slug when saving?', threadSlug)
-📝 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.
| console.log('is there a thread slug when saving?', threadSlug) |
🤖 Prompt for AI Agents
In apps/pro-web/lib/queries/use-save-document.ts around line 115, remove the
debug console.log('is there a thread slug when saving?', threadSlug) statement
because structured logging via logWorkspace() already records hasThreadSlug on
line 121; delete that console.log line and ensure no leftover unused
variables/imports remain, then run lint/format to confirm.
|
|
||
| if (userId && session?.user) { | ||
| await savePreferences(userId, session?.user.hasuraJwt, { | ||
| latestOrganization: name, | ||
| }) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Preference saving order may cause inconsistency on partial failure.
The savePreferences call happens before the IDB updateItem (line 179). If the IDB save fails after preferences are saved to the server, the state becomes inconsistent. Consider either:
- Moving preference saving to
onSettledafter successful mutation - Wrapping both operations in a try-catch with proper rollback
+ // Save to IDB first (local source of truth)
await updateItem(WORKSPACE_STRUCTURE_KEY, payload)
+ // Then persist preferences to server (secondary)
if (userId && session?.user) {
await savePreferences(userId, session?.user.hasuraJwt, {
latestOrganization: name,
})
}
- await updateItem(WORKSPACE_STRUCTURE_KEY, payload)Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In apps/pro-web/lib/queries/use-workspace-mutations.ts around lines 173 to 178,
savePreferences is called before the IDB update which can leave server prefs
updated while local IDB fails; move the savePreferences call out of the
pre-mutation path and instead invoke it in the mutation's onSettled/onSuccess
callback after the IDB update completes successfully, or alternatively wrap both
the server preference save and the IDB update in a single try-catch and perform
a rollback (delete or revert server prefs) if the second operation fails; update
the code to ensure atomicity by choosing one approach and handling errors
accordingly.
| onMutate: async ( | ||
| { project, name, type = 'text', organization, department, template }, | ||
| { meta }, | ||
| ) => { | ||
| console.log('useAddDocument-onMutate', { meta }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
context.meta is undefined - potential bug.
The onMutate callback accesses context.meta (line 174), but context in the onMutate signature is just an object containing meta from mutation options. However, context.meta here appears to reference mutation context, which isn't available in onMutate. The second parameter of onMutate is { meta } from the mutation call itself, not a context object.
- onMutate: async (
- { project, name, type = 'text', organization, department, template },
- { meta },
- ) => {
- console.log('useAddDocument-onMutate', { meta })
+ onMutate: async ({
+ project,
+ name,
+ type = 'text',
+ organization,
+ department,
+ template,
+ }) => {🤖 Prompt for AI Agents
In apps/pro-web/lib/queries/use-workspace-mutations.ts around lines 439 to 443,
the onMutate handler is incorrectly treating the second parameter as a context
object and referencing context.meta; change the handler to use the destructured
meta directly (i.e., use the `{ meta }` parameter you already receive) and
replace any uses of `context.meta` with `meta`; if other code expects a
mutation-context object, update the signature/usage consistently or add a
properly-scoped context return instead of referencing a non-existent
`context.meta`.
| if (userId && session?.user) { | ||
| await savePreferences(userId, session?.user.hasuraJwt, { | ||
| latestOrganization: newStructure?.activeOrganization || null, | ||
| latestProject: newStructure?.activeProject || null, | ||
| }) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Async savePreferences in onMutate blocks optimistic updates and risks inconsistency.
Calling await savePreferences() inside onMutate blocks the optimistic update until the server responds, defeating the purpose of optimistic updates. Additionally, if savePreferences succeeds but the mutationFn fails, the server preferences won't be rolled back by onError.
Consider moving preference persistence to onSuccess or onSettled:
onMutate: async ({
organization,
department,
project,
document,
activeTab,
}) => {
// ... cancel queries and optimistic update ...
- if (userId && session?.user) {
- await savePreferences(userId, session?.user.hasuraJwt, {
- latestOrganization: newStructure?.activeOrganization || null,
- latestProject: newStructure?.activeProject || null,
- })
- }
-
return { previousStructure }
},
+ onSuccess: async (data) => {
+ if (userId && session?.user) {
+ await savePreferences(userId, session?.user.hasuraJwt, {
+ latestOrganization: data?.activeOrganization || null,
+ latestProject: data?.activeProject || null,
+ })
+ }
+ },🤖 Prompt for AI Agents
In apps/pro-web/lib/queries/use-workspace-mutations.ts around lines 691-696, do
not await savePreferences inside onMutate because that blocks optimistic updates
and prevents rollback on mutation failure; remove the await and the call from
onMutate (or convert it to a non-blocking fire-and-forget if you must persist
immediately), and instead invoke savePreferences in onSuccess (or onSettled)
using the final/newStructure and session to persist server state; also implement
onError to restore previous preferences (call savePreferences with the previous
values) if you want server-side rollback when the mutation fails.
| currentChecksum && base64Content && fileSize | ||
| ? [ | ||
| { | ||
| version: 1, | ||
| checksum: currentChecksum, | ||
| content: base64Content, | ||
| url: base64Content, | ||
| size: fileSize, | ||
| updatedAt, | ||
| }, | ||
| ] | ||
| : [], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Empty versions array when fileSize is 0.
The condition currentChecksum && base64Content && fileSize (line 956) will be falsy when fileSize is 0, resulting in an empty versions array. A document with empty content would have fileSize === 0, which is a valid case.
versions:
- currentChecksum && base64Content && fileSize
+ currentChecksum && base64Content && fileSize !== undefined
? [
{
version: 1,Or more explicitly:
versions:
- currentChecksum && base64Content && fileSize
+ currentChecksum && base64Content && typeof fileSize === 'number'📝 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.
| currentChecksum && base64Content && fileSize | |
| ? [ | |
| { | |
| version: 1, | |
| checksum: currentChecksum, | |
| content: base64Content, | |
| url: base64Content, | |
| size: fileSize, | |
| updatedAt, | |
| }, | |
| ] | |
| : [], | |
| currentChecksum && base64Content && fileSize !== undefined | |
| ? [ | |
| { | |
| version: 1, | |
| checksum: currentChecksum, | |
| content: base64Content, | |
| url: base64Content, | |
| size: fileSize, | |
| updatedAt, | |
| }, | |
| ] | |
| : [], |
🤖 Prompt for AI Agents
In apps/pro-web/lib/queries/use-workspace-mutations.ts around lines 956 to 967,
the ternary uses a truthy check `currentChecksum && base64Content && fileSize`
which treats fileSize===0 as falsy and yields an empty versions array; change
the condition to explicitly allow zero-size files (for example check that
currentChecksum and base64Content are present and that fileSize is a finite
number >= 0, or at minimum fileSize !== null && fileSize !== undefined) so a
document with fileSize === 0 produces the single version object rather than an
empty array.
|
@merivercap @derianrddev @sheriffjimoh — this PR is fixing core issues with the workspace navigation and a few enhancements around the feature. Make sure to take the changes from develop thus there might be conflicts with your changes on your active branches. @coderabbitai revise the team PRs and recently created branches to verify any possible conflict to make action to. This PR is making fixes in the pro-web and hasura app. Provide any additional indication if necessary |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: Length of output: 12032 @AndlerRL I've analyzed the open PRs and branches for potential conflicts with PR #598. Here's what I found: 🚨 High Priority ConflictsPR #599 (@sheriffjimoh - "User N8N workflows") - HIGH RISKConflicting files:
Impact: Both PRs modify Hasura metadata for chatbot and preference tables. PR #598 adds department relationships and organization_chatbots to chatbot table, plus latest_organization/latest_project fields to preference. PR #599 modifies these same tables for workflow integration. Recommendation: @sheriffjimoh should rebase on develop after #598 merges and regenerate GenQL types. The preference table changes will require careful merge. PR #597 (@derianrddev - "Brand kit wizard") - HIGH RISKConflicting files:
Impact:
Recommendation: @derianrddev should rebase after #598 merges. Pay special attention to:
|
Summary by Sourcery
Improve workspace-aware thread loading, filtering, and navigation across the Pro web app.
Bug Fixes:
Enhancements:
Summary by CodeRabbit
Bug Fixes
New Features
Refactor
Chores
✏️ Tip: You can customize this high-level summary in your review settings.