Skip to content

feat: add Queue Repeat extension#1148

Closed
Malikeq wants to merge 12 commits into
spicetify:mainfrom
Malikeq:main
Closed

feat: add Queue Repeat extension#1148
Malikeq wants to merge 12 commits into
spicetify:mainfrom
Malikeq:main

Conversation

@Malikeq
Copy link
Copy Markdown

@Malikeq Malikeq commented May 3, 2026

Add a new extension called Queue Repeat. This extension allows users to loop tracks that are added manually to the "Next Up" queue. It includes a toggle button in the player bar and handles dynamic queue updates.

Summary by CodeRabbit

  • New Features

    • Added a Queue Repeat extension: a player-toolbar toggle that enables infinite queue looping, captures the current queue, re-queues tracks as they finish, and shows notifications for activation, new tracks, and deactivation.
  • Documentation

    • Added a full README covering features, UI behavior, installation and uninstall steps, usage notes, previews, and expected file structure.
  • Chores

    • Added extension metadata and preview asset for distribution.

@Malikeq Malikeq requested a review from a team as a code owner May 3, 2026 18:39
@Malikeq Malikeq requested review from theRealPadster and removed request for a team May 3, 2026 18:39
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 3, 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

Walkthrough

Adds a new "Queue Repeat" Spicetify extension: manifest, README, and a single-file IIFE that injects a toggle button into the player UI, captures current + queued tracks, polls the queue for newly added tracks, and re-queues finished tracks to form an infinite loop.

Changes

Queue Repeat Extension

Layer / File(s) Summary
Extension Metadata
src/extensions/queue-repeat/manifest.json
Adds manifest with name, description, main = queue-repeat.js, authors, readme reference, version 1.0.0, and preview asset.
Documentation
src/extensions/queue-repeat/README.md
Adds README with feature description, UI/state behavior, notifications, install/uninstall commands, and expected file layout.
Data / Runtime Shape
src/extensions/queue-repeat/queue-repeat.js
Introduces in-memory state: repeatList, previousTrackUri, isActive, queueWatcherInterval, and isPolling guard.
Spicetify Readiness
src/extensions/queue-repeat/queue-repeat.js
Adds waitForSpicetify() helper to poll for Player/Platform API availability before init.
Queue Fetching / Parsing
src/extensions/queue-repeat/queue-repeat.js
Adds getCurrentTrackUri() and getAllQueueTracks() calling PlayerAPI.getQueue(), extracting URIs from queued and nextTracks shapes, deduplicating results.
Core Implementation
src/extensions/queue-repeat/queue-repeat.js
IIFE that seeds repeatList from current track + queue, sets previousTrackUri, handles songchange events to re-queue previous track when present in repeatList, and provides enable/disable flows.
Watcher Lifecycle & Polling
src/extensions/queue-repeat/queue-repeat.js
Implements startQueueWatcher(), stopQueueWatcher(), and pollForNewQueueTracks() on a 2s interval with re-entrancy guard and incremental-add detection/notifications.
UI Injection & Styling
src/extensions/queue-repeat/queue-repeat.js
Injects <style id="qr-styles">, defines ICON_SVG, creates an accessible SVG toggle button, inserts left of Lyrics when found or into right-side/footer fallbacks, and updates qr-active class/title.
Resilience / DOM Watcher
src/extensions/queue-repeat/queue-repeat.js
setupButton() retries injection up to 20 times; watchButtonRemoval() uses a MutationObserver to re-inject the button if removed.
Notifications & Logging
src/extensions/queue-repeat/queue-repeat.js
Shows enable/disable and incremental-add notifications and logs lifecycle events, errors, and queue contents.

Sequence Diagram

sequenceDiagram
    participant User
    participant UI as QR Button
    participant Ext as QueueRepeat Ext
    participant Player as Spicetify PlayerAPI
    participant Queue as Queue/NextTracks

    User->>UI: Click toggle
    UI->>Ext: toggleQueueRepeat()

    alt Enable
        Ext->>Player: getAllQueueTracks()
        Player-->>Ext: queue URIs
        Ext->>Ext: build repeatList & set previousTrackUri
        Ext->>Ext: start polling (2s)
        Ext-->>UI: set active visual
        Ext-->>User: notify enabled
    else Disable
        Ext->>Ext: clear repeatList, stop polling
        Ext-->>UI: set inactive visual
        Ext-->>User: notify disabled
    end

    loop while enabled
        Ext->>Player: pollForNewQueueTracks()
        Player-->>Ext: newly queued URIs
        Ext->>Ext: append deduped URIs & notify
    end

    Player->>Ext: songchange event
    Ext->>Ext: onSongChange()
    alt previousTrackUri in repeatList
        Ext->>Player: addToQueue(previousTrackUri)
    end
    Ext->>Ext: update previousTrackUri
