Skip to content

feat: player toolbar, across integration, content library#153

Merged
WINOFFRG merged 7 commits into
mainfrom
feat/toolbar-streams
Jun 6, 2026
Merged

feat: player toolbar, across integration, content library#153
WINOFFRG merged 7 commits into
mainfrom
feat/toolbar-streams

Conversation

@WINOFFRG

@WINOFFRG WINOFFRG commented Jun 6, 2026

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

  • New Features

    • Stream panel: playlists, live streams, presets, and a custom stream input with config
    • Saved streams and a catalog of Blender Open Films + Apple Music charts
    • Settings tab in block toolbar and an improved multi-overlay stream picker
  • Improvements

    • Redesigned preview/toolbar with animated expansion, URL-driven expanded state, and resizable desktop layout
    • Cleaner preset names, better audio detection, and refined playlist UX
  • Documentation

    • Updated video-player usage examples to single-source loading

Copilot AI review requested due to automatic review settings June 6, 2026 17:33
@coderabbitai

coderabbitai Bot commented Jun 6, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@rohangupta-zomato, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 13 minutes and 17 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: f8646609-bd5b-4e46-9a1a-8bdae0311110

📥 Commits

Reviewing files that changed from the base of the PR and between 5cf35ff and 7d6f15a.

⛔ Files ignored due to path filters (2)
  • apps/www/package.json is excluded by none and included by none
  • bun.lock is excluded by !**/*.lock and included by none
📒 Files selected for processing (39)
  • apps/www/app/blocks/[[...slug]]/page.tsx
  • apps/www/components/blocks/block-page-shell.tsx
  • apps/www/components/blocks/block-showcase.tsx
  • apps/www/components/blocks/block-toolbar.tsx
  • apps/www/components/blocks/preview-pane.tsx
  • apps/www/components/cloud-search.tsx
  • apps/www/components/markdown.tsx
  • apps/www/components/players/audio-player/demo-player.tsx
  • apps/www/components/players/audio-player/hover-player.tsx
  • apps/www/components/players/video-player/demo-assets.ts
  • apps/www/components/players/video-player/player-container.tsx
  • apps/www/components/stream-panel/content-catalog.ts
  • apps/www/components/stream-panel/content-overview-overlay.tsx
  • apps/www/components/stream-panel/custom-overlay.tsx
  • apps/www/components/stream-panel/index.ts
  • apps/www/components/stream-panel/overlay-shell.tsx
  • apps/www/components/stream-panel/panel-popover.config.ts
  • apps/www/components/stream-panel/panel-popover.tsx
  • apps/www/components/stream-panel/panel-slider.tsx
  • apps/www/components/stream-panel/playlists-overlay.tsx
  • apps/www/components/stream-panel/presets-overlay.tsx
  • apps/www/components/stream-panel/provider.tsx
  • apps/www/components/stream-panel/saved-overlay.tsx
  • apps/www/components/stream-panel/use-stream-panel-sync.ts
  • apps/www/components/stream-panel/use-stream-panel.ts
  • apps/www/content/docs/blocks/video-player.mdx
  • apps/www/lib/docs-dial-store.ts
  • apps/www/lib/stream-presets.ts
  • apps/www/registry/collection/registry-blocks.ts
  • apps/www/registry/collection/registry-ui.ts
  • apps/www/registry/default/blocks/audio-player/components/fixed-timeline-control.tsx
  • apps/www/registry/default/blocks/video-player/components/media-player.tsx
  • apps/www/registry/default/blocks/video-player/components/playlist.tsx
  • apps/www/registry/default/blocks/video-player/page.tsx
  • apps/www/registry/default/examples/player-root-demo.tsx
  • apps/www/registry/default/hooks/use-asset.ts
  • apps/www/registry/default/hooks/use-playback.ts
  • apps/www/registry/default/hooks/use-player.ts
  • apps/www/registry/default/hooks/use-volume.ts
📝 Walkthrough

Walkthrough

Introduces a persisted stream-panel store and placement-driven overlay stack; adds Apple Music and Blender catalog adapters; rewrites StreamPanel into a multi-overlay controller-based component; syncs preview expansion to URL; and integrates the new panel across block toolbars, previews, and audio/video players.

Changes

Unified stream-panel refactor

Layer / File(s) Summary
Persisted store and context controller
apps/www/components/stream-panel/use-stream-panel.ts, apps/www/components/stream-panel/provider.tsx
Typed persisted Zustand store for per-player selections, autoplay/muted/volume, saved streams; provider exposes controller registration and setOpen.
Catalog utilities and configuration contracts
apps/www/components/stream-panel/content-catalog.ts, apps/www/components/stream-panel/panel-popover.config.ts
Adds Apple Music & Blender OpenFilm types, zod validators, fetchers, caption injection, playlist preset normalization, and panel config types/constants.
Overlay shell and placement-driven rendering
apps/www/components/stream-panel/overlay-shell.tsx
Refactors OverlayShell to placement states (active/covered/idle) with LazyMotion animations and optional descriptions.
Content selection overlays
apps/www/components/stream-panel/content-overview-overlay.tsx, apps/www/components/stream-panel/playlists-overlay.tsx, apps/www/components/stream-panel/presets-overlay.tsx, apps/www/components/stream-panel/saved-overlay.tsx, apps/www/components/stream-panel/custom-overlay.tsx
Implements overview/presets/playlists/saved/custom overlays, wired to stream-panel store and placement props.
StreamPanel composition and public exports
apps/www/components/stream-panel/panel-popover.tsx, apps/www/components/stream-panel/index.ts
Rewrites StreamPanel into a multi-overlay stack with shared UI primitives and variant rendering; removes PanelSlider export.
Sync logic and content restoration
apps/www/components/stream-panel/use-stream-panel-sync.ts
Refactors sync hook to accept playerType, restores persisted selections post-hydration, supports playlist/custom/Blender loads with abort and caching, and exposes new playlist/preset handlers.
Block toolbar and preview pane UI updates
apps/www/components/blocks/block-toolbar.tsx, apps/www/components/blocks/preview-pane.tsx
BlockToolbar registers stream controllers and measures bounded content height with ResizeObserver; preview pane derives expanded from URL and animates with LazyMotion.
Block page shell and responsive layout
apps/www/components/blocks/block-page-shell.tsx, apps/www/app/blocks/[[...slug]]/page.tsx
Extracts responsive layout into BlockPageShell; page delegates info, preview, and sets initial DOM dataset from expanded query.
Player showcase and component integration
apps/www/components/blocks/block-showcase.tsx, apps/www/components/players/audio-player/demo-player.tsx, apps/www/components/players/audio-player/hover-player.tsx, apps/www/components/players/video-player/player-container.tsx, apps/www/registry/default/examples/player-root-demo.tsx
Embeds BlockStreamSync / StreamPanelProvider into demos and players, replaces playlist props with mediaProps single-source usage, and wires new handlers.
Playback error handling & audio detection
apps/www/registry/default/hooks/use-asset.ts, apps/www/registry/default/hooks/use-playback.ts, apps/www/registry/default/hooks/use-player.ts, apps/www/registry/default/blocks/video-player/components/media-player.tsx, apps/www/registry/default/hooks/use-volume.ts
Centralizes playback.setError, routes asset load failures to playback error state, adds PlayerErrorScreen retry UI, and broadens has-audio detection using media metadata and lifecycle events.
Docs and presets
apps/www/content/docs/blocks/video-player.mdx, apps/www/lib/stream-presets.ts, apps/www/registry/default/blocks/video-player/components/playlist.tsx, apps/www/registry/default/blocks/video-player/page.tsx
Updates docs for mediaProps API, renames preset display labels to remove format qualifiers, hides single-item playlists, and simplifies example usage.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

🐰 A panel that persists through time,
Overlays stacked in tidy rhyme,
Presets, streams, and captions sing,
Controllers bounce — the callbacks spring,
Zustand hums, the preview glows — hooray for dev-time!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% 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 PR title accurately summarizes the major changes: a new player toolbar, stream panel integration across components, and a content library system for managing playlists and presets.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/toolbar-streams

Copilot AI 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.

Pull request overview

This PR upgrades the docs-site player experience by introducing a richer Stream Panel (presets + playlists + saved/custom streams), integrating it into block previews and demo players, and updating the video-player block to support single-source loading in addition to playlist-backed loading.

Changes:

  • Replaces the old docs dial store and basic Stream Panel UI with a persisted Stream Panel store + overlay stack (content overview, playlists, presets, saved/custom).
  • Integrates Stream Panel controls into the block preview toolbar and home/demo players, including playlist preset loading and selection persistence.
  • Updates the video-player block/docs to demonstrate mediaProps={{ src }} single-source loading and hides the playlist menu when fewer than 2 items are queued.

Reviewed changes

Copilot reviewed 30 out of 31 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
bun.lock Locks the newly added icon dependency.
apps/www/package.json Adds @central-icons-react/... used by the new toolbar/overlays.
apps/www/registry/default/examples/player-root-demo.tsx Wires Stream Panel sync for preset + playlist changes and adjusts panel placement.
apps/www/registry/default/blocks/video-player/page.tsx Switches the block page from playlist demo to single-source mediaProps.src.
apps/www/registry/default/blocks/video-player/components/playlist.tsx Hides playlist UI when the queue has fewer than 2 items.
apps/www/lib/stream-presets.ts Tweaks preset display names used in the Stream Panel UI.
apps/www/lib/docs-dial-store.ts Removes the old persisted docs dial Zustand store.
apps/www/content/docs/blocks/video-player.mdx Updates block docs for single-source loading + clarifies behavior.
apps/www/components/stream-panel/use-stream-panel.ts Adds the new persisted Stream Panel Zustand store (saved streams, selections, prefs).
apps/www/components/stream-panel/use-stream-panel-sync.ts Adds playlist preset loading, selection restore, and Blender stream integration.
apps/www/components/stream-panel/saved-overlay.tsx Updates Saved overlay UI and adds “Add Custom” affordance.
apps/www/components/stream-panel/provider.tsx Expands provider to support a registered controller and shared open state.
apps/www/components/stream-panel/presets-overlay.tsx Updates preset overlay UI and selection handling for overlay stack.
apps/www/components/stream-panel/playlists-overlay.tsx New overlay for selecting playlist presets.
apps/www/components/stream-panel/panel-slider.tsx Removes the unused slider component.
apps/www/components/stream-panel/panel-popover.tsx Major Stream Panel UI refactor: overlay stack + new actions + new icons.
apps/www/components/stream-panel/panel-popover.config.ts Centralizes Stream Panel overlay/position constants and prop types.
apps/www/components/stream-panel/overlay-shell.tsx Refactors overlay shell for stacked overlays + animation/placement control.
apps/www/components/stream-panel/index.ts Updates exports after removing slider.
apps/www/components/stream-panel/custom-overlay.tsx Updates Custom overlay UI and switches to the new Stream Panel store.
apps/www/components/stream-panel/content-overview-overlay.tsx New overlay: overview menu for Streams / Playlists / Live.
apps/www/components/stream-panel/content-catalog.ts New content catalog + Blender playlist/stream fetching helpers.
apps/www/components/players/video-player/player-container.tsx Integrates Stream Panel into the home video player with a selector trigger.
apps/www/components/players/video-player/demo-assets.ts Removes hardcoded demo assets in favor of preset-driven selection.
apps/www/components/players/audio-player/hover-player.tsx Wraps audio demo in StreamPanelProvider and syncs selections.
apps/www/components/players/audio-player/demo-player.tsx Adds child slot + uses persisted selection to decide auto-load behavior.
apps/www/components/blocks/preview-pane.tsx Persists “expanded” state in the URL and improves layout/animation handling.
apps/www/components/blocks/block-toolbar.tsx Reworks toolbar to host Stream Panel content and register per-player controllers.
apps/www/components/blocks/block-showcase.tsx Uses BlockStreamSync to provide stream selection within showcase players.
apps/www/app/layout.tsx Changes head handling to use next/head (needs follow-up fix).
apps/www/app/blocks/[[...slug]]/page.tsx Syncs expanded state early via inline script and adjusts panel transitions.

Comment thread apps/www/app/layout.tsx Outdated
Comment on lines +9 to +10
import { Inter } from "next/font/google"
import Head from "next/head"
Comment thread apps/www/app/layout.tsx Outdated
Comment on lines +76 to +78
<Head>
<UserJotProvider projectId="cmjs634l4043b15ldylgedgwi" />
</head>
</Head>

@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: 4

🧹 Nitpick comments (6)
apps/www/components/stream-panel/content-catalog.ts (2)

98-100: ⚡ Quick win

Consider adding fetch timeout for resilience.

The fetch call honors the AbortSignal for cancellation, but lacks an explicit timeout. If the Blender API becomes unresponsive, the request could hang until the caller aborts. Consider wrapping with AbortSignal.timeout() or setting a reasonable default timeout.

⏱️ Example timeout pattern
export async function fetchBlenderStream(
  assetId: string,
  signal?: AbortSignal
): Promise<BlenderStreamResponse> {
  const timeoutSignal = AbortSignal.timeout(10000) // 10s timeout
  const combinedSignal = signal 
    ? AbortSignal.any([signal, timeoutSignal])
    : timeoutSignal

  const response = await fetch(`${BLENDER_API_BASE_URL}/stream/${assetId}`, {
    signal: combinedSignal,
  })
  // ...
}
🤖 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/www/components/stream-panel/content-catalog.ts` around lines 98 - 100,
The fetch to `${BLENDER_API_BASE_URL}/stream/${assetId}` in content-catalog.ts
currently uses the provided signal but has no timeout; update the enclosing
function (the fetch call that takes assetId and signal) to create a timeout
signal (e.g., AbortSignal.timeout(10000) or configurable), combine it with the
incoming signal via AbortSignal.any([...]) when present, and pass that
combinedSignal to fetch so the request will abort after the timeout if the
Blender API is unresponsive.

106-106: ⚖️ Poor tradeoff

Validate API response shape to prevent runtime type errors.

The type assertion as BlenderStreamResponse assumes the API returns the expected shape. If the API changes or returns malformed data, this could cause downstream runtime errors. Consider adding runtime validation (e.g., Zod schema) or defensive checks.

🤖 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/www/components/stream-panel/content-catalog.ts` at line 106, The code
currently asserts the fetch result with "return (await response.json()) as
BlenderStreamResponse", which can cause runtime errors if the API returns an
unexpected shape; replace the assertion with runtime validation: define a Zod
schema (e.g., BlenderStreamResponseSchema) matching BlenderStreamResponse, call
schema.parse or schema.safeParse on await response.json(), and handle validation
failures (log/error/throw or return a safe fallback) so downstream code only
receives a validated BlenderStreamResponse object; update the function that
performs the fetch (the place using response and returning
BlenderStreamResponse) to use this validation flow.
apps/www/lib/stream-presets.ts (1)

203-340: ⚖️ Poor tradeoff

Consider restoring format qualifiers for duplicate preset names.

Multiple presets now share identical display names:

  • "Angel One" appears 3 times (lines 108, 203, 221)
  • "Sintel 4K" appears 2 times (lines 118, 261)
  • "Art of Motion" appears 2 times (lines 128, 271)
  • "Big Buck Bunny" appears 3 times (lines 56, 307, 340)

While each preset has a unique id, users selecting from a dropdown or list that displays only the name field will see duplicates and may struggle to differentiate between variants. Consider re-adding distinguishing qualifiers (e.g., "Angel One (Widevine)", "Angel One (ClearKey)") or ensuring the UI always displays description or features alongside the name.

🤖 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/www/lib/stream-presets.ts` around lines 203 - 340, Several presets in
the presets array have duplicate name values which will be ambiguous in UI
lists; update the name fields to include distinguishing qualifiers (e.g., append
" (Widevine)", " (ClearKey)", " (HLS)" or resolution/format) for the relevant
entries such as the Angel One presets (ids like shaka-angel-clearkey and
shaka-angel-hls-widevine), the Sintel 4K entry (id shaka-sintel-widevine), and
other duplicates (e.g., bitmovin-hls-aes128, mp4-bunny-progressive,
dash-if-thumbnails) so the display name uniquely identifies the variant while
keeping the existing unique id values intact.
apps/www/components/blocks/preview-pane.tsx (1)

110-112: 💤 Low value

Potential hydration mismatch when expanded=true is in URL.

The component initializes expanded to false (Line 36), then useLayoutEffect reads from URL and may set it to true. Since useLayoutEffect runs after hydration, this could cause a visual flash where the pane renders collapsed then immediately expands.

Consider initializing from URL during SSR-safe initial render or using useSyncExternalStore pattern for URL state to avoid the flash:

💡 Suggested approach
// Initialize lazily from URL (client-only)
const [expanded, setExpanded] = useState(() => {
  if (typeof window === "undefined") return false
  return getExpandedFromUrl()
})

This won't help SSR but prevents the double-render flash on client navigation.

🤖 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/www/components/blocks/preview-pane.tsx` around lines 110 - 112, The
component currently initializes the expanded state to false and then updates it
from the URL inside useLayoutEffect, causing a hydration/flash when
expanded=true in the URL; change the initialization of the expanded state to
derive its initial value from getExpandedFromUrl in a lazy useState initializer
(checking typeof window to avoid SSR errors) so the initial render matches the
URL, and retain the existing useLayoutEffect or remove redundant updates; update
references to expanded, setExpanded, useLayoutEffect, and getExpandedFromUrl
accordingly.
apps/www/components/stream-panel/panel-popover.tsx (1)

59-59: ⚡ Quick win

Use granular selectors for store state access.

Similar to custom-overlay.tsx, this file accesses the entire store. Per coding guidelines, use granular selectors for each field and setter needed:

const muted = useStreamPanelStore((s) => s.muted)
const autoplay = useStreamPanelStore((s) => s.autoplay)
const savedStreams = useStreamPanelStore((s) => s.savedStreams)
const contentSelections = useStreamPanelStore((s) => s.contentSelections)
const setMuted = useStreamPanelStore((s) => s.setMuted)
const setAutoplay = useStreamPanelStore((s) => s.setAutoplay)
const setContentSelection = useStreamPanelStore((s) => s.setContentSelection)
🤖 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/www/components/stream-panel/panel-popover.tsx` at line 59, The file
currently pulls the entire store via the store = useStreamPanelStore() call;
replace that with granular selectors to avoid unnecessary re-renders by
selecting only the fields and setters used (e.g., replace store usage with const
muted = useStreamPanelStore(s => s.muted), const autoplay =
useStreamPanelStore(s => s.autoplay), const savedStreams = useStreamPanelStore(s
=> s.savedStreams), const contentSelections = useStreamPanelStore(s =>
s.contentSelections), and the setters const setMuted = useStreamPanelStore(s =>
s.setMuted), const setAutoplay = useStreamPanelStore(s => s.setAutoplay), const
setContentSelection = useStreamPanelStore(s => s.setContentSelection)); update
any references that previously used store.* to use these new variables.

Source: Coding guidelines

apps/www/components/stream-panel/custom-overlay.tsx (1)

27-27: ⚡ Quick win

Use granular selectors for store state access.

Per coding guidelines, store access should use granular per-feature selectors like useStreamPanelStore(s => s.field) rather than accessing the entire store. Consider selecting only the fields and setters needed:

const customSrc = useStreamPanelStore((s) => s.customSrc)
const customConfig = useStreamPanelStore((s) => s.customConfig)
const setCustomSrc = useStreamPanelStore((s) => s.setCustomSrc)
const setCustomConfig = useStreamPanelStore((s) => s.setCustomConfig)
const saveStream = useStreamPanelStore((s) => s.saveStream)

This prevents unnecessary re-renders when unrelated store fields change.

🤖 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/www/components/stream-panel/custom-overlay.tsx` at line 27, Replace the
single broad store access via useStreamPanelStore() with granular selectors that
pull only the needed fields and setters to avoid extra re-renders: select
customSrc, customConfig, setCustomSrc, setCustomConfig and saveStream
individually using useStreamPanelStore(s => s.<field>) so components only
subscribe to the pieces they use (refer to useStreamPanelStore, customSrc,
customConfig, setCustomSrc, setCustomConfig, saveStream).

Source: Coding guidelines

🤖 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/www/app/layout.tsx`:
- Line 10: Remove the incompatible next/head import from the App Router layout:
delete the import statement for "next/head" and any usage of the Head component
in app/layout.tsx (referenced by the import and any <Head> JSX). Instead rely on
the existing metadata export (and canonical App Router metadata APIs) to set
page title/meta; ensure no remaining references to the Head symbol remain in the
file.
- Around line 76-78: The layout currently uses the next/head <Head> component
which does not work in the App Router and causes UserJotProvider to silently
fail; fix by either reverting the <Head> usage to a raw <head> element in
layout.tsx so UserJotProvider(projectId="cmjs634l4043b15ldylgedgwi") is rendered
in the correct head, or move UserJotProvider out of <Head> into the top-level
<body> (or a client-side provider root) if the library supports body placement;
if unsure, consult UserJot's App Router integration docs for the recommended
placement and adjust the UserJotProvider location accordingly.

In `@apps/www/components/stream-panel/content-catalog.ts`:
- Around line 80-91: Replace the Promise.all that awaits adding captions so a
single failing caption doesn't abort playback: iterate stream.captions and for
each call player.addTextTrackAsync (referencing stream.captions and
player.addTextTrackAsync) and handle failures per-item (use Promise.allSettled
or attach .catch to each promise), log or warn on individual caption errors, and
continue without throwing so overall caption-loading is best-effort and does not
block main playback.

In `@apps/www/components/stream-panel/use-stream-panel.ts`:
- Around line 1-2: This module defines a client-only Zustand store (uses create
and persist and calls crypto.randomUUID()), so add the React "use client"
directive as the very first line of the file to force browser-only evaluation;
place the string "use client" above the existing imports (which reference create
and persist) so crypto.randomUUID() and the Zustand store initialization run
only on the client.

---

Nitpick comments:
In `@apps/www/components/blocks/preview-pane.tsx`:
- Around line 110-112: The component currently initializes the expanded state to
false and then updates it from the URL inside useLayoutEffect, causing a
hydration/flash when expanded=true in the URL; change the initialization of the
expanded state to derive its initial value from getExpandedFromUrl in a lazy
useState initializer (checking typeof window to avoid SSR errors) so the initial
render matches the URL, and retain the existing useLayoutEffect or remove
redundant updates; update references to expanded, setExpanded, useLayoutEffect,
and getExpandedFromUrl accordingly.

In `@apps/www/components/stream-panel/content-catalog.ts`:
- Around line 98-100: The fetch to `${BLENDER_API_BASE_URL}/stream/${assetId}`
in content-catalog.ts currently uses the provided signal but has no timeout;
update the enclosing function (the fetch call that takes assetId and signal) to
create a timeout signal (e.g., AbortSignal.timeout(10000) or configurable),
combine it with the incoming signal via AbortSignal.any([...]) when present, and
pass that combinedSignal to fetch so the request will abort after the timeout if
the Blender API is unresponsive.
- Line 106: The code currently asserts the fetch result with "return (await
response.json()) as BlenderStreamResponse", which can cause runtime errors if
the API returns an unexpected shape; replace the assertion with runtime
validation: define a Zod schema (e.g., BlenderStreamResponseSchema) matching
BlenderStreamResponse, call schema.parse or schema.safeParse on await
response.json(), and handle validation failures (log/error/throw or return a
safe fallback) so downstream code only receives a validated
BlenderStreamResponse object; update the function that performs the fetch (the
place using response and returning BlenderStreamResponse) to use this validation
flow.

In `@apps/www/components/stream-panel/custom-overlay.tsx`:
- Line 27: Replace the single broad store access via useStreamPanelStore() with
granular selectors that pull only the needed fields and setters to avoid extra
re-renders: select customSrc, customConfig, setCustomSrc, setCustomConfig and
saveStream individually using useStreamPanelStore(s => s.<field>) so components
only subscribe to the pieces they use (refer to useStreamPanelStore, customSrc,
customConfig, setCustomSrc, setCustomConfig, saveStream).

In `@apps/www/components/stream-panel/panel-popover.tsx`:
- Line 59: The file currently pulls the entire store via the store =
useStreamPanelStore() call; replace that with granular selectors to avoid
unnecessary re-renders by selecting only the fields and setters used (e.g.,
replace store usage with const muted = useStreamPanelStore(s => s.muted), const
autoplay = useStreamPanelStore(s => s.autoplay), const savedStreams =
useStreamPanelStore(s => s.savedStreams), const contentSelections =
useStreamPanelStore(s => s.contentSelections), and the setters const setMuted =
useStreamPanelStore(s => s.setMuted), const setAutoplay = useStreamPanelStore(s
=> s.setAutoplay), const setContentSelection = useStreamPanelStore(s =>
s.setContentSelection)); update any references that previously used store.* to
use these new variables.

In `@apps/www/lib/stream-presets.ts`:
- Around line 203-340: Several presets in the presets array have duplicate name
values which will be ambiguous in UI lists; update the name fields to include
distinguishing qualifiers (e.g., append " (Widevine)", " (ClearKey)", " (HLS)"
or resolution/format) for the relevant entries such as the Angel One presets
(ids like shaka-angel-clearkey and shaka-angel-hls-widevine), the Sintel 4K
entry (id shaka-sintel-widevine), and other duplicates (e.g.,
bitmovin-hls-aes128, mp4-bunny-progressive, dash-if-thumbnails) so the display
name uniquely identifies the variant while keeping the existing unique id values
intact.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: f422db75-a47a-4a43-a0e1-035a87fc4ec3

📥 Commits

Reviewing files that changed from the base of the PR and between ef270d5 and fee1f07.

⛔ Files ignored due to path filters (2)
  • apps/www/package.json is excluded by none and included by none
  • bun.lock is excluded by !**/*.lock and included by none
