Skip to content

feat(lyrics-plus): add idling indicator for pauses and UI enhancements#3726

Open
ianz56 wants to merge 3 commits intospicetify:mainfrom
ianz56:feat/idling-pauses-fix
Open

feat(lyrics-plus): add idling indicator for pauses and UI enhancements#3726
ianz56 wants to merge 3 commits intospicetify:mainfrom
ianz56:feat/idling-pauses-fix

Conversation

@ianz56
Copy link
Contributor

@ianz56 ianz56 commented Mar 7, 2026

Add an idling indicator for pause lines and long gaps between lyrics >8 seconds, fix RTL handling in karaoke lyrics, and UI enhancements.
Maybe solved this issues #3363

2026-03-07.22-04-05.mp4

Summary by CodeRabbit

  • New Features

    • Added per-word RTL support and improved synced-lyrics rendering, including refined line visibility and initial scroll behavior.
    • Enhanced pause handling: pause progress display, conditional pause indicators, and dynamic “♪” pause markers.
  • Style

    • Improved visuals for past lyrics lines and hover/transition behavior for karaoke words (LTR and RTL).
    • IdlingIndicator now accepts optional className and style props for easier styling.

@coderabbitai
Copy link

coderabbitai bot commented Mar 7, 2026

📝 Walkthrough

Walkthrough

Expanded IdlingIndicator props (adds className, style). Added pause-line utilities and pause-marker rendering, RTL detection, and per-word karaoke rendering changes. Reworked line visibility/active-state logic and performer rendering. Added CSS for past/hidden lines, unsynced layout tweaks, and RTL karaoke gradients.

Changes

Cohort / File(s) Summary
IdlingIndicator & Helpers
CustomApps/lyrics-plus/Pages.js
IdlingIndicator signature updated to accept className and style; style/class handling adjusted. New utilities added for pause-line processing and pause progress calculation.
Lyric Rendering & State Logic
CustomApps/lyrics-plus/Pages.js
Introduced isPauseLine, findNextLineStartTime, processPauseLines, isRTLText, and renderPerformer; per-word karaoke rendering updated for RTL and per-word completion; line visibility and active-state logic revised (includes initial-scroll ref).
Performer Rendering Consolidation
CustomApps/lyrics-plus/Pages.js
Replaced inline performer render blocks with shared renderPerformer helper across synced and unsynced flows; conditional rendering adjusted for pause indicators.
CSS: Layout, Visibility & RTL
CustomApps/lyrics-plus/style.css
Added styles for unsynced layout tweaks, hover underline, LyricsLine-past, LyricsLine-hidden, and RTL-specific karaoke word gradient/backdrop and hover behavior adjustments.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Suggested reviewers

  • rxri

Poem

I twitch my nose and tap a key, 🐇
Pause-marks hum where gaps should be,
Words align — LTR or RTL delight,
Performers prance in soft moonlight,
A tiny hop, the lyrics sing to me. 🎶

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 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: adding an idling indicator for pauses and UI enhancements, which aligns with the PR's core objectives and the substantial changes to pause-line handling, RTL support, and styling.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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 and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
CustomApps/lyrics-plus/Pages.js (1)

85-94: Gap detection only works for Musixmatch karaoke lyrics.

The gap detection logic at lines 85-94 depends on line.endTime, which is only provided by the Musixmatch karaoke provider (see ProviderMusixmatch.js:282-291). Synced lyrics from Spotify and Netease providers only have startTime, so this code path will never execute for those sources.

This isn't a bug since it's properly guarded with line.endTime != null, but users might expect pause indicators to appear for long instrumental sections in all synced lyrics, not just Musixmatch karaoke.

