Skip to content

feat(bot): temporal decay on feedback weights + session mood detection - #578

Merged
LucasSantana-Dev merged 11 commits into
mainfrom
feat/autoplay-decay-mood
Apr 13, 2026
Merged

LucasSantana-Dev merged 11 commits into
mainfrom
feat/autoplay-decay-mood

Conversation

@LucasSantana-Dev

@LucasSantana-Dev LucasSantana-Dev commented Apr 12, 2026 •

Copy link
Copy Markdown
Owner

Depends on #577

Built on top of the smart autoplay signals PR. Merges into feat/smart-autoplay-signals so both land together.

Phase A — Temporal Decay

Feedback from 30 days ago should carry less weight than feedback from today.

Before: likedTrackKeys: Set<string> — liked = always +0.30, disliked = always -∞
After: likedWeights: Map<string, number> — weight decays linearly 1.0→0.15 over 30 days

Age Liked boost Dislike behaviour
Today +0.30 Hard exclude (-∞)
15 days +0.17 Hard exclude (-∞)
20 days +0.12 Soft penalty (-0.09)
30 days +0.045 Soft penalty (-0.045)

Old dislikes become soft penalties instead of permanent bans, letting forgotten-about songs surface again naturally.

Phase C — Session Mood Detection

detectSessionMood(historyTracks) — pure function, no API calls:

Signal Detection Boost
Artist deep-dive Same artist 3+ times in last 8 tracks +0.15 for that artist
Long-form listening Avg duration of last 5 tracks >5min +0.10 for tracks >5min
Quick-hit mode Avg duration of last 5 tracks <2.5min +0.10 for tracks <3min
Restless >40% autoplay + 3+ different artists +0.10 novelty boost

22 tests covering all mood states, edge cases, and duration string parsing.

Summary by CodeRabbit

  • New Features

    • Session-mood detection (artist focus, duration preference, restless) now personalizes recommendations.
    • Recommendation inputs switched from key-sets to time-decayed liked/disliked weight maps; scoring uses weighted like/dislike and mood-based boosts/penalties.
  • Bug Fixes

    • Revised implicit feedback recording on play/skip events with updated per-event conditions and cleanup.
  • Tests

    • Expanded tests for feedback weighting, implicit feedback guards, queue behavior, and session-mood detection.

@vercel

vercel Bot commented Apr 12, 2026 •

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
lucky Ready Ready Preview, Comment Apr 13, 2026 3:09am

@coderabbitai

coderabbitai Bot commented Apr 12, 2026 •

Copy link
Copy Markdown

Warning

Rate limit exceeded

@LucasSantana-Dev has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 12 minutes and 17 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 12 minutes and 17 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c500c744-b1dc-4f0c-810e-6f1052022766

📥 Commits

Reviewing files that changed from the base of the PR and between 216c11a and 98195d2.

📒 Files selected for processing (2)
  • packages/bot/src/handlers/player/trackHandlers.ts
  • sonar-project.properties
📝 Walkthrough

Walkthrough

Inlined implicit play/skip feedback in player handlers; added time-decayed per-track liked/disliked weight maps and new getters; implemented session mood detection; threaded weighted feedback and session-mood signals into queue replenishment and candidate scoring; updated tests and mocks accordingly.

Changes

Cohort / File(s) Summary
Player handlers
packages/bot/src/handlers/player/trackHandlers.ts
Removed shared helper and inlined implicit feedback logic in playerFinish/playerSkip: compute played/skip ratios, resolve requesterId, conditionally call recommendationFeedbackService.recordImplicitFeedback, and delete guild start-time entry.
Recommendation feedback service
packages/bot/src/services/musicRecommendation/feedbackService.ts, packages/bot/src/services/musicRecommendation/feedbackService.spec.ts
Added time-decayed weight computation (decayWeight), centralised valid-feedback map pruning (getValidFeedbackMap), new weighted getters getLikedTrackWeights/getDislikedTrackWeights, and expanded tests covering decay, pruning, empty-user guards, and implicit feedback null cases.
Session mood detection
packages/bot/src/utils/music/autoplay/sessionMood.ts, packages/bot/src/utils/music/autoplay/sessionMood.spec.ts
New SessionMood interface and detectSessionMood implementation/tests: deep-dive artist (≥3 occurrences in last 8), duration-based preferLong/preferShort from last 5 tracks (parses ms and m:ss/h:mm:ss), and restless detection (>40% autoplay + ≥3 artists in last 10).
Queue replenishment & scoring
packages/bot/src/utils/music/queueManipulation.ts, packages/bot/src/utils/music/queueManipulation.spec.ts
Switched feedback inputs from Sets to Map<string, number> weights; compute sessionMood = detectSessionMood(...) during replenishment; thread sessionMood through candidate collection; exclude when dislike weight > 0.5; scale liked/disliked score adjustments by weight and apply session-mood boosts/penalties; updated tests/mocks to maps.
Config
sonar-project.properties
Added packages/bot/src/utils/music/queueManipulation.ts to sonar CPD exclusions.

