Skip to main content

kui_core/schema/
doors.rs

1//! The verb table: one row per call an app or host makes (register a
2//! resource, move focus, open a menu, size a window) with its spelling in
3//! Rust, C, Odin, Node and Lua, or the reason a binding has none.
4//!
5//! A Rust caller uses the methods on `Ui`, `Core` and friends directly;
6//! this table exists so the bindings stay in step with them and with each
7//! other. Each binding's test checks its names against [`DOORS`], so a
8//! verb added to one binding is a row here with its other cells, or a red
9//! test. A [`Cell::No`] says once why a binding lacks a verb instead of
10//! every reader re-deriving it: Lua is a guest with a view-time env,
11//! Node's `Ctx` is a driver and its `KuiWindow` refuses input, C is both,
12//! and Odin's doors are C's. The Odin column is checked by the Odin
13//! binding's generator (packages/odin/gen), which reads this table through
14//! `examples/rust/tools/schema-dump.rs`.
15//!
16//! ```rust
17//! use kui_core::schema::{Cell, DOORS};
18//!
19//! let open_menu = DOORS.iter().find(|d| d.rust == "Ui::open_menu").expect("a verb");
20//! assert!(matches!(open_menu.c, Cell::Is(_)));
21//! ```
22
23/// One binding's cell.
24#[derive(Clone, Copy, Debug, PartialEq, Eq)]
25pub enum Cell {
26    /// The binding has the verb under this name: a `kui_*` function in C;
27    /// a procedure of package `kui` in Odin; a method in Node, on both
28    /// classes unless prefixed `Ctx.` or `KuiWindow.`, or a function the
29    /// package exports when prefixed `kui.`; a function on `env` in Lua.
30    /// Checked by the binding's test.
31    Is(&'static str),
32    /// The binding has the same thing in another form — a prop, a
33    /// reading of `env`, a callback, a constructor option — and the text
34    /// says which.
35    As(&'static str),
36    /// The binding does not have it, and this is why.
37    No(&'static str),
38}
39
40/// One verb.
41pub struct Door {
42    /// The Rust spelling: `Ui::x` (view-time), `Core::x` (a host between
43    /// frames), `SharedResources::x` (the session's registry, reached as
44    /// `core.resources`), `Tokens::x` (a table), `Launcher::x` (the runner).
45    pub rust: &'static str,
46    pub c: Cell,
47    /// A procedure in the Odin binding's package `kui` (packages/odin),
48    /// checked by its generator. Odin's doors are kui.h's, so its column
49    /// follows C's: the same function under its name without `kui_`, the
50    /// Odin spelling of what C has another way, and `No` where C has none.
51    pub odin: Cell,
52    pub node: Cell,
53    pub lua: Cell,
54    pub doc: &'static str,
55}
56
57use Cell::{As, Is, No};
58
59/// The reason most of Lua's column is `No`: a script's env is a *reading*
60/// the host hands it for one `view`, not a handle on the host.
61/// It declares a tree and answers events; what it registers, drives,
62/// times or reads back is the host's.
63pub const GUEST: &str = "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";
64
65/// The reason for Lua's `No` on the resource rows.
66const NO_HANDLE: &str = "a script owns no handle: the host registers and the script names the id it was given (`image { id = }`, `font = id`, `audio { src = id }`)";
67
68/// The reason for Odin's `No`: every Odin door is a kui.h function, so
69/// what C has no door for, Odin has none for either - for C's reason, in
70/// C's cell.
71const ODIN_IS_C: &str =
72    "Odin's doors are kui.h's (packages/odin): C has none, for the reason in C's cell";
73
74/// The reason for Node's `No` on the renderer rows (not done
75/// here).
76const NEVER_PAINTS: &str = "a Node host never paints: the renderer behind `KuiWindow` is the runner's, and a headless `Ctx` has none";
77
78pub const DOORS: &[Door] = &[
79    // -- Resources ---------------------------------------------------------
80    Door {
81        rust: "SharedResources::add_image",
82        c: Is("kui_image_add"),
83        odin: Is("image_add"),
84        node: Is("addImage"),
85        lua: No(NO_HANDLE),
86        doc: "Registers RGBA pixels and mints an id for `<image src>`.",
87    },
88    Door {
89        rust: "Core::update_image",
90        c: Is("kui_image_update"),
91        odin: Is("image_update"),
92        node: Is("updateImage"),
93        lua: No(NO_HANDLE),
94        doc: "Replaces the pixels behind a live id, keeping the id (ADR 0025).",
95    },
96    Door {
97        rust: "Core::remove_image",
98        c: Is("kui_image_remove"),
99        odin: Is("image_remove"),
100        node: Is("removeImage"),
101        lua: No(NO_HANDLE),
102        doc: "Drops an image; every window's atlas lets it go (backlog AR8).",
103    },
104    Door {
105        rust: "Core::image_pixels",
106        c: Is("kui_image_pixels"),
107        odin: Is("image_pixels"),
108        node: No(NEVER_PAINTS),
109        lua: No(NO_HANDLE),
110        doc: "The pixels behind a handle, for a renderer meeting a texture quad.",
111    },
112    Door {
113        rust: "Core::parse_path",
114        c: Is("kui_path_parse"),
115        odin: Is("path_parse"),
116        node: As("`d` on `<path>` is the string; the addon hands it to this parser"),
117        lua: As("`d` on `path { }` is the string; the host hands it to this parser"),
118        doc: "SVG path data to the flat op form a `path` draws (ADR 0040): one parser, so every binding draws the same shape.",
119    },
120    Door {
121        rust: "Core::add_fragment",
122        c: Is("kui_fragment_add"),
123        odin: Is("fragment_add"),
124        node: Is("addFragment"),
125        lua: No(NO_HANDLE),
126        doc: "Registers a WGSL function and mints an id for `<fragment src>` (ADR 0015).",
127    },
128    Door {
129        rust: "Core::remove_fragment",
130        c: Is("kui_fragment_remove"),
131        odin: Is("fragment_remove"),
132        node: Is("removeFragment"),
133        lua: No(NO_HANDLE),
134        doc: "Drops a fragment; the renderer drops its pipelines (backlog AR8).",
135    },
136    Door {
137        rust: "Core::fragment_module_source",
138        c: Is("kui_fragment_source"),
139        odin: Is("fragment_source"),
140        node: No(NEVER_PAINTS),
141        lua: No(NO_HANDLE),
142        doc: "The whole WGSL module behind a handle, which is what a renderer compiles.",
143    },
144    Door {
145        rust: "Core::add_font_data",
146        c: Is("kui_font_add"),
147        odin: Is("font_add"),
148        node: Is("addFont"),
149        lua: No(NO_HANDLE),
150        doc: "Registers a font's bytes and mints an id for `font`.",
151    },
152    Door {
153        rust: "Core::add_system_font",
154        c: Is("kui_font_add_system"),
155        odin: Is("font_add_system"),
156        node: Is("addSystemFont"),
157        lua: No(NO_HANDLE),
158        doc: "Registers an installed family by name.",
159    },
160    Door {
161        rust: "Core::load_font_file",
162        c: Is("kui_font_load_file"),
163        odin: Is("font_load_file"),
164        node: Is("loadFontFile"),
165        lua: No(NO_HANDLE),
166        doc: "Registers a font file by path.",
167    },
168    Door {
169        rust: "Core::load_fonts_dir",
170        c: Is("kui_font_load_dir"),
171        odin: Is("font_load_dir"),
172        node: Is("loadFontsDir"),
173        lua: No(NO_HANDLE),
174        doc: "Registers every font file in a directory.",
175    },
176    Door {
177        rust: "Core::reload_system_fonts",
178        c: Is("kui_font_reload_system"),
179        odin: Is("font_reload_system"),
180        node: Is("reloadSystemFonts"),
181        lua: No(GUEST),
182        doc: "Scans the system's fonts again, so a font installed while the app runs is found (the scan is otherwise once a process); returns how many faces came and went.",
183    },
184    Door {
185        rust: "Core::set_fallback_fonts",
186        c: Is("kui_font_set_fallback"),
187        odin: Is("font_set_fallback"),
188        node: Is("setFallbackFonts"),
189        lua: No(NO_HANDLE),
190        doc: "The fonts asked, in order, for a character the text's own family lacks, before the platform's fallback list (backlog F121).",
191    },
192    Door {
193        rust: "Core::fallback_fonts",
194        c: No("the list is the one the host set"),
195        odin: No(ODIN_IS_C),
196        node: No("the list is the one the host set"),
197        lua: No(NO_HANDLE),
198        doc: "The families `set_fallback_fonts` named, in order.",
199    },
200    Door {
201        rust: "Core::remove_font",
202        c: Is("kui_font_remove"),
203        odin: Is("font_remove"),
204        node: Is("removeFont"),
205        lua: No(NO_HANDLE),
206        doc: "Drops a font.",
207    },
208    Door {
209        rust: "Core::system_font_families",
210        c: Is("kui_font_families"),
211        odin: Is("font_families"),
212        node: Is("systemFontFamilies"),
213        lua: No(NO_HANDLE),
214        doc: "The installed family names `add_system_font` accepts.",
215    },
216    Door {
217        rust: "Core::system_fonts",
218        c: Is("kui_system_fonts"),
219        odin: Is("system_fonts"),
220        node: Is("systemFonts"),
221        lua: No(NO_HANDLE),
222        doc: "The same families, each with what the font database read off its faces: `monospaced` (every face fixed-pitch), `weights`, `italic` (backlog F97) — a font picker's monospaced-first list without a file loaded or a glyph shaped.",
223    },
224    Door {
225        rust: "Core::add_sound",
226        c: Is("kui_sound_add"),
227        odin: Is("sound_add"),
228        node: Is("addSound"),
229        lua: No(NO_HANDLE),
230        doc: "Registers a sound's bytes and mints an id for `<audio src>`, `clickSound` and `play`.",
231    },
232    Door {
233        rust: "Core::remove_sound",
234        c: Is("kui_sound_remove"),
235        odin: Is("sound_remove"),
236        node: Is("removeSound"),
237        lua: No(NO_HANDLE),
238        doc: "Drops a sound.",
239    },
240    Door {
241        rust: "Core::set_text_cache_budget",
242        c: Is("kui_set_text_cache_budget"),
243        odin: Is("set_text_cache_budget"),
244        node: Is("setTextCacheBudget"),
245        lua: No(GUEST),
246        doc: "The shaped-text cache's byte budget (backlog C16).",
247    },
248    Door {
249        rust: "Core::text_cache_bytes",
250        c: Is("kui_text_cache_bytes"),
251        odin: Is("text_cache_bytes"),
252        node: Is("textCacheBytes"),
253        lua: No(GUEST),
254        doc: "What the shaped-text cache holds.",
255    },
256    // -- Audio -------------------------------------------------------------
257    Door {
258        rust: "Ui::play",
259        c: Is("kui_play"),
260        odin: Is("play"),
261        node: Is("play"),
262        lua: No(
263            "a script owns no sound handle, and its env is the view's: a playback started there would start again every frame — `audio { src = id }` is the declarative form, and what a script has",
264        ),
265        doc: "Starts a playback of a registered sound, outside any node; answers the playback id.",
266    },
267    Door {
268        rust: "Core::stop",
269        c: Is("kui_stop"),
270        odin: Is("stop"),
271        node: Is("stop"),
272        lua: No("as `play`: a script declares `audio { }` and stops it by not declaring it"),
273        doc: "Stops a playback, with an optional fade.",
274    },
275    Door {
276        rust: "Core::set_volume",
277        c: Is("kui_set_volume"),
278        odin: Is("set_volume"),
279        node: Is("setVolume"),
280        lua: As("`audio { volume = }` applies live"),
281        doc: "A playback's volume, with an optional tween.",
282    },
283    Door {
284        rust: "Core::pause",
285        c: Is("kui_pause"),
286        odin: Is("pause"),
287        node: Is("pause"),
288        lua: As("`audio { paused = true }` applies live"),
289        doc: "Pauses a playback.",
290    },
291    Door {
292        rust: "Core::resume",
293        c: Is("kui_resume"),
294        odin: Is("resume"),
295        node: Is("resume"),
296        lua: As("`audio { paused = false }`"),
297        doc: "Resumes a paused playback.",
298    },
299    Door {
300        rust: "Core::set_master_volume",
301        c: Is("kui_set_master_volume"),
302        odin: Is("set_master_volume"),
303        node: Is("setMasterVolume"),
304        lua: No(GUEST),
305        doc: "The device's master volume, with an optional tween.",
306    },
307    // -- Assistive ---------------------------------------------------------
308    Door {
309        rust: "Ui::announce",
310        c: Is("kui_announce"),
311        odin: Is("announce"),
312        node: Is("announce"),
313        lua: Is("announce"),
314        doc: "Says something once with no node behind it (ADR 0001).",
315    },
316    Door {
317        rust: "Core::take_announcements",
318        c: Is("kui_take_announcements"),
319        odin: Is("take_announcements"),
320        node: Is("Ctx.announcements"),
321        lua: No(GUEST),
322        doc: "Drains what was announced, for a host bridging assistive technology; a `KuiWindow`'s bridge is the runner's.",
323    },
324    Door {
325        rust: "Core::access_tree",
326        c: Is("kui_access_tree"),
327        odin: Is("access_tree"),
328        node: Is("accessTree"),
329        lua: No(GUEST),
330        doc: "The access tree of the last finished frame (ADR 0001).",
331    },
332    // -- Focus -------------------------------------------------------------
333    Door {
334        rust: "Ui::focus",
335        c: Is("kui_focus"),
336        odin: Is("focus"),
337        node: Is("focus"),
338        lua: Is("set_focus"),
339        doc: "Moves focus to a node now; an app's move stands over a modal's restore (backlog AR17). `keyFocus` is the declarative, edge-triggered form.",
340    },
341    Door {
342        rust: "Ui::blur",
343        c: As("`kui_focus(ctx, 0)`"),
344        odin: As("`focus(ui, 0)`"),
345        node: Is("blur"),
346        lua: Is("blur"),
347        doc: "Drops focus.",
348    },
349    Door {
350        rust: "Ui::focus_next",
351        c: Is("kui_focus_next"),
352        odin: Is("focus_next"),
353        node: Is("focusNext"),
354        lua: Is("focus_next"),
355        doc: "Steps the Tab ring forward (ADR 0002).",
356    },
357    Door {
358        rust: "Ui::focus_prev",
359        c: As("`kui_focus_next(ctx, false)`"),
360        odin: As("`focus_next(ui, false)`"),
361        node: Is("focusPrev"),
362        lua: Is("focus_prev"),
363        doc: "Steps the Tab ring backward.",
364    },
365    Door {
366        rust: "Ui::focus_region",
367        c: Is("kui_focus_region"),
368        odin: Is("focus_region"),
369        node: Is("focusRegion"),
370        lua: Is("focus_region"),
371        doc: "Enters a `focusRegion`'s ring, or leaves it for the main one (ADR 0022).",
372    },
373    Door {
374        rust: "Ui::region",
375        c: Is("kui_region"),
376        odin: Is("region"),
377        node: Is("region"),
378        lua: As("`env.region`, a reading"),
379        doc: "The region in effect.",
380    },
381    Door {
382        rust: "Ui::focused",
383        c: Is("kui_focused"),
384        odin: Is("focused"),
385        node: Is("focused"),
386        lua: As("`env.focus`, a reading"),
387        doc: "The focused node's key.",
388    },
389    Door {
390        rust: "Ui::is_focused",
391        c: Is("kui_is_focused"),
392        odin: Is("is_focused"),
393        node: Is("isFocused"),
394        lua: Is("is_focused"),
395        doc: "Whether a node has focus.",
396    },
397    Door {
398        rust: "Ui::focus_visible",
399        c: Is("kui_focus_visible"),
400        odin: Is("focus_visible"),
401        node: Is("focusVisible"),
402        lua: As("`env.focus_visible`, a reading"),
403        doc: "Whether focus came from the keyboard and the ring should show.",
404    },
405    Door {
406        rust: "Ui::key_of",
407        c: Is("kui_key_of"),
408        odin: Is("key_of"),
409        node: Is("keyOf"),
410        lua: As("every query and verb takes the label itself (`key_query`)"),
411        doc: "The key a key label names this frame: the name the view opened the node under, not the one a reader hears.",
412    },
413    Door {
414        rust: "Core::key_named",
415        c: Is("kui_key_named"),
416        odin: Is("key_named"),
417        node: Is("keyNamed"),
418        lua: No(GUEST),
419        doc: "The key of the first node a reader hears named so in the last frame: a test presses the button named Like.",
420    },
421    Door {
422        rust: "Core::label_of",
423        c: No(
424            "the label is the app's own word for the node, and every door names a node by it or by the key an event carried; the one reader is the devtools' inspector, in the core",
425        ),
426        odin: No(ODIN_IS_C),
427        node: No("the same reason as C's"),
428        lua: No("the same reason as C's"),
429        doc: "The label a key was opened under.",
430    },
431    Door {
432        rust: "Ui::caret_visible",
433        c: Is("kui_caret_visible"),
434        odin: Is("caret_visible"),
435        node: Is("caretVisible"),
436        lua: As("`env.caret_visible`, a reading"),
437        doc: "The blink phase a custom editor draws its caret on.",
438    },
439    Door {
440        rust: "Core::set_caret_visible",
441        c: Is("kui_set_caret_visible"),
442        odin: Is("set_caret_visible"),
443        node: Is("setCaretVisible"),
444        lua: No(GUEST),
445        doc: "The host's blink clock writes the phase.",
446    },
447    Door {
448        rust: "Core::has_caret",
449        c: Is("kui_has_caret"),
450        odin: Is("has_caret"),
451        node: Is("hasCaret"),
452        lua: No(GUEST),
453        doc: "Whether anything focused draws a caret to blink — a `caretSolid` line's is not one — which arms a host's blink clock.",
454    },
455    Door {
456        rust: "Core::caret_stamp",
457        c: Is("kui_caret_stamp"),
458        odin: Is("caret_stamp"),
459        node: As(
460            "the loop in `index.js` runs the blink from `nextDeadlineMs`; a headless `Ctx` never blinks",
461        ),
462        lua: No(GUEST),
463        doc: "Changes when the caret moves or focus does, which re-arms the clock solid.",
464    },
465    // -- Queries -----------------------------------------------------------
466    Door {
467        rust: "Ui::is_hovered",
468        c: Is("kui_is_hovered"),
469        odin: Is("is_hovered"),
470        node: Is("isHovered"),
471        lua: Is("is_hovered"),
472        doc: "Whether the pointer is over a node.",
473    },
474    Door {
475        rust: "Ui::is_pressed",
476        c: Is("kui_is_pressed"),
477        odin: Is("is_pressed"),
478        node: Is("isPressed"),
479        lua: Is("is_pressed"),
480        doc: "Whether a press started on a node and the pointer is still over it.",
481    },
482    Door {
483        rust: "Ui::is_drop_target",
484        c: Is("kui_is_drop_target"),
485        odin: Is("is_drop_target"),
486        node: Is("isDropTarget"),
487        lua: Is("is_drop_target"),
488        doc: "Whether files dragged in from the OS are over a node (ADR 0031) — for drop-dependent layout; the colour is `drop_bg`.",
489    },
490    Door {
491        rust: "Core::drop_target",
492        c: Is("kui_drop_target"),
493        odin: Is("drop_target"),
494        node: Is("dropTarget"),
495        lua: Is("drop_target"),
496        doc: "The drop zone the dragged files are over, if any — what a driver answers the OS with, and what a test reads to say a zone was found (ADR 0031, decision 5).",
497    },
498    Door {
499        rust: "Ui::is_group_hovered",
500        c: As(
501            "`hoverBg` / `pressedBg` on a `hoverGroup` member paint it; the reader is what the Rust widgets ask when they paint by hand",
502        ),
503        odin: As("`hover_bg` / `pressed_bg` on a `hover_group` member paint it"),
504        node: As("the same form as C's"),
505        lua: As("the same form as C's"),
506        doc: "Whether any member of a hover group is hovered (`is_group_pressed` the same for a press).",
507    },
508    Door {
509        rust: "Core::cursor",
510        c: No("the pointer's position is the driver's own fact — it injected it"),
511        odin: No(ODIN_IS_C),
512        node: No("the same reason as C's"),
513        lua: No("the same reason as C's, one step removed"),
514        doc: "Where the pointer is, in logical viewport px.",
515    },
516    Door {
517        rust: "Core::cursor_shape",
518        c: Is("kui_cursor_shape"),
519        odin: Is("cursor_shape"),
520        node: Is("cursorShape"),
521        lua: No(GUEST),
522        doc: "The pointer shape the frame asks for, which the host sets on its window.",
523    },
524    Door {
525        rust: "Ui::layout_of",
526        c: Is("kui_layout_of"),
527        odin: Is("layout_of"),
528        node: Is("layoutOf"),
529        lua: Is("layout_of"),
530        doc: "Where layout put a node last frame (backlog C26).",
531    },
532    Door {
533        rust: "Ui::scroll_offset",
534        c: Is("kui_scroll_offset"),
535        odin: Is("scroll_offset"),
536        node: Is("scrollOffset"),
537        lua: Is("scroll_offset"),
538        doc: "A scrolling node's offset.",
539    },
540    Door {
541        rust: "Ui::scroll_geometry",
542        c: Is("kui_scroll_geometry"),
543        odin: Is("scroll_geometry"),
544        node: Is("scrollGeometry"),
545        lua: Is("scroll_geometry"),
546        doc: "A scrolling node's viewport and content sizes.",
547    },
548    Door {
549        rust: "Ui::set_scroll",
550        c: Is("kui_set_scroll"),
551        odin: Is("set_scroll"),
552        node: Is("setScroll"),
553        lua: Is("set_scroll"),
554        doc: "Scrolls a node to an offset.",
555    },
556    Door {
557        rust: "Ui::shift_scroll",
558        c: Is("kui_shift_scroll"),
559        odin: Is("shift_scroll"),
560        node: Is("shiftScroll"),
561        lua: Is("shift_scroll"),
562        doc: "Moves a node's scroll by content that moved under it, with no ease: a variable-height list's anchor (backlog C46).",
563    },
564    Door {
565        rust: "Ui::reveal",
566        c: Is("kui_reveal"),
567        odin: Is("reveal"),
568        node: Is("reveal"),
569        lua: Is("reveal"),
570        doc: "Scrolls whatever encloses a node until it is in view.",
571    },
572    Door {
573        rust: "Ui::text_hit",
574        c: Is("kui_text_hit"),
575        odin: Is("text_hit"),
576        node: Is("textHit"),
577        lua: Is("text_hit"),
578        doc: "The byte and line under a point in a node's text (backlog C18).",
579    },
580    Door {
581        rust: "Ui::caret_rect",
582        c: Is("kui_caret_rect"),
583        odin: Is("caret_rect"),
584        node: Is("caretRect"),
585        lua: Is("caret_rect"),
586        doc: "The caret rect for a byte offset in a node's text.",
587    },
588    Door {
589        rust: "Core::ime_rect",
590        c: Is("kui_ime_rect"),
591        odin: Is("ime_rect"),
592        node: Is("imeRect"),
593        lua: No(GUEST),
594        doc: "Where the OS candidate window goes, which the host hands to the platform (backlog C17).",
595    },
596    Door {
597        rust: "Ui::measure_text",
598        c: Is("kui_measure_text"),
599        odin: Is("measure_text"),
600        node: Is("measureText"),
601        lua: Is("measure_text"),
602        doc: "Shapes text in a style at a width and answers its size and line count.",
603    },
604    Door {
605        rust: "Ui::measure_rich_text",
606        c: Is("kui_measure_rich_text"),
607        odin: Is("measure_rich_text"),
608        node: As("`measureText` takes spans too"),
609        lua: As("`measure_text` takes spans too"),
610        doc: "The same for spans, shaped as one paragraph.",
611    },
612    Door {
613        rust: "Ui::edit_text",
614        c: Is("kui_edit_text"),
615        odin: Is("edit_text"),
616        node: Is("editText"),
617        lua: Is("edit_text"),
618        doc: "An editor's text, by key or by label.",
619    },
620    Door {
621        rust: "Ui::set_edit_text",
622        c: Is("kui_edit_set_text"),
623        odin: Is("edit_set_text"),
624        node: Is("setEditText"),
625        lua: Is("set_edit_text"),
626        doc: "Replaces an editor's text, caret at the end.",
627    },
628    Door {
629        rust: "Ui::set_edit_text_by_label",
630        c: Is("kui_edit_set_text_label"),
631        odin: Is("edit_set_text_label"),
632        node: As("`setEditText` takes the label too"),
633        lua: As("`set_edit_text` takes the label too"),
634        doc: "The same by the label an editor's `key` declares, which reaches one the frame is about to declare (backlog AR26).",
635    },
636    Door {
637        rust: "Core::animating",
638        c: Is("kui_animating"),
639        odin: Is("animating"),
640        node: Is("animating"),
641        lua: No(GUEST),
642        doc: "Whether the last frame left a transition mid-flight, so the host draws another without waiting for input.",
643    },
644    Door {
645        rust: "Core::owed",
646        c: Is("kui_owed"),
647        odin: Is("owed"),
648        node: Is("owed"),
649        lua: No(GUEST),
650        doc: "The same by kind — a finite transition, a keyframe cycle, a departing ghost, a requested frame, an autoscroll — so a test can wait for the transitions to run out under a cycle that never ends; Node's loop has `quiet()` for that wait (backlog F64).",
651    },
652    Door {
653        rust: "Core::set_frame_trace",
654        c: Is("kui_set_frame_trace"),
655        odin: Is("set_frame_trace"),
656        node: Is("setFrameTrace"),
657        lua: No(GUEST),
658        doc: "Turns on the trace of why frames run: who holds an owed frame, and whether a frame changed what is drawn (backlog F111).",
659    },
660    Door {
661        rust: "Core::frame_cause",
662        c: Is("kui_frame_cause"),
663        odin: Is("frame_cause"),
664        node: Is("frameCause"),
665        lua: No(GUEST),
666        doc: "Why the frame being built runs: the input it answers by kind, what the driver noted, and `owed` after a frame that owed one (backlog F111).",
667    },
668    Door {
669        rust: "Core::begin_frame_cause",
670        c: No(
671            "a C host's view runs between `kui_frame_begin` and `kui_frame_finish`, inside the frame it builds, so it reads that frame already",
672        ),
673        odin: No(ODIN_IS_C),
674        node: Is("Ctx.beginFrameCause"),
675        lua: No(GUEST),
676        doc: "Starts the next frame's record ahead of its `begin_frame`, for a driver whose view runs before the frame it is for — Node's loop — so `frame_cause` and `owed_by` read from the view answer that frame (backlog RG81).",
677    },
678    Door {
679        rust: "Core::note_frame_cause",
680        c: Is("kui_note_frame_cause"),
681        odin: Is("note_frame_cause"),
682        node: No(
683            "the drivers that note a reason are kui-native's, which a `KuiWindow` runs on; a `Ctx` driven by hand has nothing but the input the core already records",
684        ),
685        lua: No(GUEST),
686        doc: "A driver adds a reason the core cannot see — a wake, a resize, a blink, a retry — to the next frame's (backlog F111).",
687    },
688    Door {
689        rust: "Core::owed_by",
690        c: No(
691            "lists of named holders are strings the library would own across calls, an [out-array] struct and an ABI bump for a reading that is a debugging aid; a C host reads the kinds from `kui_owed`",
692        ),
693        odin: No(ODIN_IS_C),
694        node: Is("owedBy"),
695        lua: No(GUEST),
696        doc: "Who holds the frame the last one left owed: `owed` with the nodes, slots and calling lines named (backlog F111).",
697    },
698    Door {
699        rust: "Core::frame_unchanged",
700        c: Is("kui_frame_unchanged"),
701        odin: Is("frame_unchanged"),
702        node: Is("frameUnchanged"),
703        lua: No(GUEST),
704        doc: "Whether the last finished frame drew exactly what the one before drew, traced (backlog F111).",
705    },
706    Door {
707        rust: "Ui::request_frame",
708        c: As("`animate` on a node, and `kui_animating` for the driver to read"),
709        odin: As("`animate` on a node, and `animating` for the driver to read"),
710        node: As("the same form as C's"),
711        lua: As("the same form as C's"),
712        doc: "Asks for a frame after this one; the driver paces off `animating()`.",
713    },
714    Door {
715        rust: "Ui::modifiers",
716        c: No(
717            "the held modifiers ride on every key and pointer event's `mods`; the reader is what the stock editor's Shift-drag asks, inside the core",
718        ),
719        odin: No(ODIN_IS_C),
720        node: No("the same reason as C's"),
721        lua: No("the same reason as C's"),
722        doc: "The modifier keys held now.",
723    },
724    // -- Selection ---------------------------------------------------------
725    Door {
726        rust: "Ui::selection_text",
727        c: Is("kui_selection_text"),
728        odin: Is("selection_text"),
729        node: Is("selectionText"),
730        lua: Is("selection_text"),
731        doc: "The window's selected text — a scope's, a grid's or the focused editor's.",
732    },
733    Door {
734        rust: "Ui::selection_html",
735        c: Is("kui_selection_html"),
736        odin: Is("selection_html"),
737        node: Is("selectionHtml"),
738        lua: Is("selection_html"),
739        doc: "The same with the formatting the text declared.",
740    },
741    Door {
742        rust: "Ui::selection_ends",
743        c: Is("kui_selection_ends"),
744        odin: Is("selection_ends"),
745        node: Is("selectionEnds"),
746        lua: Is("selection_ends"),
747        doc: "A text selection's anchor and focus as row indices and bytes (ADR 0029).",
748    },
749    Door {
750        rust: "Ui::cell_selection",
751        c: Is("kui_cell_selection"),
752        odin: Is("cell_selection"),
753        node: Is("cellSelection"),
754        lua: Is("cell_selection"),
755        doc: "A `cells` grid's selection: its ends as absolute lines and columns, and whether it is a block (ADR 0017 §4).",
756    },
757    Door {
758        rust: "Ui::select_all_in",
759        c: Is("kui_select_all_in"),
760        odin: Is("select_all_in"),
761        node: Is("selectAllIn"),
762        lua: Is("select_all_in"),
763        doc: "Select All, scoped to a `selectable` node or a grid.",
764    },
765    Door {
766        rust: "Ui::clear_selection",
767        c: Is("kui_clear_selection"),
768        odin: Is("clear_selection"),
769        node: Is("clearSelection"),
770        lua: Is("clear_selection"),
771        doc: "Drops the window's selection.",
772    },
773    Door {
774        rust: "Ui::request_copy",
775        c: Is("kui_request_copy"),
776        odin: Is("request_copy"),
777        node: Is("requestCopy"),
778        lua: Is("request_copy"),
779        doc: "Asks for the selection as a copy, which may come back as a `selectionrange` question.",
780    },
781    Door {
782        rust: "Ui::answer_selection_range",
783        c: Is("kui_answer_selection_range"),
784        odin: Is("answer_selection_range"),
785        node: Is("answerSelectionRange"),
786        lua: Is("answer_selection_range"),
787        doc: "The app's answer to that question.",
788    },
789    Door {
790        rust: "Ui::set_clipboard",
791        c: Is("kui_set_clipboard"),
792        odin: Is("set_clipboard"),
793        node: Is("setClipboard"),
794        lua: Is("set_clipboard"),
795        doc: "A key sink's own Ctrl-C: posts a clipboard action for the host (backlog C33).",
796    },
797    Door {
798        rust: "Ui::set_clipboard_secret",
799        c: Is("kui_set_clipboard_secret"),
800        odin: Is("set_clipboard_secret"),
801        node: Is("setClipboardSecret"),
802        lua: Is("set_clipboard_secret"),
803        doc: "Posts a secret for the clipboard, which the host writes marked concealed and transient the way a password manager does, so no clipboard manager shows or keeps it (backlog F84).",
804    },
805    Door {
806        rust: "Ui::request_paste",
807        c: Is("kui_request_paste"),
808        odin: Is("request_paste"),
809        node: Is("requestPaste"),
810        lua: Is("request_paste"),
811        doc: "A key sink's own Ctrl-V: the clipboard comes back as a commit, marked `concealed` / `transient` when the pasteboard said so (backlog F84). One ask at a time — a second while one is unanswered is dropped.",
812    },
813    Door {
814        rust: "Ui::awaiting_paste",
815        c: Is("kui_awaiting_paste"),
816        odin: Is("awaiting_paste"),
817        node: Is("awaitingPaste"),
818        lua: Is("awaiting_paste"),
819        doc: "Whether a paste asked for is still unanswered (backlog AR34).",
820    },
821    Door {
822        rust: "Ui::request_files",
823        c: Is("kui_request_files"),
824        odin: Is("request_files"),
825        node: Is("requestFiles"),
826        lua: Is("request_files"),
827        doc: "Asks for the platform's Open, Save or folder dialog; the answer is a `files` event to whoever asked. One at a time — a second while one is out is dropped (backlog C51).",
828    },
829    Door {
830        rust: "Ui::awaiting_files",
831        c: Is("kui_awaiting_files"),
832        odin: Is("awaiting_files"),
833        node: Is("awaitingFiles"),
834        lua: Is("awaiting_files"),
835        doc: "Whether a file dialog asked for is still unanswered.",
836    },
837    Door {
838        rust: "Core::take_file_requests",
839        c: As("`kui_take_file_request`, then `kui_file_request_filter` per filter"),
840        odin: As("`take_file_request`, then `file_request_filter` per filter"),
841        node: Is("takeFileRequests"),
842        lua: No(GUEST),
843        doc: "Drains the dialog asked for, for a host that shows it itself; the runner does. The answer goes back as input (`Ctx.answerFiles`, `kui_input_files`).",
844    },
845    Door {
846        rust: "Core::set_lookup_available",
847        c: Is("kui_set_lookup_available"),
848        odin: Is("set_lookup_available"),
849        node: Is("setLookupAvailable"),
850        lua: No(GUEST),
851        doc: "Whether the host can show the platform's definition panel, which decides whether Look Up is offered.",
852    },
853    // -- Menus -------------------------------------------------------------
854    Door {
855        rust: "Ui::open_menu",
856        c: Is("kui_open_menu"),
857        odin: Is("open_menu"),
858        node: Is("openMenu"),
859        lua: Is("open_menu"),
860        doc: "Opens a context menu on a node at a point.",
861    },
862    Door {
863        rust: "Ui::close_menu",
864        c: Is("kui_close_menu"),
865        odin: Is("close_menu"),
866        node: Is("closeMenu"),
867        lua: Is("close_menu"),
868        doc: "Closes it.",
869    },
870    Door {
871        rust: "Core::take_menu_actions",
872        c: Is("kui_take_menu_action"),
873        odin: Is("take_menu_action"),
874        node: Is("takeMenuActions"),
875        lua: As(
876            "a chosen row comes back as a `menu` event on the node; the clipboard actions are the host's",
877        ),
878        doc: "Drains what a menu (or a chord, or the standard bar) asked of the host: a clipboard write, a paste, a Look Up.",
879    },
880    Door {
881        rust: "Core::menu",
882        c: As(
883            "`kui_menu_item_count` / `kui_menu_item`, one row at a time, and a submenu's by path with `kui_menu_submenu_count` / `kui_menu_item_path`",
884        ),
885        odin: As(
886            "`menu_item_count` / `menu_item`, one row at a time, and a submenu's by path with `menu_submenu_count` / `menu_item_path`",
887        ),
888        node: Is("menu"),
889        lua: No(GUEST),
890        doc: "The open menu, for a host showing it natively.",
891    },
892    Door {
893        rust: "Core::set_native_menus",
894        c: Is("kui_set_native_menus"),
895        odin: Is("set_native_menus"),
896        node: Is("setNativeMenus"),
897        lua: No(GUEST),
898        doc: "Whether the host shows menus itself; the core then draws none.",
899    },
900    Door {
901        rust: "Core::activate_menu_item",
902        c: Is("kui_activate_menu_item"),
903        odin: Is("activate_menu_item"),
904        node: Is("activateMenuItem"),
905        lua: No(GUEST),
906        doc: "Reports that the host's own menu chose a row; a row that cannot be chosen (disabled, a separator) is refused and the menu stays open.",
907    },
908    Door {
909        rust: "Core::activate_menu_path",
910        c: Is("kui_activate_menu_path"),
911        odin: Is("activate_menu_path"),
912        node: Is("activateMenuPath"),
913        lua: No(GUEST),
914        doc: "`activate_menu_item` for a row inside a submenu, by its path through the rows' submenus (backlog F128); a row that opens a submenu is refused.",
915    },
916    Door {
917        rust: "Core::menu_bar",
918        c: As(
919            "`kui_menu_bar_menu_count` / `kui_menu_bar_menu` / `kui_menu_bar_item`, one row at a time, and a submenu's by path with `kui_menu_bar_submenu_count` / `kui_menu_bar_item_path`",
920        ),
921        odin: As(
922            "`menu_bar_menu_count` / `menu_bar_menu` / `menu_bar_item`, one row at a time, and a submenu's by path with `menu_bar_submenu_count` / `menu_bar_item_path`",
923        ),
924        node: Is("menuBar"),
925        lua: No(GUEST),
926        doc: "The declared menu bar, for a host handing it to the OS.",
927    },
928    Door {
929        rust: "Core::set_native_menu_bar",
930        c: Is("kui_set_native_menu_bar"),
931        odin: Is("set_native_menu_bar"),
932        node: Is("setNativeMenuBar"),
933        lua: No(GUEST),
934        doc: "Whether the host owns the bar; the core then draws no strip.",
935    },
936    Door {
937        rust: "Core::activate_menu_bar_item",
938        c: Is("kui_activate_menu_bar_item"),
939        odin: Is("activate_menu_bar_item"),
940        node: Is("activateMenuBarItem"),
941        lua: No(GUEST),
942        doc: "Reports that the OS bar chose a row.",
943    },
944    Door {
945        rust: "Core::activate_menu_bar_path",
946        c: Is("kui_activate_menu_bar_path"),
947        odin: Is("activate_menu_bar_path"),
948        node: Is("activateMenuBarPath"),
949        lua: No(GUEST),
950        doc: "`activate_menu_bar_item` for a row inside a submenu, by its path (backlog F128).",
951    },
952    // -- Windows -----------------------------------------------------------
953    Door {
954        rust: "Ui::window",
955        c: Is("kui_window_declare"),
956        odin: Is("window_declare"),
957        node: As("the root's `windows` prop"),
958        lua: As("the root's `windows` field"),
959        doc: "Declares that a named window exists this frame (ADR 0003 step 3).",
960    },
961    Door {
962        rust: "Core::windows",
963        c: As("the ids arrive on `KUI_CMD_OPEN`; a host keeps the list it opened"),
964        odin: As(
965            "the ids arrive on `take_window_command`'s `.Open`; a host keeps the list it opened",
966        ),
967        node: Is("windows"),
968        lua: No(GUEST),
969        doc: "The names of the windows open now.",
970    },
971    Door {
972        rust: "Ui::window_name",
973        c: Is("kui_ctx_window_name"),
974        odin: Is("ctx_window_name"),
975        node: Is("windowName"),
976        lua: As("`env.window.name`, a reading"),
977        doc: "The name of the window this context draws.",
978    },
979    Door {
980        rust: "Ui::set_window_size",
981        c: Is("kui_set_window_size"),
982        odin: Is("set_window_size"),
983        node: Is("setWindowSize"),
984        lua: Is("set_window_size"),
985        doc: "Asks the driver to resize a window.",
986    },
987    Door {
988        rust: "Ui::focus_window",
989        c: Is("kui_focus_window"),
990        odin: Is("focus_window"),
991        node: Is("focusWindow"),
992        lua: Is("focus_window"),
993        doc: "Asks the driver to bring a window to the front.",
994    },
995    Door {
996        rust: "Ui::window_title",
997        c: Is("kui_window_title"),
998        odin: Is("window_title"),
999        node: As("the root's `title` prop"),
1000        lua: As("the root's `title` field"),
1001        doc: "Declares the window's title this frame.",
1002    },
1003    Door {
1004        rust: "Ui::always_on_top",
1005        c: Is("kui_set_always_on_top"),
1006        odin: Is("set_always_on_top"),
1007        node: As("the root's `alwaysOnTop` prop"),
1008        lua: As("the root's `always_on_top` field"),
1009        doc: "Declares that the window sits above every other app's this frame (backlog C30).",
1010    },
1011    Door {
1012        rust: "Ui::secure_input",
1013        c: Is("kui_set_secure_input"),
1014        odin: Is("set_secure_input"),
1015        node: As("the root's `secureInput` prop"),
1016        lua: As("the root's `secure_input` field"),
1017        doc: "Declares that this frame wants secure keyboard entry while the window has the keyboard — a password prompt (backlog F85).",
1018    },
1019    Door {
1020        rust: "Ui::option_as_alt",
1021        c: Is("kui_set_option_as_alt"),
1022        odin: Is("set_option_as_alt"),
1023        node: As("the root's `optionAsAlt` prop"),
1024        lua: As("the root's `option_as_alt` field"),
1025        doc: "Declares which Option keys act as Alt in this window on macOS, so a dead key like ⌥u arrives as `<A-u>` (backlog F113).",
1026    },
1027    Door {
1028        rust: "Ui::ime_off",
1029        c: Is("kui_set_ime_off"),
1030        odin: Is("set_ime_off"),
1031        node: As("the root's `imeOff` prop"),
1032        lua: As("the root's `ime_off` field"),
1033        doc: "Declares that this window takes the keyboard as keys, with the input method off — no composition, and on a Mac no dead keys and no press-and-hold, so a held letter repeats (backlog F125).",
1034    },
1035    Door {
1036        rust: "Ui::window_command",
1037        c: As(
1038            "the chrome roles (`KuiSpec.window_role`) are the door; the verb is what `widgets::window_buttons` lowers to",
1039        ),
1040        odin: As("`window` on a node (`Spec.window`, the chrome roles) is the door"),
1041        node: As(
1042            "`KuiWindow.close()` for the one command the runner takes from outside a frame; the rest are `windowRole`",
1043        ),
1044        lua: As("`window_role`"),
1045        doc: "Minimize, toggle-maximize, start-drag, close — what a chrome node asks for on a press.",
1046    },
1047    Door {
1048        rust: "Core::window_title",
1049        c: Is("kui_window_title_get"),
1050        odin: Is("window_title_get"),
1051        node: Is("Ctx.windowTitle"),
1052        lua: No(GUEST),
1053        doc: "What the frame declared, for a driver applying it; a `KuiWindow` applies its own.",
1054    },
1055    Door {
1056        rust: "Core::always_on_top",
1057        c: Is("kui_always_on_top_get"),
1058        odin: Is("always_on_top_get"),
1059        node: Is("Ctx.alwaysOnTop"),
1060        lua: As("`env.window.always_on_top`, a reading"),
1061        doc: "The same for the level.",
1062    },
1063    Door {
1064        rust: "Core::secure_input",
1065        c: Is("kui_secure_input_get"),
1066        odin: Is("secure_input_get"),
1067        node: Is("Ctx.secureInput"),
1068        lua: No(GUEST),
1069        doc: "The same for the secure-input ask: what a driver with its own loop reads to make the platform call; the runner makes it for a `KuiWindow` and `kui_run`.",
1070    },
1071    Door {
1072        rust: "Core::option_as_alt",
1073        c: Is("kui_option_as_alt_get"),
1074        odin: Is("option_as_alt_get"),
1075        node: Is("Ctx.optionAsAlt"),
1076        lua: No(GUEST),
1077        doc: "The same for the Option-as-Alt ask: what a driver with its own loop reads to apply it to its window; the runner applies it for a `KuiWindow` and `kui_run`.",
1078    },
1079    Door {
1080        rust: "Core::ime_off",
1081        c: Is("kui_ime_off_get"),
1082        odin: Is("ime_off_get"),
1083        node: Is("Ctx.imeOff"),
1084        lua: No(GUEST),
1085        doc: "The same for the input-method ask: what a driver with its own loop reads to apply it to its window; the runner applies it for a `KuiWindow` and `kui_run`.",
1086    },
1087    Door {
1088        rust: "Core::take_window_commands",
1089        c: Is("kui_take_window_command"),
1090        odin: Is("take_window_command"),
1091        node: Is("Ctx.windowCommands"),
1092        lua: No(GUEST),
1093        doc: "Drains what the frame asked of the driver: open, close, resize, focus, redraw.",
1094    },
1095    Door {
1096        rust: "Core::window_closed",
1097        c: Is("kui_window_closed"),
1098        odin: Is("window_closed"),
1099        node: Is("Ctx.windowClosed"),
1100        lua: No(GUEST),
1101        doc: "The driver reports a window gone.",
1102    },
1103    Door {
1104        rust: "Core::dismiss_window",
1105        c: Is("kui_window_dismissed"),
1106        odin: Is("window_dismissed"),
1107        node: Is("Ctx.windowDismissed"),
1108        lua: No(GUEST),
1109        doc: "The driver reports a popup dismissed, with why (ADR 0003 step 4).",
1110    },
1111    // -- Theme, metrics, tokens --------------------------------------------
1112    Door {
1113        rust: "Core::set_theme",
1114        c: Is("kui_theme_set"),
1115        odin: Is("theme_set"),
1116        node: Is("setTheme"),
1117        lua: No("read-only: the palette is the host's (ADR 0019)"),
1118        doc: "Pins a whole palette.",
1119    },
1120    Door {
1121        rust: "Core::set_accent",
1122        c: Is("kui_theme_set_accent"),
1123        odin: Is("theme_set_accent"),
1124        node: Is("setAccent"),
1125        lua: No("as `set_theme`"),
1126        doc: "Pins an accent and keeps the OS's base.",
1127    },
1128    Door {
1129        rust: "Ui::theme",
1130        c: Is("kui_theme"),
1131        odin: Is("theme"),
1132        node: Is("theme"),
1133        lua: As("`env.theme`, a reading"),
1134        doc: "The palette in effect (`THEME_ROLES`).",
1135    },
1136    Door {
1137        rust: "Core::set_metrics",
1138        c: Is("kui_metrics_set"),
1139        odin: Is("metrics_set"),
1140        node: Is("setMetrics"),
1141        lua: No("as `set_theme` (backlog T2)"),
1142        doc: "Pins the stock widgets' sizes.",
1143    },
1144    Door {
1145        rust: "Ui::metrics",
1146        c: Is("kui_metrics"),
1147        odin: Is("metrics"),
1148        node: Is("metrics"),
1149        lua: As("`env.metrics`, a reading"),
1150        doc: "The sizes in effect (`METRIC_ROLES`).",
1151    },
1152    Door {
1153        rust: "Ui::set_tokens",
1154        c: Is("kui_tokens_set"),
1155        odin: Is("tokens_set"),
1156        node: Is("setTokens"),
1157        lua: Is("set_tokens"),
1158        doc: "Declares the origin's colour and length tokens (ADR 0027).",
1159    },
1160    Door {
1161        rust: "Tokens::derive",
1162        c: Is("kui_tokens_derive"),
1163        odin: Is("tokens_derive"),
1164        node: As("a colour with `from` in `setTokens`"),
1165        lua: As("a colour with `from` in `set_tokens`"),
1166        doc: "Adds derived colours to the declared ones (ADR 0028).",
1167    },
1168    Door {
1169        rust: "Ui::tokens",
1170        c: As("`kui_token_color` / `kui_token_length`, one name at a time"),
1171        odin: As("`token_color` / `token_length`, one name at a time"),
1172        node: Is("tokens"),
1173        lua: As("`env.tokens`, a reading"),
1174        doc: "The tokens in effect, resolved for the appearance.",
1175    },
1176    Door {
1177        rust: "Core::tokens_declared",
1178        c: No(
1179            "a plugin declares in every `kui_ext_view` and pays the parse; a reader that lets it skip the second is one line, once a plugin asks for it",
1180        ),
1181        odin: No(ODIN_IS_C),
1182        node: No("an app declares once, before its loop"),
1183        lua: No("the `tokens` global is declared once, at load"),
1184        doc: "Whether an origin declared tokens.",
1185    },
1186    // -- Diagnostics and devtools ------------------------------------------
1187    Door {
1188        rust: "Core::set_diagnostics",
1189        c: Is("kui_set_diagnostics"),
1190        odin: Is("set_diagnostics"),
1191        node: Is("setDiagnostics"),
1192        lua: No(GUEST),
1193        doc: "Turns the per-frame checks on.",
1194    },
1195    Door {
1196        rust: "Core::take_warnings",
1197        c: Is("kui_take_warnings"),
1198        odin: Is("take_warnings"),
1199        node: Is("warnings"),
1200        lua: No("the host drains and the Lua runner prints"),
1201        doc: "Drains the warnings raised since the last call.",
1202    },
1203    Door {
1204        rust: "Core::warnings_raised",
1205        c: No(
1206            "the C smoke round drains `kui_take_warnings` after each frame; a non-draining reader waits for a C harness that needs one",
1207        ),
1208        odin: No(ODIN_IS_C),
1209        node: Is("warningsRaised"),
1210        lua: No(GUEST),
1211        doc: "The warnings raised so far, undrained, which is what an example's self-check reads (ADR 0021).",
1212    },
1213    Door {
1214        rust: "Core::set_devtools",
1215        c: Is("kui_set_devtools"),
1216        odin: Is("set_devtools"),
1217        node: Is("setDevtools"),
1218        lua: No(GUEST),
1219        doc: "Turns the devtools panel on.",
1220    },
1221    Door {
1222        rust: "Core::devtools",
1223        c: Is("kui_devtools"),
1224        odin: Is("devtools"),
1225        node: Is("devtools"),
1226        lua: No(GUEST),
1227        doc: "Whether it is on.",
1228    },
1229    Door {
1230        rust: "Core::set_devtools_dock",
1231        c: Is("kui_set_devtools_dock"),
1232        odin: Is("set_devtools_dock"),
1233        node: Is("setDevtoolsDock"),
1234        lua: No(GUEST),
1235        doc: "Where it sits.",
1236    },
1237    Door {
1238        rust: "Core::devtools_dock",
1239        c: Is("kui_devtools_dock"),
1240        odin: Is("devtools_dock"),
1241        node: Is("devtoolsDock"),
1242        lua: No(GUEST),
1243        doc: "Where it sits, read back.",
1244    },
1245    Door {
1246        rust: "Core::host_rect",
1247        c: Is("kui_host_rect"),
1248        odin: Is("host_rect"),
1249        node: Is("hostArea"),
1250        lua: No(GUEST),
1251        doc: "Where the frame laid the host out in the window, logical px: the viewport with its origin, which is what tells the app's quads from the dock's (backlog F92).",
1252    },
1253    Door {
1254        rust: "Core::set_devtools_theme",
1255        c: Is("kui_set_devtools_theme"),
1256        odin: Is("set_devtools_theme"),
1257        node: Is("setDevtoolsTheme"),
1258        lua: No(GUEST),
1259        doc: "Seeds the panel's theme override.",
1260    },
1261    Door {
1262        rust: "Core::set_devtools_key",
1263        c: Is("kui_set_devtools_key"),
1264        odin: Is("set_devtools_key"),
1265        node: Is("setDevtoolsKey"),
1266        lua: No(GUEST),
1267        doc: "Respells the chord that moves the keyboard into the panel (`Ctrl+Shift+I` by default).",
1268    },
1269    Door {
1270        rust: "Core::devtools_key",
1271        c: Is("kui_devtools_key"),
1272        odin: Is("devtools_key"),
1273        node: Is("devtoolsKey"),
1274        lua: No(GUEST),
1275        doc: "That chord, read back in its portable spelling.",
1276    },
1277    Door {
1278        rust: "Ui::devtools_tab",
1279        c: Is("kui_devtools_tab"),
1280        odin: Is("devtools_tab"),
1281        node: As("`<devtoolsTab name label slot/>`"),
1282        lua: As("`devtools_tab { name=, label=, slot= }`"),
1283        doc: "Declares a devtools tab an extension fills through the slot named (ADR 0032).",
1284    },
1285    Door {
1286        rust: "Ui::devtools_tab_with",
1287        c: Is("kui_devtools_tab_open"),
1288        odin: Is("devtools_tab_open"),
1289        node: As(
1290            "`<devtoolsTab name label>{() => …}</devtoolsTab>`, the function child called only while the tab is on show",
1291        ),
1292        lua: As(
1293            "`devtools_tab { name=, label=, view = function(env) … end }`, called only while the tab is on show",
1294        ),
1295        doc: "Declares a devtools tab the host draws itself, and draws it only while it is on show.",
1296    },
1297    Door {
1298        rust: "Core::devtools_shown_tab",
1299        c: No(
1300            "a C host's open answers whether the tab is on show (`kui_devtools_tab_open`); nothing encodes ahead of the core there",
1301        ),
1302        odin: No(ODIN_IS_C),
1303        node: Is("devtoolsShownTab"),
1304        lua: No("the runner's converter reads it for the script (ADR 0032, decision 3)"),
1305        doc: "The declared devtools tab on show, which a data binding reads once a frame to call the tab's function.",
1306    },
1307    Door {
1308        rust: "Core::devtools_selected",
1309        c: Is("kui_devtools_selected"),
1310        odin: Is("devtools_selected"),
1311        node: Is("devtoolsSelected"),
1312        lua: No(GUEST),
1313        doc: "The node the panel's tree tab has selected (ADR 0032, decision 4).",
1314    },
1315    Door {
1316        rust: "Core::devtools_hovered",
1317        c: Is("kui_devtools_hovered"),
1318        odin: Is("devtools_hovered"),
1319        node: Is("devtoolsHovered"),
1320        lua: No(GUEST),
1321        doc: "The tree row under the pointer.",
1322    },
1323    Door {
1324        rust: "Core::devtools_picked",
1325        c: Is("kui_devtools_picked"),
1326        odin: Is("devtools_picked"),
1327        node: Is("devtoolsPicked"),
1328        lua: No(GUEST),
1329        doc: "The node the picker is over.",
1330    },
1331    Door {
1332        rust: "Core::set_devtools_pick",
1333        c: Is("kui_set_devtools_pick"),
1334        odin: Is("set_devtools_pick"),
1335        node: Is("setDevtoolsPick"),
1336        lua: No(GUEST),
1337        doc: "Raises the panel's picker from outside it, or puts it away; raised from a declared tab, the pick lands in `devtools_selected` and the tab stays up.",
1338    },
1339    Door {
1340        rust: "Core::devtools_picking",
1341        c: Is("kui_devtools_picking"),
1342        odin: Is("devtools_picking"),
1343        node: Is("devtoolsPicking"),
1344        lua: No(GUEST),
1345        doc: "Whether the picker is up.",
1346    },
1347    Door {
1348        rust: "Core::set_devtools_selected",
1349        c: Is("kui_set_devtools_selected"),
1350        odin: Is("set_devtools_selected"),
1351        node: Is("setDevtoolsSelected"),
1352        lua: No(GUEST),
1353        doc: "Selects and reveals a node in the tree tab from outside the panel.",
1354    },
1355    Door {
1356        rust: "Core::set_devtools_tab",
1357        c: Is("kui_set_devtools_tab"),
1358        odin: Is("set_devtools_tab"),
1359        node: Is("setDevtoolsTab"),
1360        lua: No(GUEST),
1361        doc: "Shows the panel's tab named — one of its own, in any case, or a declared one, as declared — from outside the panel, as the strip's click does; a hidden panel comes back docked.",
1362    },
1363    Door {
1364        rust: "Core::devtools_current_tab",
1365        c: Is("kui_devtools_current_tab"),
1366        odin: Is("devtools_current_tab"),
1367        node: Is("devtoolsCurrentTab"),
1368        lua: No(GUEST),
1369        doc: "The tab the panel is on, by name.",
1370    },
1371    Door {
1372        rust: "Core::set_devtools_legend",
1373        c: Is("kui_set_devtools_legend"),
1374        odin: Is("set_devtools_legend"),
1375        node: Is("setDevtoolsLegend"),
1376        lua: No(GUEST),
1377        doc: "The key legend the panel's facts tab shows.",
1378    },
1379    Door {
1380        rust: "Core::set_inspect",
1381        c: Is("kui_set_inspect"),
1382        odin: Is("set_inspect"),
1383        node: Is("setInspect"),
1384        lua: No(GUEST),
1385        doc: "Turns the per-frame node snapshot behind `nodes` on.",
1386    },
1387    Door {
1388        rust: "Core::nodes",
1389        c: Is("kui_nodes"),
1390        odin: Is("nodes"),
1391        node: Is("nodes"),
1392        lua: No(GUEST),
1393        doc: "The last frame's nodes with what layout and the declarations made of them — a tree view's and an inspector's data.",
1394    },
1395    // -- Extensions --------------------------------------------------------
1396    Door {
1397        rust: "Ui::add_extension",
1398        c: Is("kui_ctx_add_extension"),
1399        odin: Is("ctx_add_extension"),
1400        node: Is("Ctx.addExtension"),
1401        lua: Is("add_extension"),
1402        doc: "Loads a plugin under a namespace; a `KuiWindow` takes its list at construction (`extensions`).",
1403    },
1404    Door {
1405        rust: "Ui::slot_kept",
1406        c: Is("kui_slot_kept"),
1407        odin: Is("slot_kept"),
1408        node: As("`<slot name params keep/>`"),
1409        lua: As("`fill { name=, params=, keep = true }`"),
1410        doc: "Declares a slot as `slot` does and keeps what the fill built — every node as its door saw it, the slots inside, every fact of the frame it read — for `slot_replay` to push again (ADR 0045).",
1411    },
1412    Door {
1413        rust: "Ui::slot_replay",
1414        c: Is("kui_slot_replay"),
1415        odin: Is("slot_replay"),
1416        node: As("`<slot name params replay/>`"),
1417        lua: As("`fill { name=, params=, replay = true }`"),
1418        doc: "The host's claim that nothing it feeds the extension changed: the core checks the params, every fact the kept fill read and the slot's place, and pushes the kept nodes again without the extension, or fills and keeps and says why (ADR 0045).",
1419    },
1420    Door {
1421        rust: "Core::slot_fill",
1422        c: Is("kui_slot_fill"),
1423        odin: Is("slot_fill"),
1424        node: Is("slotFill"),
1425        lua: Is("slot_fill"),
1426        doc: "What the last `slot_replay` of a name answered this frame, or the frame before while this one is being built: replayed, or why it was filled fresh (ADR 0045).",
1427    },
1428    Door {
1429        rust: "Launcher::extensions",
1430        c: As("`kui_ctx_extension_count` / `kui_ctx_extension_namespace`, one at a time"),
1431        odin: As("`ctx_extension_count` / `ctx_extension_namespace`, one at a time"),
1432        node: Is("Ctx.extensionNamespaces"),
1433        lua: Is("extension_namespaces"),
1434        doc: "The namespaces loaded.",
1435    },
1436    // -- The driver's half: what a host does to run a core ----------------
1437    // Lua has none of these, for the one reason `GUEST` states; Node's are
1438    // on `Ctx` alone because a `KuiWindow`'s driver is the runner.
1439    Door {
1440        rust: "Core::frame",
1441        c: As("`kui_frame_begin` … `kui_frame_finish`"),
1442        odin: As("`frame_begin` … `frame_finish`, or `kui.frame` around a view"),
1443        node: Is("Ctx.frame"),
1444        lua: No(GUEST),
1445        doc: "Runs one frame: the view, layout, the draw list; `KuiWindow.setView` is the windowed form, the runner calling it.",
1446    },
1447    Door {
1448        rust: "Core::output",
1449        c: Is("kui_draw_data"),
1450        odin: Is("draw_data"),
1451        node: Is("quads"),
1452        lua: No(GUEST),
1453        doc: "The draw list: quads, clips, fragment and texture draws (`clips`, `fragmentDraws`, `textureDraws` beside `quads` in Node) and the frame's stats.",
1454    },
1455    Door {
1456        rust: "Core::take_pending_events",
1457        c: Is("kui_poll_event"),
1458        odin: Is("poll_event"),
1459        node: Is("pollEvents"),
1460        lua: As("`on_event(ev)`, pushed after each frame"),
1461        doc: "What the frame and the input since produced, for `update`.",
1462    },
1463    Door {
1464        rust: "Core::set_time",
1465        c: Is("kui_set_time"),
1466        odin: Is("set_time"),
1467        node: Is("Ctx.setTime"),
1468        lua: No(GUEST),
1469        doc: "The clock the tweens read; a window's runner sets it from the display.",
1470    },
1471    Door {
1472        rust: "Ui::exit_with",
1473        c: Is("kui_exit_with"),
1474        odin: Is("exit_with"),
1475        node: Is("exitWith"),
1476        lua: Is("exit_with"),
1477        doc: "Names the exit a node leaves by if this frame stops declaring it, over the one it declared: a throw a button aims.",
1478    },
1479    Door {
1480        rust: "Ui::request_frame_at",
1481        c: Is("kui_request_frame_at"),
1482        odin: Is("request_frame_at"),
1483        node: Is("requestFrameAt"),
1484        lua: Is("request_frame_at"),
1485        doc: "Asks for a frame at a time on the frame clock — a toast's expiry, a sequence's beat — with nothing owed until then.",
1486    },
1487    Door {
1488        rust: "Core::next_frame_at",
1489        c: Is("kui_next_frame_at"),
1490        odin: Is("next_frame_at"),
1491        node: Is("nextFrameAt"),
1492        lua: No(GUEST),
1493        doc: "The time a driver should next draw at for a frame asked at a time; infinity in C and Odin, null in Node, for none.",
1494    },
1495    Door {
1496        rust: "Ui::now",
1497        c: Is("kui_now"),
1498        odin: Is("now"),
1499        node: Is("now"),
1500        lua: As("`env.now`, a reading"),
1501        doc: "The frame clock in seconds, the one the tweens read: what a view times its own deadlines by.",
1502    },
1503    Door {
1504        rust: "Core::env",
1505        c: As("`kui_env_set` and its four siblings, `ENV_FIELDS`' C column"),
1506        odin: As("`env_set` and its four siblings"),
1507        node: Is("Ctx.setEnv"),
1508        lua: No(GUEST),
1509        doc: "The host facts written in (the `env` field); a `KuiWindow`'s runner writes its own.",
1510    },
1511    Door {
1512        rust: "Ui::env",
1513        c: No("C is the host, so it writes the facts and has no reading (`ENV_FIELDS`)"),
1514        odin: No(ODIN_IS_C),
1515        node: Is("env"),
1516        lua: As("`env`, the view's argument"),
1517        doc: "The facts read back, `ENV_FIELDS` row for row.",
1518    },
1519    Door {
1520        rust: "Core::handle_input",
1521        c: As("`kui_input_cursor` … `kui_input_access`, one per `InputEvent`"),
1522        odin: As(
1523            "`input_cursor` … `input_access`, one per input event; `click` and `press` for a drive",
1524        ),
1525        node: As(
1526            "`Ctx.cursor` … `Ctx.access`, one per `InputEvent`; a `KuiWindow` refuses injection",
1527        ),
1528        lua: No(GUEST),
1529        doc: "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).",
1530    },
1531    Door {
1532        rust: "Core::modifiers",
1533        c: Is("kui_input_modifiers"),
1534        odin: Is("input_modifiers"),
1535        node: Is("Ctx.modifiers"),
1536        lua: No(GUEST),
1537        doc: "The modifier state, reported on its own when the OS does (backlog AR22).",
1538    },
1539    Door {
1540        rust: "Core::release_held_keys",
1541        c: Is("kui_release_held_keys"),
1542        odin: Is("release_held_keys"),
1543        node: As(
1544            "`Ctx.setEnv({focused: false})` releases, as losing the keyboard does for every driver (ADR 0020)",
1545        ),
1546        lua: No(GUEST),
1547        doc: "Lets go of every key the focused sink holds.",
1548    },
1549    Door {
1550        rust: "Core::set_subpixel_text",
1551        c: Is("kui_set_subpixel_text"),
1552        odin: Is("set_subpixel_text"),
1553        node: No(NEVER_PAINTS),
1554        lua: No(GUEST),
1555        doc: "LCD subpixel coverage for outline glyphs, for a renderer that blends per channel.",
1556    },
1557    Door {
1558        rust: "Core::take_audio_commands",
1559        c: Is("kui_take_audio_commands"),
1560        odin: Is("take_audio_commands"),
1561        node: Is("Ctx.audioCommands"),
1562        lua: No(GUEST),
1563        doc: "Drains what the frame asked of the audio device; a `KuiWindow`'s device is the runner's.",
1564    },
1565    Door {
1566        rust: "Core::audio_ended",
1567        c: Is("kui_audio_ended"),
1568        odin: Is("audio_ended"),
1569        node: Is("Ctx.audioEnded"),
1570        lua: No(GUEST),
1571        doc: "The device reports a playback over.",
1572    },
1573    Door {
1574        rust: "Core::audio_truncated",
1575        c: Is("kui_audio_truncated"),
1576        odin: Is("audio_truncated"),
1577        node: Is("Ctx.audioTruncated"),
1578        lua: No(GUEST),
1579        doc: "The device reports a stop that cut a playback short — a one-shot node's removal becomes `truncated-playback`.",
1580    },
1581    Door {
1582        rust: "Core::audio_refused",
1583        c: Is("kui_audio_refused"),
1584        odin: Is("audio_refused"),
1585        node: Is("Ctx.audioRefused"),
1586        lua: No(GUEST),
1587        doc: "The device reports a play it would not take — a `refused` sound event and `playback-refused`.",
1588    },
1589    // -- The runner's options (`Launcher` in Rust, `WindowOptions` in Node,
1590    // `kui_run_with` in C). One row for the set, since they are one
1591    // decision: what a window opens as.
1592    Door {
1593        rust: "Launcher::size",
1594        c: As("`width` / `height` in the `KuiRunConfig` `kui_run_with` takes"),
1595        odin: As("`width` / `height` in the `Run_Config` `kui.run` takes"),
1596        node: As("`width` / `height` in `WindowOptions`"),
1597        lua: No(GUEST),
1598        doc: "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.",
1599    },
1600    Door {
1601        rust: "Launcher::icon",
1602        c: Is("kui_set_icon"),
1603        odin: Is("set_icon"),
1604        node: As("`icon` in `WindowOptions`"),
1605        lua: No(GUEST),
1606        doc: "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.",
1607    },
1608    Door {
1609        rust: "Launcher::icon_bytes",
1610        c: As("`kui_decode_image`, then `kui_set_icon`"),
1611        odin: As("`decode_image`, then `set_icon`"),
1612        node: As("`kui.decodeImage`, then `icon` in `WindowOptions`"),
1613        lua: No(GUEST),
1614        doc: "The icon from an image file's bytes, a PNG most often, through the runner's decoder.",
1615    },
1616    Door {
1617        rust: "kui_native::decode_image",
1618        c: Is("kui_decode_image"),
1619        odin: Is("decode_image"),
1620        node: Is("kui.decodeImage"),
1621        lua: No(GUEST),
1622        doc: "PNG, JPEG, WebP or GIF bytes to straight RGBA with the runner's decoder, the shape `add_image` takes; C's frees with `kui_pixels_free`.",
1623    },
1624    Door {
1625        rust: "kui_native::decode_animation",
1626        c: Is("kui_decode_animation"),
1627        odin: Is("decode_animation"),
1628        node: Is("kui.decodeAnimation"),
1629        lua: No(GUEST),
1630        doc: "Every frame of an animated GIF, APNG or WebP, each the whole canvas, with the seconds each shows and the loop count.",
1631    },
1632    Door {
1633        rust: "Animation::at",
1634        c: Is("kui_animation_at"),
1635        odin: Is("animation_at"),
1636        node: Is("kui.animationAt"),
1637        lua: No(GUEST),
1638        doc: "Which frame of an animation a moment shows and when the next is due: what `update_image` and `request_frame_at` take to play a GIF on the frame clock.",
1639    },
1640    Door {
1641        rust: "App::teardown",
1642        c: Is("kui_on_teardown"),
1643        odin: As("the `teardown` procedure `kui.run` takes"),
1644        node: Is("KuiWindow.onTeardown"),
1645        lua: No(GUEST),
1646        doc: "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.",
1647    },
1648];
1649
1650#[cfg(test)]
1651mod tests {
1652    use super::*;
1653
1654    /// A row's Rust spelling is a `pub fn` in the file its prefix names —
1655    /// `Ui::` in `ui.rs`, `Core::` under `runtime/`, `SharedResources::`
1656    /// in `session.rs`, `Tokens::` in `tokens.rs`, `Launcher::` in the
1657    /// `kui-native` crate, `kui_native::` and `Animation::` in its
1658    /// `decode.rs`, and `App::` a method of that crate's `App` trait (a
1659    /// trait's `fn` is public without the word) — so a renamed or
1660    /// removed verb is a red row and not a stale one, which is the pin
1661    /// Rust's column can have without reflection.
1662    #[test]
1663    fn every_rust_spelling_is_a_public_fn() {
1664        let root = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("src");
1665        let read = |p: std::path::PathBuf| std::fs::read_to_string(&p).unwrap_or_default();
1666        let mut runtime = read(root.join("runtime.rs"));
1667        for dir in ["runtime", "runtime/devtools"] {
1668            for entry in std::fs::read_dir(root.join(dir)).unwrap() {
1669                let p = entry.unwrap().path();
1670                if p.extension().is_some_and(|e| e == "rs") {
1671                    runtime.push_str(&read(p));
1672                }
1673            }
1674        }
1675        let ui = read(root.join("ui.rs"));
1676        let session = read(root.join("session.rs"));
1677        let tokens = read(root.join("tokens.rs"));
1678        let launcher = read(root.join("../../kui-native/src/lib.rs"));
1679        let decode = read(root.join("../../kui-native/src/decode.rs"));
1680        // The `App` trait's body: a method of it is a callback the app
1681        // writes, spelled `fn name(` and public by being the trait's.
1682        let app_trait = launcher
1683            .split_once("pub trait App {")
1684            .map(|(_, rest)| rest.split_once("\n}\n").map_or(rest, |(body, _)| body))
1685            .unwrap_or_default();
1686        for d in DOORS {
1687            let (ty, name) = d.rust.split_once("::").expect(d.rust);
1688            let src = match ty {
1689                "Ui" => &ui,
1690                "Core" => &runtime,
1691                "SharedResources" => &session,
1692                "Tokens" => &tokens,
1693                "Launcher" => &launcher,
1694                "kui_native" | "Animation" => &decode,
1695                "App" => app_trait,
1696                other => panic!("{}: {other} is not a prefix the table knows", d.rust),
1697            };
1698            let (public, any) = (
1699                format!("{}fn {name}(", if ty == "App" { "" } else { "pub " }),
1700                format!("fn {name}("),
1701            );
1702            assert!(
1703                src.contains(&public) || (ty == "Core" && name == "env"),
1704                "{}: no `{public}` in {ty}'s sources{}",
1705                d.rust,
1706                if src.contains(&any) {
1707                    " (a private fn is)"
1708                } else {
1709                    ""
1710                }
1711            );
1712        }
1713    }
1714
1715    /// The table is one row per verb, and a `No` says why in a sentence
1716    /// rather than in a word — the reasons are what ADR 0020 said a table
1717    /// would be made of, and the point of building one.
1718    #[test]
1719    fn rows_are_unique_and_every_no_has_a_reason() {
1720        let mut seen = std::collections::BTreeSet::new();
1721        for d in DOORS {
1722            assert!(seen.insert(d.rust), "{} is two rows", d.rust);
1723            assert!(!d.doc.is_empty(), "{} has no doc", d.rust);
1724            for (binding, cell) in [("C", d.c), ("Node", d.node), ("Lua", d.lua)] {
1725                match cell {
1726                    Is(name) => assert!(
1727                        !name.is_empty() && !name.contains(' '),
1728                        "{} in {binding}: {name:?} is not a name",
1729                        d.rust
1730                    ),
1731                    As(how) | No(how) => assert!(
1732                        how.len() >= 12,
1733                        "{} in {binding}: {how:?} is not a reason",
1734                        d.rust
1735                    ),
1736                }
1737            }
1738        }
1739    }
1740
1741    /// The C column's spellings are the header's: `kui_` and snake case.
1742    /// Node's are camelCase, optionally under one of the two classes;
1743    /// Lua's snake case. A cell in the wrong column's spelling is a pasted
1744    /// row.
1745    #[test]
1746    fn cells_are_spelled_in_their_bindings_case() {
1747        let snake = |s: &str| {
1748            s.bytes()
1749                .all(|b| b.is_ascii_lowercase() || b == b'_' || b.is_ascii_digit())
1750        };
1751        for d in DOORS {
1752            if let Is(c) = d.c {
1753                assert!(
1754                    c.starts_with("kui_") && snake(c),
1755                    "{}: C cell {c:?}",
1756                    d.rust
1757                );
1758            }
1759            if let Is(n) = d.node {
1760                let n = n
1761                    .strip_prefix("Ctx.")
1762                    .or_else(|| n.strip_prefix("KuiWindow."))
1763                    .unwrap_or(n);
1764                assert!(
1765                    !n.contains('_') && n.starts_with(|c: char| c.is_ascii_lowercase()),
1766                    "{}: Node cell {n:?}",
1767                    d.rust
1768                );
1769            }
1770            if let Is(l) = d.lua {
1771                assert!(snake(l), "{}: Lua cell {l:?}", d.rust);
1772            }
1773        }
1774    }
1775}