feat(ui): add silk aurora hero background - #18
Conversation
|
@sorkhademanthan is attempting to deploy a commit to the Componentry Team on Vercel. A member of the Team first needs to authorize it. |
📝 WalkthroughWalkthroughChangesAurora Flow component
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant DocsPage
participant AuroraFlowDocs
participant AuroraFlowPlayground
participant AuroraFlow
participant WebGLContext
DocsPage->>AuroraFlowDocs: load aurora-flow documentation
AuroraFlowDocs->>AuroraFlowPlayground: render playground and controls
AuroraFlowPlayground->>AuroraFlow: apply configuration
AuroraFlow->>WebGLContext: initialize and render shaders
WebGLContext-->>AuroraFlow: report rendering failure when initialization fails
AuroraFlow->>AuroraFlow: render CSS fallback
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1🛠️ Fix failing CI checks 💡
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (8)
packages/ui/src/components/silk-aurora.tsx (6)
230-234: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueConsider accepting 3-digit hex shorthand.
HEX_COLOR_REGEXaccepts only 6-digit hex. A consumer that passes#fffgets the preset color instead, with no warning. Shorthand hex is common in a public palette API.🎨 Proposed refactor
-const HEX_COLOR_REGEX = /^#?[0-9a-fA-F]{6}$/; +const HEX_COLOR_REGEX = /^#?(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/;function sanitizeHexColor(value: string | undefined, fallback: string) { const trimmed = value?.trim() ?? ""; if (!HEX_COLOR_REGEX.test(trimmed)) return fallback; - return trimmed.startsWith("#") ? trimmed : `#${trimmed}`; + const digits = trimmed.startsWith("#") ? trimmed.slice(1) : trimmed; + return `#${digits.length === 3 ? digits.replace(/./g, "$&$&") : digits}`; }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/ui/src/components/silk-aurora.tsx` around lines 230 - 234, Update sanitizeHexColor and HEX_COLOR_REGEX to accept both 3-digit and 6-digit hexadecimal colors, with or without the leading “#”. Preserve the existing fallback behavior for invalid values and normalization that adds “#” when it is absent.
436-439: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueDelete the program and shaders on this early return.
The link-failure branch at Line 425 and the uniform-lookup branch at Line 488 both delete the program, shaders, and buffer. This branch deletes nothing. The abandoned context makes the practical impact small, but the inconsistency is easy to remove.
🧹 Proposed cleanup
if (position < 0 || !buffer) { + if (buffer) gl.deleteBuffer(buffer); + gl.deleteProgram(program); + gl.deleteShader(vertexShader); + gl.deleteShader(fragmentShader); setHasWebGLError(true); return; }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/ui/src/components/silk-aurora.tsx` around lines 436 - 439, Update the early-return branch in the WebGL initialization flow around the position and buffer check to delete the created program, shaders, and buffer before setting hasWebGLError and returning, matching the cleanup performed by the link-failure and uniform-lookup branches.
497-504: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick winCache the container rect instead of measuring on every pointer move.
handlePointerMovecallscontainer.getBoundingClientRect()for eachpointermoveevent. That is a forced layout read on a high-frequency event, and it runs on the main thread while the page may be doing other work.resizealready runs on everyResizeObservercallback, so the rect can be cached there and reused.⚡ Proposed refactor
+ let rect = container.getBoundingClientRect(); const handlePointerMove = (event: PointerEvent) => { if (!settingsRef.current.pointerInteraction || reducedMotion) return; - const rect = container.getBoundingClientRect(); + if (rect.width === 0 || rect.height === 0) return; targetPointerRef.current = { x: clamp((event.clientX - rect.left) / rect.width, 0, 1), y: clamp(1 - (event.clientY - rect.top) / rect.height, 0, 1), }; };Refresh the cached rect inside
resize(Line 589) and in the scroll handler, becausegetBoundingClientRectreturns viewport-relative coordinates that change as the page scrolls.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/ui/src/components/silk-aurora.tsx` around lines 497 - 504, Cache the container bounding rect in the existing resize flow and refresh it in the scroll handler to account for viewport-relative coordinates. Update handlePointerMove to reuse the cached rect instead of calling container.getBoundingClientRect() on every pointer event, preserving the current clamping behavior.
656-694: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winExtract the shared container to remove the duplicated wrapper markup.
fallbackand the main branch repeat the same container: identicalcnclass list, identicalstyle, identical{...props}spread, identical gradient layer, and identical children wrapper. The two copies already differ inrefand the canvas. A future change to the layout or the gradient must be applied in both places.♻️ Proposed refactor
+ const renderShell = (ref: React.Ref<HTMLDivElement> | null, canvas: React.ReactNode) => ( + <div + ref={ref ?? undefined} + className={cn("relative min-h-[420px] w-full overflow-hidden", className)} + style={{ ...style, borderRadius }} + {...props} + > + <div + aria-hidden="true" + className="absolute inset-0" + style={staticBackground} + /> + {canvas} + {children && <div className="relative z-10 size-full">{children}</div>} + </div> + ); + return ( - <WebGLErrorBoundary fallback={fallback}> - ... - </WebGLErrorBoundary> + <WebGLErrorBoundary fallback={renderShell(null, null)}> + {renderShell( + containerRef, + !hasWebGLError && ( + <canvas + ref={canvasRef} + aria-hidden="true" + className="pointer-events-none absolute inset-0 size-full" + /> + ), + )} + </WebGLErrorBoundary> );Note that
{...props}is spread into both branches today. Ifpropscontains anid, and the boundary swaps branches, the id stays unique, so behavior is unchanged.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/ui/src/components/silk-aurora.tsx` around lines 656 - 694, Refactor the shared container markup used by fallback and the main render in the SilkAurora component into a single reusable wrapper, preserving the existing className, style, props spread, staticBackground layer, and children wrapper. Keep only the branch-specific ref on the main container and canvas rendering controlled by hasWebGLError, while maintaining the current WebGLErrorBoundary behavior.
395-412: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winSurface the shader info log on compile failure.
compileShaderdiscardsgl.getShaderInfoLog(shader). If this shader fails to compile on a specific driver, the component silently degrades to the gradient fallback and no diagnostic reaches the developer. The shader is long and uses driver-sensitive constructs, so a compile failure in the field is plausible.ripple-transition.tsxalready includes the info log in its error path.🔍 Proposed refactor
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { + if (process.env.NODE_ENV !== "production") { + console.error( + "SilkAurora shader compilation failed:", + gl.getShaderInfoLog(shader), + ); + } gl.deleteShader(shader); return null; }Apply the same treatment to
gl.getProgramInfoLog(program)in the link failure branch at Line 425.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/ui/src/components/silk-aurora.tsx` around lines 395 - 412, Update compileShader in silk-aurora.tsx to retrieve and surface gl.getShaderInfoLog(shader) before deleting the shader on compilation failure, preserving the existing null return and fallback behavior. Also update the program link failure branch near the shader setup to retrieve and surface gl.getProgramInfoLog(program), matching the diagnostic handling used in ripple-transition.tsx.
374-375: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueMove the
settingsRefassignment out of the render body.
settingsRef.current = settingsmutates a ref during render, which React documents as unsafe. Keep the ref assignment in auseEffectkeyed bysettingsso discardable renders do not leave committed ref values from replays.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/ui/src/components/silk-aurora.tsx` around lines 374 - 375, Move the settingsRef.current assignment out of the render body and into a React useEffect keyed by settings. Preserve the existing useRef initialization and update the ref only after the settings change is committed, ensuring discarded renders cannot mutate the ref.apps/web/components/docs/previews/silk-aurora-playground.tsx (2)
200-222: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd
aria-pressedto the preset buttons.The preset buttons signal selection only through a border color. Screen reader users receive no selection state. Add
aria-pressedso the active preset is announced.♿ Proposed fix
<button key={preset} type="button" + aria-pressed={config.preset === preset} onClick={() => selectPreset(preset)}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/web/components/docs/previews/silk-aurora-playground.tsx` around lines 200 - 222, Add aria-pressed to each preset button rendered in PRESET_NAMES.map, setting it to whether config.preset equals the current preset so screen readers receive the active selection state.
102-139: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueConsider omitting
colorswhen it matches the preset palette.
generateCodealways emits bothpresetandcolors.SilkAuroraresolves each channel fromcolors?.[index]first, so the emittedcolorsarray always overrides the preset. A user who only selects a preset then copies a snippet where the preset value has no effect. Emitcolorsonly when the array differs fromSILK_AURORA_PRESETS[config.preset].♻️ Proposed refactor
function generateCode(config: SilkAuroraConfig) { + const isPresetPalette = config.colors.every( + (color, index) => color === SILK_AURORA_PRESETS[config.preset][index], + ); + const colorsLine = isPresetPalette + ? "" + : `\n colors={${JSON.stringify(config.colors)}}`; return `import { SilkAurora } from "`@/components/ui/silk-aurora`" <SilkAurora - preset="${config.preset}" - colors={${JSON.stringify(config.colors)}} + preset="${config.preset}"${colorsLine} speed={${config.speed}}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/web/components/docs/previews/silk-aurora-playground.tsx` around lines 102 - 139, Update generateCode to compare config.colors with SILK_AURORA_PRESETS[config.preset] and emit the colors prop only when they differ; otherwise omit it so SilkAurora uses the selected preset palette. Preserve the existing colors serialization when a custom palette is configured.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/ui/src/components/silk-aurora.tsx`:
- Around line 599-623: Track whether the container is currently intersecting in
the IntersectionObserver callback, and update handleVisibility to restart the
animation only when that tracked state is true. Preserve stopping behavior for
hidden documents and ensure off-screen instances remain paused after visibility
changes.
- Around line 245-257: Update useReducedMotion to lazily initialize reduced from
window.matchMedia("(prefers-reduced-motion: reduce)").matches when window is
available, while returning false during SSR. Keep the existing useEffect
listener and cleanup, using the same media-query behavior without initially
enabling motion for reduced-motion users.
- Around line 164-168: Update the fragment output in the shader’s gl_FragColor
assignment to premultiply the clamped RGB color by the same clamped u_opacity
alpha, preserving the existing color grading and alpha clamping while matching
the premultipliedAlpha context.
---
Nitpick comments:
In `@apps/web/components/docs/previews/silk-aurora-playground.tsx`:
- Around line 200-222: Add aria-pressed to each preset button rendered in
PRESET_NAMES.map, setting it to whether config.preset equals the current preset
so screen readers receive the active selection state.
- Around line 102-139: Update generateCode to compare config.colors with
SILK_AURORA_PRESETS[config.preset] and emit the colors prop only when they
differ; otherwise omit it so SilkAurora uses the selected preset palette.
Preserve the existing colors serialization when a custom palette is configured.
In `@packages/ui/src/components/silk-aurora.tsx`:
- Around line 230-234: Update sanitizeHexColor and HEX_COLOR_REGEX to accept
both 3-digit and 6-digit hexadecimal colors, with or without the leading “#”.
Preserve the existing fallback behavior for invalid values and normalization
that adds “#” when it is absent.
- Around line 436-439: Update the early-return branch in the WebGL
initialization flow around the position and buffer check to delete the created
program, shaders, and buffer before setting hasWebGLError and returning,
matching the cleanup performed by the link-failure and uniform-lookup branches.
- Around line 497-504: Cache the container bounding rect in the existing resize
flow and refresh it in the scroll handler to account for viewport-relative
coordinates. Update handlePointerMove to reuse the cached rect instead of
calling container.getBoundingClientRect() on every pointer event, preserving the
current clamping behavior.
- Around line 656-694: Refactor the shared container markup used by fallback and
the main render in the SilkAurora component into a single reusable wrapper,
preserving the existing className, style, props spread, staticBackground layer,
and children wrapper. Keep only the branch-specific ref on the main container
and canvas rendering controlled by hasWebGLError, while maintaining the current
WebGLErrorBoundary behavior.
- Around line 395-412: Update compileShader in silk-aurora.tsx to retrieve and
surface gl.getShaderInfoLog(shader) before deleting the shader on compilation
failure, preserving the existing null return and fallback behavior. Also update
the program link failure branch near the shader setup to retrieve and surface
gl.getProgramInfoLog(program), matching the diagnostic handling used in
ripple-transition.tsx.
- Around line 374-375: Move the settingsRef.current assignment out of the render
body and into a React useEffect keyed by settings. Preserve the existing useRef
initialization and update the ref only after the settings change is committed,
ensuring discarded renders cannot mutate the ref.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: d03c5e82-3bef-41f4-8f99-ba4a418eee0d
📒 Files selected for processing (7)
apps/web/app/docs/page.tsxapps/web/components/docs/previews/silk-aurora-card-preview.tsxapps/web/components/docs/previews/silk-aurora-playground.tsxapps/web/components/docs/silk-aurora.tsxapps/web/public/r/silk-aurora.jsonapps/web/registry/index.tspackages/ui/src/components/silk-aurora.tsx
| const start = () => { | ||
| if (running || reducedMotion || document.hidden) return; | ||
| running = true; | ||
| lastNow = performance.now(); | ||
| rafId = window.requestAnimationFrame(draw); | ||
| }; | ||
| const stop = () => { | ||
| running = false; | ||
| window.cancelAnimationFrame(rafId); | ||
| }; | ||
|
|
||
| rafId = requestAnimationFrame(render); | ||
|
|
||
| return () => { | ||
| container.removeEventListener("pointermove", handlePointerMove); | ||
| container.removeEventListener("pointerleave", handlePointerLeave); | ||
| cancelAnimationFrame(rafId); | ||
| resizeObserver.disconnect(); | ||
| gl.deleteBuffer(buffer); | ||
| gl.deleteProgram(program); | ||
| gl.deleteShader(vertexShader); | ||
| gl.deleteShader(fragmentShader); | ||
| }; | ||
| } catch { | ||
| setHasWebGLError(true); | ||
| return () => { | ||
| container.removeEventListener("pointermove", handlePointerMove); | ||
| container.removeEventListener("pointerleave", handlePointerLeave); | ||
| }; | ||
| } | ||
| }, [hasWebGLError, settings]); | ||
| resize(); | ||
| drawStaticRef.current = () => draw(performance.now(), false); | ||
| const resizeObserver = new ResizeObserver(resize); | ||
| resizeObserver.observe(container); | ||
| const intersectionObserver = new IntersectionObserver(([entry]) => { | ||
| if (entry?.isIntersecting) start(); | ||
| else stop(); | ||
| }); | ||
| intersectionObserver.observe(container); | ||
| const handleVisibility = () => { | ||
| if (document.hidden) stop(); | ||
| else start(); | ||
| }; | ||
| document.addEventListener("visibilitychange", handleVisibility); |
There was a problem hiding this comment.
🚀 Performance & Scalability | 🟠 Major | ⚡ Quick win
handleVisibility restarts the loop for off-screen instances.
start() checks running, reducedMotion, and document.hidden. It does not check whether the container intersects the viewport. When the user switches away from the tab and back, handleVisibility calls start() unconditionally, so an instance that the IntersectionObserver previously stopped begins animating again. IntersectionObserver does not fire again until the intersection state actually changes, so the loop keeps running off-screen indefinitely.
This contradicts the documented behavior in apps/web/components/docs/silk-aurora.tsx, which states the component pauses outside the viewport.
🐛 Proposed fix: track intersection state
+ let visible = false;
const start = () => {
- if (running || reducedMotion || document.hidden) return;
+ if (running || reducedMotion || document.hidden || !visible) return;
running = true;
lastNow = performance.now();
rafId = window.requestAnimationFrame(draw);
}; const intersectionObserver = new IntersectionObserver(([entry]) => {
- if (entry?.isIntersecting) start();
- else stop();
+ visible = entry?.isIntersecting ?? false;
+ if (visible) start();
+ else stop();
});📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const start = () => { | |
| if (running || reducedMotion || document.hidden) return; | |
| running = true; | |
| lastNow = performance.now(); | |
| rafId = window.requestAnimationFrame(draw); | |
| }; | |
| const stop = () => { | |
| running = false; | |
| window.cancelAnimationFrame(rafId); | |
| }; | |
| rafId = requestAnimationFrame(render); | |
| return () => { | |
| container.removeEventListener("pointermove", handlePointerMove); | |
| container.removeEventListener("pointerleave", handlePointerLeave); | |
| cancelAnimationFrame(rafId); | |
| resizeObserver.disconnect(); | |
| gl.deleteBuffer(buffer); | |
| gl.deleteProgram(program); | |
| gl.deleteShader(vertexShader); | |
| gl.deleteShader(fragmentShader); | |
| }; | |
| } catch { | |
| setHasWebGLError(true); | |
| return () => { | |
| container.removeEventListener("pointermove", handlePointerMove); | |
| container.removeEventListener("pointerleave", handlePointerLeave); | |
| }; | |
| } | |
| }, [hasWebGLError, settings]); | |
| resize(); | |
| drawStaticRef.current = () => draw(performance.now(), false); | |
| const resizeObserver = new ResizeObserver(resize); | |
| resizeObserver.observe(container); | |
| const intersectionObserver = new IntersectionObserver(([entry]) => { | |
| if (entry?.isIntersecting) start(); | |
| else stop(); | |
| }); | |
| intersectionObserver.observe(container); | |
| const handleVisibility = () => { | |
| if (document.hidden) stop(); | |
| else start(); | |
| }; | |
| document.addEventListener("visibilitychange", handleVisibility); | |
| let visible = false; | |
| const start = () => { | |
| if (running || reducedMotion || document.hidden || !visible) return; | |
| running = true; | |
| lastNow = performance.now(); | |
| rafId = window.requestAnimationFrame(draw); | |
| }; | |
| const stop = () => { | |
| running = false; | |
| window.cancelAnimationFrame(rafId); | |
| }; | |
| resize(); | |
| drawStaticRef.current = () => draw(performance.now(), false); | |
| const resizeObserver = new ResizeObserver(resize); | |
| resizeObserver.observe(container); | |
| const intersectionObserver = new IntersectionObserver(([entry]) => { | |
| visible = entry?.isIntersecting ?? false; | |
| if (visible) start(); | |
| else stop(); | |
| }); | |
| intersectionObserver.observe(container); | |
| const handleVisibility = () => { | |
| if (document.hidden) stop(); | |
| else start(); | |
| }; | |
| document.addEventListener("visibilitychange", handleVisibility); |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/ui/src/components/silk-aurora.tsx` around lines 599 - 623, Track
whether the container is currently intersecting in the IntersectionObserver
callback, and update handleVisibility to restart the animation only when that
tracked state is true. Preserve stopping behavior for hidden documents and
ensure off-screen instances remain paused after visibility changes.
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/web/app/docs/page.tsx (1)
196-200: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winRestore a live preview branch for
silk-aurora.
apps/web/app/docs/page.tsxonly special-casestext-morphandaurora-flow, so thesilk-auroracatalog card no longer uses a live preview component and falls back to the poster/video path. Add back aSilkAuroraCardPreviewbranch or update the docs to confirm this fallback is intentional.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/web/app/docs/page.tsx` around lines 196 - 200, Add a live-preview condition in the component rendering flow alongside the existing `text-morph` and `aurora-flow` branches, matching the `silk-aurora` slug to `SilkAuroraCardPreview`. Ensure the corresponding preview component is available in this page and preserve the existing fallback behavior for all other catalog cards.
🧹 Nitpick comments (4)
packages/ui/src/components/aurora-flow.tsx (3)
636-639: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winConsider releasing the WebGL context on unmount.
Browsers cap the number of live WebGL contexts per page, commonly around 16. Deleting the program, shaders, and buffer does not release the context. The context is reclaimed only when the canvas is garbage collected, and that timing is not deterministic. The documentation page mounts several Aurora Flow instances, so repeated mount and unmount cycles can exhaust the cap and force older contexts to be lost.
Call
WEBGL_lose_context.loseContext()in the cleanup to release the context immediately.♻️ Proposed change
gl.deleteBuffer(buffer); gl.deleteProgram(program); gl.deleteShader(vertexShader); gl.deleteShader(fragmentShader); + gl.getExtension("WEBGL_lose_context")?.loseContext();🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/ui/src/components/aurora-flow.tsx` around lines 636 - 639, Update the unmount cleanup around the WebGL resource deletion in the Aurora Flow component to obtain the WEBGL_lose_context extension and call loseContext() after releasing the buffers, program, and shaders. Keep the cleanup safe when the extension is unavailable.
374-375: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueDo not write to a ref during render.
React documents ref writes during render as unsupported. Under concurrent rendering, React can discard a render, and the rAF loop then reads settings that were never committed. Move the assignment into an effect.
♻️ Proposed change
const settingsRef = React.useRef(settings); - settingsRef.current = settings; + React.useEffect(() => { + settingsRef.current = settings; + }, [settings]);Note: the existing effect at Line 643 already depends on
settings. Order the effects so the ref update runs first.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/ui/src/components/aurora-flow.tsx` around lines 374 - 375, Move the settingsRef.current assignment out of render and into a React effect that depends on settings. In the component containing settingsRef, place this ref-update effect before the existing settings-dependent effect around the rAF loop so committed settings are updated first.
15-16: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winGuard the
highpprecision qualifier for WebGL1 portability.WebGL1 does not guarantee
highpsupport in fragment shaders. On devices without it, the shader fails to compile and the component falls back to the static gradient. Use the standard preprocessor guard to keep the animated effect on those devices.♻️ Proposed precision guard
const FRAGMENT_SHADER = ` -precision highp float; +#ifdef GL_FRAGMENT_PRECISION_HIGH +precision highp float; +#else +precision mediump float; +#endif🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/ui/src/components/aurora-flow.tsx` around lines 15 - 16, Update the FRAGMENT_SHADER precision declaration to guard highp support with the standard WebGL preprocessor check, providing a mediump fallback when highp is unavailable so the shader remains compilable on WebGL1 devices.apps/web/components/docs/previews/aurora-flow-playground.tsx (1)
161-238: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd
aria-pressedto preset buttons.The preset buttons at Lines 201-211 show the selected preset only through border color. Screen reader users cannot detect the selected preset from this markup. Add
aria-pressed={config.preset === preset}to each preset button.♻️ Proposed fix
<button key={preset} type="button" onClick={() => selectPreset(preset)} + aria-pressed={config.preset === preset} className={cn(🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/web/components/docs/previews/aurora-flow-playground.tsx` around lines 161 - 238, Add aria-pressed to each preset button rendered in AuroraFlowPersonalizePanel, using the existing config.preset === preset selection check so assistive technologies expose the active preset state.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@apps/web/public/r/aurora-flow.json`:
- Around line 19-23: Extract components/ui/webgl-error-boundary.tsx from the
aurora-flow registry item and define it as its own reusable registry:ui item.
Update animated-gradient, aurora-flow, ripple-transition, silk-aurora, and
watermelon manifests to reference that shared item instead of embedding the same
path, preserving the existing WebGLErrorBoundary and WebGLFallback
implementation.
In `@packages/ui/src/components/aurora-flow.tsx`:
- Around line 384-389: Align the WebGL alpha configuration with the fragment
shader output in the canvas setup around getContext: either set
premultipliedAlpha to false for the existing straight-alpha shader, or update
the fragment shader’s output to multiply RGB by the clamped opacity while
retaining premultipliedAlpha true. Preserve correct rendering across the Opacity
slider’s 0–1 range.
- Around line 614-623: Track the current intersection state in the
IntersectionObserver flow around handleVisibility, updating it from each
observer entry. Gate the visibility-triggered start() call on both document
visibility and the stored intersecting state, while preserving stop() behavior
when either condition is false.
---
Outside diff comments:
In `@apps/web/app/docs/page.tsx`:
- Around line 196-200: Add a live-preview condition in the component rendering
flow alongside the existing `text-morph` and `aurora-flow` branches, matching
the `silk-aurora` slug to `SilkAuroraCardPreview`. Ensure the corresponding
preview component is available in this page and preserve the existing fallback
behavior for all other catalog cards.
---
Nitpick comments:
In `@apps/web/components/docs/previews/aurora-flow-playground.tsx`:
- Around line 161-238: Add aria-pressed to each preset button rendered in
AuroraFlowPersonalizePanel, using the existing config.preset === preset
selection check so assistive technologies expose the active preset state.
In `@packages/ui/src/components/aurora-flow.tsx`:
- Around line 636-639: Update the unmount cleanup around the WebGL resource
deletion in the Aurora Flow component to obtain the WEBGL_lose_context extension
and call loseContext() after releasing the buffers, program, and shaders. Keep
the cleanup safe when the extension is unavailable.
- Around line 374-375: Move the settingsRef.current assignment out of render and
into a React effect that depends on settings. In the component containing
settingsRef, place this ref-update effect before the existing settings-dependent
effect around the rAF loop so committed settings are updated first.
- Around line 15-16: Update the FRAGMENT_SHADER precision declaration to guard
highp support with the standard WebGL preprocessor check, providing a mediump
fallback when highp is unavailable so the shader remains compilable on WebGL1
devices.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 4552db53-cfb6-4eff-b81a-ba03a2378b1c
📒 Files selected for processing (10)
apps/web/app/docs/page.tsxapps/web/components/docs/aurora-flow.tsxapps/web/components/docs/lazy-registry.tsapps/web/components/docs/previews/aurora-flow-card-preview.tsxapps/web/components/docs/previews/aurora-flow-playground.tsxapps/web/instrumentation-client.tsapps/web/public/r/aurora-flow.jsonapps/web/public/r/registry.jsonapps/web/registry/index.tspackages/ui/src/components/aurora-flow.tsx
💤 Files with no reviewable changes (1)
- apps/web/instrumentation-client.ts
| { | ||
| "path": "components/ui/webgl-error-boundary.tsx", | ||
| "content": "\"use client\";\n\nimport { cn } from \"@/lib/utils\";\nimport * as React from \"react\";\n\ninterface WebGLErrorBoundaryProps {\n children: React.ReactNode;\n fallback?: React.ReactNode;\n onError?: (error: Error, errorInfo: React.ErrorInfo) => void;\n}\n\ninterface WebGLErrorBoundaryState {\n hasError: boolean;\n}\n\nexport class WebGLErrorBoundary extends React.Component<\n WebGLErrorBoundaryProps,\n WebGLErrorBoundaryState\n> {\n public state: WebGLErrorBoundaryState = { hasError: false };\n\n static getDerivedStateFromError(): WebGLErrorBoundaryState {\n return { hasError: true };\n }\n\n componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {\n this.props.onError?.(error, errorInfo);\n }\n\n render() {\n if (this.state.hasError) {\n return this.props.fallback ?? <WebGLFallback />;\n }\n return this.props.children;\n }\n}\n\ninterface WebGLFallbackProps {\n className?: string;\n message?: string;\n}\n\nexport function WebGLFallback({\n className,\n message = \"Interactive WebGL content is unavailable on this device/browser.\",\n}: WebGLFallbackProps) {\n return (\n <div\n className={cn(\n \"flex h-full w-full items-center justify-center bg-gradient-to-br from-zinc-950 via-slate-900 to-zinc-900 px-4 text-center text-sm text-white/75\",\n className,\n )}\n role=\"status\"\n aria-live=\"polite\"\n >\n <p>{message}</p>\n </div>\n );\n}\n", | ||
| "type": "registry:ui" | ||
| } |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Description: Look for other registry manifests bundling the same file path.
rg -n "webgl-error-boundary" apps/web/public/r/*.jsonRepository: harshjdhv/componentry
Length of output: 50378
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf 'Files containing webgl-error-boundary in registry manifests:\n'
rg -n '"path":\s*"components/ui/webgl-error-boundary.tsx"|webgl-error-boundary\.tsx' apps/web/public/r/registry.json apps/web/public/r/*.json
printf '\nManifest entries with path:\n'
python3 - <<'PY'
from pathlib import Path
import json
for path in sorted(Path("apps/web/public/r").glob("*.json")):
try:
data=json.loads(path.read_text())
except Exception:
continue
for item in (data if isinstance(data, dict) else {} if not isinstance(data,list) else data):
if isinstance(item, dict) and item.get("path") == "components/ui/webgl-error-boundary.tsx":
print(path.name, item.get("type"), item.get("path", item.get("name")), repr(item.get("content","")[:80]))
PYRepository: harshjdhv/componentry
Length of output: 1032
🏁 Script executed:
#!/bin/bash
set -euo pipefail
for file in apps/web/public/r/animated-gradient.json \
apps/web/public/r/aurora-flow.json \
apps/web/public/r/ripple-transition.json \
apps/web/public/r/dither-prism-hero.json \
apps/web/public/r/image-ripple-effect.json \
apps/web/public/r/particle-galaxy.json \
apps/web/public/r/silk-aurora.json \
apps/web/public/r/webgl-liquid.json; do
printf '\n=== %s ===\n' "$file"
python3 - <<'PY' "$file"
import json, sys
from pathlib import Path
name = Path(sys.argv[1]).stem
data = json.loads(Path(sys.argv[1]).read_text())
items = data if isinstance(data, list) else [data] if isinstance(data, dict) else []
for item in items:
if isinstance(item, dict) and item.get("path") == "components/ui/webgl-error-boundary.tsx":
content = (item.get("content") or "")[:400]
exports = []
for token in ["WebGLErrorBoundary", "WebGLFallback"]:
exports.append(f"{token}={1 if token in content else 0}")
first_lines = (content or "").replace("\\n", "\n").splitlines()[:6]
print(f"manifest={name}")
print(f"type={item.get('type')}")
print(f"paths={','.join(i.get('path') for i in items if isinstance(i, dict))}")
print("first_fragment=" + "\n".join(first_lines[:4]))
print("exports=" + ",".join(exports))
break
else:
print("no webgl-error-boundary entry")
PY
doneRepository: harshjdhv/componentry
Length of output: 782
Move webgl-error-boundary.tsx to its own registry item.
Multiple registry manifests, including animated-gradient, aurora-flow, ripple-transition, silk-aurora, and watermelon, write to the same components/ui/webgl-error-boundary.tsx path. Use a separate reusable registry:ui item so installing these components together does not overwrite one implementation with another.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/web/public/r/aurora-flow.json` around lines 19 - 23, Extract
components/ui/webgl-error-boundary.tsx from the aurora-flow registry item and
define it as its own reusable registry:ui item. Update animated-gradient,
aurora-flow, ripple-transition, silk-aurora, and watermelon manifests to
reference that shared item instead of embedding the same path, preserving the
existing WebGLErrorBoundary and WebGLFallback implementation.
| const gl = canvas.getContext("webgl", { | ||
| antialias: false, | ||
| alpha: true, | ||
| premultipliedAlpha: true, | ||
| powerPreference: "high-performance", | ||
| }); |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
premultipliedAlpha: true does not match the straight-alpha shader output.
The context requests premultiplied alpha. The fragment shader at Line 168 writes vec4(clamp(color, 0.0, 1.0), clamp(u_opacity, 0.0, 1.0)), which is straight alpha. When opacity is below 1, the browser composites RGB that was never multiplied by alpha, so the canvas renders brighter than requested. The playground exposes an Opacity slider from 0 to 1, so this path is reachable.
Choose one fix: set premultipliedAlpha: false, or multiply the color by the alpha in the shader.
🐛 Proposed fix (multiply in the shader)
- gl_FragColor = vec4(clamp(color, 0.0, 1.0), clamp(u_opacity, 0.0, 1.0));
+ float alpha = clamp(u_opacity, 0.0, 1.0);
+ gl_FragColor = vec4(clamp(color, 0.0, 1.0) * alpha, alpha);🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/ui/src/components/aurora-flow.tsx` around lines 384 - 389, Align the
WebGL alpha configuration with the fragment shader output in the canvas setup
around getContext: either set premultipliedAlpha to false for the existing
straight-alpha shader, or update the fragment shader’s output to multiply RGB by
the clamped opacity while retaining premultipliedAlpha true. Preserve correct
rendering across the Opacity slider’s 0–1 range.
| const intersectionObserver = new IntersectionObserver(([entry]) => { | ||
| if (entry?.isIntersecting) start(); | ||
| else stop(); | ||
| }); | ||
| intersectionObserver.observe(container); | ||
| const handleVisibility = () => { | ||
| if (document.hidden) stop(); | ||
| else start(); | ||
| }; | ||
| document.addEventListener("visibilitychange", handleVisibility); |
There was a problem hiding this comment.
🚀 Performance & Scalability | 🟡 Minor | ⚡ Quick win
handleVisibility restarts the loop for an off-screen canvas.
start() reads document.hidden but not the intersection state. If the element is off-screen when the tab becomes visible, handleVisibility calls start() and the rAF loop runs for a canvas that nobody sees. The loop stops only when the IntersectionObserver fires again. Track the intersection state and gate start() on it.
🐛 Proposed fix
+ let visible = false;
const start = () => {
- if (running || reducedMotion || document.hidden) return;
+ if (running || reducedMotion || document.hidden || !visible) return;
running = true;
lastNow = performance.now();
rafId = window.requestAnimationFrame(draw);
};
@@
const intersectionObserver = new IntersectionObserver(([entry]) => {
- if (entry?.isIntersecting) start();
- else stop();
+ visible = Boolean(entry?.isIntersecting);
+ if (visible) start();
+ else stop();
});📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const intersectionObserver = new IntersectionObserver(([entry]) => { | |
| if (entry?.isIntersecting) start(); | |
| else stop(); | |
| }); | |
| intersectionObserver.observe(container); | |
| const handleVisibility = () => { | |
| if (document.hidden) stop(); | |
| else start(); | |
| }; | |
| document.addEventListener("visibilitychange", handleVisibility); | |
| let visible = false; | |
| const intersectionObserver = new IntersectionObserver(([entry]) => { | |
| visible = Boolean(entry?.isIntersecting); | |
| if (visible) start(); | |
| else stop(); | |
| }); | |
| intersectionObserver.observe(container); | |
| const handleVisibility = () => { | |
| if (document.hidden) stop(); | |
| else start(); | |
| }; | |
| document.addEventListener("visibilitychange", handleVisibility); |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/ui/src/components/aurora-flow.tsx` around lines 614 - 623, Track the
current intersection state in the IntersectionObserver flow around
handleVisibility, updating it from each observer entry. Gate the
visibility-triggered start() call on both document visibility and the stored
intersecting state, while preserving stop() behavior when either condition is
false.
Adds the Silk Aurora procedural hero background, interactive
playground, live catalog preview, presets, registry integration,
reduced-motion support, and documentation.
Summary by CodeRabbit
New Features
Bug Fixes