| author | |
| committer | |
| log | 64a991ad455234e6fdd808a5a3caadc51f18a152 |
| tree | 6092e27e03ee32be715d335488f358349e05f934 |
| parent | 0ec4fbc4a376cbf6465e59b05506ce9d89b3d078 |
| signature | Signed by PGP key B5690EEEBB952194 |
4 files changed, 93 insertions(+), 26 deletions(-)
apps/web/src/components/BranchToolbar.logic.test.ts+33| ... | ... | @@ -17,6 +17,7 @@ import { |
| 17 | 17 | resolvePreviousWorktreeLabel, |
| 18 | 18 | resolvePreviousWorktreeSeed, |
| 19 | 19 | shouldIncludeBranchPickerItem, |
| 20 | shouldShowComposerContextStrip, | |
| 20 | 21 | shouldShowEnvironmentIndicator, |
| 21 | 22 | } from "./BranchToolbar.logic"; |
| 22 | 23 | |
| ... | ... | @@ -421,6 +422,38 @@ describe("shouldShowEnvironmentIndicator", () => { |
| 421 | 422 | }); |
| 422 | 423 | }); |
| 423 | 424 | |
| 425 | describe("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 | ||
| 424 | 457 | describe("resolveEffectiveEnvMode", () => { |
| 425 | 458 | it("treats draft threads already attached to a worktree as current-checkout mode", () => { |
| 426 | 459 | expect( |
apps/web/src/components/BranchToolbar.logic.ts+8| ... | ... | @@ -54,6 +54,14 @@ export function shouldShowEnvironmentIndicator(input: { |
| 54 | 54 | return input.activeEnvironment !== null && !input.activeEnvironment.isPrimary; |
| 55 | 55 | } |
| 56 | 56 | |
| 57 | export function shouldShowComposerContextStrip(input: { | |
| 58 | hasActiveProject: boolean; | |
| 59 | isGitRepo: boolean; | |
| 60 | showEnvironmentIndicator: boolean; | |
| 61 | }): boolean { | |
| 62 | return input.hasActiveProject && (input.isGitRepo || input.showEnvironmentIndicator); | |
| 63 | } | |
| 64 | ||
| 57 | 65 | export function resolveEnvModeLabel(mode: EnvMode): string { |
| 58 | 66 | return mode === "worktree" ? "New worktree" : "Current checkout"; |
| 59 | 67 | } |
apps/web/src/components/BranchToolbar.tsx+32-24| ... | ... | @@ -44,6 +44,7 @@ import { Separator } from "./ui/separator"; |
| 44 | 44 | interface BranchToolbarProps { |
| 45 | 45 | environmentId: EnvironmentId; |
| 46 | 46 | threadId: ThreadId; |
| 47 | showGitControls: boolean; | |
| 47 | 48 | draftId?: DraftId; |
| 48 | 49 | onEnvModeChange: (mode: EnvMode) => void; |
| 49 | 50 | effectiveEnvModeOverride?: EnvMode; |
| ... | ... | @@ -309,6 +310,7 @@ function useLabelsOverflow(element: HTMLDivElement | null): boolean { |
| 309 | 310 | export const BranchToolbar = memo(function BranchToolbar({ |
| 310 | 311 | environmentId, |
| 311 | 312 | threadId, |
| 313 | showGitControls, | |
| 312 | 314 | draftId, |
| 313 | 315 | onEnvModeChange, |
| 314 | 316 | effectiveEnvModeOverride, |
| ... | ... | @@ -403,7 +405,7 @@ export const BranchToolbar = memo(function BranchToolbar({ |
| 403 | 405 | data-compact={labelsOverflow ? "" : undefined} |
| 404 | 406 | 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" |
| 405 | 407 | > |
| 406 | {isMobile ? ( | |
| 408 | {isMobile && showGitControls ? ( | |
| 407 | 409 | <MobileRunContextSelector |
| 408 | 410 | envLocked={envLocked} |
| 409 | 411 | envModeLocked={envModeLocked} |
| ... | ... | @@ -428,34 +430,40 @@ export const BranchToolbar = memo(function BranchToolbar({ |
| 428 | 430 | availableEnvironments={availableEnvironments} |
| 429 | 431 | {...(showEnvironmentPicker && onEnvironmentChange ? { onEnvironmentChange } : {})} |
| 430 | 432 | /> |
| 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} | |
| 432 | 436 | </> |
| 433 | 437 | )} |
| 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} | |
| 442 | 448 | </div> |
| 443 | 449 | )} |
| 444 | 450 | |
| 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} | |
| 459 | 467 | </div> |
| 460 | 468 | ); |
| 461 | 469 | }); |
apps/web/src/components/ChatView.tsx+20-2| ... | ... | @@ -244,7 +244,12 @@ import { ChatHeader } from "./chat/ChatHeader"; |
| 244 | 244 | import { PanelLayoutControls, RightPanelMaximizeControl } from "./chat/PanelLayoutControls"; |
| 245 | 245 | import { type ExpandedImagePreview } from "./chat/ExpandedImagePreview"; |
| 246 | 246 | import { NoActiveThreadState } from "./NoActiveThreadState"; |
| 247 | import { resolveEffectiveEnvMode, resolveLocalCheckoutBranchMismatch } from "./BranchToolbar.logic"; | |
| 247 | import { | |
| 248 | resolveEffectiveEnvMode, | |
| 249 | resolveLocalCheckoutBranchMismatch, | |
| 250 | shouldShowComposerContextStrip, | |
| 251 | shouldShowEnvironmentIndicator, | |
| 252 | } from "./BranchToolbar.logic"; | |
| 248 | 253 | import { |
| 249 | 254 | getProviderStatusBannerKey, |
| 250 | 255 | ProviderStatusBanner, |
| ... | ... | @@ -1745,6 +1750,14 @@ function ChatViewContent(props: ChatViewProps) { |
| 1745 | 1750 | return envs; |
| 1746 | 1751 | }, [activeProject, allProjects, projectGroupingSettings, primaryEnvironmentId, environmentById]); |
| 1747 | 1752 | 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 | }); | |
| 1748 | 1761 | |
| 1749 | 1762 | const openPullRequestDialog = useCallback( |
| 1750 | 1763 | (reference?: string) => { |
| ... | ... | @@ -2514,7 +2527,11 @@ function ChatViewContent(props: ChatViewProps) { |
| 2514 | 2527 | terminalUiLaunchContext?.threadId === activeThreadId ? terminalUiLaunchContext : null; |
| 2515 | 2528 | // Default true while loading to avoid toolbar flicker. |
| 2516 | 2529 | 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 | }); | |
| 2518 | 2535 | const initialDiffPanelGitScope = |
| 2519 | 2536 | gitStatusQuery.data?.hasWorkingTreeChanges === true ? "unstaged" : "branch"; |
| 2520 | 2537 | const diffPanelGitStatusResolutionKey = gitStatusQuery.data ? "resolved" : "pending"; |
| ... | ... | @@ -6180,6 +6197,7 @@ function ChatViewContent(props: ChatViewProps) { |
| 6180 | 6197 | <BranchToolbar |
| 6181 | 6198 | environmentId={activeThread.environmentId} |
| 6182 | 6199 | threadId={activeThread.id} |
| 6200 | showGitControls={isGitRepo} | |
| 6183 | 6201 | {...(routeKind === "draft" && draftId ? { draftId } : {})} |
| 6184 | 6202 | onEnvModeChange={onEnvModeChange} |
| 6185 | 6203 | startFromOrigin={startFromOrigin} |