Skip to content

Refactor swap debounce: separate user input from programmatic actions#1697

Merged
gemcoder21 merged 2 commits intomainfrom
1696-refactor-swap-debounce
Feb 16, 2026
Merged

Refactor swap debounce: separate user input from programmatic actions#1697
gemcoder21 merged 2 commits intomainfrom
1696-refactor-swap-debounce

Conversation

@DRadmir
Copy link
Contributor

@DRadmir DRadmir commented Feb 16, 2026

  • Add SwapFetchTrigger with DebouncableTrigger protocol
  • User keyboard input triggers debounced fetch (isImmediate: false)
  • Button actions (percent, asset change, retry, min amount) trigger immediate fetch
  • Timer calls fetch() directly instead of setting trigger
  • Add fetchTriggerIsImmediate test covering all 5 trigger cases

Close: #1696

  - Add SwapFetchTrigger with DebouncableTrigger protocol
  - User keyboard input triggers debounced fetch (isImmediate: false)
  - Button actions (percent, asset change, retry, min amount) trigger immediate fetch
  - Timer calls fetch() directly instead of setting trigger
  - Add fetchTriggerIsImmediate test covering all 5 trigger cases
@DRadmir DRadmir self-assigned this Feb 16, 2026
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @DRadmir, 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 refactors the swap quote fetching mechanism to provide more precise control over when quote requests are made. By introducing a dedicated SwapFetchTrigger and distinguishing between immediate and debounced actions, the system can now respond more appropriately to different user interactions, improving responsiveness for critical actions while maintaining efficiency for continuous input. This change streamlines the state management related to fetching and enhances the overall user experience in the swap flow.

Highlights

  • Introduced SwapFetchTrigger: A new SwapFetchTrigger struct conforming to DebouncableTrigger has been added to explicitly manage swap quote fetching, including an isImmediate flag to differentiate between debounced and immediate fetch requests.
  • Refactored Debounce Logic: The previous SwapFetchState enum and its associated debounce mechanism have been removed. Fetching is now triggered by setting the fetchTrigger property in SwapSceneViewModel, which is observed by a debouncedTask modifier in SwapScene.swift.
  • Separated User Input from Programmatic Actions: User keyboard input for amount changes now triggers a debounced fetch (isImmediate: false), while button actions (e.g., selecting percentage, changing assets, retrying) trigger an immediate fetch (isImmediate: true). This provides more granular control over when quote fetches occur.
  • Simplified SwapSceneViewModel Fetching: The fetch function in SwapSceneViewModel has been simplified, no longer taking a delay parameter. A new private setFetchTrigger method encapsulates the logic for creating and assigning the SwapFetchTrigger based on the desired immediacy.
  • Updated debouncedTask Modifier: The DebouncedTaskModifier struct has been removed, and its logic integrated directly into a View extension. The id parameter for debouncedTask now accepts an optional DebouncableTrigger, allowing for more flexible usage.
  • Enhanced Test Coverage: A new test case fetchTriggerIsImmediate was added to SwapSceneViewModelTests to verify that various user interactions correctly set the isImmediate flag on the SwapFetchTrigger.