Consider adding a comment documenting this limitation, or estimating gaps using consecutive startTime values when endTime is unavailable.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@CustomApps/lyrics-plus/Pages.js` around lines 85 - 94, The gap detection
currently only runs when line.endTime is present, so LONG_PAUSE_THRESHOLD-based
pause markers never appear for providers that only supply startTime (e.g.,
Spotify/Netease); update the logic in the block using
line.endTime/nextLine.startTime (the code around the check with isPauseLine and
result.push) to fall back to estimating the previous line's end by using the
nextLine.startTime of the current line pair (e.g., treat previous endTime as the
current line's startTime when endTime is null) and compare that estimated gap to
LONG_PAUSE_THRESHOLD, or if you prefer the minimal change, add a clear comment
above the existing if explaining the limitation and recommending the estimation
approach for future work; ensure you reference LONG_PAUSE_THRESHOLD,
isPauseLine, line.endTime, nextLine.startTime and the result.push call when
making the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@CustomApps/lyrics-plus/Pages.js`:
- Around line 85-94: The gap detection currently only runs when line.endTime is
present, so LONG_PAUSE_THRESHOLD-based pause markers never appear for providers
that only supply startTime (e.g., Spotify/Netease); update the logic in the
block using line.endTime/nextLine.startTime (the code around the check with
isPauseLine and result.push) to fall back to estimating the previous line's end
by using the nextLine.startTime of the current line pair (e.g., treat previous
endTime as the current line's startTime when endTime is null) and compare that
estimated gap to LONG_PAUSE_THRESHOLD, or if you prefer the minimal change, add
a clear comment above the existing if explaining the limitation and recommending
the estimation approach for future work; ensure you reference
LONG_PAUSE_THRESHOLD, isPauseLine, line.endTime, nextLine.startTime and the
result.push call when making the change.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 57072133-afe3-428e-963e-a1cb98e781d9

📥 Commits

Reviewing files that changed from the base of the PR and between 404bb10 and 48a6bbb.

📒 Files selected for processing (2)
  • CustomApps/lyrics-plus/Pages.js
  • CustomApps/lyrics-plus/style.css

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
CustomApps/lyrics-plus/Pages.js (2)

128-132: Consider using a separate accumulator variable instead of mutating startTime

The startTime parameter is being reassigned within the loop to track cumulative word timing. While this works correctly (primitives are passed by value in JavaScript), mutating a function parameter can be confusing for maintainability. Using a separate variable like accumulatedTime would clarify intent.

♻️ Suggested refactor for clarity
 return text.map(({ word, time }, i) => {
 	const isRTL = isRTLText(typeof word === "string" ? word : "");
-	const isWordActive = position >= startTime;
-	startTime += time;
-	const isWordComplete = isWordActive && position >= startTime;
+	const wordStartTime = startTime;
+	startTime += time; // accumulate for next iteration
+	const isWordActive = position >= wordStartTime;
+	const isWordComplete = isWordActive && position >= startTime;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@CustomApps/lyrics-plus/Pages.js` around lines 128 - 132, The loop mutates the
function parameter startTime inside text.map which reduces clarity; introduce a
new local accumulator (e.g., accumulatedTime) initialized to startTime before
the map and use/update accumulatedTime inside the map to compute isWordActive
and isWordComplete and to add each word's time, leaving the original startTime
parameter unchanged and maintaining the same comparisons in
isWordActive/isWordComplete and subsequent logic (identify usages in the map
callback where isWordActive, isWordComplete and the startTime increment occur).

43-54: Minor: Consider defensive type check before calling .trim()

If text?.props?.children?.[0] resolves to a non-string truthy value (e.g., a nested React element), calling .trim() on line 53 would throw a TypeError. Given that lyrics data comes from multiple providers, a defensive check could prevent edge-case crashes.

♻️ Suggested defensive fix
 const isPauseLine = (text) => {
 	if (!text) return true;
 	if (Array.isArray(text)) {
 		const joined = text
 			.map((w) => (typeof w === "object" ? w.word : w))
 			.join("")
 			.trim();
 		return joined === "♪" || joined === "";
 	}
 	const str = typeof text === "object" ? text?.props?.children?.[0] : text;
-	return !str || str.trim() === "♪" || str.trim() === "";
+	return !str || (typeof str === "string" && (str.trim() === "♪" || str.trim() === ""));
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@CustomApps/lyrics-plus/Pages.js` around lines 43 - 54, The isPauseLine
function may call .trim() on a non-string when text is an object (e.g., a React
element); update the logic to defensively coerce or verify str is a string
before trimming: compute str = typeof text === "object" ?
text?.props?.children?.[0] : text, then if typeof str !== "string" convert it to
a safe string (e.g., String(str) or extract inner text) or bail out (treat as
pause) before calling str.trim(); apply this change inside isPauseLine so
.trim() is only invoked on confirmed string values.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@CustomApps/lyrics-plus/Pages.js`:
- Around line 128-132: The loop mutates the function parameter startTime inside
text.map which reduces clarity; introduce a new local accumulator (e.g.,
accumulatedTime) initialized to startTime before the map and use/update
accumulatedTime inside the map to compute isWordActive and isWordComplete and to
add each word's time, leaving the original startTime parameter unchanged and
maintaining the same comparisons in isWordActive/isWordComplete and subsequent
logic (identify usages in the map callback where isWordActive, isWordComplete
and the startTime increment occur).
- Around line 43-54: The isPauseLine function may call .trim() on a non-string
when text is an object (e.g., a React element); update the logic to defensively
coerce or verify str is a string before trimming: compute str = typeof text ===
"object" ? text?.props?.children?.[0] : text, then if typeof str !== "string"
convert it to a safe string (e.g., String(str) or extract inner text) or bail
out (treat as pause) before calling str.trim(); apply this change inside
isPauseLine so .trim() is only invoked on confirmed string values.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 192e03ae-fd1b-468b-8f3f-2e3f4697f15c

📥 Commits

Reviewing files that changed from the base of the PR and between 48a6bbb and 1da89a2.

📒 Files selected for processing (1)
  • CustomApps/lyrics-plus/Pages.js

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