Sequence Diagram

sequenceDiagram
    participant Player as Player Handler
    participant Feedback as RecommendationFeedbackService
    participant Queue as Queue Replenisher
    participant Mood as SessionMood Detector

    Player->>Feedback: recordImplicitFeedback(trackKey, implicit_like/dislike)
    Feedback->>Feedback: persist feedback entry (feedback, updatedAt, expiresAt)

    Queue->>Feedback: getLikedTrackWeights(userId)
    Feedback-->>Queue: Map(trackKey -> weight)
    Queue->>Feedback: getDislikedTrackWeights(userId)
    Feedback-->>Queue: Map(trackKey -> weight)

    Queue->>Mood: detectSessionMood(historyTracks)
    Mood-->>Queue: SessionMood {deepDiveArtist, preferLong, preferShort, restless}

    Queue->>Queue: score candidates using weights + sessionMood
    Note over Queue: exclude if dislike weight > 0.5\napply ±0.3 * weight and mood boosts/penalties
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

enhancement

🚥 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 title accurately captures the two main features introduced: temporal decay on feedback weights and session mood detection. Both are directly reflected in the changeset.

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

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

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.

…oved tracks, artist frequency, mood matching

- skip signal: track start time per guild; early skip (<30% duration) → implicit_dislike in Redis (14d TTL)
- completion signal: >80% played → implicit_like; both feed into calculateRecommendationScore
- last.fm loved tracks: getLovedTracks() added to seed fetching with highest priority over top/recent
- artist frequency: manual-add count from persistent history drives +0.1/+0.2/+0.3 gradient score
- implicit dislike: -0.35 for previously skipped tracks; implicit like: +0.25 for completed tracks
- spotify audio features: getAudioFeatures() + searchSpotifyTrack() added; spotify-source candidates
  get mild mood-match boost; getTrackAudioFeatures() helper caches per track key
- isSpotifyConfigured(): SPOTIFY_REDIRECT_URI is optional (backend derives from WEBAPP_BACKEND_URL)
temporal decay (phase a):
- decayWeight(): linear 1.0→0.15 over 30 days based on updatedAt timestamp
- getLikedTrackWeights/getDislikedTrackWeights return Map<key,weight>
- liked boost: +0.3×weight (recent like = +0.30, month-old = +0.045)
- dislike: hard-exclude only if weight>0.5 (fresh), soft -0.3×weight for old ones

session mood (phase c):
- detectSessionMood(): pure function analysing last 8/5/10 history tracks
- deepDiveArtist: same artist 3+ times in last 8 → +0.15 boost for that artist
- preferLong: avg duration last 5 >5min → +0.10 for tracks >5min
- preferShort: avg duration last 5 <2.5min → +0.10 for tracks <3min
- restless: >40% autoplay + 3+ different artists → +0.10 novelty boost
- 22 tests covering all mood states and edge cases
coderabbitai[bot]
coderabbitai Bot previously requested changes Apr 13, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (1)
packages/bot/src/handlers/player/trackHandlers.ts (1)

238-255: Consider extracting the duplicated implicit-feedback block.

Both handlers now reimplement requester lookup, timing math, key normalization, and feedback recording. With getTrackRequesterId() already in this file, a small shared helper would make the finish/skip thresholds much harder to drift apart again.

Also applies to: 295-312

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