🧠 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
  • Features/Swap/Sources/Scenes/SwapScene.swift
    • Replaced the old .debounce modifier with the new .debouncedTask modifier, utilizing model.fetchTrigger.
  • Features/Swap/Sources/Types/SwapFetchState.swift
    • Removed the SwapFetchState enum, eliminating its role in managing fetch states and delays.
  • Features/Swap/Sources/Types/SwapFetchTrigger.swift
    • Added SwapFetchTrigger struct, which conforms to DebouncableTrigger and includes input and isImmediate properties.
  • Features/Swap/Sources/Types/SwapState.swift
    • Removed the fetch property of type SwapFetchState from SwapState.
  • Features/Swap/Sources/ViewModels/SwapSceneViewModel.swift
    • Removed quoteTaskDebounceTimeout static property.
    • Added fetchTrigger property of type SwapFetchTrigger?.
    • Simplified the fetch() method to rely on currentInput and removed the delay parameter.
    • Removed the onFetchStateChange method.
    • Modified various action handlers (onChangeFromValue, onChangeFromAsset, onChangeToAsset, onSelectPercent, applyMinAmount, onSelectActionButton for retry) to use the new setFetchTrigger method.
    • Introduced currentInput computed property to derive SwapQuoteInput.
    • Added setFetchTrigger(isImmediate: Bool) private method to manage the fetchTrigger state.
    • Removed direct assignments to swapState.fetch.
  • Features/Swap/Tests/SwapTests/SwapButtonViewModelTests.swift
    • Updated SwapState initializers in tests to use the quotes parameter instead of availability.
  • Features/Swap/Tests/SwapTests/SwapSceneViewModelTests.swift
    • Updated test calls from onFetchStateChange to the new fetch() method.
    • Added a new test fetchTriggerIsImmediate to validate the isImmediate flag behavior.
    • Adjusted SwapSceneViewModel.mock() setup for better test consistency.
    • Removed the SwapQuoteInput.mock() helper.
  • Packages/Components/Sources/ViewModifiers/DebouncedTaskModifier.swift
    • Removed the DebouncedTaskModifier struct.
    • Refactored the debouncedTask extension on View to directly embed the debouncing logic.
    • Changed the id parameter of debouncedTask to accept an optional DebouncableTrigger.
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.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

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 refactors the swap quote fetching logic by separating user input-driven debounced fetches from immediate programmatic fetches. The introduction of SwapFetchTrigger and the simplification of the debouncedTask modifier are excellent improvements that enhance clarity and maintainability. The removal of SwapFetchState further streamlines the state management.

I've identified a potential concurrency issue related to the timer-based refresh and a minor bug in one of the new tests. My detailed comments provide suggestions to address these points.

Comment on lines +180 to 183
func fetch() async {
guard let currentInput else { return }
await performFetch(input: currentInput)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The onTimer in SwapScene calls this fetch() method directly. Other user actions use setFetchTrigger, which allows the .debouncedTask modifier to manage and cancel tasks. This direct call can lead to concurrent fetches (one from the timer, one from a user action), potentially causing race conditions or unnecessary network requests.

To ensure all fetches are serialized and cancellable, consider aligning the timer's behavior with the trigger-based mechanism. A possible approach is to introduce a public method like refreshQuotes() that calls setFetchTrigger(isImmediate: true), and have the timer call this new method. This would centralize fetch management within the .debouncedTask.

#expect(model.fetchTrigger?.isImmediate == true)

model.fetchTrigger = nil
model.swapState.quotes = .error(SwapperError.NoQuoteAvailable)
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The error SwapperError.NoQuoteAvailable does not seem to conform to RetryableError. This will cause buttonViewModel.buttonAction to default to .swap instead of .retryQuotes, and the assertion on line 120 will likely fail.

To correctly test the retry logic, you should use an error that conforms to RetryableError, like the local TestError struct.

Suggested change
model.swapState.quotes = .error(SwapperError.NoQuoteAvailable)
model.swapState.quotes = .error(TestError())

Replace the multiple Duration.Debounce constants with a single Duration.debounce and update related APIs and call sites. Default interval parameters for debounce and debouncedTask modifiers now use .debounce; callers updated to use .debounce (or .none where appropriate). Adjusted view models and views (SelectAssetScene, AddNodeSceneViewModel, SwapScene, ConfirmTransferScene, WalletSearchScene, NameRecordView) to the new API and removed the old Debounce struct.
@gemcoder21 gemcoder21 merged commit 30d407f into main Feb 16, 2026
1 check passed
@gemcoder21 gemcoder21 deleted the 1696-refactor-swap-debounce branch February 16, 2026 19:44
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.

Refactor swap debounce

2 participants