Skip to content

Address review feedback: Add CI, MIT license, template Info.plist, an…#6

Merged
VariableThe merged 2 commits into
mainfrom
feature/address-review-feedback
May 30, 2026
Merged

Address review feedback: Add CI, MIT license, template Info.plist, an…#6
VariableThe merged 2 commits into
mainfrom
feature/address-review-feedback

Conversation

@VariableThe

@VariableThe VariableThe commented May 30, 2026

Copy link
Copy Markdown
Owner

…d fix polling

Summary by CodeRabbit

  • New Features

    • Improved playback state detection using system notifications for faster track updates
    • Enhanced lyrics fetching with fallback support for multiple sources
  • Documentation

    • Clarified lyrics sources: Apple Music natively supported, with fallback to lrclib.net for all services
  • Chores

    • Added automated CI/CD pipelines for testing and releases

Review Change Stack

@coderabbitai

coderabbitai Bot commented May 30, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@VariableThe, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 51 minutes and 59 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, 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 include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 67c4aa90-ce36-4f47-8abb-ee7ca9f57c07

📥 Commits

Reviewing files that changed from the base of the PR and between 46563cc and 2d1a66b.

📒 Files selected for processing (2)
  • .github/workflows/release.yml
  • Sources/VinylApp.swift
📝 Walkthrough

Walkthrough

This PR adds continuous integration and release automation via GitHub Actions workflows, introduces an Info.plist template to replace inline XML generation, establishes MIT licensing, and refactors VinylApp's playback logic to respond to system notifications (Apple Music and Spotify) while simplifying the polling loop by extracting the core fetch and update logic into a reusable method.

Changes

Vinyl Application Build and Playback Updates

Layer / File(s) Summary
Build and release automation
.github/workflows/ci.yml, .github/workflows/release.yml
CI workflow runs swift build and swift test on push and pull requests to main. Release workflow triggers on v* tags, runs make app, zips Vinyl.app, and publishes the archive as a GitHub Release using softprops/action-gh-release@v1.
Application configuration and metadata
Info.plist.template, Makefile, LICENSE, README.md
Info.plist template with placeholders for app metadata and NSAppleEventsUsageDescription permission. Makefile now uses sed to substitute __APP_NAME__, __BUNDLE_ID__, and __VERSION__ into the template instead of echoing XML. MIT license added with 2026 copyright. README features section clarified to state Spotify lyrics are fetched exclusively via lrclib.net while Apple Music is queried natively first.
Playback state management and notification-driven updates
Sources/VinylApp.swift, Sources/MenuBarEngine.swift, Tests/VinylTests/VinylTests.swift
VinylApp maintains currentTrackKey as instance state and registers DistributedNotificationCenter observers for Apple Music and Spotify playback-state notifications. Each notification triggers an async performFetch() call that fetches current media state, detects track-key changes, manages artwork tasks, and attempts to load cached lyrics or fetch from LRCLIB with a fallback to Apple Music native lyrics (for Music player only). startPollingLoop() is simplified to repeatedly call performFetch(). MenuBarEngine no longer tracks needsScroll. Test testLyricLineCodable verifies LyricLine can be JSON-encoded and decoded for caching purposes.

Sequence Diagram

sequenceDiagram
  participant DistribNotif as DistributedNotificationCenter
  participant AppDelegate as AppDelegate
  participant PerformFetch as performFetch
  participant LyricFetch as LRCLIB / Apple Music
  participant MenuBar as MenuBarEngine
  
  DistribNotif->>AppDelegate: playerStateDidChange (Apple Music or Spotify)
  AppDelegate->>PerformFetch: async performFetch()
  PerformFetch->>PerformFetch: fetchCurrentState()
  PerformFetch->>PerformFetch: detect track key change
  PerformFetch->>LyricFetch: LRCLIB fetch (primary)
  LyricFetch-->>PerformFetch: lyrics or error
  PerformFetch->>LyricFetch: Apple Music native (fallback if Music player)
  LyricFetch-->>PerformFetch: native lyrics
  PerformFetch->>MenuBar: update(state, lyrics, artwork)
  MenuBar->>MenuBar: render scrolled title from lyrics
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Poem

🐰 A rabbit hops through CI gates,
With workflows that automate,
Plists from templates neatly sewn,
While notifications wake the app—
No more polling, just a snap! 🎵

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

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.
Title check ❓ Inconclusive The PR title is partially related to the changeset—it mentions adding CI, MIT license, and Info.plist template, which are present, but is truncated with "and…" and doesn't clearly convey the main focus or the polling improvements. Complete the title to fully capture the changes, such as: 'Add CI workflow, MIT license, Info.plist template, and refactor polling logic' to make the scope clearer.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
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
  • Commit unit tests in branch feature/address-review-feedback

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.

@VariableThe

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented May 30, 2026

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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

🧹 Nitpick comments (1)
Sources/VinylApp.swift (1)

80-82: ⚡ Quick win

Redundant cache write triggers unnecessary disk I/O.

When cached lyrics exist, calling setLyricsLoaded(cached, forKey: newTrackKey) re-writes the same lyrics to the cache and calls saveCacheToDisk() unnecessarily (see AppStateActor.swift:58-63).

