authorgravatar for 118054752+extoci@users.noreply.github.comExotic <118054752+extoci@users.noreply.github.com> 2026-09-04 01:23:01+03:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-09-03 15:23:01-07:00
log77138cf33194a303adfbbd69cc93efa69f84245d
treefcc15483469abf1455334f3e2d802add58bc6332
parent0869ad648b67a286d7a47af878f1d256d0ff689f
signature Signed by PGP key B5690EEEBB952194

fix(web): dont collapse composer when interacting with bottom row (#9490)


7 files changed, 23 insertions(+), 7 deletions(-)

apps/web/src/components/BranchToolbar.tsx+2-1
...@@ -40,6 +40,7 @@ import {...@@ -40,6 +40,7 @@ import {
40} from "./ui/menu";40} from "./ui/menu";
41import { Separator } from "./ui/separator";41import { Separator } from "./ui/separator";
42import { ComposerSurface } from "./chat/ComposerSurface";42import { ComposerSurface } from "./chat/ComposerSurface";
43import { composerFloatingLayerProps } from "./chat/composerEventScope";
43import { measureRestingComposerControls } from "./chat/restingComposerControlsMeasurement";44import { measureRestingComposerControls } from "./chat/restingComposerControlsMeasurement";
44import { resolveRestingComposerControlsNaturalWidth } from "./composerFooterLayout";45import { resolveRestingComposerControlsNaturalWidth } from "./composerFooterLayout";
45import { cn } from "~/lib/utils";46import { cn } from "~/lib/utils";
...@@ -160,7 +161,7 @@ const MobileRunContextSelector = memo(function MobileRunContextSelector({...@@ -160,7 +161,7 @@ const MobileRunContextSelector = memo(function MobileRunContextSelector({
160 {triggerContent}161 {triggerContent}
161 <ChevronDownIcon className="size-3 shrink-0 opacity-50" />162 <ChevronDownIcon className="size-3 shrink-0 opacity-50" />
162 </MenuTrigger>163 </MenuTrigger>
163 <MenuPopup align="start" side="top" className="w-64">164 <MenuPopup align="start" side="top" className="w-64" {...composerFloatingLayerProps}>
164 {showEnvironmentPicker && availableEnvironments && onEnvironmentChange ? (165 {showEnvironmentPicker && availableEnvironments && onEnvironmentChange ? (
165 <>166 <>
166 <MenuGroup>167 <MenuGroup>
apps/web/src/components/BranchToolbarBranchSelector.tsx+7-1
...@@ -34,6 +34,7 @@ import { vcsEnvironment } from "../state/vcs";...@@ -34,6 +34,7 @@ import { vcsEnvironment } from "../state/vcs";
34import { cn } from "../lib/utils";34import { cn } from "../lib/utils";
35import { parsePullRequestReference } from "../pullRequestReference";35import { parsePullRequestReference } from "../pullRequestReference";
36import { getSourceControlPresentation } from "../sourceControlPresentation";36import { getSourceControlPresentation } from "../sourceControlPresentation";
37import { composerFloatingLayerProps } from "./chat/composerEventScope";
37import {38import {
38 deriveLocalBranchNameFromRemoteRef,39 deriveLocalBranchNameFromRemoteRef,
39 resolveBranchTriggerLabel,40 resolveBranchTriggerLabel,
...@@ -788,7 +789,12 @@ export function BranchToolbarBranchSelector({...@@ -788,7 +789,12 @@ export function BranchToolbarBranchSelector({
788 </ComboboxTrigger>789 </ComboboxTrigger>
789 </span>790 </span>
790 </div>791 </div>
791 <ComboboxPopup align="end" side="top" className="flex w-80 flex-col">792 <ComboboxPopup
793 align="end"
794 side="top"
795 className="flex w-80 flex-col"
796 {...composerFloatingLayerProps}
797 >
792 <div className="shrink-0 px-3 pt-2.5">798 <div className="shrink-0 px-3 pt-2.5">
793 <div className="relative -translate-y-px border-b border-border/70 pb-1.5 transition-colors focus-within:border-ring">799 <div className="relative -translate-y-px border-b border-border/70 pb-1.5 transition-colors focus-within:border-ring">
794 <SearchIcon800 <SearchIcon
apps/web/src/components/BranchToolbarEnvModeSelector.tsx+2-1
...@@ -7,6 +7,7 @@ import {...@@ -7,6 +7,7 @@ import {
7 resolveLockedWorkspaceLabel,7 resolveLockedWorkspaceLabel,
8 type EnvMode,8 type EnvMode,
9} from "./BranchToolbar.logic";9} from "./BranchToolbar.logic";
10import { composerFloatingLayerProps } from "./chat/composerEventScope";
10import {11import {
11 Select,12 Select,
12 SelectGroup,13 SelectGroup,
...@@ -113,7 +114,7 @@ export const BranchToolbarEnvModeSelector = memo(function BranchToolbarEnvModeSe...@@ -113,7 +114,7 @@ export const BranchToolbarEnvModeSelector = memo(function BranchToolbarEnvModeSe
113 </span>114 </span>
114 </span>115 </span>
115 </SelectTrigger>116 </SelectTrigger>
116 <SelectPopup>117 <SelectPopup {...composerFloatingLayerProps}>
117 <SelectGroup>118 <SelectGroup>
118 <SelectGroupLabel>Workspace</SelectGroupLabel>119 <SelectGroupLabel>Workspace</SelectGroupLabel>
119 <SelectItem value="local">120 <SelectItem value="local">
apps/web/src/components/BranchToolbarEnvironmentSelector.tsx+2-1
...@@ -3,6 +3,7 @@ import { memo, useMemo } from "react";...@@ -3,6 +3,7 @@ import { memo, useMemo } from "react";
33
4import type { EnvironmentOption } from "./BranchToolbar.logic";4import type { EnvironmentOption } from "./BranchToolbar.logic";
5import { EnvironmentMachineIcon } from "./EnvironmentMachineIcon";5import { EnvironmentMachineIcon } from "./EnvironmentMachineIcon";
6import { composerFloatingLayerProps } from "./chat/composerEventScope";
6import {7import {
7 Select,8 Select,
8 SelectGroup,9 SelectGroup,
...@@ -101,7 +102,7 @@ export const BranchToolbarEnvironmentSelector = memo(function BranchToolbarEnvir...@@ -101,7 +102,7 @@ export const BranchToolbarEnvironmentSelector = memo(function BranchToolbarEnvir
101 </span>102 </span>
102 </span>103 </span>
103 </SelectTrigger>104 </SelectTrigger>
104 <SelectPopup>105 <SelectPopup {...composerFloatingLayerProps}>
105 <SelectGroup>106 <SelectGroup>
106 <SelectGroupLabel>Run on</SelectGroupLabel>107 <SelectGroupLabel>Run on</SelectGroupLabel>
107 {availableEnvironments.map((env) => (108 {availableEnvironments.map((env) => (
apps/web/src/components/chat/ChatComposer.tsx+2-3
...@@ -4283,7 +4283,7 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps)...@@ -4283,7 +4283,7 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps)
4283 const composerSurface = composerSurfaceRef.current;4283 const composerSurface = composerSurfaceRef.current;
4284 const composerForm = composerFormRef.current;4284 const composerForm = composerFormRef.current;
4285 const activeElement = document.activeElement;4285 const activeElement = document.activeElement;
4286 if (activeElement instanceof Element && isInsideComposerFloatingLayer(activeElement)) {4286 if (isInsideRestingComposerControlScope(activeElement)) {
4287 return;4287 return;
4288 }4288 }
4289 if (4289 if (
...@@ -4303,8 +4303,7 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps)...@@ -4303,8 +4303,7 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps)
4303 const isInsideDesktopComposerFocusScope = (target: EventTarget | null) =>4303 const isInsideDesktopComposerFocusScope = (target: EventTarget | null) =>
4304 Boolean(4304 Boolean(
4305 target instanceof Node &&4305 target instanceof Node &&
4306 (composerFormRef.current?.contains(target) ||4306 (composerFormRef.current?.contains(target) || isInsideRestingComposerControlScope(target)),
4307 (target instanceof Element && isInsideComposerFloatingLayer(target))),
4308 );4307 );
4309 const handleFocusIn = (event: FocusEvent) => {4308 const handleFocusIn = (event: FocusEvent) => {
4310 if (!isInsideDesktopComposerFocusScope(event.target)) {4309 if (!isInsideDesktopComposerFocusScope(event.target)) {
apps/web/src/components/chat/composerEventScope.test.ts+7
...@@ -29,6 +29,13 @@ describe("composer event scopes", () => {...@@ -29,6 +29,13 @@ describe("composer event scopes", () => {
29 expect(isInsideRestingComposerControlScope(target as unknown as EventTarget)).toBe(true);29 expect(isInsideRestingComposerControlScope(target as unknown as EventTarget)).toBe(true);
30 });30 });
3131
32 it("recognizes events from the composer context strip controls", () => {
33 vi.stubGlobal("Element", FakeElement);
34
35 const target = new FakeElement("[data-composer-context-control]");
36 expect(isInsideRestingComposerControlScope(target as unknown as EventTarget)).toBe(true);
37 });
38
32 it("keeps resting image previews focused without expanding their subtree", () => {39 it("keeps resting image previews focused without expanding their subtree", () => {
33 vi.stubGlobal("Element", FakeElement);40 vi.stubGlobal("Element", FakeElement);
3441
apps/web/src/components/chat/composerEventScope.ts+1
...@@ -26,6 +26,7 @@ export function isInsideRestingComposerControlScope(target: EventTarget | null):...@@ -26,6 +26,7 @@ export function isInsideRestingComposerControlScope(target: EventTarget | null):
26 target instanceof Element &&26 target instanceof Element &&
27 (target.closest('[data-chat-composer-resting-controls="true"]') !== null ||27 (target.closest('[data-chat-composer-resting-controls="true"]') !== null ||
28 target.closest('[data-chat-composer-resting-images="true"]') !== null ||28 target.closest('[data-chat-composer-resting-images="true"]') !== null ||
29 target.closest("[data-composer-context-control]") !== null ||
29 isInsideComposerFloatingLayer(target))30 isInsideComposerFloatingLayer(target))
30 );31 );
31}32}