History

fix(web): align thread details panel controls and menus (#3606)

maria committed 4 months ago

Commit 7bf0a2c1 parent 2f774f811 files · +81 −19Browse files

Unified · Split

11 files
  1. apps/server/src/claudeModelOptions.test.ts +1 −1
  2. apps/server/src/orchestration-v2/ProviderSelectionTransition.test.ts +1 −1
  3. apps/web/src/components/BranchToolbarBranchSelector.tsx +5 −1
  4. apps/web/src/components/BranchToolbarEnvModeSelector.tsx +11 −3
  5. apps/web/src/components/BranchToolbarEnvironmentSelector.tsx +14 −3
  6. apps/web/src/components/GitActionsControl.tsx +8 −1
  7. apps/web/src/components/ProjectScriptsControl.tsx +10 −2
  8. apps/web/src/components/chat/OpenInPicker.tsx +11 −3
  9. apps/web/src/components/chat/ThreadRelationshipsControl.tsx +5 −3
  10. apps/web/src/components/chat/glassPopupStyles.ts +6 −0
  11. apps/web/src/components/chat/threadDetailsPanelStyles.ts +9 −1
apps/server/src/claudeModelOptions.test.ts+1−1
@@ -1,4 +1,4 @@
1−import { describe, expect, it } from "vitest";
1+import { describe, expect, it } from "@effect/vitest";
22
33 import { ProviderInstanceId, type ModelSelection } from "@t3tools/contracts";
44
apps/server/src/orchestration-v2/ProviderSelectionTransition.test.ts+1−1
@@ -1,4 +1,4 @@
1−import { describe, expect, it } from "vitest";
1+import { describe, expect, it } from "@effect/vitest";
22
33 import {
44 type ModelSelection,
apps/web/src/components/BranchToolbarBranchSelector.tsx+5−1
@@ -31,6 +31,7 @@ import { cn } from "../lib/utils"; · 30 unmodified lines
3131 import {
3232 THREAD_DETAILS_PANEL_CHEVRON_CLASS,
3333 THREAD_DETAILS_PANEL_ICON_CLASS,
34+ THREAD_DETAILS_PANEL_ROW_POPUP_CLASS,
3435 THREAD_DETAILS_PANEL_ROW_CLASS,
3536 } from "./chat/threadDetailsPanelStyles";
3637 import { parsePullRequestReference } from "../pullRequestReference";
@@ -730,7 +731,10 @@ export function BranchToolbarBranchSelector({ · 693 unmodified lines
730731 <ComboboxPopup
731732 align={displayMode === "panel" ? "start" : "end"}
732733 side={displayMode === "panel" ? "bottom" : "top"}
733− className="flex w-80 flex-col"
734+ className={cn(
735+ "flex flex-col",
736+ displayMode === "panel" ? THREAD_DETAILS_PANEL_ROW_POPUP_CLASS : "w-80",
737+ )}
734738 >
735739 <div className="shrink-0 px-3 pt-2.5">
736740 <div className="relative -translate-y-px border-b border-border/70 pb-1.5 transition-colors focus-within:border-ring">
apps/web/src/components/BranchToolbarEnvModeSelector.tsx+11−3
@@ -4,7 +4,8 @@ import { cn } from "../lib/utils"; · 3 unmodified lines
44 import {
55 THREAD_DETAILS_PANEL_ICON_CLASS,
66 THREAD_DETAILS_PANEL_LOCKED_ROW_CLASS,
7− THREAD_DETAILS_PANEL_ROW_CLASS,
7+ THREAD_DETAILS_PANEL_ROW_POPUP_CLASS,
8+ THREAD_DETAILS_PANEL_SELECT_ROW_CLASS,
89 } from "./chat/threadDetailsPanelStyles";
910
1011 import {
@@ -109,7 +110,7 @@ export const BranchToolbarEnvModeSelector = memo(function BranchToolbarEnvModeSe · 98 unmodified lines
109110 size={displayMode === "panel" ? "default" : "xs"}
110111 className={cn(
111112 "font-medium",
112− displayMode === "panel" && THREAD_DETAILS_PANEL_ROW_CLASS,
113+ displayMode === "panel" && THREAD_DETAILS_PANEL_SELECT_ROW_CLASS,
113114 )}
114115 aria-label="Workspace"
115116 />
@@ -137,7 +138,14 @@ export const BranchToolbarEnvModeSelector = memo(function BranchToolbarEnvModeSe · 21 unmodified lines
137138 </TooltipTrigger>
138139 {workspacePath ? <TooltipPopup side="left">{workspacePath}</TooltipPopup> : null}
139140 </Tooltip>
140− <SelectPopup>
141+ <SelectPopup
142+ {...(displayMode === "panel"
143+ ? {
144+ alignItemWithTrigger: false,
145+ popupClassName: THREAD_DETAILS_PANEL_ROW_POPUP_CLASS,
146+ }
147+ : {})}
148+ >
141149 <SelectGroup>
142150 <SelectGroupLabel>Workspace</SelectGroupLabel>
143151 <SelectItem value="local">
apps/web/src/components/BranchToolbarEnvironmentSelector.tsx+14−3
@@ -7,7 +7,8 @@ import { cn } from "../lib/utils"; · 6 unmodified lines
77 import {
88 THREAD_DETAILS_PANEL_ICON_CLASS,
99 THREAD_DETAILS_PANEL_LOCKED_ROW_CLASS,
10− THREAD_DETAILS_PANEL_ROW_CLASS,
10+ THREAD_DETAILS_PANEL_ROW_POPUP_CLASS,
11+ THREAD_DETAILS_PANEL_SELECT_ROW_CLASS,
1112 } from "./chat/threadDetailsPanelStyles";
1213 import {
1314 Select,
@@ -79,7 +80,10 @@ export const BranchToolbarEnvironmentSelector = memo(function BranchToolbarEnvir · 65 unmodified lines
7980 <SelectTrigger
8081 variant="ghost"
8182 size={displayMode === "panel" ? "default" : "xs"}
82− className={cn("font-medium", displayMode === "panel" && THREAD_DETAILS_PANEL_ROW_CLASS)}
83+ className={cn(
84+ "font-medium",
85+ displayMode === "panel" && THREAD_DETAILS_PANEL_SELECT_ROW_CLASS,
86+ )}
8387 aria-label="Run on"
8488 >
8589 {activeEnvironment?.isPrimary ? (
@@ -93,7 +97,14 @@ export const BranchToolbarEnvironmentSelector = memo(function BranchToolbarEnvir · 7 unmodified lines
9397 )}
9498 <SelectValue />
9599 </SelectTrigger>
96− <SelectPopup>
100+ <SelectPopup
101+ {...(displayMode === "panel"
102+ ? {
103+ alignItemWithTrigger: false,
104+ popupClassName: THREAD_DETAILS_PANEL_ROW_POPUP_CLASS,
105+ }
106+ : {})}
107+ >
97108 <SelectGroup>
98109 <SelectGroupLabel>Run on</SelectGroupLabel>
99110 {availableEnvironments.map((env) => (
apps/web/src/components/GitActionsControl.tsx+8−1
@@ -91,6 +91,7 @@ import { readLocalApi } from "~/localApi"; · 90 unmodified lines
9191 import {
9292 THREAD_DETAILS_PANEL_CHEVRON_CLASS,
9393 THREAD_DETAILS_PANEL_ICON_CLASS,
94+ THREAD_DETAILS_PANEL_ROW_POPUP_CLASS,
9495 THREAD_DETAILS_PANEL_ROW_CLASS,
9596 THREAD_DETAILS_PANEL_SPLIT_GROUP_CLASS,
9697 THREAD_DETAILS_PANEL_SPLIT_PRIMARY_CLASS,
@@ -989,6 +990,7 @@ export default function GitActionsControl({ · 892 unmodified lines
989990 }: GitActionsControlProps) {
990991 const isPanel = displayMode === "panel";
991992 const ActionGroup = isPanel ? "div" : Group;
993+ const panelAnchorRef = useRef<HTMLDivElement | null>(null);
992994 const updateThreadMetadata = useAtomCommand(
993995 threadEnvironment.updateMetadata,
994996 "thread branch metadata update",
@@ -1706,6 +1708,7 @@ export default function GitActionsControl({ · 711 unmodified lines
17061708 <ActionGroup
17071709 role="group"
17081710 aria-label="Git actions"
1711+ {...(isPanel ? { ref: panelAnchorRef } : {})}
17091712 className={cn("shrink-0", isPanel && THREAD_DETAILS_PANEL_SPLIT_GROUP_CLASS)}
17101713 >
17111714 {quickActionDisabledReason ? (
@@ -1793,7 +1796,11 @@ export default function GitActionsControl({ · 81 unmodified lines
17931796 className={isPanel ? THREAD_DETAILS_PANEL_CHEVRON_CLASS : "size-4"}
17941797 />
17951798 </MenuTrigger>
1796− <MenuPopup align="end" className="w-full">
1799+ <MenuPopup
1800+ align="end"
1801+ {...(isPanel ? { anchor: panelAnchorRef } : {})}
1802+ className={isPanel ? THREAD_DETAILS_PANEL_ROW_POPUP_CLASS : "w-full"}
1803+ >
17971804 {gitActionMenuItems.map((item) => {
17981805 const disabledReason = getMenuActionDisabledReason({
17991806 item,
apps/web/src/components/ProjectScriptsControl.tsx+10−2
@@ -63,6 +63,7 @@ import { cn } from "~/lib/utils"; · 62 unmodified lines
6363 import {
6464 THREAD_DETAILS_PANEL_CHEVRON_CLASS,
6565 THREAD_DETAILS_PANEL_ICON_CLASS,
66+ THREAD_DETAILS_PANEL_ROW_POPUP_CLASS,
6667 THREAD_DETAILS_PANEL_ROW_CLASS,
6768 THREAD_DETAILS_PANEL_SPLIT_GROUP_CLASS,
6869 THREAD_DETAILS_PANEL_SPLIT_PRIMARY_CLASS,
@@ -134,6 +135,7 @@ export default function ProjectScriptsControl({ · 65 unmodified lines
134135 }: ProjectScriptsControlProps) {
135136 const isPanel = displayMode === "panel";
136137 const ActionGroup = isPanel ? "div" : Group;
138+ const panelAnchorRef = React.useRef<HTMLDivElement | null>(null);
137139 const addScriptFormId = React.useId();
138140 const [editingScriptId, setEditingScriptId] = useState<string | null>(null);
139141 const [dialogOpen, setDialogOpen] = useState(false);
@@ -267,7 +269,9 @@ export default function ProjectScriptsControl({ · 127 unmodified lines
267269 <ActionGroup
268270 role="group"
269271 aria-label="Project scripts"
270− {...(isPanel ? { className: THREAD_DETAILS_PANEL_SPLIT_GROUP_CLASS } : {})}
272+ {...(isPanel
273+ ? { className: THREAD_DETAILS_PANEL_SPLIT_GROUP_CLASS, ref: panelAnchorRef }
274+ : {})}
271275 >
272276 <Tooltip>
273277 <TooltipTrigger
@@ -316,7 +320,11 @@ export default function ProjectScriptsControl({ · 42 unmodified lines
316320 className={isPanel ? THREAD_DETAILS_PANEL_CHEVRON_CLASS : "size-4"}
317321 />
318322 </MenuTrigger>
319− <MenuPopup align="end">
323+ <MenuPopup
324+ align="end"
325+ {...(isPanel ? { anchor: panelAnchorRef } : {})}
326+ className={isPanel ? THREAD_DETAILS_PANEL_ROW_POPUP_CLASS : undefined}
327+ >
320328 {scripts.map((script) => {
321329 const shortcutLabel = shortcutLabelForCommand(
322330 keybindings,
apps/web/src/components/chat/OpenInPicker.tsx+11−3
@@ -1,5 +1,5 @@
11 import { EditorId, type EnvironmentId, type ResolvedKeybindingsConfig } from "@t3tools/contracts";
2−import { memo, useCallback, useMemo } from "react";
2+import { memo, useCallback, useMemo, useRef } from "react";
33 import { shortcutLabelForCommand } from "../../keybindings";
44 import { usePreferredEditor } from "../../editorPreferences";
55 import { ChevronDownIcon, FolderClosedIcon } from "lucide-react";
@@ -37,6 +37,7 @@ import { useAtomCommand } from "~/state/use-atom-command"; · 31 unmodified lines
3737 import {
3838 THREAD_DETAILS_PANEL_CHEVRON_CLASS,
3939 THREAD_DETAILS_PANEL_ICON_CLASS,
40+ THREAD_DETAILS_PANEL_ROW_POPUP_CLASS,
4041 THREAD_DETAILS_PANEL_SPLIT_GROUP_CLASS,
4142 THREAD_DETAILS_PANEL_SPLIT_PRIMARY_CLASS,
4243 THREAD_DETAILS_PANEL_SPLIT_SECONDARY_CLASS,
@@ -176,6 +177,7 @@ export const OpenInPicker = memo(function OpenInPicker({ · 133 unmodified lines
176177 }) {
177178 const isPanel = displayMode === "panel";
178179 const ActionGroup = isPanel ? "div" : Group;
180+ const panelAnchorRef = useRef<HTMLDivElement | null>(null);
179181 const openInEditorMutation = useAtomCommand(shellEnvironment.openInEditor, "open in editor");
180182 const [preferredEditor, setPreferredEditor] = usePreferredEditor(availableEditors);
181183 const options = useMemo(
@@ -212,7 +214,9 @@ export const OpenInPicker = memo(function OpenInPicker({ · 30 unmodified lines
212214 <ActionGroup
213215 aria-label="Open in editor"
214216 role="group"
215− {...(isPanel ? { className: THREAD_DETAILS_PANEL_SPLIT_GROUP_CLASS } : {})}
217+ {...(isPanel
218+ ? { className: THREAD_DETAILS_PANEL_SPLIT_GROUP_CLASS, ref: panelAnchorRef }
219+ : {})}
216220 >
217221 <Button
218222 aria-label={compact ? "Open file in preferred editor" : primaryLabel}
@@ -260,7 +264,11 @@ export const OpenInPicker = memo(function OpenInPicker({ · 41 unmodified lines
260264 className={isPanel ? THREAD_DETAILS_PANEL_CHEVRON_CLASS : "size-4"}
261265 />
262266 </MenuTrigger>
263− <MenuPopup align="end">
267+ <MenuPopup
268+ align="end"
269+ {...(isPanel ? { anchor: panelAnchorRef } : {})}
270+ className={isPanel ? THREAD_DETAILS_PANEL_ROW_POPUP_CLASS : undefined}
271+ >
264272 {options.length === 0 && <MenuItem disabled>No installed editors found</MenuItem>}
265273 {options.map(({ label, Icon, value }) => (
266274 <MenuItem key={value} onClick={() => openInEditor(value)}>
apps/web/src/components/chat/ThreadRelationshipsControl.tsx+5−3
@@ -30,15 +30,17 @@ import { Button } from "../ui/button"; · 29 unmodified lines
3030 import { Menu, MenuItem, MenuPopup, MenuTrigger } from "../ui/menu";
3131 import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip";
3232 import {
33− THREAD_DETAILS_PANEL_ICON_CLASS,
3433 THREAD_DETAILS_PANEL_ICON_ACTION_CLASS,
3534 THREAD_DETAILS_PANEL_LINK_ROW_CLASS,
3635 THREAD_DETAILS_PANEL_LINK_SPLIT_GROUP_CLASS,
3736 THREAD_DETAILS_PANEL_LINK_SPLIT_PRIMARY_CLASS,
3837 THREAD_DETAILS_PANEL_LINK_SPLIT_SECONDARY_CLASS,
38+ THREAD_DETAILS_PANEL_MENU_POPUP_CLASS,
3939 THREAD_DETAILS_PANEL_SPLIT_SEPARATOR_CLASS,
4040 } from "./threadDetailsPanelStyles";
4141
42+const THREAD_RELATIONSHIP_ICON_CLASS = "size-4 shrink-0 text-muted-foreground";
43+
4244 function relationshipLabel(edge: ThreadRelationshipEdge, currentThreadId: ThreadId) {
4345 if (edge.kind === "transfer") return "Context transfer";
4446 if (edge.kind === "subagent") {
@@ -190,7 +192,7 @@ export function ThreadRelationshipsPanel(props: { · 145 unmodified lines
190192 >
191193 <MoreHorizontalIcon className="size-3.5" />
192194 </MenuTrigger>
193− <MenuPopup align="end">
195+ <MenuPopup align="end" className={THREAD_DETAILS_PANEL_MENU_POPUP_CLASS}>
194196 <MenuItem onClick={() => void detach()}>
195197 <UnplugIcon className="size-3.5" />
196198 Disconnect agent session
@@ -221,7 +223,7 @@ export function ThreadRelationshipsPanel(props: { · 24 unmodified lines
221223 const relationshipContent = (
222224 <>
223225 <span className="relative -mx-0.5 grid size-4 shrink-0 place-items-center">
224− <RelationshipIcon className={THREAD_DETAILS_PANEL_ICON_CLASS} />
226+ <RelationshipIcon className={THREAD_RELATIONSHIP_ICON_CLASS} />
225227 <span
226228 className={cn(
227229 "absolute -bottom-1 -right-1 size-2 rounded-full border-2 border-card",
apps/web/src/components/chat/glassPopupStyles.ts+6−0
@@ -0,0 +1,6 @@
1+export const CHAT_GLASS_POPUP_SURFACE_CLASS =
2+ "floating-glass-surface !bg-[color-mix(in_srgb,var(--card)_20%,transparent)] dark:!bg-[color-mix(in_srgb,var(--card)_45%,transparent)]";
3+
4+export const CHAT_GLASS_ROW_POPUP_CLASS = `${CHAT_GLASS_POPUP_SURFACE_CLASS} w-(--anchor-width)`;
5+
6+export const CHAT_GLASS_MENU_POPUP_CLASS = `${CHAT_GLASS_POPUP_SURFACE_CLASS} min-w-60 max-w-(--available-width)`;
apps/web/src/components/chat/threadDetailsPanelStyles.ts+9−1
@@ -1,3 +1,5 @@
1+import { CHAT_GLASS_MENU_POPUP_CLASS, CHAT_GLASS_ROW_POPUP_CLASS } from "./glassPopupStyles";
2+
13 /**
24 * Visual primitives shared by controls when they are rendered in the thread details panel.
35 *
@@ -19,6 +21,8 @@ const THREAD_DETAILS_PANEL_SPLIT_BUTTON_SURFACE_CLASS = `${THREAD_DETAILS_PANEL_ · 15 unmodified lines
1921
2022 export const THREAD_DETAILS_PANEL_ROW_CLASS = `h-9 w-full justify-start gap-2.5 rounded-lg border-transparent bg-transparent px-2.5 text-[13px] font-medium text-foreground/80 sm:h-9 sm:text-[13px] ${THREAD_DETAILS_PANEL_ROW_SURFACE_CLASS}`;
2123
24+export const THREAD_DETAILS_PANEL_SELECT_ROW_CLASS = `${THREAD_DETAILS_PANEL_ROW_CLASS} pe-0 before:pointer-events-none before:absolute before:inset-0 [&_[data-slot=select-icon]]:-me-px [&_[data-slot=select-icon]]:relative [&_[data-slot=select-icon]]:flex [&_[data-slot=select-icon]]:h-full [&_[data-slot=select-icon]]:w-8 [&_[data-slot=select-icon]]:shrink-0 [&_[data-slot=select-icon]]:items-center [&_[data-slot=select-icon]]:justify-center [&_[data-slot=select-icon]]:before:absolute [&_[data-slot=select-icon]]:before:-left-px [&_[data-slot=select-icon]]:before:top-1/2 [&_[data-slot=select-icon]]:before:h-4 [&_[data-slot=select-icon]]:before:w-px [&_[data-slot=select-icon]]:before:-translate-y-1/2 [&_[data-slot=select-icon]]:before:bg-border/65 [&_[data-slot=select-icon]>svg]:me-0 [&_[data-slot=select-icon]>svg]:size-4 [&_[data-slot=select-icon]>svg]:text-muted-foreground [&_[data-slot=select-icon]>svg]:opacity-100`;
25+
2226 export const THREAD_DETAILS_PANEL_LINK_ROW_CLASS = `flex h-9 min-w-0 flex-1 cursor-pointer items-center justify-start gap-2.5 rounded-lg border border-transparent bg-transparent px-2.5 text-left text-[13px] font-medium text-foreground/80 disabled:cursor-not-allowed disabled:opacity-55 sm:h-9 sm:text-[13px] ${THREAD_DETAILS_PANEL_ROW_SURFACE_CLASS}`;
2327
2428 export const THREAD_DETAILS_PANEL_LINK_SPLIT_GROUP_CLASS = `group/thread-details-link flex w-full items-center rounded-lg transition-colors ${THREAD_DETAILS_PANEL_HOVER_SURFACE_CLASS}`;
@@ -30,7 +34,7 @@ export const THREAD_DETAILS_PANEL_LINK_SPLIT_SECONDARY_CLASS = `h-9 w-8 shrink-0 · 5 unmodified lines
3034 export const THREAD_DETAILS_PANEL_LOCKED_ROW_CLASS =
3135 "h-9 w-full justify-start gap-2.5 rounded-lg border border-transparent px-2.5 text-[13px] font-medium text-foreground/80 sm:h-9 sm:text-[13px]";
3236
33−export const THREAD_DETAILS_PANEL_ICON_CLASS = "size-4 shrink-0 text-muted-foreground";
37+export const THREAD_DETAILS_PANEL_ICON_CLASS = "-mx-0.5 size-4 shrink-0 text-muted-foreground";
3438
3539 export const THREAD_DETAILS_PANEL_CHEVRON_CLASS = "size-4 shrink-0 text-muted-foreground";
3640
@@ -43,3 +47,7 @@ export const THREAD_DETAILS_PANEL_SPLIT_PRIMARY_CLASS = `h-9 min-w-0 flex-1 just · 6 unmodified lines
4347 export const THREAD_DETAILS_PANEL_SPLIT_SECONDARY_CLASS = `h-9 w-8 rounded-s-none border-transparent bg-transparent px-0 sm:h-9 sm:w-8 ${THREAD_DETAILS_PANEL_SPLIT_BUTTON_SURFACE_CLASS}`;
4448
4549 export const THREAD_DETAILS_PANEL_SPLIT_SEPARATOR_CLASS = "h-4 w-px shrink-0 bg-border/65";
50+
51+export const THREAD_DETAILS_PANEL_ROW_POPUP_CLASS = CHAT_GLASS_ROW_POPUP_CLASS;
52+
53+export const THREAD_DETAILS_PANEL_MENU_POPUP_CLASS = CHAT_GLASS_MENU_POPUP_CLASS;