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,146 @@
1
+ # Routing
2
+
3
+ There is no URL bar and no `history` object, so every router on this page
4
+ runs on an in-memory location. That is not a workaround — both libraries ship
5
+ a first-party memory backend, because both already support React Native and
6
+ testing.
7
+
8
+ What consistently does not survive is the _link_ element. `<Link>` renders an
9
+ `<a>` host in both routers, and there is no `<a>` here. Navigate imperatively
10
+ from a `<box onClick>` instead; that is two lines and it is all you lose.
11
+
12
+ Also worth saying up front: a desktop app usually wants stack or tab
13
+ navigation semantics rather than URL paths. These libraries give you the
14
+ matching machinery, not a back stack — build that on top if you need it.
15
+
16
+ ## wouter {#wouter}
17
+
18
+ **Out of the box — with `wouter/memory-location`.** wouter@3.10.0.
19
+
20
+ A 2 kB router: `<Router>`, `<Route>`, `<Switch>`, `useLocation`, pattern
21
+ params. Location sources are pluggable hooks and the package ships a
22
+ non-browser one, so there is no adapter to write.
23
+
24
+ ```jsx
25
+ import React from 'react';
26
+ import { createRoot } from 'react-x11';
27
+ import { Router, Route, Switch } from 'wouter';
28
+ import { memoryLocation } from 'wouter/memory-location';
29
+
30
+ const { hook, navigate } = memoryLocation({ path: '/' });
31
+
32
+ // the react-x11 replacement for <Link>
33
+ const NavText = ({ href, children }) => (
34
+ <box style={{ height: 20 }} onClick={() => navigate(href)}>
35
+ <text style={{ color: '#08c' }}>{children}</text>
36
+ </box>
37
+ );
38
+
39
+ const App = () => (
40
+ <window width={400} height={300} title="routed">
41
+ <box style={{ flexGrow: 1, flexDirection: 'column' }}>
42
+ <Router hook={hook}>
43
+ <box style={{ flexDirection: 'row', gap: 8, height: 20 }}>
44
+ <NavText href="/">home</NavText>
45
+ <NavText href="/about">about</NavText>
46
+ </box>
47
+ <Switch>
48
+ <Route path="/">
49
+ <text>home page</text>
50
+ </Route>
51
+ <Route path="/about">
52
+ <text>about page</text>
53
+ </Route>
54
+ <Route>
55
+ <text>not found</text>
56
+ </Route>
57
+ </Switch>
58
+ </Router>
59
+ </box>
60
+ </window>
61
+ );
62
+
63
+ (await createRoot()).render(<App />);
64
+ ```
65
+
66
+ - **Always mount the memory hook at the top.** A `<Route>` outside a
67
+ `<Router hook={...}>` falls back to `useBrowserLocation` and crashes
68
+ immediately with `ReferenceError: location is not defined`.
69
+ - **`<Link>` does not work.** It renders an `<a>` host element.
70
+ `<Link asChild>` avoids the `<a>` but still does not navigate: its click
71
+ guard checks `event.button !== 0`, and react-x11's synthetic
72
+ `MouseEvent.button` is the X button number, which is `1` for left.
73
+ - `useLocation()[1]` gives you the same `navigate` from inside components;
74
+ the module-level one from `memoryLocation` is just more convenient with a
75
+ single router.
76
+ - `useHashLocation` and `useBrowserLocation` are browser-only. Everything
77
+ else — matching, params, nesting, `useRoute` — is pure.
78
+ - `memoryLocation({ record: true })` keeps a `history` array, which is the
79
+ raw material for a back stack and handy to assert on in tests.
80
+
81
+ ## react-router {#react-router}
82
+
83
+ **Out of the box — `MemoryRouter`, and none of the DOM components.**
84
+ react-router 7.18.2.
85
+
86
+ The router core — `useRoutes`, `useNavigate`, `useParams`, `useLocation`,
87
+ loaders, actions — is DOM-free. Import from `react-router`, not
88
+ `react-router-dom`; the DOM package re-exports the core plus the browser
89
+ entry points, and the browser entry points are exactly what fails.
90
+
91
+ ```jsx
92
+ import React from 'react';
93
+ import { createRoot } from 'react-x11';
94
+ import {
95
+ MemoryRouter,
96
+ Routes,
97
+ Route,
98
+ useNavigate,
99
+ useLocation,
100
+ } from 'react-router';
101
+
102
+ function Home() {
103
+ const navigate = useNavigate();
104
+ return (
105
+ <box style={{ flexDirection: 'column' }}>
106
+ <text>home page</text>
107
+ <box style={{ height: 20 }} onClick={() => navigate('/about')}>
108
+ <text>go to about</text>
109
+ </box>
110
+ </box>
111
+ );
112
+ }
113
+
114
+ function About() {
115
+ const loc = useLocation();
116
+ return <text>{`about page (${loc.pathname})`}</text>;
117
+ }
118
+
119
+ (await createRoot()).render(
120
+ <window width={300} height={200} title="routed">
121
+ <box style={{ flexGrow: 1 }}>
122
+ <MemoryRouter initialEntries={['/']}>
123
+ <Routes>
124
+ <Route path="/" element={<Home />} />
125
+ <Route path="/about" element={<About />} />
126
+ <Route path="*" element={<text>not found</text>} />
127
+ </Routes>
128
+ </MemoryRouter>
129
+ </box>
130
+ </window>,
131
+ );
132
+ ```
133
+
134
+ - **`BrowserRouter` and `HashRouter` both fail** — they call
135
+ `createBrowserHistory`, which needs `window.history`. The error is
136
+ `react-x11: uncaught error ReferenceError: document is not defined` at
137
+ `getUrlBasedHistory`, and note that `render()` does not throw: react-x11
138
+ logs it and the window count stays 0.
139
+ - **`<Link>`, `<NavLink>` and `<Form>` all render `<a>`/`<form>` hosts** and
140
+ cannot work. Use `useNavigate()` on a `<box onClick>`.
141
+ - A `<Link>` with a bare string child hits the raw-text error before the
142
+ element error: `react-x11: raw text "go" must be wrapped in a <text>
143
+ element`. Both are in the
144
+ [register](../ecosystem.md#render-time).
145
+ - If you do not need loaders and actions, wouter above is the smaller
146
+ dependency by a wide margin.
@@ -0,0 +1,361 @@
1
+ # State management
2
+
3
+ Every store here is renderer-agnostic: they subscribe through React's own
4
+ `useSyncExternalStore` and never touch a host environment. The out-of-React
5
+ surface matters more in a desktop app than on the web — an X event callback,
6
+ an ntk timer or a D-Bus signal handler can write to the store directly, and
7
+ every subscribed component under every `<window>` re-renders.
8
+
9
+ Versions below are what was tested; anything newer in the same major is
10
+ expected to behave the same.
11
+
12
+ ## zustand {#zustand}
13
+
14
+ **Out of the box.** zustand@5.0.14.
15
+
16
+ A small unopinionated store: `create((set) => ({...}))` returns a hook that
17
+ components call with a selector. Subscription goes through
18
+ `useSyncExternalStore` imported from `react` itself, and the store is usable
19
+ outside React via `useStore.getState()` / `useStore.subscribe()`. The
20
+ default recommendation for app state: zero DOM, zero adapter.
21
+
22
+ ```jsx
23
+ import React from 'react';
24
+ import { createRoot } from 'react-x11';
25
+ import { create } from 'zustand';
26
+
27
+ const useStore = create((set) => ({
28
+ n: 0,
29
+ inc: () => set((s) => ({ n: s.n + 1 })),
30
+ }));
31
+
32
+ function App() {
33
+ const n = useStore((s) => s.n);
34
+ return (
35
+ <window width={200} height={100} title="counter">
36
+ <box style={{ padding: 8 }} onClick={() => useStore.getState().inc()}>
37
+ <text>count: {n}</text>
38
+ </box>
39
+ </window>
40
+ );
41
+ }
42
+
43
+ (await createRoot()).render(<App />);
44
+ ```
45
+
46
+ Because the store is module-level rather than tree-level, several windows
47
+ share it for free.
48
+
49
+ - `zustand/middleware`'s `persist` defaults to `localStorage`; pass a
50
+ `storage` backed by `node:fs` (`createJSONStorage(() => fileStorage)`).
51
+ - `zustand/traditional` (`createWithEqualityFn`) also works; the
52
+ shallow-compare helper is pure.
53
+
54
+ ## jotai {#jotai}
55
+
56
+ **Out of the box.** jotai@2.20.2.
57
+
58
+ Bottom-up atomic state: `atom(initial)` defines a unit of state,
59
+ `useAtom`/`useAtomValue`/`useSetAtom` read and write it, and derived atoms
60
+ recompute from their dependencies. Atoms are pure and the React bindings
61
+ import only `react`.
62
+
63
+ The explicit-store pattern is the right one for a desktop app: create the
64
+ store at startup, hand it to the tree, and let X event handlers or
65
+ background work call `store.set(...)`. One store shared by several
66
+ `<window>` trees keeps them in sync automatically.
67
+
68
+ ```jsx
69
+ import React from 'react';
70
+ import { createRoot } from 'react-x11';
71
+ import { atom, createStore, Provider, useAtomValue } from 'jotai';
72
+
73
+ const countAtom = atom(0);
74
+ const doubledAtom = atom((get) => get(countAtom) * 2);
75
+ const store = createStore();
76
+
77
+ function App() {
78
+ const doubled = useAtomValue(doubledAtom);
79
+ return (
80
+ <window width={200} height={100} title="jotai">
81
+ <text>doubled: {doubled}</text>
82
+ </window>
83
+ );
84
+ }
85
+
86
+ (await createRoot()).render(
87
+ <Provider store={store}>
88
+ <App />
89
+ </Provider>,
90
+ );
91
+ setInterval(() => store.set(countAtom, (n) => n + 1), 1000);
92
+ ```
93
+
94
+ - `jotai/utils`' `atomWithStorage` defaults to `localStorage`; supply a
95
+ custom storage (sync or async).
96
+ - Async atoms suspend; wrap consumers in `<React.Suspense>` — react-x11
97
+ renders fallbacks like any React 19 host — or use `loadable()`.
98
+
99
+ ## valtio {#valtio}
100
+
101
+ **Out of the box.** valtio@2.3.2.
102
+
103
+ Proxy-based state: `proxy(obj)` returns an object you mutate directly;
104
+ `useSnapshot(state)` returns an immutable snapshot and tracks which
105
+ properties were read, re-rendering only when those change.
106
+
107
+ Mutation-from-anywhere maps well onto X event handlers and ntk callbacks.
108
+ `subscribe(state, cb)` is handy for pushing state into non-React things —
109
+ writing a selection to the X clipboard, updating a window title
110
+ imperatively.
111
+
112
+ ```jsx
113
+ import React from 'react';
114
+ import { createRoot } from 'react-x11';
115
+ import { proxy, useSnapshot } from 'valtio';
116
+
117
+ const state = proxy({ n: 0, log: [] });
118
+
119
+ function App() {
120
+ const snap = useSnapshot(state);
121
+ return (
122
+ <window width={220} height={120} title="valtio">
123
+ <box
124
+ style={{ padding: 8 }}
125
+ onClick={() => {
126
+ state.n += 1;
127
+ state.log.push(Date.now());
128
+ }}
129
+ >
130
+ <text>clicked {snap.n} times</text>
131
+ </box>
132
+ </window>
133
+ );
134
+ }
135
+
136
+ (await createRoot()).render(<App />);
137
+ ```
138
+
139
+ - Valtio 2 no longer deep-clones snapshots the way v1 did in dev; read the
140
+ v2 migration notes if you are coming from v1 examples.
141
+ - `valtio/utils`' `devtools()` targets the Redux DevTools browser extension.
142
+ It is a no-op without one, and there is nothing to connect to under X11.
143
+
144
+ ## XState {#xstate}
145
+
146
+ **Out of the box.** xstate@5.32.5, @xstate/react@6.1.0.
147
+
148
+ `createMachine` describes states, events, guards and actions; `createActor`
149
+ interprets one; `@xstate/react` provides `useMachine`, `useActor`,
150
+ `useActorRef` and `useSelector`. The interpreter has no environment coupling
151
+ and the React bindings subscribe via `useSyncExternalStore`.
152
+
153
+ Desktop chrome is where statecharts earn their keep: drag-resize state,
154
+ modal and focus flows, a connection lifecycle (`connecting → connected →
155
+ retrying`), long-running jobs with cancellation. An actor started at app
156
+ level can outlive any single `<window>` and drive several of them.
157
+
158
+ ```jsx
159
+ import React from 'react';
160
+ import { createRoot } from 'react-x11';
161
+ import { createMachine, createActor } from 'xstate';
162
+ import { useSelector } from '@xstate/react';
163
+
164
+ const toggle = createMachine({
165
+ id: 'toggle',
166
+ initial: 'off',
167
+ states: {
168
+ off: { on: { TOGGLE: 'on' } },
169
+ on: { on: { TOGGLE: 'off' } },
170
+ },
171
+ });
172
+ const actor = createActor(toggle).start();
173
+
174
+ function App() {
175
+ const value = useSelector(actor, (s) => s.value);
176
+ return (
177
+ <window width={220} height={120} title="xstate">
178
+ <box
179
+ style={{ padding: 8 }}
180
+ onClick={() => actor.send({ type: 'TOGGLE' })}
181
+ >
182
+ <text>state: {value}</text>
183
+ </box>
184
+ </window>
185
+ );
186
+ }
187
+
188
+ (await createRoot()).render(<App />);
189
+ ```
190
+
191
+ - `useMachine(machine)` inside a component works too and gives you a
192
+ component-scoped actor; the external-actor pattern above is better for
193
+ machines shared across windows.
194
+ - `@statelyai/inspect` connects over WebSocket to Stately's editor. The
195
+ transport works from Node; the iframe inspector expects a browser.
196
+
197
+ ## Redux {#redux}
198
+
199
+ **Out of the box — pin `react-redux@>=9`.** react-redux@9.3.0,
200
+ @reduxjs/toolkit@2.12.0, redux@5.0.1.
201
+
202
+ react-redux 9 dropped the `react-dom`/`react-native` imports it used for
203
+ `unstable_batchedUpdates` and subscribes through React's own
204
+ `useSyncExternalStore`, so it is renderer-agnostic and needs no shim here.
205
+ redux core and RTK are pure JS throughout, listener and observable
206
+ middleware included.
207
+
208
+ ```jsx
209
+ import React from 'react';
210
+ import { createRoot } from 'react-x11';
211
+ import { configureStore, createSlice } from '@reduxjs/toolkit';
212
+ import { Provider, useSelector, useDispatch } from 'react-redux';
213
+
214
+ const counter = createSlice({
215
+ name: 'counter',
216
+ initialState: { n: 0 },
217
+ reducers: {
218
+ inc: (s) => {
219
+ s.n += 1;
220
+ }, // immer draft — mutation is fine
221
+ },
222
+ });
223
+ const store = configureStore({ reducer: { counter: counter.reducer } });
224
+
225
+ function Counter() {
226
+ const n = useSelector((s) => s.counter.n);
227
+ const dispatch = useDispatch();
228
+ return (
229
+ <box style={{ padding: 8 }} onClick={() => dispatch(counter.actions.inc())}>
230
+ <text>count: {n}</text>
231
+ </box>
232
+ );
233
+ }
234
+
235
+ (await createRoot()).render(
236
+ <Provider store={store}>
237
+ <window width={220} height={120} title="redux">
238
+ <Counter />
239
+ </window>
240
+ </Provider>,
241
+ );
242
+ ```
243
+
244
+ - **The version pin is the one thing you must know.** react-redux 8 imports
245
+ `react-dom` unconditionally, and the failure is a module-resolution error
246
+ at startup, not a runtime warning. Both of its failure modes — the npm
247
+ `ERESOLVE` on React 19 and the `Cannot find module 'react-dom'` behind
248
+ `--legacy-peer-deps` — are in the
249
+ [negative-results register](../ecosystem.md#install-and-import-time).
250
+ - RTK's `devTools: true` (the default) looks for the Redux DevTools
251
+ extension global; harmless when absent. `@redux-devtools/cli` is a remote
252
+ devtools server if you want inspection.
253
+ - RTK Query works, but `setupListeners()` wires
254
+ `window.addEventListener('focus'|'online')`, which are absent under Node.
255
+ Pass a custom handler to `setupListeners(dispatch, customHandler)` wired
256
+ to `<window onFocus>`, the same way as the
257
+ [TanStack Query adapter](data-fetching.md#tanstack-query).
258
+
259
+ ## Immer {#immer}
260
+
261
+ **Out of the box.** immer@11.1.15.
262
+
263
+ `produce(base, recipe)` runs a mutating recipe against a Proxy draft and
264
+ returns a structurally-shared immutable result. Curried `produce(recipe)` is
265
+ a drop-in `setState` updater. Pure JS, no environment assumptions — and it
266
+ is already inside `@reduxjs/toolkit`'s `createSlice` reducers and available
267
+ as zustand middleware (`zustand/middleware/immer`).
268
+
269
+ ```jsx
270
+ import React from 'react';
271
+ import { createRoot } from 'react-x11';
272
+ import { produce } from 'immer';
273
+
274
+ function App() {
275
+ const [state, setState] = React.useState({
276
+ items: [{ label: 'a', done: false }],
277
+ });
278
+ const toggle = (i) =>
279
+ setState(
280
+ produce((draft) => {
281
+ draft.items[i].done = !draft.items[i].done;
282
+ }),
283
+ );
284
+ return (
285
+ <window width={260} height={160} title="todo">
286
+ {state.items.map((it, i) => (
287
+ <box key={it.label} style={{ padding: 4 }} onClick={() => toggle(i)}>
288
+ <text>
289
+ {it.done ? '[x] ' : '[ ] '}
290
+ {it.label}
291
+ </text>
292
+ </box>
293
+ ))}
294
+ </window>
295
+ );
296
+ }
297
+
298
+ (await createRoot()).render(<App />);
299
+ ```
300
+
301
+ Nothing to adapt. Useful directly with `useState`/`useReducer` for the
302
+ nested state desktop apps accumulate — tree views, multi-pane layouts.
303
+
304
+ ## MobX {#mobx}
305
+
306
+ **Adapter — no code, but `react-dom` must be installed.** mobx@6.16.1,
307
+ mobx-react-lite@4.1.1.
308
+
309
+ Observable state via `makeAutoObservable`/`observable`, with
310
+ `mobx-react-lite`'s `observer()` HOC re-rendering components when the
311
+ observables they dereference change. mobx core is pure JS.
312
+
313
+ The wrinkle: **every build of mobx-react-lite unconditionally does
314
+ `import { unstable_batchedUpdates } from "react-dom"`** at module load, to
315
+ install React's batcher as mobx's reaction scheduler. In a react-x11 app
316
+ react-dom is not otherwise present, so the import fails at startup with
317
+ `Cannot find module 'react-dom'`.
318
+
319
+ The fix is `npm install react-dom`. Its top-level entry is Node-safe (it is
320
+ the same entry SSR uses), react-x11 never renders through it, and React 19's
321
+ `unstable_batchedUpdates` is a thin wrapper that batches correctly for any
322
+ renderer. With react-dom present, `observer` subscribes through
323
+ `use-sync-external-store` and mutations from X event handlers or timers
324
+ re-render the tree.
325
+
326
+ ```jsx
327
+ // prerequisite: npm install react-dom (satisfies mobx-react-lite's import;
328
+ // react-x11 does not render through it)
329
+ import React from 'react';
330
+ import { createRoot } from 'react-x11';
331
+ import { makeAutoObservable, runInAction } from 'mobx';
332
+ import { observer } from 'mobx-react-lite';
333
+
334
+ const store = makeAutoObservable({ n: 0 });
335
+
336
+ const App = observer(function App() {
337
+ return (
338
+ <window width={220} height={120} title="mobx">
339
+ <box
340
+ style={{ padding: 8 }}
341
+ onClick={() =>
342
+ runInAction(() => {
343
+ store.n += 1;
344
+ })
345
+ }
346
+ >
347
+ <text>count: {store.n}</text>
348
+ </box>
349
+ </window>
350
+ );
351
+ });
352
+
353
+ (await createRoot()).render(<App />);
354
+ ```
355
+
356
+ - Without react-dom the app fails at import time, not render time; the error
357
+ names mobx-react-lite in its require stack.
358
+ - `mobx-react` (the bigger sibling) layers on mobx-react-lite, so the same
359
+ note applies. Prefer `-lite`.
360
+ - Do not import from `react-dom` yourself. It is a compatibility shim here,
361
+ nothing more.