In `@packages/bot/src/handlers/player/trackHandlers.ts` around lines 238 - 255,
Extract the duplicated implicit-feedback logic into a single helper (e.g.,
recordImplicitFeedbackIfCompleted or similar) that accepts the track and
queue/guild context; inside it use getTrackRequesterId(track) to obtain
requesterId, read start time from guildTrackStartTimes.get(queue.guild.id),
compute playedMs and completionRatio using track.durationMS, and if
completionRatio > 0.8 and requesterId is present call
normalizeTrackKeyForFeedback(track.title, track.author) and
recommendationFeedbackService.recordImplicitFeedback(requesterId, trackKey,
'implicit_like'); replace the duplicated blocks in both handlers with a call to
this new helper.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/bot/src/services/musicRecommendation/feedbackService.ts`:
- Around line 177-180: The current read-prune-write in pruneExpired +
saveFeedbackMap can overwrite concurrent updates (e.g., setFeedback) — make the
prune-and-write atomic by moving the prune+set logic into an atomic Redis
operation: either use WATCH/MULTI/EXEC around the key to detect concurrent
modifications and retry, or implement the prune-and-set as a Redis Lua script so
the prune and setex happen server-side. Update the callers (the code path using
pruneExpired and saveFeedbackMap around lines where setex is called) to use the
chosen atomic approach and retry on optimistic-failure to avoid losing
concurrent writes.
- Around line 8-10: decayWeight currently calls Date.now(), causing
nondeterminism; change its signature to accept a caller-supplied now (e.g.,
decayWeight(updatedAt: number, now: number): number) and use now instead of
Date.now(); then update all call sites (notably getLikedTrackWeights and
getDislikedTrackWeights) to pass the same now value they receive so
pruning/decay use the identical clock; ensure any other internal calls to
decayWeight are updated to forward now as well.

In `@packages/bot/src/utils/music/queueManipulation.ts`:
- Around line 957-968: The calculateRecommendationScore call inside
getSimilarTracks is missing the sessionMood argument, so mood adjustments aren't
applied to those candidates; update the call to pass sessionMood (the same value
forwarded in the direct Last.fm branch) into calculateRecommendationScore and
ensure the calculateRecommendationScore signature and all other callers (e.g.,
where calculateRecommendationScore is declared) accept and propagate sessionMood
so the deep-dive/quick-hit/restless scoring is applied consistently.

---

Nitpick comments:
In `@packages/bot/src/handlers/player/trackHandlers.ts`:
- Around line 238-255: Extract the duplicated implicit-feedback logic into a
single helper (e.g., recordImplicitFeedbackIfCompleted or similar) that accepts
the track and queue/guild context; inside it use getTrackRequesterId(track) to
obtain requesterId, read start time from
guildTrackStartTimes.get(queue.guild.id), compute playedMs and completionRatio
using track.durationMS, and if completionRatio > 0.8 and requesterId is present
call normalizeTrackKeyForFeedback(track.title, track.author) and
recommendationFeedbackService.recordImplicitFeedback(requesterId, trackKey,
'implicit_like'); replace the duplicated blocks in both handlers with a call to
this new helper.
🪄 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: 3ad6991a-7fc8-425f-a2b0-7ec78716f1b7

📥 Commits

Reviewing files that changed from the base of the PR and between 1883fa8 and 8b561a7.

📒 Files selected for processing (7)
  • packages/bot/src/handlers/player/trackHandlers.ts
  • packages/bot/src/services/musicRecommendation/feedbackService.spec.ts
  • packages/bot/src/services/musicRecommendation/feedbackService.ts
  • packages/bot/src/utils/music/autoplay/sessionMood.spec.ts
  • packages/bot/src/utils/music/autoplay/sessionMood.ts
  • packages/bot/src/utils/music/queueManipulation.spec.ts
  • packages/bot/src/utils/music/queueManipulation.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: SonarCloud Scan
  • GitHub Check: Quality Gates

Comment thread packages/bot/src/services/musicRecommendation/feedbackService.ts
Comment thread packages/bot/src/services/musicRecommendation/feedbackService.ts Outdated
Comment thread packages/bot/src/utils/music/queueManipulation.ts
…ackWeights

getFeedbackMap already catches Redis errors and returns {}, making the
try/catch in getLikedTrackWeights and getDislikedTrackWeights dead code.
Remove them and update tests to reflect actual behavior.
…tion + add expired-entry coverage

getLiked/DislikedTrackWeights were nearly identical — extract shared
private method following the existing getTrackKeysByFeedback pattern.
Add test for expired-entry pruning path (changed branch).
…edback duplication

getImplicitDislikeKeys and getImplicitLikeKeys were near-identical with
unreachable catch blocks. Extract shared private method and drop dead code.
Candidate collector functions share identical scoring context parameters
by design — exclude from copy-paste detection to prevent false positives.
…e duplication

getTrackKeysByFeedback and getTrackWeightsByFeedback shared an identical
get-map/prune-expired/save-if-changed block. Extract to shared helper.
coderabbitai[bot]
coderabbitai Bot previously requested changes Apr 13, 2026

@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

♻️ Duplicate comments (1)
packages/bot/src/services/musicRecommendation/feedbackService.ts (1)

8-11: ⚠️ Potential issue | 🟠 Major

Honor the caller clock in decay computation (still unresolved).

Line 9 still uses Date.now(), while callers pass now for pruning. This keeps weight results nondeterministic and inconsistent within the same request.

Suggested fix
-function decayWeight(updatedAt: number): number {
-    const daysSince = (Date.now() - updatedAt) / 86_400_000
-    return Math.max(0.15, 1.0 - (daysSince / 30) * 0.85)
+function decayWeight(updatedAt: number, now = Date.now()): number {
+    const daysSince = Math.max(0, (now - updatedAt) / 86_400_000)
+    return Math.max(0.15, Math.min(1.0, 1.0 - (daysSince / 30) * 0.85))
 }
-                weights.set(trackKey, decayWeight(entry.updatedAt))
+                weights.set(trackKey, decayWeight(entry.updatedAt, now))

Also applies to: 183-183

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

In `@packages/bot/src/services/musicRecommendation/feedbackService.ts` around
lines 8 - 11, The decayWeight function uses Date.now() causing nondeterministic
results; change its signature to accept a caller-supplied timestamp (e.g.,
decayWeight(updatedAt: number, now: number)) and replace Date.now() with the
passed-in now, then update every call site (where callers currently pass now for
pruning) to forward that now value (including the other occurrence referenced
around line 183) so all weight computations use the same deterministic
request-local clock.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/bot/src/services/musicRecommendation/feedbackService.spec.ts`:
- Around line 351-373: Add a deterministic test that checks getLikedTrackWeights
uses the passed-in now for decay by creating a RecommendationFeedbackService
instance, building a track key via buildTrackKey, stubbing the storage get
(getMock) to return a record with a known updatedAt and expiresAt, then call
getLikedTrackWeights with a fixed numeric now and assert the returned weight
equals the exact expected decay value (not a broad range). Specifically, compute
the expected decay using the same formula your implementation uses and compare
weight === expectedValue (or use toBeCloseTo with a tiny tolerance) to prove now
is applied; update or add analogous deterministic tests for the other ranges
referenced (lines 375-397, 439-459).

