Skip to content

feat: name the framework being debugged in the Rozenite app - #449

Merged
V3RON merged 5 commits into
mainfrom
claude/framework-label-devtools-footer-htwlyi
Aug 23, 2026
Merged

feat: name the framework being debugged in the Rozenite app#449
V3RON merged 5 commits into
mainfrom
claude/framework-label-devtools-footer-htwlyi

Conversation

@V3RON

@V3RON V3RON commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Description

Names the framework a Rozenite window is attached to, now that three of them are supported and nothing said which one you were looking at.

  • The standalone app names it in the status footer, next to the connection badge, and in its own window title: Lynx · Pixel 8 - Rozenite. That covers both places this app runs — a browser tab shows the title directly, and the Electron shell picks it up through Electron's default page-title propagation (the title: 'Rozenite' in packages/electron-app/src/main.js is only the value before the page loads).
  • React Native DevTools keeps the title it sets itself. Rozenite does not touch it — @rozenite/runtime is unchanged against main.
  • @rozenite/lynx-dev now answers ReactNativeApplication.enable with the metadataUpdated event a device implementing that domain would have sent, so a Lynx target reports its own framework instead of the host having to infer one.

Related Issue

Closes #450

Context

The framework is read per target, from ReactNativeApplication.metadataUpdated — the event React Native already sends unprompted once the domain is enabled. Nothing new is invented on the wire for React Native; it was already reporting enough, and no code was reading it.

Why integrationName and not platform. platform means the device OS everywhere else in this domain — React Native sends ios/android from RCTPlatformName and its Android equivalent — and a Lynx app runs on those same two, so platform: "lynx" would be a category error. integrationName is the free-form "which integration drives this target" field; React Native's own values there are strings like iOS Bridge (RCTBridge). The reader's precedence: a known integrationName wins, then platform === "web" (what @rozenite/chrome-extension reports, and the only framework that is a platform), otherwise React Native. That default matters — a future OS must not silently cost the label.

Why the bridge has to speak here. translate-host-message.ts already answers ReactNativeApplication.enable locally and never forwards it, because Lynx has no such domain and a -32601 would fail the connection into a permanent retry loop. Having claimed to enable the domain, it now also has to speak for it — otherwise a host that enables it and waits learns nothing, forever. Every field in the synthesized event is a real value DebugRouter reported about the client (appDisplayName, deviceName, platform from client.os, appIdentifier when present); reactNativeVersion is omitted rather than faked. HostAction's reply gained an optional events[], and the socket route resolves the client per message rather than capturing it at open, since a reload mints a fresh one.

No dev-server involvement. An earlier revision of this branch also reported the server's platform in /rozenite/app/config as a pre-handshake fallback. It answered a different question — which platform the server serves, not what the target is — so it was removed; middleware.ts and app/src/config.ts are unchanged against main. The footer and title fill in once the handshake produces the event, and getTarget() keeps the last known answer so neither blinks away while reloading or reconnecting.

Note that the label never says "Web" in practice today: Rozenite for Web is debugged through React Native DevTools, which this PR leaves alone, so a web target never reaches this app. The reader handles it anyway — it costs nothing and it is what the extension already reports — and the same is true in reverse for Lynx, whose dev server serves no Fusebox frontend.

Testing

Automated, from the repository root after git fetch origin main:

  • pnpm checks:affected — 102/102 tasks
  • pnpm test:affected — 63/63 tasks

New tests:

  • @rozenite/lynx-dev — the metadata event's exact payload, the no-bundle-id and no-client-connected paths, that Runtime.addBinding stays a bare reply, and a real-socket test over http + ws asserting the reply arrives before the event.
  • @rozenite/app — the metadata reader's precedence (Lynx, web, React Native's own integration names, unreadable payloads), that the connection notifies subscribers without a status change, that the framework survives a reconnect, the title builder's fallbacks (either part missing degrades to something readable rather than a stray separator), and that the rendered app titles its window once the target reports.

Not verified on a device: no Lynx device was available in this environment, so the Lynx handshake was exercised through the fakes above rather than end to end. The Electron title path is likewise reasoned from main.js having no page-title-updated handler, not observed — worth a quick look when someone next runs the shell. With titleBarStyle: 'hidden' the title is not drawn in the window frame, so what changes there is the window's name in Mission Control, alt-tab and the taskbar rather than anything in-window.