📒 Files selected for processing (29)
  • apps/www/app/blocks/[[...slug]]/page.tsx
  • apps/www/app/layout.tsx
  • apps/www/components/blocks/block-showcase.tsx
  • apps/www/components/blocks/block-toolbar.tsx
  • apps/www/components/blocks/preview-pane.tsx
  • apps/www/components/players/audio-player/demo-player.tsx
  • apps/www/components/players/audio-player/hover-player.tsx
  • apps/www/components/players/video-player/demo-assets.ts
  • apps/www/components/players/video-player/player-container.tsx
  • apps/www/components/stream-panel/content-catalog.ts
  • apps/www/components/stream-panel/content-overview-overlay.tsx
  • apps/www/components/stream-panel/custom-overlay.tsx
  • apps/www/components/stream-panel/index.ts
  • apps/www/components/stream-panel/overlay-shell.tsx
  • apps/www/components/stream-panel/panel-popover.config.ts
  • apps/www/components/stream-panel/panel-popover.tsx
  • apps/www/components/stream-panel/panel-slider.tsx
  • apps/www/components/stream-panel/playlists-overlay.tsx
  • apps/www/components/stream-panel/presets-overlay.tsx
  • apps/www/components/stream-panel/provider.tsx
  • apps/www/components/stream-panel/saved-overlay.tsx
  • apps/www/components/stream-panel/use-stream-panel-sync.ts
  • apps/www/components/stream-panel/use-stream-panel.ts
  • apps/www/content/docs/blocks/video-player.mdx
  • apps/www/lib/docs-dial-store.ts
  • apps/www/lib/stream-presets.ts
  • apps/www/registry/default/blocks/video-player/components/playlist.tsx
  • apps/www/registry/default/blocks/video-player/page.tsx
  • apps/www/registry/default/examples/player-root-demo.tsx
