react-x11 2.51.1 → 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 (121) hide show
  1. package/README.md +94 -61
  2. package/docs/README.md +256 -0
  3. package/docs/accessibility.md +575 -0
  4. package/docs/appearance.md +317 -0
  5. package/docs/click-to-component.md +92 -0
  6. package/docs/clipboard.md +262 -0
  7. package/docs/components.md +1231 -0
  8. package/docs/dbus.md +205 -0
  9. package/docs/debugging.md +371 -0
  10. package/docs/desktop-calendar.md +247 -0
  11. package/docs/desktop.md +523 -0
  12. package/docs/devtools.md +133 -0
  13. package/docs/drag-and-drop.md +585 -0
  14. package/docs/ecosystem/animation.md +360 -0
  15. package/docs/ecosystem/data-fetching.md +186 -0
  16. package/docs/ecosystem/dev-tooling.md +393 -0
  17. package/docs/ecosystem/forms.md +277 -0
  18. package/docs/ecosystem/headless.md +194 -0
  19. package/docs/ecosystem/i18n.md +86 -0
  20. package/docs/ecosystem/icons.md +377 -0
  21. package/docs/ecosystem/layout.md +206 -0
  22. package/docs/ecosystem/routing.md +135 -0
  23. package/docs/ecosystem/state.md +344 -0
  24. package/docs/ecosystem/testing.md +474 -0
  25. package/docs/ecosystem/theming.md +494 -0
  26. package/docs/ecosystem.md +439 -0
  27. package/docs/elements.md +2079 -0
  28. package/docs/embedding.md +239 -0
  29. package/docs/events.md +742 -0
  30. package/docs/extending.md +1700 -0
  31. package/docs/eyedropper.md +164 -0
  32. package/docs/filedialog.md +222 -0
  33. package/docs/frame.md +436 -0
  34. package/docs/gl.md +173 -0
  35. package/docs/globalmenu.md +195 -0
  36. package/docs/macos.md +592 -0
  37. package/docs/notifications.md +150 -0
  38. package/docs/packaging.md +468 -0
  39. package/docs/permissions.md +143 -0
  40. package/docs/react-features.md +405 -0
  41. package/docs/remote.md +179 -0
  42. package/docs/scale.md +225 -0
  43. package/docs/security.md +132 -0
  44. package/docs/styling.md +1710 -0
  45. package/docs/system.md +519 -0
  46. package/docs/testing.md +494 -0
  47. package/docs/typescript.md +75 -0
  48. package/docs/uri-schemes.md +340 -0
  49. package/docs/wayland-backend.md +332 -0
  50. package/docs/windows-integrations.md +177 -0
  51. package/docs/windows.md +380 -0
  52. package/package.json +11 -3
  53. package/src/Reconciler.js +2 -2
  54. package/src/a11y.js +1 -1
  55. package/src/backend/context2d.js +3 -3
  56. package/src/bootstrap.js +1 -1
  57. package/src/bus.js +39 -26
  58. package/src/cli/create.d.ts +43 -0
  59. package/src/cli/create.js +305 -0
  60. package/src/cli/env.js +484 -0
  61. package/src/cli/index.js +29 -2
  62. package/src/cli/templates/AGENTS.template.md +195 -0
  63. package/src/cli/templates/DESIGN.template.md +269 -0
  64. package/src/cocoa/app.js +6 -6
  65. package/src/cocoa/bezels.js +1 -1
  66. package/src/cocoa/fonts.js +1 -1
  67. package/src/cocoa/globalmenu.js +1 -1
  68. package/src/cocoa/keymap.js +1 -1
  69. package/src/cocoa/panewindow.js +1 -1
  70. package/src/cocoa/player.js +1 -1
  71. package/src/cocoa/presenter.js +2 -2
  72. package/src/cocoa/promotion.js +1 -1
  73. package/src/cocoa/relaunch.js +1 -1
  74. package/src/cocoa/threaded.js +1 -1
  75. package/src/cocoa/video.js +1 -1
  76. package/src/cocoa/window.js +4 -4
  77. package/src/components/Menu.js +7 -2
  78. package/src/components/Switch.js +1 -1
  79. package/src/components/native.js +1 -1
  80. package/src/desktopintegration.js +2 -1
  81. package/src/desktopsettings.js +193 -24
  82. package/src/dnd.js +2 -2
  83. package/src/filedialog.js +3 -3
  84. package/src/glnodes.js +1 -1
  85. package/src/grid.js +1 -1
  86. package/src/keyboardstate.js +28 -0
  87. package/src/layouts.js +1 -1
  88. package/src/node.d.ts +10 -10
  89. package/src/nodes/animation.js +1 -1
  90. package/src/nodes/invalidate.js +1 -1
  91. package/src/nodes/layouthost.js +1 -1
  92. package/src/nodes/video.js +19 -8
  93. package/src/nodes/window/droptarget.js +1 -1
  94. package/src/pacing.js +1 -1
  95. package/src/portal.js +31 -0
  96. package/src/refresh/index.js +2 -1
  97. package/src/screencolor.js +5 -10
  98. package/src/types/system.d.ts +8 -6
  99. package/src/types/video.d.ts +5 -2
  100. package/src/videobackend.js +28 -0
  101. package/src/videoframes.js +29 -16
  102. package/src/wayland/app.js +86 -0
  103. package/src/wayland/clipboard.js +14 -7
  104. package/src/wayland/connection.js +3 -0
  105. package/src/wayland/context2d.js +129 -10
  106. package/src/wayland/protocols/index.json +6 -0
  107. package/src/wayland/protocols/xdg-foreign-unstable-v2.json +1 -0
  108. package/src/wayland/seat.js +28 -1
  109. package/src/wayland/swapchain.js +23 -0
  110. package/src/wayland/video.js +376 -0
  111. package/src/wayland/xkb.js +137 -0
  112. package/src/win32/a11y.js +1 -1
  113. package/src/win32/app.js +2 -2
  114. package/src/win32/fonts.js +1 -1
  115. package/src/win32/glarea.js +4 -4
  116. package/src/win32/overlay.js +1 -1
  117. package/src/win32/panewindow.js +3 -3
  118. package/src/win32/shell.js +1 -1
  119. package/src/win32/window.js +2 -2
  120. package/src/windowid.js +1 -1
  121. package/src/xvideo.js +189 -0
