Skip to content

Fix SwiftUI onChange performance warning with debouncing#11

Open
TinyTb wants to merge 3 commits into
PicoMLX:mainfrom
TinyTb:fix/swiftui-onchange-performance-warning
Open

Fix SwiftUI onChange performance warning with debouncing#11
TinyTb wants to merge 3 commits into
PicoMLX:mainfrom
TinyTb:fix/swiftui-onchange-performance-warning

Conversation

@TinyTb

@TinyTb TinyTb commented Feb 14, 2026

Copy link
Copy Markdown

Summary

  • Fixes SwiftUI performance warning: onChange(of: String) action tried to update multiple times per frame
  • Implements debouncing pattern to batch rapid log updates before triggering scroll actions
  • Applied to both ServerLogView and SetupView

Problem

When server logs were being appended rapidly via Server.log.append(), the onChange(of: server.log) modifier would fire multiple times within a single frame, causing SwiftUI to issue a performance warning.

Solution

  • Added scrollTask state variable to track pending scroll operations
  • Cancel any pending scroll task when new log updates arrive
  • Use 50ms delay to batch rapid updates
  • Only execute scroll if task wasn't cancelled (no newer update arrived)

Test plan

  • Build project successfully
  • Start a server and verify logs still auto-scroll smoothly
  • Verify performance warning no longer appears in console
  • Test with rapid log output to ensure debouncing works correctly

🤖 Generated with Claude Code

TinyTb and others added 2 commits February 13, 2026 22:52
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>
@gemini-code-assist

Copy link
Copy Markdown

Summary of Changes

Hello @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 mlx_lm package's native server, and improves the robustness of the Conda environment setup for script execution.

Highlights

  • SwiftUI Performance Improvement: Implemented a debouncing pattern for onChange modifiers in ServerLogView and SetupView to prevent rapid UI updates, resolving a SwiftUI performance warning related to multiple updates per frame. A 50ms delay is used to batch log updates before triggering scroll actions.
  • MLX Server Migration: Migrated the MLX server implementation from the mlxserver library to the mlx_lm package's built-in server functionality. This involves using subprocess.run to launch the mlx_lm server directly.
  • Robust Conda Initialization: Updated the Execute.command script to conditionally source conda.sh from specific installation paths, improving Conda environment activation reliability and avoiding conflicts with general shell profile configurations like Oh My Zsh.
  • Dependency List Update: The Dependencies.plist file was updated to reflect the change from mlxserver to mlx-lm, including adjustments to the package name, minimum required version (now 0.29.0), installation commands, and associated URLs.

🧠 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
  • PicoMLXServer/Bash Commands/Execute.command
    • Replaced source ~/.bash_profile with conditional sourcing of conda.sh from common Conda installation directories.
    • Added comments explaining the improved Conda initialization logic.
  • PicoMLXServer/Server Manager/Models/server.py
    • Added #!/usr/bin/env python3 shebang.
    • Replaced from mlxserver import MLXServer with import subprocess.
    • Changed server initialization from direct MLXServer instantiation to launching mlx_lm server via subprocess.run.
  • PicoMLXServer/Server Manager/ServerLogView.swift
    • Added a @State private var scrollTask: Task<Void, Never>? property to manage debounced scroll operations.
    • Modified the onChange(of: server.log) modifier to implement a 50ms debouncing delay for scrolling to the bottom of the log view.
  • PicoMLXServer/Setup/Models/Dependencies.plist
    • Updated the comment for the MLX dependency to reflect 'MLX Language Model library with built-in server support'.
    • Changed the dependency name from mlxserver to mlx-lm.
    • Updated version check commands and installation commands to use mlx-lm.
    • Increased the minimumVersionRequired for mlx-lm from 0.1.14 to 0.29.0.
    • Updated the URL for the mlx-lm dependency.
  • PicoMLXServer/Setup/SetupView.swift
    • Added a @State private var scrollTask: Task<Void, Never>? property to manage debounced scroll operations.
    • Modified the onChange(of: consoleLog) modifier to implement a 50ms debouncing delay for scrolling to the bottom of the console log.
Activity
  • The author has provided a detailed test plan and marked all items as completed, indicating successful builds, smooth auto-scrolling, absence of performance warnings, and correct debouncing behavior under rapid log output.
Using Gemini Code Assist

The 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 /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

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 .gemini/ folder in the base of the repository. Detailed instructions can be found here.

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

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@gemini-code-assist gemini-code-assist 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.

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>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

This command and others in this block have been updated to use mlx-lm, but the dependency's name on line 256 is still mlxserver. This inconsistency could lead to errors. Please update the name to mlx-lm for consistency.

<key>installCommands</key>
<array>
<string>python3 pip install mlxserver</string>
<string>pip install mlx-lm</string>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

The install command is correctly updated for mlx-lm. However, the minimumVersionRequired on line 264 is still 0.1.14, which was for the old mlxserver. This should be updated to 0.29.0 to match the requirement for mlx-lm as specified in the conda dependency section (line 127).

Comment on lines +55 to +63
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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

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.

Suggested change
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

Comment on lines 108 to +111
<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>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

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>

Comment on lines 108 to 117
.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)
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

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>
@ronaldmannak

Copy link
Copy Markdown
Collaborator

@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

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