feat(middleware): discover debug targets through one Rozenite endpoint - #490
Merged
Merged
Conversation
Implements docs/adr/0000-single-target-discovery-endpoint.md: GET /rozenite/agent/targets is now the only way Rozenite code discovers debug targets. The middleware's mapper is the single implementation of the selection rules and now stamps each target with which integration (react-native or lynx) serves it; the CLI and the app no longer parse /json/list themselves and instead fetch this endpoint and read that field directly rather than guessing it from a scanned port.
- pageId now names a page within its own device (the `page` query
parameter of webSocketDebuggerUrl), not the globally unique
<deviceId>-<pageId> composite, so a reconnect's preferred-page match
in @rozenite/app actually matches.
- Validate the /rozenite/agent/targets envelope shape in both the app's
and the CLI's fetchers before trusting `ok`/`error`/`result.targets`,
throwing the unreachable/unexpected-response error on a malformed
body instead of letting a bare TypeError burn all reconnect attempts.
- Port the deleted CLI selection-rule tests to
packages/middleware/src/__tests__/metro-discovery.test.ts against the
real getMetroTargets/resolveMetroTarget, and add session-manager
coverage for the lynx/lynx-web host integration mapping.
- Give DevServerFailure a `kind` ('unreachable' | 'error') so
formatNoTargetsMessage only tells someone to start their dev server
when every scanned port was actually unreachable, not when a server
answered with its own error.
- Reword stale /json/list-era comments to point at the middleware's
targets endpoint, and rewrite the garbled reconnect-preference
comment in metro-target-resolution.ts.
- Derive hostIntegration with an exhaustive switch over
RozeniteIntegration instead of a ternary that failed open to
react-native.
- Fall back to the bare target label in the CLI picker when an older
middleware sends an integration this build does not recognise.
Both fetchers checked `response.ok` before parsing the body, but the middleware's `sendError` always pairs `ok:false` with an HTTP 400/404, so the real error envelope's own message was discarded in favour of a bare "responded with status 400". Parse the body first regardless of status; fall back to a status/unexpected-response message only when the body turns out not to be a usable envelope at all. - packages/app/src/connection/metro-target-resolution.ts and packages/cli/src/commands/dev-servers.ts: reorder validation so a valid ok:false envelope's error.message always wins, and update their tests to exercise the real ok:false + HTTP 400 shape plus a 404/non-JSON body. - dev-servers.ts: formatNoTargetsMessage's non-unreachable branch now ends with an actionable "pass --port" suffix instead of bare details. - metro-target-resolution.test.ts: drop a duplicated test case. - device-connection.test.ts: give mocked targets a device-local pageId (derived from their own webSocketDebuggerUrl) instead of defaulting to the composite id, and add an end-to-end reconnect test where the preferred page is second in the list. - open-command.test.ts: fixtures use device-local pageId values instead of composite ids, matching the new invariant. - logical-device-id.ts: fix a comment quoting the old, now-incorrect error text.
V3RON
force-pushed
the
feat/unified-target-discovery
branch
from
September 3, 2026 08:24
6e0d435 to
0f37a7f
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Makes
GET /rozenite/agent/targetsthe single way Rozenite code discovers debug targets, on both React Native (Metro) and Lynx, and records the decision as the repository's first ADR (docs/adr/0000-single-target-discovery-endpoint.md).@rozenite/middlewarestamps each target withintegration(react-native|lynx), taken from its own config rather than guessed from a port.rozenite openfetches the endpoint from each scanned dev server and labels the picker fromintegration. Its private copy of the/json/listselection rules is deleted.@rozenite/appre-resolves a target after a recoverable disconnect through the same endpoint, filtering ondeviceIdand preferring the page it was debugging. Its private copy of the selection rules is deleted.MetroTarget.pageIdis now the page's id within its device (thepagequery parameter ofwebSocketDebuggerUrl);idkeeps the globally unique composite.docs/adr/README.mdand the ADR.Related Issue
Closes #482
Context
The issue proposes extracting the pure selection rules into a shared module. That fixes the drift but not extensibility, since every consumer would still parse
/json/listitself. The endpoint already existed for the Agent SDK and is already served on the Lynx dev server, because@rozenite/lynx-devmounts the full middleware under/rozenite. Making it canonical removes two of the three copies and gives new target properties a single home. The ADR spells out the scenarios: the React Native DevTools injection path never discovers targets and is untouched, and@rozenite/web'shandleOpenDebuggerkeeps its/json/listcall on purpose because it mirrors the React Native CLI.Host and port resolution for the middleware's internal
/json/listcall is deliberately unchanged. The CLI does not fall back to/json/list, since the CLI and middleware are released in lockstep.Two adversarial review passes caught that the app compared the
pagequery value against the composite<deviceId>-<pageId>id, so the "reconnect to the same page" rule never fired. FixingpageIdin the one mapper resolves it for every consumer. The second pass caught that both fetchers checked HTTP status before the body, discarding the endpoint's error message, since the middleware always pairsok:falsewith a 400 or 404; both now parse the envelope first.Testing
git fetch origin main && pnpm checks:affected && pnpm test:affected— all green (middleware, CLI, and app suites all green).pnpm release:plan— changeset present, minor bump on the fixed@rozenite/*set.packages/middleware/src/__tests__/metro-discovery.test.tsruns the selection rules against a real local HTTP server, includingintegrationstamping andpageIdextraction.pnpm --filter @rozenite/app buildinspected: nonode:specifiers,express, orwsin the browser bundle.