Skip to content

fix: CodeRabbit auto-fixes for PR #78 - #79

Merged
Mag1cByt3s merged 1 commit into
sillybackgroundtestfrom
coderabbitai/autofix/2fffe71
Jul 30, 2026
Merged

fix: CodeRabbit auto-fixes for PR #78#79
Mag1cByt3s merged 1 commit into
sillybackgroundtestfrom
coderabbitai/autofix/2fffe71

Conversation

@coderabbitai

@coderabbitai coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

This stacked PR contains CodeRabbit auto-fixes for #78.

Files modified:

  • Twinskaraoke/Components/Shimeji/ShimejiSpriteView.swift
  • Twinskaraoke/Features/Account/SettingsView.swift
  • Twinskaraoke/Features/Account/ShimejiSettingsView.swift
  • Twinskaraoke/Services/Shimeji/ShimejiEngine.swift
  • Twinskaraoke/Services/Shimeji/ShimejiResourceManager.swift
  • Twinskaraoke/Services/Shimeji/ShimejiZipReader.swift
  • Twinskaraoke/Theme/AuroraBackgroundViews.swift
  • TwinskaraokeShared/Localization/Localizable.xcstrings

Summary by Sourcery

Improve robustness and safety of Shimeji asset downloading, extraction, and rendering while refining related UI behavior.

New Features:

  • Add explicit download control for the Shimeji pack in settings, replacing automatic download on view appearance.
  • Introduce the ability to cancel an in-progress Shimeji pack download when the feature is disabled.

Bug Fixes:

  • Prevent unsafe zip extraction paths to avoid zip-slip vulnerabilities when unpacking Shimeji assets.
  • Ensure Shimeji instances respect non-negative maximum counts and normalize animation frame indices when sequences change.
  • Delay Shimeji spawning until valid bounds are available to avoid layout issues on initial start.
  • Clear cached sprite images after pack extraction or removal to avoid stale graphics being displayed.
  • Handle failed Shimeji pack downloads more reliably by validating HTTP responses and non-nil temporary files.

Enhancements:

  • Configure Shimeji pack downloads with a longer resource timeout suitable for large assets.
  • Limit in-memory sprite image caching with count and size caps to reduce memory usage.
  • Align sprite view sizing with the shared ShimejiEngine display size constant for consistency.
  • Make floating cloud background colors respond to the current color scheme instead of a fixed dark scheme.
  • Prepare for future SHA-256 checksum validation of the Shimeji pack download.

Fixed 8 file(s) based on 13 unresolved review comments.

Co-authored-by: CodeRabbit <noreply@coderabbit.ai>
@coderabbitai
coderabbitai Bot requested a review from ytsodacan July 30, 2026 08:43
@coderabbitai coderabbitai Bot mentioned this pull request Jul 30, 2026
@sourcery-ai

sourcery-ai Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Reviewer's Guide

Refines Shimeji download management, safety, caching, and animation behavior, adjusts related settings UI flow, and makes theme clouds respect the active color scheme.

Sequence diagram for the updated Shimeji download and cancellation flow

sequenceDiagram
    actor User
    participant SettingsView
    participant ShimejiSettingsView
    participant ShimejiResourceManager
    participant URLSession
    participant ShimejiZipReader
    participant ShimejiSpriteView

    User->>SettingsView: toggleShimeji.onChange(newValue=true)
    SettingsView->>ShimejiResourceManager: download()
    ShimejiResourceManager->>URLSession: downloadTask(with: packURL)
    URLSession-->>ShimejiResourceManager: handleDownloadCompletion(tempURL,response,error)
    ShimejiResourceManager->>ShimejiZipReader: extractAll(from: tempURL,to: packDirectory)
    ShimejiZipReader-->>ShimejiResourceManager: manifest
    ShimejiResourceManager->>ShimejiSpriteView: invalidateImageCache()

    alt User cancels while downloading
        User->>SettingsView: toggleShimeji.onChange(newValue=false)
        SettingsView->>ShimejiResourceManager: cancelDownload()
        ShimejiResourceManager->>URLSession: downloadTask.cancel()
        ShimejiResourceManager->>ShimejiResourceManager: state = notDownloaded
    end

    alt User manually starts download in ShimejiSettingsView
        User->>ShimejiSettingsView: tap Download button
        ShimejiSettingsView->>ShimejiResourceManager: download()
    end