💤 Files with no reviewable changes (4)
  • apps/www/components/stream-panel/index.ts
  • apps/www/components/players/video-player/demo-assets.ts
  • apps/www/lib/docs-dial-store.ts
  • apps/www/components/stream-panel/panel-slider.tsx

Comment thread apps/www/app/layout.tsx Outdated
Comment thread apps/www/app/layout.tsx Outdated
Comment thread apps/www/components/stream-panel/content-catalog.ts Outdated
Comment thread apps/www/components/stream-panel/use-stream-panel.ts

@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 (2)
apps/www/components/stream-panel/content-catalog.ts (2)

252-274: 💤 Low value

Consider adding timeout and validation consistency with Blender fetch.

fetchBlenderStream uses both a timeout wrapper and zod validation, but fetchAppleMusicChartAssets has neither. If the Apple Music API is slow or returns unexpected shapes, this could cause hangs or runtime errors.

🤖 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/www/components/stream-panel/content-catalog.ts` around lines 252 - 274,
The fetchAppleMusicChartAssets function lacks the timeout and response schema
validation used in fetchBlenderStream; wrap the fetch call in the same timeout
helper (the same pattern used by fetchBlenderStream), validate the parsed JSON
against the AppleMusicChartsResponse zod schema (or create one if missing)
before using chart.items, and throw a clear error when validation fails; update
the function that calls getAppleMusicLocaleHeaders / uses
APPLE_MUSIC_API_BASE_URL and keep mapping via toAppleMusicChartAsset only after
successful validation to avoid hangs and runtime shape errors.

321-337: AppleMusicChartAsset.genre is repurposed as the secondary label (fallback)
In toAppleMusicChartAsset (apps/www/components/stream-panel/content-catalog.ts), genre: item.artistName is consistent with the fact that AppleMusicChartItem doesn’t include any genre field. The audio-player UI renders asset.genre as an unlabeled secondary line under the track title (apps/www/registry/default/blocks/audio-player/components/track-info.tsx, playlist.tsx), so this mapping likely shows the artist when “genre” is unavailable.
If this reuse isn’t intended, omit genre when Apple doesn’t provide it or update the UI/asset contract to render artistName separately instead of overloading genre.

🤖 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/www/components/stream-panel/content-catalog.ts` around lines 321 - 337,
The mapping in toAppleMusicChartAsset currently sets AppleMusicChartAsset.genre
= item.artistName which repurposes genre as a fallback label; instead, stop
overloading genre: change to only set genre when a real genre exists (omit or
set undefined/null when AppleMusicChartItem has no genre), and ensure artistName
remains on artistName so the UI can render it explicitly if needed; update the
toAppleMusicChartAsset function to leave genre unset when AppleMusicChartItem
lacks genre information and keep artistName populated.
🤖 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/www/components/blocks/block-page-shell.tsx`:
- Around line 25-27: The mobile branch in block-page-shell.tsx currently returns
only info (the if (!isDesktop) return ...), which omits the preview; update the
non-desktop return to render both preview and info (e.g., stacked or column
layout) so the preview prop is not discarded. Locate the isDesktop check in the
BlockPageShell component and change the returned JSX to include both preview and
info (preserving existing classes like "w-full" and using a mobile-friendly
wrapper) so callers passing preview continue to see it on small screens.

In `@apps/www/components/blocks/preview-pane.tsx`:
- Around line 22-23: The effect that syncs the local expanded state reads
window.location.search but only depends on pathname, so query-only navigation
can desync expanded; change it to use Next's useSearchParams (or otherwise
derive from the router's search params) and include that searchParams value in
the effect dependencies, then compute expanded from searchParams.get('expanded')
(update the effect inside the component where expanded and the useLayoutEffect
are defined) so the effect re-runs on query changes and keeps expanded in sync.

In `@apps/www/components/stream-panel/content-catalog.ts`:
- Around line 252-274: fetchAppleMusicChartAssets is missing abort guards after
awaits; after the await fetch(...) and again after await response.json() check
the provided signal (signal?.aborted) and abort the function (e.g., throw new
DOMException('Aborted', 'AbortError') or return early) to mirror the pattern
used in fetchBlenderStream and the loaders in use-stream-panel-sync.ts, ensuring
you perform these checks before continuing to process response.ok and
chart.items.

---

Nitpick comments:
In `@apps/www/components/stream-panel/content-catalog.ts`:
- Around line 252-274: The fetchAppleMusicChartAssets function lacks the timeout
and response schema validation used in fetchBlenderStream; wrap the fetch call
in the same timeout helper (the same pattern used by fetchBlenderStream),
validate the parsed JSON against the AppleMusicChartsResponse zod schema (or
create one if missing) before using chart.items, and throw a clear error when
validation fails; update the function that calls getAppleMusicLocaleHeaders /
uses APPLE_MUSIC_API_BASE_URL and keep mapping via toAppleMusicChartAsset only
after successful validation to avoid hangs and runtime shape errors.
- Around line 321-337: The mapping in toAppleMusicChartAsset currently sets
AppleMusicChartAsset.genre = item.artistName which repurposes genre as a
fallback label; instead, stop overloading genre: change to only set genre when a
real genre exists (omit or set undefined/null when AppleMusicChartItem has no
genre), and ensure artistName remains on artistName so the UI can render it
explicitly if needed; update the toAppleMusicChartAsset function to leave genre
unset when AppleMusicChartItem lacks genre information and keep artistName
populated.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 794153bf-afed-41d8-953f-57f99472565f

📥 Commits

Reviewing files that changed from the base of the PR and between fee1f07 and 29a5a7f.

📒 Files selected for processing (11)
  • apps/www/app/blocks/[[...slug]]/page.tsx
  • apps/www/components/blocks/block-page-shell.tsx
  • apps/www/components/blocks/preview-pane.tsx
  • apps/www/components/players/audio-player/hover-player.tsx
  • apps/www/components/stream-panel/content-catalog.ts
  • apps/www/components/stream-panel/custom-overlay.tsx
  • apps/www/components/stream-panel/panel-popover.tsx
  • apps/www/components/stream-panel/use-stream-panel-sync.ts
  • apps/www/components/stream-panel/use-stream-panel.ts
  • apps/www/registry/default/blocks/audio-player/components/fixed-timeline-control.tsx
  • apps/www/registry/default/hooks/use-volume.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • apps/www/components/stream-panel/use-stream-panel.ts
  • apps/www/components/stream-panel/panel-popover.tsx
  • apps/www/components/stream-panel/custom-overlay.tsx

Comment thread apps/www/components/blocks/block-page-shell.tsx Outdated
Comment thread apps/www/components/blocks/preview-pane.tsx
Comment thread apps/www/components/stream-panel/content-catalog.ts

@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: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
apps/www/components/stream-panel/content-catalog.ts (2)

241-248: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Add abort guards and Zod validation for Blender playlist fetch.

The Blender playlist branch has multiple issues:

  1. Missing abort guards: No throwIfAborted checks after the fetch (line 241) or response.json() (line 246) await points, violating the coding guideline for async load functions.
  2. Unsafe type assertion: Line 246 uses type assertion instead of Zod validation, inconsistent with all other endpoints (fetchAppleMusicChartAssets uses safeParse, fetchBlenderStream uses .parse). This bypasses runtime validation and can cause crashes from malformed API responses.
  3. Missing timeout signal: Unlike fetchAppleMusicChartAssets and fetchBlenderStream, this fetch doesn't compose a timeout signal for resilience.
🛡️ Proposed fix

First, add a Zod schema for the playlist response (place after line 165):

const BlenderPlaylistItemSchema = z.object({
  description: z.string().optional(),
  duration: z.number().optional(),
  id: z.string(),
  images: BlenderOpenFilmImagesSchema.optional(),
  subtitle: z.string().optional(),
  title: z.string(),
  year: z.number().optional(),
})

const BlenderPlaylistResponseSchema = z.object({
  count: z.number(),
  items: z.array(BlenderPlaylistItemSchema),
  title: z.string(),
})

Then update the fetch logic:

   if (playlistId !== BLENDER_OPEN_FILMS_PLAYLIST_ID) {
     throw new Error(`Unknown playlist preset "${playlistId}".`)
   }
-  const response = await fetch(`${BLENDER_API_BASE_URL}/playlist`, { signal })
+  
+  const combinedSignal = createTimeoutSignal(signal, BLENDER_STREAM_TIMEOUT_MS)
+  const response = await fetch(`${BLENDER_API_BASE_URL}/playlist`, {
+    signal: combinedSignal,
+  })
+  throwIfAborted(combinedSignal)
+
   if (!response.ok) {
     throw new Error(`Failed to fetch Blender playlist: ${response.statusText}`)
   }

-  const playlist = (await response.json()) as BlenderPlaylistResponse
+  const responseJson: unknown = await response.json()
+  throwIfAborted(combinedSignal)
+
+  const playlist = BlenderPlaylistResponseSchema.parse(responseJson)

   return playlist.items.map(toBlenderOpenFilmAsset)
 }
🤖 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/www/components/stream-panel/content-catalog.ts` around lines 241 - 248,
The Blender playlist branch must add timeout signal composition, abort guards,
and Zod validation: compose a timeout signal like other loaders (as used in
fetchAppleMusicChartAssets/fetchBlenderStream), pass it into fetch, call
throwIfAborted() immediately after the fetch and again after awaiting
response.json(), and validate the parsed body with a new Zod schema (e.g.,
BlenderPlaylistResponseSchema/BlenderPlaylistItemSchema) using safeParse; on
validation failure throw a descriptive error, and then map validated items with
toBlenderOpenFilmAsset.

