Skip to content
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
79a58ef
fix: dept from path guard
AndlerRL Dec 2, 2025
da775f9
fix: workspace structure chk missing project in dept
AndlerRL Dec 2, 2025
235fe8e
Merge branch 'develop' into bugfix/workspace-structure
AndlerRL Dec 2, 2025
a2f3b80
fix: department n threads navigation
AndlerRL Dec 3, 2025
133cb74
fix: workspace active navigation persistent state
AndlerRL Dec 4, 2025
2c5d35f
style: header component clean up
AndlerRL Dec 4, 2025
1598bb3
fix: css build
AndlerRL Dec 4, 2025
379cc6b
fix: new proj among dept + thread refresh & filter
AndlerRL Dec 9, 2025
0a34914
chore: upt nextjs ver
AndlerRL Dec 9, 2025
47b7139
chore: upt react ver
AndlerRL Dec 9, 2025
264fe8c
Merge branch 'develop' into bugfix/workspace-structure
AndlerRL Dec 9, 2025
559553a
chore: rm legacy component
AndlerRL Dec 9, 2025
4aee8b7
chore: rm legacy component
AndlerRL Dec 9, 2025
29b9479
chore: rm legacy files
AndlerRL Dec 9, 2025
7778cb6
fix: project list, org list, thread list filter
AndlerRL Dec 11, 2025
8c81d60
Merge branch 'develop' into bugfix/workspace-navigation-v2
AndlerRL Dec 11, 2025
fb84c4a
Merge branch 'develop' into bugfix/workspace-navigation-v2
AndlerRL Dec 15, 2025
b8c8043
revert: fix sync merge
AndlerRL Dec 15, 2025
eabbf5c
fix: initial threads & count with latest org state
AndlerRL Dec 16, 2025
ccd6b28
fix: hasura org table rls
AndlerRL Dec 16, 2025
af0f6fe
fix: hasura pref table rls
AndlerRL Dec 16, 2025
7fe730e
fix: document save race condition + active state + navigation filter …
AndlerRL Dec 16, 2025
b9e701c
fix: default user pref at sign up
AndlerRL Dec 16, 2025
0dbfa66
fix: rm misplaced import
AndlerRL Dec 16, 2025
7e17ada
fix: create & save new document from draft
AndlerRL Dec 17, 2025
518347f
fix: new draft, doc & org upt & workspace doc nav pathname upt
AndlerRL Dec 18, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/pro-web/app/actions/thread.actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -722,7 +722,7 @@ export async function getUserWorkspaceInitialState(
const activeDepartment = activeOrganization
? (() => {
const deptName = departmentsByOrgArray[activeOrganization]?.[0]
if (!deptName) return ['General', 1] as [string, number] // Default to General
if (!deptName) return null

// Get department ID from our chatbot-based mapping
const deptMap = departmentsByOrgFromChatbots[activeOrganization]
Expand Down
16 changes: 7 additions & 9 deletions apps/pro-web/components/routes/pro/pro.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -113,15 +113,13 @@ export function Pro({

return (
<>
{!activeOrganization &&
(organizationList?.length || 0) > 0 &&
!isWorkspaceStructureLoading && (
<OnboardingWelcome
isOpen={showOnboarding || !activeOrganization}
onOpen={() => setShowOnboarding(true)}
onClose={() => setShowOnboarding(false)}
/>
)}
{!activeOrganization || organizationList?.length === 0 ? (
<OnboardingWelcome
isOpen={showOnboarding}
onOpen={() => setShowOnboarding(true)}
onClose={() => setShowOnboarding(false)}
/>
) : null}
<AnimatePresence>
{shouldShowChatPanel && (
<ChatPanelPro
Expand Down
88 changes: 54 additions & 34 deletions apps/pro-web/components/routes/thread/user-thread-panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ import {
getThreads,
} from '@/services/hasura'
import type { GetBrowseThreadsParams } from '@/services/hasura/hasura.service.type'
import type { ThreadOrganizationMetadata } from '@/types/thread.types'
import { debounce, uniqBy } from 'lodash'
import type { Thread, User } from 'mb-genql'
import type { Session } from 'next-auth'
Expand Down Expand Up @@ -123,7 +124,7 @@ export default function UserThreadPanel({
totalThreads: number
}>({
threads: [],
count: 0,
count: initialCount,
totalThreads: 0,
})
const { totalThreads } = state
Expand Down Expand Up @@ -183,17 +184,36 @@ export default function UserThreadPanel({
}

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,
],
) ?? []
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

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.

const count = state.count
// state.count !== 0 || state.count > initialCount || isAdminMode || searchTerm
// ? state.count
// : initialCount

// biome-ignore lint/correctness/useExhaustiveDependencies: <explanation>
const loadMore = useCallback(async () => {
Expand Down Expand Up @@ -234,13 +254,11 @@ export default function UserThreadPanel({
departmentId: activeDepartment,
chatbotName,
keyword: searchTerm,
...(hasWorkspaceContext && {
organizationData: {
organization: activeOrganization as string,
department: activeWorkspaceDepartment?.[0] as string,
project: activeProject as string,
},
}),
organizationData: {
organization: activeOrganization || undefined,
department: activeWorkspaceDepartment?.[0],
project: activeProject || undefined,
},
})
}

Expand Down Expand Up @@ -374,19 +392,23 @@ export default function UserThreadPanel({
const currentFetchId = Date.now() // Generate a unique identifier for the current fetch
fetchIdRef.current = currentFetchId

const { threads: newThreads, count } = await getThreads({
const getThreadsParams = {
jwt: session?.user?.hasuraJwt as string,
userId: session?.user.id,
limit: PAGE_SIZE,
departmentId: categoryResponse?.categoryId || activeDepartment,
chatbotName: chatbotName || activeChatbot?.name,
...(hasWorkspaceContext && {
organizationData: {
organization: activeOrganization as string,
department: activeWorkspaceDepartment?.[0] as string,
project: activeProject as string,
},
}),
organizationData: {
organization: activeOrganization || undefined,
department: activeWorkspaceDepartment?.[0],
project: activeProject || undefined,
},
}
const { threads: newThreads, count } = await getThreads(getThreadsParams)

console.log('newThreads + getThreadsParams', {
newThreads,
getThreadsParams,
})
Comment on lines +377 to 380
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 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.

Suggested change
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.


// Check if the fetchId matches the current fetchId stored in the ref
Expand Down Expand Up @@ -451,13 +473,11 @@ export default function UserThreadPanel({
departmentId: activeDepartment,
chatbotName,
keyword: term,
...(hasWorkspaceContext && {
organizationData: {
organization: activeOrganization as string,
department: activeWorkspaceDepartment?.[0] as string,
project: activeProject as string,
},
}),
organizationData: {
organization: activeOrganization || undefined,
department: activeWorkspaceDepartment?.[0],
project: activeProject || undefined,
},
})
}

Expand Down
31 changes: 20 additions & 11 deletions apps/pro-web/components/routes/workspace/workspace-breadcrumb.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ import { useAddProject, useUserId } from '@/lib/queries'
import { getCanonicalDomain } from '@/lib/url'
import type { WorkspaceDocumentMetadata } from '@/types/thread.types'
import { toSlug, useLocalStorage } from 'mb-lib'
import { useMemo, useState } from 'react'
import { startTransition, useMemo, useState } from 'react'

export function WorkspaceBreadcrumb() {
const { navigateTo } = useSidebar()
const { navigateTo, setActiveDepartment } = useSidebar()
const {
activeThread,
isOpenPopup,
Expand All @@ -39,7 +39,6 @@ export function WorkspaceBreadcrumb() {
userDocuments,
organizationList,
setActiveOrganization,
setActiveDepartment,
setActiveProject,
setActiveDocument,
} = workspaceContext
Expand Down Expand Up @@ -86,7 +85,7 @@ export function WorkspaceBreadcrumb() {
}

const changeDepartment = async (dept: [string, number]) => {
setActiveDepartment(dept)
setActiveDepartment(dept[1])

// Get projects in new department
const newProjects =
Expand Down Expand Up @@ -289,14 +288,24 @@ export function WorkspaceBreadcrumb() {
label="Organization"
value={activeOrganization}
options={organizationList || []}
onSelect={(v) => {
if (v === activeOrganization) return
setActiveOrganization(v === 'None' ? null : v)
setActiveDepartment(null)
setActiveProject(null)
onSelect={(organization) => {
if (organization === activeOrganization) return

const projectsByOrg = activeDepartment?.[0]
? projectsByDept[organization]?.[activeDepartment?.[0]]
: []

setActiveOrganization(
organization === 'None' ? null : organization,
)
setActiveProject(projectsByOrg?.[0] || null)
setActiveDocument(null)
if (isOpenPopup) setIsOpenPopup(false)
setActiveThread(null)
setShouldRefreshThreads(true)

startTransition(() => {
setIsOpenPopup(false)
setActiveThread(null)
})
}}
addType="organization"
onNewItem={onNewItem}
Expand Down
2 changes: 1 addition & 1 deletion apps/pro-web/lib/hooks/use-mb-chat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1043,7 +1043,7 @@ export function MBChatProvider({ children }: { children: React.ReactNode }) {
metadata: {
organizationData: {
organization: activeOrganization,
department: activeDepartment?.[0] || 'General',
department: activeDepartment?.[0],
project: activeProject,
},
},
Expand Down
12 changes: 8 additions & 4 deletions apps/pro-web/lib/hooks/use-workspace.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -250,9 +250,15 @@ export function WorkspaceProvider({
// Derived projectList (flatten) recomputed on changes
// biome-ignore lint/correctness/useExhaustiveDependencies: We need to hear the organization/project changes and the projects by dept
const projectList = useMemo(() => {
if (
!activeOrganization ||
!projectsByDept ||
!projectsByDept[activeOrganization]
)
return []
return Array.from(
new Set(
Object.values(projectsByDept).flatMap((deptMap) =>
Object.values(projectsByDept[activeOrganization]).flatMap((deptMap) =>
Object.values(deptMap).flat(),
),
),
Expand Down Expand Up @@ -329,9 +335,7 @@ export function WorkspaceProvider({
workspaceStructure?.activeOrganization ||
workspaceStructure?.organizationList[0],
department:
activeDepartment?.[0] ||
workspaceStructure?.activeDepartment?.[0] ||
'General',
activeDepartment?.[0] || workspaceStructure?.activeDepartment?.[0],
})

// Initialize document with type-appropriate content
Expand Down
13 changes: 12 additions & 1 deletion apps/pro-web/lib/queries/use-workspace-mutations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,18 @@ export function useAddOrganization(userId: string | undefined) {
}

// Create new organization data
let newOrgData: OrganizationData = { name, chatbots }
const doesOrgExist = currentStructure.organizationData.some(
(org) => org.name === name,
)

if (doesOrgExist) {
throw new Error('Organization with this name already exists')
}

let newOrgData: OrganizationData = {
name,
chatbots,
}

// If user is authenticated, save to database first to get the ID
try {
Expand Down
11 changes: 9 additions & 2 deletions apps/pro-web/lib/queries/use-workspace-structure.ts
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,9 @@ export function useWorkspaceStructure(
}
}

finalStructure.organizationList.sort()
finalStructure.organizationList = Array.from(
new Set(finalStructure.organizationList),
).sort()

// Merge departments and projects for all orgs
for (const orgName of sourceToMerge.organizationList) {
Expand Down Expand Up @@ -332,7 +334,12 @@ export function useWorkspaceStructure(
}

// Second pass: ensure all projects exist in all departments for each org
for (const [org, projects] of projectsByOrg.entries()) {
for (const org of finalStructure.organizationList) {
const orgProjects = projectsByOrg.get(org)
const projects = orgProjects
? Array.from(orgProjects)
: ['Project 1']

const departments = finalStructure.departmentsByOrg[org] || []

// Initialize projectsByDept for this org if needed
Expand Down
15 changes: 13 additions & 2 deletions apps/pro-web/services/hasura/hasura.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -358,7 +358,7 @@ export async function getThreads({
keyword,
organizationData,
}: GetThreadsParams & {
organizationData?: ThreadOrganizationMetadata
organizationData?: Partial<ThreadOrganizationMetadata>
}) {
const client = getHasuraClient({ jwt })

Expand Down Expand Up @@ -415,7 +415,18 @@ export async function getThreads({
where: {
metadata: {
_contains: {
organizationData,
// We conditionally add only the fields that are defined. This way we filter correctly and with more accuracy.
organizationData: {
...(organizationData.organization
? { organization: organizationData.organization }
: {}),
...(organizationData.department
? { department: organizationData.department }
: {}),
...(organizationData.project
? { project: organizationData.project }
: {}),
},
},
},
},
Expand Down