claude added 5 commits August 21, 2026 17:51
Rozenite now debugs React Native, Lynx and web apps, and nothing in the UI
said which one a window was showing.

React Native DevTools gets it from the target's own application metadata
(`ReactNativeApplication.metadataUpdated`), where React Native reports the
device OS and the Chrome extension reports `web`, and puts it in front of
the window title so several open windows stay tellable apart. The frontend
rebuilds that title from its own parts on every metadata change, so the
label is re-applied on each title mutation rather than written once.

The standalone app gets it from the dev server instead: a Lynx dev server
answers `/json/list` in Metro's dialect on purpose, so the connection gives
nothing away and `/rozenite/app/config` now reports the platform, which the
status footer names next to the connection badge.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EmWnUnqTJoiJ9Kztnz8Ucx
Three platforms can now be behind a Rozenite window, and nothing said
which one. React Native DevTools prefixes the framework onto its window
title, and the standalone app names it in the status footer.

Each target reports its own framework over the metadata event React
Native already sends: `@rozenite/lynx-dev`'s bridge, which answers
`ReactNativeApplication.enable` on behalf of a device with no such
domain, now follows that reply with the `metadataUpdated` event the
device would have sent. The framework goes in `integrationName` — the
free-form "which integration drives this target" field — so `platform`
keeps meaning the device OS, as it does everywhere else in the domain.

The dev server also reports its platform in `/rozenite/app/config`. It
is the coarser answer (it describes the server, not the target), so the
device's own answer wins; it exists to fill the gap before a device has
connected, which is what keeps the footer readable while connecting.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EmWnUnqTJoiJ9Kztnz8Ucx
The dev server's platform answered a different question — which platform
*it* serves, not what this target is — so the footer now waits for the
target's own `ReactNativeApplication.metadataUpdated` and shows nothing
until the handshake produces it. Drops `platform` from
`/rozenite/app/config`, restoring that payload to what it was.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EmWnUnqTJoiJ9Kztnz8Ucx
Naming the framework inside a window already titled "React Native
DevTools" says nothing, so the label is now written only when the target
is something else — a web page today. Relabelling to React Native strips
a stale label rather than leaving it behind.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EmWnUnqTJoiJ9Kztnz8Ucx
React Native DevTools sets its own window title and Rozenite no longer
touches it — the title tracker and its supporting type declaration are
gone, leaving @rozenite/runtime unchanged against main. The standalone
app instead titles its own window ("Lynx · Pixel 8 - Rozenite"), which
covers both places that app runs: a browser tab directly, and the
Electron shell through Electron's default page-title propagation.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EmWnUnqTJoiJ9Kztnz8Ucx
@V3RON V3RON changed the title feat: name the framework being debugged in the DevTools title and app footer feat: name the framework being debugged in the Rozenite app Aug 22, 2026
@V3RON
V3RON merged commit 1bbf5d2 into main Aug 23, 2026
4 checks passed
@V3RON
V3RON deleted the claude/framework-label-devtools-footer-htwlyi branch August 23, 2026 19:30
V3RON added a commit that referenced this pull request Aug 31, 2026
`main` gained a `Framework` label read from
`ReactNativeApplication.metadataUpdated` while this branch was open, so
`device-connection.ts` carried two independent answers to "what is this
target": that label, and the probe's `targetIsWeb`. Two readings of one
fact can disagree — a target reporting `platform: 'web'` without a DOM
would light up the footer and a compatibility gate differently — and the
metadata reading is the racy one this branch exists to stop relying on.

`resolveFramework` now takes every signal and applies them in precedence
order: a known `integrationName` first, since it is the only thing that
can identify Lynx and the probe cannot see it at all; then the probe,
which is the same signal that resolves a target's `RozeniteIntegration`;
then `platform` as a display-only fallback for when the probe could not
answer, so the label still appears. The gate is never handed that
fallback — it is told the target is unknown instead.

The label still waits for the metadata event, as #449 made it. Publishing
one from the probe alone would read a Lynx target as "React Native" until
the integration name corrected it, trading a label that appears once for a
visible flicker.

Claude-Session: https://claude.ai/code/session_01AEzqE9P3sGFinTPJGK8CBi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Show which framework is being debugged in the DevTools title and the app footer

2 participants