Skip to content

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

Open
ianz56 wants to merge 6 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 6 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

    • Per-word RTL support and improved karaoke word visuals.
    • Smarter initial scrolling and refined active-line visibility.
    • Enhanced pause handling: consolidated pause lines, pause progress display, conditional pause indicators and dynamic pause markers.
    • Unified performer rendering across lyric views.
  • Style

    • Improved visuals for past lyric lines, hover/transition behavior for karaoke words (LTR/RTL).
    • Idling indicator accepts external class and style for easier customization.

@coderabbitai
Copy link

coderabbitai bot commented Mar 7, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 04dbebbe-7c38-4a32-aeed-3f099396b0d6

📥 Commits

Reviewing files that changed from the base of the PR and between 68c5741 and e6510c3.

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

📝 Walkthrough

Walkthrough

Added public className and style to IdlingIndicator; introduced pause-line utilities and pause markers; added RTL detection and per-word karaoke handling; consolidated performer rendering; adjusted active-line/scrolling/padding logic for pauses; added CSS for past/hidden lines, unsynced layout tweaks, and RTL karaoke gradients.

Changes

Cohort / File(s) Summary
Pages & Helpers
CustomApps/lyrics-plus/Pages.js
IdlingIndicator signature extended to ({ isActive, progress, delay, className = "", style = {} }). Added isPauseLine, findNextLineStartTime, getPauseIndicator, processPauseLines, isRTLText, LONG_PAUSE_THRESHOLD, and renderPerformer. Karaoke line rendering updated for per-word active/complete states and RTL; pause-line handling inserted into lyric flow and padding/scroll calculations.
Per-line Idling & Performer Rendering
CustomApps/lyrics-plus/Pages.js
Integrated per-line IdlingIndicator rendering with computed progress/delay; consolidated multiple inline performer render blocks into renderPerformer; adapted initial scroll behavior and active line index logic to account for pause lines and long gaps; preserved clipboard copy behavior within new structure.
Styles (RTL, visibility, layout)
CustomApps/lyrics-plus/style.css
Added .LyricsLine-past, .LyricsLine-hidden; unsynced lyrics spacing/hover underline rules; RTL-specific karaoke word gradient/backdrop and adjusted hover/positioning rules for LTR/RTL karaoke words; restored/adjusted hover behavior mappings.

Sequence Diagram(s)

(omitted)

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Suggested reviewers

  • rxri

Poem

I twitch my nose and tap a key, 🐇
Pause-dots bloom where silence grows,
Words shift left or shimmer right,
Performers peep into the light,
I hop — the chorus glows. 🎶