@@ -0,0 +1,195 @@
1
+ <!-- BEGIN:react-x11-agent-rules -->
2
+
3
+ # This is react-x11, not react-dom
4
+
5
+ This app is React drawing native desktop windows with react-x11
6
+ {{version}}. There is no DOM, no browser and no CSS. What you know about
7
+ React carries over: components, hooks, state, context, Suspense, error
8
+ boundaries. What you know about the DOM does not. Before writing UI, read the
9
+ docs installed with the package. They match the version this app runs and
10
+ are newer than your training data.
11
+
12
+ ## Where to look
13
+
14
+ **Docs**, installed with the package (paths from this file's directory):
15
+ `node_modules/react-x11/docs/`
16
+
17
+ | page | for |
18
+ | --------------------------------------------------- | ---------------------------------------------------------------------------- |
19
+ | `react-features.md` | **read first**: where a DOM habit breaks (refs, measuring, portals, effects) |
20
+ | `elements.md` | the host elements and their props |
21
+ | `styling.md` | the `style` prop: layout, paint, `:hover` blocks, theme tokens |
22
+ | `components.md` | `Button`, `Select`, `Dialog`, `MenuBar`, `Tabs`, `Table`, `SplitPane`, … |
23
+ | `events.md` | events, focus, keys, shortcuts |
24
+ | `appearance.md` | light and dark, the accent, reduced motion |
25
+ | `globalmenu.md` | where `MenuBar` goes on each platform |
26
+ | `testing.md` | `react-x11/test`: render headless, query, click, type, write a PNG |
27
+ | `accessibility.md` | roles, names, what a screen reader hears |
28
+ | `filedialog.md`, `notifications.md`, `clipboard.md` | the desktop around the app |
29
+ | `drag-and-drop.md`, `system.md`, `packaging.md` | more of it, and shipping the app |
30
+ | `ecosystem/*.md` | state, forms, routing, data fetching, i18n with libraries you know |
31
+ | `README.md` | the map of every page |
32
+
33
+ The same pages are online at
34
+ `https://sidorares.github.io/react-x11/docs/reference/<page>`.
35
+
36
+ **Types**: `node_modules/react-x11/src/index.d.ts` and
37
+ `node_modules/react-x11/src/types/*.d.ts` hold every element, style key, event
38
+ and widget with its props. Grep them for any prop you are not sure of.
39
+
40
+ **Examples** are not in the package. These are pinned to this version:
41
+ `https://github.com/sidorares/react-x11/tree/v{{version}}/examples`, raw files
42
+ at `https://raw.githubusercontent.com/sidorares/react-x11/v{{version}}/examples/<file>`.
43
+ `finder.jsx` and `settings.jsx` (sidebar and content), `tasks.jsx` (list and
44
+ input), `widgets.jsx` (every control), `dashboard.jsx`, `menu.jsx`,
45
+ `theming.jsx`. Read the closest one or two before writing a screen.
46
+
47
+ ## Rules you will trip on
48
+
49
+ Most of these throw with a message that names the fix. Read the message
50
+ before guessing.
51
+
52
+ - **Host elements are lowercase and few**: `<window>`, `<popup>`, `<box>`,
53
+ `<text>`, `<textinput>`, `<textarea>`, `<image>`, `<svg>`, `<canvas>`,
54
+ `<video>`, `<glarea>`. No `<div>`, `<span>`, `<button>`, `<input>`.
55
+ - **Everything drawn is inside a `<window>`.** `<window>` takes its geometry as
56
+ props, not style: `title`, `width`, `height`, `minWidth`, `minHeight`,
57
+ `resizable`.
58
+ - **Raw strings and numbers go only inside `<text>`.** `<box>hi</box>` throws.
59
+ A `<text>` inside a `<text>` is a styled span.
60
+ - **Style lives in `style`**, an object or an array (`[s.row, on && s.on]`,
61
+ falsy entries skipped). `<box padding={8}>` throws. There is no
62
+ `className`, no CSS file, no Tailwind, no styled-components.
63
+ - **State blocks paint only.** `':hover'`, `':active'`, `':focus'`,
64
+ `':focus-visible'`, `':focus-within'` and `':disabled'` inside `style` may set
65
+ colours, `background`, `opacity`, `outline`, `borderRadius`. Not padding,
66
+ size or `border` (its width is layout).
67
+ - **Flexbox is React Native's direction with CSS's shrink**: `flexDirection`
68
+ defaults to `'column'`, and children of a column stretch to its full width.
69
+ A `Button` in a column becomes a slab: give it `alignSelf: 'flex-start'`, or
70
+ put buttons in a `flexDirection: 'row'` box. `display: 'grid'` and `gap`
71
+ work as in CSS.
72
+ - **A pane smaller than its content scrolls** only with
73
+ `overflow: 'scroll'` (or `'hidden'`, or `minHeight: 0`). For 1000+ rows use
74
+ `Table`, which is virtualized.
75
+ - **Colours are theme tokens**: `'$background'`, `'$surface'`, `'$text'`,
76
+ `'$textMuted'`, `'$border'`, `'$accent'`, `'$accentText'`, `'$accentHover'`,
77
+ `'$accentActive'`, `'$surfaceHover'`, `'$surfaceActive'`,
78
+ `'$hoverBackground'`/`'$hoverText'` (a selected row), `'$danger'`,
79
+ `'$success'`, `'$warning'`, `'$info'`, `'$link'`. They follow the desktop's
80
+ light or dark scheme and its accent. A hard-coded grey is wrong in one of
81
+ the two. `useTheme()` gives the resolved values, for `<canvas>` drawing.
82
+ - **Events**: `onClick`, `onMouseDown`/`Up`/`Move`/`Enter`/`Leave`, `onWheel`,
83
+ `onContextMenu`, and `onKeyDown`/`onKeyUp` on an element that is
84
+ `focusable`. `Button` takes **`onPress`**. Every input's `onChange` gets an
85
+ event: read `ev.value`. Keys: compare `ev.keysym` with `XK_RETURN`,
86
+ `XK_ESCAPE`, `XK_DOWN`, … from `'react-x11/keysyms'`.
87
+ - **Shortcuts**: `[['Control', 'S']]` is ⌘S on macOS and Ctrl+S elsewhere. Put
88
+ them on `MenuBar` items (`shortcut`) or use `useAccelerator(chord, fn)`.
89
+ Never bind Copy, Paste, Cut, Select All or Undo in a menu while a text field
90
+ can have focus: the fields handle those already, and both would run.
91
+ - **Web UI libraries do not work** (MUI, Radix, shadcn, recharts, react-dom
92
+ anything). Plain JavaScript libraries do: zustand, zod, date-fns, `fetch`.
93
+ - **No `localStorage`.** Persist with
94
+ `createSettings({ appId, defaults })` from `react-x11`
95
+ (`const [value, setValue] = settings.use('key')`) or with `node:fs`.
96
+ - **Paths are absolute**: `new URL('./data.json', import.meta.url)`. On macOS
97
+ the app's JavaScript runs on a worker thread, where `process.chdir()` throws.
98
+
99
+ ## Bigger components
100
+
101
+ Charts, a data table, a tree, tabs, markdown, a code editor, a rich text
102
+ editor, a calendar and date picker, a colour picker, a timeline, a reorderable
103
+ list, flow graphs, maps, formulas and a 3D scene are in
104
+ `@react-x11/components`. Install it, then import from its subpaths, not the
105
+ package root: `@react-x11/components/charts`, `/table`, `/tree`, `/tabs`,
106
+ `/markdown`, `/code-editor`, `/rich-text-editor`, `/calendar`,
107
+ `/color-picker`, `/timeline`, `/reorder`, `/flow`, `/maps`, `/formula`,
108
+ `/three`. Its `README.md` and `docs/components/<name>.md` are in
109
+ `node_modules/@react-x11/components/`, and each subpath has its own `.d.ts`.
110
+
111
+ ## Commands
112
+
113
+ - `{{run}} start` runs the app once.
114
+ - `{{run}} dev` runs it with hot reload. It keeps running and is meant for a
115
+ person at the keyboard; do not wait for it to exit.
116
+ - `npx react-x11 help` lists every `REACT_X11_*` variable.
117
+ `REACT_X11_DEBUG_LAYOUT=1` outlines every laid-out box, for a layout that
118
+ does not do what you expect.
119
+
120
+ ## Check your work: run it and look at it
121
+
122
+ Passing tests are not the finish line. A clipped label, a stretched button or
123
+ a colour that is wrong in dark mode passes every test and is obvious in a
124
+ picture.
125
+
126
+ 1. Render the app headless and write a picture of each scheme.
127
+ `react-x11/test` runs a real X server inside the test process: no window
128
+ opens, nothing to install but `pngjs` (`npm install -D pngjs`).
129
+
130
+ ```jsx
131
+ // test/App.test.jsx — run with: node --import tsx --test test/*.test.jsx
132
+ import React from 'react';
133
+ import { mkdirSync } from 'node:fs';
134
+ import { afterEach, test } from 'node:test';
135
+ import {
136
+ cleanup,
137
+ renderX11,
138
+ screen,
139
+ toPNG,
140
+ userEvent,
141
+ } from 'react-x11/test';
142
+ import App from '../src/App.jsx';
143
+
144
+ afterEach(cleanup);
145
+ // a font file, so text measures the same every run; Linux finds one itself
146
+ const fonts = {
147
+ darwin: { 'sans-serif': '/System/Library/Fonts/Supplemental/Arial.ttf' },
148
+ win32: { 'sans-serif': 'C:\\Windows\\Fonts\\segoeui.ttf' },
149
+ }[process.platform];
150
+
151
+ for (const colorScheme of ['light', 'dark']) {
152
+ test(`renders in ${colorScheme}`, async () => {
153
+ const { ctx, window: win } = await renderX11(<App />, {
154
+ wrap: false, // App renders its own <window>
155
+ fonts,
156
+ colorScheme,
157
+ });
158
+ // drive it the way a person would, then assert on what shows
159
+ // await userEvent.click(screen.getByRole('button', { name: 'Add' }));
160
+ // screen.getByText('1 item');
161
+ mkdirSync('shots', { recursive: true });
162
+ const { width, height } = win; // the size the app's window came out
163
+ await toPNG(ctx, `shots/${colorScheme}.png`, { width, height });
164
+ });
165
+ }
166
+ ```
167
+
168
+ Queries are Testing Library's: `getByText`, `getByRole(role, { name })`,
169
+ `getByPlaceholder`, `findBy*`, `queryBy*`, and `userEvent.click`, `type`,
170
+ `key`, `hover`. A `getBy*` that matches twice throws: add
171
+ `{ exact: true }` or narrow with `within(node)`.
172
+
173
+ 2. **Open both PNGs and look at them.** Fix what looks wrong: clipped or
174
+ overlapping text, a cramped or empty window, rows that do not line up, a
175
+ colour that ignores dark mode, a control with no hover or pressed state.
176
+ Render again. Two or three passes is normal.
177
+ 3. The headless render is the X11 backend: native control bezels, the macOS
178
+ menu bar and SF Symbols do not appear in it, and bold may come out regular.
179
+ Judge layout, spacing, colour and text from it. Then run `{{run}} start`
180
+ for a few seconds to confirm the real app starts with nothing in its
181
+ output.
182
+
183
+ ## Design
184
+
185
+ Read [DESIGN.md](DESIGN.md) before writing UI. It says how this app should
186
+ look and how to get each piece out of react-x11.
187
+
188
+ <!-- END:react-x11-agent-rules -->
189
+
190
+ The block above comes from react-x11 {{version}}. Keep notes about this app
191
+ below it, so the block can be replaced with a newer version's.
192
+
193
+ ## This app
194
+
195
+ <!-- What it is, who it is for, and the decisions an agent should keep to. -->
@@ -0,0 +1,269 @@
1
+ # Design
2
+
3
+ How this app should look and behave. People and coding agents both read it
4
+ before changing the UI.
5
+
6
+ The first section is this app's brief: fill it in. The rest is the house
7
+ style react-x11 {{version}} starts every app with: the conventions of the
8
+ desktops the app runs on, and how to get each one out of react-x11. Change
9
+ any of it that this app does differently. Then the agent follows your rules
10
+ and not the defaults.
11
+
12
+ ## This app
13
+
14
+ - **What it is**: _one line: what someone opens it to do._
15
+ - **Who uses it, and how often**: _all day, or once a week?_
16
+ - **Platforms**: macOS, Windows and Linux, each looking like itself. _Or
17
+ name the one that matters most._
18
+ - **Layout**: _sidebar and content, three panes, content and inspector, or
19
+ one compact window (see "Window anatomy")._
20
+ - **Density**: regular. _Compact for a data-heavy tool._
21
+ - **Colour**: the desktop's own, with the user's accent. No brand palette.
22
+ _If there is a brand colour, name it and the one or two places it may
23
+ appear._
24
+ - **Voice**: short and plain. Buttons are verbs ("Add Account", not "OK").
25
+
26
+ ## Look like the desktop it runs on
27
+
28
+ A desktop app is quiet, dense, aligned and driven from the keyboard. It is
29
+ light or dark because the desktop is. People notice in a second when an app is
30
+ a web page in a window.
31
+
32
+ react-x11 gives you most of this already:
33
+
34
+ - The theme tokens are the desktop's palette, its light or dark scheme and its
35
+ accent.
36
+ - The widgets wear AppKit's own bezels on macOS.
37
+ - `MenuBar` goes where the platform keeps menus.
38
+
39
+ Keep those defaults, and spend the effort on layout, copy and behaviour.
40
+
41
+ ## Window anatomy
42
+
43
+ - **One main window** with a clear structure. Pick one of the standard
44
+ layouts:
45
+ - **Sidebar and content** (Mail, Files, Explorer): a list of places or
46
+ collections on the left, 200–260 px wide, then the content. This is the
47
+ default for anything with collections of things.
48
+ - **Sidebar, list and detail**: three columns, with the middle list
49
+ 260–320 px wide.
50
+ - **Content and inspector**: a 240–300 px panel on the right with the
51
+ properties of the selection.
52
+ - **One compact window** for a single-purpose utility: a timer, a
53
+ converter. This needs no sidebar.
54
+ - **A toolbar** under the title bar: a 40–48 px row with the few most-used
55
+ actions as icon buttons, and a search field at the trailing end.
56
+ - **Dialogs for short tasks**: a modal dialog for a three-field "New item…"
57
+ form, and a popup anchored to what it edits for a small editor. Never take
58
+ over the whole window for a short form.
59
+ - **Size**: 900×600 by default for a sidebar app, 720×480 for a simple one,
60
+ about 360×480 for a utility. **Always set a minimum size** (about 600×400
61
+ for a sidebar app), so the layout never collapses.
62
+
63
+ ## Menus and shortcuts
64
+
65
+ People try a shortcut before they look for a button. Write shortcuts with
66
+ `Control`: it is ⌘ on macOS and Ctrl elsewhere. The standard menus, in order:
67
+
68
+ - **File**: New (Control+N), Open… (Control+O), Close Window (Control+W), Save
69
+ (Control+S), Save As… (Control+Shift+S), Export…, Print… (Control+P)
70
+ - **Edit**: Undo, Redo, Cut, Copy, Paste, Delete, Select All, Find…
71
+ (Control+F)
72
+ - **View**: Show or Hide Sidebar, view modes (Control+1, Control+2, …), Zoom
73
+ In and Zoom Out
74
+ - _the app's own_: its own verbs (Item, Format, Playback, …), with shortcuts
75
+ for the frequent ones
76
+ - **Help**: _App name_ Help
77
+
78
+ And the rules around them:
79
+
80
+ - **Where the menu shows**: on macOS `MenuBar` becomes the system menu bar.
81
+ On Linux it goes to the desktop's panel where one hosts menus (KDE
82
+ Plasma), and is drawn in the window everywhere else, Windows included.
83
+ - **Settings and Quit**: on macOS, react-x11 adds the app menu itself, with
84
+ Quit (⌘Q) only. Put "Settings…" (Control+,) at the end of your first menu.
85
+ On Windows and Linux, put Settings… in Edit or File, and Quit (Control+Q) at
86
+ the end of File.
87
+ - An item that opens a window or asks for more ends in "…". An item that does
88
+ not apply is disabled, not hidden. Every frequent action is in a menu, even
89
+ when it also has a button.
90
+ - The text fields already handle Copy, Paste, Cut, Select All, Undo and Redo,
91
+ with a right-click menu of their own. Do not put those shortcuts on menu
92
+ items while a field can have focus: both would run.
93
+
94
+ ## Type
95
+
96
+ - **Base size**: 13 on macOS, which is AppKit's body size, and 14 elsewhere,
97
+ which is react-x11's default. Set it once at the root (see the recipes
98
+ below). Never set `fontFamily`: the default is the system's UI face.
99
+ - **Scale**: base for body, controls and lists; base −2 in `'$textMuted'` for
100
+ secondary text and captions; base +2 and +4, semibold (600), for titles; base
101
+ +9 for a rare large title. Use at most three sizes on one screen.
102
+ Hierarchy comes from weight and the muted colour, not from size jumps.
103
+ - A number that changes while you watch (a timer, a price, a count) uses
104
+ `fontVariantNumeric: 'tabular-nums'`.
105
+ - A one-line label that can overflow: `textWrap: 'nowrap'` and
106
+ `textOverflow: 'ellipsis'`.
107
+
108
+ ## Colour
109
+
110
+ - **Semantic tokens only**: `'$background'` for the window, `'$surface'` for
111
+ lists, fields and raised areas, `'$text'`, `'$textMuted'`, `'$border'` for
112
+ separators, `'$accent'`, `'$hoverBackground'` and `'$hoverText'` for a
113
+ selected row, and `'$danger'`, `'$success'`, `'$warning'`, `'$info'` for
114
+ status. A hard-coded grey is wrong in light or in dark.
115
+ - **The accent is the user's**, not yours. Use it for the primary button, the
116
+ selection, focus and toggles.
117
+ - **Colour highlights; it is not the ground.** Most of a window is neutral.
118
+ Big saturated panels are a web look.
119
+ - **Light and dark both look intended.** Shadows almost vanish in dark, so
120
+ separation comes from a slightly lighter surface instead.
121
+
122
+ ## Spacing and sizes
123
+
124
+ - A 4 and 8 px grid: 20 px window margins (16 compact), 8 px between related
125
+ controls, 16–20 px between groups, 6–12 px of padding inside a row.
126
+ - Let the widgets size themselves: they follow the platform. Rows you draw
127
+ yourself: 28 px on macOS, 32–36 px elsewhere.
128
+ - Corners match the widgets'. `useTheme().radius` is their radius.
129
+ - Separators are 1 px lines in `'$border'`, not borders around every box.
130
+ - Align everything to a few vertical lines. Form labels are either
131
+ right-aligned against a column of fields or stacked above them in a narrow
132
+ pane.
133
+
134
+ ## Interaction
135
+
136
+ - **Every input gets an answer on the event that started it**: a hover tint,
137
+ a distinct pressed state on mouse down, then the action on release.
138
+ - **Keyboard**: Tab moves focus, Space and Return activate, arrows move
139
+ through lists and grids, Return confirms a dialog, Escape cancels it,
140
+ Control+F focuses search, Delete removes the selection. The focus ring
141
+ shows on keyboard focus.
142
+ - **Selection**: one click selects (a filled row in `'$hoverBackground'`),
143
+ and a double click or Return opens.
144
+ - **Right click** shows a context menu with the item's verbs.
145
+ - **Dialog buttons** are named for what they do ("Delete 3 Items", not
146
+ "Yes"). On macOS and GNOME, Cancel comes first and the action is last, at
147
+ the trailing edge. On Windows and KDE the action comes first, then Cancel.
148
+ - **Destructive actions** can be undone, or are confirmed in a dialog whose
149
+ button says what it deletes.
150
+ - **Empty states**: a short centred line in `'$textMuted'` ("No Notes"), an
151
+ optional one-line explanation, and at most one button. Never a blank pane.
152
+ - **Success is shown in place**, not in a "Saved!" dialog. Dialogs are for
153
+ decisions.
154
+
155
+ ## Icons
156
+
157
+ - Icons label places and actions in sidebars and toolbars. They do not
158
+ decorate paragraphs. Never use emoji as UI icons.
159
+ - Use the platform's own icons, which are monoline, sized like the text and
160
+ drawn in its colour: `<image src={{ symbol }} />` draws an SF Symbol on
161
+ macOS (`'folder'`) and an icon from the freedesktop theme on Linux
162
+ (`'folder-symbolic'`). A name the desktop lacks draws nothing, and Windows
163
+ has no icon theme to draw from. So an icon is never the only label, and an
164
+ app that needs icons everywhere ships its own.
165
+ - Affordances (chevrons, check, close, plus, more) come from `<Icon name>`,
166
+ which is the same everywhere.
167
+
168
+ ## Motion and accessibility
169
+
170
+ - Motion is subtle and fast: 120–200 ms for hover, selection and disclosure.
171
+ Nothing bounces, and nothing animates on a timer the user did not start.
172
+ With reduced motion on, cut or cross-fade instead.
173
+ - Every control has a name a screen reader can say: an icon-only button needs
174
+ an `aria-label`. Text contrast is at least 4.5:1. Nothing is reachable by
175
+ mouse only. State is never shown by colour alone: add an icon or a word.
176
+
177
+ ## The same, in react-x11
178
+
179
+ - **Window size and minimum**: `<window title="…" width={900} height={600}
180
+ minWidth={600} minHeight={400}>`
181
+ - **The app's name on macOS**: `createRoot({ cocoa: { appName: 'Notes' } })`:
182
+ what the menu bar and ⌘-Tab show. Without it they say "node".
183
+ - **Base type size**: `<ThemeProvider value={{ fontSize: process.platform ===
184
+ 'darwin' ? 13 : 14 }}>` around the app.
185
+ - **Menu bar**: `<MenuBar menus={[{ label: 'File', items: [...] }]} />` inside
186
+ the window. Item: `{ label, onSelect, shortcut: [['Control', 'N']], enabled,
187
+ items }`, or `{ type: 'separator' }`. A check item: `toggleType:
188
+ 'checkmark', toggleState: 0 or 1`.
189
+ - **A shortcut with no menu item**: `useAccelerator([['Control', 'K']],
190
+ handler)`
191
+ - **Sidebar**: a `<box>` with `width: 220`, `backgroundColor: '$surface'`,
192
+ `borderEndWidth: 1`, `borderColor: '$border'`, `padding: 8`, `gap: 2`. Rows:
193
+ `flexDirection: 'row'`, `alignItems: 'center'`, `gap: 8`, `paddingInline:
194
+ 8`, `borderRadius: 6`, `':hover': { backgroundColor: '$surfaceHover' }`, and
195
+ when selected `backgroundColor: '$hoverBackground'` with text in
196
+ `'$hoverText'`. Section headers: base −2, `fontWeight: 600`, `'$textMuted'`.
197
+ See `examples/finder.jsx` and `settings.jsx`.
198
+ - **Resizable split**: `<SplitPane direction="row" defaultSize={240}
199
+ min={180}>` with two children.
200
+ - **Toolbar**: a row under the title bar: `flexDirection: 'row'`, `alignItems:
201
+ 'center'`, `gap: 8`, `height: 44`, `paddingInline: 12`, `borderBottomWidth:
202
+ 1`, `borderColor: '$border'`. Icon buttons in it, then `<textinput
203
+ placeholder="Search" />` last.
204
+ - **Buttons**: `<Button onPress={…}>`. `primary` is the default action, one
205
+ per view. `variant="outline"` for a secondary one, `variant="ghost"
206
+ size="small"` for toolbar and inline icon buttons. Never size a `Button`
207
+ yourself: `style` sizes its slot.
208
+ - **Other controls**: `Checkbox`, `Switch`, `RadioGroup` and `Radio`,
209
+ `Slider`, `Select` (`options`, `value`), `ProgressBar`, `Tooltip label="…"`,
210
+ `<textinput placeholder>`, `<textarea>`. Every `onChange(ev)` gives
211
+ `ev.value`.
212
+ - **Tabs**: `<Tabs items={[{ id, label, content }]} />`
213
+ - **Modal dialog**: `<Dialog open title="…" onClose={…}
214
+ actions={<>…buttons…</>}>`: a real window that keeps focus. Escape and the
215
+ close button call `onClose`. `autoFocus` on its first field.
216
+ - **Context menu and popover**: `<ContextMenu items={[…]}>` around the node it
217
+ belongs to. `<popup anchor={{ to: ref, placement: 'bottom' }}>` for a custom
218
+ popover.
219
+ - **Lists, tables, trees**: `Table` (`columns: [{ id, label, width, value,
220
+ align }]`, `rows`, `onActivate`) for sortable, virtualized data. `Tree` from
221
+ `@react-x11/components/tree`. Draw short lists yourself.
222
+ - **Hover and press on your own box**: `cursor: 'pointer'`, `':hover': {
223
+ backgroundColor: '$surfaceHover' }`, `':active': { backgroundColor:
224
+ '$surfaceActive' }`, `transition: 120`.
225
+ - **Focus and keys**: `focusable` (and `tabIndex`) on a custom control, and
226
+ the focus ring comes with it. `onKeyDown={(ev) => ev.keysym === XK_DOWN &&
227
+ …}` with keysyms from `react-x11/keysyms`. `role` and `aria-label` so a
228
+ screen reader can name it.
229
+ - **Light, dark, reduced motion**: tokens follow the desktop.
230
+ `useSystemAppearance()` gives `{ colorScheme, accent, reducedMotion, … }`,
231
+ and `transition: reducedMotion ? 0 : 150` respects it.
232
+ - **A tinted alert**: `backgroundColor: tint(theme.danger, 0.12)` with `tint`
233
+ from `react-x11/style` and `theme` from `useTheme()`, and `'$danger'` for
234
+ its text.
235
+ - **Files, notifications, settings**: `useFileDialog()` gives `openFile()` and
236
+ `saveFile()`, which resolve to paths or `null`. `useNotifier()` gives
237
+ `notify({ summary, body })`: check `available` first and never depend on it.
238
+ `createSettings({ appId, defaults })` remembers state.
239
+ - **Charts**: `@react-x11/components/charts`, at least 200 px tall, with
240
+ series in `'$accent'`, `'$info'`, `'$success'`, and a tooltip.
241
+
242
+ ## What makes it look like a website (avoid)
243
+
244
+ - **A hero header**: a big title and tagline at the top of the window. The
245
+ window's title is in the title bar, and content starts with content.
246
+ - **Card soup**: every item in a rounded, shadowed, padded card on a grey
247
+ page. Use lists with separators and selection. Keep cards for things that
248
+ are visual: a gallery, a dashboard tile.
249
+ - **A centred landing page**: content in a narrow column with empty sides.
250
+ Fill the window and align to the leading edge.
251
+ - **Phone and web patterns**: hamburger menus, bottom tab bars, floating
252
+ action buttons. Use the menu bar, a sidebar, a toolbar.
253
+ - **Wide buttons**: the column-stretch trap, and buttons styled as links.
254
+ Buttons are compact and sit in a row at the trailing edge or in a toolbar.
255
+ - **Gradients, glows, neon, emoji icons, big shadows**, and everything
256
+ rounded to 16 px or more.
257
+ - **A palette of your own that ignores dark mode**, or replaces the user's
258
+ accent.
259
+ - **Sample data that looks fake**: "Lorem ipsum", "Item 1, Item 2". Seed 10–30
260
+ plausible, varied records. It is the cheapest polish there is.
261
+ - **Buttons that do nothing.** Cut them, or disable them and say why.
262
+
263
+ ## Before calling it done
264
+
265
+ Look at a picture of every screen, in light and in dark. Is there one
266
+ obvious primary area? Do the leading edges line up, and are the row heights
267
+ consistent? Is there one accent-coloured thing per region, and is nothing
268
+ clipped? If it looks like part of the operating system rather than a
269
+ landing page, it is done.
package/src/cocoa/app.js CHANGED
@@ -5,7 +5,7 @@
5
5
  // that stands in for the X socket.
