@qxuken/kui 0.1.0-alpha.41 → 0.1.0-alpha.42

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 CHANGED
@@ -21,6 +21,194 @@ 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.42 (2026-10-07)
25
+
26
+ **What breaks.**
27
+
28
+ - A press after a dead key it does not combine with carries what the
29
+ platform typed in its `text`: German's `^ space` is a Space whose
30
+ `text` is `^`, and `^ z` a `z` whose `text` is `^z`, where they said `" "`
31
+ and `z` — in a key sink's payload and in what an editor inserts (under
32
+ Fixed, RG127). A keymap that binds Space by its `text` rather than its
33
+ `code` sees the accent there.
34
+ - Node: a whole number past 2^53 in a message comes back a `number`,
35
+ where it came back a `BigInt` (RG138).
36
+ - C: `kui_take_menu_action` with a NULL or too-small `out` leaves the
37
+ action queued, where it dropped it; `kui_take_warnings` keeps what does
38
+ not fit `cap` for the next call, where it dropped it; `kui_draw_data`'s
39
+ `fragments` and `textures` are NULL on a frame that draws none, where
40
+ they were a non-NULL pointer to nothing (RG133, RG134, RG131).
41
+ - A text style whose size or line height is under a pixel — 0, negative,
42
+ NaN — shapes at a pixel, where Node and Lua aborted or hung and Rust
43
+ panicked (RG136); a cell grid's rows are a pixel apart at an infinite
44
+ one, where they went to the layout's limit (RG144).
45
+ - Rust: `schema::Door`, `schema::CustomProp` and `schema::ElementDef` gain
46
+ `odin` (under Added, the Odin binding), so a struct literal of one needs
47
+ the field.
48
+
49
+ C stays at ABI 25 (kui.h gains no function and no field; four doors now
50
+ keep what it always promised) and the Node wire at v21 (no frame version).
51
+
52
+ ### Added
53
+
54
+ - **An Odin binding, experimental** (`packages/odin`). `kui/c` mirrors
55
+ kui.h declaration by declaration, pinned to the C compiler's layout by
56
+ 848 generated `#assert`s. `kui` is the typed layer, generated from
57
+ kui.h and the prop schema:
58
+ - `Spec` and `Text_Style`, one field per schema row;
59
+ - an enum or bit set per family of constants;
60
+ - a door per C function: out-params as results, arrays as slices,
61
+ messages as plain Odin values with `#[derive(Message)]`'s `kind`
62
+ rule.
63
+
64
+ The elements are written by hand: containers close at the end of their
65
+ `if` through `@(deferred_in)`. A C function the hand-written files do
66
+ not call gets a generated door, so the binding covers kui.h whole: 219
67
+ generated, 51 by hand, 2 skipped with their reason.
68
+ `examples/odin/tools/surface.odin` is `surface.c` through it, and calls
69
+ every door, and `examples/odin/tools/conformance.odin` rebuilds the
70
+ scene corpus through `Spec` and the doors, matching the reference report
71
+ byte for byte, as the Rust, Lua, C and Node adapters do.
72
+ `polyline` and `polygon` take points (`[][2]f32`), the count kui.h
73
+ means; a dash or a pivot is a fixed array in a `Maybe` (`[5]f32`,
74
+ `[2]f32`), so a short one cannot be written; and pixels are checked
75
+ against the size beside them.
76
+
77
+ It covers extensions both ways. An Odin host loads a plugin with
78
+ `ctx_add_extension` and `slot`. An Odin plugin is a shared library whose
79
+ seven `kui_ext_*` exports are one line each over `extension.odin`.
80
+ Built with `KUI_PLUGIN` on macOS and Linux, it links no kui and loads
81
+ into any host (on Windows it imports `kui_ffi.dll`, as a C plugin does):
82
+ `nu scripts/odin.nu slots` drives the Odin panel in the C and Rust
83
+ hosts and the C panel in the Odin host.
84
+
85
+ Run it with `nu scripts/odin.nu gen | test | slots | run counter`. It is
86
+ not published. CI's `check` installs a pinned Odin release (`ODIN_VERSION`)
87
+ and runs `gen --check`, `check`, `test` and `slots`, so a header or schema
88
+ change that was not regenerated goes red there. All four also run green
89
+ by hand on Windows x64 and Linux x64. On Windows the binding links
90
+ `kui_ffi.dll.lib`, odin.nu puts `kui_ffi.dll` beside the programs, and
91
+ `gen --check` reads a CRLF checkout as current.
92
+ - `scripts/pack-ffi.nu`: libkui_ffi for every platform kui ships, dynamic
93
+ and static, with `kui.h`, rustc's `native-static-libs` as `link.txt`, a
94
+ tarball each and `SHA256SUMS`. It is for a C or Odin host that links a
95
+ library instead of building the workspace. The machine's own platform
96
+ is built natively. Linux x64 and arm64 (glibc 2.28) and Windows x64 are
97
+ built in a pinned Docker image, with cargo-zigbuild at the release
98
+ workflow's pins and with cargo-xwin. Windows also asks for
99
+ `--accept-msvc-license`, for the CRT and SDK xwin downloads. macOS is
100
+ built on a Mac only. A Windows pack carries windows-targets' import
101
+ libraries (`windows.0.53.0.lib`, `windows.0.52.0.lib`), which its
102
+ `link.txt` names and no SDK has, so a static link finds them. Every
103
+ leg has been run, and a C host linked against each x64 pack alone,
104
+ dynamically and statically, and run; the arm64 pack was read, with no
105
+ arm64 machine to run it on. `kui.h` ships with LF line ends from any
106
+ checkout.
107
+ - `examples/rust/tools/schema-dump.rs`: the prop schema (props,
108
+ composites, elements, events, the verb table, the name lists) as JSON,
109
+ for a binding generated outside Rust.
110
+ - The verb table (`schema::DOORS`) and `docs/props.md` have an Odin
111
+ column beside C's, each spelling held by the Odin generator both ways;
112
+ Node's `protocol()` carries it (`Door.odin`).
113
+
114
+ ### Fixed
115
+
116
+ - **A dead key types its accent before a key it does not combine with**
117
+ (backlog RG127, from the Windows and Linux round after alpha.41). With
118
+ German, `^ space` typed a space where it types `^`, `´ space` a space
119
+ where it types `´`, and `^ z` a `z` where it types `^z` — on Windows
120
+ always, and under X11 with `imeOff` on; on US-International, where `'`
121
+ and `"` are dead, a quote could not be typed at all. winit composed it
122
+ right: the runner read a press's text off the logical key, which is
123
+ the key the accent fell back to, and Space's insert was a space
124
+ whatever the press carried. A press's text is now what the platform
125
+ composed, the layout's character where it composed nothing, and Space
126
+ inserts its own. `^ e` was right and is unchanged; with the input
127
+ method on, X11 composes through XIM as before.
128
+ - The docs of F103 (alpha.41) said an animating Windows window behind
129
+ other windows skips its frames. It does not: DWM composes a covered
130
+ window, and it draws at the display's rate, 241 frames a second here,
131
+ as an uncovered one does; minimized, it asks for none (RG45). F103's
132
+ wait holds there only for an acquire that times out. `retry.rs` says so
133
+ now (backlog RG128).
134
+ - **Text is read aloud on Windows and Linux** (backlog RG129, from the
135
+ regression and smoke round after RG127). Every static text — a line of
136
+ text, a list row's content, a drawn title — reached UI Automation with
137
+ an empty Name and AT-SPI with an empty name: AccessKit names a label by
138
+ its value, and kui set only its label. macOS read it all along.
139
+ - **A disclosure or a select opens from Narrator** (RG130). UI
140
+ Automation gives a node with `expanded` its ExpandCollapse pattern in
141
+ place of Invoke, and its Expand and Collapse reached nothing. They are
142
+ the node's click.
143
+ - **A text size or line height of 0 no longer ends the process** (RG136).
144
+ `lineHeight: 0`, `size: 0`, or a size under 0.4 (whose line height
145
+ rounds to 0) aborted a Node or Lua process — cosmic-text asserts a line
146
+ height is not 0 — and a negative one spun its layout; on an `edit`, a
147
+ `cells` and in `measureText` too. Every buffer is shaped at a pixel at
148
+ least. C's door had always read them as unset.
149
+ - C: what kui.h says a door hands out lives as long as it says (RG131–
150
+ RG134). A second `kui_draw_data` in a frame freed the arrays the first
151
+ handed out; `kui_access_runs` freed the strings `kui_access_tree`
152
+ handed out; reading a menu row or asking for a copy freed a taken menu
153
+ action's text; and `kui_take_warnings` dropped what did not fit `cap`,
154
+ where kui.h says the rest wait.
155
+ - A custom editor whose `caret` or `selection_anchor` falls inside a
156
+ character has an access tree (RG135): the run was sliced there and
157
+ panicked, which under C emptied the tree every frame; the position is
158
+ the character's start.
159
+ - Node: `measureText` inside a `<devtoolsTab>` function child no longer
160
+ breaks the frame being encoded (RG137); a message, a `<select>` option
161
+ or a `menuBar` row holding a string cut through an emoji draws its lone
162
+ surrogate as U+FFFD rather than failing the frame, and `dir: null` is
163
+ absent like every other null prop (RG138, RG140).
164
+
165
+ ### Native verification
166
+
167
+ The by-hand round alpha.6 introduced (backlog R4), on 2026-10-07, over
168
+ the rounds after the alpha.41 tag — the Odin binding and `pack-ffi.nu`,
169
+ RG127 and RG128 from the Windows and Linux round after alpha.41, and the
170
+ regression and smoke round after RG127 (RG129–RG138) — with alpha.42's
171
+ pre-tag pass over them on the Mac: the mechanical round, then four
172
+ read-only reviews of the diff since alpha.41 (the Odin binding; the C
173
+ doors and `pack-ffi.nu`; the runner's keys, the access bridge, the text
174
+ floor and Node; the docs), each claim probed. They filed RG140–RG144,
175
+ built before the tag (a `<select>` option or menu bar row cut through an
176
+ emoji failing the Node frame, `kui_draw_data` while a frame built losing
177
+ the finished frame's fragment draws, the Odin drains freeing the strings
178
+ they returned, Odin's popups, unchecked slices and acronym kinds, a cell
179
+ grid's infinite line height), and RG145, open.
180
+
181
+ **macOS**, the pre-tag pass. fmt and clippy are clean; `cargo test
182
+ --workspace --features kui-core/conformance`: **1856 tests over 139
183
+ suites**. The C round passes, and so do the **57 scenes**, the Odin
184
+ binding's `gen --check`, `check`, `test` and `slots`, Node's tests under
185
+ `KUI_CONFORMANCE_REQUIRED=1` (**215 of 215**), `npm run gen` with no
186
+ diff, the headless round and the book. The windowed round with Node's:
187
+ **124 windows**. The AX audit: **106/106**. RG127's dead keys, which the
188
+ Windows and Linux round could not reach on a Mac: twelve sequences typed
189
+ into the `edit` example through `CGEventPostToPid` and read back through
190
+ AX, each as AppKit composes it (`⌥e e` is `é`, `⌥e space` `´`, `⌥e z`
191
+ `´z`). The bench guard against the alpha.41 tag: **green**, the guarded
192
+ rows −1.2 to −0.3%, RG126's warm cell grid −0.4%.
193
+
194
+ **Windows 11 x64 (MSVC) and Linux x64 (WSL Ubuntu 24.04), rustc
195
+ 1.99.0**, on the tree with the pre-tag fixes and `pack-ffi.nu`'s. fmt
196
+ and `cargo clippy --workspace --all-targets --features
197
+ kui-core/conformance -- -D warnings` are clean on both. `cargo test
198
+ --workspace --features kui-core/conformance`: **1857 tests over 140
199
+ suites** on Windows (5 ignored), **1853 over 139** on Linux (4
200
+ ignored), **0 failed**. `cargo audit --deny warnings` is clean over
201
+ `Cargo.lock`'s 444 crates. `pack-ffi.nu` ran every leg but macOS's: native
202
+ on both, and linux-x64, linux-arm64 and win32-x64 (cargo-xwin) in its
203
+ container. A C host linked against each x64 pack alone, dynamically and
204
+ statically, ran; the arm64 pack was read, not run. It found three things,
205
+ fixed before the tag (RG145): a CRLF `kui.h` from a CRLF checkout, the
206
+ closing table lost off a terminal, and a `docker` that could not be
207
+ spawned stopping the script. Earlier the same day, before the pre-tag
208
+ fixes, the Odin binding's four steps passed on both, and the regression
209
+ and smoke round after RG127 ran the windowed smoke and walked the
210
+ accessibility tree through UI Automation and AT-SPI.
211
+
24
212
  ## 0.1.0-alpha.41 (2026-10-07)
25
213
 
26
214
  **What breaks.**
package/encoder.js CHANGED
@@ -402,7 +402,8 @@ export function createEncoder(P) {
402
402
  const np = fi++;
403
403
  let n = 0;
404
404
  // dir and size first: the decoder constructs spec/style from them.
405
- if (p.dir !== undefined && p.dir !== 'column') {
405
+ // `!= null`: a null dir is absent, as every other null prop is.
406
+ if (p.dir != null && p.dir !== 'column') {
406
407
  // `table` is a column whose rows' cells line up (ADR 0033).
407
408
  const dir = p.dir === 'row' ? 1 : p.dir === 'table' ? 2 : undefined;
408
409
  if (dir === undefined) throw new Error(`bad dir ${JSON.stringify(p.dir)} (row | column | table)`);
package/index.d.ts CHANGED
@@ -1890,6 +1890,8 @@ export type DoorCell = { is: string } | { as: string } | { no: string };
1890
1890
  export interface Door {
1891
1891
  rust: string;
1892
1892
  c: DoorCell;
1893
+ /** The Odin binding's (packages/odin), pinned by its generator. */
1894
+ odin: DoorCell;
1893
1895
  node: DoorCell;
1894
1896
  lua: DoorCell;
1895
1897
  doc: string;
package/index.js CHANGED
@@ -52,8 +52,12 @@ export function withEffects(model, ...effects) {
52
52
 
53
53
  // A frame crosses the boundary one way: JS encodes the tree into one
54
54
  // Float64Array + string table and the addon lowers it zero-copy. One encoder
55
- // serves every context — its buffers are consumed synchronously.
55
+ // serves every context — its buffers are consumed synchronously. Measuring
56
+ // has one of its own: a `<devtoolsTab>`'s function child is called in the
57
+ // middle of an encode, and a `measureText` there reset the frame's buffers
58
+ // under it ("binary frame must start with the root op").
56
59
  const encoder = createEncoder(native.protocol());
60
+ const measurer = createEncoder(native.protocol());
57
61
 
58
62
  // A prop name the schema does not know has no wire id, so it never crosses:
59
63
  // the encoder is the only side that can see it, and it reports what it
@@ -206,7 +210,7 @@ KuiWindow.prototype.setView = function setView(tree, window) {
206
210
  // it draws. A style name the schema does not know is reported like a
207
211
  // view's would be.
208
212
  function measureText(content, style, maxWidth) {
209
- const { stream, strings, unknown, unknownTokens } = encoder.encodeText(content, style, this[TOKENS]);
213
+ const { stream, strings, unknown, unknownTokens } = measurer.encodeText(content, style, this[TOKENS]);
210
214
  const m = this.measureTextBinary(stream, strings, maxWidth);
211
215
  reportUnknown(this, unknown, unknownTokens);
212
216
  return m;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qxuken/kui",
3
- "version": "0.1.0-alpha.41",
3
+ "version": "0.1.0-alpha.42",
4
4
  "description": "kui for Node: JSX views lowered into the kui IR, Elm-style messages as data",
5
5
  "license": "MIT",
6
6
  "repository": {
Binary file
Binary file
Binary file