From 0968f0cf398635a4b11e212a7bb5bb89a3f36b46 Mon Sep 17 00:00:00 2001 From: Tim Black Date: Mon, 27 Apr 2026 13:48:05 -0700 Subject: [PATCH 1/5] feat(SS-5656): aurora shader background on empty chat state MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Uses shaders.com instead of react-three/fiber — no GLSL, no render loop, WebGPU-first with WebGL2 fallback built in. ShapeShift brand palette (purple-500 / green-500 / purple-300) with slow drift. Gated behind useIsMobile (768px) and a WebGL capability check; falls back to a CSS radial-gradient on mobile or when WebGL is unavailable. Shader chunk is lazy-loaded so it doesn't hit the main bundle. Co-Authored-By: Claude Sonnet 4.6 --- apps/agentic-chat/package.json | 1 + .../src/components/AuroraBackground.tsx | 62 +++++++++++++++++++ apps/agentic-chat/src/components/Chat.tsx | 6 +- bun.lock | 5 ++ 4 files changed, 72 insertions(+), 2 deletions(-) create mode 100644 apps/agentic-chat/src/components/AuroraBackground.tsx diff --git a/apps/agentic-chat/package.json b/apps/agentic-chat/package.json index 90dd628b..01051bcb 100644 --- a/apps/agentic-chat/package.json +++ b/apps/agentic-chat/package.json @@ -55,6 +55,7 @@ "rehype-katex": "^7.0.1", "remark-gfm": "^4.0.1", "remark-math": "^6.0.0", + "shaders": "^2.5.109", "sonner": "^2.0.7", "tailwind-merge": "^3.2.0", "viem": "*", diff --git a/apps/agentic-chat/src/components/AuroraBackground.tsx b/apps/agentic-chat/src/components/AuroraBackground.tsx new file mode 100644 index 00000000..2379f201 --- /dev/null +++ b/apps/agentic-chat/src/components/AuroraBackground.tsx @@ -0,0 +1,62 @@ +import { lazy, Suspense } from 'react' + +import { useIsMobile } from '@/hooks/use-mobile' + +const ShaderAurora = lazy(() => + import('shaders/react').then(m => ({ + default: function AuroraShader() { + const { Shader, Aurora } = m + return ( + + + + ) + }, + })) +) + +function isWebGLAvailable(): boolean { + try { + const canvas = document.createElement('canvas') + return !!(window.WebGLRenderingContext && (canvas.getContext('webgl2') ?? canvas.getContext('webgl'))) + } catch { + return false + } +} + +function CSSFallback() { + return ( +
+ ) +} + +export function AuroraBackground() { + const isMobile = useIsMobile() + + if (isMobile || !isWebGLAvailable()) { + return + } + + return ( + }> + + + ) +} diff --git a/apps/agentic-chat/src/components/Chat.tsx b/apps/agentic-chat/src/components/Chat.tsx index 5c1e1e75..db4f8c53 100644 --- a/apps/agentic-chat/src/components/Chat.tsx +++ b/apps/agentic-chat/src/components/Chat.tsx @@ -6,6 +6,7 @@ import { useStreamPauseDetector } from '../hooks/useStreamPauseDetector' import { useChatContext } from '../providers/ChatProvider' import { AssistantMessage } from './AssistantMessage' +import { AuroraBackground } from './AuroraBackground' import { Composer } from './Composer' import { LoadingIndicator } from './LoadingIndicator' import { Button } from './ui/Button' @@ -93,8 +94,9 @@ export function Chat() { {/* Messages viewport */}
{isEmpty ? ( -
-
How can I help you today?
+
+ +
How can I help you today?
) : ( Date: Mon, 27 Apr 2026 15:16:00 -0700 Subject: [PATCH 2/5] fix(SS-5656): start aurora animation loop + glass chrome on header/bottom MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Animation fix: shaders.com IntersectionObserver only calls startAnimation() on a not-visible → visible transition. Mounting with display:none then flipping to block on the next animation frame forces that transition. UI: header, suggestion strip, and composer get bg/80 backdrop-blur-md so the aurora bleeds through. Header and suggestion strip get a border to ground them visually against the background. Built with Claude --- apps/agentic-chat/src/app/dashboard/page.tsx | 2 +- .../src/components/AuroraBackground.tsx | 42 ++++++++++++------- apps/agentic-chat/src/components/Chat.tsx | 4 +- 3 files changed, 30 insertions(+), 18 deletions(-) diff --git a/apps/agentic-chat/src/app/dashboard/page.tsx b/apps/agentic-chat/src/app/dashboard/page.tsx index db3e1a0e..d2886e3e 100644 --- a/apps/agentic-chat/src/app/dashboard/page.tsx +++ b/apps/agentic-chat/src/app/dashboard/page.tsx @@ -13,7 +13,7 @@ export const Dashboard = () => { {isSidebarLeftEnabled && } -
+
{isSidebarLeftEnabled && }
diff --git a/apps/agentic-chat/src/components/AuroraBackground.tsx b/apps/agentic-chat/src/components/AuroraBackground.tsx index 2379f201..2acae802 100644 --- a/apps/agentic-chat/src/components/AuroraBackground.tsx +++ b/apps/agentic-chat/src/components/AuroraBackground.tsx @@ -1,4 +1,4 @@ -import { lazy, Suspense } from 'react' +import { lazy, Suspense, useEffect, useRef, useState } from 'react' import { useIsMobile } from '@/hooks/use-mobile' @@ -6,21 +6,33 @@ const ShaderAurora = lazy(() => import('shaders/react').then(m => ({ default: function AuroraShader() { const { Shader, Aurora } = m + // Start hidden so IntersectionObserver sees the transition to visible, + // which is what triggers the animation loop in the shaders library. + const [visible, setVisible] = useState(false) + const rafRef = useRef(0) + + useEffect(() => { + rafRef.current = requestAnimationFrame(() => setVisible(true)) + return () => cancelAnimationFrame(rafRef.current) + }, []) + return ( - - - +
+ + + +
) }, })) diff --git a/apps/agentic-chat/src/components/Chat.tsx b/apps/agentic-chat/src/components/Chat.tsx index db4f8c53..6de5a268 100644 --- a/apps/agentic-chat/src/components/Chat.tsx +++ b/apps/agentic-chat/src/components/Chat.tsx @@ -118,7 +118,7 @@ export function Chat() { {/* Suggestions above composer - only shown when empty */} {isEmpty && ( -
+
{WELCOME_SUGGESTIONS.map((suggestion, index) => (