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({
248248 if (!hasActiveThread || !activeProject) return null;
249249
250250 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">
252252 {isMobile ? (
253253 <MobileRunContextSelector
254254 envLocked={envLocked}
apps/web/src/components/BranchToolbarBranchSelector.tsx+1-1
......@@ -731,7 +731,7 @@ export function BranchToolbarBranchSelector({
731731 >
732732 <ComboboxTrigger
733733 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"
735735 disabled={isInitialBranchesLoadPending || isBranchActionPending}
736736 >
737737 <GitBranchIcon className="size-3 shrink-0 opacity-70" />
apps/web/src/components/ChatView.tsx+122-112
......@@ -2352,6 +2352,7 @@ function ChatViewContent(props: ChatViewProps) {
23522352 terminalUiLaunchContext?.threadId === activeThreadId ? terminalUiLaunchContext : null;
23532353 // Default true while loading to avoid toolbar flicker.
23542354 const isGitRepo = gitStatusQuery.data?.isRepo ?? true;
2355 const showComposerContextStrip = isGitRepo && activeProject !== null;
23552356 const initialDiffPanelGitScope =
23562357 gitStatusQuery.data?.hasWorkingTreeChanges === true ? "unstaged" : "branch";
23572358 const diffPanelGitStatusResolutionKey = gitStatusQuery.data ? "resolved" : "pending";
......@@ -5592,119 +5593,128 @@ function ChatViewContent(props: ChatViewProps) {
55925593 : undefined
55935594 }
55945595 >
5595 <div className="chat-composer-glass-host relative z-10 mx-auto w-full max-w-3xl rounded-[22px]">
5596 <div ref={attachDraftHeroComposerAnchorRef} className="relative z-10">
5597 <ChatComposer
5598 composerRef={composerRef}
5599 composerDraftTarget={composerDraftTarget}
5600 environmentId={environmentId}
5601 routeKind={routeKind}
5602 routeThreadRef={routeThreadRef}
5603 draftId={draftId}
5604 activeThreadId={activeThreadId}
5605 activeThreadEnvironmentId={activeThread?.environmentId}
5606 activeThread={activeThread}
5607 isServerThread={isServerThread}
5608 isLocalDraftThread={isLocalDraftThread}
5609 forceExpandedOnMobile={forceExpandedMobileComposer && isDraftHeroState}
5610 projectSelectionRequired={isLocalDraftThread && activeProject === null}
5611 phase={phase}
5612 isConnecting={isConnecting}
5613 isSendBusy={isSendBusy}
5614 isPreparingWorktree={isPreparingWorktree}
5615 environmentUnavailable={activeEnvironmentUnavailableState}
5616 activePendingApproval={activePendingApproval}
5617 pendingApprovals={pendingApprovals}
5618 pendingUserInputs={pendingUserInputs}
5619 activePendingProgress={activePendingProgress}
5620 activePendingResolvedAnswers={activePendingResolvedAnswers}
5621 activePendingIsResponding={activePendingIsResponding}
5622 activePendingDraftAnswers={activePendingDraftAnswers}
5623 activePendingQuestionIndex={activePendingQuestionIndex}
5624 respondingRequestIds={respondingRequestIds}
5625 showPlanFollowUpPrompt={showPlanFollowUpPrompt}
5626 activeProposedPlan={activeProposedPlan}
5627 activePlan={activePlan as { turnId?: TurnId } | null}
5628 sidebarProposedPlan={sidebarProposedPlan as { turnId?: TurnId } | null}
5629 planSidebarLabel={planSidebarLabel}
5630 planSidebarOpen={planSidebarOpen}
5631 runtimeMode={runtimeMode}
5632 interactionMode={interactionMode}
5633 lockedProvider={lockedProvider}
5634 providerStatuses={providerStatuses as ServerProvider[]}
5635 activeProjectDefaultModelSelection={activeProject?.defaultModelSelection}
5636 activeThreadModelSelection={activeThread?.modelSelection}
5637 activeThreadActivities={activeThread?.activities}
5638 resolvedTheme={resolvedTheme}
5639 settings={settings}
5640 keybindings={keybindings}
5641 terminalOpen={Boolean(terminalUiState.terminalOpen)}
5642 gitCwd={gitCwd}
5643 promptRef={promptRef}
5644 composerImagesRef={composerImagesRef}
5645 composerTerminalContextsRef={composerTerminalContextsRef}
5646 composerElementContextsRef={composerElementContextsRef}
5647 onSend={onSend}
5648 onInterrupt={onInterrupt}
5649 onImplementPlanInNewThread={onImplementPlanInNewThread}
5650 onRespondToApproval={onRespondToApproval}
5651 onSelectActivePendingUserInputOption={
5652 onSelectActivePendingUserInputOption
5653 }
5654 onAdvanceActivePendingUserInput={onAdvanceActivePendingUserInput}
5655 onPreviousActivePendingUserInputQuestion={
5656 onPreviousActivePendingUserInputQuestion
5657 }
5658 onChangeActivePendingUserInputCustomAnswer={
5659 onChangeActivePendingUserInputCustomAnswer
5660 }
5661 onProviderModelSelect={onProviderModelSelect}
5662 getModelDisabledReason={getModelDisabledReason}
5663 toggleInteractionMode={toggleInteractionMode}
5664 handleRuntimeModeChange={handleRuntimeModeChange}
5665 handleInteractionModeChange={handleInteractionModeChange}
5666 togglePlanSidebar={togglePlanSidebar}
5667 focusComposer={focusComposer}
5668 scheduleComposerFocus={scheduleComposerFocus}
5669 setThreadError={setThreadError}
5670 onExpandImage={onExpandTimelineImage}
5671 />
5596 <div
5597 className={cn(
5598 "chat-composer-glass-shell relative mx-auto w-full max-w-3xl",
5599 showComposerContextStrip && "chat-composer-glass-shell-with-context",
5600 )}
5601 >
5602 <div className="chat-composer-glass-host relative z-10 w-full rounded-[22px]">
5603 <div ref={attachDraftHeroComposerAnchorRef} className="relative z-10">
5604 <ChatComposer
5605 composerRef={composerRef}
5606 composerDraftTarget={composerDraftTarget}
5607 environmentId={environmentId}
5608 routeKind={routeKind}
5609 routeThreadRef={routeThreadRef}
5610 draftId={draftId}
5611 activeThreadId={activeThreadId}
5612 activeThreadEnvironmentId={activeThread?.environmentId}
5613 activeThread={activeThread}
5614 isServerThread={isServerThread}
5615 isLocalDraftThread={isLocalDraftThread}
5616 forceExpandedOnMobile={forceExpandedMobileComposer && isDraftHeroState}
5617 projectSelectionRequired={isLocalDraftThread && activeProject === null}
5618 phase={phase}
5619 isConnecting={isConnecting}
5620 isSendBusy={isSendBusy}
5621 isPreparingWorktree={isPreparingWorktree}
5622 environmentUnavailable={activeEnvironmentUnavailableState}
5623 activePendingApproval={activePendingApproval}
5624 pendingApprovals={pendingApprovals}
5625 pendingUserInputs={pendingUserInputs}
5626 activePendingProgress={activePendingProgress}
5627 activePendingResolvedAnswers={activePendingResolvedAnswers}
5628 activePendingIsResponding={activePendingIsResponding}
5629 activePendingDraftAnswers={activePendingDraftAnswers}
5630 activePendingQuestionIndex={activePendingQuestionIndex}
5631 respondingRequestIds={respondingRequestIds}
5632 showPlanFollowUpPrompt={showPlanFollowUpPrompt}
5633 activeProposedPlan={activeProposedPlan}
5634 activePlan={activePlan as { turnId?: TurnId } | null}
5635 sidebarProposedPlan={sidebarProposedPlan as { turnId?: TurnId } | null}
5636 planSidebarLabel={planSidebarLabel}
5637 planSidebarOpen={planSidebarOpen}
5638 runtimeMode={runtimeMode}
5639 interactionMode={interactionMode}
5640 lockedProvider={lockedProvider}
5641 providerStatuses={providerStatuses as ServerProvider[]}
5642 activeProjectDefaultModelSelection={
5643 activeProject?.defaultModelSelection
5644 }
5645 activeThreadModelSelection={activeThread?.modelSelection}
5646 activeThreadActivities={activeThread?.activities}
5647 resolvedTheme={resolvedTheme}
5648 settings={settings}
5649 keybindings={keybindings}
5650 terminalOpen={Boolean(terminalUiState.terminalOpen)}
5651 gitCwd={gitCwd}
5652 promptRef={promptRef}
5653 composerImagesRef={composerImagesRef}
5654 composerTerminalContextsRef={composerTerminalContextsRef}
5655 composerElementContextsRef={composerElementContextsRef}
5656 onSend={onSend}
5657 onInterrupt={onInterrupt}
5658 onImplementPlanInNewThread={onImplementPlanInNewThread}
5659 onRespondToApproval={onRespondToApproval}
5660 onSelectActivePendingUserInputOption={
5661 onSelectActivePendingUserInputOption
5662 }
5663 onAdvanceActivePendingUserInput={onAdvanceActivePendingUserInput}
5664 onPreviousActivePendingUserInputQuestion={
5665 onPreviousActivePendingUserInputQuestion
5666 }
5667 onChangeActivePendingUserInputCustomAnswer={
5668 onChangeActivePendingUserInputCustomAnswer
5669 }
5670 onProviderModelSelect={onProviderModelSelect}
5671 getModelDisabledReason={getModelDisabledReason}
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>
56725682 </div>
5673 </div>
5674 <div className="min-h-0">
5675 <div
5676 data-terminal-open={terminalUiState.terminalOpen ? "true" : undefined}
5677 className="relative z-0"
5678 >
5679 {isGitRepo && (
5680 <div className="pointer-events-auto">
5681 <BranchToolbar
5682 environmentId={activeThread.environmentId}
5683 threadId={activeThread.id}
5684 {...(routeKind === "draft" && draftId ? { draftId } : {})}
5685 onEnvModeChange={onEnvModeChange}
5686 startFromOrigin={startFromOrigin}
5687 onStartFromOriginChange={onStartFromOriginChange}
5688 {...(canOverrideServerThreadEnvMode
5689 ? { effectiveEnvModeOverride: envMode }
5690 : {})}
5691 {...(canOverrideServerThreadEnvMode
5692 ? {
5693 activeThreadBranchOverride: activeThreadBranch,
5694 onActiveThreadBranchOverrideChange:
5695 setPendingServerThreadBranch,
5696 }
5697 : {})}
5698 envLocked={envLocked}
5699 onComposerFocusRequest={scheduleComposerFocus}
5700 {...(canCheckoutPullRequestIntoThread
5701 ? { onCheckoutPullRequestRequest: openPullRequestDialog }
5702 : {})}
5703 {...(hasMultipleEnvironments ? { onEnvironmentChange } : {})}
5704 availableEnvironments={logicalProjectEnvironments}
5705 />
5706 </div>
5707 )}
5683 <div className="min-h-0">
5684 <div
5685 data-terminal-open={terminalUiState.terminalOpen ? "true" : undefined}
5686 className="relative z-0"
5687 >
5688 {showComposerContextStrip && (
5689 <div className="pointer-events-auto">
5690 <BranchToolbar
5691 environmentId={activeThread.environmentId}
5692 threadId={activeThread.id}
5693 {...(routeKind === "draft" && draftId ? { draftId } : {})}
5694 onEnvModeChange={onEnvModeChange}
5695 startFromOrigin={startFromOrigin}
5696 onStartFromOriginChange={onStartFromOriginChange}
5697 {...(canOverrideServerThreadEnvMode
5698 ? { effectiveEnvModeOverride: envMode }
5699 : {})}
5700 {...(canOverrideServerThreadEnvMode
5701 ? {
5702 activeThreadBranchOverride: activeThreadBranch,
5703 onActiveThreadBranchOverrideChange:
5704 setPendingServerThreadBranch,
5705 }
5706 : {})}
5707 envLocked={envLocked}
5708 onComposerFocusRequest={scheduleComposerFocus}
5709 {...(canCheckoutPullRequestIntoThread
5710 ? { onCheckoutPullRequestRequest: openPullRequestDialog }
5711 : {})}
5712 {...(hasMultipleEnvironments ? { onEnvironmentChange } : {})}
5713 availableEnvironments={logicalProjectEnvironments}
5714 />
5715 </div>
5716 )}
5717 </div>
57085718 </div>
57095719 </div>
57105720 <div
apps/web/src/index.css+92-17
......@@ -377,21 +377,20 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil
377377 backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturation));
378378 }
379379
380 .chat-composer-glass-host {
380 .chat-composer-glass-shell {
381381 --chat-composer-glass-surface: var(--card);
382 --chat-composer-outline: rgb(0 0 0 / 8%);
382383
383384 position: relative;
384 box-shadow:
385 0 0 0 1px rgb(0 0 0 / 8%),
386 0 12px 28px -18px rgb(0 0 0 / 40%);
385 isolation: isolate;
387386 }
388387
389 .chat-composer-glass-host::before {
388 .chat-composer-glass-shell::before {
390389 pointer-events: none;
391390 position: absolute;
392391 z-index: 0;
393392 inset: 0;
394 border-radius: inherit;
393 border-radius: 22px;
395394 background: color-mix(
396395 in srgb,
397396 var(--chat-composer-glass-surface) var(--glass-opacity),
......@@ -402,9 +401,73 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil
402401 content: "";
403402 }
404403
405 .chat-composer-context-strip {
406 --chat-composer-context-surface: var(--card);
404 .chat-composer-glass-shell-with-context {
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 {
408471 position: relative;
409472 isolation: isolate;
410473 }
......@@ -413,27 +476,39 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil
413476 position: absolute;
414477 z-index: -1;
415478 inset: 0;
479 border: 1px solid var(--chat-composer-outline);
416480 border-radius: 0 0 16px 16px;
417 background: color-mix(
418 in srgb,
419 var(--chat-composer-context-surface) var(--glass-opacity),
420 transparent
421 );
481 /* Start the strip outline exactly where the composer's 22px curve reaches its tangent. */
422482 -webkit-mask-image: linear-gradient(to bottom, transparent 0 1rem, black 1rem);
423483 mask-image: linear-gradient(to bottom, transparent 0 1rem, black 1rem);
424484 box-shadow: 0 12px 28px -18px rgb(0 0 0 / 40%);
425485 content: "";
426 -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturation));
427 backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturation));
428486 }
429487
430 .dark .chat-composer-glass-host {
488 .dark .chat-composer-glass-shell {
431489 --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 {
433495 box-shadow: none;
434496 }
435497
498 .dark .chat-composer-glass-host::after {
499 box-shadow: inset 0 1px var(--chat-composer-highlight);
500 }
501
436502 .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%);
437512 box-shadow: 0 14px 32px -18px rgb(0 0 0 / 75%);
438513 }
439514
......@@ -641,7 +716,7 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil
641716 background: var(--background) !important;
642717 }
643718
644 .chat-composer-glass-host::before {
719 .chat-composer-glass-shell::before {
645720 background: var(--chat-composer-glass-surface);
646721 }
647722