react-x11 2.51.0 → 2.52.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/README.md +87 -54
  2. package/docs/README.md +410 -0
  3. package/docs/accessibility.md +628 -0
  4. package/docs/appearance.md +416 -0
  5. package/docs/architecture/accessibility.md +470 -0
  6. package/docs/architecture/animation.md +707 -0
  7. package/docs/architecture/container-queries.md +525 -0
  8. package/docs/architecture/custom-layout.md +334 -0
  9. package/docs/architecture/drag-and-drop.md +941 -0
  10. package/docs/architecture/element-layer-contents.md +622 -0
  11. package/docs/architecture/frame-pacing.md +255 -0
  12. package/docs/architecture/grid-layout.md +358 -0
  13. package/docs/architecture/protocol-efficiency.md +357 -0
  14. package/docs/architecture/video.md +1149 -0
  15. package/docs/architecture/windowed-regions.md +854 -0
  16. package/docs/click-to-component.md +125 -0
  17. package/docs/clipboard.md +281 -0
  18. package/docs/components.md +1749 -0
  19. package/docs/dbus.md +277 -0
  20. package/docs/debugging.md +464 -0
  21. package/docs/desktop-calendar.md +284 -0
  22. package/docs/desktop.md +648 -0
  23. package/docs/devtools.md +250 -0
  24. package/docs/drag-and-drop.md +663 -0
  25. package/docs/ecosystem/animation.md +394 -0
  26. package/docs/ecosystem/data-fetching.md +201 -0
  27. package/docs/ecosystem/dev-tooling.md +436 -0
  28. package/docs/ecosystem/forms.md +313 -0
  29. package/docs/ecosystem/headless.md +219 -0
  30. package/docs/ecosystem/i18n.md +89 -0
  31. package/docs/ecosystem/icons.md +410 -0
  32. package/docs/ecosystem/layout.md +216 -0
  33. package/docs/ecosystem/routing.md +146 -0
  34. package/docs/ecosystem/state.md +361 -0
  35. package/docs/ecosystem/testing.md +500 -0
  36. package/docs/ecosystem/theming.md +540 -0
  37. package/docs/ecosystem.md +473 -0
  38. package/docs/elements.md +2762 -0
  39. package/docs/embedding.md +288 -0
  40. package/docs/events.md +1025 -0
  41. package/docs/extending.md +2242 -0
  42. package/docs/eyedropper.md +223 -0
  43. package/docs/filedialog.md +258 -0
  44. package/docs/frame.md +580 -0
  45. package/docs/gl.md +170 -0
  46. package/docs/globalmenu.md +278 -0
  47. package/docs/glx.md +137 -0
  48. package/docs/macos.md +3295 -0
  49. package/docs/notifications.md +164 -0
  50. package/docs/packaging.md +611 -0
  51. package/docs/permissions.md +148 -0
  52. package/docs/react-features.md +430 -0
  53. package/docs/remote.md +223 -0
  54. package/docs/scale.md +339 -0
  55. package/docs/security.md +132 -0
  56. package/docs/styling.md +1928 -0
  57. package/docs/system.md +599 -0
  58. package/docs/testing.md +514 -0
  59. package/docs/typescript.md +117 -0
  60. package/docs/uri-schemes.md +380 -0
  61. package/docs/wayland-backend.md +591 -0
  62. package/docs/wayland.md +545 -0
  63. package/docs/windows-embedding.md +271 -0
  64. package/docs/windows-gl.md +221 -0
  65. package/docs/windows-integrations.md +233 -0
  66. package/docs/windows.md +1404 -0
  67. package/package.json +10 -3
  68. package/src/bus.js +39 -26
  69. package/src/cli/create.d.ts +43 -0
  70. package/src/cli/create.js +305 -0
  71. package/src/cli/env.js +484 -0
  72. package/src/cli/index.js +29 -2
  73. package/src/cli/templates/AGENTS.template.md +195 -0
  74. package/src/cli/templates/DESIGN.template.md +269 -0
  75. package/src/desktopintegration.js +2 -1
  76. package/src/desktopsettings.js +193 -24
  77. package/src/filedialog.js +3 -3
  78. package/src/keyboardstate.js +28 -0
  79. package/src/node.d.ts +10 -10
  80. package/src/portal.js +31 -0
  81. package/src/screencolor.js +5 -10
  82. package/src/types/system.d.ts +8 -6
  83. package/src/wayland/app.js +59 -0
  84. package/src/wayland/clipboard.js +14 -7
  85. package/src/wayland/connection.js +3 -0
  86. package/src/wayland/context2d.js +129 -10
  87. package/src/wayland/protocols/index.json +6 -0
  88. package/src/wayland/protocols/xdg-foreign-unstable-v2.json +1 -0
  89. package/src/wayland/seat.js +28 -1
  90. package/src/wayland/swapchain.js +23 -0
  91. package/src/wayland/xkb.js +137 -0
