authorgravatar for me@t3.ggTheo Browne <me@t3.gg> 2026-07-21 14:29:23-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-07-21 14:29:23-07:00
log23c18fda7a969634a30888e36b2da45f6d66a83b
tree49a65cdd7b888d6a7f4577521ab3744c2f22a8a4
parentc0bb2373450231e3931937d2f703e35ce906ed85
signature Signed by PGP key B5690EEEBB952194

fix(web): keep composer editable while disconnected (#4241)

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

1 files changed, 8 insertions(+), 12 deletions(-)

apps/web/src/components/chat/ChatComposer.tsx+8-12
...@@ -1159,6 +1159,7 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps)...@@ -1159,6 +1159,7 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps)
1159 isSendBusy ||1159 isSendBusy ||
1160 isConnecting ||1160 isConnecting ||
1161 projectSelectionRequired ||1161 projectSelectionRequired ||
1162 environmentUnavailable !== null ||
1162 !composerSendState.hasSendableContent;1163 !composerSendState.hasSendableContent;
1163 const collapsedComposerPrimaryActionLabel = "Send message";1164 const collapsedComposerPrimaryActionLabel = "Send message";
1164 const showMobilePendingAnswerActions =1165 const showMobilePendingAnswerActions =
...@@ -1697,7 +1698,9 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps)...@@ -1697,7 +1698,9 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps)
16971698
1698 const shouldBlurMobileComposerOnSubmit = useCallback(() => {1699 const shouldBlurMobileComposerOnSubmit = useCallback(() => {
1699 if (!isMobileViewport) return false;1700 if (!isMobileViewport) return false;
1700 if (isSendBusy || isConnecting || phase === "running") return false;1701 if (isSendBusy || isConnecting || environmentUnavailable !== null || phase === "running") {
1702 return false;
1703 }
1701 if (activePendingProgress) {1704 if (activePendingProgress) {
1702 return activePendingProgress.isLastQuestion && Boolean(activePendingResolvedAnswers);1705 return activePendingProgress.isLastQuestion && Boolean(activePendingResolvedAnswers);
1703 }1706 }
...@@ -1706,6 +1709,7 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps)...@@ -1706,6 +1709,7 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps)
1706 activePendingProgress,1709 activePendingProgress,
1707 activePendingResolvedAnswers,1710 activePendingResolvedAnswers,
1708 composerSendState.hasSendableContent,1711 composerSendState.hasSendableContent,
1712 environmentUnavailable,
1709 isConnecting,1713 isConnecting,
1710 isMobileViewport,1714 isMobileViewport,
1711 isSendBusy,1715 isSendBusy,
...@@ -1892,8 +1896,7 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps)...@@ -1892,8 +1896,7 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps)
1892 isConnecting ||1896 isConnecting ||
1893 isComposerApprovalState ||1897 isComposerApprovalState ||
1894 pendingUserInputs.length > 0 ||1898 pendingUserInputs.length > 0 ||
1895 projectSelectionRequired ||1899 projectSelectionRequired
1896 (environmentUnavailable !== null && activePendingProgress === null)
1897 ) {1900 ) {
1898 return false;1901 return false;
1899 }1902 }
...@@ -2101,8 +2104,6 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps)...@@ -2101,8 +2104,6 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps)
2101 isComposerApprovalState,2104 isComposerApprovalState,
2102 pendingUserInputs.length,2105 pendingUserInputs.length,
2103 projectSelectionRequired,2106 projectSelectionRequired,
2104 environmentUnavailable,
2105 activePendingProgress,
2106 applyPromptReplacement,2107 applyPromptReplacement,
2107 isComposerModelPickerOpen,2108 isComposerModelPickerOpen,
2108 readComposerSnapshot,2109 readComposerSnapshot,
...@@ -2144,7 +2145,7 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps)...@@ -2144,7 +2145,7 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps)
2144 className={cn(2145 className={cn(
2145 "chat-composer-glass rounded-[20px] border transition-[background-color] duration-200 has-focus-visible:border-ring/45",2146 "chat-composer-glass rounded-[20px] border transition-[background-color] duration-200 has-focus-visible:border-ring/45",
2146 isDragOverComposer ? "border-primary/70 bg-accent/45" : "border-border",2147 isDragOverComposer ? "border-primary/70 bg-accent/45" : "border-border",
2147 environmentUnavailable || projectSelectionRequired ? "opacity-75" : null,2148 projectSelectionRequired ? "opacity-75" : null,
2148 composerProviderState.composerSurfaceClassName,2149 composerProviderState.composerSurfaceClassName,
2149 )}2150 )}
2150 onFocusCapture={(event) => {2151 onFocusCapture={(event) => {
...@@ -2502,12 +2503,7 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps)...@@ -2502,12 +2503,7 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps)
2502 ? "Ask for follow-up changes or attach images"2503 ? "Ask for follow-up changes or attach images"
2503 : "Ask anything, @tag files/folders, $use skills, or / for commands"2504 : "Ask anything, @tag files/folders, $use skills, or / for commands"
2504 }2505 }
2505 disabled={2506 disabled={isConnecting || isComposerApprovalState || projectSelectionRequired}
2506 isConnecting ||
2507 isComposerApprovalState ||
2508 projectSelectionRequired ||
2509 (environmentUnavailable !== null && activePendingProgress === null)
2510 }
2511 />2507 />
2512 {showMobilePendingAnswerActions ? (2508 {showMobilePendingAnswerActions ? (
2513 <div2509 <div