Skip to content

Commit 20bd0b3

Browse files
committed
refactor(cli): add context providers to eliminate prop drilling
- Create MessageActionsProvider for message interaction callbacks - Create ChatThemeProvider for theme and styling props - Update MessageWithAgents, MessageBlock, and ChatScrollArea to use contexts - Remove 12+ props from message component interfaces - Update tests to wrap components with context providers This eliminates severe prop drilling through 3+ component levels.
1 parent a2498da commit 20bd0b3

File tree

7 files changed

+160
-191
lines changed

7 files changed

+160
-191
lines changed

cli/src/chat.tsx

Lines changed: 42 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import { MessageWithAgents } from './components/message-with-agents'
88
import { FeedbackContainer } from './components/feedback-container'
99
import { ChatScrollArea } from './components/chat-scroll-area'
1010
import { ChatInputArea } from './components/chat-input-area'
11+
import { MessageActionsProvider } from './contexts/message-actions-context'
12+
import { ChatThemeProvider } from './contexts/chat-theme-context'
1113
import { useFeedbackStore } from './state/feedback-store'
1214
import { useAuthStore } from './state/auth-store'
1315
import { toggleCollapsedById, autoCollapseBlocks } from './utils/block-tree'
@@ -724,36 +726,44 @@ export const Chat = ({
724726
})
725727

