From a80e9e975057e9d66cf0d8fdbfb46d56178b9655 Mon Sep 17 00:00:00 2001 From: "t3-code[bot]" <269035359+t3-code[bot]@users.noreply.github.com> Date: Sun, 23 Aug 2026 22:32:20 +0000 Subject: [PATCH 1/5] fix(web): stop composer banner glass overlap Co-authored-by: Exotic <118054752+extoci@users.noreply.github.com> --- apps/web/src/components/chat/ComposerBannerStack.test.tsx | 1 - apps/web/src/components/chat/ComposerBannerStack.tsx | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/web/src/components/chat/ComposerBannerStack.test.tsx b/apps/web/src/components/chat/ComposerBannerStack.test.tsx index 9cf64fc6ff41..f07836ab32a6 100644 --- a/apps/web/src/components/chat/ComposerBannerStack.test.tsx +++ b/apps/web/src/components/chat/ComposerBannerStack.test.tsx @@ -53,7 +53,6 @@ describe("ComposerBannerStack", () => { expect(markup).not.toContain("data-composer-banner-stack-expanded-items"); expect(markup).toContain("chat-composer-drawer-surface"); expect(markup).toContain("chat-composer-drawer-attached"); - expect(markup).toContain("before:mask-none"); expect(markup).toContain("text-xs"); expect(markup).toContain('data-composer-banner-drawer="true"'); expect(markup).toContain('data-variant="warning"'); diff --git a/apps/web/src/components/chat/ComposerBannerStack.tsx b/apps/web/src/components/chat/ComposerBannerStack.tsx index 3158c8bd95b2..d8b8761447cb 100644 --- a/apps/web/src/components/chat/ComposerBannerStack.tsx +++ b/apps/web/src/components/chat/ComposerBannerStack.tsx @@ -199,7 +199,7 @@ function ComposerBannerStackAlert({ variant={item.variant} className={cn( attached - ? "chat-composer-drawer-surface chat-composer-drawer-attached px-3 pt-2 pb-[calc(var(--chat-composer-attachment-overlap)_+_0.375rem)] text-xs before:mask-none sm:px-4" + ? "chat-composer-drawer-surface chat-composer-drawer-attached px-3 pt-2 pb-[calc(var(--chat-composer-attachment-overlap)_+_0.375rem)] text-xs sm:px-4" : "alert-glass rounded-[22px]", item.className, )} From 3298ebb93a2145c1d49b3a51dd6feef584d1302b Mon Sep 17 00:00:00 2001 From: "t3-code[bot]" <269035359+t3-code[bot]@users.noreply.github.com> Date: Sun, 23 Aug 2026 23:11:20 +0000 Subject: [PATCH 2/5] fix(web): keep composer banners opaque Co-authored-by: Exotic <118054752+extoci@users.noreply.github.com> --- apps/web/src/components/chat/ComposerBannerStack.tsx | 2 +- apps/web/src/index.css | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/apps/web/src/components/chat/ComposerBannerStack.tsx b/apps/web/src/components/chat/ComposerBannerStack.tsx index d8b8761447cb..bbfaf34f2043 100644 --- a/apps/web/src/components/chat/ComposerBannerStack.tsx +++ b/apps/web/src/components/chat/ComposerBannerStack.tsx @@ -199,7 +199,7 @@ function ComposerBannerStackAlert({ variant={item.variant} className={cn( attached - ? "chat-composer-drawer-surface chat-composer-drawer-attached px-3 pt-2 pb-[calc(var(--chat-composer-attachment-overlap)_+_0.375rem)] text-xs sm:px-4" + ? "chat-composer-banner-surface chat-composer-drawer-surface chat-composer-drawer-attached px-3 pt-2 pb-[calc(var(--chat-composer-attachment-overlap)_+_0.375rem)] text-xs sm:px-4" : "alert-glass rounded-[22px]", item.className, )} diff --git a/apps/web/src/index.css b/apps/web/src/index.css index f69adb9cf08e..ff30254ef508 100644 --- a/apps/web/src/index.css +++ b/apps/web/src/index.css @@ -916,6 +916,12 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil } } + /* Banner text must hide timeline content, but the fill still stops where the + composer overlap begins so two translucent surfaces never stack. */ + .chat-composer-banner-surface::before { + --glass-opacity: 100%; + } + .chat-composer-drawer-slot + .chat-composer-drawer-slot::before, .chat-composer-drawer-slot + :has(.chat-composer-top-drawer) .chat-composer-top-drawer::before { border-top: 0; From fd8f7caee2fd3aa471229da375ce43b80ecbc25c Mon Sep 17 00:00:00 2001 From: "t3-code[bot]" <269035359+t3-code[bot]@users.noreply.github.com> Date: Sun, 23 Aug 2026 23:26:03 +0000 Subject: [PATCH 3/5] fix(web): fade timeline beneath composer overlays Co-authored-by: Exotic <118054752+extoci@users.noreply.github.com> --- .../components/chat/ComposerBannerStack.tsx | 2 +- .../src/components/chat/MessagesTimeline.tsx | 27 +++++++++++++++++-- apps/web/src/index.css | 6 ----- 3 files changed, 26 insertions(+), 9 deletions(-) diff --git a/apps/web/src/components/chat/ComposerBannerStack.tsx b/apps/web/src/components/chat/ComposerBannerStack.tsx index bbfaf34f2043..d8b8761447cb 100644 --- a/apps/web/src/components/chat/ComposerBannerStack.tsx +++ b/apps/web/src/components/chat/ComposerBannerStack.tsx @@ -199,7 +199,7 @@ function ComposerBannerStackAlert({ variant={item.variant} className={cn( attached - ? "chat-composer-banner-surface chat-composer-drawer-surface chat-composer-drawer-attached px-3 pt-2 pb-[calc(var(--chat-composer-attachment-overlap)_+_0.375rem)] text-xs sm:px-4" + ? "chat-composer-drawer-surface chat-composer-drawer-attached px-3 pt-2 pb-[calc(var(--chat-composer-attachment-overlap)_+_0.375rem)] text-xs sm:px-4" : "alert-glass rounded-[22px]", item.className, )} diff --git a/apps/web/src/components/chat/MessagesTimeline.tsx b/apps/web/src/components/chat/MessagesTimeline.tsx index af920c0d6156..1cb65314d26b 100644 --- a/apps/web/src/components/chat/MessagesTimeline.tsx +++ b/apps/web/src/components/chat/MessagesTimeline.tsx @@ -29,6 +29,7 @@ import { type KeyboardEvent, type MouseEvent, type ReactNode, + type CSSProperties, } from "react"; import { LegendList, type LegendListRef } from "@legendapp/list/react"; import { FileDiff } from "@pierre/diffs/react"; @@ -450,6 +451,19 @@ export const MessagesTimeline = memo(function MessagesTimeline({ ); return config ? { ...config, onReady: handleAnchorReady } : undefined; }, [anchorMessageId, handleAnchorReady, rows]); + const timelineScrollFadeMask = + contentInsetEndAdjustment > 0 + ? `linear-gradient(to bottom, ${ + topFadeEnabled ? "transparent 0, black var(--timeline-top-fade-height)," : "black 0," + } black calc(100% - var(--timeline-composer-overlay-height) - 1.5rem), transparent calc(100% - var(--timeline-composer-overlay-height)), transparent 100%)` + : null; + const timelineViewportStyle = + timelineScrollFadeMask === null + ? undefined + : ({ + "--timeline-composer-overlay-height": `${contentInsetEndAdjustment}px`, + "--virtualized-scroll-fade-mask": timelineScrollFadeMask, + } as CSSProperties); const handleScroll = useCallback(() => { const state = listRef.current?.getState?.(); @@ -581,7 +595,15 @@ export const MessagesTimeline = memo(function MessagesTimeline({ return ( -
+
ref={listRef} data={rows} @@ -601,7 +623,8 @@ export const MessagesTimeline = memo(function MessagesTimeline({ onScroll={handleScroll} className={cn( "scrollbar-gutter-both h-full min-h-0 overflow-x-hidden overscroll-y-contain px-3 [overflow-anchor:none] sm:px-5", - topFadeEnabled && "topbar-scroll-fade", + contentInsetEndAdjustment > 0 && "virtualized-scroll-fade", + contentInsetEndAdjustment === 0 && topFadeEnabled && "topbar-scroll-fade", )} ListHeaderComponent={ loadEarlier !== null ? ( diff --git a/apps/web/src/index.css b/apps/web/src/index.css index ff30254ef508..f69adb9cf08e 100644 --- a/apps/web/src/index.css +++ b/apps/web/src/index.css @@ -916,12 +916,6 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil } } - /* Banner text must hide timeline content, but the fill still stops where the - composer overlap begins so two translucent surfaces never stack. */ - .chat-composer-banner-surface::before { - --glass-opacity: 100%; - } - .chat-composer-drawer-slot + .chat-composer-drawer-slot::before, .chat-composer-drawer-slot + :has(.chat-composer-top-drawer) .chat-composer-top-drawer::before { border-top: 0; From 87166b01d15046985b348d9455d4451b97a93e7e Mon Sep 17 00:00:00 2001 From: "t3-code[bot]" <269035359+t3-code[bot]@users.noreply.github.com> Date: Sun, 23 Aug 2026 23:39:20 +0000 Subject: [PATCH 4/5] fix(web): preserve timeline fade bounds Co-authored-by: Exotic <118054752+extoci@users.noreply.github.com> --- .../src/components/chat/MessagesTimeline.tsx | 22 +++++++------------ 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/apps/web/src/components/chat/MessagesTimeline.tsx b/apps/web/src/components/chat/MessagesTimeline.tsx index 1cb65314d26b..4ba8da399b91 100644 --- a/apps/web/src/components/chat/MessagesTimeline.tsx +++ b/apps/web/src/components/chat/MessagesTimeline.tsx @@ -451,18 +451,17 @@ export const MessagesTimeline = memo(function MessagesTimeline({ ); return config ? { ...config, onReady: handleAnchorReady } : undefined; }, [anchorMessageId, handleAnchorReady, rows]); - const timelineScrollFadeMask = + const timelineBottomFadeMask = contentInsetEndAdjustment > 0 - ? `linear-gradient(to bottom, ${ - topFadeEnabled ? "transparent 0, black var(--timeline-top-fade-height)," : "black 0," - } black calc(100% - var(--timeline-composer-overlay-height) - 1.5rem), transparent calc(100% - var(--timeline-composer-overlay-height)), transparent 100%)` + ? "linear-gradient(to bottom, black 0, black calc(100% - var(--timeline-composer-overlay-height) - 1.5rem), transparent calc(100% - var(--timeline-composer-overlay-height)), transparent 100%)" : null; const timelineViewportStyle = - timelineScrollFadeMask === null + timelineBottomFadeMask === null ? undefined : ({ - "--timeline-composer-overlay-height": `${contentInsetEndAdjustment}px`, - "--virtualized-scroll-fade-mask": timelineScrollFadeMask, + "--timeline-composer-overlay-height": `min(${contentInsetEndAdjustment}px, 60%)`, + WebkitMaskImage: timelineBottomFadeMask, + maskImage: timelineBottomFadeMask, } as CSSProperties); const handleScroll = useCallback(() => { @@ -597,11 +596,7 @@ export const MessagesTimeline = memo(function MessagesTimeline({
@@ -623,8 +618,7 @@ export const MessagesTimeline = memo(function MessagesTimeline({ onScroll={handleScroll} className={cn( "scrollbar-gutter-both h-full min-h-0 overflow-x-hidden overscroll-y-contain px-3 [overflow-anchor:none] sm:px-5", - contentInsetEndAdjustment > 0 && "virtualized-scroll-fade", - contentInsetEndAdjustment === 0 && topFadeEnabled && "topbar-scroll-fade", + topFadeEnabled && "topbar-scroll-fade", )} ListHeaderComponent={ loadEarlier !== null ? ( From 846252361c66020e316eb88d7cc2f7d14eb8ce1f Mon Sep 17 00:00:00 2001 From: "t3-code[bot]" <269035359+t3-code[bot]@users.noreply.github.com> Date: Sun, 23 Aug 2026 23:47:37 +0000 Subject: [PATCH 5/5] fix(web): keep timeline chrome outside fades Co-authored-by: Exotic <118054752+extoci@users.noreply.github.com> --- .../src/components/chat/MessagesTimeline.tsx | 13 +++--- apps/web/src/index.css | 42 +++++++++++++++++++ 2 files changed, 47 insertions(+), 8 deletions(-) diff --git a/apps/web/src/components/chat/MessagesTimeline.tsx b/apps/web/src/components/chat/MessagesTimeline.tsx index 4ba8da399b91..966bbef7324d 100644 --- a/apps/web/src/components/chat/MessagesTimeline.tsx +++ b/apps/web/src/components/chat/MessagesTimeline.tsx @@ -451,17 +451,11 @@ export const MessagesTimeline = memo(function MessagesTimeline({ ); return config ? { ...config, onReady: handleAnchorReady } : undefined; }, [anchorMessageId, handleAnchorReady, rows]); - const timelineBottomFadeMask = - contentInsetEndAdjustment > 0 - ? "linear-gradient(to bottom, black 0, black calc(100% - var(--timeline-composer-overlay-height) - 1.5rem), transparent calc(100% - var(--timeline-composer-overlay-height)), transparent 100%)" - : null; const timelineViewportStyle = - timelineBottomFadeMask === null + contentInsetEndAdjustment === 0 ? undefined : ({ "--timeline-composer-overlay-height": `min(${contentInsetEndAdjustment}px, 60%)`, - WebkitMaskImage: timelineBottomFadeMask, - maskImage: timelineBottomFadeMask, } as CSSProperties); const handleScroll = useCallback(() => { @@ -618,7 +612,10 @@ export const MessagesTimeline = memo(function MessagesTimeline({ onScroll={handleScroll} className={cn( "scrollbar-gutter-both h-full min-h-0 overflow-x-hidden overscroll-y-contain px-3 [overflow-anchor:none] sm:px-5", - topFadeEnabled && "topbar-scroll-fade", + contentInsetEndAdjustment > 0 && "virtualized-scroll-fade", + contentInsetEndAdjustment > 0 && + (topFadeEnabled ? "timeline-scroll-combined-fade" : "timeline-scroll-bottom-fade"), + contentInsetEndAdjustment === 0 && topFadeEnabled && "topbar-scroll-fade", )} ListHeaderComponent={ loadEarlier !== null ? ( diff --git a/apps/web/src/index.css b/apps/web/src/index.css index f69adb9cf08e..1573e7abdcc0 100644 --- a/apps/web/src/index.css +++ b/apps/web/src/index.css @@ -415,6 +415,48 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil var(--app-scrollbar-width) 100%; } +@utility timeline-scroll-bottom-fade { + --virtualized-scroll-fade-mask: linear-gradient( + to bottom, + black 0, + black calc(100% - var(--timeline-composer-overlay-height) - 1.5rem), + transparent calc(100% - var(--timeline-composer-overlay-height)), + transparent 100% + ); +} + +@utility timeline-scroll-combined-fade { + --virtualized-scroll-fade-mask: linear-gradient( + to bottom, + transparent 0, + rgb(0 0 0 / 10%) 0.25rem, + rgb(0 0 0 / 30%) 0.6rem, + rgb(0 0 0 / 58%) 1.05rem, + rgb(0 0 0 / 82%) 1.55rem, + rgb(0 0 0 / 96%) 2.05rem, + black 2.5rem, + black calc(100% - var(--timeline-composer-overlay-height) - 1.5rem), + transparent calc(100% - var(--timeline-composer-overlay-height)), + transparent 100% + ); + + @variant sm { + --virtualized-scroll-fade-mask: linear-gradient( + to bottom, + transparent 0, + rgb(0 0 0 / 10%) 0.3rem, + rgb(0 0 0 / 30%) 0.72rem, + rgb(0 0 0 / 58%) 1.26rem, + rgb(0 0 0 / 82%) 1.86rem, + rgb(0 0 0 / 96%) 2.46rem, + black 3rem, + black calc(100% - var(--timeline-composer-overlay-height) - 1.5rem), + transparent calc(100% - var(--timeline-composer-overlay-height)), + transparent 100% + ); + } +} + /* Stage-channel art needs a mask and pseudo-element gradient, so keep the behavior composable without tying it to the global components layer. */ @utility sidebar-stage-backdrop {