Loading

Estimated Code Review Effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

A rabbit nudges songs back in line,
Clicks a button, hums a looping chime.
Tracks return like friends anew,
Queue grows long, the music true,
🐇🎶 Endless hops through every rhyme.

🚥 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 title accurately summarizes the main change: adding a new Queue Repeat extension for Spicetify with documentation, manifest, and implementation files.
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

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


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
Copy Markdown

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

🧹 Nitpick comments (1)
src/extensions/queue-repeat/queue-repeat.js (1)

41-41: ⚡ Quick win

Remove debug log before merging.

log("Queue raw: " + JSON.stringify(q).slice(0, 200)) will spam the browser console for every poll cycle in production (every 2 seconds while the watcher is active).

📝 Proposed fix
-            log("Queue raw: " + JSON.stringify(q).slice(0, 200));
-
             const queued     = q?.queued     ?? [];
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/extensions/queue-repeat/queue-repeat.js` at line 41, Remove the noisy
debug statement log("Queue raw: " + JSON.stringify(q).slice(0, 200)) that runs
every poll cycle; either delete this line from the polling/watcher code in
queue-repeat.js or wrap it behind a debug/config flag (e.g., only call it when
DEBUG or verbose logging is enabled) so production polling no longer spams the
console.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/extensions/queue-repeat/queue-repeat.js`:
- Line 95: The forEach callbacks on repeatList implicitly return the result of
log(...) which Biome flags; replace the forEach usage (repeatList.forEach(...))
with a for...of iteration that does not return a value — e.g., iterate over
repeatList.entries() to get index and uri and call log(...) inside the loop;
apply the same change for the other identical forEach occurrence that also calls
log.
- Around line 118-139: pollForNewQueueTracks can run concurrently because
setInterval may call it again before the previous async work (getAllQueueTracks)
finishes, causing duplicate URIs to be pushed into repeatList; add an in-flight
guard (e.g., a module-scoped boolean like isPolling) checked at the top of
pollForNewQueueTracks (return early if true), set to true immediately when
starting the async work and ensure it is reset to false in a finally block so
overlapping executions are prevented; update references to isActive,
getAllQueueTracks, repeatList and repeatSet inside pollForNewQueueTracks only
after acquiring the guard so duplicates cannot be added.

In `@src/extensions/queue-repeat/README.md`:
- Line 1: Update the README.md markdown: add descriptive alt text to the image
reference by replacing the bare `![](Foto.png)` with an alt text string (e.g.,
`![Queue repeat screenshot](Foto.png)`), and add a language specifier of `text`
(or `plaintext`) to the two fenced code blocks that list file paths so they
become ```text fenced blocks; look for the code fences that show
`%APPDATA%\spicetify\Extensions\queue-repeat.js` and the `Extensions/ └──
queue-repeat.js` directory listing and add the `text` language tag to each.
- Around line 69-72: The uninstall instruction in README.md is wrong — update
the uninstall command string "spicetify config extensions queue-repeat.js" to
append a trailing dash so it reads "spicetify config extensions
queue-repeat.js-" (leave the following "spicetify apply" line as-is) so the
extension is actually removed when following the README's uninstall steps.

---

Nitpick comments:
In `@src/extensions/queue-repeat/queue-repeat.js`:
- Line 41: Remove the noisy debug statement log("Queue raw: " +
JSON.stringify(q).slice(0, 200)) that runs every poll cycle; either delete this
line from the polling/watcher code in queue-repeat.js or wrap it behind a
debug/config flag (e.g., only call it when DEBUG or verbose logging is enabled)
so production polling no longer spams the console.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 85a07300-a0d6-4312-914c-96884ab744e2

📥 Commits

Reviewing files that changed from the base of the PR and between 9412279 and 7c65884.

⛔ Files ignored due to path filters (1)
  • src/extensions/queue-repeat/Foto.png is excluded by !**/*.png
📒 Files selected for processing (3)
  • src/extensions/queue-repeat/README.md
  • src/extensions/queue-repeat/manifest.json
  • src/extensions/queue-repeat/queue-repeat.js

Comment thread src/extensions/queue-repeat/queue-repeat.js Outdated
Comment thread src/extensions/queue-repeat/queue-repeat.js
Comment thread src/extensions/queue-repeat/README.md Outdated
Comment thread src/extensions/queue-repeat/README.md
Malikeq added 2 commits May 4, 2026 01:56
Add isPolling flag to prevent multiple polling executions.
Copy link
Copy Markdown

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

🧹 Nitpick comments (1)
src/extensions/queue-repeat/queue-repeat.js (1)

42-42: 💤 Low value

Remove or gate production debug logging.

getAllQueueTracks is called during activation and every 2 s while active, so this line dumps up to 200 characters of raw queue JSON to the console on every poll cycle. Consider removing it or wrapping it in a const DEBUG = false guard.

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

In `@src/extensions/queue-repeat/queue-repeat.js` at line 42, The debug log in
getAllQueueTracks ("Queue raw: " + JSON.stringify(q).slice(0, 200)) floods the
console on every poll; either remove that statement or wrap it behind a
disable-able guard—e.g., add a module-level constant like DEBUG = false and
change the call to only log when DEBUG is true (or use an existing extension
debug/config flag); update the log use in getAllQueueTracks so production runs
omit the JSON dump while keeping the single DEBUG gate for easy toggling.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/extensions/queue-repeat/queue-repeat.js`:
- Around line 391-396: When re-injecting the button after DOM removal, the
visual state isn't re-applied; ensure that after calling injectButton() (or
inside injectButton()) you call updateButtonVisual(isActive) so the recreated
element reflects the current isActive state. Locate injectButton(),
makeButton(), and the re-injection block that sets buttonElement = null and
setTimeout(...); update that flow to invoke updateButtonVisual(isActive) once
the new buttonElement exists (or have makeButton initialize from isActive) so
the button doesn't appear "off" when isActive is true.
- Around line 178-180: Replace the noisy call to
Spicetify.Platform.PlayerAPI.addToQueue with the silent API Spicetify.addToQueue
so re-queuing in queue-repeat does not produce "Added to queue" toasts; locate
the call to Spicetify.Platform.PlayerAPI.addToQueue (the try block that awaits
it and then calls log("Re-queued.")) and invoke Spicetify.addToQueue with the
same track identifier(s) (previousTrackUri) instead, keeping the surrounding
error handling and log("Re-queued.") intact.
- Around line 263-272: The toggle button created in makeButton lacks
aria-pressed so screen readers can't detect its on/off state; set an initial
aria-pressed ("false" or based on current repeat state) on the btn created in
makeButton and update that attribute whenever toggleQueueRepeat runs (e.g.,
after awaiting toggleQueueRepeat(), set btn.setAttribute("aria-pressed",
newState) or compute the new state and update the attribute and title
accordingly). Locate makeButton and the click handler that calls
toggleQueueRepeat to add the initial aria-pressed and the post-toggle update so
the DOM always reflects the current state for assistive tech.
- Around line 141-148: The function pollForNewQueueTracks is missing its closing
brace causing subsequent declarations (startQueueWatcher, stopQueueWatcher,
init, and the final IIFE closure) to be nested incorrectly; fix by adding the
missing closing brace and ensuring the finally block is closed properly so
pollForNewQueueTracks ends before the startQueueWatcher function declaration,
then verify the IIFE closure remains balanced (check pollForNewQueueTracks,
startQueueWatcher, stopQueueWatcher, init and the trailing })() are all at
top-level scope).

---

Nitpick comments:
In `@src/extensions/queue-repeat/queue-repeat.js`:
- Line 42: The debug log in getAllQueueTracks ("Queue raw: " +
JSON.stringify(q).slice(0, 200)) floods the console on every poll; either remove
that statement or wrap it behind a disable-able guard—e.g., add a module-level
constant like DEBUG = false and change the call to only log when DEBUG is true
(or use an existing extension debug/config flag); update the log use in
getAllQueueTracks so production runs omit the JSON dump while keeping the single
DEBUG gate for easy toggling.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4be6b2d1-e85f-4c75-800f-25f60f964be9

📥 Commits

Reviewing files that changed from the base of the PR and between e551f67 and fa19ab9.

📒 Files selected for processing (1)
  • src/extensions/queue-repeat/queue-repeat.js

