authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-07-24 00:32:14+02:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-07-23 18:32:14-04:00
log6ef7aa8399640fd2bf10fcb126c0db93fee47ff0
tree4b71bec9bda22c95ef3bb3759888aa706bd17a0d
parent193e3c62e6d52fcc200d823ca6d9061d13f81f23
signature Signed by PGP key B5690EEEBB952194

Fix composer context strip alignment and glass shell (#4404)


4 files changed, 216 insertions(+), 131 deletions(-)

apps/web/src/components/BranchToolbar.tsx+1-1
...@@ -248,7 +248,7 @@ export const BranchToolbar = memo(function BranchToolbar({...@@ -248,7 +248,7 @@ export const BranchToolbar = memo(function BranchToolbar({
248 if (!hasActiveThread || !activeProject) return null;248 if (!hasActiveThread || !activeProject) return null;
249249
250 return (250 return (
251 <div className="chat-composer-context-strip -mt-4 mx-auto flex w-[calc(100%-1.5rem)] max-w-[calc(48rem-1.5rem)] items-center gap-2 pt-5 pb-1 ps-1">251 <div className="chat-composer-context-strip -mt-4 mx-auto flex w-[calc(100%-2.75rem)] max-w-[calc(48rem-2.75rem)] items-center gap-2 px-1 pt-5 pb-1">
252 {isMobile ? (252 {isMobile ? (
253 <MobileRunContextSelector253 <MobileRunContextSelector
254 envLocked={envLocked}254 envLocked={envLocked}
apps/web/src/components/BranchToolbarBranchSelector.tsx+1-1
...@@ -731,7 +731,7 @@ export function BranchToolbarBranchSelector({...@@ -731,7 +731,7 @@ export function BranchToolbarBranchSelector({
731 >731 >
732 <ComboboxTrigger732 <ComboboxTrigger
733 render={<Button variant="ghost" size="xs" />}733 render={<Button variant="ghost" size="xs" />}
734 className="min-w-0 text-muted-foreground/70 hover:text-foreground/80"734 className="min-w-0 max-w-full text-muted-foreground/70 hover:text-foreground/80"
735 disabled={isInitialBranchesLoadPending || isBranchActionPending}735 disabled={isInitialBranchesLoadPending || isBranchActionPending}
736 >736 >
737 <GitBranchIcon className="size-3 shrink-0 opacity-70" />737 <GitBranchIcon className="size-3 shrink-0 opacity-70" />
apps/web/src/components/ChatView.tsx+122-112
...@@ -2352,6 +2352,7 @@ function ChatViewContent(props: ChatViewProps) {...@@ -2352,6 +2352,7 @@ function ChatViewContent(props: ChatViewProps) {
2352 terminalUiLaunchContext?.threadId === activeThreadId ? terminalUiLaunchContext : null;2352 terminalUiLaunchContext?.threadId === activeThreadId ? terminalUiLaunchContext : null;
2353 // Default true while loading to avoid toolbar flicker.2353 // Default true while loading to avoid toolbar flicker.
2354 const isGitRepo = gitStatusQuery.data?.isRepo ?? true;2354 const isGitRepo = gitStatusQuery.data?.isRepo ?? true;
2355 const showComposerContextStrip = isGitRepo && activeProject !== null;
2355 const initialDiffPanelGitScope =2356 const initialDiffPanelGitScope =
2356 gitStatusQuery.data?.hasWorkingTreeChanges === true ? "unstaged" : "branch";2357 gitStatusQuery.data?.hasWorkingTreeChanges === true ? "unstaged" : "branch";
2357 const diffPanelGitStatusResolutionKey = gitStatusQuery.data ? "resolved" : "pending";2358 const diffPanelGitStatusResolutionKey = gitStatusQuery.data ? "resolved" : "pending";
...@@ -5592,119 +5593,128 @@ function ChatViewContent(props: ChatViewProps) {...@@ -5592,119 +5593,128 @@ function ChatViewContent(props: ChatViewProps) {
5592 : undefined5593 : undefined
5593 }5594 }
5594 >5595 >
5595 <div className="chat-composer-glass-host relative z-10 mx-auto w-full max-w-3xl rounded-[22px]">5596 <div
5596 <div ref={attachDraftHeroComposerAnchorRef} className="relative z-10">5597 className={cn(
5597 <ChatComposer5598 "chat-composer-glass-shell relative mx-auto w-full max-w-3xl",
5598 composerRef={composerRef}5599 showComposerContextStrip && "chat-composer-glass-shell-with-context",
5599 composerDraftTarget={composerDraftTarget}5600 )}
5600 environmentId={environmentId}5601 >
5601 routeKind={routeKind}5602 <div className="chat-composer-glass-host relative z-10 w-full rounded-[22px]">
5602 routeThreadRef={routeThreadRef}5603 <div ref={attachDraftHeroComposerAnchorRef} className="relative z-10">
5603 draftId={draftId}5604 <ChatComposer
5604 activeThreadId={activeThreadId}5605 composerRef={composerRef}
5605 activeThreadEnvironmentId={activeThread?.environmentId}5606 composerDraftTarget={composerDraftTarget}
5606 activeThread={activeThread}5607 environmentId={environmentId}
5607 isServerThread={isServerThread}5608 routeKind={routeKind}
5608 isLocalDraftThread={isLocalDraftThread}5609 routeThreadRef={routeThreadRef}
5609 forceExpandedOnMobile={forceExpandedMobileComposer && isDraftHeroState}5610 draftId={draftId}
5610 projectSelectionRequired={isLocalDraftThread && activeProject === null}5611 activeThreadId={activeThreadId}
5611 phase={phase}5612 activeThreadEnvironmentId={activeThread?.environmentId}
5612 isConnecting={isConnecting}5613 activeThread={activeThread}
5613 isSendBusy={isSendBusy}5614 isServerThread={isServerThread}
5614 isPreparingWorktree={isPreparingWorktree}5615 isLocalDraftThread={isLocalDraftThread}
5615 environmentUnavailable={activeEnvironmentUnavailableState}5616 forceExpandedOnMobile={forceExpandedMobileComposer && isDraftHeroState}
5616 activePendingApproval={activePendingApproval}5617 projectSelectionRequired={isLocalDraftThread && activeProject === null}
5617 pendingApprovals={pendingApprovals}5618 phase={phase}
5618 pendingUserInputs={pendingUserInputs}5619 isConnecting={isConnecting}
5619 activePendingProgress={activePendingProgress}5620 isSendBusy={isSendBusy}
5620 activePendingResolvedAnswers={activePendingResolvedAnswers}5621 isPreparingWorktree={isPreparingWorktree}
5621 activePendingIsResponding={activePendingIsResponding}5622 environmentUnavailable={activeEnvironmentUnavailableState}
5622 activePendingDraftAnswers={activePendingDraftAnswers}5623 activePendingApproval={activePendingApproval}
5623 activePendingQuestionIndex={activePendingQuestionIndex}5624 pendingApprovals={pendingApprovals}
5624 respondingRequestIds={respondingRequestIds}5625 pendingUserInputs={pendingUserInputs}
5625 showPlanFollowUpPrompt={showPlanFollowUpPrompt}5626 activePendingProgress={activePendingProgress}
5626 activeProposedPlan={activeProposedPlan}5627 activePendingResolvedAnswers={activePendingResolvedAnswers}
5627 activePlan={activePlan as { turnId?: TurnId } | null}5628 activePendingIsResponding={activePendingIsResponding}
5628 sidebarProposedPlan={sidebarProposedPlan as { turnId?: TurnId } | null}5629 activePendingDraftAnswers={activePendingDraftAnswers}
5629 planSidebarLabel={planSidebarLabel}5630 activePendingQuestionIndex={activePendingQuestionIndex}
5630 planSidebarOpen={planSidebarOpen}5631 respondingRequestIds={respondingRequestIds}
5631 runtimeMode={runtimeMode}5632 showPlanFollowUpPrompt={showPlanFollowUpPrompt}
5632 interactionMode={interactionMode}5633 activeProposedPlan={activeProposedPlan}
5633 lockedProvider={lockedProvider}5634 activePlan={activePlan as { turnId?: TurnId } | null}
5634 providerStatuses={providerStatuses as ServerProvider[]}5635 sidebarProposedPlan={sidebarProposedPlan as { turnId?: TurnId } | null}
5635 activeProjectDefaultModelSelection={activeProject?.defaultModelSelection}5636 planSidebarLabel={planSidebarLabel}
5636 activeThreadModelSelection={activeThread?.modelSelection}5637 planSidebarOpen={planSidebarOpen}
5637 activeThreadActivities={activeThread?.activities}5638 runtimeMode={runtimeMode}
5638 resolvedTheme={resolvedTheme}5639 interactionMode={interactionMode}
5639 settings={settings}5640 lockedProvider={lockedProvider}
5640 keybindings={keybindings}5641 providerStatuses={providerStatuses as ServerProvider[]}
5641 terminalOpen={Boolean(terminalUiState.terminalOpen)}5642 activeProjectDefaultModelSelection={
5642 gitCwd={gitCwd}5643 activeProject?.defaultModelSelection
5643 promptRef={promptRef}5644 }
5644 composerImagesRef={composerImagesRef}5645 activeThreadModelSelection={activeThread?.modelSelection}
5645 composerTerminalContextsRef={composerTerminalContextsRef}5646 activeThreadActivities={activeThread?.activities}
5646 composerElementContextsRef={composerElementContextsRef}5647 resolvedTheme={resolvedTheme}
5647 onSend={onSend}5648 settings={settings}
5648 onInterrupt={onInterrupt}5649 keybindings={keybindings}
5649 onImplementPlanInNewThread={onImplementPlanInNewThread}5650 terminalOpen={Boolean(terminalUiState.terminalOpen)}
5650 onRespondToApproval={onRespondToApproval}5651 gitCwd={gitCwd}
5651 onSelectActivePendingUserInputOption={5652 promptRef={promptRef}
5652 onSelectActivePendingUserInputOption5653 composerImagesRef={composerImagesRef}
5653 }5654 composerTerminalContextsRef={composerTerminalContextsRef}
5654 onAdvanceActivePendingUserInput={onAdvanceActivePendingUserInput}5655 composerElementContextsRef={composerElementContextsRef}
5655 onPreviousActivePendingUserInputQuestion={5656 onSend={onSend}
5656 onPreviousActivePendingUserInputQuestion5657 onInterrupt={onInterrupt}
5657 }5658 onImplementPlanInNewThread={onImplementPlanInNewThread}
5658 onChangeActivePendingUserInputCustomAnswer={5659 onRespondToApproval={onRespondToApproval}
5659 onChangeActivePendingUserInputCustomAnswer5660 onSelectActivePendingUserInputOption={
5660 }5661 onSelectActivePendingUserInputOption
5661 onProviderModelSelect={onProviderModelSelect}5662 }
5662 getModelDisabledReason={getModelDisabledReason}5663 onAdvanceActivePendingUserInput={onAdvanceActivePendingUserInput}
5663 toggleInteractionMode={toggleInteractionMode}5664 onPreviousActivePendingUserInputQuestion={
5664 handleRuntimeModeChange={handleRuntimeModeChange}5665 onPreviousActivePendingUserInputQuestion
5665 handleInteractionModeChange={handleInteractionModeChange}5666 }
5666 togglePlanSidebar={togglePlanSidebar}5667 onChangeActivePendingUserInputCustomAnswer={
5667 focusComposer={focusComposer}5668 onChangeActivePendingUserInputCustomAnswer
5668 scheduleComposerFocus={scheduleComposerFocus}5669 }
5669 setThreadError={setThreadError}5670 onProviderModelSelect={onProviderModelSelect}
5670 onExpandImage={onExpandTimelineImage}5671 getModelDisabledReason={getModelDisabledReason}
5671 />5672 toggleInteractionMode={toggleInteractionMode}
5673 handleRuntimeModeChange={handleRuntimeModeChange}
5674 handleInteractionModeChange={handleInteractionModeChange}
5675 togglePlanSidebar={togglePlanSidebar}
5676 focusComposer={focusComposer}
5677 scheduleComposerFocus={scheduleComposerFocus}
5678 setThreadError={setThreadError}
5679 onExpandImage={onExpandTimelineImage}
5680 />
5681 </div>
5672 </div>5682 </div>
5673 </div>5683 <div className="min-h-0">
5674 <div className="min-h-0">5684 <div
5675 <div5685 data-terminal-open={terminalUiState.terminalOpen ? "true" : undefined}
5676 data-terminal-open={terminalUiState.terminalOpen ? "true" : undefined}5686 className="relative z-0"
5677 className="relative z-0"5687 >
5678 >5688 {showComposerContextStrip && (
5679 {isGitRepo && (5689 <div className="pointer-events-auto">
5680 <div className="pointer-events-auto">5690 <BranchToolbar
5681 <BranchToolbar5691 environmentId={activeThread.environmentId}
5682 environmentId={activeThread.environmentId}5692 threadId={activeThread.id}
5683 threadId={activeThread.id}5693 {...(routeKind === "draft" && draftId ? { draftId } : {})}
5684 {...(routeKind === "draft" && draftId ? { draftId } : {})}5694 onEnvModeChange={onEnvModeChange}
5685 onEnvModeChange={onEnvModeChange}5695 startFromOrigin={startFromOrigin}
5686 startFromOrigin={startFromOrigin}5696 onStartFromOriginChange={onStartFromOriginChange}
5687 onStartFromOriginChange={onStartFromOriginChange}5697 {...(canOverrideServerThreadEnvMode
5688 {...(canOverrideServerThreadEnvMode5698 ? { effectiveEnvModeOverride: envMode }
5689 ? { effectiveEnvModeOverride: envMode }5699 : {})}
5690 : {})}5700 {...(canOverrideServerThreadEnvMode
5691 {...(canOverrideServerThreadEnvMode5701 ? {
5692 ? {5702 activeThreadBranchOverride: activeThreadBranch,
5693 activeThreadBranchOverride: activeThreadBranch,5703 onActiveThreadBranchOverrideChange:
5694 onActiveThreadBranchOverrideChange:5704 setPendingServerThreadBranch,
5695 setPendingServerThreadBranch,5705 }
5696 }5706 : {})}
5697 : {})}5707 envLocked={envLocked}
5698 envLocked={envLocked}5708 onComposerFocusRequest={scheduleComposerFocus}
5699 onComposerFocusRequest={scheduleComposerFocus}5709 {...(canCheckoutPullRequestIntoThread
5700 {...(canCheckoutPullRequestIntoThread5710 ? { onCheckoutPullRequestRequest: openPullRequestDialog }
5701 ? { onCheckoutPullRequestRequest: openPullRequestDialog }5711 : {})}
5702 : {})}5712 {...(hasMultipleEnvironments ? { onEnvironmentChange } : {})}
5703 {...(hasMultipleEnvironments ? { onEnvironmentChange } : {})}5713 availableEnvironments={logicalProjectEnvironments}
5704 availableEnvironments={logicalProjectEnvironments}5714 />
5705 />5715 </div>
5706 </div>5716 )}
5707 )}5717 </div>
5708 </div>5718 </div>
5709 </div>5719 </div>
5710 <div5720 <div
apps/web/src/index.css+92-17
...@@ -377,21 +377,20 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil...@@ -377,21 +377,20 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil
377 backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturation));377 backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturation));
378 }378 }
379379
380 .chat-composer-glass-host {380 .chat-composer-glass-shell {
381 --chat-composer-glass-surface: var(--card);381 --chat-composer-glass-surface: var(--card);
382 --chat-composer-outline: rgb(0 0 0 / 8%);
382383
383 position: relative;384 position: relative;
384 box-shadow:385 isolation: isolate;
385 0 0 0 1px rgb(0 0 0 / 8%),
386 0 12px 28px -18px rgb(0 0 0 / 40%);
387 }386 }
388387
389 .chat-composer-glass-host::before {388 .chat-composer-glass-shell::before {
390 pointer-events: none;389 pointer-events: none;
391 position: absolute;390 position: absolute;
392 z-index: 0;391 z-index: 0;
393 inset: 0;392 inset: 0;
394 border-radius: inherit;393 border-radius: 22px;
395 background: color-mix(394 background: color-mix(
396 in srgb,395 in srgb,
397 var(--chat-composer-glass-surface) var(--glass-opacity),396 var(--chat-composer-glass-surface) var(--glass-opacity),
...@@ -402,9 +401,73 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil...@@ -402,9 +401,73 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil
402 content: "";401 content: "";
403 }402 }
404403
405 .chat-composer-context-strip {404 .chat-composer-glass-shell-with-context {
406 --chat-composer-context-surface: var(--card);405 --chat-composer-context-extension: 2.25rem;
406 }
407
408 .chat-composer-glass-shell-with-context::before {
409 border-radius: 0;
410 /*
411 * One continuous glass layer: a 22px composer joined to a 16px strip,
412 * whose visible sides align with the composer's bottom tangents.
413 */
414 clip-path: shape(
415 from 0 22px,
416 curve to 22px 0 with 0 9.85px / 9.85px 0,
417 line to calc(100% - 22px) 0,
418 curve to 100% 22px with calc(100% - 9.85px) 0 / 100% 9.85px,
419 line to 100% calc(100% - var(--chat-composer-context-extension) - 22px),
420 curve to calc(100% - 22px) calc(100% - var(--chat-composer-context-extension)) with 100%
421 calc(100% - var(--chat-composer-context-extension) - 9.85px) / calc(100% - 9.85px)
422 calc(100% - var(--chat-composer-context-extension)),
423 line to calc(100% - 22px) calc(100% - 16px),
424 curve to calc(100% - 38px) 100% with calc(100% - 22px) calc(100% - 7.16px) /
425 calc(100% - 29.16px) 100%,
426 line to 38px 100%,
427 curve to 22px calc(100% - 16px) with 29.16px 100% / 22px calc(100% - 7.16px),
428 line to 22px calc(100% - var(--chat-composer-context-extension)),
429 curve to 0 calc(100% - var(--chat-composer-context-extension) - 22px) with 9.85px
430 calc(100% - var(--chat-composer-context-extension)) / 0
431 calc(100% - var(--chat-composer-context-extension) - 9.85px),
432 line to 0 22px,
433 close
434 );
435 }
436
437 @media (min-width: 40rem) {
438 .chat-composer-glass-shell-with-context {
439 /* The xs toolbar controls shrink by 4px at Tailwind's sm breakpoint. */
440 --chat-composer-context-extension: 2rem;
441 }
442 }
443
444 .chat-composer-glass-host {
445 position: relative;
446 box-shadow: 0 12px 28px -18px rgb(0 0 0 / 40%);
447 }
448
449 .chat-composer-glass-host::after {
450 pointer-events: none;
451 position: absolute;
452 z-index: 1;
453 inset: 0;
454 border: 1px solid var(--chat-composer-outline);
455 border-radius: inherit;
456 /* Keep the attachment seam open; the strip continues the outer outline below. */
457 clip-path: polygon(
458 0 0,
459 100% 0,
460 100% 100%,
461 calc(100% - 22px) 100%,
462 calc(100% - 22px) calc(100% - 2px),
463 22px calc(100% - 2px),
464 22px 100%,
465 0 100%
466 );
467 content: "";
468 }
407469
470 .chat-composer-context-strip {
408 position: relative;471 position: relative;
409 isolation: isolate;472 isolation: isolate;
410 }473 }
...@@ -413,27 +476,39 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil...@@ -413,27 +476,39 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil
413 position: absolute;476 position: absolute;
414 z-index: -1;477 z-index: -1;
415 inset: 0;478 inset: 0;
479 border: 1px solid var(--chat-composer-outline);
416 border-radius: 0 0 16px 16px;480 border-radius: 0 0 16px 16px;
417 background: color-mix(481 /* Start the strip outline exactly where the composer's 22px curve reaches its tangent. */
418 in srgb,
419 var(--chat-composer-context-surface) var(--glass-opacity),
420 transparent
421 );
422 -webkit-mask-image: linear-gradient(to bottom, transparent 0 1rem, black 1rem);482 -webkit-mask-image: linear-gradient(to bottom, transparent 0 1rem, black 1rem);
423 mask-image: linear-gradient(to bottom, transparent 0 1rem, black 1rem);483 mask-image: linear-gradient(to bottom, transparent 0 1rem, black 1rem);
424 box-shadow: 0 12px 28px -18px rgb(0 0 0 / 40%);484 box-shadow: 0 12px 28px -18px rgb(0 0 0 / 40%);
425 content: "";485 content: "";
426 -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturation));
427 backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturation));
428 }486 }
429487
430 .dark .chat-composer-glass-host {488 .dark .chat-composer-glass-shell {
431 --chat-composer-glass-surface: color-mix(in srgb, var(--background) 96%, var(--color-white));489 --chat-composer-glass-surface: color-mix(in srgb, var(--background) 96%, var(--color-white));
490 --chat-composer-outline: color-mix(in srgb, var(--color-white) 5%, transparent);
491 --chat-composer-highlight: rgb(255 255 255 / 3%);
492 }
432493
494 .dark .chat-composer-glass-host {
433 box-shadow: none;495 box-shadow: none;
434 }496 }
435497
498 .dark .chat-composer-glass-host::after {
499 box-shadow: inset 0 1px var(--chat-composer-highlight);
500 }
501
436 .dark .chat-composer-context-strip::before {502 .dark .chat-composer-context-strip::before {
503 border-color: rgb(255 255 255 / 7%);
504 background:
505 linear-gradient(
506 to bottom,
507 transparent 0 1rem,
508 rgb(0 0 0 / 18%) 1rem,
509 transparent calc(1rem + 10px)
510 ),
511 rgb(255 255 255 / 2%);
437 box-shadow: 0 14px 32px -18px rgb(0 0 0 / 75%);512 box-shadow: 0 14px 32px -18px rgb(0 0 0 / 75%);
438 }513 }
439514
...@@ -641,7 +716,7 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil...@@ -641,7 +716,7 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil
641 background: var(--background) !important;716 background: var(--background) !important;
642 }717 }
643718
644 .chat-composer-glass-host::before {719 .chat-composer-glass-shell::before {
645 background: var(--chat-composer-glass-surface);720 background: var(--chat-composer-glass-surface);
646 }721 }
647722