authorgravatar for 97427849+flamboh@users.noreply.github.comoliver <97427849+flamboh@users.noreply.github.com> 2026-09-16 15:07:08-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-09-16 19:07:08-03:00
logdda44ed33280106aa46612fd9e1f1475bb5a0712
treeceff4a641e602cb98ad089f103b716cb08f9857b
parent01e64193d9d2abfae8f7b3d20f66824e38397bd6
signature Signed by PGP key B5690EEEBB952194

fix(web): show tooltips for composer environment and workspace controls (#11787)


3 files changed, 132 insertions(+), 90 deletions(-)

apps/web/src/components/BranchToolbar.tsx+24-10
...@@ -53,6 +53,7 @@ import {...@@ -53,6 +53,7 @@ import {
53 MenuTrigger,53 MenuTrigger,
54} from "./ui/menu";54} from "./ui/menu";
55import { Separator } from "./ui/separator";55import { Separator } from "./ui/separator";
56import { Tooltip, TooltipPopup, TooltipTrigger } from "./ui/tooltip";
56import { ComposerSurface } from "./chat/ComposerSurface";57import { ComposerSurface } from "./chat/ComposerSurface";
57import { useComposerMenuProps } from "./chat/composerEventScope";58import { useComposerMenuProps } from "./chat/composerEventScope";
58import { measureRestingComposerControls } from "./chat/restingComposerControlsMeasurement";59import { measureRestingComposerControls } from "./chat/restingComposerControlsMeasurement";
...@@ -137,22 +138,35 @@ const MobileRunContextSelector = memo(function MobileRunContextSelector({...@@ -137,22 +138,35 @@ const MobileRunContextSelector = memo(function MobileRunContextSelector({
137 ? resolveEnvModeLabel("worktree")138 ? resolveEnvModeLabel("worktree")
138 : resolveCurrentWorkspaceLabel(activeWorktreePath);139 : resolveCurrentWorkspaceLabel(activeWorktreePath);
139 const isLocked = envLocked || envModeLocked;140 const isLocked = envLocked || envModeLocked;
141 const workspaceIcon = (
142 <Tooltip>
143 <TooltipTrigger render={<span className="inline-flex shrink-0" />}>
144 <WorkspaceIcon className={cn("size-3 shrink-0", showEnvironmentIndicator && "mx-0!")} />
145 </TooltipTrigger>
146 <TooltipPopup>{workspaceLabel}</TooltipPopup>
147 </Tooltip>
148 );
140 const icon = showEnvironmentIndicator ? (149 const icon = showEnvironmentIndicator ? (
141 // Button's base styles apply `-mx-0.5` to descendant SVGs, which eats 4px150 // Button's base styles apply `-mx-0.5` to descendant SVGs, which eats 4px
142 // out of whatever gap we set. mx-0! cancels that so gap-0.5 reads as 2px.151 // out of whatever gap we set. mx-0! cancels that so gap-0.5 reads as 2px.
143 <span className="inline-flex shrink-0 items-center gap-0.5">152 <span className="inline-flex shrink-0 items-center gap-0.5">
144 {autoEnvironmentLabel ? (153 <Tooltip>
145 <ScaleIcon className="size-3 shrink-0 mx-0!" aria-hidden="true" />154 <TooltipTrigger render={<span className="inline-flex shrink-0" />}>
146 ) : (155 {autoEnvironmentLabel ? (
147 <EnvironmentMachineIcon156 <ScaleIcon className="size-3 shrink-0 mx-0!" aria-hidden="true" />
148 kind={activeEnvironment?.machine ?? "server"}157 ) : (
149 className="size-3 shrink-0 mx-0!"158 <EnvironmentMachineIcon
150 />159 kind={activeEnvironment?.machine ?? "server"}
151 )}160 className="size-3 shrink-0 mx-0!"
152 <WorkspaceIcon className="size-3 shrink-0 mx-0!" />161 />
162 )}
163 </TooltipTrigger>
164 <TooltipPopup>{autoEnvironmentLabel ?? activeEnvironment?.label ?? "Run on"}</TooltipPopup>
165 </Tooltip>
166 {workspaceIcon}
153 </span>167 </span>
154 ) : (168 ) : (
155 <WorkspaceIcon className="size-3 shrink-0" />169 workspaceIcon
156 );170 );
157 const triggerContent = (171 const triggerContent = (
158 <>172 <>
apps/web/src/components/BranchToolbarEnvModeSelector.tsx+56-40
...@@ -17,6 +17,7 @@ import {...@@ -17,6 +17,7 @@ import {
17 SelectTrigger,17 SelectTrigger,
18 SelectValue,18 SelectValue,
19} from "./ui/select";19} from "./ui/select";
20import { Tooltip, TooltipPopup, TooltipTrigger } from "./ui/tooltip";
2021
21const PREVIOUS_WORKTREE_SELECT_VALUE = "previous-worktree";22const PREVIOUS_WORKTREE_SELECT_VALUE = "previous-worktree";
2223
...@@ -52,27 +53,31 @@ export const BranchToolbarEnvModeSelector = memo(function BranchToolbarEnvModeSe...@@ -52,27 +53,31 @@ export const BranchToolbarEnvModeSelector = memo(function BranchToolbarEnvModeSe
5253
53 if (envLocked) {54 if (envLocked) {
54 return (55 return (
55 <span56 <Tooltip>
56 className="inline-flex h-7 min-w-0 items-center gap-1 border border-transparent px-[calc(--spacing(2)-1px)] font-normal text-muted-foreground/70 text-xs sm:h-6"57 <TooltipTrigger
57 data-composer-context-control58 render={<span />}
58 >59 className="inline-flex h-7 min-w-0 items-center gap-1 border border-transparent px-[calc(--spacing(2)-1px)] font-normal text-muted-foreground/70 text-xs sm:h-6"
59 {activeWorktreePath ? (60 data-composer-context-control
60 <FolderGitIcon className="size-3 shrink-0" />
61 ) : (
62 <FolderIcon className="size-3 shrink-0" />
63 )}
64 <span
65 data-composer-label
66 className="min-w-0 max-w-[240px] group-data-[compact]/composer-context:max-w-0"
67 >61 >
62 {activeWorktreePath ? (
63 <FolderGitIcon className="size-3 shrink-0" />
64 ) : (
65 <FolderIcon className="size-3 shrink-0" />
66 )}
68 <span67 <span
69 data-composer-label-motion68 data-composer-label
70 className="block w-full min-w-0 max-w-[240px] truncate transition-opacity duration-180 ease-[cubic-bezier(0.32,0.72,0,1)] group-data-[compact]/composer-context:opacity-0 motion-reduce:transition-none"69 className="min-w-0 max-w-[240px] group-data-[compact]/composer-context:max-w-0"
71 >70 >
72 {resolveLockedWorkspaceLabel(activeWorktreePath)}71 <span
72 data-composer-label-motion
73 className="block w-full min-w-0 max-w-[240px] truncate transition-opacity duration-180 ease-[cubic-bezier(0.32,0.72,0,1)] group-data-[compact]/composer-context:opacity-0 motion-reduce:transition-none"
74 >
75 {resolveLockedWorkspaceLabel(activeWorktreePath)}
76 </span>
73 </span>77 </span>
74 </span>78 </TooltipTrigger>
75 </span>79 <TooltipPopup>{resolveLockedWorkspaceLabel(activeWorktreePath)}</TooltipPopup>
80 </Tooltip>
76 );81 );
77 }82 }
7883
...@@ -89,33 +94,44 @@ export const BranchToolbarEnvModeSelector = memo(function BranchToolbarEnvModeSe...@@ -89,33 +94,44 @@ export const BranchToolbarEnvModeSelector = memo(function BranchToolbarEnvModeSe
89 }}94 }}
90 items={envModeItems}95 items={envModeItems}
91 >96 >
92 <SelectTrigger97 <Tooltip>
93 variant="ghost"98 <TooltipTrigger
94 size="xs"99 render={
95 className="min-w-0 shrink font-normal text-xs!"100 <SelectTrigger
96 aria-label="Workspace"101 variant="ghost"
97 data-composer-shortcut="composer.workspace"102 size="xs"
98 data-composer-context-control103 className="min-w-0 shrink font-normal text-xs!"
99 >104 aria-label="Workspace"
100 {effectiveEnvMode === "worktree" ? (105 data-composer-shortcut="composer.workspace"
101 <FolderGit2Icon className="size-3" />106 data-composer-context-control
102 ) : activeWorktreePath ? (107 />
103 <FolderGitIcon className="size-3" />108 }
104 ) : (
105 <FolderIcon className="size-3" />
106 )}
107 <span
108 data-composer-label
109 className="min-w-0 max-w-[240px] group-data-[compact]/composer-context:max-w-0"
110 >109 >
110 {effectiveEnvMode === "worktree" ? (
111 <FolderGit2Icon className="size-3" />
112 ) : activeWorktreePath ? (
113 <FolderGitIcon className="size-3" />
114 ) : (
115 <FolderIcon className="size-3" />
116 )}
111 <span117 <span
112 data-composer-label-motion118 data-composer-label
113 className="block w-full min-w-0 max-w-[240px] truncate transition-opacity duration-180 ease-[cubic-bezier(0.32,0.72,0,1)] group-data-[compact]/composer-context:opacity-0 motion-reduce:transition-none"119 className="min-w-0 max-w-[240px] group-data-[compact]/composer-context:max-w-0"
114 >120 >
115 <SelectValue />121 <span
122 data-composer-label-motion
123 className="block w-full min-w-0 max-w-[240px] truncate transition-opacity duration-180 ease-[cubic-bezier(0.32,0.72,0,1)] group-data-[compact]/composer-context:opacity-0 motion-reduce:transition-none"
124 >
125 <SelectValue />
126 </span>
116 </span>127 </span>
117 </span>128 </TooltipTrigger>
118 </SelectTrigger>129 <TooltipPopup>
130 {effectiveEnvMode === "worktree"
131 ? resolveEnvModeLabel("worktree")
132 : resolveCurrentWorkspaceLabel(activeWorktreePath)}
133 </TooltipPopup>
134 </Tooltip>
119 <SelectPopup alignItemWithTrigger={false} {...composerFloatingLayerProps}>135 <SelectPopup alignItemWithTrigger={false} {...composerFloatingLayerProps}>
120 <SelectGroup>136 <SelectGroup>
121 <SelectGroupLabel>Workspace</SelectGroupLabel>137 <SelectGroupLabel>Workspace</SelectGroupLabel>
apps/web/src/components/BranchToolbarEnvironmentSelector.tsx+52-40
...@@ -14,6 +14,7 @@ import {...@@ -14,6 +14,7 @@ import {
14 SelectTrigger,14 SelectTrigger,
15 SelectValue,15 SelectValue,
16} from "./ui/select";16} from "./ui/select";
17import { Tooltip, TooltipPopup, TooltipTrigger } from "./ui/tooltip";
1718
18interface BranchToolbarEnvironmentSelectorProps {19interface BranchToolbarEnvironmentSelectorProps {
19 autoEnvironmentLabel?: string | undefined;20 autoEnvironmentLabel?: string | undefined;
...@@ -59,26 +60,30 @@ export const BranchToolbarEnvironmentSelector = memo(function BranchToolbarEnvir...@@ -59,26 +60,30 @@ export const BranchToolbarEnvironmentSelector = memo(function BranchToolbarEnvir
59 // only thing in the strip.60 // only thing in the strip.
60 if (envLocked || onEnvironmentChange === undefined) {61 if (envLocked || onEnvironmentChange === undefined) {
61 return (62 return (
62 <span63 <Tooltip>
63 className="inline-flex h-7 min-w-0 max-w-full items-center gap-1 border border-transparent px-[calc(--spacing(2)-1px)] font-normal text-muted-foreground/70 text-xs sm:h-6"64 <TooltipTrigger
64 data-composer-context-control65 render={<span />}
65 >66 className="inline-flex h-7 min-w-0 max-w-full items-center gap-1 border border-transparent px-[calc(--spacing(2)-1px)] font-normal text-muted-foreground/70 text-xs sm:h-6"
66 <EnvironmentMachineIcon67 data-composer-context-control
67 kind={activeEnvironment?.machine ?? "server"}
68 className="size-3 shrink-0"
69 />
70 <span
71 data-composer-label
72 className="min-w-0 max-w-[240px] group-data-[compact]/composer-context:max-w-0"
73 >68 >
69 <EnvironmentMachineIcon
70 kind={activeEnvironment?.machine ?? "server"}
71 className="size-3 shrink-0"
72 />
74 <span73 <span
75 data-composer-label-motion74 data-composer-label
76 className="block w-full min-w-0 max-w-[240px] truncate transition-opacity duration-180 ease-[cubic-bezier(0.32,0.72,0,1)] group-data-[compact]/composer-context:opacity-0 motion-reduce:transition-none"75 className="min-w-0 max-w-[240px] group-data-[compact]/composer-context:max-w-0"
77 >76 >
78 {activeEnvironment?.label ?? "Run on"}77 <span
78 data-composer-label-motion
79 className="block w-full min-w-0 max-w-[240px] truncate transition-opacity duration-180 ease-[cubic-bezier(0.32,0.72,0,1)] group-data-[compact]/composer-context:opacity-0 motion-reduce:transition-none"
80 >
81 {activeEnvironment?.label ?? "Run on"}
82 </span>
79 </span>83 </span>
80 </span>84 </TooltipTrigger>
81 </span>85 <TooltipPopup>{activeEnvironment?.label ?? "Run on"}</TooltipPopup>
86 </Tooltip>
82 );87 );
83 }88 }
8489
...@@ -91,34 +96,41 @@ export const BranchToolbarEnvironmentSelector = memo(function BranchToolbarEnvir...@@ -91,34 +96,41 @@ export const BranchToolbarEnvironmentSelector = memo(function BranchToolbarEnvir
91 }96 }
92 items={environmentItems}97 items={environmentItems}
93 >98 >
94 <SelectTrigger99 <Tooltip>
95 variant="ghost"100 <TooltipTrigger
96 size="xs"101 render={
97 className="min-w-0 max-w-full font-normal text-xs!"102 <SelectTrigger
98 aria-label="Run on"103 variant="ghost"
99 data-composer-shortcut="composer.host"104 size="xs"
100 data-composer-context-control105 className="min-w-0 max-w-full font-normal text-xs!"
101 >106 aria-label="Run on"
102 {autoEnvironmentLabel ? (107 data-composer-shortcut="composer.host"
103 <ScaleIcon className="size-3 shrink-0" aria-hidden="true" />108 data-composer-context-control
104 ) : (109 />
105 <EnvironmentMachineIcon110 }
106 kind={activeEnvironment?.machine ?? "server"}
107 className="size-3 shrink-0"
108 />
109 )}
110 <span
111 data-composer-label
112 className="min-w-0 max-w-[240px] group-data-[compact]/composer-context:max-w-0"
113 >111 >
112 {autoEnvironmentLabel ? (
113 <ScaleIcon className="size-3 shrink-0" aria-hidden="true" />
114 ) : (
115 <EnvironmentMachineIcon
116 kind={activeEnvironment?.machine ?? "server"}
117 className="size-3 shrink-0"
118 />
119 )}
114 <span120 <span
115 data-composer-label-motion121 data-composer-label
116 className="block w-full min-w-0 max-w-[240px] truncate transition-opacity duration-180 ease-[cubic-bezier(0.32,0.72,0,1)] group-data-[compact]/composer-context:opacity-0 motion-reduce:transition-none"122 className="min-w-0 max-w-[240px] group-data-[compact]/composer-context:max-w-0"
117 >123 >
118 <SelectValue />124 <span
125 data-composer-label-motion
126 className="block w-full min-w-0 max-w-[240px] truncate transition-opacity duration-180 ease-[cubic-bezier(0.32,0.72,0,1)] group-data-[compact]/composer-context:opacity-0 motion-reduce:transition-none"
127 >
128 <SelectValue />
129 </span>
119 </span>130 </span>
120 </span>131 </TooltipTrigger>
121 </SelectTrigger>132 <TooltipPopup>{autoEnvironmentLabel ?? activeEnvironment?.label ?? "Run on"}</TooltipPopup>
133 </Tooltip>
122 <SelectPopup alignItemWithTrigger={false} {...composerFloatingLayerProps}>134 <SelectPopup alignItemWithTrigger={false} {...composerFloatingLayerProps}>
123 <SelectGroup>135 <SelectGroup>
124 <SelectGroupLabel>Run on</SelectGroupLabel>136 <SelectGroupLabel>Run on</SelectGroupLabel>