Skip to content

Commit 755afef

Browse files
committed
refactor(ui): eliminate prop drilling in editor, home, sidebar, and logs dashboard
- panel editor: activeSearchTarget was relayed untouched through SubBlock and 30+ input components (depth up to 6); now provided once via ActiveSearchTargetProvider and re-provided at tool-input's synthetic sub-block transformation points; removed the vestigial workspaceId option from SubBlockInputController/useSubBlockInput - home: ChatSurfaceProvider carries chatId/userId and stable interaction callbacks to UserInput/MessageContent/MessageActions; MothershipResourcesProvider carries the five resource operations to ResourceTabs, removing MothershipView's pure-relay props - sidebar: extended SidebarDragContext into SidebarListContext so WorkflowItem/FolderItem read selection/drag callbacks directly; moved the hidden import input up to sidebar.tsx (also fixes import no-op while the list shows a skeleton) - logs dashboard: DashboardSegmentsContext feeds StatusBar directly; WorkflowsList no longer relays segment selection state
1 parent 1ff445a commit 755afef

67 files changed

Lines changed: 1200 additions & 1040 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

apps/sim/app/workspace/[workspaceId]/components/message-actions/message-actions.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
toast,
1818
} from '@/components/emcn'
1919
import { cn } from '@/lib/core/utils/cn'
20+
import { useChatSurface } from '@/app/workspace/[workspaceId]/home/components/chat-surface-context'
2021
import { useSubmitCopilotFeedback } from '@/hooks/queries/copilot-feedback'
2122
import { useForkMothershipChat } from '@/hooks/queries/mothership-chats'
2223
import { useFolderStore } from '@/stores/folders/store'
@@ -49,21 +50,20 @@ const BUTTON_CLASS =
4950

5051
interface MessageActionsProps {
5152
content: string
52-
chatId?: string
5353
userQuery?: string
5454
requestId?: string
5555
messageId?: string
5656
}
5757