@@ -0,0 +1,250 @@
1
+ # React DevTools
2
+
3
+ react-x11 speaks to the standalone React DevTools app over its WebSocket
4
+ bridge.
5
+
6
+ ## Setup
7
+
8
+ ```sh
9
+ npm i -D react-devtools-core # already a dev dep of this repo
10
+
11
+ # terminal 1 — the standalone UI (listens on :8097)
12
+ npx react-devtools
13
+
14
+ # terminal 2 — any app/example with the bridge enabled
15
+ REACT_X11_DEVTOOLS=1 npm run examples:dashboard
16
+ ```
17
+
18
+ Either order works: the backend retries every two seconds, so an app
19
+ started first attaches a moment after the UI comes up. Under
20
+ `npx react-x11 dev` the bridge is on by default wherever
21
+ `react-devtools-core` is installed — run `npx react-devtools` and the app
22
+ shows up, `REACT_X11_DEVTOOLS=0` turns it off — and where it is not
23
+ installed while a UI is listening, `dev` says what to install.
24
+
25
+ What you get:
26
+
27
+ - the **component tree**, live as the app updates;
28
+ - **props, hooks and state** when selecting a component, and editing any of
29
+ them — the edit goes through the reconciler's own override path, so the
30
+ app really re-renders;
31
+ - **highlight-on-hover** — hovering an element in the tree tints its rect
32
+ in the X11 window;
33
+ - **"highlight updates when components render"** — the rects that just
34
+ re-rendered are outlined, in DevTools' own colour ramp (teal for a first
35
+ render through amber for one that keeps re-rendering), fading out on the
36
+ backend's clock;
37
+ - the **element picker** — the crosshair in the DevTools toolbar. While it
38
+ is on, the pointer belongs to DevTools: motion tints whatever is under
39
+ it, a click selects that element in the tree, `Escape` gives up. The app
40
+ sees none of those events;
41
+ - the **style editor** — the box model and the flattened `style` of a
42
+ selected element, editable live. Host elements are hidden by the default
43
+ component filter, so turn that filter off (⚙ → Components → uncheck
44
+ "host components") to have something with a style to select;
45
+ - the Profiler's commit list and flamegraph, including **"reload and start
46
+ profiling"**, which re-execs the app with profiling already on so the
47
+ mount itself is recorded. **Not** its Timeline view, which needs
48
+ `injectProfilingHooks` — this reconciler build does not expose it, so
49
+ there is nothing to enable;
50
+ - **DevTools' own settings** — component filters, console patching — kept
51
+ between runs, in `$XDG_CACHE_HOME/react-x11/devtools.json`
52
+ (`~/Library/Caches/react-x11/devtools.json` on macOS).
53
+
54
+ With `REACT_X11_CLICK_TO_COMPONENT=1` as well, an Alt+Click also selects
55
+ the clicked element in the DevTools tree on its way to opening the source
56
+ ([click-to-component.md](click-to-component.md)).
57
+
58
+ `REACT_X11_DEVTOOLS_HOST` / `REACT_X11_DEVTOOLS_PORT` point the backend at
59
+ a non-default standalone (e.g. devtools on another machine — handy when the
60
+ X app runs on a headless box). `REACT_X11_DEVTOOLS_STATE` moves the
61
+ settings file.
62
+
63
+ DevTools has no network panel — nothing in its protocol carries network
64
+ data, and no injection would change that. For that, `node --inspect` and
65
+ `chrome://inspect` still work alongside all of this.
66
+
67
+ ## Opening the source from the DevTools panel
68
+
69
+ The inspected-element panel already shows where a component was written —
70
+ `file:///…/tasks.jsx:42:7`, from the same React 19 call-site stacks
71
+ click-to-component reads, absolute and source-mapped. Making that line
72
+ _clickable_ is a standalone-side setting, and it has two independent
73
+ routes:
74
+
75
+ - **Spawn an editor.** The standalone launches one itself, choosing it from
76
+ `REACT_EDITOR` (a full command line, shell-quoted), else — on macOS — by
77
+ looking through `ps x` for an editor that is already running, else
78
+ `VISUAL`, else `EDITOR`. It knows each editor's argument style — vim
79
+ takes `+42`, sublime and the JetBrains editors take `file:42` — so this
80
+ is the route to reach for:
81
+
82
+ ```sh
83
+ REACT_EDITOR=cursor npx react-devtools
84
+ ```
85
+
86
+ - **Open a URL.** ⚙ → General → "Open local files directly in your code
87
+ editor", with the VS Code preset or a custom template using `{path}`,
88
+ `{line}` and `{column}`. The frontend turns our `file://` source into a
89
+ plain path before filling it in. The setting lives in the standalone's
90
+ own storage, not the app's.
91
+
92
+ Both are configured where the standalone runs, which may be a different
93
+ machine from the app. Neither is something the app can inject: the source
94
+ location is all the backend is asked for, and it is already sent.
95
+ Click-to-component ([click-to-component.md](click-to-component.md)) is the
96
+ app-side answer to the same question, and the one that works when DevTools
97
+ runs somewhere without your editor on it.
98
+
99
+ The same backend, minus the socket, powers `react-x11/test`'s component
100
+ inspection: `inspect()`/`setHook` in
101
+ [testing.md](testing.md#inspectnode-and-sethook) drive the renderer
102
+ interface the backend registers at injection, entirely in-process — no
103
+ standalone app, no `ws`, no env variable. If a test run does set
104
+ `REACT_X11_DEVTOOLS=1`, the two share one renderer registration (see the
105
+ guard in `connect()`).
106
+
107
+ ## Watching the wire
108
+
109
+ The bridge is a plain WebSocket carrying text frames of `{"event": …,
110
+ "payload": …}` — no CDP, no binary framing. `scripts/devtools-proxy.mjs`
111
+ sits in the middle of it and prints what goes past, decoding the packed
112
+ `operations` arrays (the tree deltas) into readable lines:
113
+
114
+ ```sh
115
+ npx react-devtools # terminal 1 — the real UI on :8097
116
+ npm run devtools:proxy # terminal 2 — :8098 -> :8097
117
+ REACT_X11_DEVTOOLS=1 REACT_X11_DEVTOOLS_PORT=8098 npm run examples:dashboard
118
+ ```
119
+
120
+ ```
121
+ 0.008 ▲ app operations 193B
122
+ · renderer 1, root 1
123
+ · add root 1
124
+ · add 3 <Counter> (Function) under 2
125
+ 0.291 ▼ devtools inspectElement {"id":3,"rendererID":1,…}
126
+ ```
127
+
128
+ `--only`/`--skip` filter by event name, `--full` prints whole payloads, and
129
+ `--jsonl FILE` appends every frame for later analysis.
130
+
131
+ ## How it works (and its sharp edges)
132
+
133
+ `src/DevToolsIntegration.js`. When `REACT_X11_DEVTOOLS` is set, the hook is
134
+ installed from `createRoot()` rather than at import time — a
135
+ top-level await there would make every bundle of react-x11 ESM-only, which
136
+ rules out a single executable ([packaging.md](packaging.md)). The ordering
137
+ guarantee is the same: a commit can only follow a root, and there is no way
138
+ to get one without awaiting the install. It connects the backend and
139
+ registers the renderer via `injectIntoDevTools`.
140
+
141
+ The backend never stops trying to connect — a retry timer while no UI
142
+ listens, an open socket while one does — and either kept the process up
143
+ after the app's last window closed. Its code runs in a scope
144
+ (`inBackend`) where the timers it starts and the sockets it opens are
145
+ unref'd, entered at our connect call, at its socket's close and error
146
+ handlers, and at the timers it armed there; the globals are swapped only
147
+ for that synchronous stretch, so the app's own `setTimeout` and
148
+ `WebSocket` are untouched. The socket is `ws` from react-devtools-core's
149
+ own dependencies, which is why `ws` is not a separate install.
150
+
151
+ Under `react-x11 dev` the hook is installed earlier, before React Refresh
152
+ loads: Refresh installs a stub hook where it finds none, the backend
153
+ installs none where it finds one, and the stub cannot connect.
154
+
155
+ Notes for maintainers:
156
+
157
+ - **`injectIntoDevTools()` takes no arguments** in react-reconciler 0.34.
158
+ What it reports comes from the host config — `rendererPackageName`,
159
+ `rendererVersion`, `extraDevToolsConfig` — and an argument passed here is
160
+ ignored without a word, which is how react-x11 shipped for a while with
161
+ its renderer name and version quietly missing (#121). A test in
162
+ `test/smoke.test.js` asserts what reaches the hook, so the next
163
+ reconciler bump cannot repeat it.
164
+ - react-devtools-core v7 requires `initialize()` before
165
+ `connectToDevTools`, exposes its API on the **default export** under
166
+ node ESM interop, and expects browser-ish globals (`self`, `window`) —
167
+ all handled in `prepare()`.
168
+ - The Highlighter only emits `showNativeHighlight` for host instances that
169
+ implement `getClientRects()` returning a non-empty rect, and anything
170
+ with `getClientRects` is measured at mount through
171
+ `instance.ownerDocument.documentElement` — nodes therefore expose both
172
+ (see `Node.getClientRects` / the `ownerDocument` stub in
173
+ `src/nodes/`).
174
+ - Highlight events arrive on the backend agent
175
+ (`hook.reactDevtoolsAgent`, or the `'react-devtools'` hook event);
176
+ `attachHighlightAgent` maps the public instance to its owning window
177
+ node and paints a translucent overlay.
178
+ - **What makes the native paths run at all** is
179
+ `isReactNativeEnvironment()`, which the backend defines as
180
+ `window.document == null`. That is true for a node process with
181
+ `window = global`, so the backend takes React Native's route everywhere:
182
+ it _asks the host_ to draw the highlight and the update outlines, and to
183
+ drive the picker, instead of drawing into a DOM overlay of its own. Every
184
+ injection below is one of those asks.
185
+ - **Update outlines**: the backend counts updates, picks each rect's colour
186
+ and expires it on its own clock, then emits `drawTraceUpdates`
187
+ (`[{node, color}]` — the whole live set, every time) and
188
+ `disableTraceUpdates`. `attachTraceUpdatesAgent` groups them by window
189
+ and hands each root the rects; `WindowNode.setTraceUpdates` is a dumb
190
+ overlay that replaces rather than accumulates. It schedules those redraws
191
+ on `requestAnimationFrame`, which node does not have — `prepare()`
192
+ installs an unref'd `setTimeout` shim, and without it the first traced
193
+ commit throws inside the backend.
194
+ - **The picker**: with no `window.addEventListener` to install, the backend
195
+ emits `startInspectingNative` and waits. `attachPickerAgent` takes the
196
+ pointer through `setInspectHandler` (events.js) — motion, press and
197
+ release are answered and go no further — and calls `agent.selectNode()`
198
+ then `agent.stopInspectingNative(true)`. Cancelling from the DevTools
199
+ side arrives as a bridge message with no host-facing event, so the
200
+ picker also listens on `agent._bridge` directly; without that a
201
+ cancelled picker would keep swallowing the app's input.
202
+ - **The style editor** needs three things and is offered only if it gets
203
+ them: `resolveRNStyle` (our `flattenStyle`, minus state blocks, which are
204
+ not editable values), `nativeStyleEditorValidAttributes`
205
+ (`STYLE_PROP_NAMES`), and `instance.measure(cb)` on host instances for
206
+ the box model — RN's contract, implemented by `Node.measure`. Edits
207
+ arrive as `overrideValueAtPath` on `['style']` with the value wrapped in
208
+ an array, which works here only because a react-x11 `style` prop already
209
+ takes an array.
210
+ - **Reload and profile** is gated on a backend check that asks whether
211
+ synchronous XHR works, so `isReloadAndProfileSupported: true` is passed
212
+ explicitly. Registering a renderer does not start profiling, either — the
213
+ run that comes back from the restart starts it itself, before the first
214
+ commit, which is the only reason to restart at all.
215
+ - `localStorage`/`sessionStorage` are **defined** on the global rather than
216
+ assigned: reading `global.localStorage` first is what makes node print
217
+ its `--localstorage-file` warning on every DevTools run.
218
+ - **Hook names and sources need a deeper stack than V8 keeps.** React reads
219
+ each hook's location — and the custom-hook nesting the names come from —
220
+ off a stack captured at the hook dispatch, and `Error.stackTraceLimit` is
221
+ 10 by default. A loader leaves frames of its own between the hook and its
222
+ call site (`tsx`, which every `npm run examples:*` goes through, costs
223
+ about ten), so the identifying frames fall off the end: `hookSource`
224
+ comes back `{fileName: null, lineNumber: null, …}`, the frontend logs
225
+ "Hook source code location not found", and the element shows its hooks
226
+ with no names and no source lines (#514). `prepare()` raises the limit to
227
+ 50 when the bridge is enabled, and only upwards, so an app that asked for
228
+ more keeps it. `test/devtools-hooks.test.js` squeezes the budget and
229
+ asserts the names and sources still come back over the bridge.
230
+ - **Selecting a component with hooks can blank the DevTools window.** That
231
+ is the standalone app's own renderer process aborting, upstream of
232
+ anything the host sends: it happens with every hook source complete, and
233
+ under runtimes with no loader at all. Turning ⚙ → Components → "Always
234
+ parse hook names for the selected element" off makes it intermittent
235
+ rather than reliable; restarting `npx react-devtools` reconnects to the
236
+ app, which is unaffected.
237
+ - `test/devtools.test.js` drives the real backend over a socket for all of
238
+ the above; the fake-agent tests in `test/smoke.test.js` prove the drawing
239
+ but not that the backend still asks for it.
240
+
241
+ ## Other debugging aids
242
+
243
+ - `REACT_X11_DEBUG_LAYOUT=1` — outline every laid-out node, color-coded by
244
+ tree depth.
245
+ - Refs: drawn elements expose the retained node (`abs` rect, `scrollTo`,
246
+ …); `<window>`/`<popup>` expose the live ntk window (`getContext('2d')`,
247
+ `queryTree`, `setCursor`, …).
248
+ - The in-process X server (`x11/lib/xserver`) renders headlessly and reads
249
+ pixels back — see `test/integration.test.js` for the harness pattern,
250
+ which is also how the README screenshots are generated.