react-x11 2.52.0 → 2.53.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 (112) hide show
  1. package/README.md +10 -10
  2. package/docs/README.md +160 -314
  3. package/docs/accessibility.md +176 -229
  4. package/docs/appearance.md +77 -176
  5. package/docs/click-to-component.md +18 -51
  6. package/docs/clipboard.md +40 -59
  7. package/docs/components.md +518 -1036
  8. package/docs/dbus.md +61 -133
  9. package/docs/debugging.md +152 -245
  10. package/docs/desktop-calendar.md +51 -88
  11. package/docs/desktop.md +222 -347
  12. package/docs/devtools.md +50 -167
  13. package/docs/drag-and-drop.md +126 -204
  14. package/docs/ecosystem/animation.md +91 -125
  15. package/docs/ecosystem/data-fetching.md +38 -53
  16. package/docs/ecosystem/dev-tooling.md +159 -202
  17. package/docs/ecosystem/forms.md +61 -97
  18. package/docs/ecosystem/headless.md +64 -89
  19. package/docs/ecosystem/i18n.md +18 -21
  20. package/docs/ecosystem/icons.md +93 -126
  21. package/docs/ecosystem/layout.md +42 -52
  22. package/docs/ecosystem/routing.md +18 -29
  23. package/docs/ecosystem/state.md +71 -88
  24. package/docs/ecosystem/testing.md +131 -157
  25. package/docs/ecosystem/theming.md +119 -165
  26. package/docs/ecosystem.md +145 -179
  27. package/docs/elements.md +1091 -1774
  28. package/docs/embedding.md +83 -132
  29. package/docs/events.md +412 -695
  30. package/docs/extending.md +784 -1326
  31. package/docs/eyedropper.md +63 -122
  32. package/docs/filedialog.md +71 -107
  33. package/docs/frame.md +195 -339
  34. package/docs/gl.md +65 -62
  35. package/docs/globalmenu.md +63 -146
  36. package/docs/macos.md +531 -3234
  37. package/docs/notifications.md +33 -47
  38. package/docs/packaging.md +166 -309
  39. package/docs/permissions.md +9 -14
  40. package/docs/react-features.md +81 -106
  41. package/docs/remote.md +32 -76
  42. package/docs/scale.md +72 -186
  43. package/docs/styling.md +329 -547
  44. package/docs/system.md +200 -280
  45. package/docs/testing.md +63 -83
  46. package/docs/typescript.md +6 -48
  47. package/docs/uri-schemes.md +67 -107
  48. package/docs/wayland-backend.md +200 -459
  49. package/docs/windows-integrations.md +75 -131
  50. package/docs/windows.md +343 -1367
  51. package/package.json +3 -2
  52. package/src/Reconciler.js +2 -2
  53. package/src/a11y.js +1 -1
  54. package/src/backend/context2d.js +3 -3
  55. package/src/bootstrap.js +1 -1
  56. package/src/cocoa/app.js +6 -6
  57. package/src/cocoa/bezels.js +1 -1
  58. package/src/cocoa/fonts.js +1 -1
  59. package/src/cocoa/globalmenu.js +1 -1
  60. package/src/cocoa/keymap.js +1 -1
  61. package/src/cocoa/panewindow.js +1 -1
  62. package/src/cocoa/player.js +1 -1
  63. package/src/cocoa/presenter.js +2 -2
  64. package/src/cocoa/promotion.js +1 -1
  65. package/src/cocoa/relaunch.js +1 -1
  66. package/src/cocoa/threaded.js +1 -1
  67. package/src/cocoa/video.js +1 -1
  68. package/src/cocoa/window.js +4 -4
  69. package/src/components/Menu.js +7 -2
  70. package/src/components/Switch.js +1 -1
  71. package/src/components/native.js +1 -1
  72. package/src/dnd.js +2 -2
  73. package/src/glnodes.js +1 -1
  74. package/src/grid.js +1 -1
  75. package/src/layouts.js +1 -1
  76. package/src/nodes/animation.js +1 -1
  77. package/src/nodes/invalidate.js +1 -1
  78. package/src/nodes/layouthost.js +1 -1
  79. package/src/nodes/video.js +19 -8
  80. package/src/nodes/window/droptarget.js +1 -1
  81. package/src/pacing.js +1 -1
  82. package/src/refresh/index.js +2 -1
  83. package/src/types/video.d.ts +5 -2
  84. package/src/videobackend.js +28 -0
  85. package/src/videoframes.js +29 -16
  86. package/src/wayland/app.js +27 -0
  87. package/src/wayland/video.js +376 -0
  88. package/src/win32/a11y.js +1 -1
  89. package/src/win32/app.js +2 -2
  90. package/src/win32/fonts.js +1 -1
  91. package/src/win32/glarea.js +4 -4
  92. package/src/win32/overlay.js +1 -1
  93. package/src/win32/panewindow.js +3 -3
  94. package/src/win32/shell.js +1 -1
  95. package/src/win32/window.js +2 -2
  96. package/src/windowid.js +1 -1
  97. package/src/xvideo.js +189 -0
  98. package/docs/architecture/accessibility.md +0 -470
  99. package/docs/architecture/animation.md +0 -707
  100. package/docs/architecture/container-queries.md +0 -525
  101. package/docs/architecture/custom-layout.md +0 -334
  102. package/docs/architecture/drag-and-drop.md +0 -941
  103. package/docs/architecture/element-layer-contents.md +0 -622
  104. package/docs/architecture/frame-pacing.md +0 -255
  105. package/docs/architecture/grid-layout.md +0 -358
  106. package/docs/architecture/protocol-efficiency.md +0 -357
  107. package/docs/architecture/video.md +0 -1149
  108. package/docs/architecture/windowed-regions.md +0 -854
  109. package/docs/glx.md +0 -137
  110. package/docs/wayland.md +0 -545
  111. package/docs/windows-embedding.md +0 -271
  112. package/docs/windows-gl.md +0 -221