Source: Coding guidelines


214-228: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Add abort guards after each await in fetchBlenderStream.

This function is missing abort checks after the fetch and response.json() await points, violating the coding guideline to use abort guards after each await in async load functions. Without these guards, if the signal is aborted mid-execution, the function will continue processing stale data instead of terminating, creating a race condition.

Compare to fetchAppleMusicChartAssets (lines 291, 300), which properly includes throwIfAborted checks after each await point.

🛡️ Proposed fix
 export async function fetchBlenderStream(
   assetId: string,
   signal?: AbortSignal
 ): Promise<BlenderStreamResponse> {
   const combinedSignal = createTimeoutSignal(signal, BLENDER_STREAM_TIMEOUT_MS)
   const response = await fetch(`${BLENDER_API_BASE_URL}/stream/${assetId}`, {
     signal: combinedSignal,
   })
+  throwIfAborted(combinedSignal)

   if (!response.ok) {
     throw new Error(`Failed to fetch Blender stream: ${response.statusText}`)
   }

-  return BlenderStreamResponseSchema.parse(await response.json())
+  const responseJson: unknown = await response.json()
+  throwIfAborted(combinedSignal)
+
+  return BlenderStreamResponseSchema.parse(responseJson)
 }
🤖 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/www/components/stream-panel/content-catalog.ts` around lines 214 - 228,
The fetchBlenderStream function needs abort guards after each await: after the
fetch(...) await and after awaiting response.json(); use the same abort helper
used elsewhere (e.g., throwIfAborted) with the combinedSignal created by
createTimeoutSignal(BLENDER_STREAM_TIMEOUT_MS) to throw if aborted, and call it
immediately after the fetch resolves and again before calling
BlenderStreamResponseSchema.parse on the JSON result so the function stops
instead of processing stale data.

Source: Coding guidelines

apps/www/registry/collection/registry-blocks.ts (1)

8-15: ⚠️ Potential issue | 🟠 Major

Add lucide-react to the video-player block dependencies in apps/www/registry/collection/registry-blocks.ts.

media-player.tsx imports RotateCwIcon from "lucide-react", but the video-player registry block manifest doesn’t include lucide-react in its npm dependencies, so installed builds can miss the package. Add "lucide-react" and rerun bun run registry:build in apps/www/.

🤖 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/www/registry/collection/registry-blocks.ts` around lines 8 - 15, The
video-player block manifest is missing the lucide-react npm dependency while
media-player.tsx imports RotateCwIcon from "lucide-react"; add "lucide-react" to
the dependencies array for the video-player block in registry-blocks.ts (the
same array containing "`@phosphor-icons/react`", "`@base-ui/react`", etc.), then
rebuild the registry by running the registry build command (e.g., bun run
registry:build).

