Skip to content

Commit e1cc080

Browse files
authored
🥅 fix: Keep Mobile Composer Actions In Bounds (#15825)
1 parent d510b6f commit e1cc080

4 files changed

Lines changed: 42 additions & 13 deletions

File tree

client/src/components/Chat/Input/ChatForm.tsx

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -745,6 +745,11 @@ const ChatForm = memo(function ChatForm({
745745
onFocus={handleTextareaFocus}
746746
onBlur={handleTextareaBlur}
747747
aria-label={localize('com_ui_message_input')}
748+
aria-describedby={
749+
codeWorkspace.state === 'choose' || codeWorkspace.state === 'missing'
750+
? `code-workspace-hint-${index}`
751+
: undefined
752+
}
748753
onClick={handleFocusOrClick}
749754
style={{ height: 44, overflowY: 'auto' }}
750755
className={cn(
@@ -763,13 +768,22 @@ const ChatForm = memo(function ChatForm({
763768
</div>
764769
</div>
765770
)}
771+
{(codeWorkspace.state === 'choose' || codeWorkspace.state === 'missing') && (
772+
<p
773+
id={`code-workspace-hint-${index}`}
774+
role="status"
775+
className="px-5 pb-2 text-sm text-text-secondary"
776+
>
777+
{localize('com_error_code_workspace_required')}
778+
</p>
779+
)}
766780
<div
767781
className={cn(
768-
'@container items-between flex gap-2 pb-2',
782+
'@container flex flex-wrap items-center gap-2 px-2 pb-2',
769783
isRTL ? 'flex-row-reverse' : 'flex-row',
770784
)}
771785
>
772-
<div className={`${isRTL ? 'mr-2' : 'ml-2'}`}>
786+
<div className="shrink-0">
773787
<AttachFileChat
774788
conversation={conversation}
775789
disableInputs={disableInputs}
@@ -808,7 +822,7 @@ const ChatForm = memo(function ChatForm({
808822
{index === 0 && conversationId != null && (
809823
<PendingToolApprovalButton conversationId={conversationId} />
810824
)}
811-
<div className="mx-auto flex" />
825+
<div className="grow" />
812826
<TokenUsage index={index} conversation={conversation} isSubmitting={isSubmitting} />
813827
{SpeechToText && (
814828
<AudioRecorder
@@ -821,7 +835,7 @@ const ChatForm = memo(function ChatForm({
821835
{steering.duringRunActive &&
822836
steering.canControlGeneration &&
823837
(textValue?.trim() ?? '') !== '' && (
824-
<div className={`${isRTL ? 'ml-2' : 'mr-2'}`}>
838+
<div className="shrink-0">
825839
<InterruptSteerButton
826840
steering={steering}
827841
getText={() => methods.getValues('text')}
@@ -830,7 +844,7 @@ const ChatForm = memo(function ChatForm({
830844
/>
831845
</div>
832846
)}
833-
<div className={`${isRTL ? 'ml-2' : 'mr-2'}`}>
847+
<div className={cn('shrink-0', isRTL ? 'mr-auto' : 'ml-auto')}>
834848
{isSubmitting &&
835849
(showStopButton || steering.duringRunActive) &&
836850
!answerMode.composerAnswers

client/src/components/Chat/Input/CodeApprovalMenu.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,15 +80,17 @@ export default function CodeApprovalMenu({
8080
)}`}
8181
className={cn(
8282
composerControlClasses(),
83-
'px-2.5 md:px-theme-normal',
83+
'min-w-0 max-w-full px-2.5 md:px-theme-normal',
8484
isOpen && 'bg-surface-hover',
8585
disabled && 'cursor-not-allowed opacity-50',
8686
)}
8787
/>
8888
}
8989
>
9090
<SelectedIcon className="size-4 shrink-0 text-text-secondary" aria-hidden="true" />
91-
<span className="max-w-[12rem] truncate">{localize(modeOptions[selected].label)}</span>
91+
<span className="min-w-0 max-w-[12rem] truncate">
92+
{localize(modeOptions[selected].label)}
93+
</span>
9294
<ChevronDown
9395
className={cn(
9496
'size-3 shrink-0 text-text-secondary transition-transform',
@@ -103,7 +105,7 @@ export default function CodeApprovalMenu({
103105
unmountOnHide={true}
104106
className={cn(
105107
'z-50 flex min-w-[280px] max-w-[min(320px,calc(100vw-2rem))] flex-col rounded-xl',
106-
'border border-border-light bg-presentation p-1.5 shadow-lg',
108+
'max-h-[var(--popover-available-height)] overflow-y-auto border border-border-light bg-presentation p-1.5 shadow-lg',
107109
'origin-bottom opacity-0 transition-[opacity,transform] duration-200 ease-out',
108110
'data-[enter]:scale-100 data-[enter]:opacity-100',
109111
'scale-95 data-[leave]:scale-95 data-[leave]:opacity-0',

client/src/components/Chat/Input/CodeWorkspaceMenu.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -90,15 +90,15 @@ export default function CodeWorkspaceMenu({
9090
aria-label={`${localize('com_ui_code_workspace')}: ${label}`}
9191
className={cn(
9292
composerControlClasses(),
93-
'px-2.5 md:px-theme-normal',
93+
'min-w-0 max-w-full px-2.5 md:px-theme-normal',
9494
isOpen && 'bg-surface-hover',
9595
(disabled || !canChoose) && 'cursor-not-allowed opacity-50',
9696
)}
9797
/>
9898
}
9999
>
100100
<Icon className="size-4 shrink-0 text-text-secondary" aria-hidden="true" />
101-
<span className="max-w-[12rem] truncate">{label}</span>
101+
<span className="min-w-0 max-w-[12rem] truncate">{label}</span>
102102
{canChoose && (
103103
<ChevronDown
104104
className={cn(
@@ -116,7 +116,7 @@ export default function CodeWorkspaceMenu({
116116
unmountOnHide={true}
117117
className={cn(
118118
'z-50 flex min-w-[280px] max-w-[min(360px,calc(100vw-2rem))] flex-col rounded-xl',
119-
'border border-border-light bg-presentation p-1.5 shadow-lg',
119+
'max-h-[var(--popover-available-height)] overflow-y-auto border border-border-light bg-presentation p-1.5 shadow-lg',
120120
'origin-bottom opacity-0 transition-[opacity,transform] duration-200 ease-out',
121121
'data-[enter]:scale-100 data-[enter]:opacity-100',
122122
'scale-95 data-[leave]:scale-95 data-[leave]:opacity-0',

client/src/hooks/Agents/__tests__/useCodeWorkspace.test.tsx

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ describe('useCodeWorkspace', () => {
156156
expect(result.current.selections).toBeUndefined();
157157
});
158158

159-
it('requires an explicit choice when several workspaces are advertised', () => {
159+
it('requires a workspace in full access mode and enables submission after explicit selection', () => {
160160
mockStatus.mockReturnValue([
161161
{
162162
data: {
@@ -170,12 +170,25 @@ describe('useCodeWorkspace', () => {
170170
},
171171
]);
172172

173-
const { result } = renderHook(() => useCodeWorkspace(conversation()));
173+
const initialConversation: TConversation = {
174+
...conversation(),
175+
codeApprovalMode: 'fullAccess',
176+
};
177+
const { result, rerender } = renderHook(({ current }) => useCodeWorkspace(current), {
178+
initialProps: { current: initialConversation },
179+
});
174180

175181
expect(result.current.state).toBe('choose');
176182
expect(result.current.canSubmit).toBe(false);
177183
expect(result.current.selections).toBeUndefined();
178184
expect(result.current.resolveSubmission()).toBeUndefined();
185+
186+
const selection = { environmentId: 'personal-vm', workspaceId: 'canary-a' };
187+
rerender({ current: { ...initialConversation, codeWorkspaces: [selection] } });
188+
189+
expect(result.current.state).toBe('ready');
190+
expect(result.current.canSubmit).toBe(true);
191+
expect(result.current.resolveSubmission([selection])).toEqual({ codeWorkspaces: [selection] });
179192
});
180193

181194
it('submits an explicit selection from several advertised workspaces', () => {

0 commit comments

Comments
 (0)