@qxuken/kui 0.1.0-alpha.43 → 0.1.0-alpha.45
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.
- package/CHANGELOG.md +253 -2
- package/docs/adr/0023-layers-stack-in-the-order-they-open.md +11 -4
- package/encoder.js +3 -1
- package/howto.md +9 -2
- package/package.json +1 -1
- package/prebuilds/darwin-arm64/kui_node.node +0 -0
- package/prebuilds/linux-arm64/kui_node.node +0 -0
- package/prebuilds/linux-x64/kui_node.node +0 -0
- package/prebuilds/win32-x64/kui_node.node +0 -0
- package/props.md +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -21,6 +21,259 @@ listed under both (backlog F61, from the alpha.12 field reports: the list
|
|
|
21
21
|
is what the release knows it broke, and a fix it did not think of as one
|
|
22
22
|
was the first bare bump to break an app in five releases).
|
|
23
23
|
|
|
24
|
+
## 0.1.0-alpha.45 (2026-10-08)
|
|
25
|
+
|
|
26
|
+
**What breaks.**
|
|
27
|
+
|
|
28
|
+
- A menu row whose accelerator would leave its label under 48 px — a
|
|
29
|
+
window narrower than the accelerator with its gaps — draws the
|
|
30
|
+
accelerator cut short with "…" and the label's first glyphs, where it
|
|
31
|
+
drew the whole accelerator past the panel's edge and no label (under
|
|
32
|
+
Fixed, RG154).
|
|
33
|
+
- A `menu_panel` floating in the viewport whose caller declared its
|
|
34
|
+
ceiling as a size expression (`max_width(Bound::Calc(..))`, `"50%"`)
|
|
35
|
+
bounds its labels by that ceiling read against the window, where it
|
|
36
|
+
bounded them by the window alone (under Fixed, RG154). A panel
|
|
37
|
+
anchored elsewhere keeps the window as its labels' bound.
|
|
38
|
+
- A menu bar the app leaves out of a frame while a submenu switch waits
|
|
39
|
+
its 0.3 s starts the wait again when the bar is back, where the switch
|
|
40
|
+
went through at once on the next build (under Fixed, RG154).
|
|
41
|
+
- Windows and Plasma: a `Blur` or `Tinted` window on a desktop with no
|
|
42
|
+
wallpaper kui can read is `Opaque` from its first frame again, as it
|
|
43
|
+
was in alpha.43; alpha.44's `Tinted`-then-`Opaque` is GNOME's alone
|
|
44
|
+
now (under Fixed, RG154).
|
|
45
|
+
|
|
46
|
+
### Fixed
|
|
47
|
+
|
|
48
|
+
- **The accelerator is bounded too** (backlog RG154, from the alpha.44
|
|
49
|
+
pre-tag pass). A row's label was what the panel's width bounded, and
|
|
50
|
+
its accelerator was drawn whole: in a window narrower than the
|
|
51
|
+
accelerator the label shrank to nothing and the accelerator ran past
|
|
52
|
+
the panel, which is what RG150 set out to stop. The label keeps a
|
|
53
|
+
floor of 48 px (`widgets::MENU_LABEL_MIN`) and the accelerator takes
|
|
54
|
+
what that leaves, ending in "…". And a ceiling declared as a size
|
|
55
|
+
expression on a panel floating in the viewport now caps the labels as
|
|
56
|
+
a px one does, read against the window; a panel anchored to a node
|
|
57
|
+
keeps the window's ceiling for its labels, since the room layout will
|
|
58
|
+
read the expression against is not placed when the rows are built.
|
|
59
|
+
- **A menu bar the app stops drawing owes no frames** (backlog RG154).
|
|
60
|
+
A submenu switch waiting its 0.3 s was cleared by the next build of the
|
|
61
|
+
bar's menu; an app that stopped drawing the bar inside the wait (or
|
|
62
|
+
handed it to the platform) kept the switch, and the frames it owed,
|
|
63
|
+
for good. A frame that builds no menu on a surface drops the switch
|
|
64
|
+
waiting there.
|
|
65
|
+
- **Windows and Plasma: no wallpaper, opaque from the first frame**
|
|
66
|
+
(backlog RG154). RG150 moved finding the wallpaper's path to the
|
|
67
|
+
loader's thread for GNOME's sake, where it is up to three `gsettings`
|
|
68
|
+
processes; on Windows it is one call and on Plasma one file read, so
|
|
69
|
+
the loop asks there and a window with none to draw never reads
|
|
70
|
+
`Tinted` first. GNOME keeps the thread. Windows run on screen;
|
|
71
|
+
Plasma compiled and read.
|
|
72
|
+
|
|
73
|
+
**What you can delete.**
|
|
74
|
+
|
|
75
|
+
- A shorter accelerator chosen for a menu that has to fit a narrow
|
|
76
|
+
window (RG154).
|
|
77
|
+
- A frame an app requested itself after stopping its menu bar, to let
|
|
78
|
+
the core settle (RG154).
|
|
79
|
+
|
|
80
|
+
### Native verification
|
|
81
|
+
|
|
82
|
+
The by-hand round alpha.6 introduced (backlog R4), on 2026-10-08, over
|
|
83
|
+
RG154 — what alpha.44's pre-tag pass left — with alpha.45's pre-tag pass
|
|
84
|
+
over it on the Windows machine and under WSLg: the mechanical round on
|
|
85
|
+
both, then a read-only review of the diff since alpha.44, each claim
|
|
86
|
+
probed. It filed nothing; its one finding, a `Calc` ceiling read against
|
|
87
|
+
the window for a panel anchored elsewhere, was corrected inside RG154
|
|
88
|
+
before the tag.
|
|
89
|
+
|
|
90
|
+
**Windows**, the pre-tag pass. fmt and clippy are clean; `nu
|
|
91
|
+
scripts/test.nu --node`: **2148 tests over 143 suites**, 0 failed. The
|
|
92
|
+
C round passes (6 checks), and so do the **57 scenes** through Rust,
|
|
93
|
+
Lua, C, Node and Odin; the Odin binding's four steps with CI's pinned
|
|
94
|
+
`dev-2026-09`; Node's tests under `KUI_CONFORMANCE_REQUIRED=1` (**218 of
|
|
95
|
+
219**, the one skip Windows'), `npm run gen` with no diff, the
|
|
96
|
+
examples' typecheck and the headless round. The windowed round with
|
|
97
|
+
Node's: **53 examples on both bases**, clean on a first run; `counter`
|
|
98
|
+
and `host` opened by hand after `cbuild`, and the backdrop examples
|
|
99
|
+
opened with and without `KUI_BACKDROP_EMULATE`. The bench guard against
|
|
100
|
+
the alpha.44 tag, on a quiet machine: **green**, the eight guarded rows
|
|
101
|
+
−0.7% to +2.1% (`frame_10k_rects_with_access_tree` the high one), the
|
|
102
|
+
worst run-to-run spread 3.5%. A first run beside the WSL round read two
|
|
103
|
+
rows unreadable at 13 and 21% spread, which is why it was run again.
|
|
104
|
+
|
|
105
|
+
**Linux**, under WSLg (llvmpipe), the same commit: fmt and clippy
|
|
106
|
+
clean; `cargo test --workspace`: **1926 tests over 142 suites**, 0
|
|
107
|
+
failed; the C round (5 checks) and the 57 scenes through every adapter,
|
|
108
|
+
Node **219 of 219** with the corpus required, gen clean, the typecheck,
|
|
109
|
+
the headless round. The windowed round under X11 with Node's: **53
|
|
110
|
+
examples on both bases**, clean on a first run, two at a time. The
|
|
111
|
+
backdrop examples under X11 read no wallpaper (WSLg has none to name)
|
|
112
|
+
and went `Opaque`, as they should; Plasma's on-the-loop read is
|
|
113
|
+
compiled and read, not run. No Mac ran this round: the AX audit is CI's
|
|
114
|
+
and the next Mac round's.
|
|
115
|
+
|
|
116
|
+
## 0.1.0-alpha.44 (2026-10-08)
|
|
117
|
+
|
|
118
|
+
**What breaks.**
|
|
119
|
+
|
|
120
|
+
- A menu row wider than the window — a recent file's path, a long
|
|
121
|
+
`<select>` option — draws its label cut short with "…" in a menu as
|
|
122
|
+
wide as the window less 8 px a side (and never narrower than the
|
|
123
|
+
metric's menu width, 200 px), where the menu ran off the edge (under
|
|
124
|
+
Fixed, RG150).
|
|
125
|
+
- With a frame clock (every runner sets one), moving the pointer from an
|
|
126
|
+
open submenu's row to another row of its menu switches after 0.3 s of
|
|
127
|
+
rest there, not at once (under Fixed, RG150). A driver that sets no
|
|
128
|
+
clock switches at once, as before.
|
|
129
|
+
- A `<select>` option given `items` (or a C `KuiMenuItem` with
|
|
130
|
+
`submenu`) opens no submenu: the rows are dropped, with an
|
|
131
|
+
`unknown-prop` warning in Node and Lua (under Fixed, RG150).
|
|
132
|
+
- Windows: a `Blur` or `Tinted` window whose environment presents through
|
|
133
|
+
the window's handle (`WGPU_DX12_PRESENTATION_SYSTEM=hwnd`) or names no
|
|
134
|
+
D3D12 in `WGPU_BACKEND` draws the wallpaper kui reads and reports
|
|
135
|
+
`Tinted` (or `Opaque`), where it was translucent over black and
|
|
136
|
+
reported `Blur`; a transparent window whose `WGPU_BACKEND` lists D3D12
|
|
137
|
+
among others opens D3D12 alone (under Fixed, RG150).
|
|
138
|
+
- Windows and Linux: a `Blur` or `Tinted` window on a desktop with no
|
|
139
|
+
wallpaper kui can read (a solid colour, a file it cannot decode) reads
|
|
140
|
+
`Tinted` for its first frame or frames and `Opaque` once the loader's
|
|
141
|
+
thread has answered, where alpha.43 read the path on the event loop
|
|
142
|
+
and said `Opaque` from the first frame (under Fixed, RG150). A view
|
|
143
|
+
that branches on the backdrop sees it change once, early.
|
|
144
|
+
- An image or a fragment that declares `border` draws it, as a ring over
|
|
145
|
+
the content, where it drew none (under Fixed, RG152): an app that
|
|
146
|
+
kept the `border` and drew its own ring around the picture draws two.
|
|
147
|
+
|
|
148
|
+
### Added
|
|
149
|
+
|
|
150
|
+
- `MenuItem::stray_keys`, `MenuItem::stray_option_keys` and
|
|
151
|
+
`MenuBar::stray_keys`: the keys of plain-data menu rows no row reads,
|
|
152
|
+
a submenu's rows' included, for a binding to warn about (backlog
|
|
153
|
+
RG150).
|
|
154
|
+
- `kui_wgpu::see_through_by_visual` (and `_with`, its pure form): whether
|
|
155
|
+
a Windows window can be seen through, by the environment wgpu reads —
|
|
156
|
+
the one answer the window's surface and the swapchain both take
|
|
157
|
+
(backlog RG150).
|
|
158
|
+
|
|
159
|
+
### Fixed
|
|
160
|
+
|
|
161
|
+
- **A menu has a width ceiling** (backlog RG150, from alpha.43's pre-tag
|
|
162
|
+
pass). F127 made a menu as wide as its widest row, with nothing above
|
|
163
|
+
it: a long path or `<select>` option ran the panel and its
|
|
164
|
+
accelerators off a narrow window. A menu is now never wider than the
|
|
165
|
+
window less 8 px a side (a narrower ceiling the caller declared in px
|
|
166
|
+
stands; the metric's menu width is the floor), and a row's label is
|
|
167
|
+
bounded by what its accelerator leaves it and ends in "…".
|
|
168
|
+
- **A submenu survives the pointer passing over a row on its way in**
|
|
169
|
+
(backlog RG150). A diagonal path from a row to a lower row of its
|
|
170
|
+
submenu crosses the rows below it, and each one closed the submenu. A
|
|
171
|
+
row that would close one now waits 0.3 s of rest on the frame clock,
|
|
172
|
+
and moving into the submenu cancels it; the frames for the wait are
|
|
173
|
+
owed. And a submenu the keyboard closed opens again when the pointer
|
|
174
|
+
leaves the menu and comes back to its row.
|
|
175
|
+
- **A stray key inside a submenu warns** (backlog RG150). Only a select's
|
|
176
|
+
top-level options were checked, so `{ label, disabled: true }` inside a
|
|
177
|
+
submenu was silently an enabled row; Node's `openMenu` and `<menuBar>`
|
|
178
|
+
and Lua's `open_menu` and `menu_bar` now warn for every level.
|
|
179
|
+
- **The backdrop blur's scratch is the size of what it blurs** (backlog
|
|
180
|
+
RG150). Four surface-sized textures, cleared and stored by every pass,
|
|
181
|
+
were ~236 MB at 5K and three full-surface stores per blurred node, and
|
|
182
|
+
were kept by a window gone idle. The scratch is now the largest
|
|
183
|
+
region's, its passes load rather than clear, the composite rides in the
|
|
184
|
+
pass that follows, and the first frame without a blur drops it all:
|
|
185
|
+
65.5 MB → 16.8 MB and ~230 → ~150 µs a frame at 2560×1600 for one
|
|
186
|
+
toolbar, the output bit-identical.
|
|
187
|
+
- **Windows: one answer for the window's surface and the swapchain**
|
|
188
|
+
(backlog RG150). `WGPU_BACKEND` decided the first and
|
|
189
|
+
`WGPU_DX12_PRESENTATION_SYSTEM` the second, so `WGPU_BACKEND=dx12` drew
|
|
190
|
+
translucency over black while the window reported `Blur`. Compiled and
|
|
191
|
+
read, not run.
|
|
192
|
+
- **Linux: the wallpaper is found off the event loop** (backlog RG150):
|
|
193
|
+
up to three `gsettings` runs at window creation on GNOME before the
|
|
194
|
+
window showed; and Wayland's blur managers are bound once per process,
|
|
195
|
+
not once per window and never released. A window with no wallpaper to
|
|
196
|
+
read is `Tinted` until the thread answers, within its first frames,
|
|
197
|
+
and `Opaque` from then on (see What breaks). Compiled and read, not
|
|
198
|
+
run; the Linux half built and smoked under WSLg's X11 in the pre-tag
|
|
199
|
+
pass.
|
|
200
|
+
|
|
201
|
+
- **A box that becomes a float keeps a float it held above it** (backlog
|
|
202
|
+
RG151, from berainder). The float stack kept last frame's floats in
|
|
203
|
+
their order and put a new one on top, so a box that kept its key while
|
|
204
|
+
turning into a float went over the float inside it: a debug build
|
|
205
|
+
panicked on the stack's own assertion, a release build painted and hit
|
|
206
|
+
the inner float under its parent. A float found below the one it is in
|
|
207
|
+
now moves to just above it.
|
|
208
|
+
- **`border` on an image draws** (backlog RG152, from berainder). The
|
|
209
|
+
border was painted with the background, under the picture, which
|
|
210
|
+
covered it; on an image or a fragment it is now a ring over the
|
|
211
|
+
content, as over a gradient. And an image with a `gradient` and a
|
|
212
|
+
`border` no longer carries an empty quad where the gradient's own
|
|
213
|
+
ring went (from the alpha.44 pre-tag pass).
|
|
214
|
+
- **The float stack keeps the order of two floats a box held, and
|
|
215
|
+
sorts a float moved into one** (backlog RG153, from the alpha.44
|
|
216
|
+
pre-tag pass). RG151's move put each held float just above the box in
|
|
217
|
+
turn, so of two — a name panel and a tip opened over it — the first
|
|
218
|
+
came out on top; and a float that moves into another float under a
|
|
219
|
+
key the app keeps (`open_key`, `leaf_key`) changed no rank, so the
|
|
220
|
+
steady path kept it under the float it is now in and the debug
|
|
221
|
+
assertion tripped by RG151's other road. A held float now waits for
|
|
222
|
+
the float it is in and goes just above it, in the order it had; and
|
|
223
|
+
the steady order is checked for nesting and rebuilt when it fails.
|
|
224
|
+
|
|
225
|
+
**What you can delete.**
|
|
226
|
+
|
|
227
|
+
- An app's own truncation of menu labels to keep a menu on screen
|
|
228
|
+
(RG150).
|
|
229
|
+
- A key of its own for a box on each side of becoming a float — the
|
|
230
|
+
card behind and the card on top — kept only to stop a float inside it
|
|
231
|
+
falling under it (RG151), or to keep two floats inside it in the order
|
|
232
|
+
they opened (RG153).
|
|
233
|
+
- A padded box around an image to draw its border (RG152).
|
|
234
|
+
|
|
235
|
+
### Native verification
|
|
236
|
+
|
|
237
|
+
The by-hand round alpha.6 introduced (backlog R4), on 2026-10-08, over
|
|
238
|
+
RG150 from alpha.43's pre-tag pass and berainder's RG151 and RG152, with
|
|
239
|
+
alpha.44's pre-tag pass over them on the Windows machine and under WSLg:
|
|
240
|
+
the mechanical round on both, then three read-only reviews of the diff
|
|
241
|
+
since alpha.43 (the menus; the backdrop and the blur, read against
|
|
242
|
+
wgpu's and wayland-client's sources; the floats, the image border and
|
|
243
|
+
the docs), each claim probed. They filed RG153, built before the tag
|
|
244
|
+
(the float stack reversing two floats a box held, and keeping a float
|
|
245
|
+
moved into another under a kept key below it), and RG154, open; and the
|
|
246
|
+
round itself caught F127's width test failing on both platforms under
|
|
247
|
+
RG150's ceiling (its accelerator is a word each there, wider than the
|
|
248
|
+
test's window).
|
|
249
|
+
|
|
250
|
+
**Windows**, the pre-tag pass. fmt and clippy are clean; `nu
|
|
251
|
+
scripts/test.nu --node`: **2144 tests over 143 suites**, 0 failed. The C round passes (6 checks), and so do the **57 scenes**
|
|
252
|
+
through Rust, Lua, C, Node and Odin; the Odin binding's four steps with
|
|
253
|
+
CI's pinned `dev-2026-09`; Node's tests under
|
|
254
|
+
`KUI_CONFORMANCE_REQUIRED=1` (**218 of 219**, the one skip Windows'),
|
|
255
|
+
`npm run gen` with no diff, the examples' typecheck, the headless round
|
|
256
|
+
(37 drives) and the book's listing. The windowed round with Node's:
|
|
257
|
+
**53 examples on both bases**, every one clean on a first run; `counter`
|
|
258
|
+
and `host` opened by hand after `cbuild`. The bench guard against the
|
|
259
|
+
alpha.43 tag: **green**, the guarded rows −5.9% to +3.6%
|
|
260
|
+
(`frame_10k_rects_with_access_tree`; `frame_10k_rects` itself −5.9%),
|
|
261
|
+
the worst run-to-run spread on a guarded row 5.4%.
|
|
262
|
+
|
|
263
|
+
**Linux**, under WSLg (llvmpipe), the same commit with the pass's
|
|
264
|
+
fixes: fmt and clippy clean; `cargo test --workspace`: **1921 tests
|
|
265
|
+
over 142 suites**, 0 failed; the C round (5 checks) and the 57 scenes
|
|
266
|
+
through every adapter, the Odin binding's four steps, Node **219 of
|
|
267
|
+
219** with the corpus required, gen clean, the typecheck, the headless
|
|
268
|
+
round. The windowed round under X11 with Node's: 53 examples on both
|
|
269
|
+
bases; eight windows of a first run four at a time died with
|
|
270
|
+
"X connection to :0 broken" — XWayland's, twice — and the four
|
|
271
|
+
examples drew every frame run again one at a time. Nothing opened a
|
|
272
|
+
Wayland window (Weston's decorations crash under winit here) and
|
|
273
|
+
nothing read a wallpaper: F126's Linux half is still compiled and read,
|
|
274
|
+
not met on KDE or GNOME. No Mac ran this round: the AX audit and the
|
|
275
|
+
macOS halves of RG150 are CI's check and the next Mac round's.
|
|
276
|
+
|
|
24
277
|
## 0.1.0-alpha.43 (2026-10-08)
|
|
25
278
|
|
|
26
279
|
**What breaks.**
|
|
@@ -250,8 +503,6 @@ in the JSON a row already was.
|
|
|
250
503
|
…" rows one per folder up to a cap (F128).
|
|
251
504
|
- A toolbar's opaque fill over content that scrolls under it (F129).
|
|
252
505
|
- Inline code drawn in the body face, or as a box beside the text (F130).
|
|
253
|
-
- A toolbar's opaque fill over content that scrolls under it (F129).
|
|
254
|
-
- Inline code drawn in the body face, or as a box beside the text (F130).
|
|
255
506
|
|
|
256
507
|
### Native verification
|
|
257
508
|
|
|
@@ -180,10 +180,17 @@ and none of which the tree order gives:
|
|
|
180
180
|
declares it under a fresh key. This is the only "raise" and it is not a
|
|
181
181
|
row; see *What was declined*.
|
|
182
182
|
|
|
183
|
-
A nested float is above its enclosing float
|
|
184
|
-
the same frame (appended after its parent, in tree
|
|
185
|
-
|
|
186
|
-
|
|
183
|
+
A nested float is above its enclosing float, and almost always without a
|
|
184
|
+
rule: it opened the same frame (appended after its parent, in tree
|
|
185
|
+
order) or a later one (appended above), and a key derived from its
|
|
186
|
+
parent's cannot have been opened first. Two roads reach the reverse,
|
|
187
|
+
both under a key the app keeps across the change (backlog RG151 and
|
|
188
|
+
RG153, 2026-10-08): a box that becomes a float around a float it already
|
|
189
|
+
held — the box is new to the stack and the held float is not — and a
|
|
190
|
+
float that moves into another float under `open_key`, which changes no
|
|
191
|
+
rank. So the rebuild places each float after the float it is in, the
|
|
192
|
+
held ones in the order they had, and the steady order is checked for the
|
|
193
|
+
same and rebuilt when it fails. A `debug_assert` says the result holds.
|
|
187
194
|
|
|
188
195
|
The steady state — the same float roots as last frame — is one
|
|
189
196
|
comparison of two short key lists and costs nothing more; the first
|
package/encoder.js
CHANGED
|
@@ -1026,7 +1026,9 @@ export function createEncoder(P) {
|
|
|
1026
1026
|
for (const o of p.options) {
|
|
1027
1027
|
const ok = (typeof o === 'string' && o.length > 0) || (o !== null && typeof o === 'object' && !Array.isArray(o));
|
|
1028
1028
|
if (!ok) throw new Error('<select> options are non-empty strings or menu item objects { label, id, enabled }');
|
|
1029
|
-
|
|
1029
|
+
// An option is chosen, never opened: its `items` are dropped,
|
|
1030
|
+
// so they are reported as any key no option reads (RG150).
|
|
1031
|
+
if (typeof o === 'object') for (const k in o) if (!MENU_ITEM_KEYS.has(k) || k === 'items') unknown.push([MENU_ITEM, k]);
|
|
1030
1032
|
}
|
|
1031
1033
|
const current = p.current;
|
|
1032
1034
|
if (current != null && (!Number.isInteger(current) || current < 0)) {
|
package/howto.md
CHANGED
|
@@ -805,14 +805,21 @@ clicked, or Enter or the Right arrow is pressed; Left or Escape closes
|
|
|
805
805
|
it, and Escape again closes the menu. It nests, in a context menu and in
|
|
806
806
|
a menu bar, drawn or the platform's. A row inside is chosen like any
|
|
807
807
|
row: one `{kind:"menu", role, item}` with its own `id`, on the node the
|
|
808
|
-
menu is about.
|
|
808
|
+
menu is about. Give the rows inside an `id`: a row without one posts its
|
|
809
|
+
label, and "Name" under "Sort by ▸" and "Name" under "Group by ▸" would
|
|
810
|
+
post the same `item`. A host that shows menus itself reports one with
|
|
809
811
|
`Core::activate_menu_path(&[1, 0])` (Node `activateMenuPath`, C
|
|
810
812
|
`kui_activate_menu_path` with a path of `size_t`s). In C a row's
|
|
811
813
|
`submenu` / `submenu_count` nest the same `KuiMenuItem`s, and
|
|
812
814
|
`KUI_MENU_ITEM_SUBMENU` in a row's flags says it has rows, read with
|
|
813
815
|
`kui_menu_item_path`. An
|
|
814
816
|
`accel` in the portable spelling (`"mod+shift+n"`) is drawn the
|
|
815
|
-
platform's way, and the menu widens to its longest row
|
|
817
|
+
platform's way, and the menu widens to its longest row, up to the window
|
|
818
|
+
less a margin (and never below the metric's menu width), where a longer
|
|
819
|
+
label ends in an ellipsis — and past the label's 48 px floor, the
|
|
820
|
+
accelerator does. On the frame
|
|
821
|
+
clock, an open submenu waits 0.3 s before giving way to a row the
|
|
822
|
+
pointer crosses, so a diagonal path into it does not close it.
|
|
816
823
|
|
|
817
824
|
[ADR 0018](docs/adr/0018-a-menu-bar-the-app-declares.md) ·
|
|
818
825
|
[`tests/submenu.rs`](../crates/kui-core/tests/submenu.rs)
|
package/package.json
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/props.md
CHANGED
|
@@ -118,9 +118,9 @@ where they make sense); text props apply to `<text>` and `<edit>`.
|
|
|
118
118
|
|---|---|---|---|---|---|
|
|
119
119
|
| `color` | `color` | `KuiTextStyle.color` | `Text_Style.color` | color (`#hex` or `0xRRGGBBAA`), or a `"$color"` token | Text color; default foreground when omitted. |
|
|
120
120
|
| `ellipsis` | `ellipsis` | `KuiTextStyle.ellipsis` | `Text_Style.ellipsis` | boolean | End the last line with an ellipsis when the text is cut off: a single line unless `maxLines` says otherwise. |
|
|
121
|
-
| `family` | `family` | `KuiTextStyle.family` (`KUI_FONT_*`) | `Text_Style.family` | `sans` \\| `serif` \\| `mono` \\| a family name | Font family: `sans`, `serif` or `mono`, kui's own, or the name of an installed family or one loaded with `loadFontsDir` / `loadFontFile` — `"Berkeley Mono"` — drawn in its face in the frame that names it (ADR 0037). A name is matched as `addSystemFont` matches it and registered in the session on first sight, exactly as the font database spells it (`"menlo"` is not `"Menlo"`); the session's first registration of any font maps the installed font files once (~30 ms on a Mac, backlog DX24), which a family named in a view pays in that frame. `systemFonts()` lists the names there are. A name nothing matches shapes as sans and raises `unknown-family`. It and `font` set the same thing, so declare one. |
|
|
121
|
+
| `family` | `family` | `KuiTextStyle.family` (`KUI_FONT_*`); `KuiSpan.family` (with `KUI_SPAN_FAMILY`) | `Text_Style.family` | `sans` \\| `serif` \\| `mono` \\| a family name | Font family: `sans`, `serif` or `mono`, kui's own, or the name of an installed family or one loaded with `loadFontsDir` / `loadFontFile` — `"Berkeley Mono"` — drawn in its face in the frame that names it (ADR 0037). A name is matched as `addSystemFont` matches it and registered in the session on first sight, exactly as the font database spells it (`"menlo"` is not `"Menlo"`); the session's first registration of any font maps the installed font files once (~30 ms on a Mac, backlog DX24), which a family named in a view pays in that frame. `systemFonts()` lists the names there are. A name nothing matches shapes as sans and raises `unknown-family`. It and `font` set the same thing, so declare one. |
|
|
122
122
|
| `features` | `features` | `KuiTextStyle.features` (a `KuiStr`, the same spelling) | `Text_Style.features` | string | OpenType features for the shaper, as `tag=value` pairs separated by spaces or commas — a bare `tag` is 1, `-tag` is 0: `"liga=0 calt=0"` keeps a coding font from joining `->` and `!=` (what a terminal built on runs needs to hold its grid), `"tnum"` lines figures up in a gutter, `"ss01"` picks a stylistic set. Unset, the font's own defaults apply. At most 8; part of what the text is shaped as, so two texts differing only here are shaped twice. |
|
|
123
|
-
| `font` | `font` | `KuiTextStyle.font` (from `kui_font_add*`) | `Text_Style.font` | resource handle | A registered font handle (addFont / addSystemFont); overrides `family`. |
|
|
123
|
+
| `font` | `font` | `KuiTextStyle.font` (from `kui_font_add*`); `KuiSpan.font` | `Text_Style.font` | resource handle | A registered font handle (addFont / addSystemFont); overrides `family`. |
|
|
124
124
|
| `lineHeight` | `line_height` | `KuiTextStyle.line_height` | `Text_Style.line_height` | number, or a `"$length"` token | Line height (logical px); default size * 1.35. |
|
|
125
125
|
| `maxLines` | `max_lines` | `KuiTextStyle.max_lines` | `Text_Style.max_lines` | number, or a `"$length"` token | Lay out at most this many lines (0 = unlimited); with `ellipsis`, a line clamp. |
|
|
126
126
|
| `strikethrough` | `strikethrough` | `KuiTextStyle.decoration` (`KUI_DECO_STRIKETHROUGH`); `KuiSpan.flags` (`KUI_SPAN_STRIKETHROUGH`) | `Text_Style.strikethrough` | boolean | A line through the text, where the face puts its strikeout. Paint only; on a `<span>` the span alone, per line. |
|
|
@@ -146,7 +146,7 @@ where they make sense); text props apply to `<text>` and `<edit>`.
|
|
|
146
146
|
| `pad`, `padX`, `padY`, `padL`, `padR`, `padT`, `padB` | `pad = n` or `pad = { all=, x=, y=, l=, r=, t=, b= }` | `pad_l`, `pad_r`, `pad_t`, `pad_b` | `Spec.pad`: `kui.pad(16)`, `kui.pad(16, 8)`, or `{l = .., r = .., t = .., b = ..}` | Padding; a frontend reports the names it saw and `PadShorthand::resolve` turns them into four edges — an edge falls back to its axis, an axis to the all-round `pad`, and the specific one always wins. |
|
|
147
147
|
| `rowCount` | `row_count` | `kui_row_count` | `Spec.row_count`, a `Maybe(u64)`, which calls `kui.row_count` on the node | How many `index`ed rows this node's virtual list has, built or not. `uniformList` / `uniform_list` / `widgets::uniform_list` and `widgets::list` declare it on their container; a list composed by hand says it beside `scrollY`. What it buys: Select All (Cmd/Ctrl-A, the menu's row) inside a `selectable` virtual list selects the *data*, rows `0..rowCount`, rather than the rows the frame built, and the copy is a `selectionrange` ask whose `to.byte` is past the last row's length when that row is not built — cut it to the row. Without it Select All is the built rows, which is all the core can see. |
|
|
148
148
|
| `secureInput` (root box only) | `secure_input = true` (root table) | `kui_set_secure_input` | `kui.set_secure_input` | Declares that this frame wants the keyboard to this window kept from every other process while the window has it — macOS's Secure Keyboard Entry, what a terminal turns on at a password prompt (backlog F85). Frame state the way `alwaysOnTop` is, default false: declare it on every frame the prompt is up, and the frame that stops is what turns it off, so nothing has to remember to undo it. The runner owns the platform call and its balance: `EnableSecureEventInput` is process-wide and counted, and the runner holds one count while a window whose frame asked has the keyboard, giving it back when that window loses the keyboard, closes or stops asking, and at exit — Apple's rule, since while it is on no other process can read the keyboard at all (a launcher's hotkey, a text expander, an accessibility tool). Nothing on Windows or Linux, which have no such switch. A C host with its own loop reads the ask with `kui_secure_input_get` and makes the call itself. |
|
|
149
|
-
| `size` (text) | `size` | `KuiTextStyle.size` | `Text_Style.size` | Font size in logical px; the text style is constructed from it, so declare it for the other style props to apply at that size. |
|
|
149
|
+
| `size` (text) | `size` | `KuiTextStyle.size`; `KuiSpan.size` | `Text_Style.size` | Font size in logical px; the text style is constructed from it, so declare it for the other style props to apply at that size. |
|
|
150
150
|
| `title` (root box only) | `window_title` (root table) | `kui_window_title` | `kui.window_title` | Declares the window title for this frame; the driver diffs and applies. |
|
|
151
151
|
| `tooltip="hint"` | `tooltip = "hint"` | `KuiSpec.tooltip` (`kui_tooltip` / `kui_tooltip_with` draw a hint that is not hover-gated) | `Spec.tooltip` (`kui.tooltip` / `kui.tooltip_with` draw a hint that is not hover-gated) | Floats a hint below the node while hovered. All three effects — hover tracking, the accessible description, and the float itself — come from `PropsOut::apply_tooltip`, so no frontend can implement two of them; a Rust view has all three in `NodeSpec::tooltip` (`NodeSpec::apply_tooltip` is the spec half, for a caller that floats the hint itself). The `description` row is that middle effect on its own, for a hint that is spoken and never drawn. On a box or a `fragment` the float is the node's last child; a leaf holds no children — a `line`, `polygon`, `path`, `cells` grid, `image` or `edit` — and its hint floats beside it instead, anchored to it, and lands below its box the same way, out of every clip and flipping above near the window's bottom (backlog RG113; `PropsOut::for_leaf`). A leaf draws its description, which is the hint unless a `description` applied after it overwrote the slot. A `line`, `polygon` or `path` is hovered by its shape, so its hint shows while the pointer is on the stroke or inside the outline, not anywhere in its box. |
|
|
152
152
|
| `windows={[{ name, kind?, anchor?, width?, height?, activates? }]}` (root box only; `windows: (model) => [...]` in the loop config) | `windows = { { name=, kind=, anchor=, width=, height=, activates= } }` (root table) | `kui_window_declare` | `kui.window_declare` | Declares which windows exist this frame, by stable name (`docs/adr/0004-multi-window.md`). A window opens on the first frame any window's frame declares it — its config is read then and never again, since the user owns its geometry once it exists — and closes on the first frame none does. The driver drains the `Open` / `Close` that result, and the app sees `{kind:"window", phase, name, id}`. A window the user closed does not reopen while it is still declared: stop declaring it, then declare it again. `kind: "popup"` makes it a menu surface instead: borderless, off the taskbar, owned by the window that declared it and closed with it, placed in screen coordinates against `anchor` — the `{x, y, w, h}` an `onLayout` node reported — and non-activating unless `activates` says otherwise, so the field that opened it keeps the focus ring while the arrows walk the list. A press outside it or Escape raises `{kind:"dismiss", reason, name, id}` and closes nothing, exactly as a `modal` node's does: stop declaring the window. Reach for a popup only for the placements a float cannot make — a list taller than the window, a menu with nowhere in-window to go, a panel beside the app; everything else stays `fit` plus a `modal` float, which costs one tree instead of an OS surface. |
|
|
@@ -654,6 +654,6 @@ its generator (`nu scripts/odin.nu gen --check`).
|
|
|
654
654
|
| `Core::audio_ended` | `kui_audio_ended` | `audio_ended` | `Ctx.audioEnded` | *none: a script is a guest in the host's frame (ADR 0014): its env is the view's reading, and registering, driving, pacing and reading back are the host's* | The device reports a playback over. |
|
|
655
655
|
| `Core::audio_truncated` | `kui_audio_truncated` | `audio_truncated` | `Ctx.audioTruncated` | *none: a script is a guest in the host's frame (ADR 0014): its env is the view's reading, and registering, driving, pacing and reading back are the host's* | The device reports a stop that cut a playback short — a one-shot node's removal becomes `truncated-playback`. |
|
|
656
656
|
| `Core::audio_refused` | `kui_audio_refused` | `audio_refused` | `Ctx.audioRefused` | *none: a script is a guest in the host's frame (ADR 0014): its env is the view's reading, and registering, driving, pacing and reading back are the host's* | The device reports a play it would not take — a `refused` sound event and `playback-refused`. |
|
|
657
|
-
| `Launcher::size` | `width` / `height` in the `KuiRunConfig` `kui_run_with` takes | `width` / `height` in the `Run_Config` `kui.run` takes | `width` / `height` in `WindowOptions` | *none: a script is a guest in the host's frame (ADR 0014): its env is the view's reading, and registering, driving, pacing and reading back are the host's* | The window's opening size; `min_size` / `max_size` / `chrome` / `text_aa` / `diagnostics` / `frame_latency` are the rest of the set, and each binding's form carries them all (`min_w`, `chrome`, `text_aa`, `diagnostics`, `frame_latency` in C; `minWidth`, `chrome`, `textAa`, `diagnostics`, `frameLatency` in Node). `Launcher::devtools` and `Launcher::core` are the two the others reach another way: `kui_set_devtools` / `setDevtools` on the context, and the context handed to `kui_run_with` *is* the core. |
|
|
657
|
+
| `Launcher::size` | `width` / `height` in the `KuiRunConfig` `kui_run_with` takes | `width` / `height` in the `Run_Config` `kui.run` takes | `width` / `height` in `WindowOptions` | *none: a script is a guest in the host's frame (ADR 0014): its env is the view's reading, and registering, driving, pacing and reading back are the host's* | The window's opening size; `min_size` / `max_size` / `chrome` / `text_aa` / `diagnostics` / `frame_latency` / `backdrop` are the rest of the set, and each binding's form carries them all (`min_w`, `chrome`, `text_aa`, `diagnostics`, `frame_latency`, `backdrop` in C and Odin; `minWidth`, `chrome`, `textAa`, `diagnostics`, `frameLatency`, `backdrop` in Node). `Launcher::devtools` and `Launcher::core` are the two the others reach another way: `kui_set_devtools` / `setDevtools` on the context, and the context handed to `kui_run_with` *is* the core. |
|
|
658
658
|
| `Launcher::icon` | `kui_set_icon` | `set_icon` | `icon` in `WindowOptions` | *none: a script is a guest in the host's frame (ADR 0014): its env is the view's reading, and registering, driving, pacing and reading back are the host's* | The icon every window of the app is created with — RGBA pixels and their size — shown by Windows in the title bar, Alt-Tab and the taskbar and by X11's window manager; macOS (the bundle's `.icns`) and Wayland (the `.desktop` file's) have no window icon (backlog F86). `Launcher::icon_resource` is the Windows executable's own icon resource, which wins there — C's `resource` argument, Node's `icon.resource`. C's is a free function called before `kui_run`, for `kui_on_teardown`'s reason. |
|
|
659
659
|
| `App::teardown` | `kui_on_teardown` | the `teardown` procedure `kui.run` takes | `KuiWindow.onTeardown` | *none: a script is a guest in the host's frame (ADR 0014): its env is the view's reading, and registering, driving, pacing and reading back are the host's* | The window going for good — its close button, Quit from the menu or the dock, a close command on it, a pumped runner ended — heard once, before `run` returns or the process exits, with nothing drawing: the place to keep what the app would lose with the window (backlog F74, the other two hosts under RG1). On macOS a Quit ends the process from inside the loop, so this is the only thing an app runs on ⌘Q — nothing after `run`, `kui_run` or `await runWindowed(...)` does, not even `process.on('exit')`. C's is a free function called before `kui_run`, with the run's `user`, since `kui_run`'s app is three arguments and not a struct. Node's is the window's door, called from inside the pump that saw the window go; `runWindowed` registers its config's `teardown(model)` there, and `createApp`'s `app.teardown()` runs the same one for a headless drive. |
|