Sources: Coding guidelines, Learnings

🧹 Nitpick comments (1)
apps/www/registry/default/blocks/video-player/components/media-player.tsx (1)

116-121: ⚡ Quick win

Avoid useAsset() in this overlay; subscribe to only the fields you need.

PlayerErrorScreen only needs currentItem and loadAsset, but useAsset() composes the broader asset/playlist API, so this component rerenders on unrelated state changes and breaks the selector-only access rule. Please switch this to the relevant store selectors directly.

As per coding guidelines, **/*.{ts,tsx} must use granular per-feature selectors like useXxxStore(s => s.field) for state access—never access entire slices. Based on learnings, store selectors must use the correct useXxxStore(s => s.field) pattern instead of convenience hooks.

🤖 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/www/registry/default/blocks/video-player/components/media-player.tsx`
around lines 116 - 121, PlayerErrorScreen is using the broad useAsset() hook
(currentItem, loadAsset) which causes unnecessary rerenders; replace it with
granular store selectors—subscribe only to the fields you need via the
per-feature selector pattern (e.g., useAssetStore(s => s.currentItem) and
useAssetStore(s => s.loadAsset)) and update the retryStream callback to
reference those selected values so the component no longer reads the entire
asset/playlist slice.

Sources: Coding guidelines, Learnings

🤖 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/www/components/blocks/preview-pane.tsx`:
- Around line 24-27: BlockPreviewWithToolbar calls the client-only hook
useSearchParams() but is rendered from static pages (via BlockPageShell /
block-showcase.tsx and the blocks layout) without a Suspense boundary; wrap the
preview subtree in a React.Suspense boundary so Next can hydrate client hooks
safely. Specifically, locate where BlockPreviewWithToolbar is injected (e.g., in
BlockPageShell or apps/www/app/blocks/layout.tsx / block-showcase.tsx) and wrap
that component (or its parent container) with <Suspense fallback={...}> to
provide a client-side boundary for useSearchParams(), ensuring the preview pane
can use client hooks without static render errors.

---

Outside diff comments:
In `@apps/www/components/stream-panel/content-catalog.ts`:
- Around line 241-248: The Blender playlist branch must add timeout signal
composition, abort guards, and Zod validation: compose a timeout signal like
other loaders (as used in fetchAppleMusicChartAssets/fetchBlenderStream), pass
it into fetch, call throwIfAborted() immediately after the fetch and again after
awaiting response.json(), and validate the parsed body with a new Zod schema
(e.g., BlenderPlaylistResponseSchema/BlenderPlaylistItemSchema) using safeParse;
on validation failure throw a descriptive error, and then map validated items
with toBlenderOpenFilmAsset.
- Around line 214-228: The fetchBlenderStream function needs abort guards after
each await: after the fetch(...) await and after awaiting response.json(); use
the same abort helper used elsewhere (e.g., throwIfAborted) with the
combinedSignal created by createTimeoutSignal(BLENDER_STREAM_TIMEOUT_MS) to
throw if aborted, and call it immediately after the fetch resolves and again
before calling BlenderStreamResponseSchema.parse on the JSON result so the
function stops instead of processing stale data.

In `@apps/www/registry/collection/registry-blocks.ts`:
- Around line 8-15: The video-player block manifest is missing the lucide-react
npm dependency while media-player.tsx imports RotateCwIcon from "lucide-react";
add "lucide-react" to the dependencies array for the video-player block in
registry-blocks.ts (the same array containing "`@phosphor-icons/react`",
"`@base-ui/react`", etc.), then rebuild the registry by running the registry build
command (e.g., bun run registry:build).

---

Nitpick comments:
In `@apps/www/registry/default/blocks/video-player/components/media-player.tsx`:
- Around line 116-121: PlayerErrorScreen is using the broad useAsset() hook
(currentItem, loadAsset) which causes unnecessary rerenders; replace it with
granular store selectors—subscribe only to the fields you need via the
per-feature selector pattern (e.g., useAssetStore(s => s.currentItem) and
useAssetStore(s => s.loadAsset)) and update the retryStream callback to
reference those selected values so the component no longer reads the entire
asset/playlist slice.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: a7a7f85b-502c-48a7-afaa-04a0f64f6900

📥 Commits

Reviewing files that changed from the base of the PR and between 29a5a7f and 5cf35ff.

⛔ Files ignored due to path filters (2)
  • apps/www/package.json is excluded by none and included by none
  • bun.lock is excluded by !**/*.lock and included by none
