feat: extract a host integration contract behind @rozenite/integration - #458
Draft
V3RON wants to merge 3 commits into
Draft
feat: extract a host integration contract behind @rozenite/integration#458V3RON wants to merge 3 commits into
V3RON wants to merge 3 commits into
Conversation
Rozenite now runs in three integrations, and a plugin built for one can load into another and break. This lays the producer side of a compatibility gate: plugins declare what they support, and the dev server can say what a given target actually is. `@rozenite/tools` gains the shared vocabulary — `react-native`, `react-native-web`, `lynx`, `lynx-web` — plus `resolveIntegration`, which combines the host a dev server serves with the device platform CDP reports for a target. That combination is what makes one Metro serving a native app and a browser tab at once resolvable: the host alone cannot answer it. `@rozenite/middleware` answers a `Rozenite.getEnvironment` CDP domain from inside the inspector proxy, by patching `createDevMiddleware` and composing with whatever `unstable_customInspectorMessageHandler` the embedder already passes (Expo passes one). The domain is answered locally and never forwarded — a device has no such domain and would reply -32601. Every failure mode degrades to a warning and the config-supplied host integration, which is also served through `/rozenite/app/config` and `__ROZENITE__` as the pre-handshake fallback. `@rozenite/vite-plugin` gains `integrations` in `rozenite.config.ts`, validated at config load so a typo fails the build, and always emitted into `rozenite.json` so a manifest answers the question even when the field is omitted. The middleware's `platform` option is renamed to `integration` while it is still unreleased, so it stops colliding with the device-OS meaning `platform` carries everywhere else. Nothing enforces compatibility yet — consumption follows separately. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VyZswYgjDu7i6zoeFct8kX
Follow-up cleanup on the previous commit, no behavior change. - Extract `requireDevMiddlewareInternal` into `resolve.ts`. Reaching past `@react-native/dev-middleware`'s `exports` map to require one of its internal modules is the version-sensitive part of both patches into that package, and it now lives in one place instead of two copies that could drift. - Drop two try/catch blocks in the CDP message handlers whose catch could never fire: reading a frame is total on its own, since `asRecord` narrows anything the wire can carry and every access after it is optional-chained. The remaining guards cover the two things that genuinely throw — code we don't own, and the reply send. - Narrow the local mirror of dev-middleware's connection type to the one member this code touches. - Give `getEntryPointHTML` an options object. It had grown to six positional parameters, several of them same-typed and optional, where a transposition would still type-check. - Drop `ROZENITE_INTEGRATION_LABEL`, which nothing reads yet; it belongs in the change that first displays a label. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VyZswYgjDu7i6zoeFct8kX
WIP. The middleware decided what to do by asking "is this Lynx?" in nine places. Behind those conditionals were four real differences between the hosts Rozenite serves, so this gives them a contract and two implementations. `@rozenite/integration` defines `RozeniteIntegrationProvider` — an id, an environment preflight, a debugger frontend path (or `null`), and whatever hooks the bundler's dev server needs — and ships the React Native implementation, which absorbs the React Native resolution, version check and the two `@react-native/dev-middleware` patches that used to live in the middleware. `@rozenite/lynx-dev` implements the Lynx side, where all four are no-ops or `null`. `initializeRozenite` now takes the implementation rather than a string it branches on, defaulting to React Native so `@rozenite/metro` and `@rozenite/repack` are unchanged. The unreleased `integration` config value has no setter left, so it is gone; everything that read it now reads `integration.id`. All nine conditionals are gone rather than renamed: three of them collapse into "does this integration have a frontend to serve?", which is the test of whether the contract earns its place. Still to come, deliberately not here: the source-map member lands with its first caller, and PR #448's capability profiles are reconciled once that merges. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VyZswYgjDu7i6zoeFct8kX
V3RON
force-pushed
the
claude/plugin-integration-compatibility-yj0odc
branch
from
August 31, 2026 08:22
2cc6fd4 to
bd7de21
Compare
Base automatically changed from
claude/plugin-integration-compatibility-yj0odc
to
main
August 31, 2026 09:27
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
Draft / WIP — opened for direction on the contract's shape before it grows further. Stacked on #456.
@rozenite/middlewaredecided what to do by asking "is this Lynx?" in nine places. Behind those conditionals sat four real differences between the hosts Rozenite serves, so this gives them a contract and two implementations.@rozenite/integrationdefinesRozeniteIntegrationProvider— anid, an environment preflight, a debugger frontend path (ornull), and whatever hooks a bundler's dev server needs — and ships the React Native implementation. That implementation absorbs the React Native resolution, the version check and the two@react-native/dev-middlewarepatches, which all move out of the middleware.@rozenite/lynx-devimplements the Lynx side, where all four members are no-ops ornull.initializeRozenitetakes the implementation, not a string it branches on, defaulting to React Native so@rozenite/metroand@rozenite/repackare untouched. The unreleasedintegrationconfig value has no setter left, so it is removed; everything that read it now readsintegration.id.Net 939 deletions against 131 insertions in the tracked diff, plus the new package.
Related Issue
Part of #455 — the abstraction the compatibility work kept pointing at.
Context
The acceptance test was deleting conditionals, not renaming them. Three of the nine collapse into
if (debuggerFrontendPath)— "does this integration have a frontend to serve?" — which is the shape that says the contract is real rather than anisLynxin a new coat. NoisLynx-style branch survives in the middleware.What is deliberately not in the contract. No source-map or symbolication member: it lands with its first caller, the same discipline applied to the CDP domain in #456 rather than building a dispatch table for one method.
packages/middleware/src/agent/**is untouched, so #448's capability profiles can be reconciled on their own terms once that merges — they are arguably a fifth member of this contract, but that is a follow-up, not a guess made here.Known open questions for review — the reason this is a draft:
verifyEnvironmentpreservesverifyReactNativeVersion's current behaviour, which exits the process on a version mismatch rather than throwing. The contract documents that rather than quietly changing process-exit semantics inside a refactor, but a thrown error is probably the better contract and would be a small follow-up.Resolved react-native to: …) was dropped rather than grow the interface for it.@rozenite/toolsnow exportsRozeniteLogLevelalongside theloggerit already exported, so the new package reuses that logger instead of adding a third copy of it.Testing
From the repository root, after
git fetch origin main:pnpm checks:affected— passedpnpm test:affected— 65/65 tasksintegration-domain.test.tsandintegration-domain.e2e.test.tsmoved to the new package with their assertions unchanged. The end-to-end test was re-confirmed after the move to fail when the dev-middleware patch is disabled, so it still fails for the reason it exists.middleware.test.ts's Lynx suite now builds a small fake implementation of the contract instead of passing{ integration: 'lynx' }, which is a fair proxy for how a third host would arrive.Not verified on a device: no Lynx app or React Native device was exercised; the Lynx implementation is all no-ops, and the React Native one is covered by the moved suites plus a new contract test.
Generated by Claude Code