726728
return (
727-
<box
728-
style={{
729-
flexDirection: 'column',
730-
gap: 0,
731-
paddingLeft: 1,
732-
paddingRight: 1,
733-
flexGrow: 1,
729+
<MessageActionsProvider
730+
value={{
731+
onToggleCollapsed: handleCollapseToggle,
732+
onBuildFast: handleBuildFast,
733+
onBuildMax: handleBuildMax,
734+
onFeedback: handleMessageFeedback,
735+
onCloseFeedback: handleCloseFeedback,
734736
}}
735737
>
736-
<ChatScrollArea
737-
scrollRef={scrollRef}
738-
appliedScrollboxProps={appliedScrollboxProps}
739-
headerContent={headerContent}
740-
virtualizationNotice={virtualizationNotice}
741-
topLevelMessages={topLevelMessages}
742-
markdownPalette={markdownPalette}
743-
streamingAgents={streamingAgents}
744-
messageTree={messageTree}
745-
messages={messages}
746-
separatorWidth={separatorWidth}
747-
theme={theme}
748-
setFocusedAgentId={setFocusedAgentId}
749-
isWaitingForResponse={isWaitingForResponse}
750-
timerStartTime={timerStartTime}
751-
handleCollapseToggle={handleCollapseToggle}
752-
handleBuildFast={handleBuildFast}
753-
handleBuildMax={handleBuildMax}
754-
handleMessageFeedback={handleMessageFeedback}
755-
handleCloseFeedback={handleCloseFeedback}
756-
/>
738+
<ChatThemeProvider
739+
value={{
740+
theme,
741+
markdownPalette,
742+
availableWidth: separatorWidth,
743+
timerStartTime,
744+
}}
745+
>
746+
<box
747+
style={{
748+
flexDirection: 'column',
749+
gap: 0,
750+
paddingLeft: 1,
751+
paddingRight: 1,
752+
flexGrow: 1,
753+
}}
754+
>
755+
<ChatScrollArea
756+
scrollRef={scrollRef}
757+
appliedScrollboxProps={appliedScrollboxProps}
758+
headerContent={headerContent}
759+
virtualizationNotice={virtualizationNotice}
760+
topLevelMessages={topLevelMessages}
761+
streamingAgents={streamingAgents}
762+
messageTree={messageTree}
763+
messages={messages}
764+
setFocusedAgentId={setFocusedAgentId}
765+
isWaitingForResponse={isWaitingForResponse}
766+
/>
757767

758768
<box
759769
style={{
@@ -814,7 +824,9 @@ export const Chat = ({
814824
)}
815825
</box>
816826

817-
{validationBanner}
818-
</box>
827+
{validationBanner}
828+
</box>
829+
</ChatThemeProvider>
830+
</MessageActionsProvider>
819831
)
820832
}

cli/src/components/__tests__/message-block.streaming.test.tsx

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import { renderToStaticMarkup } from 'react-dom/server'
55
import { initializeThemeStore } from '../../hooks/use-theme'
66
import { chatThemes, createMarkdownPalette } from '../../utils/theme-system'
77
import { MessageBlock } from '../message-block'
8+
import { MessageActionsProvider } from '../../contexts/message-actions-context'
9+
import { ChatThemeProvider } from '../../contexts/chat-theme-context'
810

911
import type { MarkdownPalette } from '../../utils/markdown-renderer'
1012

@@ -34,41 +36,47 @@ const baseProps = {
3436
codeBlockWidth: 72,
3537
palette,
3638
},
37-
availableWidth: 80,
38-
markdownPalette: basePalette,
39-
collapsedAgents: new Set<string>(),
40-
autoCollapsedAgents: new Set<string>(),
4139
streamingAgents: new Set<string>(),
40+
}
41+
42+
const messageActions = {
4243
onToggleCollapsed: () => {},
4344
onBuildFast: () => {},
4445
onBuildMax: () => {},
45-
setCollapsedAgents: () => {},
46-
addAutoCollapsedAgent: () => {},
46+
onFeedback: () => {},
47+
onCloseFeedback: () => {},
4748
}
4849

50+
const createThemeContext = (timerStartTime: number | null) => ({
51+
theme,
52+
markdownPalette: basePalette,
53+
availableWidth: 80,
54+
timerStartTime,
55+
})
56+
4957
const createTimerStartTime = (elapsedSeconds: number): number | null =>
5058
elapsedSeconds > 0 ? Date.now() - elapsedSeconds * 1000 : null
5159

5260
describe('MessageBlock streaming indicator', () => {
5361
test('shows elapsed seconds while streaming', () => {
5462
const markup = renderToStaticMarkup(
55-
<MessageBlock
56-
{...baseProps}
57-
isLoading={true}
58-
timerStartTime={createTimerStartTime(4)}
59-
/>,
63+
<MessageActionsProvider value={messageActions}>
64+
<ChatThemeProvider value={createThemeContext(createTimerStartTime(4))}>
65+
<MessageBlock {...baseProps} isLoading={true} />
66+
</ChatThemeProvider>
67+
</MessageActionsProvider>,
6068
)
6169

6270
expect(markup).toContain('4s')
6371
})
6472

6573
test('hides elapsed seconds when timer has not advanced', () => {
6674
const markup = renderToStaticMarkup(
67-
<MessageBlock
68-
{...baseProps}
69-
isLoading={true}
70-
timerStartTime={createTimerStartTime(0)}
71-
/>,
75+
<MessageActionsProvider value={messageActions}>
76+
<ChatThemeProvider value={createThemeContext(createTimerStartTime(0))}>
77+
<MessageBlock {...baseProps} isLoading={true} />
78+
</ChatThemeProvider>
79+
</MessageActionsProvider>,
7280
)
7381

7482
expect(markup).not.toContain('0s')

cli/src/components/chat-scroll-area.tsx

Lines changed: 1 addition & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import React from 'react'
22

33
import { MessageWithAgents } from './message-with-agents'
4-
import type { ChatMessage, ContentBlock } from '../types/chat'
5-
import type { FileTreeNode } from '@codebuff/common/util/file'
4+
import type { ChatMessage } from '../types/chat'
65
import type { ScrollBoxRenderable } from '@opentui/core'
76
import type { Dispatch, SetStateAction } from 'react'
87

@@ -12,20 +11,11 @@ interface ChatScrollAreaProps {
1211
headerContent: React.ReactNode
1312
virtualizationNotice: React.ReactNode
1413
topLevelMessages: ChatMessage[]
15-
markdownPalette: any
1614
streamingAgents: Set<string>
1715
messageTree: Map<string, ChatMessage[]>
1816
messages: ChatMessage[]
19-
separatorWidth: number
20-
theme: any
2117
setFocusedAgentId: Dispatch<SetStateAction<string | null>>
2218
isWaitingForResponse: boolean
23-
timerStartTime: number | null
24-
handleCollapseToggle: (id: string) => void
25-
handleBuildFast: () => void
26-
handleBuildMax: () => void
27-
handleMessageFeedback: (id: string) => void
28-
handleCloseFeedback: () => void
2919
}
3020

3121
export const ChatScrollArea = ({
@@ -34,20 +24,11 @@ export const ChatScrollArea = ({
3424
headerContent,
3525
virtualizationNotice,
3626
topLevelMessages,
37-
markdownPalette,
3827
streamingAgents,
3928
messageTree,
4029
messages,
41-
separatorWidth,
42-
theme,
4330
setFocusedAgentId,
4431
isWaitingForResponse,
45-
timerStartTime,
46-
handleCollapseToggle,
47-
handleBuildFast,
48-
handleBuildMax,
49-
handleMessageFeedback,
50-
handleCloseFeedback,
5132
}: ChatScrollAreaProps) => {
5233
return (
5334
<scrollbox
@@ -94,20 +75,11 @@ export const ChatScrollArea = ({
9475
message={message}
9576
depth={0}
9677
isLastMessage={isLast}
97-
theme={theme}
98-
markdownPalette={markdownPalette}
9978
streamingAgents={streamingAgents}
10079
messageTree={messageTree}
10180
messages={messages}
102-
availableWidth={separatorWidth}
10381
setFocusedAgentId={setFocusedAgentId}
10482
isWaitingForResponse={isWaitingForResponse}
105-
timerStartTime={timerStartTime}
106-
onToggleCollapsed={handleCollapseToggle}
107-
onBuildFast={handleBuildFast}
108-
onBuildMax={handleBuildMax}
109-
onFeedback={handleMessageFeedback}
110-
onCloseFeedback={handleCloseFeedback}
11183
/>
11284
)
11385
})}

cli/src/components/message-block.tsx

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import { FeedbackIconButton } from './feedback-icon-button'
77
import { useTheme } from '../hooks/use-theme'
88
import { useWhyDidYouUpdateById } from '../hooks/use-why-did-you-update'
99
import { type MarkdownPalette } from '../utils/markdown-renderer'
10+
import { useMessageActions } from '../contexts/message-actions-context'
11+
import { useChatTheme } from '../contexts/chat-theme-context'
1012
import {
1113
useFeedbackStore,
1214
selectIsFeedbackOpenForMessage,
@@ -32,18 +34,10 @@ interface MessageBlockProps {
3234
isComplete?: boolean
3335
completionTime?: string
3436
credits?: number
35-
timerStartTime: number | null
3637
textColor?: ThemeColor
3738
timestampColor: string
3839
markdownOptions: { codeBlockWidth: number; palette: MarkdownPalette }
39-
availableWidth: number
40-
markdownPalette: MarkdownPalette
4140
streamingAgents: Set<string>
42-
onToggleCollapsed: (id: string) => void
43-
onBuildFast: () => void
44-
onBuildMax: () => void
45-
onFeedback?: (messageId: string) => void
46-
onCloseFeedback?: () => void
4741
}
4842

4943
const trimTrailingNewlines = (value: string): string =>
@@ -64,19 +58,13 @@ export const MessageBlock = memo((props: MessageBlockProps): ReactNode => {
6458
isComplete,
6559
completionTime,
6660
credits,
67-
timerStartTime,
6861
textColor,
6962
timestampColor,
7063
markdownOptions,
71-
availableWidth,
72-
markdownPalette,
7364
streamingAgents,
74-
onToggleCollapsed,
75-
onBuildFast,
76-
onBuildMax,
77-
onFeedback,
78-
onCloseFeedback,
7965
} = props
66+
const { availableWidth, markdownPalette, timerStartTime } = useChatTheme()
67+
const { onToggleCollapsed, onBuildFast, onBuildMax, onFeedback, onCloseFeedback } = useMessageActions()
8068
useWhyDidYouUpdateById('MessageBlock', messageId, props, {
8169
logLevel: 'debug',
8270
enabled: false,

0 commit comments

Comments
 (0)