📒 Files selected for processing (13)
  • apps/www/app/blocks/[[...slug]]/page.tsx
  • apps/www/components/blocks/block-page-shell.tsx
  • apps/www/components/blocks/preview-pane.tsx
  • apps/www/components/cloud-search.tsx
  • apps/www/components/markdown.tsx
  • apps/www/components/players/video-player/player-container.tsx
  • apps/www/components/stream-panel/content-catalog.ts
  • apps/www/components/stream-panel/use-stream-panel-sync.ts
  • apps/www/registry/collection/registry-blocks.ts
  • apps/www/registry/default/blocks/video-player/components/media-player.tsx
  • apps/www/registry/default/hooks/use-asset.ts
  • apps/www/registry/default/hooks/use-playback.ts
  • apps/www/registry/default/hooks/use-player.ts
💤 Files with no reviewable changes (2)
  • apps/www/components/cloud-search.tsx
  • apps/www/components/markdown.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/www/components/players/video-player/player-container.tsx
  • apps/www/components/stream-panel/use-stream-panel-sync.ts

Comment on lines +24 to +27
const searchParams = useSearchParams()
const [expanded, setExpanded] = useState(() => {
return searchParams.get(EXPANDED_QUERY_PARAM) === "true"
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Static route config ==="
sed -n '1,120p' 'apps/www/app/blocks/[[...slug]]/page.tsx'

echo
echo "=== BlockPreviewWithToolbar usages ==="
rg -n -C4 'BlockPreviewWithToolbar' --type=tsx

echo
echo "=== Suspense boundaries in related trees ==="
rg -n -C3 '\bSuspense\b' --type=tsx \
  apps/www/app/blocks \
  apps/www/components/blocks \
  apps/www/components/players \
  apps/www/registry/default

Repository: WINOFFRG/limeplay

Length of output: 2871


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Static route config ==="
sed -n '1,160p' 'apps/www/app/blocks/[[...slug]]/page.tsx'

echo
echo "=== Locate BlockPreviewWithToolbar definition + usages ==="
rg -n "BlockPreviewWithToolbar" apps/www -g'*.ts' -g'*.tsx' -g'*.js' -g'*.jsx'

echo
echo "=== Locate Suspense boundaries around blocks/preview trees ==="
rg -n "\\bSuspense\\b" apps/www/app/blocks -g'*.ts' -g'*.tsx'
rg -n "\\bSuspense\\b" apps/www/components/blocks -g'*.ts' -g'*.tsx'

echo
echo "=== Inspect BlockPageShell implementation (likely caller boundary) ==="
ls -1 apps/www/components/blocks | sed -n '1,200p'
for f in \
  apps/www/components/blocks/block-page-shell.tsx \
  apps/www/components/blocks/preview-pane.tsx \
  apps/www/components/blocks/.* 2>/dev/null; do
  :
done

sed -n '1,240p' 'apps/www/components/blocks/block-page-shell.tsx' 2>/dev/null || true
sed -n '1,240p' 'apps/www/components/blocks/preview-pane.tsx' 2>/dev/null || true

Repository: WINOFFRG/limeplay

Length of output: 3429


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== preview-pane.tsx (full, expected small) ==="
sed -n '1,220p' apps/www/components/blocks/preview-pane.tsx

echo
echo "=== block-showcase.tsx (full, expected small) ==="
sed -n '1,240p' apps/www/components/blocks/block-showcase.tsx

echo
echo "=== block-page-shell.tsx (full, expected small) ==="
sed -n '1,260p' apps/www/components/blocks/block-page-shell.tsx

echo
echo "=== Suspense occurrences in the relevant block components ==="
rg -n "\\bSuspense\\b|<Suspense\\b|React\\.Suspense" apps/www/components/blocks -g'*.ts' -g'*.tsx'

echo
echo "=== Any Suspense in the immediate blocks route module tree ==="
rg -n "\\bSuspense\\b|<Suspense\\b|React\\.Suspense" apps/www/app/blocks -g'*.ts' -g'*.tsx' -S

Repository: WINOFFRG/limeplay

Length of output: 9352


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Suspense boundaries in apps/www/app/blocks ==="
rg -n "\\bSuspense\\b|<Suspense\\b|React\\.Suspense" apps/www/app/blocks -g'*.ts' -g'*.tsx' || true

echo
echo "=== Route/layout files under apps/www/app/blocks (to inspect for Suspense) ==="
fd -t f -e '(png|jpg|jpeg|gif|webp|svg|ico|css)' 'layout.tsx' apps/www/app/blocks -d 6 || true
fd -t f -e '(png|jpg|jpeg|gif|webp|svg|ico|css)' 'layout.ts' apps/www/app/blocks -d 6 || true
fd -t f -e '(png|jpg|jpeg|gif|webp|svg|ico|css)' 'page.tsx' apps/www/app/blocks -d 6 || true

echo
echo "=== Inspect blocks route layouts (if any) ==="
for f in $(fd -t f 'layout.tsx' apps/www/app/blocks -d 6 2>/dev/null); do
  echo "--- $f (first 200 lines) ---"
  sed -n '1,200p' "$f"
done

Repository: WINOFFRG/limeplay

Length of output: 1308


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Suspense boundaries in apps/www/app ==="
rg -n "\\bSuspense\\b|<Suspense\\b|React\\.Suspense" apps/www/app -g'*.ts' -g'*.tsx' || true

echo
echo "=== Layout files above /blocks segment ==="
# print first-level layouts and any group layouts likely above blocks
find apps/www/app -maxdepth 4 -name 'layout.tsx' | sort | sed -n '1,200p'

echo
echo "=== Inspect top-level layouts likely involved ==="
for f in $(find apps/www/app -maxdepth 2 -name 'layout.tsx' | sort); do
  echo "--- $f (first 220 lines) ---"
  sed -n '1,220p' "$f"
done

Repository: WINOFFRG/limeplay

Length of output: 5079


Wrap the blocks useSearchParams() preview in Suspense

  • BlockPreviewWithToolbar (apps/www/components/blocks/preview-pane.tsx, lines ~24-27 and ~65-67) calls useSearchParams().
  • It’s rendered on the static blocks route (apps/www/app/blocks/[[...slug]]/page.tsx, dynamic = "force-static") via BlockPageShell/block-showcase.tsx.
  • There’s no <Suspense> boundary wrapping the preview subtree in BlockPageShell or apps/www/app/blocks/layout.tsx, so Next may bail out / error for useSearchParams() on static rendering.
🤖 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/www/components/blocks/preview-pane.tsx` around lines 24 - 27,
BlockPreviewWithToolbar calls the client-only hook useSearchParams() but is
rendered from static pages (via BlockPageShell / block-showcase.tsx and the
blocks layout) without a Suspense boundary; wrap the preview subtree in a
React.Suspense boundary so Next can hydrate client hooks safely. Specifically,
locate where BlockPreviewWithToolbar is injected (e.g., in BlockPageShell or
apps/www/app/blocks/layout.tsx / block-showcase.tsx) and wrap that component (or
its parent container) with <Suspense fallback={...}> to provide a client-side
boundary for useSearchParams(), ensuring the preview pane can use client hooks
without static render errors.

@WINOFFRG WINOFFRG merged commit e9e26cf into main Jun 6, 2026
6 checks passed

@cubic-dev-ai cubic-dev-ai 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.

4 issues found across 41 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="apps/www/components/stream-panel/use-stream-panel.ts">

<violation number="1" location="apps/www/components/stream-panel/use-stream-panel.ts:74">
P2: `savedStreams` from storage is not validated; non-array persisted data can break save/remove actions at runtime.</violation>
</file>

<file name="apps/www/components/blocks/preview-pane.tsx">

<violation number="1" location="apps/www/components/blocks/preview-pane.tsx:83">
P2: Expanded/collapsed layout no longer animates; motion animate values are constant. This regresses the spring transition to an abrupt className layout switch.</violation>
</file>

<file name="apps/www/registry/collection/registry-ui.ts">

<violation number="1" location="apps/www/registry/collection/registry-ui.ts:16">
P1: Registry dependency "item" does not exist — no component with that name is registered in any collection file.</violation>
</file>

<file name="apps/www/components/stream-panel/use-stream-panel-sync.ts">

<violation number="1" location="apps/www/components/stream-panel/use-stream-panel-sync.ts:233">
P2: Stale persisted selection can prevent any initial media load. When preset lookup fails and `selection.src` is missing, restore exits without falling back to a default.</violation>
</file>

Tip: cubic can generate docs of your entire codebase and keep them up to date. Try it here.

Fix all with cubic | Re-trigger cubic

],
name: "error-screen",
registryDependencies: ["media-provider"],
registryDependencies: ["media-provider", "item"],

@cubic-dev-ai cubic-dev-ai Bot Jun 13, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1: Registry dependency "item" does not exist — no component with that name is registered in any collection file.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/www/registry/collection/registry-ui.ts, line 16:

<comment>Registry dependency "item" does not exist — no component with that name is registered in any collection file.</comment>

<file context>
@@ -12,7 +13,7 @@ export const ui: Registry["items"] = [
     ],
     name: "error-screen",
-    registryDependencies: ["media-provider"],
+    registryDependencies: ["media-provider", "item"],
     type: "registry:ui",
   },
</file context>
Fix with cubic

persistedState.contentSelections = contentSelections
}
if (muted !== undefined) persistedState.muted = muted
if (savedStreams !== undefined) persistedState.savedStreams = savedStreams

@cubic-dev-ai cubic-dev-ai Bot Jun 13, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: savedStreams from storage is not validated; non-array persisted data can break save/remove actions at runtime.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/www/components/stream-panel/use-stream-panel.ts, line 74:

<comment>`savedStreams` from storage is not validated; non-array persisted data can break save/remove actions at runtime.</comment>

<file context>
@@ -0,0 +1,142 @@
+    persistedState.contentSelections = contentSelections
+  }
+  if (muted !== undefined) persistedState.muted = muted
+  if (savedStreams !== undefined) persistedState.savedStreams = savedStreams
+  if (volume !== undefined) persistedState.volume = volume
+
</file context>
Fix with cubic

<div className="relative flex flex-1 flex-col overflow-hidden rounded-2xl bg-muted">
<LazyMotion features={domAnimation}>
<m.div
animate={{ opacity: 1, scale: 1 }}

@cubic-dev-ai cubic-dev-ai Bot Jun 13, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: Expanded/collapsed layout no longer animates; motion animate values are constant. This regresses the spring transition to an abrupt className layout switch.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/www/components/blocks/preview-pane.tsx, line 83:

<comment>Expanded/collapsed layout no longer animates; motion animate values are constant. This regresses the spring transition to an abrupt className layout switch.</comment>

<file context>
@@ -82,55 +76,52 @@ export function BlockPreviewWithToolbar({
-          <div className="relative flex flex-1 flex-col overflow-hidden rounded-2xl bg-muted">
+      <LazyMotion features={domAnimation}>
+        <m.div
+          animate={{ opacity: 1, scale: 1 }}
+          className={cn(
+            `
</file context>
Suggested change
animate={{ opacity: 1, scale: 1 }}
layout
Fix with cubic

return
}

if (!selection.src) return

@cubic-dev-ai cubic-dev-ai Bot Jun 13, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: Stale persisted selection can prevent any initial media load. When preset lookup fails and selection.src is missing, restore exits without falling back to a default.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/www/components/stream-panel/use-stream-panel-sync.ts, line 233:

<comment>Stale persisted selection can prevent any initial media load. When preset lookup fails and `selection.src` is missing, restore exits without falling back to a default.</comment>

<file context>
@@ -57,63 +148,232 @@ export function useStreamPanelSync() {
+        return
+      }
+
+      if (!selection.src) return
+      abortPlaylistRequest()
+      loadCustomStream(selection.src, selection.config, selection.id)
</file context>
Fix with cubic

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