Loading

File-Level Changes

Change Details Files
Harden Shimeji pack download handling and add cache invalidation hooks.
  • Add placeholder expected SHA-256 checksum constant and comments for future verification.
  • Configure URLSession with a longer resource timeout and pass URLResponse through to completion handler.
  • Introduce cancelDownload() to cleanly cancel downloads, clear observers, and reset state.
  • Validate HTTP status codes before treating downloads as successful and improve error messaging.
  • Invalidate sprite image cache on successful extraction and when deleting the pack.
Twinskaraoke/Services/Shimeji/ShimejiResourceManager.swift
Twinskaraoke/Components/Shimeji/ShimejiSpriteView.swift
Improve Shimeji animation engine robustness and spawning behavior.
  • Defer spawning when bounds are zero and respawn once bounds are initialized, wiring this through bounds didSet.
  • Trigger floor target recalculation when bounds or navBarY change, guarding against redundant work.
  • Clamp max instance count to a non-negative value before enforcing the upper limit.
  • Reset frameTimer whenever frameIndex is normalized or forced to zero, and guard against out-of-range frameIndex when action changes.
Twinskaraoke/Services/Shimeji/ShimejiEngine.swift
Add zip-slip protections when extracting Shimeji packs.
  • Track canonical destination directory path and the canonical path for each extracted file.
  • Validate archive paths to reject absolute paths and any ".." components.
  • Ensure each resolved file path stays within the destination directory, otherwise fail with a dedicated ZipError.unsafePath.
Twinskaraoke/Services/Shimeji/ShimejiZipReader.swift
Constrain Shimeji sprite image caching and expose an invalidation API.
  • Wrap NSCache initialization to set countLimit and totalCostLimit for the cache.
  • Expose a removeAll() method on the cache singleton to clear all cached images.
  • Add a static invalidateImageCache() on the view that delegates to the cache singleton.
  • Use ShimejiEngine.displaySize as the single source of truth for sprite sizing and positioning.
Twinskaraoke/Components/Shimeji/ShimejiSpriteView.swift
Twinskaraoke/Services/Shimeji/ShimejiEngine.swift
Refine Shimeji settings UI and download control flow.
  • Replace automatic pack download on Shimeji settings appear with an explicit "Download" button that triggers haptics.
  • Update main Settings toggle binding to cancel any in-progress Shimeji download when toggled off while still starting a download when toggled on from notDownloaded.
Twinskaraoke/Features/Account/ShimejiSettingsView.swift
Twinskaraoke/Features/Account/SettingsView.swift
Make aurora floating clouds respond to the active color scheme.
  • Inject ColorScheme from the environment instead of a fixed dark scheme.
  • Pass the dynamic colorScheme into Theme.ellipses* helpers for all cloud instances.
Twinskaraoke/Theme/AuroraBackgroundViews.swift

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor Author

Important

Review skipped

This PR was authored by the user configured for CodeRabbit reviews. CodeRabbit does not review PRs authored by this user. It's recommended to use a dedicated user account to post CodeRabbit review feedback.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 23169074-d472-49ea-b631-033d4846e805

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Comment @coderabbitai help to get the list of available commands.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Hey - I've found 1 issue

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="Twinskaraoke/Components/Shimeji/ShimejiSpriteView.swift" line_range="15" />
<code_context>
+        init() {
+            cache = NSCache<NSString, UIImage>()
+            cache.countLimit = 200
+            cache.totalCostLimit = 50 * 1024 * 1024 // 50 MB
+        }

</code_context>
<issue_to_address>
**suggestion (performance):** Total cost limit on NSCache is ineffective without providing per-object costs.

