Skip to content

Commit 9274552

Browse files
committed
refactor: move validation errors from global banner to per-message popover
- Remove global validation error banner from app.tsx and chat.tsx - Add ValidationErrorPopover component that attaches to user messages - Add [!] button to user messages with validation errors - Refactor validation error formatting and display logic - Add validationErrors field to ChatMessage type - Create validation-error-helpers utility for filtering network errors - Update use-send-message to attach errors to user messages instead of creating error messages Note: This changes the UX for displaying validation errors but introduces a regression where initial startup validation errors are no longer visible. This will be addressed in a follow-up commit.
1 parent 071bb93 commit 9274552

14 files changed

+474
-373
lines changed

cli/src/app.tsx

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ import { NetworkError, RETRYABLE_ERROR_CODES } from '@codebuff/sdk'
2020
import type { AuthStatus } from './utils/status-indicator-state'
2121
import { getProjectRoot } from './project-files'
2222
import { useChatStore } from './state/chat-store'
23-
import { createValidationErrorBlocks } from './utils/create-validation-error-blocks'
2423
import { openFileAtPath } from './utils/open-file'
2524

2625
import type { MultilineInputHandle } from './components/multiline-input'
@@ -198,33 +197,13 @@ export const App = ({
198197
/>
199198
</box>
200199
) : null}
201-
{validationErrors.length > 0 && (
202-
<box style={{ flexDirection: 'column', gap: 0 }}>
203-
{createValidationErrorBlocks({
204-
errors: validationErrors,
205-
loadedAgentsData,
206-
availableWidth: separatorWidth,
207-
}).map((block, idx) => {
208-
if (block.type === 'html') {
209-
return (
210-
<box key={`validation-error-${idx}`}>
211-
{block.render({ textColor: theme.foreground, theme })}
212-
</box>
213-
)
214-
}
215-
return null
216-
})}
217-
</box>
218-
)}
219200
</box>
220201
)
221202
}, [
222203
loadedAgentsData,
223204
logoBlock,
224205
theme,
225206
isAgentListCollapsed,
226-
validationErrors,
227-
separatorWidth,
228207
])
229208

230209
// Derive auth reachability + retrying state inline from authQuery error

cli/src/chat.tsx

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ import { useSuggestionMenuHandlers } from './hooks/use-suggestion-menu-handlers'
3737
import { useTerminalDimensions } from './hooks/use-terminal-dimensions'
3838
import { useTheme } from './hooks/use-theme'
3939
import { useTimeout } from './hooks/use-timeout'
40-
import { useValidationBanner } from './hooks/use-validation-banner'
40+
4141
import { useChatStore } from './state/chat-store'
4242
import { useFeedbackStore } from './state/feedback-store'
4343
import { createChatScrollAcceleration } from './utils/chat-scroll-accel'
@@ -651,6 +651,7 @@ export const Chat = ({
651651
resumeQueue,
652652
continueChat,
653653
continueChatId,
654+
onOpenFeedback: () => handleOpenFeedbackForMessage(null),
654655
})
655656

656657
sendMessageRef.current = sendMessage
@@ -720,16 +721,30 @@ export const Chat = ({
720721
}, [cursorPosition])
721722

