M3: SwiftUI app — TrafficList, Inspector, SQLite store - #74
Conversation
There was a problem hiding this comment.
1 issue found across 12 files
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
This is likely a transient issue. You can re-trigger a run from the dashboard. |
Fixes for PR #74 review comments (cubic): - FlowStore: protect against the persist/clear race. Each clear() bumps a generation counter; in-flight persistence tasks check the counter inside the database.write closure and skip the save if the user has cleared since the task was scheduled. Without this, a "Clear" click could be silently undone by a still-pending insert that landed afterwards. - New thread-safe GenerationCounter helper (NSLock-backed). Tests: - TrafficFilterTests: 10 cases covering search, host/method/status bucket filters, errors-only, status bucket boundaries - JSONFormatterTests: empty / valid JSON / non-JSON content types / shape detection / leading whitespace / invalid JSON - Package.swift: ReverseAPITests testTarget
|
@greptile review Generated by Claude Code |
e78f8e9 to
fbf9bd7
Compare
Fixes for PR #74 review comments (cubic): - FlowStore: protect against the persist/clear race. Each clear() bumps a generation counter; in-flight persistence tasks check the counter inside the database.write closure and skip the save if the user has cleared since the task was scheduled. Without this, a "Clear" click could be silently undone by a still-pending insert that landed afterwards. - New thread-safe GenerationCounter helper (NSLock-backed). Tests: - TrafficFilterTests: 10 cases covering search, host/method/status bucket filters, errors-only, status bucket boundaries - JSONFormatterTests: empty / valid JSON / non-JSON content types / shape detection / leading whitespace / invalid JSON - Package.swift: ReverseAPITests testTarget
33bfb0d to
3c7f333
Compare
|
@greptile review Generated by Claude Code |
Fixes for PR #74 review comments (cubic): - FlowStore: protect against the persist/clear race. Each clear() bumps a generation counter; in-flight persistence tasks check the counter inside the database.write closure and skip the save if the user has cleared since the task was scheduled. Without this, a "Clear" click could be silently undone by a still-pending insert that landed afterwards. - New thread-safe GenerationCounter helper (NSLock-backed). Tests: - TrafficFilterTests: 10 cases covering search, host/method/status bucket filters, errors-only, status bucket boundaries - JSONFormatterTests: empty / valid JSON / non-JSON content types / shape detection / leading whitespace / invalid JSON - Package.swift: ReverseAPITests testTarget
3c7f333 to
d9d7ff5
Compare
6a29aaf to
1d3b348
Compare
Fixes for PR #74 review comments (cubic): - FlowStore: protect against the persist/clear race. Each clear() bumps a generation counter; in-flight persistence tasks check the counter inside the database.write closure and skip the save if the user has cleared since the task was scheduled. Without this, a "Clear" click could be silently undone by a still-pending insert that landed afterwards. - New thread-safe GenerationCounter helper (NSLock-backed). Tests: - TrafficFilterTests: 10 cases covering search, host/method/status bucket filters, errors-only, status bucket boundaries - JSONFormatterTests: empty / valid JSON / non-JSON content types / shape detection / leading whitespace / invalid JSON - Package.swift: ReverseAPITests testTarget
d9d7ff5 to
c27a3ee
Compare
There was a problem hiding this comment.
1 issue found across 2 files (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
There was a problem hiding this comment.
1 issue found across 3 files (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
There was a problem hiding this comment.
2 issues found across 3 files (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
There was a problem hiding this comment.
2 issues found across 6 files (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
There was a problem hiding this comment.
1 issue found across 4 files (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
|
You're iterating quickly on this pull request. To help protect your rate limits, cubic has paused automatic reviews on new pushes for now—when you're ready for another review, comment |
1d3b348 to
826b83e
Compare
Fixes for PR #74 review comments (cubic): - FlowStore: protect against the persist/clear race. Each clear() bumps a generation counter; in-flight persistence tasks check the counter inside the database.write closure and skip the save if the user has cleared since the task was scheduled. Without this, a "Clear" click could be silently undone by a still-pending insert that landed afterwards. - New thread-safe GenerationCounter helper (NSLock-backed). Tests: - TrafficFilterTests: 10 cases covering search, host/method/status bucket filters, errors-only, status bucket boundaries - JSONFormatterTests: empty / valid JSON / non-JSON content types / shape detection / leading whitespace / invalid JSON - Package.swift: ReverseAPITests testTarget
773e59f to
32a4681
Compare
826b83e to
d66ead0
Compare
Fixes for PR #74 review comments (cubic): - FlowStore: protect against the persist/clear race. Each clear() bumps a generation counter; in-flight persistence tasks check the counter inside the database.write closure and skip the save if the user has cleared since the task was scheduled. Without this, a "Clear" click could be silently undone by a still-pending insert that landed afterwards. - New thread-safe GenerationCounter helper (NSLock-backed). Tests: - TrafficFilterTests: 10 cases covering search, host/method/status bucket filters, errors-only, status bucket boundaries - JSONFormatterTests: empty / valid JSON / non-JSON content types / shape detection / leading whitespace / invalid JSON - Package.swift: ReverseAPITests testTarget
32a4681 to
4b47b70
Compare
- ReverseAPI app target wired through Package.swift - AppState (@mainactor @observable) owns engine, flow store, installer, system proxy, capture toggles, CA install/uninstall - FlowStore: GRDB-backed SQLite persistence with reactive @observable array of CapturedFlow, subscribes to FlowBus, loads last 500 on boot - TrafficListView: SwiftUI Table with method/host/path/status/size/duration columns, search box, host/method/status/errors filters - InspectorView: Overview / Request / Response tabs with header table and JSON-aware body pretty printer - CaptureToolbar: capture, CA trust, system proxy, clear; status line - ProxyEngine: split stop() (channel close) from terminate() (also shuts down the event-loop group) so capture can be toggled - NIOFoundationCompat added to ReverseAPIProxy deps
Fixes for PR #74 review comments (cubic): - FlowStore: protect against the persist/clear race. Each clear() bumps a generation counter; in-flight persistence tasks check the counter inside the database.write closure and skip the save if the user has cleared since the task was scheduled. Without this, a "Clear" click could be silently undone by a still-pending insert that landed afterwards. - New thread-safe GenerationCounter helper (NSLock-backed). Tests: - TrafficFilterTests: 10 cases covering search, host/method/status bucket filters, errors-only, status bucket boundaries - JSONFormatterTests: empty / valid JSON / non-JSON content types / shape detection / leading whitespace / invalid JSON - Package.swift: ReverseAPITests testTarget
d66ead0 to
1982716
Compare
4b47b70 to
66fbb3c
Compare
|
Bugbot is not enabled for your account, so this pull request was not reviewed. Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs. |
…e group ProxyEngine created its MultiThreadedEventLoopGroup once and stop() shut it down, so the app (which reuses a single engine) could only capture once per launch: toggling capture off, or the cleanup after a failed device-mode start, left the group permanently shut down and every later start() failed with EventLoopError.shutdown. Split the lifecycle: stop() now only closes the server channel (restartable), and terminate() closes the channel and shuts the group down for good. AppState already calls stop() on capture-off/window-close and terminate() on app quit, so this matches the intended usage. Adds ProxyEngineLifecycleTests: start→stop→start succeeds, and stop() is idempotent. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WDWn9mRd9dbCmb3jPY53tw
There was a problem hiding this comment.
1 issue found across 2 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="macos/Tests/ReverseAPIProxyTests/ProxyEngineLifecycleTests.swift">
<violation number="1" location="macos/Tests/ReverseAPIProxyTests/ProxyEngineLifecycleTests.swift:12">
P2: If `start()` or `stop()` throws in either test, `engine.terminate()` is never called and the `MultiThreadedEventLoopGroup` (with `System.coreCount` threads) leaks for the remainder of the test-suite process. Use `override func tearDown() async throws` to ensure cleanup regardless of where the test exits.</violation>
</file>
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
| /// failed device-mode start, would brick all future captures until relaunch). | ||
| func testCaptureCanRestartAfterStop() async throws { | ||
| let root = try CertificateAuthority.generateRoot() | ||
| let engine = try ProxyEngine(root: root, port: 0) |
There was a problem hiding this comment.
P2: If start() or stop() throws in either test, engine.terminate() is never called and the MultiThreadedEventLoopGroup (with System.coreCount threads) leaks for the remainder of the test-suite process. Use override func tearDown() async throws to ensure cleanup regardless of where the test exits.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At macos/Tests/ReverseAPIProxyTests/ProxyEngineLifecycleTests.swift, line 12:
<comment>If `start()` or `stop()` throws in either test, `engine.terminate()` is never called and the `MultiThreadedEventLoopGroup` (with `System.coreCount` threads) leaks for the remainder of the test-suite process. Use `override func tearDown() async throws` to ensure cleanup regardless of where the test exits.</comment>
<file context>
@@ -0,0 +1,40 @@
+ /// failed device-mode start, would brick all future captures until relaunch).
+ func testCaptureCanRestartAfterStop() async throws {
+ let root = try CertificateAuthority.generateRoot()
+ let engine = try ProxyEngine(root: root, port: 0)
+
+ try await engine.start()
</file context>
Stacked on top of #73.
First UI slice — a working SwiftUI macOS app on top of the proxy engine.
What's in
ReverseAPI(alongsiderae-proxy) with@mainSwiftUI App@MainActor+@Observable) — owns the engine, flow store, CA installer, system proxy controller. Handles toggle capture / install CA / enable system proxy / clear, all backgrounded off the main thread where they may block.~/Library/Application Support/ReverseAPI/flows.sqlite. Subscribes toFlowBus, keepsflows: [CapturedFlow]reactive, persists finished flows asynchronously, restores the last 500 on launch.Tablewith Time / Method / Host / Path / Status / Size / Duration columns + search + filter menu (errors-only, by host / method / status bucket).ProxyEngine fix
Split
stop()(just closes the listening channel) fromterminate()(also shuts down the event-loop group). Without this, capture could only be toggled once.Run
First launch: click Install CA, then System proxy, then Start capture. Every app that respects the macOS proxy will now show up in the list.
Out of scope
Generated by Claude Code
Summary by cubic
Adds a SwiftUI macOS app
raewith a live traffic list, inspector, and SQLite-backed persistence. Capture is now restartable (stop only closes the listener), system proxy is auto-enabled/restored in device mode, and responses stream to the client to cut latency.New Features
ReverseAPIapp target andraeexecutable;AppStatemanages the proxy engine, CA trust, system proxy, reactive store, and a boot-failure view.FlowStore:GRDB.swift-backed SQLite (last 500 flows), subscribes toFlowBus, writes finished flows off the main thread, restores recent flows on launch; derives host/method options incrementally to avoid recompute churn.TrafficListView: table with search and filters (errors, host, method, status buckets, resource types).InspectorView: Overview/Request/Response tabs with JSON pretty print, text/binary fallback, and timings.rae.Bug Fixes
Accept-Encoding: identityfor clearer bodies.ProxyEngine:stop()now only closes the server channel so capture can restart;terminate()shuts down the event-loop group; wired into app quit, with a lifecycle test verifying start→stop→start.FlowStore: guard persist/clear races via a generation counter; reduce UI work by caching host/method options.Written for commit 75bf6d9. Summary will update on new commits.
Greptile Summary
Adds the first SwiftUI macOS UI slice on top of the existing proxy engine: a live traffic list (
TrafficListView), a request/response inspector (InspectorView), a GRDB-backed SQLite flow store (FlowStore), and anAppStateobject that ties capture toggling, CA trust installation, and system-proxy control together. TheProxyEngineis also fixed sostop()andterminate()are separated, enabling repeated capture toggling.@MainActor+@Observabledesign is clean;persist()uses a generation counter to guard against clear-vs-write races; synchronousloadInitial()/clear()DB calls on the main actor and silent swallowing of persist errors are flagged in existing review comments.Tablewith seven columns, a filter bar for host/method/status/errors, and search;ByteCountFormatteris re-allocated per-cell-render andhostOptions/methodOptionsare O(n) scanned on every flow update (noted below).terminate()is correctly split out but is not yet wired into the SwiftUI app lifecycle (existing comment).Confidence Score: 5/5
The changes are well-structured and the new UI layer correctly delegates blocking work off the main thread; no regressions are introduced.
The new code introduces no data-loss or correctness regressions; the generation counter correctly handles clear-vs-persist races, the subscription and weak-self patterns are correct, and the filter logic is well-tested. The only new findings are minor render-efficiency nits in TrafficListView.
FlowStore.swift carries the most open risk from prior review comments (synchronous DB I/O on main actor, silent persist failures); TrafficListView.swift has the two new render-efficiency notes.
Important Files Changed
Comments Outside Diff (3)
macos/Sources/ReverseAPIProxy/ProxyEngine.swift, line 1149-1152 (link)terminate()is never called at app exitterminate()was added to cleanly shut down the NIOEventLoopGroup, but nothing in the SwiftUI app orAppStateever invokes it.stopCapture()only callsengine.stop(), which closes the listener but leaves the thread pool running. When the user quits the app, NIO's threads are abandoned to OS process cleanup rather than being gracefully drained. Sinceterminate()is the only path togroup.shutdownGracefully(), anAppState.terminate()method (hooked into the SwiftUI scene lifecycle viaonReceive(NotificationCenter.default.publisher(for: NSApplication.willTerminateNotification))orNSApplicationDelegate.applicationWillTerminate) would close this out cleanly.Prompt To Fix With AI
macos/Sources/ReverseAPI/Storage/FlowStore.swift, line 358-370 (link)Both error paths in
persist()discard the error: thePersistedFlow(from:)conversion bails out with a barereturn, and the GRDB write usestry?which drops any write failure. If the database is full, the path is wrong, or the file is corrupt, finished flows are lost with no signal to the user or any log output. Even aprint/Loggercall on the catch branch would make this diagnosable.Prompt To Fix With AI
macos/Sources/ReverseAPI/Storage/FlowStore.swift, line 327-332 (link)loadInitial()(called frominit) andclear()both use the synchronousdatabase.read/database.writeAPIs, which block the calling thread. BecauseFlowStoreis@MainActor, these block the main thread. With 500 rows on cold start or a large DB on clear, this can visibly stall the UI.persist()correctly offloads to a detached task; the same pattern (ordatabase.asyncRead/asyncWrite) should be applied here.Prompt To Fix With AI
Prompt To Fix All With AI
Reviews (3): Last reviewed commit: "M3 review fixes + tests" | Re-trigger Greptile