6
6
  //
7
7
  // Two run loops. By default Node's loop is master and a timer pumps AppKit
8
- // (docs/macos.md §"Input and the run loop", option 1): input waits for the
8
+ // (design/macos.md §"Input and the run loop", option 1): input waits for the
9
9
  // pump, and AppKit's modal loops (live resize, menu tracking, a drag) stall
10
10
  // JS timers — but not the delegate callbacks, which is why live resize
11
11
  // still relayouts: the resize event flushes the frame synchronously on its
@@ -13,7 +13,7 @@
13
13
  // Under the launcher (`node --import react-x11/cocoa-main`, src/cocoa/
14
14
  // main.js) the app runs on a worker instead, AppKit keeps the main thread
15
15
  // in a real `[NSApp run]`, and this object is fed by the bridge's batches
16
- // (`_routeBatch`) with no pump at all (docs/macos.md §"JS on a worker").
16
+ // (`_routeBatch`) with no pump at all (design/macos.md §"JS on a worker").
17
17
  import { isMainThread } from 'node:worker_threads';
18
18
 
19
19
  import { cssColorStraight } from 'ntk/color';
@@ -64,11 +64,11 @@ const PUMP_INTERVAL_MS = 8;
64
64
  // The edge moves when the delegate returns, so the budget is how long a
65
65
  // slow frame may hold the drag back. 50 covers what a large tree costs —
66
66
  // the Cocoa `resize` cell measures 28ms a tick at 3,662 nodes
67
- // (docs/macos.md) — with room, where the bridge's own test met every tick
67
+ // (design/macos.md) — with room, where the bridge's own test met every tick
68
68
  // in 3.1ms against 3ms of layout. Past three 60Hz frames an edge that waits
69
69
  // reads as the drag stuttering, so a tree slower than that lets the edge
70
70
  // go first and its frame land after (Flutter's Windows embedder waits up
71
- // to 100, docs/windows.md §"Resize"). `createRoot({ cocoa: { resizeWait } })`
71
+ // to 100, design/windows.md §"Resize: where the threads meet"). `createRoot({ cocoa: { resizeWait } })`
72
72
  // sets it; 0 is no handshake at all.
73
73
  const RESIZE_WAIT_MS = 50;
74
74
  // What `createRoot({ cocoa: { paneStretch } })` and
@@ -272,7 +272,7 @@ export class CocoaApp {
272
272
  }
273
273
 
274
274
  // 'surface' (the measured default) or 'layers' — the retained CALayer
275
- // presenter, opt-in while docs/macos.md's measure-first gate is open.
275
+ // presenter, opt-in while design/macos.md's measure-first gate is open.
276
276
  this._presenterMode =