package/docs/frame.md CHANGED
@@ -12,61 +12,53 @@ import { Frame } from 'react-x11';
12
12
  ```
13
13
 
14
14
  `<Frame>` mounts a module of your application in a **process of its own** and
15
- embeds its output here, laid out like any other child. It is this package's
16
- iframe: the same composition of a display boundary and a process boundary,
17
- behind one element, and it works on X11, Cocoa and win32. The
18
- [Wayland backend](wayland-backend.md) has neither of the mechanisms below, so
19
- a `<Frame>` there fails at once: `fallback` renders and no pane is started.
20
-
21
- How the display boundary is drawn differs, and it is worth knowing which one
22
- you are on when reading a trace:
23
-
24
- - **X11** — the pane renders into a real window of its own and the host
25
- embeds it over [`<foreign>`](embedding.md). Two boundaries that already
26
- existed separately, with `<Frame>` as the contract between them.
27
- - **Cocoa** — there is no server to share, so the pane paints into
28
- `IOSurface`s created shared and presents by message; the host points one
29
- sublayer of its window at whichever surface the pane last presented. The
30
- pane has no `NSWindow` at all, and runs no AppKit. Every other window it
31
- makes — a menu, a dropdown's sheet, a tooltip, a dialog — it paints the
32
- same way, and the host shows in an `NSWindow` of its own (see "Sizing,
33
- stacking, input").
34
- - **Windows** — the same shape as Cocoa with a **DirectComposition surface
35
- handle** in the IOSurface's place: the pane makes the handle, draws into it
36
- through a composition swapchain, and duplicates it into the host, which
37
- gives it to a visual in its own window. The pane has no HWND.
38
- [windows-embedding.md](windows-embedding.md) has the reasoning; the one
39
- thing worth knowing from here is that the handle is named **once**, not per
40
- frame — after that the compositor scans out of whatever the pane last
41
- presented, so a pane frame costs no message, no fence and no copy.
42
-
43
- Either way the host owns layout and hit testing and the pane owns its
44
- drawing, which is the line that makes this **CPU offloading, not
45
- isolation**.
46
-
47
- What the process boundary buys is the part no amount of careful coding buys
48
- in-process:
49
-
50
- - **Parallelism where this framework is CPU-bound.** The pane's
51
- reconciliation, layout, text shaping and painting run on another core. A
52
- pane that grinds for 400ms does not delay the shell's input handling by a
53
- microsecond — `npm run examples:frame` makes the difference visible with a
54
- ticker you can watch freeze, or not.
15
+ embeds its output here, laid out like any other child — this package's
16
+ iframe, on X11, Cocoa and win32. On the [Wayland backend](wayland-backend.md)
17
+ a `<Frame>` fails at once: `fallback` renders and no pane is started.
18
+
19
+ - **X11** — the pane renders into a real window of its own, which the host
20
+ embeds over [`<foreign>`](embedding.md).
21
+ - **Cocoa** — the pane has no `NSWindow` and runs no AppKit: it paints into
22
+ shared `IOSurface`s the host shows in a layer of its window, and every other
23
+ window it makes (a menu, a dropdown's sheet, a tooltip, a dialog) the host
24
+ shows in an `NSWindow` of its own.
25
+ - **Windows** — the same, with a DirectComposition surface; the pane has no
26
+ HWND.
27
+
28
+ The host owns layout and hit testing, the pane its drawing: **CPU
29
+ offloading, not isolation**. What the process boundary buys:
30
+
31
+ - **Parallelism.** The pane's reconciliation, layout, text shaping and
32
+ painting run on another core; a pane that grinds for 400ms does not delay
33
+ the shell's input (`npm run examples:frame` shows it with a ticker).
55
34
  - **Crash isolation.** An uncaught throw, a native-module crash, an OOM: the
56
- pane dies, the `fallback` renders, `restart()` respawns. The shell never
57
- knew.
58
- - **Leak isolation.** A pane that leaks is a killable leak, bounded by the
59
- heap its `execArgv` gives it
60
- ([The pane process](#the-pane-process-flags-and-environment)), and its GC
61
- pauses are its own.
62
-
63
- And what it does not buy, said once here and again in
64
- [security.md](security.md): **it is not a security boundary.** On X11 the
35
+ pane dies, `fallback` renders, `restart()` respawns.
36
+ - **Leak isolation.** A leaking pane is a killable leak, bounded by the heap
37
+ its `execArgv` gives it, and its GC pauses are its own.
38
+
39
+ **It is not a security boundary** ([security.md](security.md)). On X11 the
65
40
  pane holds a full-privilege connection to the same X server — it can read
66
41
  keys, take screenshots, move windows — and on Cocoa it is an ordinary child
67
- process of your application with your file system and your network.
68
- `<Frame>` contains a pane's _failures_, not its intentions; code you would
69
- not run in-process is code you should not run at all.
42
+ process with your file system and your network. `<Frame>` contains a pane's
43
+ _failures_, not its intentions.
44
+
45
+ | prop | |
46
+ | --------------------- | ------------------------------------------------------------------------ |
47
+ | `src` | the pane module: a `URL` or an absolute path |
48
+ | `props` | data for the pane; functions in it become stubs |
49
+ | `style` | the pane's rect; `backgroundColor` shows while no pane frame does |
50
+ | `fallback` | element or `({ error, restart }) => …` for a pane that died or never ran |
51
+ | `bridge` | bridged contexts: `true` (default), `false`, or a list of keys |
52
+ | `display` | X display for the pane; default the inherited `$DISPLAY` |
53
+ | `execArgv`, `env` | flags and environment for the pane's process |
54
+ | `watchdog` | ms unanswered before the pane counts as stuck; 15000, or `false` |
55
+ | `focusable` | whether the pane joins the tab order, like any `<foreign>` |
56
+ | `onStarted`, `onExit` | the pane mounted; its process ended |
57
+ | `transport` | replaces the fork |
58
+ | `ref` | `{ restart(), pid }` |
59
+
60
+ Design notes (the display boundary per backend, and how a Cocoa pane's
61
+ frames, windows and videos travel): [design/frame.md](../design/frame.md).
70
62
 
71
63
  ## The pane module
72
64
 
@@ -97,27 +89,24 @@ if (!isFramed() && !process.env.REACT_X11_NO_AUTORUN) {
97
89
  }
98
90
  ```
