fix: require content to create workspace — no more empty submissions

Replace allowEmptySubmit/emptySubmitLabel with hasExternalContent prop
that treats GitHub items the same as text and images in submit gating.
This commit is contained in:
Mohamed Boudra
2026-04-14 17:28:31 +07:00
parent 89db6d3187
commit c80d7a8165
5 changed files with 17 additions and 46 deletions
@@ -3,7 +3,7 @@ export type AgentInputSubmitResult = "noop" | "queued" | "submitted" | "failed";
export interface AgentInputSubmitActionInput<TImage> {
message: string;
imageAttachments?: TImage[];
allowEmptySubmit?: boolean;
hasExternalContent?: boolean;
forceSend?: boolean;
isAgentRunning: boolean;
canSubmit: boolean;
@@ -23,7 +23,7 @@ export async function submitAgentInput<TImage>(
const trimmedMessage = input.message.trim();
const imageAttachments = input.imageAttachments;
if (!trimmedMessage && !imageAttachments?.length && !input.allowEmptySubmit) {
if (!trimmedMessage && !imageAttachments?.length && !input.hasExternalContent) {
return "noop";
}
+5 -8
View File
@@ -65,9 +65,8 @@ interface ComposerProps {
serverId: string;
isInputActive: boolean;
onSubmitMessage?: (payload: MessagePayload) => Promise<void>;
allowEmptySubmit?: boolean;
/** Label to show on the submit button when the input is empty and allowEmptySubmit is true. */
emptySubmitLabel?: string;
/** When true, the submit button is enabled even without text or images (e.g. external attachment selected). */
hasExternalContent?: boolean;
/** Externally controlled loading state. When true, disables the submit button. */
isSubmitLoading?: boolean;
/** When true, blurs the input immediately when submitting. */
@@ -105,8 +104,7 @@ export function Composer({
serverId,
isInputActive,
onSubmitMessage,
allowEmptySubmit = false,
emptySubmitLabel,
hasExternalContent = false,
isSubmitLoading = false,
blurOnSubmit = false,
value,
@@ -354,7 +352,7 @@ export function Composer({
await submitAgentInput({
message,
imageAttachments,
allowEmptySubmit,
hasExternalContent,
forceSend,
isAgentRunning: agentState.status === "running",
// Parent-managed submits are still valid submit paths even when the
@@ -746,8 +744,7 @@ export function Composer({
value={userInput}
onChangeText={setUserInput}
onSubmit={handleSubmit}
allowEmptySubmit={allowEmptySubmit}
emptySubmitLabel={emptySubmitLabel}
hasExternalContent={hasExternalContent}
isSubmitDisabled={isProcessing || isSubmitLoading}
isSubmitLoading={isProcessing || isSubmitLoading}
images={selectedImages}
+9 -33
View File
@@ -62,9 +62,8 @@ export interface MessageInputProps {
value: string;
onChangeText: (text: string) => void;
onSubmit: (payload: MessagePayload) => void;
allowEmptySubmit?: boolean;
/** Label to show on the submit button when the input is empty and allowEmptySubmit is true. */
emptySubmitLabel?: string;
/** When true, the submit button is enabled even without text or images (e.g. external attachment selected). */
hasExternalContent?: boolean;
isSubmitDisabled?: boolean;
isSubmitLoading?: boolean;
images?: ImageAttachment[];
@@ -201,8 +200,7 @@ export const MessageInput = forwardRef<MessageInputRef, MessageInputProps>(funct
value,
onChangeText,
onSubmit,
allowEmptySubmit = false,
emptySubmitLabel,
hasExternalContent = false,
isSubmitDisabled = false,
isSubmitLoading = false,
images = [],
@@ -573,7 +571,7 @@ export const MessageInput = forwardRef<MessageInputRef, MessageInputProps>(funct
const handleSendMessage = useCallback(() => {
const trimmed = value.trim();
if (!trimmed && images.length === 0 && !allowEmptySubmit) return;
if (!trimmed && images.length === 0 && !hasExternalContent) return;
const payload = {
text: trimmed,
images: images.length > 0 ? images : undefined,
@@ -583,7 +581,7 @@ export const MessageInput = forwardRef<MessageInputRef, MessageInputProps>(funct
inputHeightRef.current = MIN_INPUT_HEIGHT;
setInputHeight(MIN_INPUT_HEIGHT);
onHeightChange?.(MIN_INPUT_HEIGHT);
}, [value, images, onSubmit, isAgentRunning, onHeightChange, allowEmptySubmit]);
}, [value, images, onSubmit, isAgentRunning, onHeightChange, hasExternalContent]);
const handleQueueMessage = useCallback(() => {
if (!onQueue) return;
@@ -935,10 +933,8 @@ export const MessageInput = forwardRef<MessageInputRef, MessageInputProps>(funct
const hasImages = images.length > 0;
const hasRealContent = value.trim().length > 0 || hasImages;
const hasSendableContent = hasRealContent || allowEmptySubmit;
const hasSendableContent = hasRealContent || hasExternalContent;
const shouldShowSendButton = hasSendableContent || isSubmitLoading;
const showEmptySubmitLabel =
allowEmptySubmit && emptySubmitLabel && !hasRealContent && !isSubmitLoading;
const canPressLoadingButton = isSubmitLoading && typeof onSubmitLoadingPress === "function";
const isSendButtonDisabled =
disabled || (!canPressLoadingButton && (isSubmitDisabled || isSubmitLoading));
@@ -1170,19 +1166,15 @@ export const MessageInput = forwardRef<MessageInputRef, MessageInputProps>(funct
<TooltipTrigger
onPress={canPressLoadingButton ? onSubmitLoadingPress : handleDefaultSendAction}
disabled={isSendButtonDisabled}
accessibilityLabel={
showEmptySubmitLabel ? emptySubmitLabel : submitAccessibilityLabel
}
accessibilityLabel={submitAccessibilityLabel}
accessibilityRole="button"
style={[
showEmptySubmitLabel ? styles.emptySubmitButton : styles.sendButton,
styles.sendButton,
isSendButtonDisabled && styles.buttonDisabled,
]}
>
{isSubmitLoading ? (
<ActivityIndicator size="small" color="white" />
) : showEmptySubmitLabel ? (
<Text style={styles.emptySubmitLabel}>{emptySubmitLabel}</Text>
) : (
<ArrowUp size={buttonIconSize} color="white" />
)}
@@ -1190,11 +1182,7 @@ export const MessageInput = forwardRef<MessageInputRef, MessageInputProps>(funct
<TooltipContent side="top" align="center" offset={8}>
<View style={styles.tooltipRow}>
<Text style={styles.tooltipText}>
{showEmptySubmitLabel
? emptySubmitLabel
: defaultActionQueues
? "Queue"
: "Send"}
{defaultActionQueues ? "Queue" : "Send"}
</Text>
{sendKeys ? <Shortcut chord={sendKeys} style={styles.tooltipShortcut} /> : null}
</View>
@@ -1379,18 +1367,6 @@ const styles = StyleSheet.create(((theme: any) => ({
alignItems: "center",
justifyContent: "center",
},
emptySubmitButton: {
height: 28,
borderRadius: theme.borderRadius.full,
backgroundColor: theme.colors.accent,
alignItems: "center",
justifyContent: "center",
paddingHorizontal: theme.spacing[3],
},
emptySubmitLabel: {
color: "white",
fontSize: theme.fontSize.sm,
},
iconButtonHovered: {
backgroundColor: theme.colors.surface2,
},
@@ -289,8 +289,7 @@ export function NewWorkspaceScreen({
serverId={serverId}
isInputActive={true}
onSubmitMessage={handleCreateChatAgent}
allowEmptySubmit
emptySubmitLabel="Create"
hasExternalContent={selectedGithubItem !== null}
isSubmitLoading={pendingAction === "chat"}
blurOnSubmit={true}
value={chatDraft.text}
@@ -287,7 +287,6 @@ export function WorkspaceDraftAgentTab({
serverId={serverId}
isInputActive={isPaneFocused}
onSubmitMessage={handleCreateFromInput}
allowEmptySubmit={false}
isSubmitLoading={isSubmitting}
blurOnSubmit={true}
value={draftInput.text}