History

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

Julius Marminge committed 3 months ago

Commit 6ef7aa81 parent 193e3c64 files · +216 −131Browse files

Unified · Split

4 files
  1. apps/web/src/components/BranchToolbar.tsx +1 −1
  2. apps/web/src/components/BranchToolbarBranchSelector.tsx +1 −1
  3. apps/web/src/components/ChatView.tsx +122 −112
  4. apps/web/src/index.css +92 −17
apps/web/src/components/BranchToolbar.tsx+1−1
@@ -248,7 +248,7 @@ export const BranchToolbar = memo(function BranchToolbar({ · 247 unmodified lines
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({ · 730 unmodified lines
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) { · 2351 unmodified lines
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) { · 3234 unmodified lines
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 · 376 unmodified lines
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 · 4 unmodified lines
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 · 2 unmodified lines
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 · 201 unmodified lines
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