99
91
 
100
- The pane process inherits the parent's `execArgv`, which is what carries a
101
- dev loader (tsx, the refresh loader) into it during development; a bundled
102
- application forks plain JavaScript and needs none. It connects to the
103
- display the host names in `display`, or `$DISPLAY` like everything else.
104
- What else it is started with is the `<Frame>`'s to say — see
92
+ The pane process inherits the parent's `execArgv`, which carries a dev loader
93
+ (tsx, the refresh loader) into it during development; a bundled application
94
+ forks plain JavaScript and needs none. It connects to the display the host
95
+ names in `display`, or `$DISPLAY`. What else it is started with is the
96
+ `<Frame>`'s to say — see
105
97
  [The pane process](#the-pane-process-flags-and-environment).
106
98
 
107
- On macOS a pane stays on its process's main thread, where an app moves onto
108
- a worker ([macos.md](macos.md#js-on-a-worker-a-ui-thread-of-the-bridges-own)):
109
- it has no AppKit for that thread to keep, and the IPC channel it talks to
110
- its host over is that thread's — a worker has no `process.send`. That holds
111
- under `--import react-x11/cocoa-main` too, which a pane inherits whenever its
112
- host was started under it.
113
-
114
- Nor is a macOS pane an app of its own: it launches with AppKit's
115
- `prohibited` activation policy, so it has no Dock tile and no ⌘-Tab entry
116
- and is never the active app — its host's window has the focus and forwards
117
- what the pane is owed. Launched with the bridge's default, `regular`, every
118
- pane was another `node` in the Dock beside its host, one for each tab of the
119
- components' browser example. A root that names `cocoa.activationPolicy`
120
- itself still has the one it names.
99
+ On macOS:
100
+
101
+ - A pane stays on its process's main thread, where an app moves onto a
102
+ worker ([macos.md](macos.md#js-on-a-worker-a-ui-thread-of-the-bridges-own)):
103
+ it has no AppKit for that thread to keep, and its IPC channel is that
104
+ thread's (a worker has no `process.send`). That holds under
105
+ `--import react-x11/cocoa-main` too, which a pane inherits from its host.
106
+ - A pane launches with AppKit's `prohibited` activation policy: no Dock tile,
107
+ no ⌘-Tab entry, never the active app — its host's window has the focus and
108
+ forwards what the pane is owed. A root that names `cocoa.activationPolicy`
109
+ itself still has the one it names.
121
110
 
122
111
  ## Props: a bag of data, and stubs for the functions in it
123
112
 
@@ -130,17 +119,16 @@ travel in the **same message**, so a theme flip and the state change that
130
119
  caused it land in the pane as one commit rather than a torn pair.
131
120
 
132
121
  Functions anywhere in the bag become **fire-and-forget stubs** on the pane
133
- side: calling one sends its arguments back and returns `undefined`. The
134
- arguments are sanitized on the way — functions inside them are dropped, the
135
- data around them kept — because a pane calls `onPick(item, ev)` the way it
136
- would call any handler, and `ev` is full of methods.
122
+ side: calling one sends its arguments back and returns `undefined`. Functions
123
+ inside the arguments are dropped and the data around them kept, so a pane can
124
+ call `onPick(item, ev)` the way it would call any handler, though `ev` is
125
+ full of methods.
137
126
 
138
127
  Callback identity follows function identity: a handler the parent keeps
139
128
  stable (`useCallback`) keeps its id across updates, and an id from the
140
- previous update still lands — one update of grace, which is what covers a
141
- click racing a props change. A stub stashed outside the props flow and
142
- called two updates later is dropped with a warning, not delivered to the
143
- wrong function.
129
+ previous update still lands — one update of grace, which covers a click
130
+ racing a props change. A stub stashed outside the props flow and called two
131
+ updates later is dropped with a warning, not delivered to the wrong function.
144
132
 
145
133
  Two rules of thumb that keep the wire small:
146
134
 
@@ -154,25 +142,18 @@ Two rules of thumb that keep the wire small:
154
142
 
155
143
  ## Context: bridged explicitly, theme by default
156
144
 
157
- React context cannot cross a process boundary, and most of it should not
158
- try: appearance, locale, desktop settings and fonts are resolved from the
159
- desktop identically on both sides, so each process just asks. The rule the
160
- bridge implements: **bridge what the app defines; let each process resolve
161
- what the desktop defines.**
162
-
163
- The theme is the one ambient thing the app authors, so it crosses **by
164
- default**: a `<ThemeProvider>` above a `<Frame>` reaches the pane as a real
165
- `ThemeProvider` around it — both routes, `useTheme()` and `$token` styles —
166
- and it is in the pane's very first commit, so there is no frame of default
167
- palette first. The bridged provider wraps the pane's **window** itself, the
168
- position it held in the host: the window's own background follows the app's
169
- palette rather than the pane process's desktop, and the window is the top
170
- of the node tree every `$token` beneath it resolves through — so a theme
171
- toggle in the host moves the pane's background and its static text, not
172
- only what re-renders through `useTheme()`. With no provider above the
173
- frame, nothing is bridged and the pane follows the desktop by itself: same
174
- answer, no bridge. A pane that wants its own look mounts its own provider,
175
- which wins below the bridged one the way an inner provider always wins.
145
+ React context cannot cross a process boundary, and most of it need not:
146
+ appearance, locale, desktop settings and fonts resolve identically on both
147
+ sides. The rule: **bridge what the app defines; let each process resolve what
148
+ the desktop defines.**
149
+
150
+ The theme crosses **by default**: a `<ThemeProvider>` above a `<Frame>` wraps
151
+ the pane's **window** as a real `ThemeProvider` — `useTheme()` and `$token`
152
+ styles alike — from the pane's very first commit, so the pane's background
153
+ and static text follow the app's palette and its toggles, not the pane
154
+ process's desktop. With no provider above the frame, nothing is bridged and
155
+ the pane follows the desktop by itself. A pane's own provider wins below the
156
+ bridged one, as an inner provider always does.
176
157
 
177
158
  For a context of your own, create it with `createFrameContext` in a module
178
159
  **both sides import** — which the pane already does, since its components
@@ -202,20 +183,18 @@ re-bridges automatically, because the pane's providers republish what they
202
183
  received.
203
184
 
204
185
  **Bridged values are data.** A value that will not structured-clone is
205
- dropped with a warning naming the key, and that is deliberate: a bridged
186
+ dropped with a warning naming the key. That is deliberate: a bridged
206
187
  `{ state, dispatch }` would hand live RPC stubs to every pane under the
207
- provider — invisible wiring, with an unanswerable question about which
208
- incarnation of a restarted pane holds which stub. Dispatchers travel in
209
- `props`, where the wiring is visible. `serialize`/`revive` options on
210
- `createFrameContext` cover a value that is data in spirit but a class in
211
- shape.
188
+ provider, invisibly, and a restarted pane would leave it unclear which
189
+ incarnation holds which stub. Dispatchers travel in `props`, where the wiring
190
+ is visible. `serialize`/`revive` options on `createFrameContext` cover a
191
+ value that is data in spirit but a class in shape.
212
192
 
213
- `bridge` is the off switch the default owes: `bridge={false}` bridges
214
- nothing, `bridge={['react-x11:theme', 'session']}` is an allowlist.
193
+ `bridge` is the off switch: `bridge={false}` bridges nothing,
194
+ `bridge={['react-x11:theme', 'session']}` is an allowlist.
215
195
 
216
- One honest limitation: the pane commits an IPC hop after the host, so a
217
- theme flip repaints the shell a frame before the panes. Every asynchronous
218
- boundary has this; nothing here pretends otherwise.
196
+ The pane commits an IPC hop after the host, so a theme flip repaints the
197
+ shell a frame before the panes.
219
198
 
220
199
  ## Lifecycle, from both sides
221
200
 
@@ -241,35 +220,31 @@ The pane's view:
241
220
  - **`useFrameClose(handler)`** — the host is letting the pane go: the
242
221
  `<Frame>` unmounted, or the host app is exiting. The handler may return a
243
222
  promise and may still call callback props — flushing through one is what
244
- it is for. The patience is real but bounded: the host asks by message,
245
- escalates to SIGTERM at 1.5s, SIGKILL at 4s. Not called on a crash; a
246
- close handler is a courtesy, and anything that must survive a crash
247
- belongs on disk before the close.
248
- - **`isFramed()`** — see above.
223
+ it is for. The patience is bounded: the host asks by message, escalates to
224
+ SIGTERM at 1.5s, SIGKILL at 4s. Not called on a crash; anything that must
225
+ survive a crash belongs on disk before the close.
226
+ - **`isFramed()`** — see [The pane module](#the-pane-module).
249
227
  - A host that dies without asking takes the IPC channel with it, and the
250
228
  pane exits on the disconnect — no orphans either way.
251
229
 
252
230
  ## Sizing, stacking, input
253
231
 
254
- The embedded pane is a [`<foreign>`](embedding.md), and its rules apply
255
- verbatim. The rect comes from `style` and yoga like any child — there is no
256
- content-driven sizing across the boundary. The pane's window is a real X
257
- child window, so **host-drawn content cannot overlap it**; a HUD or a
232
+ The embedded pane is a [`<foreign>`](embedding.md), and its rules apply. The
233
+ rect comes from `style` and yoga like any child — there is no content-driven
234
+ sizing across the boundary. On X11 the pane's window is a real X child
235
+ window, so **host-drawn content cannot overlap it**; a HUD or a
258
236
  "reconnecting…" banner belongs in a sibling `<popup>`. Pointer events over
259
237
  the pane are the pane's without the host ever seeing them; keys follow
260
- [embedding.md's focus rules](embedding.md#focus-and-who-gets-the-keys) —
261
- the host's handlers see every key first, and what they do not consume is
238
+ [embedding.md's focus rules](embedding.md#focus-and-who-gets-the-keys) — the
239
+ host's handlers see every key first, and what they do not consume is
262
240
  forwarded.
263
241
 
264
- On Cocoa and Windows the pointer can never be over a pane's window,
265
- because the pane has none. The host hit-tests the frame's box and forwards
266
- the input over the channel. The cursor travels the other way. The pane's
267
- tree names its cursor as a window's would, the pane sends it
268
- (`pane-cursor`), and the host shows it over the box. It is the box's
269
- default cursor, so a `cursor` in the frame's `style` still wins. On X11 the
270
- pane's own window carries its cursor, as any window does. Keys go over as
271
- the box's default action, as `<foreign>` forwards them, so a chord the host
272
- answered above the frame never reaches the pane, and with their modifiers.
242
+ On Cocoa and Windows the pane has no window for the pointer to be over: the
243
+ host hit-tests the frame's box and forwards the input. Keys go over, with
244
+ their modifiers, as the box's default action, as `<foreign>` forwards them, so
245
+ a chord the host answered above the frame never reaches the pane. The cursor
246
+ the pane's tree names is the box's default cursor, so a `cursor` in the
247
+ frame's `style` still wins. On X11 the pane's own window carries its cursor.
273
248
 
274
249
  **Tab goes through a pane as through any subtree.** The frame is one stop in
275
250
  the host's order, and the pane's own order hangs off it: Tab arriving at the
@@ -278,164 +253,61 @@ Tab while the frame has the focus is the pane's; and the pane running off an
278
253
  end of its order hands Tab back, and the host goes on from the frame. The
279
254
  pane lets go of its focus as it does, so the stop it was at shows no ring
280
255
  while the host's next one does. A modal open in the pane keeps Tab going
281
- round inside it. On X11 this is XEmbed's own chain — `FOCUS_IN` with
282
- `FOCUS_FIRST` or `FOCUS_LAST` one way, `FOCUS_NEXT` or `FOCUS_PREV` the other,
283
- which the pane's window answers and sends as an XEmbed client
284
- (`_initXEmbedTab`). On Cocoa and Windows it is the same two messages over the
285
- channel, `tabenter` and `pane-tab-out` (`EventManager._tabEnter`,
286
- `_tabOut`). Before it, the host sent Tab on and cycled its own focus as
287
- well, so Tab went between one of the pane's stops and one of the host's, and
288
- on X11 Tab went round the pane's stops for good.
289
-
290
- **A pane is drawn at the host's scale.** A pane process reads the screens
291
- when it starts, and the host read them when it did. A desk that changed in
292
- between gave the two different answers: a page forked for a new tab after
293
- the desk went to 1x (a mirror set for a projector) was laid out at 1x in a
294
- frame the host showed at 2x. Everything in it came out half the size of the
295
- tabs beside it, and every click it was sent landed twice as far in. So the
296
- host says what scale its window is drawn at, in the hello and in every
297
- `pane-rect` (`displayScale`), and a Cocoa or Windows pane takes that one
298
- instead of its own. Its window, its fonts and the windows it opens are made
299
- at it from the first commit, and a later `pane-rect` that names another
300
- scale lays the pane out again in the new unit. A `<box scale>` around the
301
- `<Frame>` is not part of it: the frame's box grows with the zoom, and the
302
- pane lays out at the window's scale in the room it was given.
256
+ round inside it.
257
+
258
+ **A pane is drawn at the host's scale.** On Cocoa and Windows the host tells
259
+ the pane the scale its window is drawn at, and the pane's window, fonts and
260
+ the windows it opens are made at that scale from the first commit — even if
261
+ the screens changed between the host's start and the pane's — and laid out
262
+ again when it changes. A `<box scale>` around the `<Frame>` is not part of
263
+ it: the frame's box grows with the zoom, and the pane lays out at the
264
+ window's scale in the room it was given.
303
265
 
304
266
  **A pane's other windows.** On X11 a `<popup>` in a pane is a real window on
305
- the pane's own connection. A Cocoa pane process runs no AppKit, so a window
306
- made there exists and is never shown — a `<Select>` in a pane took the press
307
- and opened nothing (#824). So the pane paints each window it makes besides
308
- its own into a shared ring, as it paints itself (`CocoaPaneSubwindow`,
309
- `src/cocoa/panewindow.js`), and the host makes the `NSWindow` and shows the
310
- ring in it (`src/cocoa/panehost.js`):
311
-
312
- - **Where it goes is the pane's to say.** Every `pane-rect` carries
313
- `screen`, where the frame's box is on the screen in device pixels, sent
314
- again when the box or the window moves. A popup the pane anchors to one of
315
- its nodes is placed against it exactly as a window's popup is
316
- (`windowOrigin`, src/anchor.js), and the host puts the window where the
317
- pane says.
318
- - **The window's life goes up, its input comes down.** `pane-window`
319
- messages make, map, move, resize, grab with and destroy it, and its
320
- `pane-present`s and `pane-cursor`s name it. Input on the host's window for
321
- it goes back as `pane-event`s naming it. A grab is the host's, so a press
322
- anywhere in the host's windows reaches the menu outside its bounds, which
323
- is the dismissal. A managed window — a `<Dialog>` — also sends back the
267
+ the pane's own connection. On Cocoa the host shows each window the pane makes
268
+ besides its own — a `<popup>`, a menu, a `<Select>`'s list, a `<Dialog>` — in
269
+ an `NSWindow` of its own:
270
+
271
+ - A popup the pane anchors to one of its nodes is placed against it exactly
272
+ as a window's popup is, and placed again when the frame's box or the window
273
+ moves.
274
+ - A grab is the host's, so a press anywhere in the host's windows reaches the
275
+ menu outside its bounds, which is the dismissal. A `<Dialog>` gets the
324
276
  user's moves and resizes and its close button.
325
- - **The window's focus goes down too.** The host's window gaining or losing
326
- it is sent to the pane as `focus`/`blur`, so a menu that closes when its
327
- window loses focus (`useDismissOnWindowBlur`) closes when the user goes to
328
- another application.
329
- - **Nothing waits on the host until it is listening.** These messages are
330
- held until the host's first `pane-rect`: it hears a pane from the moment
331
- it has a `<Frame>`, and knows what to do with them only once the pane is
332
- laid out in it. A window is then announced where it is, after that rect
333
- has placed what is anchored to the pane again.
334
- - **The pane's windows go with it.** A pane that exits or unmounts takes
335
- every window the host shows for it, and their grab.
336
-
337
- **A pane's videos.** A Cocoa pane runs no AppKit, so a player it made would
338
- open the item and never hear AVFoundation say a word — a `<video src>` in a
339
- pane sat on its poster and could not be started. So the host plays it
340
- (`src/cocoa/paneplayer.js`): the hello tells the pane the host plays for
341
- it, which is what makes `useSupports('mediaPlayback')` true there, and the
342
- pane's `<video>` holds a player whose verbs go up as `pane-player` messages
343
- and whose events come back down. The picture comes down the way the pane's
344
- own goes up, the other way round: the host copies the frame showing into
345
- one of a ring of three shared IOSurfaces and names it, and the pane copies
346
- it out into the element's surface. A pane has no layers to lift a part
347
- onto, so its video is always drawn, which costs the copy a covered video
348
- costs in a window — the frame once into the ring, once out of it. A host
349
- over a bridge with no player says nothing in the hello, and the pane
350
- refuses `src` as X11 does. A pane that exits takes its players, and their
351
- sound, with it.
352
-
353
- **A pane resized.** On Cocoa the host gives the pane's layer its new size as
354
- the window lays out, and the pane's frame of that size comes a pane frame
355
- later. Core Animation's default stretched the frame the layer had to the new
356
- bounds in between, so a page in a pane had its left column drawn scaled a
357
- little at every step of a window dragged wider, and then drawn back at its
358
- size. A pane whose frames of a new size come within the window's
359
- live-resize budget, `createRoot({ cocoa: { resizeWait } })` (50 ms by
360
- default), has its last frame shown at its size from the top left instead:
361
- cropped on an axis the layer shrank on, and on one it grew on, its last
362
- pixel carried over the rest — the last column across a window dragged
363
- wider, the last row down one dragged taller. A page's edge is mostly its
364
- background, so the strip continues it, a gradient included, where the
365
- `<Frame>`'s own background showed through before. Where the edge crosses
366
- content, a bottom row through a line of text, that content runs down the
367
- strip in thin lines for the frame it lasts. One that falls behind the
368
- budget is stretched across, which is the better picture of a long wait: a
369
- width moves where a page's lines break, and a squeezed page is nearer to the
370
- page laid out again than one cut off with a strip beside it. Down the pane
371
- it stays at its size, cropped or its last row carried down, since a height
372
- moves nothing a page lays out, and stretched down it was the page drawn too
373
- tall for as long as the wait lasted. Behind is
374
- judged by the frames that land: two in a row later than the budget, or none
375
- at all for longer than it, and a frame well inside it shows the next at its
376
- size again. A size the pane is never sent, because a newer one replaced it
377
- while a frame was being painted, is no frame late, and a fast drag of a
378
- page that keeps up stays at its size rather than taking one look at every
379
- step and the other at every frame. Each gesture starts out at its size, and
380
- `resizeWait: 0` stretches always, both ways, as before. An app can choose
381
- apart from the budget, which is also how long AppKit holds the window's
382
- edge for a frame: `createRoot({ cocoa: { paneStretch } })`, or
383
- `REACT_X11_COCOA_PANE_STRETCH`, is `behind` for all of the above, `never`
384
- for a last frame kept at its size and its edge carried over however late
385
- the pane is, and `always` for one stretched both ways. And the pane hears that the window is being resized
386
- live — AppKit's drag of its edge, from its begin to its end, which a pane
387
- running no AppKit has no window to hear — with every size and on its own
388
- where only that changes (`pane-rect`'s `live`, `pane-live`): its window's
389
- `liveResizing` is the host's, so core defers measuring its content floors
390
- for the drag's length and catches up once it ends, as a window of its own
391
- does, and an element can put off what it would rather do once the drag is
392
- over.
393
-
394
- **A pane's frames are named by id, and the id outlives nothing.** On Cocoa
395
- a present carries the IOSurface's id, and the host looks it up when it
396
- reads the message. A resize has the pane make a new ring of buffers, and
397
- the system gives a freed id to the next surface anyone makes — the pane's
398
- next ring, another tab's, the host's own window. A present still in the
399
- channel when its ring was freed was looked up as whichever surface had
400
- the id by then: the pane's next frame cleared or half drawn, or another
401
- tab's page, on glass for a frame at every few steps of a drag. So the host
402
- answers each present it has looked up (`pane-shown`), and the pane keeps a
403
- buffer it retired until the present naming it is answered: that is one
404
- buffer as a rule, and never more than a ring of them, the oldest freed
405
- first if the host stops answering. A pane's video comes down the same way
406
- round: the pane answers each frame once it has looked its buffer up, and
407
- the host keeps a buffer of a ring it retired — a stream that switched
408
- renditions — until the frame naming it is answered. A lookup that finds a
409
- surface of another size than its frame says is not copied out of at all.
410
-
411
- **A pane draws only into a buffer the host is done with.** The ring holds
412
- three buffers so that one present can be on its way while the next frame
413
- draws: one on the layer, one named in the channel, one free. Each is made
414
- the first time a frame takes it, copied over from the frame before: a drag
415
- gives each size one frame, and a ring made whole at each was two buffers
416
- made and cleared for nothing, 4.4 ms of every 12 ms frame of a drag of Zen
417
- Garden 101 in the browser example on a Mac. A host two
418
- presents behind — busy with a long frame of its own, a breakpoint's layout,
419
- a window resize — left the next frame nothing but the buffer on the layer or
420
- the one a present still in the channel named, and the frame drew into it
421
- anyway: the host then showed a frame half drawn. So a frame takes a buffer
422
- only once the host has answered a present after the last one that named it,
423
- and waits on the clock for the next answer when there is none. An answer
424
- says the host has looked the present up, not that the screen shows it, and
425
- a host back from a long frame has the buffer it showed throughout still on
426
- glass until its UI thread commits: so of the buffers the host is done with,
427
- a frame takes one the WindowServer has let go of too, and waits for one, a
428
- frame interval at most after the answer. A host that has not answered for
429
- 250 ms no longer holds the pane up.
430
-
431
- The same answer gates the frame an input gets on the spot
432
- (`flushPendingFrames`): a pane paints on an input only once its last present
433
- is answered and a frame interval has passed since it went, and otherwise the
434
- next frame on the clock answers it. The interval costs an input a few
435
- milliseconds at the display's rate, and is what keeps a pane at one frame a
436
- refresh: without it, input faster than the display had a frame each — 245 a
437
- second on a 120Hz panel, at 250 inputs a second — and the frames that went
438
- faster than the WindowServer let go of buffers drew into ones it still read.
277
+ - The host window's focus is sent to the pane as `focus`/`blur`, so a menu
278
+ that closes when its window loses focus (`useDismissOnWindowBlur`) closes
279
+ when the user goes to another application.
280
+ - A pane that exits or unmounts takes every window the host shows for it,
281
+ and their grab.
282
+
283
+ **A pane's videos.** On Cocoa the host plays a pane's `<video src>` for it, so
284
+ `useSupports('mediaPlayback')` is true in a pane whose host can play. A pane's
285
+ video is always drawn — it has no layer to lift it onto — so it costs the copy
286
+ a covered video costs in a window, twice: into a shared ring and out. A host
287
+ over a bridge with no player plays nothing, and the pane refuses `src` as X11
288
+ does. A pane that exits takes its players, and their sound, with it.
289
+
290
+ **A pane resized.** On Cocoa the pane's frame at a new size comes a pane frame
291
+ after the host's window lays out. What shows in between is set by
292
+ `createRoot({ cocoa: { paneStretch } })` or `REACT_X11_COCOA_PANE_STRETCH`:
293
+
294
+ - **`'behind'`** (the default) — while the pane keeps within the window's
295
+ live-resize budget, `createRoot({ cocoa: { resizeWait } })` (50 ms by
296
+ default), its last frame is shown at its size from the top left: cropped on
297
+ an axis the layer shrank on, its last column or row carried across one it
298
+ grew on. A pane two frames in a row later than the budget, or with none for
299
+ longer than it, is stretched across to its new width until a frame lands
300
+ well inside the budget; down the pane it stays at its size. Each gesture
301
+ starts out at its size, and `resizeWait: 0` stretches always, both ways.
302
+ - **`'never'`** — at its size, its edge carried over, however late the pane
303
+ is.
304
+ - **`'always'`** — stretched both ways.
305
+
306
+ A carried edge usually continues the page's background; where it crosses
307
+ content, that content runs down the strip in thin lines for a frame. The
308
+ pane's window's `liveResizing` is the host's, so content floors are measured
309
+ once the drag ends, as in a window of its own, and an element can put off
310
+ work until then too.
439
311
 
440
312
  `backgroundColor` in the frame's `style` is what shows before the pane's
441
313
  first frame and after one dies — the same server-painted rectangle
@@ -468,27 +340,25 @@ it inherits:
468
340
  `REACT_X11_FRAME` is how a pane knows it is one (`isFramed`), so the two
469
341
  are set after it and cannot be overridden here.
470
342
 
471
- Either is read by value: a pane started with the same flags in a new array
472
- — one written in place, as above, at every render — is the pane it was, and
473
- flags or variables that say something else restart it, as a new `src` does.
474
- Something a fork cannot take — a flag that is not a string — fails as a
475
- `spawn` error and starts nothing.
343
+ Either is read by value: the same flags in a new array — one written in
344
+ place, as above, at every render — keep the pane it was, and flags or
345
+ variables that say something else restart it, as a new `src` does. Something
346
+ a fork cannot take — a flag that is not a string — fails as a `spawn` error
347
+ and starts nothing.
476
348
 
477
349
  ## A pane that stops answering
478
350
 
479
351
  A pane whose event loop is stuck — a loop that never ends, a page's script
480
- with no timeout around it — stays `running` as far as anything else can
481
- tell: it is sent updates it never reads, its window stays up showing its
482
- last frame, and on X11 it holds the keyboard while the pointer is over it.
483
- Nothing about it fails, so nothing says so.
484
-
485
- So the host asks. About once a second it sends the pane a `ping`, and the
486
- pane answers from its event loop, which is the thing asked about. A pane
487
- that has not answered for `watchdog` ms — 15 seconds by default — has
488
- stopped, and goes the way a crashed one goes: the frame fails with phase
489
- `unresponsive`, `fallback` is shown and can `restart()` it, and the process
490
- is ended from outside, SIGTERM and then SIGKILL, since it cannot read an
491
- unmount. `onExit` reports it with `expected: false`.
352
+ with no timeout around it — would otherwise stay `running`: it is sent
353
+ updates it never reads, its window keeps showing its last frame, and on X11
354
+ it holds the keyboard while the pointer is over it.
355
+
356
+ So the host asks. About once a second it sends the pane a `ping`, which the
357
+ pane answers from its event loop. A pane that has not answered for
358
+ `watchdog` ms — 15 seconds by default — goes the way a crashed one goes: the
359
+ frame fails with phase `unresponsive`, `fallback` is shown and can
360
+ `restart()` it, and the process is ended from outside, SIGTERM and then
361
+ SIGKILL, since it cannot read an unmount. `onExit` reports it with `expected: false`.
492
362
 
493
363
  ```jsx
494
364
  <Frame
@@ -508,19 +378,17 @@ Three things keep it from ending a pane that is fine:
508
378
 
509
379
  - **The host's own late tick is no news of the pane's.** Where the host's
510
380
  timer comes late — the machine slept, or the host itself was stuck — the
511
- count starts again from that tick, rather than taking the time nobody
512
- was asking for as time the pane did not answer.
381
+ count starts again from that tick.
513
382
  - **A pane under the inspector is not watched.** `--inspect`,
514
383
  `--inspect-brk` or `--inspect-wait`, in its own `execArgv`, in this
515
384
  process's that it inherits, or in `NODE_OPTIONS`: a debugger stops a pane
516
385
  on purpose. The flags that only set the inspector up, which Node's test
517
- runner hands every test process, are not it.
386
+ runner hands every test process, do not count.
518
387
  - **`watchdog={false}`** watches nothing, for a pane whose work is long
519
388
  and synchronous by design.
520
389
 
521
- The ping and the pong are messages of the protocol, so a pane of an older
522
- react-x11 fails the handshake, which says why, rather than being ended for
523
- not answering a question it does not know.
390
+ A pane of an older react-x11 that does not know the ping fails the handshake,
391
+ which says why, rather than being ended for not answering.
524
392
 
525
393
  ## What a pane costs
526
394
 
@@ -533,48 +401,36 @@ not.
533
401
 
534
402
  ## The transport seam
535
403
 
536
- Everything between the host and the pane is eight message types over an
537
- injectable transport, and `transport` replaces the fork wholesale — it is
538
- how the tests run a real pane (module, root, window, props, callbacks,
539
- close handlers) in-process over a loopback pair against the in-process X
540
- server, with `structuredClone` standing in for the fork's serialization
541
- (`test/helpers/frame-loopback.js`, and `runFrameChild` in
542
- `src/frame/childmain.js` is the pane's whole bootstrap behind the same
543
- seam). It is also the door to running a pane somewhere other than a child
544
- process without `<Frame>` learning about it.
545
-
546
- A transport is handed what the fork would be — `{ src, display, execArgv,
547
- env }` — and a transport that still forks the pane, its own way (under a
548
- sandbox, with another runtime), forks the entry the default one does, which
549
- is on the exports map to be resolved: `fileURLToPath(import.meta.resolve(
404
+ `transport` replaces the fork wholesale: it is the door to running a pane
405
+ somewhere other than a child process without `<Frame>` learning about it
406
+ (react-x11's own tests run a real pane in-process over a loopback pair this
407
+ way). A transport is handed what the fork would be — `{ src, display,
408
+ execArgv, env }` — and returns the parent's end of the wire: `send`,
409
+ `onMessage`, `onExit`, and optionally `kill` and `pid` (`FrameTransport` in
410
+ the types).
411
+
412
+ A transport that still forks the pane, its own way (under a sandbox, with
413
+ another runtime), forks the entry the default one does, which is on the
414
+ exports map to be resolved: `fileURLToPath(import.meta.resolve(
550
415
  'react-x11/frame/child'))`. It is a process's entry, not a module to import:
551
416
  outside a fork it says so and exits.
552
417
 
553
418
  ## Known gaps
554
419
 
555
- Named so they are not rediscovered:
556
-
557
420
  - **Accessibility**: a pane registers on the AT-SPI bus as its own
558
421
  application. Orca reads it, and Tab crosses the boundary, but the tree is
559
422
  not unified under the host's.
560
423
  - **DevTools**: a pane is its own React tree; `REACT_X11_DEVTOOLS=1` in the
561
424
  pane process attaches a separate DevTools, not a merged one.
562
- - **No intrinsic size**: the host decides the rect; a `measure` protocol
563
- could exist and does not yet.
564
- - **A window a pane opens in its first commit is placed against a guess.**
565
- The host has not laid the pane out yet, so the pane does not know where
566
- it is. A popup anchored to a node is placed again when the first
567
- `pane-rect` comes, before anyone sees it; a `<Dialog>` open from the start
568
- is centred where the pane guessed it was, as it is not anchored.
569
- - **Windows** panes do not read `screen` yet: a popup a Windows pane
570
- anchors is placed against the screen's corner, the second half of what
571
- #824 fixed on Cocoa. Not run there.
425
+ - **No intrinsic size**: the host decides the rect.
426
+ - **A window a pane opens in its first commit is placed against a guess**,
427
+ since the host has not laid the pane out yet. A popup anchored to a node is
428
+ placed again before anyone sees it; a `<Dialog>` open from the start is
429
+ centred where the pane guessed it was.
430
+ - **Windows** panes do not know where their frame is on the screen yet: a
431
+ popup a Windows pane anchors is placed against the screen's corner. Not run
432
+ there.
572
433
  - **A pane hears its window's focus, not its frame's.** Tab out of a pane
573
434
  takes its focus with it, but a click on the host does not: the stop the
574
435
  pane had focused keeps its focus, and its ring, while the host's is
575
- elsewhere. On X11 the `<window embeddable>` underneath (created unmapped,
576
- waiting for an embedder — see [embedding.md](embedding.md)) speaks
577
- XEmbed's Tab and no more of its focus handshake — `FOCUS_CURRENT`,
578
- `FOCUS_OUT` and window activation are left to the forwarding rules above —
579
- and on Cocoa and Windows the pane is sent the host window's `focus` and
580
- `blur` alone.
436
+ elsewhere.