Skip to content

re-addressing "429: too many requests"#94

Merged
InteractionEngineer merged 8 commits into
mainfrom
fix/api-burst-and-double-slash
May 29, 2026
Merged

re-addressing "429: too many requests"#94
InteractionEngineer merged 8 commits into
mainfrom
fix/api-burst-and-double-slash

Conversation

@InteractionEngineer
Copy link
Copy Markdown
Owner

I could finally reproduce the 429 myself and went digging. The fix from #92 only touched the validate-while-typing path, but the regular bills/members loader had its own request-burst problem: every onAppear, every mutation, every tab switch fired a fresh Combine pipeline without cancelling the previous one, and a server URL ending in / produced a double slash (https://host//index.php/...) which openresty seems to rate-limit as a separate path.

I removed the leading / from staticPath, made loadBillsAndMembers single-flight (cancel-and-replace via one AnyCancellable), put the tab view models behind @StateObject so they don't get re-allocated on every render, and replaced the .onAppear reloads on the bills and balance tab with .refreshable plus a refresh on scenePhase becoming active.

While I was at it, a few related bugs got in the way of confirming the fix: sortedBills wasn't reacting to new bills (only watching $sortBy), and four views had @State on input properties (BalanceCell, BillCell, WhoPaidView, PersonText) which is why balances and edited bills only updated after reopening the project. Also fixed two background-thread @Published writes that showed up in the console (mutation completion handlers and the QR scan publisher).

Until another occurrence, this merge actually closes #81.

might be rate limited by some reverse proxies
URLSession.dataTaskPublisher emits on a background queue. The mutation sinks in ProjectManager (sendBill, deleteBill, sendMember, deleteMember) forwarded that thread straight into user-supplied completion handlers, which then touched @State, @binding and @published on the caller side producing "Publishing changes from background threads is not allowed" runtime warnings after every save / delete / add.
Copilot AI review requested due to automatic review settings May 29, 2026 13:13
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Re-addresses the HTTP 429 "Too Many Requests" issue from #81 by eliminating duplicate/burst requests to the Cospend backend and fixing a handful of related SwiftUI state-management bugs that were masking whether the fix worked.

Changes:

  • Make loadBillsAndMembers single-flight (cancel-and-replace via a stored AnyCancellable) and add an async refresh() wrapper used by .refreshable; replace .onAppear reloads in BillList/BalanceList with pull-to-refresh + scenePhase reload; hoist tab view models to @StateObject.
  • Strip the leading / from ProjectBackend.staticPath so a server URL with a trailing slash no longer produces //, and add a regression test.
  • Fix unrelated SwiftUI bugs: sortedBills now reacts to currentProject via CombineLatest; convert @State/@Binding input properties on BalanceCell, BillCell, WhoPaidView, PersonText, PersonsView to let; route background-thread @Published writes through DispatchQueue.main.

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
PayForMe/Model/Project.swift Removes leading slash from staticPath for both backends to avoid // when server URL has a trailing slash.
PayForMe/Services/ProjectManager.swift Adds single-flight loadCancellable, async refresh() wrapper, and .receive(on: .main) on mutation publishers; cancels in-flight load on project switch.
PayForMe/Views/ContentView.swift Promotes tab view models to @StateObject and reloads on scenePhase == .active.
PayForMe/Views/BillList/BillList.swift Replaces .onAppear reload with .refreshable calling ProjectManager.refresh().
PayForMe/Views/BillList/BillListViewModel.swift Uses CombineLatest($sortBy, $currentProject) so sortedBills updates when bills change.
PayForMe/Views/BillList/BillCell.swift Switches bill from @State to let; passes plain values to PersonsView.
PayForMe/Views/BillList/PersonsView.swift Switches bill and members from @Binding to let.
PayForMe/Views/BillDetail/WhoPaidView.swift Switches members from @State to let.
PayForMe/Views/PersonText.swift Switches person from @State to let.
PayForMe/Views/Balance/BalanceList.swift Replaces .onAppear reload with .refreshable; BalanceCell.balance becomes let.
PayForMe/Views/Projects/QRCodes/AddProjectQRViewModel.swift Routes passwordCorrect, isTestingSubject, and foundCode through the main queue.
PayForMeTests/NetworkRequestTests.swift Adds regression test verifying a trailing slash in the server URL no longer produces //.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread PayForMe/Services/ProjectManager.swift Outdated
@InteractionEngineer InteractionEngineer self-assigned this May 29, 2026
@InteractionEngineer InteractionEngineer merged commit d554f3a into main May 29, 2026
1 check passed
@InteractionEngineer InteractionEngineer deleted the fix/api-burst-and-double-slash branch May 29, 2026 13:32
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.

PayForMe Causes HTTP-Error 429: Too Many Requests

2 participants