🚥 Pre-merge checks | ✅ 2 | ❌ 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 (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title accurately reflects the main changes: adding an idling indicator for pauses and UI enhancements (RTL support, styling adjustments). It is concise, specific, and clearly summarizes the primary contribution.

✏️ 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
📝 Coding Plan
  • Generate coding plan for human review comments

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

Copilot AI review requested due to automatic review settings March 13, 2026 23:25
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.

Actionable comments posted: 2

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

265-275: Extract duplicated pause-indicator construction into a helper.

Both synced views build the same pause indicator block. Pulling this into one function reduces drift risk and keeps pause behavior consistent across compact/expanded modes.

Also applies to: 611-621

🤖 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 265 - 275, Extract the
duplicated pause-indicator construction into a single helper function (e.g.,
getPauseIndicator or buildPauseIndicator) that accepts the inputs used in the
calculation (lyricWithEmptyLines, lineNumber, startTime, position, and
isFocusedLine/isPause) and returns either the IdlingIndicator element or null;
inside the helper reuse findNextLineStartTime to compute nextStart, pauseStart,
pauseDuration, progress and delay (delay = pauseDuration / 3), and create the
react.createElement(IdlingIndicator, { progress, delay }) when appropriate;
replace both inline blocks (the current block around IdlingIndicator and the
similar one at the other location) with calls to this helper so both synced
views use the same logic.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@CustomApps/lyrics-plus/Pages.js`:
- Around line 199-224: The window math is doing string concatenation because
CONFIG.visual["lines-before"] and ["lines-after"] may be strings; update the
calculations in the useMemo (where targetBefore and targetAfter are computed) to
coerce those config values to numbers (e.g., use
Number(CONFIG.visual["lines-before"]) or parseInt(...,10) before adding 1) so
targetBefore and targetAfter are numeric; also apply the same numeric coercion
where the same config values are used elsewhere (e.g., the related padding logic
that affects activeLines/activeElementIndex) to prevent the string + number bug.
- Around line 543-545: The scroll effect that reads initialScroll.current and
runs on activeLineIndex should also react to lyric content changes: update the
dependency array of the useEffect that handles scrolling (the effect referencing
activeLineIndex and initialScroll.current) to include lyricsId (or the stable
identifier for lyrics) in addition to activeLineIndex so the effect re-runs when
lyrics change even if activeLineIndex remains the same; locate the useEffect
that performs scrolling and adjust its deps from [activeLineIndex] to
[activeLineIndex, lyricsId].

---

Nitpick comments:
In `@CustomApps/lyrics-plus/Pages.js`:
- Around line 265-275: Extract the duplicated pause-indicator construction into
a single helper function (e.g., getPauseIndicator or buildPauseIndicator) that
accepts the inputs used in the calculation (lyricWithEmptyLines, lineNumber,
startTime, position, and isFocusedLine/isPause) and returns either the
IdlingIndicator element or null; inside the helper reuse findNextLineStartTime
to compute nextStart, pauseStart, pauseDuration, progress and delay (delay =
pauseDuration / 3), and create the react.createElement(IdlingIndicator, {
progress, delay }) when appropriate; replace both inline blocks (the current
block around IdlingIndicator and the similar one at the other location) with
calls to this helper so both synced views use the same logic.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: dcfedbaf-df20-4098-81eb-e5b30b3050ea

📥 Commits

Reviewing files that changed from the base of the PR and between 1da89a2 and 45a2e24.

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

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds improved pause/idling visualization and RTL karaoke rendering to the Lyrics Plus custom app, alongside small UI/visibility tweaks to synced/unsynced lyric lines.

Changes:

  • Introduces pause-line detection + long-gap insertion logic to drive idling indicators during lyric downtime.
  • Adds per-word RTL support for karaoke (reversed gradient direction + hover behavior adjustments).
  • Updates styling to support hidden pause lines and “past line” visual treatment.

Reviewed changes

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

File Description
CustomApps/lyrics-plus/Pages.js Adds pause-line processing, idling indicator logic, per-word RTL karaoke classes, and refactors performer rendering/scroll behavior.
CustomApps/lyrics-plus/style.css Adds RTL karaoke gradient rules, hidden-line class, “past line” styling, and minor unsynced line hover/layout tweaks.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

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.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@CustomApps/lyrics-plus/Pages.js`:
- Around line 214-239: The current useMemo computation for
activeLines/activeElementIndex reads CONFIG.visual["lines-before"] and
["lines-after"] with Number(...) which may produce NaN; update the parsing to
coerce to a finite integer with a safe default and clamp (e.g., parseInt or
Number then Number.isFinite check, fallback to 0 or a small max) before
assigning targetBefore and targetAfter and anywhere else those CONFIG values are
used (references: targetBefore, targetAfter, useMemo and the code around
activeLines/activeElementIndex and the other occurrence near line ~300); ensure
the final values are non-NaN integers so the while loops and padding logic
always behave deterministically.
- Around line 109-113: The code falls back endTime to line.startTime but then
checks endTime > line.startTime, which prevents inserting long-gap markers when
endTime is absent; update the conditional in the block that computes gap (around
the endTime/nextLine logic) so it uses nextLine.startTime > line.startTime (or
otherwise checks the gap relative to the original start rather than requiring a
non-null endTime) instead of endTime > line.startTime, keeping the existing gap
check (gap >= LONG_PAUSE_THRESHOLD) and the isPauseLine(nextLine.text) guard so
long-gap markers are inserted even when line.endTime is missing.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: fd9be0e9-58ed-4519-bbc0-26ba92998a03

📥 Commits

Reviewing files that changed from the base of the PR and between 45a2e24 and 68c5741.

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

2 participants