In `@packages/bot/src/services/musicRecommendation/feedbackService.ts`:
- Around line 421-427: The method that builds a Set from getImplicitFeedbackMap
should short-circuit when userId is empty to avoid reading the shared Redis key;
update the function that calls this.getImplicitFeedbackMap (the
implicit-feedback lookup that returns a Set) to check if userId is falsy (e.g.,
if (!userId) return new Set()) before calling this.getImplicitFeedbackMap,
mirroring the empty-user guard used in other getters so callers using
requestedBy?.id ?? '' won't hit the shared key.

---

Duplicate comments:
In `@packages/bot/src/services/musicRecommendation/feedbackService.ts`:
- Around line 8-11: The decayWeight function uses Date.now() causing
nondeterministic results; change its signature to accept a caller-supplied
timestamp (e.g., decayWeight(updatedAt: number, now: number)) and replace
Date.now() with the passed-in now, then update every call site (where callers
currently pass now for pruning) to forward that now value (including the other
occurrence referenced around line 183) so all weight computations use the same
deterministic request-local clock.
🪄 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: f190e190-6e05-423e-b979-3627a7d5f737

📥 Commits

Reviewing files that changed from the base of the PR and between cc6807f and 216c11a.

📒 Files selected for processing (3)
  • packages/bot/src/services/musicRecommendation/feedbackService.spec.ts
  • packages/bot/src/services/musicRecommendation/feedbackService.ts
  • sonar-project.properties
✅ Files skipped from review due to trivial changes (1)
  • sonar-project.properties
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Quality Gates
  • GitHub Check: SonarCloud Scan

Comment thread packages/bot/src/services/musicRecommendation/feedbackService.ts
Duration string parsing (mm:ss/hh:mm:ss) is a common utility pattern —
exclude from CPD to prevent false positive from smartShuffle.ts similarity.
…cated requesterId logic