♻️ Proposed fix

Update only the in-memory state without re-caching:

 if let cached = await stateActor.getCachedLyrics(forKey: newTrackKey) {
-    await stateActor.setLyricsLoaded(cached, forKey: newTrackKey)
+    await stateActor.setLyricsLoaded(cached)
 } else {

This requires adding a new method to AppStateActor that updates state without re-caching:

public func setLyricsLoaded(_ lyrics: [LyricLine]) {
    self.lyricsStatus = .loaded
    self.currentLyrics = lyrics
    // Skip cache write and disk save
}
🤖 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 `@Sources/VinylApp.swift` around lines 80 - 82, The current code calls
stateActor.setLyricsLoaded(cached, forKey: newTrackKey) even when cached lyrics
exist, causing redundant cache write and saveCacheToDisk(); change this to
update only in-memory state by adding a new AppStateActor method like
setLyricsLoaded(_ lyrics: [LyricLine]) that sets lyricsStatus and currentLyrics
without invoking caching or saveCacheToDisk, then replace the call to
stateActor.setLyricsLoaded(cached, forKey: newTrackKey) with
stateActor.setLyricsLoaded(cached) so cached reads do not trigger disk I/O.
🤖 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 @.github/workflows/release.yml:
- Around line 24-27: The workflow uses the deprecated action
softprops/action-gh-release@v1 (in the job step named "Create Release"); update
the step to use softprops/action-gh-release@v3 to avoid Node-16 runtime issues
on modern runners and ensure the existing input (files: Vinyl.zip) remains
unchanged—locate the step using the "name: Create Release" or the uses:
softprops/action-gh-release@v1 and replace the version tag with `@v3`.

In `@Sources/VinylApp.swift`:
- Around line 29-40: The DistributedNotificationCenter observers registered via
DistributedNotificationCenter.default().addObserver(...) for selector
`#selector`(playerStateDidChange) (names "com.apple.Music.playerInfo" and
"com.spotify.client.PlaybackStateChanged") are never removed, causing
AppDelegate to leak; fix by removing those observers when the app terminates or
when AppDelegate deinitializes—call
DistributedNotificationCenter.default().removeObserver(self) (or
removeObserver(self, name: NSNotification.Name("..."), object: nil) for each
specific name) from applicationWillTerminate(_:) or deinit of AppDelegate to
ensure the observers are unregistered and allow deallocation.
- Around line 85-121: The current untracked Task that fetches lyrics for a Track
can outlive the track and overwrite state for a newer track; replace it with a
cancellable tracked Task stored (e.g., lyricsFetchTask) on the surrounding scope
or actor, cancel any existing lyricsFetchTask when the track changes, and then
assign a new Task that performs the same fetch logic (calling
client.fetchLyrics, bridge.fetchAppleMusicLyrics, client.parseLyrics) and
updates state via stateActor.setLyricsLoaded / setLyricsError only if the Task
has not been cancelled; ensure you reference and cancel the previous
lyricsFetchTask before creating a new one and use Task.checkCancellation or
check Task.isCancelled before writing to state to avoid stale writes.
- Around line 43-47: playerStateDidChange spawns untracked concurrent Tasks
calling performFetch which leads to races on currentTrackKey and duplicated
network/artwork/lyrics work; introduce a stored Task? property (e.g.,
notificationFetchTask) on the same type and, inside playerStateDidChange, cancel
any existing notificationFetchTask before creating and assigning a new Task that
calls await performFetch(), and ensure performFetch (and the artwork/lyrics
sub-tasks it spawns) cooperatively handle cancellation (check Task.isCancelled
or use withTaskCancellationHandler) so stale fetches stop promptly and only one
notification-driven fetch runs at a time.

---

Nitpick comments:
In `@Sources/VinylApp.swift`:
- Around line 80-82: The current code calls stateActor.setLyricsLoaded(cached,
forKey: newTrackKey) even when cached lyrics exist, causing redundant cache
write and saveCacheToDisk(); change this to update only in-memory state by
adding a new AppStateActor method like setLyricsLoaded(_ lyrics: [LyricLine])
that sets lyricsStatus and currentLyrics without invoking caching or
saveCacheToDisk, then replace the call to stateActor.setLyricsLoaded(cached,
forKey: newTrackKey) with stateActor.setLyricsLoaded(cached) so cached reads do
not trigger disk I/O.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2cc7799b-3d42-45f7-a92a-c82dca0da172

📥 Commits

Reviewing files that changed from the base of the PR and between 6bd1c86 and 46563cc.

📒 Files selected for processing (9)
  • .github/workflows/ci.yml
  • .github/workflows/release.yml
  • Info.plist.template
  • LICENSE
  • Makefile
  • README.md
  • Sources/MenuBarEngine.swift
  • Sources/VinylApp.swift
  • Tests/VinylTests/VinylTests.swift
💤 Files with no reviewable changes (1)
  • Sources/MenuBarEngine.swift

Comment thread .github/workflows/release.yml
Comment thread Sources/VinylApp.swift
Comment thread Sources/VinylApp.swift
Comment thread Sources/VinylApp.swift Outdated
@VariableThe VariableThe merged commit f1344ba into main May 30, 2026
2 checks passed
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