5858
export const MessageActions = memo(function MessageActions({
5959
content,
60-
chatId,
6160
userQuery,
6261
requestId,
6362
messageId,
6463
}: MessageActionsProps) {
6564
const router = useRouter()
6665
const params = useParams<{ workspaceId: string }>()
66+
const { chatId } = useChatSurface()
6767
const [copied, setCopied] = useState(false)
6868
const [copiedRequestId, setCopiedRequestId] = useState(false)
6969
const [pendingFeedback, setPendingFeedback] = useState<'up' | 'down' | null>(null)
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
'use client'
2+
3+
import { createContext, type ReactNode, useCallback, useContext, useMemo, useRef } from 'react'
4+
import type { MothershipResource } from '@/app/workspace/[workspaceId]/home/types'
5+
import type { ChatContext } from '@/stores/panel'
6+
7+
/**
8+
* Identity and interaction callbacks shared across a Mothership chat surface
9+
* (home conversation view, home initial view, copilot panel). Carried via
10+
* context so leaf components (UserInput, MessageContent, MessageActions) can
11+
* consume them without relaying through every intermediate component.
12+
*/
13+
interface ChatSurfaceContextValue {
14+
/** Resolved id of the chat backing this surface, if one exists yet. */
15+
chatId?: string
16+
/** Id of the user interacting with this surface. */
17+
userId?: string
18+
/** Notifies the surface owner that a context chip was added to the input. */
19+
onContextAdd: (context: ChatContext) => void
20+
/** Notifies the surface owner that a context chip was removed from the input. */
21+
onContextRemove: (context: ChatContext) => void
22+
/** Opens a workspace resource referenced from rendered message content. */
23+
onWorkspaceResourceSelect: (resource: MothershipResource) => void
24+
}
25+
26+
const noop = () => {}
27+
28+
const ChatSurfaceContext = createContext<ChatSurfaceContextValue>({
29+
onContextAdd: noop,
30+
onContextRemove: noop,
31+
onWorkspaceResourceSelect: noop,
32+
})
33+
34+
interface ChatSurfaceProviderProps {
35+
chatId?: string
36+
userId?: string
37+
onContextAdd?: (context: ChatContext) => void
38+
onContextRemove?: (context: ChatContext) => void
39+
onWorkspaceResourceSelect?: (resource: MothershipResource) => void
40+
children: ReactNode
41+
}
42+
43+
/**
44+
* Provides the chat-surface identity and interaction callbacks to descendants.
45+
* Callbacks are latched in refs and exposed as stable wrappers so the memoized
46+
* context value only changes when `chatId` or `userId` change — consumers do
47+
* not re-render when a parent re-creates a handler.
48+
*/
49+
export function ChatSurfaceProvider({
50+
chatId,
51+
userId,
52+
onContextAdd,
53+
onContextRemove,
54+
onWorkspaceResourceSelect,
55+
children,
56+
}: ChatSurfaceProviderProps) {
57+
const onContextAddRef = useRef(onContextAdd)
58+
onContextAddRef.current = onContextAdd
59+
const onContextRemoveRef = useRef(onContextRemove)
60+
onContextRemoveRef.current = onContextRemove
61+
const onWorkspaceResourceSelectRef = useRef(onWorkspaceResourceSelect)
62+
onWorkspaceResourceSelectRef.current = onWorkspaceResourceSelect
63+
64+
const stableOnContextAdd = useCallback((context: ChatContext) => {
65+
onContextAddRef.current?.(context)
66+
}, [])
67+
const stableOnContextRemove = useCallback((context: ChatContext) => {
68+
onContextRemoveRef.current?.(context)
69+
}, [])
70+
const stableOnWorkspaceResourceSelect = useCallback((resource: MothershipResource) => {
71+
onWorkspaceResourceSelectRef.current?.(resource)
72+
}, [])
73+
74+
const value = useMemo<ChatSurfaceContextValue>(
75+
() => ({
76+
chatId,
77+
userId,
78+
onContextAdd: stableOnContextAdd,
79+
onContextRemove: stableOnContextRemove,
80+
onWorkspaceResourceSelect: stableOnWorkspaceResourceSelect,
81+
}),
82+
[chatId, userId, stableOnContextAdd, stableOnContextRemove, stableOnWorkspaceResourceSelect]
83+
)
84+
85+
return <ChatSurfaceContext.Provider value={value}>{children}</ChatSurfaceContext.Provider>
86+
}
87+
88+
/**
89+
* Reads the surrounding chat surface. Outside a provider this returns no-op
90+
* callbacks and undefined identity, matching the previous optional-prop
91+
* behavior.
92+
*/
93+
export function useChatSurface(): ChatSurfaceContextValue {
94+
return useContext(ChatSurfaceContext)
95+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { ChatSurfaceProvider, useChatSurface } from './chat-surface-context'

apps/sim/app/workspace/[workspaceId]/home/components/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
export { ChatMessageAttachments } from './chat-message-attachments'
2+
export { ChatSurfaceProvider, useChatSurface } from './chat-surface-context'
23
export { ContextMentionIcon } from './context-mention-icon'
34
export { CreditsChip } from './credits-chip'
45
export {
56
assistantMessageHasRenderableContent,
67
MessageContent,
78
} from './message-content'
89
export { MothershipChat } from './mothership-chat'
10+
export {
11+
MothershipResourcesProvider,
12+
useMothershipResources,
13+
} from './mothership-resources-context'
914
export { MothershipView } from './mothership-view'
1015
export { QueuedMessages } from './queued-messages'
1116
export { SuggestedActions } from './suggested-actions'

apps/sim/app/workspace/[workspaceId]/home/components/message-content/message-content.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ import { Read as ReadTool, WorkspaceFile } from '@/lib/copilot/generated/tool-ca
66
import { isToolHiddenInUi } from '@/lib/copilot/tools/client/hidden-tools'
77
import { resolveToolDisplay } from '@/lib/copilot/tools/client/store-utils'
88
import { ClientToolCallState } from '@/lib/copilot/tools/client/tool-call-state'
9-
import type { ContentBlock, MothershipResource, OptionItem, ToolCallData } from '../../types'
9+
import { useChatSurface } from '@/app/workspace/[workspaceId]/home/components/chat-surface-context'
10+
import type { ContentBlock, OptionItem, ToolCallData } from '../../types'
1011
import { SUBAGENT_LABELS, TOOL_UI_METADATA } from '../../types'
1112
import type { AgentGroupItem } from './components'
1213
import {
@@ -676,16 +677,15 @@ interface MessageContentProps {
676677
fallbackContent: string
677678
isStreaming: boolean
678679
onOptionSelect?: (id: string) => void
679-
onWorkspaceResourceSelect?: (resource: MothershipResource) => void
680680
}
681681

682682
function MessageContentInner({
683683
blocks,
684684
fallbackContent,
685685
isStreaming = false,
686686
onOptionSelect,
687-
onWorkspaceResourceSelect,
688687
}: MessageContentProps) {
688+
const { onWorkspaceResourceSelect } = useChatSurface()
689689
const parsed = useMemo(() => (blocks.length > 0 ? parseBlocks(blocks) : []), [blocks])
690690

691691
const segments: MessageSegment[] =

apps/sim/app/workspace/[workspaceId]/home/components/mothership-chat/mothership-chat.tsx

Lines changed: 67 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { memo, useCallback, useEffect, useLayoutEffect, useMemo, useRef } from '
44
import { cn } from '@/lib/core/utils/cn'
55
import { MessageActions } from '@/app/workspace/[workspaceId]/components'
66
import { ChatMessageAttachments } from '@/app/workspace/[workspaceId]/home/components/chat-message-attachments'
7+
import { ChatSurfaceProvider } from '@/app/workspace/[workspaceId]/home/components/chat-surface-context'
78
import {
89
assistantMessageHasRenderableContent,
910
MessageContent,
@@ -124,20 +125,16 @@ interface AssistantMessageRowProps {
124125
message: ChatMessage
125126
isStreaming: boolean
126127
precedingUserContent?: string
127-
chatId?: string
128128
rowClassName: string
129129
onOptionSelect?: (id: string) => void
130-
onWorkspaceResourceSelect?: (resource: MothershipResource) => void
131130
}
132131

133132
const AssistantMessageRow = memo(function AssistantMessageRow({
134133
message,
135134
isStreaming,
136135
precedingUserContent,
137-
chatId,
138136
rowClassName,
139137
onOptionSelect,
140-
onWorkspaceResourceSelect,
141138
}: AssistantMessageRowProps) {
142139
const blocks = message.contentBlocks ?? EMPTY_BLOCKS
143140
const hasAnyBlocks = blocks.length > 0
@@ -161,13 +158,11 @@ const AssistantMessageRow = memo(function AssistantMessageRow({
161158
fallbackContent={message.content}
162159
isStreaming={isStreaming}
163160
onOptionSelect={onOptionSelect}
164-
onWorkspaceResourceSelect={onWorkspaceResourceSelect}
165161
/>
166162
{showActions && (
167163
<div className='mt-2.5'>
168164
<MessageActions
169165
content={message.content}
170-
chatId={chatId}
171166
userQuery={precedingUserContent}
172167
requestId={message.requestId}
173168
messageId={message.id}
@@ -227,17 +222,12 @@ export function MothershipChat({
227222
const userInputRef = useRef<UserInputHandle>(null)
228223

229224
const onSubmitRef = useRef(onSubmit)
230-
const onWorkspaceResourceSelectRef = useRef(onWorkspaceResourceSelect)
231225
useEffect(() => {
232226
onSubmitRef.current = onSubmit
233-
onWorkspaceResourceSelectRef.current = onWorkspaceResourceSelect
234-
}, [onSubmit, onWorkspaceResourceSelect])
227+
}, [onSubmit])
235228
const stableOnOptionSelect = useCallback((id: string) => {
236229
onSubmitRef.current(id)
237230
}, [])
238-
const stableOnWorkspaceResourceSelect = useCallback((resource: MothershipResource) => {
239-
onWorkspaceResourceSelectRef.current?.(resource)
240-
}, [])
241231

242232
function handleSendQueuedHead() {
243233
const topMessage = messageQueue[0]
@@ -272,75 +262,78 @@ export function MothershipChat({
272262
}, [isStaging, stagedMessageCount, initialScrollBlocked, scrollToBottom])
273263

274264
return (
275-
<div className={cn('flex h-full min-h-0 flex-col', className)}>
276-
<div ref={scrollContainerRef} className={styles.scrollContainer}>
277-
{isLoading && !hasMessages ? (
278-
<MothershipChatSkeleton layout={layout} />
279-
) : (
280-
<div className={styles.content}>
281-
{stagedMessages.map((msg, localIndex) => {
282-
const index = stagedOffset + localIndex
283-
if (msg.role === 'user') {
265+
<ChatSurfaceProvider
266+
chatId={chatId}
267+
userId={userId}
268+
onContextAdd={onContextAdd}
269+
onContextRemove={onContextRemove}
270+
onWorkspaceResourceSelect={onWorkspaceResourceSelect}
271+
>
272+
<div className={cn('flex h-full min-h-0 flex-col', className)}>
273+
<div ref={scrollContainerRef} className={styles.scrollContainer}>
274+
{isLoading && !hasMessages ? (
275+
<MothershipChatSkeleton layout={layout} />
276+
) : (
277+
<div className={styles.content}>
278+
{stagedMessages.map((msg, localIndex) => {
279+
const index = stagedOffset + localIndex
280+
if (msg.role === 'user') {
281+
return (
282+
<UserMessageRow
283+
key={msg.id}
284+
content={msg.content}
285+
contexts={msg.contexts}
286+
attachments={msg.attachments}
287+
rowClassName={styles.userRow}
288+
bubbleClassName={styles.userBubble}
289+
attachmentWidthClassName={styles.attachmentWidth}
290+
/>
291+
)
292+
}
293+
294+
const isLast = index === messages.length - 1
284295
return (
285-
<UserMessageRow
296+
<AssistantMessageRow
286297
key={msg.id}
287-
content={msg.content}
288-
contexts={msg.contexts}
289-
attachments={msg.attachments}
290-
rowClassName={styles.userRow}
291-
bubbleClassName={styles.userBubble}
292-
attachmentWidthClassName={styles.attachmentWidth}
298+
message={msg}
299+
isStreaming={isStreamActive && isLast}
300+
precedingUserContent={precedingUserContentByIndex[index]}
301+
rowClassName={styles.assistantRow}
302+
onOptionSelect={isLast ? stableOnOptionSelect : undefined}
293303
/>
294304
)
295-
}
305+
})}
306+
</div>
307+
)}
308+
</div>
296309

297-
const isLast = index === messages.length - 1
298-
return (
299-
<AssistantMessageRow
300-
key={msg.id}
301-
message={msg}
302-
isStreaming={isStreamActive && isLast}
303-
precedingUserContent={precedingUserContentByIndex[index]}
304-
chatId={chatId}
305-
rowClassName={styles.assistantRow}
306-
onOptionSelect={isLast ? stableOnOptionSelect : undefined}
307-
onWorkspaceResourceSelect={stableOnWorkspaceResourceSelect}
308-
/>
309-
)
310-
})}
310+
<div
311+
className={cn(styles.footer, animateInput && 'animate-slide-in-bottom')}
312+
onAnimationEnd={animateInput ? onInputAnimationEnd : undefined}
313+
>
314+
<div className={styles.footerInner}>
315+
<QueuedMessages
316+
messageQueue={messageQueue}
317+
editingQueuedId={editingQueuedId}
318+
dispatchingHeadId={dispatchingHeadId}
319+
onRemove={onRemoveQueuedMessage}
320+
onSendNow={onSendQueuedMessage}
321+
onEdit={handleEditQueued}
322+
onCancelEdit={onCancelQueueEdit}
323+
/>
324+
<UserInput
325+
ref={userInputRef}
326+
onSubmit={onSubmit}
327+
isSending={isStreamActive}
328+
onStopGeneration={onStopGeneration}
329+
isInitialView={false}
330+
onSendQueuedHead={handleSendQueuedHead}
331+
onEditQueuedTail={handleEditQueuedTail}
332+
draftScopeKey={draftScopeKey}
333+
/>
311334
</div>
312-
)}
313-
</div>
314-
315-
<div
316-
className={cn(styles.footer, animateInput && 'animate-slide-in-bottom')}
317-
onAnimationEnd={animateInput ? onInputAnimationEnd : undefined}
318-
>
319-
<div className={styles.footerInner}>
320-
<QueuedMessages
321-
messageQueue={messageQueue}
322-
editingQueuedId={editingQueuedId}
323-
dispatchingHeadId={dispatchingHeadId}
324-
onRemove={onRemoveQueuedMessage}
325-
onSendNow={onSendQueuedMessage}
326-
onEdit={handleEditQueued}
327-
onCancelEdit={onCancelQueueEdit}
328-
/>
329-
<UserInput
330-
ref={userInputRef}
331-
onSubmit={onSubmit}
332-
isSending={isStreamActive}
333-
onStopGeneration={onStopGeneration}
334-
isInitialView={false}
335-
userId={userId}
336-
onContextAdd={onContextAdd}
337-
onContextRemove={onContextRemove}
338-
onSendQueuedHead={handleSendQueuedHead}
339-
onEditQueuedTail={handleEditQueuedTail}
340-
draftScopeKey={draftScopeKey}
341-
/>
342335
</div>
343336
</div>
344-
</div>
337+
</ChatSurfaceProvider>
345338
)
346339
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export {
2+
MothershipResourcesProvider,
3+
useMothershipResources,
4+
} from './mothership-resources-context'

0 commit comments

Comments
 (0)