`image(at:)` uses `cache.setObject(image, forKey: key)` without a `cost`, so `totalCostLimit` won’t affect eviction. To make the 50 MB cap meaningful, estimate each `UIImage`’s memory usage (e.g. pixels * bytes per pixel) and pass that to `setObject(_:forKey:cost:)` so NSCache can evict based on actual cost.

Suggested implementation:

```
    /// re-read the same handful of small PNGs from disk every cycle.
    private final class ShimejiImageCache {
        static let shared = ShimejiImageCache()
        private let cache: NSCache<NSString, UIImage>

        init() {
            cache = NSCache<NSString, UIImage>()
            cache.countLimit = 200
            cache.totalCostLimit = 50 * 1024 * 1024 // 50 MB
        }

```

```
        func image(at url: URL) -> UIImage? {
            let key = url.path as NSString

            // Assuming `image` is created/loaded earlier in this method.
            // Compute an approximate memory cost so `totalCostLimit` is effective.
            if let image = image {
                let scale = image.scale
                let pixelsWide = image.size.width * scale
                let pixelsHigh = image.size.height * scale
                let bytesPerPixel: CGFloat = 4 // RGBA8
                let cost = Int(pixelsWide * pixelsHigh * bytesPerPixel)

                cache.setObject(image, forKey: key, cost: cost)
                return image
            }

            return nil

```

1. Ensure that within `image(at:)` you have logic that actually loads or retrieves a `UIImage` into a local variable named `image` before the `if let image = image` block (e.g. from disk or another cache).
2. If the method previously returned a cached image early (e.g. `if let cached = cache.object(forKey: key) { return cached }`), keep that logic intact above this new block so you avoid reloading images unnecessarily.
3. If you prefer different cost semantics (e.g. using `cgImage.bytesPerRow * cgImage.height` when available), you can adjust the cost calculation accordingly but still pass it as the `cost:` parameter to `setObject(_:forKey:cost:)`.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