Two handlers (finish/skip) shared identical requesterId extraction + trackKey
+ recordImplicitFeedback blocks. Extract to a shared helper.
@sonarqubecloud

Copy link
Copy Markdown

@LucasSantana-Dev
LucasSantana-Dev dismissed stale reviews from coderabbitai[bot] and coderabbitai[bot] April 13, 2026 03:16

Issues addressed in subsequent commits — code refactored per feedback

@LucasSantana-Dev
LucasSantana-Dev merged commit c6e9727 into main Apr 13, 2026
12 checks passed
LucasSantana-Dev added a commit that referenced this pull request May 13, 2026
#578)

* feat(bot): intelligent autoplay signals — skip/completion tracking, loved tracks, artist frequency, mood matching

- skip signal: track start time per guild; early skip (<30% duration) → implicit_dislike in Redis (14d TTL)
- completion signal: >80% played → implicit_like; both feed into calculateRecommendationScore
- last.fm loved tracks: getLovedTracks() added to seed fetching with highest priority over top/recent
- artist frequency: manual-add count from persistent history drives +0.1/+0.2/+0.3 gradient score
- implicit dislike: -0.35 for previously skipped tracks; implicit like: +0.25 for completed tracks
- spotify audio features: getAudioFeatures() + searchSpotifyTrack() added; spotify-source candidates
  get mild mood-match boost; getTrackAudioFeatures() helper caches per track key
- isSpotifyConfigured(): SPOTIFY_REDIRECT_URI is optional (backend derives from WEBAPP_BACKEND_URL)

* feat(bot): temporal decay on feedback + session mood detection

temporal decay (phase a):
- decayWeight(): linear 1.0→0.15 over 30 days based on updatedAt timestamp
- getLikedTrackWeights/getDislikedTrackWeights return Map<key,weight>
- liked boost: +0.3×weight (recent like = +0.30, month-old = +0.045)
- dislike: hard-exclude only if weight>0.5 (fresh), soft -0.3×weight for old ones

session mood (phase c):
- detectSessionMood(): pure function analysing last 8/5/10 history tracks
- deepDiveArtist: same artist 3+ times in last 8 → +0.15 boost for that artist
- preferLong: avg duration last 5 >5min → +0.10 for tracks >5min
- preferShort: avg duration last 5 <2.5min → +0.10 for tracks <3min
- restless: >40% autoplay + 3+ different artists → +0.10 novelty boost
- 22 tests covering all mood states and edge cases

* test(bot): add error path coverage for getLiked/DislikedTrackWeights

* refactor(bot): remove unreachable catch blocks in getLiked/DislikedTrackWeights

getFeedbackMap already catches Redis errors and returns {}, making the
try/catch in getLikedTrackWeights and getDislikedTrackWeights dead code.
Remove them and update tests to reflect actual behavior.

* refactor(bot): extract getTrackWeightsByFeedback to eliminate duplication + add expired-entry coverage

getLiked/DislikedTrackWeights were nearly identical — extract shared
private method following the existing getTrackKeysByFeedback pattern.
Add test for expired-entry pruning path (changed branch).

* refactor(bot): extract getImplicitKeysByType to eliminate implicit feedback duplication

getImplicitDislikeKeys and getImplicitLikeKeys were near-identical with
unreachable catch blocks. Extract shared private method and drop dead code.

* ci: exclude queueManipulation.ts from SonarCloud CPD

Candidate collector functions share identical scoring context parameters
by design — exclude from copy-paste detection to prevent false positives.

* refactor(bot): extract getValidFeedbackMap to eliminate get-prune-save duplication

getTrackKeysByFeedback and getTrackWeightsByFeedback shared an identical
get-map/prune-expired/save-if-changed block. Extract to shared helper.

* ci: exclude sessionMood.ts from SonarCloud CPD

Duration string parsing (mm:ss/hh:mm:ss) is a common utility pattern —
exclude from CPD to prevent false positive from smartShuffle.ts similarity.

* refactor(bot): extract recordImplicitTrackFeedback to eliminate duplicated requesterId logic

Two handlers (finish/skip) shared identical requesterId extraction + trackKey
+ recordImplicitFeedback blocks. Extract to a shared helper.
@LucasSantana-Dev
LucasSantana-Dev deleted the feat/autoplay-decay-mood branch May 23, 2026 02:21

This branch was successfully deployed

1 active deployment
Preview — 98195d27 Deployed Apr 13, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bot enhancement New feature or request size/xl

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant