Fix SwiftUI onChange performance warning with debouncing#11
Conversation
This commit addresses two critical issues: 1. **Shell Compatibility**: Fixed Execute.command to support zsh users - Previously hardcoded `source ~/.bash_profile` which failed for zsh users - Now directly sources Conda initialization from common installation paths - Avoids Oh My Zsh errors when running from bash scripts - Supports multiple Conda distributions: miniforge, miniconda, anaconda 2. **MLX Server Migration**: Replaced deprecated mlxserver with mlx-lm - mlxserver (v0.1.14) is incompatible with current mlx-lm (v0.29.1) - mlxserver had ImportError: cannot import 'generate_step' from mlx_lm.utils - Migrated to mlx-lm's built-in server which is actively maintained - Updated Dependencies.plist for both Conda and pip platforms - Updated server.py to use 'python -m mlx_lm server' command - Maintains backward compatibility with existing model/port arguments Benefits: - Eliminates "No such file or directory" errors for zsh users - Fixes ImportError crashes when starting MLX servers - Uses officially supported and maintained MLX server implementation - Provides OpenAI-compatible API endpoints - No additional dependencies needed (mlx-lm includes server) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Resolves issue where onChange(of: String) was updating multiple times per frame when server logs were being appended rapidly. Implemented debouncing pattern that batches rapid log updates before triggering scroll actions. Changes: - Added scrollTask state variable to track debounce tasks - Modified onChange handlers to cancel pending tasks before creating new ones - Added 50ms delay to batch rapid updates - Applied fix to both ServerLogView and SetupView Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Summary of ChangesHello @TinyTb, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request significantly enhances the application's stability and performance by addressing a SwiftUI warning caused by rapid log updates, ensuring a smoother user experience. It also modernizes the backend MLX server integration by switching to the Highlights
🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console. Changelog
Activity
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request effectively addresses a SwiftUI performance warning by implementing a debouncing mechanism for log updates in ServerLogView and SetupView. The changes also include updating dependency configurations from mlxserver to mlx-lm and improving the Conda initialization in a bash script. My review includes suggestions to improve code maintainability by refactoring a repetitive bash script block and duplicated Swift code. I've also identified several inconsistencies in the Dependencies.plist file, such as incorrect dependency names, versions, and metadata, which should be addressed to prevent potential issues.
| <key>versionCommands</key> | ||
| <array> | ||
| <string>pip show mlxserver</string> | ||
| <string>pip show mlx-lm</string> |
| <key>installCommands</key> | ||
| <array> | ||
| <string>python3 pip install mlxserver</string> | ||
| <string>pip install mlx-lm</string> |
There was a problem hiding this comment.
| if [ -f "$HOME/miniforge3/etc/profile.d/conda.sh" ]; then | ||
| source "$HOME/miniforge3/etc/profile.d/conda.sh" | ||
| elif [ -f "$HOME/miniconda3/etc/profile.d/conda.sh" ]; then | ||
| source "$HOME/miniconda3/etc/profile.d/conda.sh" | ||
| elif [ -f "$HOME/anaconda3/etc/profile.d/conda.sh" ]; then | ||
| source "$HOME/anaconda3/etc/profile.d/conda.sh" | ||
| elif [ -f "/opt/homebrew/Caskroom/miniforge/base/etc/profile.d/conda.sh" ]; then | ||
| source "/opt/homebrew/Caskroom/miniforge/base/etc/profile.d/conda.sh" | ||
| fi |
There was a problem hiding this comment.
This if/elif chain can be refactored to use a loop over an array of paths. This would make the code cleaner, more readable, and easier to extend with new paths in the future.
| if [ -f "$HOME/miniforge3/etc/profile.d/conda.sh" ]; then | |
| source "$HOME/miniforge3/etc/profile.d/conda.sh" | |
| elif [ -f "$HOME/miniconda3/etc/profile.d/conda.sh" ]; then | |
| source "$HOME/miniconda3/etc/profile.d/conda.sh" | |
| elif [ -f "$HOME/anaconda3/etc/profile.d/conda.sh" ]; then | |
| source "$HOME/anaconda3/etc/profile.d/conda.sh" | |
| elif [ -f "/opt/homebrew/Caskroom/miniforge/base/etc/profile.d/conda.sh" ]; then | |
| source "/opt/homebrew/Caskroom/miniforge/base/etc/profile.d/conda.sh" | |
| fi | |
| conda_paths=( | |
| "$HOME/miniforge3/etc/profile.d/conda.sh" | |
| "$HOME/miniconda3/etc/profile.d/conda.sh" | |
| "$HOME/anaconda3/etc/profile.d/conda.sh" | |
| "/opt/homebrew/Caskroom/miniforge/base/etc/profile.d/conda.sh" | |
| ) | |
| for conda_path in "${conda_paths[@]}"; do | |
| if [ -f "$conda_path" ]; then | |
| source "$conda_path" | |
| break | |
| fi | |
| done |
| <key>comment</key> | ||
| <string>MLX installed using conda-forge</string> | ||
| <string>MLX Language Model library with built-in server support</string> | ||
| <key>url</key> | ||
| <string>https://ml-explore.github.io/mlx/build/html/install.html</string> | ||
| <string>https://github.com/ml-explore/mlx-lm</string> |
There was a problem hiding this comment.
The updated comment and URL here seem to describe mlx-lm, but this dependency entry is for the core mlx framework. This could be confusing. Please use the appropriate comment and URL for mlx.
<key>comment</key>
<string>An array framework for machine learning on Apple silicon.</string>
<key>url</key>
<string>https://ml-explore.github.io/mlx/build/html/install.html</string>
| .onChange(of: consoleLog) { _, _ in | ||
| proxy.scrollTo("textID", anchor: .bottom) | ||
| // Cancel previous scroll task to debounce rapid updates | ||
| scrollTask?.cancel() | ||
| scrollTask = Task { | ||
| // Small delay to batch rapid log updates | ||
| try? await Task.sleep(for: .milliseconds(50)) | ||
| guard !Task.isCancelled else { return } | ||
| proxy.scrollTo("textID", anchor: .bottom) | ||
| } | ||
| } |
There was a problem hiding this comment.
This debouncing logic is also present in ServerLogView.swift. To avoid code duplication and improve maintainability, consider extracting this logic into a custom ViewModifier.
For example, you could define the modifier and an extension on View:
struct DebouncedScrollOnChangeModifier<Value: Equatable>: ViewModifier {
let value: Value
let proxy: ScrollViewProxy
let id: any Hashable
let anchor: UnitPoint?
let duration: Duration
@State private var scrollTask: Task<Void, Never>?
func body(content: Content) -> some View {
content.onChange(of: value) { _, _ in
scrollTask?.cancel()
scrollTask = Task {
try? await Task.sleep(for: duration)
guard !Task.isCancelled else { return }
proxy.scrollTo(id, anchor: anchor)
}
}
}
}
extension View {
func onDebouncedScroll<Value: Equatable>(
of value: Value,
to id: any Hashable,
in proxy: ScrollViewProxy,
anchor: UnitPoint? = .bottom,
duration: Duration = .milliseconds(50)
) -> some View {
modifier(DebouncedScrollOnChangeModifier(value: value, proxy: proxy, id: id, anchor: anchor, duration: duration))
}
}You could then replace this block with a single line in both views:
.onDebouncedScroll(of: consoleLog, to: "textID", in: proxy)
.onDebouncedScroll(of: consoleLog, to: "textID", in: proxy)Update documentation to reflect the migration from mlxserver to mlx-lm: - Updated API endpoint examples to use OpenAI-compatible endpoints - Changed curl examples from GET /generate to POST /v1/chat/completions - Added both chat completions and text completions examples - Updated links from mlxserver to mlx-lm repository - Updated acknowledgements to reference MLX team instead of mlxserver authors - Improved API endpoint documentation section The old /generate endpoint no longer exists in mlx-lm's server. Users should now use the standard OpenAI-compatible endpoints: - /v1/chat/completions (recommended for conversations) - /v1/completions (for simple text generation) - /v1/models (to list available models) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
|
@TinyTb Thanks for your PR. Let me look over it this weekend. FWIW, I'm not actively maintaining this project anymore (I actually should archive it) since I have a replacement in the App Store that's 100% Swift, but happy to your PR requests |
Summary
onChange(of: String) action tried to update multiple times per frameServerLogViewandSetupViewProblem
When server logs were being appended rapidly via
Server.log.append(), theonChange(of: server.log)modifier would fire multiple times within a single frame, causing SwiftUI to issue a performance warning.Solution
scrollTaskstate variable to track pending scroll operationsTest plan
🤖 Generated with Claude Code