Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 26 additions & 4 deletions apps/web/src/components/BranchToolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import {
MenuTrigger,
} from "./ui/menu";
import { Separator } from "./ui/separator";
import { cn } from "~/lib/utils";

interface BranchToolbarProps {
environmentId: EnvironmentId;
Expand All @@ -57,6 +58,7 @@ interface BranchToolbarProps {
onComposerFocusRequest?: () => void;
availableEnvironments?: readonly EnvironmentOption[];
onEnvironmentChange?: (environmentId: EnvironmentId) => void;
composerControlsHostRef?: (element: HTMLDivElement | null) => void;
}

interface MobileRunContextSelectorProps {
Expand Down Expand Up @@ -126,7 +128,7 @@ const MobileRunContextSelector = memo(function MobileRunContextSelector({

if (isLocked) {
return (
<span className="inline-flex h-7 min-w-0 max-w-[48%] flex-1 items-center justify-start gap-1 rounded-md border border-transparent px-[calc(--spacing(2)-1px)] text-sm font-medium text-muted-foreground/70 sm:h-6 md:hidden">
<span className="inline-flex h-7 min-w-0 max-w-[48%] flex-1 items-center justify-start gap-1 rounded-md border border-transparent px-[calc(--spacing(2)-1px)] text-sm font-normal text-muted-foreground/70 sm:h-6 md:hidden">
{triggerContent}
</span>
);
Expand All @@ -136,7 +138,7 @@ const MobileRunContextSelector = memo(function MobileRunContextSelector({
<Menu>
<MenuTrigger
render={<Button variant="ghost" size="xs" />}
className="min-w-0 max-w-[48%] flex-1 justify-start text-muted-foreground/70 hover:text-foreground/80 md:hidden"
className="min-w-0 max-w-[48%] flex-1 justify-start font-normal text-muted-foreground/70 hover:text-foreground/80 md:hidden"
>
{triggerContent}
<ChevronDownIcon className="size-3 shrink-0 opacity-50" />
Expand Down Expand Up @@ -265,6 +267,7 @@ function useLabelsOverflow(element: HTMLDivElement | null): boolean {
let groups = 0;
for (const child of current.children) {
if (!(child instanceof HTMLElement) || child.offsetWidth <= 1) continue;
if (child.matches('[data-chat-resting-composer-controls-host="true"]')) continue;
needed += contentWidth(child);
groups += 1;
}
Expand Down Expand Up @@ -389,6 +392,7 @@ export const BranchToolbar = memo(function BranchToolbar({
onComposerFocusRequest,
availableEnvironments,
onEnvironmentChange,
composerControlsHostRef,
}: BranchToolbarProps) {
const threadRef = useMemo(
() => scopeThreadRef(environmentId, threadId),
Expand Down Expand Up @@ -469,7 +473,11 @@ export const BranchToolbar = memo(function BranchToolbar({
<div
ref={setStripElement}
data-compact={labelsOverflow ? "" : undefined}
className="chat-composer-context-strip group/composer-context -mt-4 mx-auto flex w-[calc(100%-2.75rem)] max-w-[calc(48rem-2.75rem)] items-center gap-2 overflow-x-clip overflow-y-visible ps-1 pe-2 pt-5 pb-1"
className={cn(
"chat-composer-context-strip group/composer-context -mt-4 mx-auto flex w-[calc(100%-2.75rem)] max-w-[calc(48rem-2.75rem)] items-center gap-1 overflow-x-clip overflow-y-visible ps-1 pe-2 pt-5 pb-1 text-xs font-normal text-muted-foreground/70",
"[&_[data-composer-context-control]]:font-normal",
"[&_svg:not([data-composer-control-chevron])]:!text-inherit",
)}
>
{isMobile && showGitControls ? (
<MobileRunContextSelector
Expand All @@ -487,7 +495,12 @@ export const BranchToolbar = memo(function BranchToolbar({
onUsePreviousWorktree={onUsePreviousWorktree}
/>
) : (
<div className="flex min-w-0 flex-1 items-center gap-1">
<div
className={cn(
"flex min-w-0 items-center gap-1",
composerControlsHostRef ? "shrink" : "flex-1",
)}
>
{showEnvironmentIndicator && availableEnvironments && (
<>
<BranchToolbarEnvironmentSelector
Expand Down Expand Up @@ -518,6 +531,15 @@ export const BranchToolbar = memo(function BranchToolbar({
</div>
)}

{composerControlsHostRef ? (
<div
ref={composerControlsHostRef}
data-composer-context-control
data-chat-resting-composer-controls-host="true"
className="hidden min-w-0 flex-1 items-center justify-start overflow-hidden empty:hidden md:flex"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The host is visible from md up, but the composer's resting layout starts at sm: BranchToolbar uses useIsMobile() (max-md) for its mobile row, while ChatComposer derives isMobileViewport from useMediaQuery("max-sm").

Between 640px and 767px the composer therefore rests, the inline footer controls get hidden, and this portal target is display: none — the model picker, plan/build toggle and runtime select are neither visible nor reachable (the measurement hook also bails out on clientWidth === 0). That window is easy to hit in a narrow Electron window or split view.

Consider making the two breakpoints agree: gate the resting layout on max-md as well, or expose this host (and a strip layout that fits it) from sm up.

Posted via Macroscope — UI Consistency

/>
) : null}

{showGitControls ? (
<BranchToolbarBranchSelector
className="min-w-0 flex-1 justify-end md:ml-auto md:flex-none"
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/components/BranchToolbarBranchSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -759,7 +759,7 @@ export function BranchToolbarBranchSelector({
>
<ComboboxTrigger
render={<Button variant="ghost" size="xs" />}
className="min-w-0 max-w-full text-muted-foreground/70 hover:text-foreground/80"
className="min-w-0 max-w-full font-normal text-muted-foreground/70 hover:text-foreground/80"
disabled={isInitialBranchesLoadPending || isBranchActionPending}
>
<GitBranchIcon className="size-3 shrink-0 opacity-70" />
Expand Down
4 changes: 2 additions & 2 deletions apps/web/src/components/BranchToolbarEnvModeSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export const BranchToolbarEnvModeSelector = memo(function BranchToolbarEnvModeSe
if (envLocked) {
return (
<span
className="inline-flex h-7 shrink-0 items-center gap-1 border border-transparent px-[calc(--spacing(3)-1px)] text-sm font-medium text-muted-foreground/70 sm:h-6 sm:text-xs"
className="inline-flex h-7 shrink-0 items-center gap-1 border border-transparent px-[calc(--spacing(2)-1px)] text-sm font-normal text-muted-foreground/70 sm:h-6 sm:text-xs"
data-composer-context-control
>
{activeWorktreePath ? (
Expand Down Expand Up @@ -85,7 +85,7 @@ export const BranchToolbarEnvModeSelector = memo(function BranchToolbarEnvModeSe
<SelectTrigger
variant="ghost"
size="xs"
className="min-w-0 shrink font-medium"
className="min-w-0 shrink font-normal"
aria-label="Workspace"
data-composer-context-control
>
Expand Down
4 changes: 2 additions & 2 deletions apps/web/src/components/BranchToolbarEnvironmentSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export const BranchToolbarEnvironmentSelector = memo(function BranchToolbarEnvir
if (envLocked || onEnvironmentChange === undefined) {
return (
<span
className="inline-flex h-7 min-w-0 max-w-full items-center gap-1 border border-transparent px-[calc(--spacing(3)-1px)] text-sm font-medium text-muted-foreground/70 sm:h-6 sm:text-xs"
className="inline-flex h-7 min-w-0 max-w-full items-center gap-1 border border-transparent px-[calc(--spacing(2)-1px)] text-sm font-normal text-muted-foreground/70 sm:h-6 sm:text-xs"
data-composer-context-control
>
{activeEnvironment?.isPrimary ? (
Expand Down Expand Up @@ -82,7 +82,7 @@ export const BranchToolbarEnvironmentSelector = memo(function BranchToolbarEnvir
<SelectTrigger
variant="ghost"
size="xs"
className="min-w-0 max-w-full font-medium"
className="min-w-0 max-w-full font-normal"
aria-label="Run on"
data-composer-context-control
>
Expand Down
4 changes: 4 additions & 0 deletions apps/web/src/components/ChatView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1379,6 +1379,8 @@ function ChatViewContent(props: ChatViewProps) {
const composerElementContextsRef = useRef<ElementContextDraft[]>([]);
const localComposerRef = useRef<ChatComposerHandle | null>(null);
const composerRef = useComposerHandleContext() ?? localComposerRef;
const [restingComposerControlsHost, setRestingComposerControlsHost] =
useState<HTMLDivElement | null>(null);
const [isWorkspaceFileDragActive, setIsWorkspaceFileDragActive] = useState(false);
const [showScrollToBottom, setShowScrollToBottom] = useState(false);
const [expandedImage, setExpandedImage] = useState<ExpandedImagePreview | null>(null);
Expand Down Expand Up @@ -6671,6 +6673,7 @@ function ChatViewContent(props: ChatViewProps) {
keybindings={keybindings}
terminalOpen={Boolean(terminalUiState.terminalOpen)}
gitCwd={gitCwd}
restingControlsHost={restingComposerControlsHost}
promptRef={promptRef}
composerImagesRef={composerImagesRef}
composerTerminalContextsRef={composerTerminalContextsRef}
Expand Down Expand Up @@ -6733,6 +6736,7 @@ function ChatViewContent(props: ChatViewProps) {
: {})}
{...(hasMultipleEnvironments ? { onEnvironmentChange } : {})}
availableEnvironments={logicalProjectEnvironments}
composerControlsHostRef={setRestingComposerControlsHost}
/>
</div>
)}
Expand Down
11 changes: 10 additions & 1 deletion apps/web/src/components/ComposerPromptEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -885,6 +885,7 @@ interface ComposerPromptEditorProps {
disabled: boolean;
placeholder: string;
className?: string;
placeholderClassName?: string;
onRemoveTerminalContext: (contextId: string) => void;
onChange: (
nextValue: string,
Expand Down Expand Up @@ -1534,6 +1535,7 @@ function ComposerPromptEditorInner({
disabled,
placeholder,
className,
placeholderClassName,
onRemoveTerminalContext,
onChange,
onCommandKeyDown,
Expand Down Expand Up @@ -1765,7 +1767,12 @@ function ComposerPromptEditorInner({
}
placeholder={
terminalContexts.length > 0 ? null : (
<div className="pointer-events-none absolute inset-0 leading-relaxed text-placeholder">
<div
className={cn(
"pointer-events-none absolute inset-0 leading-relaxed text-placeholder",
placeholderClassName,
)}
>
{placeholder}
</div>
)
Expand Down Expand Up @@ -1795,6 +1802,7 @@ export function ComposerPromptEditor({
disabled,
placeholder,
className,
placeholderClassName,
onRemoveTerminalContext,
onChange,
onCommandKeyDown,
Expand Down Expand Up @@ -1838,6 +1846,7 @@ export function ComposerPromptEditor({
editorRef={editorRef}
{...(onCommandKeyDown ? { onCommandKeyDown } : {})}
{...(className ? { className } : {})}
{...(placeholderClassName ? { placeholderClassName } : {})}
/>
</LexicalComposer>
);
Expand Down
Loading
Loading