277
277
  options.cocoa?.presenter ??
278
278
  process.env.REACT_X11_COCOA_PRESENTER ??
@@ -391,7 +391,7 @@ export class CocoaApp {
391
391
 
392
392
  // the global-menu exports, newest active: the macOS menu bar is one per
393
393
  // app, so the most recent MenuBar owns it (per-focused-window switching
394
- // is the follow-up — docs/macos.md §Menus)
394
+ // is the follow-up — design/macos.md §Menus)
395
395
  this._globalMenus = [];
396
396
  this._activeGlobalMenu = null;
397
397
 
@@ -1,5 +1,5 @@
1
1
  // Native control bezels — AppKit's own pixels for the core controls, cached
2
- // as surfaces the ordinary 2d paint path can blit (docs/macos.md §Native
2
+ // as surfaces the ordinary 2d paint path can blit (design/macos.md §Native
3
3
  // controls). The mechanism is offscreen NSCell/NSControl rendering, the
4
4
  // WebKit/Gecko form-control technique: interaction, focus and keyboard stay
5
5
  // the shared component implementation, and only the *bezel* — the pixels of
@@ -1,6 +1,6 @@
1
1
  // The Cocoa text engine: CoreText behind the same `app.fonts` contract ntk's
2
2
  // FontManager answers on the X11 backend. The renderer touches exactly this
3
- // surface (docs/macos.md §"Text: the engine contract"):
3
+ // surface (design/macos.md §"Text: the engine contract"):
4
4
  //
5
5
  // fonts.layout(spans, base, { maxWidth, align, lineHeight, maxLines,
6
6
  // overflow, direction })
@@ -1,4 +1,4 @@
1
- // The macOS menu bar as a global-menu export (docs/macos.md §Menus).
1
+ // The macOS menu bar as a global-menu export (design/macos.md §Menus).
2
2
  //
3
3
  // The Linux global menu was built for exactly this moment: the item
4
4
  // vocabulary is data, and the pure snapshot/IdAllocator machinery in
@@ -23,7 +23,7 @@
23
23
  //
24
24
  // There is no input method behind this. The bridge's view swallows
25
25
  // `keyDown:` without `interpretKeyEvents:` and is no `NSTextInputClient`
26
- // (docs/macos.md, "IME"), so these fields are all the text there is. Two
26
+ // (design/macos.md, "IME"), so these fields are all the text there is. Two
27
27
  // consequences, both handled below:
28
28
  //
29
29
  // - **A dead key types nothing.** ⌥E on a US layout leaves `chars` empty, and
@@ -1,4 +1,4 @@
1
- // The pane process's "window" on the Cocoa backend — docs/frame.md's child
1
+ // The pane process's "window" on the Cocoa backend — design/frame.md's child
2
2
  // half, with the X server replaced by shared memory.
3
3
  //
4
4
  // On X11 a pane renders into a real window of its own and the host embeds
@@ -1,4 +1,4 @@
1
- // `<video src>` on macOS (docs/architecture/video.md §3.2, §9): a file or URL
1
+ // `<video src>` on macOS (design/architecture/video.md §3.2, §9): a file or URL
2
2
  // played by AVFoundation through @windowkit/appkit's player verbs — the
3
3
  // platform's decoder, audio, seeking and HLS — with the picture on the
4
4
  // player's own AVPlayerLayer.
@@ -1,4 +1,4 @@
1
- // The retained layer presenter — Tier L of docs/macos.md: one CALayer per
1
+ // The retained layer presenter — Tier L of design/macos.md: one CALayer per
2
2
  // drawn node, React commits landing as property sets inside a single
3
3
  // disabled-actions CATransaction per frame, the WindowServer compositing.
4
4
  //
@@ -350,7 +350,7 @@ export function stylePaintsPlain(node, style = node.style ?? {}) {
350
350
  /**
351
351
  * Style property → the key path on a PropBox's own layer, for the
352
352
  * animations the presenter takes off the frame clock and hands to the
353
- * render server (docs/architecture/animation.md §4.2). Only what the layer
353
+ * render server (design/architecture/animation.md §4.2). Only what the layer
354
354
  * expresses as a property of itself qualifies: a colour on `<text>` is a
355
355
  * re-raster per frame, a layout property moves the siblings, and both stay
356
356
  * on the JS loop. `scaled` values go out in points, as `_syncPropBox`
@@ -36,7 +36,7 @@
36
36
  // overlapped, hidden, clipped, faded or non-plain returns to the bitmap in
37
37
  // the frame that finds it, the animation handed back to the clock — and a
38
38
  // loop the clock runs is offered again once the scene would take it
39
- // (`wouldTake`), where it is in its cycle. docs/macos.md §"Layer
39
+ // (`wouldTake`), where it is in its cycle. design/macos.md §"Layer
40
40
  // promotion" is the account.
41
41
  import { now } from '../nodes/animation.js';
42
42
  import { BoxNode } from '../nodes/box.js';
@@ -1,4 +1,4 @@
1
- // Threaded mode by default (docs/macos.md §"JS on a worker"). On macOS the
1
+ // Threaded mode by default (design/macos.md §"JS on a worker"). On macOS the
2
2
  // process main thread is AppKit's — the only thread it lets own a window —
3
3
  // and while the app's JS runs there, every menu, drag, live resize and
4
4
  // modal panel stops it. So the first import of react-x11 on the main thread
@@ -1,4 +1,4 @@
1
- // Threaded mode's worker half (docs/macos.md §"JS on a worker: a UI thread
1
+ // Threaded mode's worker half (design/macos.md §"JS on a worker: a UI thread
2
2
  // of the bridge's own"): with the launcher (src/cocoa/main.js) holding the
3
3
  // process main thread in the bridge's `runMain()`, the app's JS runs on a
4
4
  // `worker_threads` Worker, and this module is what that worker needs to
@@ -1,4 +1,4 @@
1
- // A video's frames on a layer of their own (docs/architecture/video.md §7):
1
+ // A video's frames on a layer of their own (design/architecture/video.md §7):
2
2
  // what the surface presenter's sprites (src/cocoa/sprites.js) do with a part
3
3
  // whose `contents` is a `VideoFrames` sink instead of a `paint`.
4
4
  //
@@ -39,7 +39,7 @@ const FENCE_TIMEOUT_MS = 100;
39
39
  // (`_takeBack`). Three, a `CAMetalLayer`'s depth, is what an animation
40
40
  // mostly needs; an input answered between two of its frames puts two flips
41
41
  // inside one refresh, and four kept every write of a clicked 120Hz animation
42
- // off a held buffer (docs/macos.md, "Measured: the swapchain and the
42
+ // off a held buffer (design/macos.md, "Measured: the swapchain and the
43
43
  // WindowServer's hold").
44
44
  const MAX_BUFFERS = 4;
45
45
  // Past this many rects, what a buffer missed is copied whole: a buffer left
@@ -235,14 +235,14 @@ export class CocoaWindow {
235
235
  // change.
236
236
  this.damageRectCap = 16;
237
237
 
238
- // The retained layer presenter (docs/macos.md Tier L), behind
238
+ // The retained layer presenter (design/macos.md Tier L), behind
239
239
  // REACT_X11_COCOA_PRESENTER=layers while the surface path is the
240
240
  // measured default. Its hooks exist only in this mode, so the feature
241
241
  // detection in src/nodes/ keeps the surface path byte-identical; the
242
242
  // scroll blit is shadowed off because a layer frame has no backing
243
243
  // bitmap to blit. The last two are the animation seam: a transition or
244
244
  // a loop the presenter takes runs in the render server and schedules no
245
- // frames here (docs/architecture/animation.md §4).
245
+ // frames here (design/architecture/animation.md §4).
246
246
  if (app._presenterMode === 'layers') {
247
247
  this._presenter = new CocoaLayerPresenter(this);
248
248
  this.presentFrame = (windowNode) => this._presenter.frame(windowNode);
@@ -659,7 +659,7 @@ export class CocoaWindow {
659
659
  * A new size retires the chain, released on the spot (`_releaseBacking`):
660
660
  * left to the handles' finalizers, a forty-tick drag of the two-buffer
661
661
  * chain this used to be held 800MB until a collection happened to run —
662
- * the `rss +80MB` docs/macos.md measured. The layer keeps its own
662
+ * the `rss +80MB` design/macos.md measured. The layer keeps its own
663
663
  * reference to whichever IOSurface it is still showing, so the free is
664
664
  * safe while that frame is on glass. A new chain starts with the one
665
665
  * buffer its first frame paints: a live-resize tick paints its size once,