722723
const handleOpenFeedbackForMessage = useCallback(
723-
(id: string | null) => {
724+
(
725+
id: string | null,
726+
options?: {
727+
category?: string
728+
footerMessage?: string
729+
errors?: Array<{ id: string; message: string }>
730+
},
731+
) => {
724732
saveCurrentInput(inputValueRef.current, cursorPositionRef.current)
725-
openFeedbackForMessage(id)
733+
openFeedbackForMessage(id, options)
726734
},
727735
[saveCurrentInput, openFeedbackForMessage],
728736
)
729737

730738
const handleMessageFeedback = useCallback(
731-
(id: string) => {
732-
handleOpenFeedbackForMessage(id)
739+
(
740+
id: string,
741+
options?: {
742+
category?: string
743+
footerMessage?: string
744+
errors?: Array<{ id: string; message: string }>
745+
},
746+
) => {
747+
handleOpenFeedbackForMessage(id, options)
733748
},
734749
[handleOpenFeedbackForMessage],
735750
)
@@ -945,11 +960,6 @@ export const Chat = ({
945960
[handleOpenFeedbackForLatestMessage, feedbackMode],
946961
),
947962
)
948-
const validationBanner = useValidationBanner({
949-
liveValidationErrors: validationErrors,
950-
loadedAgentsData,
951-
theme,
952-
})
953963

954964
return (
955965
<box
@@ -1036,6 +1046,7 @@ export const Chat = ({
10361046
backgroundColor: 'transparent',
10371047
}}
10381048
>
1049+
10391050
{shouldShowStatusLine && (
10401051
<StatusBar
10411052
statusMessage={statusMessage}
@@ -1081,8 +1092,6 @@ export const Chat = ({
10811092
handleSubmit={handleSubmit}
10821093
/>
10831094
</box>
1084-
1085-
{validationBanner}
10861095
</box>
10871096
)
10881097
}

cli/src/components/feedback-container.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ export const FeedbackContainer: React.FC<FeedbackContainerProps> = ({
2626
feedbackCursor,
2727
feedbackCategory,
2828
feedbackMessageId,
29+
feedbackFooterMessage,
30+
errors,
2931
setFeedbackText,
3032
setFeedbackCursor,
3133
setFeedbackCategory,
@@ -40,6 +42,8 @@ export const FeedbackContainer: React.FC<FeedbackContainerProps> = ({
4042
feedbackCursor: state.feedbackCursor,
4143
feedbackCategory: state.feedbackCategory,
4244
feedbackMessageId: state.feedbackMessageId,
45+
feedbackFooterMessage: state.feedbackFooterMessage,
46+
errors: state.errors,
4347
setFeedbackText: state.setFeedbackText,
4448
setFeedbackCursor: state.setFeedbackCursor,
4549
setFeedbackCategory: state.setFeedbackCategory,
@@ -106,6 +110,7 @@ export const FeedbackContainer: React.FC<FeedbackContainerProps> = ({
106110
text,
107111
category: feedbackCategory,
108112
type: feedbackMessageId ? 'message' : 'general',
113+
errors,
109114
},
110115
runState,
111116
},
@@ -127,6 +132,7 @@ export const FeedbackContainer: React.FC<FeedbackContainerProps> = ({
127132
feedbackText,
128133
feedbackMessageId,
129134
feedbackCategory,
135+
errors,
130136
buildMessageContext,
131137
agentMode,
132138
sessionCreditsUsed,
@@ -176,6 +182,7 @@ export const FeedbackContainer: React.FC<FeedbackContainerProps> = ({
176182
onCategoryChange={setFeedbackCategory}
177183
inputRef={inputRef}
178184
width={width}
185+
footerMessage={feedbackFooterMessage}
179186
/>
180187
)
181188
}

cli/src/components/feedback-input-mode.tsx

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ interface FeedbackInputModeProps {
142142
onClear: () => void
143143
inputRef?: React.MutableRefObject<any>
144144
width: number
145+
footerMessage?: string | null
145146
}
146147

147148
export const FeedbackInputMode: React.FC<FeedbackInputModeProps> = ({
@@ -156,6 +157,7 @@ export const FeedbackInputMode: React.FC<FeedbackInputModeProps> = ({
156157
onClear,
157158
inputRef: externalInputRef,
158159
width,
160+
footerMessage,
159161
}) => {
160162
const theme = useTheme()
161163
const internalInputRef = useRef<MultilineInputHandle | null>(null)
@@ -215,21 +217,23 @@ export const FeedbackInputMode: React.FC<FeedbackInputModeProps> = ({
215217
flexDirection: 'row',
216218
alignItems: 'center',
217219
justifyContent: 'space-between',
220+
marginTop: 1,
218221
}}
219222
>
220-
<text style={{ wrapMode: 'none' }}>
223+
<text style={{ wrapMode: 'none', marginLeft: 1, marginRight: 1 }}>
221224
<span fg={theme.secondary}>
222-
Share feedback — thanks for helping us improve!
225+
Share your feedback — thanks for helping us improve!
223226
</span>
224227
</text>
225228
<box
229+
style={{ paddingRight: 1 }}
226230
onMouseDown={onCancel}
227231
onMouseOver={() => setCloseButtonHovered(true)}
228232
onMouseOut={() => setCloseButtonHovered(false)}
229233
>
230234
<text style={{ wrapMode: 'none' }} selectable={false}>
231-
<span fg={closeButtonHovered ? theme.foreground : theme.muted}>
232-
X
235+
<span fg={closeButtonHovered ? theme.foreground : theme.secondary}>
236+
[x]
233237
</span>
234238
</text>
235239
</box>
@@ -308,7 +312,9 @@ export const FeedbackInputMode: React.FC<FeedbackInputModeProps> = ({
308312
}}
309313
>
310314
<text style={{ wrapMode: 'none' }}>
311-
<span fg={theme.muted}>Session details are auto-attached</span>
315+
<span fg={theme.muted}>
316+
{footerMessage || 'Session details are auto-attached'}
317+
</span>
312318
</text>
313319
<Button
314320
onClick={() => {

0 commit comments

Comments
 (0)