Skip to content

feat(ui): add silk aurora hero background - #18

Merged
harshjdhv merged 3 commits into
harshjdhv:mainfrom
sorkhademanthan:feat/silk-aurora
Aug 2, 2026
Merged

feat(ui): add silk aurora hero background#18
harshjdhv merged 3 commits into
harshjdhv:mainfrom
sorkhademanthan:feat/silk-aurora

Conversation

@sorkhademanthan

@sorkhademanthan sorkhademanthan commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

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

    • Added the Aurora Flow animated background component with customizable color palettes, motion, lighting, interaction, and atmospheric effects.
    • Added WebGL support with responsive behavior, reduced-motion accessibility, and a CSS fallback when WebGL is unavailable.
    • Added an interactive playground with presets, live customization, reset controls, and generated usage code.
    • Added comprehensive documentation, installation guidance, responsive previews, and property references.
    • Registered Aurora Flow in the component catalog and UI registry.
  • Bug Fixes

    • Improved development startup by avoiding errors when optional analytics configuration is unavailable.

@vercel

vercel Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

@sorkhademanthan is attempting to deploy a commit to the Componentry Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Aug 2, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Changes

Aurora Flow component

Layer / File(s) Summary
Aurora Flow shader and public API
packages/ui/src/components/aurora-flow.tsx
Adds the WebGL shader, presets, props, validation helpers, and rendering controls.
WebGL rendering lifecycle
packages/ui/src/components/aurora-flow.tsx, apps/web/public/r/aurora-flow.json
Adds WebGL initialization, animation, interaction, reduced-motion handling, visibility pausing, cleanup, and CSS fallback rendering.
Interactive playground configuration
apps/web/components/docs/previews/aurora-flow-playground.tsx
Adds Zustand-backed settings, preset and color controls, live preview rendering, reset support, and generated usage code.
Documentation and registry integration
apps/web/components/docs/aurora-flow.tsx, apps/web/components/docs/lazy-registry.ts, apps/web/components/docs/previews/aurora-flow-card-preview.tsx, apps/web/app/docs/page.tsx, apps/web/registry/index.ts, apps/web/public/r/registry.json, apps/web/public/r/aurora-flow.json
Adds the documentation page, lazy loading, card preview, registry metadata, and public registry manifest. The PostHog initialization no longer throws when development variables are absent.

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
Loading

Possibly related PRs

  • harshjdhv/componentry#17: Adds another UI component with parallel documentation, playground, registry, lazy-loading, and catalog integrations.

Suggested reviewers: harshjdhv

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 6.25% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the main change: adding the Silk Aurora hero background UI feature.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (8)
packages/ui/src/components/silk-aurora.tsx (6)

230-234: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider accepting 3-digit hex shorthand.

HEX_COLOR_REGEX accepts only 6-digit hex. A consumer that passes #fff gets 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 value

Delete 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 win

Cache the container rect instead of measuring on every pointer move.

handlePointerMove calls container.getBoundingClientRect() for each pointermove event. 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. resize already runs on every ResizeObserver callback, 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, because getBoundingClientRect returns 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 win

Extract the shared container to remove the duplicated wrapper markup.

fallback and the main branch repeat the same container: identical cn class list, identical style, identical {...props} spread, identical gradient layer, and identical children wrapper. The two copies already differ in ref and 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. If props contains an id, 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 win

Surface the shader info log on compile failure.

compileShader discards gl.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.tsx already 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 value

Move the settingsRef assignment out of the render body.

settingsRef.current = settings mutates a ref during render, which React documents as unsafe. Keep the ref assignment in a useEffect keyed by settings so 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 win

Add aria-pressed to the preset buttons.

The preset buttons signal selection only through a border color. Screen reader users receive no selection state. Add aria-pressed so 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 value

Consider omitting colors when it matches the preset palette.

generateCode always emits both preset and colors. SilkAurora resolves each channel from colors?.[index] first, so the emitted colors array always overrides the preset. A user who only selects a preset then copies a snippet where the preset value has no effect. Emit colors only when the array differs from SILK_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

📥 Commits

Reviewing files that changed from the base of the PR and between 636e1e6 and d78ab9f.

📒 Files selected for processing (7)
  • apps/web/app/docs/page.tsx
  • apps/web/components/docs/previews/silk-aurora-card-preview.tsx
  • apps/web/components/docs/previews/silk-aurora-playground.tsx
  • apps/web/components/docs/silk-aurora.tsx
  • apps/web/public/r/silk-aurora.json
  • apps/web/registry/index.ts
  • packages/ui/src/components/silk-aurora.tsx

Comment thread packages/ui/src/components/silk-aurora.tsx Outdated
Comment thread packages/ui/src/components/silk-aurora.tsx Outdated
Comment on lines +599 to +623
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);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀 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.

Suggested change
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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 win

Restore a live preview branch for silk-aurora.

apps/web/app/docs/page.tsx only special-cases text-morph and aurora-flow, so the silk-aurora catalog card no longer uses a live preview component and falls back to the poster/video path. Add back a SilkAuroraCardPreview branch 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 win

Consider 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 value

Do 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 win

Guard the highp precision qualifier for WebGL1 portability.

WebGL1 does not guarantee highp support 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 win

Add aria-pressed to 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

📥 Commits

Reviewing files that changed from the base of the PR and between d78ab9f and 38ef240.

📒 Files selected for processing (10)
  • apps/web/app/docs/page.tsx
  • apps/web/components/docs/aurora-flow.tsx
  • apps/web/components/docs/lazy-registry.ts
  • apps/web/components/docs/previews/aurora-flow-card-preview.tsx
  • apps/web/components/docs/previews/aurora-flow-playground.tsx
  • apps/web/instrumentation-client.ts
  • apps/web/public/r/aurora-flow.json
  • apps/web/public/r/registry.json
  • apps/web/registry/index.ts
  • packages/ui/src/components/aurora-flow.tsx
💤 Files with no reviewable changes (1)
  • apps/web/instrumentation-client.ts

Comment on lines +19 to +23
{
"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"
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ 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/*.json

Repository: 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]))
PY

Repository: 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
done

Repository: 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.

Comment on lines +384 to +389
const gl = canvas.getContext("webgl", {
antialias: false,
alpha: true,
premultipliedAlpha: true,
powerPreference: "high-performance",
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 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.

Comment on lines +614 to +623
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);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀 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.

Suggested change
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.

@harshjdhv
harshjdhv merged commit 7e753e7 into harshjdhv:main Aug 2, 2026
2 of 3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants