fix(files): unstick monaco find widget tooltips and surface logs in mothership add-resource (#4395)

This commit is contained in:
Theodore Li
2026-05-01 21:39:27 -04:00
committed by GitHub
parent cc28ba8c70
commit 39c6aef1a6
7 changed files with 132 additions and 9 deletions
@@ -9,7 +9,7 @@ import {
DropdownMenuShortcut,
DropdownMenuTrigger,
} from '@/components/emcn'
import { Clipboard, Copy, Search } from '@/components/emcn/icons'
import { Clipboard, Copy, Search, SelectAll } from '@/components/emcn/icons'
interface EditorContextMenuProps {
isOpen: boolean
@@ -88,6 +88,7 @@ export function EditorContextMenu({
)}
<DropdownMenuSeparator />
<DropdownMenuItem onSelect={onSelectAll}>
<SelectAll />
Select all
<DropdownMenuShortcut>⌘A</DropdownMenuShortcut>
</DropdownMenuItem>
@@ -219,6 +219,27 @@ const SPLIT_MIN_PCT = 20
const SPLIT_MAX_PCT = 80
const SPLIT_DEFAULT_PCT = 50
/**
* Monaco's find-widget button hover tooltips render above the button by default. Because the find
* widget sits at the very top of the editor, the tooltip overlaps the button itself —
* `document.elementFromPoint` at the button's center returns `.hover-contents` instead of the
* button, which fires `mouseleave` on the button. Monaco then disposes the tooltip, the cursor
* lands back on the button, and the cycle repeats every ~210ms, making the buttons unclickable.
*
* Translate the tooltip's `.context-view` below the button so the cursor stays on the button
* while the tooltip is shown, and flip the carat to point up at the button.
*/
const FIND_TOOLTIP_FIX_CSS = `
[data-find-tooltip-fix] .context-view.top.left {
transform: translateY(56px);
}
[data-find-tooltip-fix] .context-view.top.left .workbench-hover-pointer.bottom {
top: -3px;
bottom: auto;
transform: rotate(225deg);
}
`
/** Maps file extensions to Monaco editor language IDs. */
const MONACO_LANGUAGE_BY_EXTENSION: Partial<Record<string, string>> = {
js: 'javascript',
@@ -663,7 +684,8 @@ export const TextEditor = memo(function TextEditor({
const closeContextMenu = () => setContextMenu(null)
return (
<div ref={containerRef} className='relative flex flex-1 overflow-hidden'>
<div ref={containerRef} data-find-tooltip-fix className='relative flex flex-1 overflow-hidden'>
<style>{FIND_TOOLTIP_FIX_CSS}</style>
{showEditor && (
<div
style={showPreviewPane ? { width: `${splitPct}%`, flexShrink: 0 } : undefined}
@@ -24,8 +24,10 @@ import type {
MothershipResource,
MothershipResourceType,
} from '@/app/workspace/[workspaceId]/home/types'
import { formatDate } from '@/app/workspace/[workspaceId]/logs/utils'
import { useFolders } from '@/hooks/queries/folders'
import { useKnowledgeBasesQuery } from '@/hooks/queries/kb/knowledge'
import { useLogsList } from '@/hooks/queries/logs'
import { useTablesList } from '@/hooks/queries/tables'
import { useTasks } from '@/hooks/queries/tasks'
import { useWorkflows } from '@/hooks/queries/workflows'
@@ -47,6 +49,18 @@ interface AvailableItemsByType {
items: AvailableItem[]
}
const LOG_DROPDOWN_LIMIT = 50
const LOG_DROPDOWN_FILTERS = {
timeRange: 'All time' as const,
level: 'all',
workflowIds: [] as string[],
folderIds: [] as string[],
triggers: [] as string[],
searchQuery: '',
limit: LOG_DROPDOWN_LIMIT,
}
export function useAvailableResources(
workspaceId: string,
existingKeys: Set<string>,
@@ -58,6 +72,13 @@ export function useAvailableResources(
const { data: knowledgeBases } = useKnowledgeBasesQuery(workspaceId)
const { data: folders = [] } = useFolders(workspaceId)
const { data: tasks = [] } = useTasks(workspaceId)
const { data: logsData } = useLogsList(workspaceId, LOG_DROPDOWN_FILTERS)
const logs = useMemo(() => (logsData?.pages ?? []).flatMap((page) => page.logs), [logsData])
const workflowColorById = useMemo(() => {
const map = new Map<string, string>()
for (const w of workflows) map.set(w.id, w.color)
return map
}, [workflows])
return useMemo(() => {
const excluded = new Set<MothershipResourceType>(excludeTypes ?? [])
@@ -115,9 +136,39 @@ export function useAvailableResources(
isOpen: existingKeys.has(`task:${t.id}`),
})),
},
{
type: 'log' as const,
items: logs.map((log) => {
const workflowName = log.workflow?.name ?? log.workflowId ?? 'Unknown'
const color =
log.workflow?.color ??
(log.workflowId ? workflowColorById.get(log.workflowId) : undefined) ??
'#888'
const time = formatDate(log.createdAt).compact
return {
id: log.id,
name: `${workflowName} · ${time}`,
workflowName,
color,
time,
isOpen: existingKeys.has(`log:${log.id}`),
}
}),
},
]
return groups.filter((g) => !excluded.has(g.type))
}, [workflows, folders, tables, files, knowledgeBases, tasks, existingKeys, excludeTypes])
}, [
workflows,
folders,
tables,
files,
knowledgeBases,
tasks,
logs,
workflowColorById,
existingKeys,
excludeTypes,
])
}
export type WorkflowTreeNode =
@@ -100,6 +100,30 @@ function IconDropdownItem({ item, icon: Icon }: DropdownItemRenderProps & { icon
)
}
function LogDropdownItem({ item }: DropdownItemRenderProps) {
const color = (item.color as string) ?? '#888'
const workflowName = (item.workflowName as string) ?? item.name
const time = (item.time as string) ?? ''
return (
<>
<div
className='h-[14px] w-[14px] flex-shrink-0 rounded-[3px] border-[2px]'
style={{
backgroundColor: color,
borderColor: workflowBorderColor(color),
backgroundClip: 'padding-box',
}}
/>
<span className='truncate'>{workflowName}</span>
{time && (
<span className='ml-auto flex-shrink-0 text-[var(--text-tertiary)] text-caption'>
{time}
</span>
)}
</>
)
}
export const RESOURCE_REGISTRY: Record<MothershipResourceType, ResourceTypeConfig> = {
generic: {
type: 'generic',
@@ -172,7 +196,7 @@ export const RESOURCE_REGISTRY: Record<MothershipResourceType, ResourceTypeConfi
renderTabIcon: (_resource, className) => (
<Library className={cn(className, 'text-[var(--text-icon)]')} />
),
renderDropdownItem: (props) => <IconDropdownItem {...props} icon={Library} />,
renderDropdownItem: (props) => <LogDropdownItem {...props} />,
},
} as const
@@ -41,11 +41,7 @@ import { useWorkspaceFiles } from '@/hooks/queries/workspace-files'
const EDGE_ZONE = 40
const SCROLL_SPEED = 8
const ADD_RESOURCE_EXCLUDED_TYPES: readonly MothershipResourceType[] = [
'folder',
'task',
'log',
] as const
const ADD_RESOURCE_EXCLUDED_TYPES: readonly MothershipResourceType[] = ['folder', 'task'] as const
/**
* Returns the id of the nearest resource to `idx` that is in `filter`
+1
View File
@@ -65,6 +65,7 @@ export { RefreshCw } from './refresh-cw'
export { Rocket } from './rocket'
export { Rows3 } from './rows3'
export { Search } from './search'
export { SelectAll } from './select-all'
export { Send } from './send'
export { Server } from './server'
export { Settings } from './settings'
@@ -0,0 +1,28 @@
import type { SVGProps } from 'react'
/**
* SelectAll icon component - four L-shaped corner brackets indicating a selection region
* @param props - SVG properties including className, fill, etc.
*/
export function SelectAll(props: SVGProps<SVGSVGElement>) {
return (
<svg
width='24'
height='24'
viewBox='0 0 24 24'
fill='none'
stroke='currentColor'
strokeWidth='2'
strokeLinecap='round'
strokeLinejoin='round'
xmlns='http://www.w3.org/2000/svg'
aria-hidden='true'
{...props}
>
<path d='M8 4H4V8' />
<path d='M16 4H20V8' />
<path d='M20 16V20H16' />
<path d='M8 20H4V16' />
</svg>
)
}