fix(preview): open Portless dev server URLs - #6514
Conversation
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
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 |
There was a problem hiding this comment.
Reviewed the Effect service changes in apps/server/src/preview/PortScanner.ts (plus its test) and the web-side helpers. One finding: the new Portless state lookup reads host state outside the Effect environment.
Posted via Macroscope — Effect Service Conventions
ApprovabilityVerdict: Not approved Macroscope's review found this PR not approvable — This change adds a cross-cutting preview capability: production port scanning now reads Portless state and ngrok APIs, rewrites discovered URLs, and adds new sidebar and browser behavior. Its external integrations and unresolved cases involving remote history, stale proxies, and terminal attribution warrant focused human review. Not approved because:
Adjust the Minimum Blocking Severity for this repo — including turning it Off — in Settings. You can add or adjust custom eligibility rules. Learn more. |
424b513 to
58040a5
Compare
There was a problem hiding this comment.
One UI consistency finding on the discovered-server label. Everything else in the changed web UI (new sidebar globe action, PreviewEmptyState/PreviewView target-port threading, openDiscoveredPort) follows the surrounding patterns: raw buttons are wrapped in TooltipTrigger render exactly like the neighboring pin/settle actions, keep cursor-pointer, focus-visible rings and aria-label, and every environment-scoped action resolves its target from the bound thread's environmentId rather than the active environment.
Posted via Macroscope — UI Consistency
There was a problem hiding this comment.
One consistency issue found in the preview local-server card label. The new sidebar globe button matches the surrounding row conventions (pinIndicator, terminalStatusIcon) for geometry, focus ring, and aria labeling, so no primitive-migration findings there.
Posted via Macroscope — UI Consistency
51572d5 to
27d303b
Compare
27d303b to
1fb6bb3
Compare
| normalizedUrl, | ||
| ).resolvedUrl; | ||
| } | ||
| } |
There was a problem hiding this comment.
Remote Portless history skips remap
Medium Severity
Remote remap for Portless URLs only runs when both targetPort and a local-proxy classification are present. Preview visits now record the named Portless URL, and the empty-state recent list only supplies targetPort/urlKind when a live discovered match exists. Without that match, resolveDiscoveredServerUrl opens the .localhost URL directly on the client instead of the remote listener, so recent Portless entries fail on remote environments whenever the server is not currently listed.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 1fb6bb3. Configure here.
| ): ReadonlyArray<DiscoveredLocalServer> => { | ||
| const visibleByServer = new Map<string, DiscoveredLocalServer>(); | ||
| const namedRouteByServer = new Map<string, DiscoveredLocalServer>(); | ||
| for (const server of snapshot.discovered) { |
There was a problem hiding this comment.
🟡 Medium preview/PortScanner.ts:303
A successful configured URL remains on localhost instead of using its Portless named proxy URL when the listener root is not HTML. namedRouteByServer is built only from snapshot.discovered, so the configured entry is missing route metadata and takes the !namedRoute branch; preserve or derive the named route for successful configured entries as well.
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/preview/PortScanner.ts around line 303:
A successful configured URL remains on `localhost` instead of using its Portless named proxy URL when the listener root is not HTML. `namedRouteByServer` is built only from `snapshot.discovered`, so the configured entry is missing route metadata and takes the `!namedRoute` branch; preserve or derive the named route for successful configured entries as well.
| .exists(path.join(stateDir, "proxy.tls")) | ||
| .pipe(Effect.orElseSucceed(() => false)); | ||
|
|
||
| return parsePortlessRouteSnapshot({ |
There was a problem hiding this comment.
🟠 High preview/PortScanner.ts:479
When the Portless proxy has died but its managed route PID is still alive, readPortlessRoutes accepts the stale route and applyNamedRoutes overwrites the working localhost URL with it, so the discovered server becomes unreachable. Verify that the Portless proxy is still listening before applying routes (or discard routes when it is not).
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/preview/PortScanner.ts around line 479:
When the Portless proxy has died but its managed route PID is still alive, `readPortlessRoutes` accepts the stale route and `applyNamedRoutes` overwrites the working localhost URL with it, so the discovered server becomes unreachable. Verify that the Portless proxy is still listening before applying routes (or discard routes when it is not).
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.
There are 2 total unresolved issues (including 1 from previous review).
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit fdf6eb5. Configure here.
| routes.set( | ||
| targetPort, | ||
| response.terminal === null ? route : { ...route, terminal: response.terminal }, | ||
| ); |
There was a problem hiding this comment.
Ngrok steals app terminal
Medium Severity
readNgrokRoutes attaches the ngrok agent’s terminal to each tunnel target, and applyNamedRoutes spreads that over the app listener. When the app and ngrok live in different threads, useThreadDiscoveredPorts reattributes the enriched server to the ngrok thread, so the sidebar globe leaves the thread that owns the app.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit fdf6eb5. Configure here.


Demo
Problem
T3 Code only exposed the underlying localhost listener for Portless-managed dev servers, so its sidebar and browser opened a transient localhost port instead of the stable named HTTPS URL.
What changed
Validation
vp test run apps/server/src/preview/PortScanner.test.ts apps/web/src/browser/browserTargetResolver.test.ts apps/web/src/components/preview/useDiscoveredLocalServers.test.ts(35 tests passed)vp run --filter t3 typecheckvp run --filter @t3tools/web typecheckhttps://artelo.localhostBuilt with GPT-5.6 in the Codex harness.
Note
Add Portless and ngrok tunnel URL support to preview server discovery
PortScannerreads Portlessroutes.jsonstate files and queries the ngrok agent API to enrich discovered local servers with named or public-tunnel URLs, carrying a newurlKindfield onDiscoveredLocalServerresolveDiscoveredServerUrlmapslocal-proxyURLs to the remote environment's listener port (preserving path/query/hash) whilepublic-tunnelURLs open directlyCOMMON_DEV_PORTSextended to include port4040; ngrok agent API ports are excluded from web probingserversEqualnow comparesurlKind, so aurlKindchange on an existing listener triggers an update notification; out-of-tree consumers ofDiscoveredLocalServermust handle the new optionalurlKindfieldMacroscope summarized fdf6eb5.
Note
Medium Risk
Changes how discovered dev URLs are resolved and opened across local vs remote environments, and adds filesystem reads and HTTP calls to the ngrok agent during port scans.
Overview
Port discovery now enriches HTTP listeners with Portless routes (from
routes.json/ proxy state) and ngrok tunnels (via the agentapi/tunnelson port 4040), tagging results with optionalurlKind(local-proxy|public-tunnel) onDiscoveredLocalServer. Ngrok agent ports are skipped for normal web probing so a non-ngrok server on 4040 still works.Opening previews passes listener port and
urlKindthroughresolveDiscoveredServerUrl: Portless named URLs open directly on a local loopback environment, but on remote environments map to the environment host on the underlying port; ngrok public URLs open directly everywhere. Sidebar globe buttons, preview empty-state cards, and recents useselectPreferredDiscoveredServer(ngrok → Portless → first listener) andformatDiscoveredServerHostfor labels instead of rawlocalhost:port.Reviewed by Cursor Bugbot for commit fdf6eb5. Bugbot is set up for automated code reviews on this repo. Configure here.