Skip to content

feat: media session API and siemap fixes#158

Open
WINOFFRG wants to merge 1 commit into
mainfrom
feat/media-session
Open

feat: media session API and siemap fixes#158
WINOFFRG wants to merge 1 commit into
mainfrom
feat/media-session

Conversation

@WINOFFRG

@WINOFFRG WINOFFRG commented Jul 4, 2026

Copy link
Copy Markdown
Owner

Review in cubic

Summary by CodeRabbit

  • New Features

    • Added browser media session support for audio and video players, including playback controls, metadata, seek handling, and picture-in-picture actions.
    • Added a new utility hook for syncing media session state with player data.
    • Improved playlist navigation with support for going to the previous item.
  • Documentation

    • Expanded docs for audio/video players and the new media session hook with setup, examples, and API details.
    • Added the new hook to the hooks index and registry pages.
  • Bug Fixes

    • Updated sitemap generation to include blocks pages and keep docs pages up to date.
    • Removed development-only script loading from the home layout and moved it to the app layout.

@coderabbitai

coderabbitai Bot commented Jul 4, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds a new use-media-session utility hook wrapping the browser Media Session API, along with AudioMediaSessionController/VideoMediaSessionController components integrating it into audio/video players. Registry entries and docs were updated. Unrelated changes include a previous() playlist store refactor, timeline thumb styling, sitemap generation for blocks pages, and dev-script relocation.

Changes

Media Session Hook and Player Integration

