authorgravatar for me@t3.ggTheo Browne <me@t3.gg> 2026-08-06 20:55:00-04:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-08-06 17:55:00-07:00
log64a991ad455234e6fdd808a5a3caadc51f18a152
tree6092e27e03ee32be715d335488f358349e05f934
parent0ec4fbc4a376cbf6465e59b05506ce9d89b3d078
signature Signed by PGP key B5690EEEBB952194

fix(web): show remote environment for non-Git projects (#5555)


4 files changed, 93 insertions(+), 26 deletions(-)

apps/web/src/components/BranchToolbar.logic.test.ts+33
......@@ -17,6 +17,7 @@ import {
1717 resolvePreviousWorktreeLabel,
1818 resolvePreviousWorktreeSeed,
1919 shouldIncludeBranchPickerItem,
20 shouldShowComposerContextStrip,
2021 shouldShowEnvironmentIndicator,
2122} from "./BranchToolbar.logic";
2223
......@@ -421,6 +422,38 @@ describe("shouldShowEnvironmentIndicator", () => {
421422 });
422423});
423424
425describe("shouldShowComposerContextStrip", () => {
426 it("keeps the environment indicator visible for a non-Git project", () => {
427 expect(
428 shouldShowComposerContextStrip({
429 hasActiveProject: true,
430 isGitRepo: false,
431 showEnvironmentIndicator: true,
432 }),
433 ).toBe(true);
434 });
435
436 it("hides the strip when a non-Git project has no environment indicator", () => {
437 expect(
438 shouldShowComposerContextStrip({
439 hasActiveProject: true,
440 isGitRepo: false,
441 showEnvironmentIndicator: false,
442 }),
443 ).toBe(false);
444 });
445
446 it("shows Git controls without requiring an environment indicator", () => {
447 expect(
448 shouldShowComposerContextStrip({
449 hasActiveProject: true,
450 isGitRepo: true,
451 showEnvironmentIndicator: false,
452 }),
453 ).toBe(true);
454 });
455});
456
424457describe("resolveEffectiveEnvMode", () => {
425458 it("treats draft threads already attached to a worktree as current-checkout mode", () => {
426459 expect(
apps/web/src/components/BranchToolbar.logic.ts+8
......@@ -54,6 +54,14 @@ export function shouldShowEnvironmentIndicator(input: {
5454 return input.activeEnvironment !== null && !input.activeEnvironment.isPrimary;
5555}
5656
57export function shouldShowComposerContextStrip(input: {
58 hasActiveProject: boolean;
59 isGitRepo: boolean;
60 showEnvironmentIndicator: boolean;
61}): boolean {
62 return input.hasActiveProject && (input.isGitRepo || input.showEnvironmentIndicator);
63}
64
5765export function resolveEnvModeLabel(mode: EnvMode): string {
5866 return mode === "worktree" ? "New worktree" : "Current checkout";
5967}
apps/web/src/components/BranchToolbar.tsx+32-24
......@@ -44,6 +44,7 @@ import { Separator } from "./ui/separator";
4444interface BranchToolbarProps {
4545 environmentId: EnvironmentId;
4646 threadId: ThreadId;
47 showGitControls: boolean;
4748 draftId?: DraftId;
4849 onEnvModeChange: (mode: EnvMode) => void;
4950 effectiveEnvModeOverride?: EnvMode;
......@@ -309,6 +310,7 @@ function useLabelsOverflow(element: HTMLDivElement | null): boolean {
309310export const BranchToolbar = memo(function BranchToolbar({
310311 environmentId,
311312 threadId,
313 showGitControls,
312314 draftId,
313315 onEnvModeChange,
314316 effectiveEnvModeOverride,
......@@ -403,7 +405,7 @@ export const BranchToolbar = memo(function BranchToolbar({
403405 data-compact={labelsOverflow ? "" : undefined}
404406 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 ps-1 pe-2 pt-5 pb-1"
405407 >
406 {isMobile ? (
408 {isMobile && showGitControls ? (
407409 <MobileRunContextSelector
408410 envLocked={envLocked}
409411 envModeLocked={envModeLocked}
......@@ -428,34 +430,40 @@ export const BranchToolbar = memo(function BranchToolbar({
428430 availableEnvironments={availableEnvironments}
429431 {...(showEnvironmentPicker && onEnvironmentChange ? { onEnvironmentChange } : {})}
430432 />
431 <Separator orientation="vertical" className="mx-0.5 h-3.5!" />
433 {showGitControls ? (
434 <Separator orientation="vertical" className="mx-0.5 h-3.5!" />
435 ) : null}
432436 </>
433437 )}
434 <BranchToolbarEnvModeSelector
435 envLocked={envModeLocked}
436 effectiveEnvMode={effectiveEnvMode}
437 activeWorktreePath={activeWorktreePath}
438 onEnvModeChange={onEnvModeChange}
439 previousWorktreeLabel={previousWorktreeLabel}
440 onUsePreviousWorktree={onUsePreviousWorktree}
441 />
438 {showGitControls ? (
439 <BranchToolbarEnvModeSelector
440 envLocked={envModeLocked}
441 effectiveEnvMode={effectiveEnvMode}
442 activeWorktreePath={activeWorktreePath}
443 onEnvModeChange={onEnvModeChange}
444 previousWorktreeLabel={previousWorktreeLabel}
445 onUsePreviousWorktree={onUsePreviousWorktree}
446 />
447 ) : null}
442448 </div>
443449 )}
444450
445 <BranchToolbarBranchSelector
446 className="min-w-0 flex-1 justify-end md:ml-auto md:flex-none"
447 environmentId={environmentId}
448 threadId={threadId}
449 {...(draftId ? { draftId } : {})}
450 envLocked={envLocked}
451 {...(effectiveEnvModeOverride ? { effectiveEnvModeOverride } : {})}
452 {...(activeThreadBranchOverride !== undefined ? { activeThreadBranchOverride } : {})}
453 {...(onActiveThreadBranchOverrideChange ? { onActiveThreadBranchOverrideChange } : {})}
454 startFromOrigin={startFromOrigin}
455 onStartFromOriginChange={onStartFromOriginChange}
456 {...(onCheckoutPullRequestRequest ? { onCheckoutPullRequestRequest } : {})}
457 {...(onComposerFocusRequest ? { onComposerFocusRequest } : {})}
458 />
451 {showGitControls ? (
452 <BranchToolbarBranchSelector
453 className="min-w-0 flex-1 justify-end md:ml-auto md:flex-none"
454 environmentId={environmentId}
455 threadId={threadId}
456 {...(draftId ? { draftId } : {})}
457 envLocked={envLocked}
458 {...(effectiveEnvModeOverride ? { effectiveEnvModeOverride } : {})}
459 {...(activeThreadBranchOverride !== undefined ? { activeThreadBranchOverride } : {})}
460 {...(onActiveThreadBranchOverrideChange ? { onActiveThreadBranchOverrideChange } : {})}
461 startFromOrigin={startFromOrigin}
462 onStartFromOriginChange={onStartFromOriginChange}
463 {...(onCheckoutPullRequestRequest ? { onCheckoutPullRequestRequest } : {})}
464 {...(onComposerFocusRequest ? { onComposerFocusRequest } : {})}
465 />
466 ) : null}
459467 </div>
460468 );
461469});
apps/web/src/components/ChatView.tsx+20-2
......@@ -244,7 +244,12 @@ import { ChatHeader } from "./chat/ChatHeader";
244244import { PanelLayoutControls, RightPanelMaximizeControl } from "./chat/PanelLayoutControls";
245245import { type ExpandedImagePreview } from "./chat/ExpandedImagePreview";
246246import { NoActiveThreadState } from "./NoActiveThreadState";
247import { resolveEffectiveEnvMode, resolveLocalCheckoutBranchMismatch } from "./BranchToolbar.logic";
247import {
248 resolveEffectiveEnvMode,
249 resolveLocalCheckoutBranchMismatch,
250 shouldShowComposerContextStrip,
251 shouldShowEnvironmentIndicator,
252} from "./BranchToolbar.logic";
248253import {
249254 getProviderStatusBannerKey,
250255 ProviderStatusBanner,
......@@ -1745,6 +1750,14 @@ function ChatViewContent(props: ChatViewProps) {
17451750 return envs;
17461751 }, [activeProject, allProjects, projectGroupingSettings, primaryEnvironmentId, environmentById]);
17471752 const hasMultipleEnvironments = logicalProjectEnvironments.length > 1;
1753 const activeEnvironmentOption =
1754 logicalProjectEnvironments.find(
1755 (environment) => environment.environmentId === activeThread?.environmentId,
1756 ) ?? null;
1757 const showComposerEnvironmentIndicator = shouldShowEnvironmentIndicator({
1758 activeEnvironment: activeEnvironmentOption,
1759 canPickEnvironment: hasMultipleEnvironments,
1760 });
17481761
17491762 const openPullRequestDialog = useCallback(
17501763 (reference?: string) => {
......@@ -2514,7 +2527,11 @@ function ChatViewContent(props: ChatViewProps) {
25142527 terminalUiLaunchContext?.threadId === activeThreadId ? terminalUiLaunchContext : null;
25152528 // Default true while loading to avoid toolbar flicker.
25162529 const isGitRepo = gitStatusQuery.data?.isRepo ?? true;
2517 const showComposerContextStrip = isGitRepo && activeProject !== null;
2530 const showComposerContextStrip = shouldShowComposerContextStrip({
2531 hasActiveProject: activeProject !== null,
2532 isGitRepo,
2533 showEnvironmentIndicator: showComposerEnvironmentIndicator,
2534 });
25182535 const initialDiffPanelGitScope =
25192536 gitStatusQuery.data?.hasWorkingTreeChanges === true ? "unstaged" : "branch";
25202537 const diffPanelGitStatusResolutionKey = gitStatusQuery.data ? "resolved" : "pending";
......@@ -6180,6 +6197,7 @@ function ChatViewContent(props: ChatViewProps) {
61806197 <BranchToolbar
61816198 environmentId={activeThread.environmentId}
61826199 threadId={activeThread.id}
6200 showGitControls={isGitRepo}
61836201 {...(routeKind === "draft" && draftId ? { draftId } : {})}
61846202 onEnvModeChange={onEnvModeChange}
61856203 startFromOrigin={startFromOrigin}