Comment thread src/extensions/queue-repeat/queue-repeat.js
Comment thread src/extensions/queue-repeat/queue-repeat.js
Comment thread src/extensions/queue-repeat/queue-repeat.js
Comment on lines +391 to +396
if (buttonElement && !document.contains(buttonElement)) {
log("Button removed from DOM, re-injecting...");
buttonElement = null;
setTimeout(() => {
if (!buttonElement) injectButton();
}, 500);
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 | 🟡 Minor | ⚡ Quick win

Re-injected button loses its active visual state.

After the MutationObserver removes and reinserts the button, makeButton() always initialises the button in the "off" state. updateButtonVisual(isActive) is never called post-injection, so if isActive is true at that moment the re-injected button will appear as "off" until the user clicks it.

🐛 Proposed fix
             setTimeout(() => {
-                if (!buttonElement) injectButton();
+                if (!buttonElement) {
+                    injectButton();
+                    updateButtonVisual(isActive);
+                }
             }, 500);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (buttonElement && !document.contains(buttonElement)) {
log("Button removed from DOM, re-injecting...");
buttonElement = null;
setTimeout(() => {
if (!buttonElement) injectButton();
}, 500);
if (buttonElement && !document.contains(buttonElement)) {
log("Button removed from DOM, re-injecting...");
buttonElement = null;
setTimeout(() => {
if (!buttonElement) {
injectButton();
updateButtonVisual(isActive);
}
}, 500);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/extensions/queue-repeat/queue-repeat.js` around lines 391 - 396, When
re-injecting the button after DOM removal, the visual state isn't re-applied;
ensure that after calling injectButton() (or inside injectButton()) you call
updateButtonVisual(isActive) so the recreated element reflects the current
isActive state. Locate injectButton(), makeButton(), and the re-injection block
that sets buttonElement = null and setTimeout(...); update that flow to invoke
updateButtonVisual(isActive) once the new buttonElement exists (or have
makeButton initialize from isActive) so the button doesn't appear "off" when
isActive is true.

Copy link
Copy Markdown

@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 `@src/extensions/queue-repeat/queue-repeat.js`:
- Around line 121-141: In pollForNewQueueTracks(), after awaiting
getAllQueueTracks() add a second guard that checks isActive (and optionally
isPolling/session state) before mutating repeatList or showing notifications; if
isActive is false, set isPolling back to false and return early to avoid stale
in-flight polls updating state — update the logic around isActive/isPolling and
repeatList so the await result is ignored when the extension was disabled during
the network call.
- Around line 22-24: The readiness gate is checking
Spicetify.Platform.PlayerAPI.addToQueue but the runtime call uses the global
Spicetify.addToQueue, so the gate can hang if only the global API exists; update
the readiness check to test the same API you call at runtime
(Spicetify.addToQueue) or check both places. Locate the readiness predicate that
references Spicetify?.Platform?.PlayerAPI?.addToQueue (and
Spicetify?.Platform?.PlayerAPI?.getQueue / Spicetify?.Player?.addEventListener)
and replace or augment the check to include Spicetify?.addToQueue (or prefer the
global Spicetify.addToQueue exclusively) so the gate passes when the actual
runtime API is present.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 48737630-8654-4fa8-9f6d-eacdee2a7d5c

📥 Commits

Reviewing files that changed from the base of the PR and between fc6e355 and bbd7b8a.

📒 Files selected for processing (1)
  • src/extensions/queue-repeat/queue-repeat.js

Comment on lines +22 to +24
Spicetify?.Platform?.PlayerAPI?.getQueue &&
Spicetify?.Platform?.PlayerAPI?.addToQueue &&
Spicetify?.Player?.addEventListener &&
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

types_file="$(fd -i 'spicetify.d.ts' | head -n1)"
if [[ -z "${types_file}" ]]; then
  echo "spicetify.d.ts not found"
  exit 1
fi

echo "Using types file: ${types_file}"
rg -nC3 'namespace Spicetify|interface PlayerAPI|addToQueue' "${types_file}"

Repository: spicetify/marketplace

Length of output: 379


🏁 Script executed:

rg -n 'interface PlayerAPI|class PlayerAPI' src/types/spicetify.d.ts -A 15

Repository: spicetify/marketplace

Length of output: 47


🏁 Script executed:

cat -n src/extensions/queue-repeat/queue-repeat.js | sed -n '15,30p'

Repository: spicetify/marketplace

Length of output: 760


🏁 Script executed:

cat -n src/extensions/queue-repeat/queue-repeat.js | sed -n '175,185p'

Repository: spicetify/marketplace

Length of output: 604


🏁 Script executed:

rg -n 'PlayerAPI' src/types/spicetify.d.ts -B 2 -A 10

Repository: spicetify/marketplace

Length of output: 47


🏁 Script executed:

rg -n 'Platform' src/types/spicetify.d.ts -B 1 -A 3

Repository: spicetify/marketplace

Length of output: 229


🏁 Script executed:

rg -n 'addToQueue' src/types/spicetify.d.ts -B 3 -A 3

Repository: spicetify/marketplace

Length of output: 252


Readiness gate checks a different queue API than the one used at runtime.

Line 23 checks for Spicetify.Platform.PlayerAPI.addToQueue, but the code at line 179 calls Spicetify.addToQueue (the global API). The types file defines only the global addToQueue() and does not type PlayerAPI. If only the global API is available, the readiness check will hang indefinitely. Gate on the API you actually call:

Proposed fix
                 if (
                     Spicetify?.Platform?.PlayerAPI?.getQueue &&
-                    Spicetify?.Platform?.PlayerAPI?.addToQueue &&
+                    Spicetify?.addToQueue &&
                     Spicetify?.Player?.addEventListener &&
                     Spicetify?.Player?.data !== undefined
                 ) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/extensions/queue-repeat/queue-repeat.js` around lines 22 - 24, The
readiness gate is checking Spicetify.Platform.PlayerAPI.addToQueue but the
runtime call uses the global Spicetify.addToQueue, so the gate can hang if only
the global API exists; update the readiness check to test the same API you call
at runtime (Spicetify.addToQueue) or check both places. Locate the readiness
predicate that references Spicetify?.Platform?.PlayerAPI?.addToQueue (and
Spicetify?.Platform?.PlayerAPI?.getQueue / Spicetify?.Player?.addEventListener)
and replace or augment the check to include Spicetify?.addToQueue (or prefer the
global Spicetify.addToQueue exclusively) so the gate passes when the actual
runtime API is present.

Comment on lines +121 to +141
async function pollForNewQueueTracks() {
if (!isActive || isPolling) return;

isPolling = true;
try {
const currentQueueUris = await getAllQueueTracks();
const repeatSet = new Set(repeatList);
const newUris = currentQueueUris.filter(uri => !repeatSet.has(uri));

if (newUris.length > 0) {
repeatList.push(...newUris);
log(`${newUris.length} new track(s) added to repeat list.`);
for (const uri of newUris) {
log(` + ${uri}`);
}
Spicetify.showNotification(
`Queue Repeat: ${newUris.length} new track(s) added`,
false,
1800
);
}
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 | ⚡ Quick win

Stale in-flight poll can update state after Queue Repeat is turned off.

If disable happens while Line 126 is awaiting getAllQueueTracks(), this invocation still reaches Lines 131-140 and can repopulate repeatList/show notifications after deactivation. Add a second active/session check right after the await and before mutating state.

Proposed fix
     async function pollForNewQueueTracks() {
         if (!isActive || isPolling) return;

         isPolling = true;
         try {
             const currentQueueUris = await getAllQueueTracks();
+            if (!isActive) return;
+
             const repeatSet        = new Set(repeatList);
             const newUris          = currentQueueUris.filter(uri => !repeatSet.has(uri));
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async function pollForNewQueueTracks() {
if (!isActive || isPolling) return;
isPolling = true;
try {
const currentQueueUris = await getAllQueueTracks();
const repeatSet = new Set(repeatList);
const newUris = currentQueueUris.filter(uri => !repeatSet.has(uri));
if (newUris.length > 0) {
repeatList.push(...newUris);
log(`${newUris.length} new track(s) added to repeat list.`);
for (const uri of newUris) {
log(` + ${uri}`);
}
Spicetify.showNotification(
`Queue Repeat: ${newUris.length} new track(s) added`,
false,
1800
);
}
async function pollForNewQueueTracks() {
if (!isActive || isPolling) return;
isPolling = true;
try {
const currentQueueUris = await getAllQueueTracks();
if (!isActive) return;
const repeatSet = new Set(repeatList);
const newUris = currentQueueUris.filter(uri => !repeatSet.has(uri));
if (newUris.length > 0) {
repeatList.push(...newUris);
log(`${newUris.length} new track(s) added to repeat list.`);
for (const uri of newUris) {
log(` + ${uri}`);
}
Spicetify.showNotification(
`Queue Repeat: ${newUris.length} new track(s) added`,
false,
1800
);
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/extensions/queue-repeat/queue-repeat.js` around lines 121 - 141, In
pollForNewQueueTracks(), after awaiting getAllQueueTracks() add a second guard
that checks isActive (and optionally isPolling/session state) before mutating
repeatList or showing notifications; if isActive is false, set isPolling back to
false and return early to avoid stale in-flight polls updating state — update
the logic around isActive/isPolling and repeatList so the await result is
ignored when the extension was disabled during the network call.

@kyrie25
Copy link
Copy Markdown
Member

kyrie25 commented May 3, 2026

This repository is not used to host user creations.

See https://github.com/spicetify/marketplace/wiki/Publishing-to-Marketplace

@kyrie25 kyrie25 closed this May 3, 2026
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