init() {
cache = NSCache<NSString, UIImage>()
cache.countLimit = 200
cache.totalCostLimit = 50 * 1024 * 1024 // 50 MB

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

suggestion (performance): Total cost limit on NSCache is ineffective without providing per-object costs.

image(at:) uses cache.setObject(image, forKey: key) without a cost, so totalCostLimit won’t affect eviction. To make the 50 MB cap meaningful, estimate each UIImage’s memory usage (e.g. pixels * bytes per pixel) and pass that to setObject(_:forKey:cost:) so NSCache can evict based on actual cost.

Suggested implementation:

    /// re-read the same handful of small PNGs from disk every cycle.
    private final class ShimejiImageCache {
        static let shared = ShimejiImageCache()
        private let cache: NSCache<NSString, UIImage>

        init() {
            cache = NSCache<NSString, UIImage>()
            cache.countLimit = 200
            cache.totalCostLimit = 50 * 1024 * 1024 // 50 MB
        }

        func image(at url: URL) -> UIImage? {
            let key = url.path as NSString

            // Assuming `image` is created/loaded earlier in this method.
            // Compute an approximate memory cost so `totalCostLimit` is effective.
            if let image = image {
                let scale = image.scale
                let pixelsWide = image.size.width * scale
                let pixelsHigh = image.size.height * scale
                let bytesPerPixel: CGFloat = 4 // RGBA8
                let cost = Int(pixelsWide * pixelsHigh * bytesPerPixel)

                cache.setObject(image, forKey: key, cost: cost)
                return image
            }

            return nil

  1. Ensure that within image(at:) you have logic that actually loads or retrieves a UIImage into a local variable named image before the if let image = image block (e.g. from disk or another cache).
  2. If the method previously returned a cached image early (e.g. if let cached = cache.object(forKey: key) { return cached }), keep that logic intact above this new block so you avoid reloading images unnecessarily.
  3. If you prefer different cost semantics (e.g. using cgImage.bytesPerRow * cgImage.height when available), you can adjust the cost calculation accordingly but still pass it as the cost: parameter to setObject(_:forKey:cost:).

@Mag1cByt3s

Copy link
Copy Markdown
Collaborator

Hey - I've found 1 issue

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="Twinskaraoke/Components/Shimeji/ShimejiSpriteView.swift" line_range="15" />
<code_context>
+        init() {
+            cache = NSCache<NSString, UIImage>()
+            cache.countLimit = 200
+            cache.totalCostLimit = 50 * 1024 * 1024 // 50 MB
+        }

</code_context>
<issue_to_address>
**suggestion (performance):** Total cost limit on NSCache is ineffective without providing per-object costs.

`image(at:)` uses `cache.setObject(image, forKey: key)` without a `cost`, so `totalCostLimit` won’t affect eviction. To make the 50 MB cap meaningful, estimate each `UIImage`’s memory usage (e.g. pixels * bytes per pixel) and pass that to `setObject(_:forKey:cost:)` so NSCache can evict based on actual cost.

Suggested implementation:

```
    /// re-read the same handful of small PNGs from disk every cycle.
    private final class ShimejiImageCache {
        static let shared = ShimejiImageCache()
        private let cache: NSCache<NSString, UIImage>

        init() {
            cache = NSCache<NSString, UIImage>()
            cache.countLimit = 200
            cache.totalCostLimit = 50 * 1024 * 1024 // 50 MB
        }

```

```
        func image(at url: URL) -> UIImage? {
            let key = url.path as NSString

            // Assuming `image` is created/loaded earlier in this method.
            // Compute an approximate memory cost so `totalCostLimit` is effective.
            if let image = image {
                let scale = image.scale
                let pixelsWide = image.size.width * scale
                let pixelsHigh = image.size.height * scale
                let bytesPerPixel: CGFloat = 4 // RGBA8
                let cost = Int(pixelsWide * pixelsHigh * bytesPerPixel)

                cache.setObject(image, forKey: key, cost: cost)
                return image
            }

            return nil

```

1. Ensure that within `image(at:)` you have logic that actually loads or retrieves a `UIImage` into a local variable named `image` before the `if let image = image` block (e.g. from disk or another cache).
2. If the method previously returned a cached image early (e.g. `if let cached = cache.object(forKey: key) { return cached }`), keep that logic intact above this new block so you avoid reloading images unnecessarily.
3. If you prefer different cost semantics (e.g. using `cgImage.bytesPerRow * cgImage.height` when available), you can adjust the cost calculation accordingly but still pass it as the `cost:` parameter to `setObject(_:forKey:cost:)`.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@coderabbitai please rework your PR to fix issues found by sourcery-ai

@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor Author

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

ast-grep outline Twinskaraoke/Components/Shimeji/ShimejiSpriteView.swift --items all --view expanded
printf '\n--- relevant cache implementation ---\n'
rg -n -C 12 'ShimejiImageCache|func image\s*\(|setObject\s*\(' Twinskaraoke/Components/Shimeji/ShimejiSpriteView.swift
printf '\n--- cache references and tests ---\n'
rg -n -C 4 'ShimejiImageCache|ShimejiSpriteView' -g '*.swift' .

Length of output: 11724


@Mag1cByt3s, confirmed—the Sourcery finding is valid. The cache currently sets a 50 MiB cost limit but inserts images without a cost, so that limit cannot budget decoded image memory. I’ve scoped the rework to the sprite cache and will preserve its cache-hit and disk-loading behavior.

  • Make each cached Shimeji UIImage contribute an image-derived cost to NSCache.
  • Prefer the decoded CGImage backing-store size and fall back to a scale-adjusted RGBA estimate when needed.
  • Keep the existing countLimit, early cache hit, missing-file behavior, and cache invalidation unchanged.

  • ✅ Pull request created - (🔄 Check again to retry)

You are interacting with an AI system.

@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor Author

✅ The requested changes have been implemented and a pull request has been created: View PR

@Mag1cByt3s
Mag1cByt3s merged commit 77fc399 into sillybackgroundtest Jul 30, 2026
4 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