diff --git a/apps/sim/app/workspace/[workspaceId]/files/files.tsx b/apps/sim/app/workspace/[workspaceId]/files/files.tsx index b96ef517aa5..7796b8d6ff2 100644 --- a/apps/sim/app/workspace/[workspaceId]/files/files.tsx +++ b/apps/sim/app/workspace/[workspaceId]/files/files.tsx @@ -1565,7 +1565,10 @@ export function Files() { }, [router, workspaceId]) const loadingBreadcrumbs = useMemo( - () => [{ label: 'Files', onClick: handleNavigateToFiles }, { label: '...' }], + (): BreadcrumbItem[] => [ + { label: 'Files', onClick: handleNavigateToFiles }, + { label: '…', terminal: true }, + ], [handleNavigateToFiles] ) diff --git a/apps/sim/app/workspace/[workspaceId]/knowledge/components/index.ts b/apps/sim/app/workspace/[workspaceId]/knowledge/components/index.ts index 4f280474ca3..92d91b2cd0f 100644 --- a/apps/sim/app/workspace/[workspaceId]/knowledge/components/index.ts +++ b/apps/sim/app/workspace/[workspaceId]/knowledge/components/index.ts @@ -4,5 +4,4 @@ export { DeleteKnowledgeBaseModal } from './delete-knowledge-base-modal' export { EditKnowledgeBaseModal } from './edit-knowledge-base-modal' export { getDocumentIcon } from './icons' export { KnowledgeBaseContextMenu } from './knowledge-base-context-menu' -export { KnowledgeHeader } from './knowledge-header' export { KnowledgeListContextMenu } from './knowledge-list-context-menu' diff --git a/apps/sim/app/workspace/[workspaceId]/knowledge/components/knowledge-header/index.ts b/apps/sim/app/workspace/[workspaceId]/knowledge/components/knowledge-header/index.ts deleted file mode 100644 index 39838545172..00000000000 --- a/apps/sim/app/workspace/[workspaceId]/knowledge/components/knowledge-header/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { KnowledgeHeader } from './knowledge-header' diff --git a/apps/sim/app/workspace/[workspaceId]/knowledge/components/knowledge-header/knowledge-header.tsx b/apps/sim/app/workspace/[workspaceId]/knowledge/components/knowledge-header/knowledge-header.tsx deleted file mode 100644 index 7d1e623ee1d..00000000000 --- a/apps/sim/app/workspace/[workspaceId]/knowledge/components/knowledge-header/knowledge-header.tsx +++ /dev/null @@ -1,204 +0,0 @@ -'use client' - -import { useState } from 'react' -import { createLogger } from '@sim/logger' -import { AlertTriangle, LibraryBig, MoreHorizontal } from 'lucide-react' -import Link from 'next/link' -import { - Button, - DropdownMenu, - DropdownMenuCheckboxItem, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuTrigger, - Tooltip, -} from '@/components/emcn' -import { ChevronDown } from '@/components/emcn/icons' -import { Trash } from '@/components/emcn/icons/trash' -import { filterButtonClass } from '@/app/workspace/[workspaceId]/knowledge/components/constants' -import { useUpdateKnowledgeBase } from '@/hooks/queries/kb/knowledge' -import { useWorkspacesQuery } from '@/hooks/queries/workspace' - -const logger = createLogger('KnowledgeHeader') - -interface BreadcrumbItem { - label: string - href?: string - id?: string -} - -const HEADER_STYLES = { - container: 'flex items-center justify-between px-6 pt-3.5 pb-6', - breadcrumbs: 'flex items-center gap-2', - icon: 'size-[18px] text-[var(--text-icon)] transition-colors', - link: 'group flex items-center gap-2 font-medium text-sm text-[var(--text-body)] transition-colors hover-hover:text-[var(--text-secondary)]', - label: 'font-medium text-sm text-[var(--text-body)]', - separator: 'text-[var(--text-icon)]', - actionsContainer: 'flex items-center gap-2', -} as const - -interface KnowledgeHeaderOptions { - knowledgeBaseId?: string - currentWorkspaceId?: string | null - onWorkspaceChange?: (workspaceId: string | null) => void | Promise - onDeleteKnowledgeBase?: () => void -} - -interface KnowledgeHeaderProps { - breadcrumbs: BreadcrumbItem[] - options?: KnowledgeHeaderOptions -} - -export function KnowledgeHeader({ breadcrumbs, options }: KnowledgeHeaderProps) { - const [isActionsMenuOpen, setIsActionsMenuOpen] = useState(false) - const [isWorkspaceMenuOpen, setIsWorkspaceMenuOpen] = useState(false) - - const { data: allWorkspaces = [], isLoading: isLoadingWorkspaces } = useWorkspacesQuery( - !!options?.knowledgeBaseId - ) - const workspaces = allWorkspaces.filter( - (ws) => ws.permissions === 'write' || ws.permissions === 'admin' - ) - - const updateKnowledgeBase = useUpdateKnowledgeBase() - - const handleWorkspaceChange = async (workspaceId: string | null) => { - if (updateKnowledgeBase.isPending || !options?.knowledgeBaseId) return - - setIsWorkspaceMenuOpen(false) - - updateKnowledgeBase.mutate( - { - knowledgeBaseId: options.knowledgeBaseId, - updates: { workspaceId }, - }, - { - onSuccess: () => { - logger.info( - `Knowledge base workspace updated: ${options.knowledgeBaseId} -> ${workspaceId}` - ) - options.onWorkspaceChange?.(workspaceId) - }, - onError: (err) => { - logger.error('Error updating workspace:', err) - }, - } - ) - } - - const currentWorkspace = workspaces.find((ws) => ws.id === options?.currentWorkspaceId) - const hasWorkspace = !!options?.currentWorkspaceId - - return ( -
-
- {breadcrumbs.map((breadcrumb, index) => { - const key = breadcrumb.id || `${breadcrumb.label}-${breadcrumb.href || index}` - - return ( -
- {index === 0 && } - - {breadcrumb.href ? ( - - {breadcrumb.label} - - ) : ( - {breadcrumb.label} - )} - - {index < breadcrumbs.length - 1 && /} -
- ) - })} -
- - {/* Actions Area */} - {options && ( -
- {/* Workspace Selector */} - {options.knowledgeBaseId && ( -
- {/* Warning icon for unassigned knowledge bases */} - {!hasWorkspace && ( - - - - - Not assigned to workspace - - )} - - {/* Workspace selector dropdown */} - - - - - - handleWorkspaceChange(null)} - > - No workspace - - - {workspaces.map((workspace) => ( - handleWorkspaceChange(workspace.id)} - > - {workspace.name} - - ))} - - {workspaces.length === 0 && !isLoadingWorkspaces && ( - - - No workspaces with write access - - - )} - - -
- )} - - {/* Actions Menu */} - {options.onDeleteKnowledgeBase && ( - - - - - - options.onDeleteKnowledgeBase?.()}> - - Delete Knowledge Base - - - - )} -
- )} -
- ) -} diff --git a/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/loading.tsx b/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/loading.tsx index c10ab133ac2..2d07d4e540c 100644 --- a/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/loading.tsx +++ b/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/loading.tsx @@ -1,6 +1,6 @@ 'use client' -import { Table as TableIcon } from '@/components/emcn' +import { Table as TableIcon } from '@/components/emcn/icons' import { type BreadcrumbItem, ResourceChromeFallback, diff --git a/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/table.tsx b/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/table.tsx index 24cda22f1cf..ef33be648df 100644 --- a/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/table.tsx +++ b/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/table.tsx @@ -12,6 +12,7 @@ import type { ColumnDefinition, Filter, TableRow as TableRowType, WorkflowGroup import { getColumnId } from '@/lib/table/column-keys' import { TABLE_LIMITS } from '@/lib/table/constants' import { + type BreadcrumbItem, type ColumnOption, Resource, type SortConfig, @@ -483,34 +484,36 @@ export function Table({ } const breadcrumbs = useMemo( - () => [ + (): BreadcrumbItem[] => [ { label: 'Tables', onClick: handleNavigateBack }, - { - label: tableData?.name ?? '', - editing: tableHeaderRename.editingId - ? { - isEditing: true, - value: tableHeaderRename.editValue, - onChange: tableHeaderRename.setEditValue, - onSubmit: tableHeaderRename.submitRename, - onCancel: tableHeaderRename.cancelRename, - } - : undefined, - dropdownItems: [ - { - label: 'Rename', - icon: Pencil, - disabled: !tableData, - onClick: handleStartTableRename, - }, - { - label: 'Delete', - icon: Trash, - disabled: !tableData, - onClick: onRequestDeleteTable, - }, - ], - }, + // While the table loads, mirror this route's loading.tsx (terminal "…" crumb) + // so no empty-label / orphaned-chevron frame renders in between. + tableData + ? { + label: tableData.name, + editing: tableHeaderRename.editingId + ? { + isEditing: true, + value: tableHeaderRename.editValue, + onChange: tableHeaderRename.setEditValue, + onSubmit: tableHeaderRename.submitRename, + onCancel: tableHeaderRename.cancelRename, + } + : undefined, + dropdownItems: [ + { + label: 'Rename', + icon: Pencil, + onClick: handleStartTableRename, + }, + { + label: 'Delete', + icon: Trash, + onClick: onRequestDeleteTable, + }, + ], + } + : { label: '…', terminal: true }, ], [ handleNavigateBack, diff --git a/apps/sim/app/workspace/[workspaceId]/tables/loading.tsx b/apps/sim/app/workspace/[workspaceId]/tables/loading.tsx index 4307a8bc121..2af5787728c 100644 --- a/apps/sim/app/workspace/[workspaceId]/tables/loading.tsx +++ b/apps/sim/app/workspace/[workspaceId]/tables/loading.tsx @@ -1,6 +1,7 @@ 'use client' -import { Plus, Table as TableIcon, Upload } from '@/components/emcn' +import { Plus, Upload } from '@/components/emcn' +import { Table as TableIcon } from '@/components/emcn/icons' import { type ChromeActionSpec, ResourceChromeFallback, diff --git a/apps/sim/components/emcn/index.ts b/apps/sim/components/emcn/index.ts index 6fcac7d268f..2d972a1ab2e 100644 --- a/apps/sim/components/emcn/index.ts +++ b/apps/sim/components/emcn/index.ts @@ -1,4 +1,11 @@ export * from './components' +/** + * `Table` exists in BOTH `./components` (data-table element) and `./icons` + * (glyph). This explicit re-export resolves the ambiguity to the COMPONENT — + * always import the icon from `@/components/emcn/icons`. Rendering the + * component as an icon paints an empty `w-full` table that squeezes its + * siblings (shipped as the tables-header "T…" flicker). + */ export { Table, TableBody,