Layer / File(s) Summary
use-media-session hook implementation
apps/www/registry/default/hooks/use-media-session.ts
Adds types, playback/position state helpers, and useMediaSession, useMediaSessionActionHandlers, useMediaSessionSync hooks with browser-guarded, error-handled writes.
Audio player media-session controller
apps/www/registry/default/blocks/audio-player/components/media-session-controller.tsx, apps/www/registry/default/blocks/audio-player/lib/media-kit.ts, apps/www/registry/default/blocks/audio-player/player.tsx
Adds AudioMediaSessionController deriving metadata/position/action handlers from audio state, exports useMediaApi, and mounts the controller in the player.
Video player media-session controller
apps/www/registry/default/blocks/video-player/components/media-session-controller.tsx, apps/www/registry/default/blocks/video-player/lib/media-kit.ts, apps/www/registry/default/blocks/video-player/player.tsx
Adds VideoMediaSessionController with PiP-aware action handlers, exports useMediaApi, mounts the controller, adds a TODO, and updates media background styling.
Registry entries and documentation
apps/www/registry/collection/registry-hooks.ts, apps/www/registry/collection/registry-blocks.ts, apps/www/content/docs/hooks/*, apps/www/content/docs/blocks/audio-player.mdx, apps/www/content/docs/blocks/video-player.mdx
Registers the hook and controller component files, adds use-media-session docs page, and updates feature/notes sections in player docs.

Estimated code review effort: 4 (Complex) | ~60 minutes

Playlist previous() Action Refactor

Layer / File(s) Summary
Playlist store previous() action
apps/www/registry/default/hooks/use-playlist.ts
Moves previous() navigation logic into the Zustand store slice, removes the hook-level useMediaEvents-based callback, and wires state.previous into the returned API.

Timeline, Sitemap, and Dev Script Updates

Layer / File(s) Summary
Timeline thumb styling
apps/www/registry/default/blocks/audio-player/components/fixed-timeline-control.tsx, apps/www/registry/default/ui/timeline-control.tsx
Adjusts transition timing and adds a --lp-timeline-thumb-left clamp-based CSS variable, forwarding caller-provided styles in the shared Thumb component.
Sitemap blocks pages
apps/www/app/sitemap.ts
Adds blocksPages from blocksSource, removes hardcoded /docs entry, and forces static generation.
Dev-only react-scan script relocation
apps/www/app/(home)/layout.tsx, apps/www/app/layout.tsx
Removes the react-scan script from the home layout and adds it to the root layout head under a development check.

Sequence Diagram(s)

sequenceDiagram
  participant Player as Audio/Video Player
  participant Controller as MediaSessionController
  participant Sync as useMediaSessionSync
  participant Session as useMediaSession
  participant Browser as navigator.mediaSession

  Player->>Controller: render with playback/timeline/asset state
  Controller->>Controller: compute metadata, position, action handlers
  Controller->>Sync: active, metadata, playbackState, position, actions
  Sync->>Session: setMetadata / setPlaybackState / setPositionState / setActionHandlers
  Session->>Browser: navigator.mediaSession API calls
  Browser-->>Session: media key / OS control events
  Session-->>Controller: registered handler invoked (play/pause/seek/next/previous)
  Controller->>Player: calls mediaApi actions
Loading

Possibly related PRs

  • WINOFFRG/limeplay#131: Both PRs modify apps/www/app/(home)/layout.tsx's root layout composition around next/script dev-only script injection.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main changes: a new media session API and sitemap updates.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/media-session

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: 2

🧹 Nitpick comments (4)
apps/www/app/sitemap.ts (1)

21-33: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider deduplicating the docsPages/blocksPages mapping logic.

Both mappings share identical shape (changeFrequency, lastModified, priority: 0.9, url from ${baseUrl}${page.url}), differing only in the source. Extracting a small helper would reduce duplication and keep both branches in sync going forward.

♻️ Proposed refactor
+function toSitemapEntries(pages: { url: string }[], baseUrl: string) {
+  return pages.map((page) => ({
+    changeFrequency: "weekly" as const,
+    lastModified: new Date(),
+    priority: 0.9,
+    url: `${baseUrl}${page.url}`,
+  }))
+}
+
-  const docsPages = source.getPages().map((page) => ({
-    changeFrequency: "weekly" as const,
-    lastModified: new Date(),
-    priority: 0.9,
-    url: `${baseUrl}${page.url}`,
-  }))
+  const docsPages = toSitemapEntries(source.getPages(), baseUrl)

-  const blocksPages = blocksSource.getPages().map((page) => ({
-    changeFrequency: "weekly" as const,
-    lastModified: new Date(),
-    priority: 0.9,
-    url: `${baseUrl}${page.url}`,
-  }))
+  const blocksPages = toSitemapEntries(blocksSource.getPages(), baseUrl)
🤖 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/app/sitemap.ts` around lines 21 - 33, The docsPages and blocksPages
mappings in sitemap generation are duplicated and should be consolidated.
Extract the shared page-to-sitemap object निर्माण into a small helper used by
both source.getPages() and blocksSource.getPages(), keeping the common fields
(changeFrequency, lastModified, priority, url) in one place so Sitemap logic
stays consistent and easier to maintain.
apps/www/registry/default/blocks/audio-player/components/fixed-timeline-control.tsx (1)

50-62: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Clamp to the rendered time width HoverTime switches to HH:MM:SS once the duration exceeds an hour, so the current 5ch bound is too narrow and can let the label clip at the track edges. Use a width based on the longest rendered format instead.

🤖 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/audio-player/components/fixed-timeline-control.tsx`
around lines 50 - 62, The fixed timeline thumb label is clamped using a
hardcoded 5ch width in the TimelineSlider.Thumb style, which is too narrow for
the longest HoverTime format. Update the clamp bounds in
fixed-timeline-control.tsx to use a width that matches the rendered maximum time
label width (including HH:MM:SS) so the thumb text stays fully visible at the
track edges.
apps/www/app/layout.tsx (1)

78-83: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Missing strategy="beforeInteractive" for react-scan.

react-scan's documented Next.js App Router setup uses strategy="beforeInteractive" so it can hook into React before hydration. Without it, this script defaults to afterInteractive and may miss early renders that react-scan is meant to catch.

🔧 Proposed fix
         {process.env.NODE_ENV === "development" && (
           <Script
             crossOrigin="anonymous"
             src="//unpkg.com/react-scan/dist/auto.global.js"
+            strategy="beforeInteractive"
           />
         )}
🤖 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/app/layout.tsx` around lines 78 - 83, The development-only
react-scan Script in the app layout is missing the required Next.js loading
strategy, so it may run too late to observe early renders. Update the Script
usage in the layout component to set strategy="beforeInteractive" on the
react-scan import so it initializes before hydration and matches the documented
App Router setup.
apps/www/registry/default/blocks/video-player/player.tsx (1)

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

TODO left in shipped code.

FallbackPoster/CurrentAssetMedia multi-support fix is flagged as a TODO. Want me to draft the FallbackPoster multi-support implementation, or should this be tracked as a follow-up issue?

🤖 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/player.tsx` at line 164, Remove
the TODO left in shipped code and replace it with the actual multi-support
handling for the poster asset in the video player. Update the `player.tsx` logic
around `FallbackPoster` and `CurrentAssetMedia` so the component supports the
intended fallback behavior directly instead of deferring it with a comment. Keep
the implementation in the same rendering path where the poster/media selection
happens, and ensure the final code is clean of placeholder TODOs.
🤖 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/registry/default/blocks/audio-player/components/media-session-controller.tsx`:
- Around line 97-168: The playlist/metadata helper logic is duplicated between
the audio and video media-session controllers, so extract the shared functions
from media-session-controller into a common module and import them from both
places. Move canMoveToNextTrack, canMoveToPreviousTrack, firstNonEmpty,
getCurrentTimelineTime, hasNextPlaylistItem, hasPreviousPlaylistItem, and
isPlaylistSource into a shared helper near use-media-session or a new shared
lib, then update both controller components to use the shared implementations.

In `@apps/www/registry/default/hooks/use-playlist.ts`:
- Around line 383-411: The previous() handler is always truncating
playlist.history even when the history entry is not the one being consumed,
which can corrupt back-navigation. Update the logic in use-playlist.ts’s
previous method so it only pops history when the chosen previousIndex actually
comes from the last history item path, and preserves history for repeat-one,
shuffle-boundary, or fallback cases where getPreviousIndex() doesn’t advance via
history. Keep the emitPlaylistChange call aligned with the final selected
previousItem/currentItem state.

---

Nitpick comments:
In `@apps/www/app/layout.tsx`:
- Around line 78-83: The development-only react-scan Script in the app layout is
missing the required Next.js loading strategy, so it may run too late to observe
early renders. Update the Script usage in the layout component to set
strategy="beforeInteractive" on the react-scan import so it initializes before
hydration and matches the documented App Router setup.

In `@apps/www/app/sitemap.ts`:
- Around line 21-33: The docsPages and blocksPages mappings in sitemap
generation are duplicated and should be consolidated. Extract the shared
page-to-sitemap object निर्माण into a small helper used by both
source.getPages() and blocksSource.getPages(), keeping the common fields
(changeFrequency, lastModified, priority, url) in one place so Sitemap logic
stays consistent and easier to maintain.

In
`@apps/www/registry/default/blocks/audio-player/components/fixed-timeline-control.tsx`:
- Around line 50-62: The fixed timeline thumb label is clamped using a hardcoded
5ch width in the TimelineSlider.Thumb style, which is too narrow for the longest
HoverTime format. Update the clamp bounds in fixed-timeline-control.tsx to use a
width that matches the rendered maximum time label width (including HH:MM:SS) so
the thumb text stays fully visible at the track edges.

In `@apps/www/registry/default/blocks/video-player/player.tsx`:
- Line 164: Remove the TODO left in shipped code and replace it with the actual
multi-support handling for the poster asset in the video player. Update the
`player.tsx` logic around `FallbackPoster` and `CurrentAssetMedia` so the
component supports the intended fallback behavior directly instead of deferring
it with a comment. Keep the implementation in the same rendering path where the
poster/media selection happens, and ensure the final code is clean of
placeholder TODOs.
🪄 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: 7dca73e7-266a-44d3-9c32-80413a4fd678

📥 Commits

Reviewing files that changed from the base of the PR and between a1c6556 and 11c2647.

📒 Files selected for processing (20)
  • apps/www/app/(home)/layout.tsx
  • apps/www/app/layout.tsx
  • apps/www/app/sitemap.ts
  • apps/www/content/docs/blocks/audio-player.mdx
  • apps/www/content/docs/blocks/video-player.mdx
  • apps/www/content/docs/hooks/index.mdx
  • apps/www/content/docs/hooks/meta.json
  • apps/www/content/docs/hooks/use-media-session.mdx
  • apps/www/registry/collection/registry-blocks.ts
  • apps/www/registry/collection/registry-hooks.ts
  • apps/www/registry/default/blocks/audio-player/components/fixed-timeline-control.tsx
  • apps/www/registry/default/blocks/audio-player/components/media-session-controller.tsx
  • apps/www/registry/default/blocks/audio-player/lib/media-kit.ts
  • apps/www/registry/default/blocks/audio-player/player.tsx
  • apps/www/registry/default/blocks/video-player/components/media-session-controller.tsx
  • apps/www/registry/default/blocks/video-player/lib/media-kit.ts
  • apps/www/registry/default/blocks/video-player/player.tsx
  • apps/www/registry/default/hooks/use-media-session.ts
  • apps/www/registry/default/hooks/use-playlist.ts
  • apps/www/registry/default/ui/timeline-control.tsx

Comment on lines +97 to +168
function canMoveToNextTrack(
sourceType: AssetSourceType | null,
playlist: PlaylistStore["playlist"]
): boolean {
return isPlaylistSource(sourceType) && hasNextPlaylistItem(playlist)
}

function canMoveToPreviousTrack(
sourceType: AssetSourceType | null,
playlist: PlaylistStore["playlist"]
): boolean {
return isPlaylistSource(sourceType) && hasPreviousPlaylistItem(playlist)
}

function firstNonEmpty(
...values: (null | number | string | undefined)[]
): string | undefined {
for (const value of values) {
if (typeof value === "number" && Number.isFinite(value)) {
return String(value)
}

if (typeof value !== "string") continue

const trimmed = value.trim()
if (trimmed) return trimmed
}

return undefined
}

function getAudioMediaSessionMetadata(
asset: AudioPlayerAsset
): MediaMetadataInit {
const metadata = getAudioAssetMetadata(asset)
const poster = firstNonEmpty(metadata.poster, asset.poster)

return {
album: firstNonEmpty(asset.albumName),
artist: firstNonEmpty(asset.artistName, metadata.subtitle),
artwork: poster ? [{ sizes: "512x512", src: poster }] : [],
title: metadata.title,
}
}

function getCurrentTimelineTime({
currentTime,
isLive,
mediaElement,
}: {
currentTime: number
isLive: boolean
mediaElement: HTMLMediaElement | null
}): number {
return isLive && mediaElement ? mediaElement.currentTime : currentTime
}

function hasNextPlaylistItem(playlist: PlaylistStore["playlist"]): boolean {
if (playlist.repeatMode === "all" && playlist.queue.length > 0) return true

return playlist.getNextIndex() !== -1
}

function hasPreviousPlaylistItem(playlist: PlaylistStore["playlist"]): boolean {
if (playlist.repeatMode === "all" && playlist.queue.length > 0) return true

return playlist.getPreviousIndex() !== -1
}

function isPlaylistSource(sourceType: AssetSourceType | null): boolean {
return sourceType === AssetSourceType.Playlist
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Extract shared playlist/metadata helpers to avoid duplication with video-player controller.

canMoveToNextTrack, canMoveToPreviousTrack, firstNonEmpty, getCurrentTimelineTime, hasNextPlaylistItem, hasPreviousPlaylistItem, and isPlaylistSource are duplicated verbatim in apps/www/registry/default/blocks/video-player/components/media-session-controller.tsx (lines 108-181). Extracting these into a shared module (e.g., alongside use-media-session.ts or a new shared lib) would prevent divergence as either player evolves.

♻️ Suggested approach
+// e.g. apps/www/registry/default/lib/media-session-controller-utils.ts
+export function isPlaylistSource(sourceType: AssetSourceType | null): boolean { ... }
+export function hasNextPlaylistItem(playlist: PlaylistStore["playlist"]): boolean { ... }
+export function hasPreviousPlaylistItem(playlist: PlaylistStore["playlist"]): boolean { ... }
+export function canMoveToNextTrack(...) { ... }
+export function canMoveToPreviousTrack(...) { ... }
+export function firstNonEmpty(...) { ... }
+export function getCurrentTimelineTime(...) { ... }

Then import from both audio-player/components/media-session-controller.tsx and video-player/components/media-session-controller.tsx.

🤖 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/audio-player/components/media-session-controller.tsx`
around lines 97 - 168, The playlist/metadata helper logic is duplicated between
the audio and video media-session controllers, so extract the shared functions
from media-session-controller into a common module and import them from both
places. Move canMoveToNextTrack, canMoveToPreviousTrack, firstNonEmpty,
getCurrentTimelineTime, hasNextPlaylistItem, hasPreviousPlaylistItem, and
isPlaylistSource into a shared helper near use-media-session or a new shared
lib, then update both controller components to use the shared implementations.

Comment on lines +383 to +411
previous: () => {
const playlist = get().playlist as PlaylistStore["playlist"]
const previousIndex = playlist.getPreviousIndex()
if (previousIndex === -1 || previousIndex >= playlist.queue.length) {
return false
}

const previousItem = playlist.queue[previousIndex] as PlaylistItem
const previousHistory =
playlist.history.length > 0
? playlist.history.slice(0, -1)
: playlist.history

set(({ playlist: p }) => {
p.currentIndex = previousIndex
p.currentItem = previousItem
p.history = previousHistory
})

emitPlaylistChange(
events.emit,
previousIndex,
previousItem,
playlist.currentIndex,
playlist.currentItem,
"previous"
)
return 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.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

file="apps/www/registry/default/hooks/use-playlist.ts"

echo "=== outline ==="
ast-grep outline "$file" --view expanded || true

echo
echo "=== relevant lines around getPreviousIndex and previous ==="
nl -ba "$file" | sed -n '180,230p; 370,430p'

Repository: WINOFFRG/limeplay

Length of output: 2784


🏁 Script executed:

#!/bin/bash
set -euo pipefail

file="apps/www/registry/default/hooks/use-playlist.ts"

echo "=== getPreviousIndex ==="
nl -ba "$file" | sed -n '180,215p'

echo
echo "=== previous() ==="
nl -ba "$file" | sed -n '383,411p'

echo
echo "=== nearby history helpers ==="
rg -n "history|previousIndex|getPreviousIndex|emitPlaylistChange" "$file"

Repository: WINOFFRG/limeplay

Length of output: 220


🏁 Script executed:

#!/bin/bash
set -euo pipefail

file="apps/www/registry/default/hooks/use-playlist.ts"

echo "=== getPreviousIndex ==="
sed -n '180,215p' "$file" | cat -n

echo
echo "=== previous() ==="
sed -n '383,411p' "$file" | cat -n

echo
echo "=== history-related helpers ==="
python3 - <<'PY'
from pathlib import Path
text = Path("apps/www/registry/default/hooks/use-playlist.ts").read_text()
for needle in ["history", "getPreviousIndex", "previousHistory", "previous:"]:
    print(f"\n-- {needle} --")
    for i, line in enumerate(text.splitlines(), 1):
        if needle in line:
            print(f"{i}: {line}")
PY

Repository: WINOFFRG/limeplay

Length of output: 4032


🏁 Script executed:

#!/bin/bash
set -euo pipefail

file="apps/www/registry/default/hooks/use-playlist.ts"

echo "=== next() and skipTo() history handling ==="
sed -n '300,360p;520,565p' "$file" | cat -n

echo
echo "=== remove / load handling around history ==="
sed -n '120,150p;250,280p' "$file" | cat -n

Repository: WINOFFRG/limeplay

Length of output: 7314


🏁 Script executed:

#!/bin/bash
set -euo pipefail

file="apps/www/registry/default/hooks/use-playlist.ts"

echo "=== selectors around hasPrevious / previousItem ==="
sed -n '630,690p' "$file" | cat -n

echo
echo "=== all references to previousItem / hasPrevious ==="
python3 - <<'PY'
from pathlib import Path
text = Path("apps/www/registry/default/hooks/use-playlist.ts").read_text().splitlines()
for i, line in enumerate(text, 1):
    if any(k in line for k in ["hasPrevious", "previousItem", "getPreviousIndex()", "previous: playlist.previous"]):
        print(f"{i}: {line}")
PY

Repository: WINOFFRG/limeplay

Length of output: 3280


Only pop history when previous() actually uses it
apps/www/registry/default/hooks/use-playlist.ts:383-411 — previousHistory always drops the last entry whenever history is non-empty, even when getPreviousIndex() returns currentIndex on repeat-one / shuffle-boundary paths or falls back because the last history item no longer exists in queue. That silently discards a valid back-navigation entry and corrupts later previous() calls.

🤖 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/hooks/use-playlist.ts` around lines 383 - 411, The
previous() handler is always truncating playlist.history even when the history
entry is not the one being consumed, which can corrupt back-navigation. Update
the logic in use-playlist.ts’s previous method so it only pops history when the
chosen previousIndex actually comes from the last history item path, and
preserves history for repeat-one, shuffle-boundary, or fallback cases where
getPreviousIndex() doesn’t advance via history. Keep the emitPlaylistChange call
aligned with the final selected previousItem/currentItem state.

@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 20 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/registry/default/blocks/audio-player/components/media-session-controller.tsx">

<violation number="1" location="apps/www/registry/default/blocks/audio-player/components/media-session-controller.tsx:136">
P3: Lock-screen/notification metadata can show the album name as the artist when an audio asset has `albumName` but no `artistName`. `metadata.subtitle` is display text that may include album/genre/year, so it is too broad for `MediaMetadata.artist`; consider using artist-specific fields only.</violation>
</file>

<file name="apps/www/app/layout.tsx">

<violation number="1" location="apps/www/app/layout.tsx:79">
P1: react-scan won't capture initial renders or hydration behavior with default `afterInteractive` strategy. The auto-instrumentation script needs to be loaded before React runs to detect re-renders properly. Add `strategy="beforeInteractive"` so it loads before Next.js hydration.</violation>
</file>

<file name="apps/www/registry/default/blocks/video-player/components/media-session-controller.tsx">

<violation number="1" location="apps/www/registry/default/blocks/video-player/components/media-session-controller.tsx:122">
P3: This introduces another copy of media-session helper logic already present in the audio controller, which raises maintenance cost and makes audio/video behavior likely to drift over time. A shared helper/module for these common functions would keep fixes and behavior aligned.</violation>
</file>

<file name="apps/www/registry/default/hooks/use-playlist.ts">

<violation number="1" location="apps/www/registry/default/hooks/use-playlist.ts:391">
P2: The `previous()` method unconditionally pops the last history entry whenever `history` is non-empty, but `getPreviousIndex()` may return an index that wasn't derived from the history stack (e.g., on repeat-one paths, shuffle boundaries, or when the last history item no longer exists in the queue). In those cases, a valid back-navigation entry is silently discarded, corrupting subsequent `previous()` calls. Consider only popping history when the returned `previousIndex` actually matches the last history entry.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread apps/www/app/layout.tsx
<head>
<UserJotProvider projectId="cmjs634l4043b15ldylgedgwi" />
{process.env.NODE_ENV === "development" && (
<Script

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: react-scan won't capture initial renders or hydration behavior with default afterInteractive strategy. The auto-instrumentation script needs to be loaded before React runs to detect re-renders properly. Add strategy="beforeInteractive" so it loads before Next.js hydration.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/www/app/layout.tsx, line 79:

<comment>react-scan won't capture initial renders or hydration behavior with default `afterInteractive` strategy. The auto-instrumentation script needs to be loaded before React runs to detect re-renders properly. Add `strategy="beforeInteractive"` so it loads before Next.js hydration.</comment>

<file context>
@@ -74,6 +75,12 @@ export default function Layout({ children }: { children: ReactNode }) {
       <head>
         <UserJotProvider projectId="cmjs634l4043b15ldylgedgwi" />
+        {process.env.NODE_ENV === "development" && (
+          <Script
+            crossOrigin="anonymous"
+            src="//unpkg.com/react-scan/dist/auto.global.js"
</file context>

}

const previousItem = playlist.queue[previousIndex] as PlaylistItem
const previousHistory =

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: The previous() method unconditionally pops the last history entry whenever history is non-empty, but getPreviousIndex() may return an index that wasn't derived from the history stack (e.g., on repeat-one paths, shuffle boundaries, or when the last history item no longer exists in the queue). In those cases, a valid back-navigation entry is silently discarded, corrupting subsequent previous() calls. Consider only popping history when the returned previousIndex actually matches the last history entry.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/www/registry/default/hooks/use-playlist.ts, line 391:

<comment>The `previous()` method unconditionally pops the last history entry whenever `history` is non-empty, but `getPreviousIndex()` may return an index that wasn't derived from the history stack (e.g., on repeat-one paths, shuffle boundaries, or when the last history item no longer exists in the queue). In those cases, a valid back-navigation entry is silently discarded, corrupting subsequent `previous()` calls. Consider only popping history when the returned `previousIndex` actually matches the last history entry.</comment>

<file context>
@@ -380,6 +380,35 @@ export function playlistFeature(): MediaFeature<PlaylistStore> {
+          }
+
+          const previousItem = playlist.queue[previousIndex] as PlaylistItem
+          const previousHistory =
+            playlist.history.length > 0
+              ? playlist.history.slice(0, -1)
</file context>


return {
album: firstNonEmpty(asset.albumName),
artist: firstNonEmpty(asset.artistName, metadata.subtitle),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3: Lock-screen/notification metadata can show the album name as the artist when an audio asset has albumName but no artistName. metadata.subtitle is display text that may include album/genre/year, so it is too broad for MediaMetadata.artist; consider using artist-specific fields only.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/www/registry/default/blocks/audio-player/components/media-session-controller.tsx, line 136:

<comment>Lock-screen/notification metadata can show the album name as the artist when an audio asset has `albumName` but no `artistName`. `metadata.subtitle` is display text that may include album/genre/year, so it is too broad for `MediaMetadata.artist`; consider using artist-specific fields only.</comment>

<file context>
@@ -0,0 +1,168 @@
+
+  return {
+    album: firstNonEmpty(asset.albumName),
+    artist: firstNonEmpty(asset.artistName, metadata.subtitle),
+    artwork: poster ? [{ sizes: "512x512", src: poster }] : [],
+    title: metadata.title,
</file context>
Suggested change
artist: firstNonEmpty(asset.artistName, metadata.subtitle),
artist: firstNonEmpty(asset.artistName),

return isPlaylistSource(sourceType) && hasPreviousPlaylistItem(playlist)
}

function firstNonEmpty(

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3: This introduces another copy of media-session helper logic already present in the audio controller, which raises maintenance cost and makes audio/video behavior likely to drift over time. A shared helper/module for these common functions would keep fixes and behavior aligned.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/www/registry/default/blocks/video-player/components/media-session-controller.tsx, line 122:

<comment>This introduces another copy of media-session helper logic already present in the audio controller, which raises maintenance cost and makes audio/video behavior likely to drift over time. A shared helper/module for these common functions would keep fixes and behavior aligned.</comment>

<file context>
@@ -0,0 +1,181 @@
+  return isPlaylistSource(sourceType) && hasPreviousPlaylistItem(playlist)
+}
+
+function firstNonEmpty(
+  ...values: (null | number | string | undefined)[]
+): string | undefined {
</file context>

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.

1 participant