@qxuken/kui 0.1.0-alpha.39 → 0.1.0-alpha.40

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,95 @@ 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.40 (2026-10-07)
25
+
26
+ **What breaks.**
27
+
28
+ - Rust: `InputEvent` gains `Open(Vec<String>)`, so an exhaustive `match`
29
+ on it needs an arm.
30
+
31
+ C stays at ABI 25 (`kui_input_open` is a new function) and the Node wire
32
+ at v21.
33
+
34
+ ### Added
35
+
36
+ - **The documents the OS asks the app to open reach it** (backlog F124,
37
+ from kawoosh). The Finder's Open With, a file dropped on the Dock icon,
38
+ `open -a YourApp file`, a double-click on a document type the bundle's
39
+ `Info.plist` declares (`CFBundleDocumentTypes`): AppKit hands every one
40
+ to the application delegate's `application:openURLs:`, which winit's
41
+ delegate does not answer, so they went nowhere. The macOS runner adds
42
+ it to winit's delegate class once per process, and the app hears
43
+ `{kind:"open", paths}` on the root — file-system paths, one event per
44
+ request, asked for or not. A launch's documents, which AppKit sends
45
+ before any window exists and leaves out of the arguments, wait for the
46
+ main window and arrive on its first turn; while the app runs they
47
+ arrive at once. A delegate class that already answers documents (an
48
+ app's own override) is left alone. Headless and for a host with its
49
+ own window: `InputEvent::Open(paths)`, C `kui_input_open`, Node
50
+ `ctx.openDocuments(paths)` (`OpenMsg` in `CoreMsg`). Windows and Linux
51
+ hand documents over in the process's arguments, and nothing sends it
52
+ there.
53
+
54
+ **What you can delete.** An `NSApplicationDelegate` method added or
55
+ swizzled by hand onto winit's delegate to hear a Finder open, and the
56
+ queue that held a launch's paths until a window existed to show them.
57
+
58
+ ### Native verification
59
+
60
+ The by-hand round alpha.6 introduced (backlog R4), on 2026-10-07 — the
61
+ one round after the alpha.39 tag: the documents the OS asks the app to
62
+ open (F124) — with a regression pass over it first: the diff read whole,
63
+ each claim probed before anything changed. The probes ran in a window,
64
+ since the half that can break is AppKit's: a scratch app on the
65
+ release tree's `kui-native`, bare and in an ad-hoc-signed bundle
66
+ declaring `public.data` (`LSHandlerRank` `Alternate`). `open -a` at
67
+ launch handed over both documents in one `open` event, `argv` the
68
+ executable alone; two `open -a` calls into the running instance arrived
69
+ as two events, in order, each with its request's paths in their order.
70
+ And the one thing an added `application:openURLs:` could have started
71
+ that F124's own checks did not look at: AppKit taking the process's
72
+ arguments for documents once the delegate answers them
73
+ (`NSTreatUnknownArgumentsAsOpen`). A binary run from a terminal with a
74
+ path, a subcommand, a missing file and a flag, bare and as the bundle's
75
+ own executable, even with `-NSTreatUnknownArgumentsAsOpen YES`, heard
76
+ no `open` — so an app that reads its arguments and hears `open` too
77
+ does not open a file twice. The non-macOS side (`pump_open_documents`
78
+ without `macos_open`) is clean under clippy for `x86_64-pc-windows-msvc`.
79
+ Nothing was filed. This round ran on the Mac alone; Windows and Linux
80
+ did not run it for this tag.
81
+
82
+ **macOS 27.0.1 on an M3 Pro MacBook Pro, rustc 1.99.0 (the toolchain
83
+ CI runs), Node 25.6.0, nu 0.116.1**, on the release commit's tree.
84
+ `cargo fmt --all --check` and `cargo clippy --workspace --all-targets
85
+ --features kui-core/conformance -- -D warnings` are clean, and so is
86
+ `cargo audit --deny warnings`. `cargo test --workspace --features
87
+ kui-core/conformance`: **1822 tests over 137 suites, 0 failed** (4
88
+ ignored). The C round, `cbuild --run`, passes its five checks; the
89
+ corpus passes its **57 scenes** in four adapters, the `drop` scene now
90
+ ending with two documents opened; the ABI is **25**. Node's `node --test
91
+ test.mjs` under `KUI_CONFORMANCE_REQUIRED=1`: **211 of 211**. `npm run
92
+ gen` leaves no diff, the examples typecheck and their lockfile installs,
93
+ the headless round passes all **35 drives**, the book builds and
94
+ `scripts/book-examples.nu --check` passes.
95
+
96
+ **The windowed round**, `smoke -- --node`, three times over — twice
97
+ before the version bump, once on the release tree: **51 Rust examples
98
+ and the eleven Node examples, each on both bases, 120 frames each, every
99
+ one exiting 0** — 124 windows, eight at a time, in 32.3, 32.5 and
100
+ 38.8 s — and `counter`, `host`, `c_panel` and `lua_panel` by hand under
101
+ `KUI_SMOKE_FRAMES=120`, each exiting 0 with nothing on stderr: **128
102
+ windows over five hosts.** The AX audit: **106/106**, the audited window
103
+ raised to the front by its pid first, and no warning on the fixture's
104
+ stderr. F124 itself was seen in kawoosh's window before the merge, built
105
+ against the branch by path.
106
+
107
+ **The bench guard** against the alpha.39 tag: **green**, none of the 8
108
+ guarded rows more than 10% slower — every one between −3.2% and +3.5%
109
+ (the worst guarded run-to-run spread 1.3%) — and no row of the run more
110
+ than 4.7% slower (`frame_1k_paths_animating`, a frame F124 does not
111
+ reach). `README.md`'s table is kept as it was.
112
+
24
113
  ## 0.1.0-alpha.39 (2026-10-06)
25
114
 
26
115
  **What breaks.**
package/howto.md CHANGED
@@ -750,6 +750,35 @@ drive, and `dropTarget()` is what a driver answers the OS with.
750
750
  [`drop` payload](props.md#events) ·
751
751
  [`examples/rust/features/drop.rs`](../examples/rust/features/drop.rs)
752
752
 
753
+ ### How do I hear the documents the OS opens: Finder's Open With, the Dock icon?
754
+
755
+ Handle `{kind:"open", paths}` in `update` (Rust `on_event`, C's event
756
+ callback): it arrives on the root, asked for or not, with the file-system
757
+ paths of the documents the Finder's Open With, a file dropped on the Dock
758
+ icon, `open -a YourApp file.rs` or a double-click handed the app. The
759
+ macOS runner hears them from the application delegate's
760
+ `application:openURLs:` — at launch, where AppKit sends them before the
761
+ window exists and puts none in the arguments, the paths wait for the main
762
+ window and arrive on its first turn; while the app runs, at once. One
763
+ `open` per request, several paths when the user opened several.
764
+
765
+ The app's bundle has to say it opens documents, or the Finder will not
766
+ offer it: an `Info.plist` with `CFBundleDocumentTypes`, one entry per
767
+ kind — `LSItemContentTypes` the UTIs (`public.plain-text`,
768
+ `public.source-code`, `public.data` for anything), `CFBundleTypeRole`
769
+ `Editor` or `Viewer`, and `LSHandlerRank` `Alternate` to be listed in Open
770
+ With without taking the type's double-click from its owner (`Default` or
771
+ `Owner` to take it). Re-register a bundle you rebuilt in place with
772
+ `lsregister -f YourApp.app` if the Finder still has the old list.
773
+
774
+ On Windows and Linux the documents are in the process's arguments (a
775
+ file association, a `.desktop` file's `%F`), and nothing sends `open`.
776
+ Headless, `ctx.openDocuments(paths)` (C `kui_input_open`, Rust
777
+ `InputEvent::Open`) is the drive.
778
+
779
+ [`open` payload](props.md#events) ·
780
+ [alpha.40 `### Added`](CHANGELOG.md#010-alpha40-2026-10-07)
781
+
753
782
  ### How do I have global shortcuts and a Tab ring at once?
754
783
 
755
784
  Put the keymap on an `onKey` sink that encloses the controls: a focused
package/index.d.ts CHANGED
@@ -368,6 +368,18 @@ export type SystemMsg = {
368
368
  * A font the app loads itself raises none. */
369
369
  export type FontsMsg = { kind: 'fonts' };
370
370
 
371
+ /** The OS asked the app to open these documents (backlog F124): the
372
+ * Finder's Open With, a file dropped on the Dock icon, `open -a`, a
373
+ * double-click on a document type the app's Info.plist declares
374
+ * (`CFBundleDocumentTypes`). Delivered on the root whether or not the app
375
+ * asked — at launch, once the window is open, and while it runs. `paths`
376
+ * are file-system paths. macOS only: Windows and Linux hand documents over
377
+ * in `process.argv`. `ctx.openDocuments(paths)` is the headless drive. */
378
+ export type OpenMsg = {
379
+ kind: 'open';
380
+ paths: string[];
381
+ };
382
+
371
383
  /** A declared window opened, or closed — because nothing declares it any
372
384
  * more, or because the user closed it. A window the user closed stays
373
385
  * closed while it is still declared (a declaration reopens a window only
@@ -496,7 +508,8 @@ export type CoreMsg =
496
508
  | SoundMsg
497
509
  | AccessMsg
498
510
  | ChangeMsg
499
- | FilesMsg;
511
+ | FilesMsg
512
+ | OpenMsg;
500
513
 
501
514
  /** A stock slider's proposal (docs/adr/0034-stock-controls-over-the-roles.md):
502
515
  * the core turned a press, a drag, an arrow, a Page key, Home / End or an
@@ -2161,6 +2174,14 @@ export declare class Ctx {
2161
2174
  * asked it is dropped.
2162
2175
  */
2163
2176
  answerFiles(paths: Array<string>): void
2177
+ /**
2178
+ * The OS asked the app to open these documents (backlog F124) — the
2179
+ * Finder's Open With, a file dropped on the Dock icon, `open -a`: the
2180
+ * app hears `{kind:"open", paths}` on the root whether or not it
2181
+ * asked; none is nothing. A `KuiWindow` on macOS hears it from the
2182
+ * runner; this is the headless drive.
2183
+ */
2184
+ openDocuments(paths: Array<string>): void
2164
2185
  /**
2165
2186
  * The dragged files released at (`x`, `y`): the zone there hears
2166
2187
  * `{kind:"drop", phase:"drop", paths, x, y, tag}` and no `leave`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qxuken/kui",
3
- "version": "0.1.0-alpha.39",
3
+ "version": "0.1.0-alpha.40",
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
Binary file
package/props.md CHANGED
@@ -202,6 +202,7 @@ one field. The payload shapes:
202
202
  | hover | `{ kind: "hover", phase: "enter" \| "leave", by: "pointer" \| "content", tag }` | The pointer entered or left an `onHover` node — also when a new frame moved it under a still cursor. `by` says which (backlog DX20): `pointer` when the pointer moved or left the window, `content` when it stayed and what is under it changed — a list scrolled by the wheel or the keys, a row that grew, a float that opened. A picker whose selection follows the pointer ignores `content`, or the rows sliding under a still pointer as the keys scroll the list drag the selection with them. |
203
203
  | drop | `{ kind: "drop", phase: "enter" \| "move" \| "leave" \| "drop", paths: string[], x, y, tag }` | Files dragged in from the OS over an `onDrop` node (`docs/adr/0031-a-drop-zone-is-a-row-and-the-files-are-an-event.md`): `enter` when they come over the zone, `move` while they move over it (never twice for one point), `leave` when they go to another zone, to no zone or out of the window, `drop` when they land — and no `leave` after a `drop`. `paths` are the OS paths as strings; `x`/`y` the pointer in logical viewport coordinates, absent on `leave`. The zone is the topmost one under the pointer by paint order; a node inside it is its, and a node that is no zone is looked past (an overlay shown on `enter` does not end the hover). Nothing is re-resolved when a frame lands: only the driver's next report moves the files, so a zone the view stops declaring hears its `leave` then. |
204
204
  | files | `{ kind: "files", paths: string[], tag }` | A file dialog's answer (backlog C51): what an Open, Save or folder dialog asked for with `requestFiles` / `request_files` / `kui_request_files` picked — the OS paths, as a `drop` carries them — or no paths when the user cancelled. `tag` is the dialog's own. It reaches whoever asked: the host, or the extension whose fill asked; one dialog is out at a time. |
205
+ | open | `{ kind: "open", paths: string[] }` on the root | The OS asked the app to open documents (backlog F124): the Finder's Open With, a file dropped on the Dock icon, `open -a App file`, a double-click on a document of a type the app's `Info.plist` declares under `CFBundleDocumentTypes`. Delivered to the host on the root whether or not anything asked — unlike `files`, which answers an ask. `paths` are file-system paths as strings; a URL of a scheme the app registers is not one, and is not carried (room is left for a `urls` beside `paths`). The macOS runner sends it at launch — held until the main window has opened, since AppKit hands the documents over before it exists and puts none in the arguments — and while the app runs; a host driving its own window sends it as input (`openDocuments`, `kui_input_open`, `InputEvent::Open`). Windows and Linux pass documents in the process's arguments instead, and nothing sends it there. |
205
206
  | layout | `{ kind: "layout", x, y, w, h, parent: { x, y, w, h }, scale, tag }` | The rect layout gave an `onLayout` node (logical px, viewport coords, after scrolling and easing): on its first frame and whenever it changes, never on a frame that left it alone. `scale` is physical px per logical px at the node — `w × scale` by `h × scale` is how many pixels to render for it before `updateImage` (the frame's scale today; where a zoom would compose in). |
206
207
  | resize | `{ kind: "resize", width, height, scale }` | The viewport changed size or DPI (logical px, delivered to the host on the root): the window less the devtools' dock while the panel is docked (`docs/adr/0024`), so a dock coming, going or being dragged is a resize too. `KuiWindow.size()` queries the same numbers, before the first frame as well (backlog F43). The first frame establishes the viewport rather than reporting it, so a dock present at launch posts none. |
207
208
  | window | `{ kind: "window", phase: "opened" \| "closed" \| "focused" \| "blurred", name, id }` | A declared window opened (the diff queued its `Open`) or closed — because nothing declares it any more, or because the user closed it, in which case it stays closed while still declared: stop declaring `name`, then declare it again to reopen. `id` is what its events carry; the event itself is on the root of whichever window's frame noticed. `focused` and `blurred` are this window gaining and losing the keyboard (backlog DX18) — `env.focused` changing, as the driver reports it — so an app that saves on blur or re-reads the clipboard on return hears it once instead of diffing `env.focused` every frame. |
@@ -631,7 +632,7 @@ binding is a row with its three other cells, or a red test.
631
632
  | `Core::set_time` | `kui_set_time` | `Ctx.setTime` | *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 clock the tweens read; a window's runner sets it from the display. |
632
633
  | `Core::env` | `kui_env_set` and its four siblings, `ENV_FIELDS`' C column | `Ctx.setEnv` | *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 host facts written in (the `env` field); a `KuiWindow`'s runner writes its own. |
633
634
  | `Ui::env` | *none: C is the host, so it writes the facts and has no reading (`ENV_FIELDS`)* | `env` | `env`, the view's argument | The facts read back, `ENV_FIELDS` row for row. |
634
- | `Core::handle_input` | `kui_input_cursor` … `kui_input_access`, one per `InputEvent` | `Ctx.cursor` … `Ctx.access`, one per `InputEvent`; a `KuiWindow` refuses injection | *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* | Pointer, wheel, key, text, IME, assistive and OS file-drag input; a wheel gesture's latching is `scroll_gesture` (`kui_input_scroll_gesture`, `Ctx.scrollGesture`, backlog F107); `press` / `release` are a click by label (`kui_input_press`, `Ctx.press`); the file drag is `drag_files` / `drop_files` / `drag_cancel` (ADR 0031); a file dialog's answer is `answer_files` (`kui_input_files`, `Ctx.answerFiles`, backlog C51). |
635
+ | `Core::handle_input` | `kui_input_cursor` … `kui_input_access`, one per `InputEvent` | `Ctx.cursor` … `Ctx.access`, one per `InputEvent`; a `KuiWindow` refuses injection | *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* | Pointer, wheel, key, text, IME, assistive and OS file-drag input; a wheel gesture's latching is `scroll_gesture` (`kui_input_scroll_gesture`, `Ctx.scrollGesture`, backlog F107); `press` / `release` are a click by label (`kui_input_press`, `Ctx.press`); the file drag is `drag_files` / `drop_files` / `drag_cancel` (ADR 0031); a file dialog's answer is `answer_files` (`kui_input_files`, `Ctx.answerFiles`, backlog C51); the documents the OS asked the app to open are `InputEvent::Open` (`kui_input_open`, `Ctx.openDocuments`, backlog F124). |
635
636
  | `Core::modifiers` | `kui_input_modifiers` | `Ctx.modifiers` | *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 modifier state, reported on its own when the OS does (backlog AR22). |
636
637
  | `Core::release_held_keys` | `kui_release_held_keys` | `Ctx.setEnv({focused: false})` releases, as losing the keyboard does for every driver (ADR 0020) | *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* | Lets go of every key the focused sink holds. |
637
638
  | `Core::set_subpixel_text` | `kui_set_subpixel_text` | *none: a Node host never paints: the renderer behind `KuiWindow` is the runner's, and a headless `Ctx` has none* | *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* | LCD subpixel coverage for outline glyphs, for a renderer that blends per channel. |