fix(desktop): eliminate stale port data from dual-source race condition#1497
fix(desktop): eliminate stale port data from dual-source race condition#1497
Conversation
The ports sidebar used a Zustand store fed by both a tRPC query (initial snapshot) and a subscription (incremental updates). When the query returned or re-fetched, setPorts() overwrote the entire store — potentially re-adding ports the subscription had already removed. Since the query had no refetchInterval, stale data persisted indefinitely. Replace the dual-source approach with the tRPC query as the single source of truth, polled every 2.5s (matching the port scanner cycle). The subscription now invalidates the query for immediate refetches instead of directly mutating state. Remove unused port-data methods from the Zustand store, keeping only the UI collapse preference.
📝 WalkthroughWalkthroughReplaced client-side merged/static port state with a single refetching TRPC source: Changes
Sequence Diagram(s)sequenceDiagram
participant Renderer as Renderer (usePortsData)
participant TRPC as TRPC ports.getAll
participant Loader as static-ports loader
participant DB as Port Detector
Renderer->>TRPC: query ports.getAll (poll every PORTS_REFETCH_INTERVAL_MS)
TRPC->>DB: fetch detected ports
TRPC->>Loader: loadStaticPorts(worktreePath) → Map<port,label>
Loader-->>TRPC: Map<port,label>
TRPC-->>Renderer: EnrichedPort[] (detected ports + label)
Renderer->>Renderer: group by workspaceId → derive workspaceName, sort
Renderer-->>UI: render WorkspacePortGroup(s) with EnrichedPort[] lists
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
⚔️ Resolve merge conflicts (beta)
No actionable comments were generated in the recent review. 🎉 🧹 Recent nitpick comments
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Move static port label resolution from the renderer into the main process getAll tRPC endpoint. This eliminates the need for separate getAllStatic/subscribeAllStatic queries, the mergePorts utility, and the MergedPort type with its nullable fields. Detected ports now come back enriched with labels directly, simplifying the renderer data flow.
Instead of pre-building a label map from all workspaces, look up the workspace path for each detected port's workspaceId and load its ports.json directly. This ensures labels resolve correctly regardless of which workspace the port scanner associates with a session.
🧹 Preview Cleanup CompleteThe following preview resources have been cleaned up:
Thank you for your contribution! 🎉 |
Summary
refetchInterval: 2500msto keep ports in sync with the scanner cycle, and changed the subscription to invalidate the query for immediate updates instead of directly mutating store stateaddPort,removePort,setPorts, etc.) from the Zustand store, keeping only the UI collapse preferenceProblem
The ports sidebar showed all ports from
ports.jsoneven when they weren't actively listening. The previous fix (#1488) correctly changedmergePortsto only include detected ports, but a race condition in the renderer kept stale data visible:ports.subscribereceivesport:removeevents → store removes ports ✓ports.getAllquery returns (possibly stale snapshot) →setPorts()overwrites the entire store, re-adding removed ports ✗refetchInterval, stale data persisted until the next window-focus refetchTest plan
ports.jsonSummary by CodeRabbit
Refactor
New Features
UX